diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64ec97f..4001517 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,4 +34,4 @@ jobs: - name: Test with meson shell: micromamba-shell {0} run: | - meson test -C build \ No newline at end of file + meson test -C build --verbose \ No newline at end of file diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..0cbc6ba --- /dev/null +++ b/.gitmessage @@ -0,0 +1,19 @@ +# Title: Summary, imperative, start upper case, don't end with a period +# No more than 50 chars. #### 50 chars is here: # + +# Remember blank line between title and body. + +# Body: Explain *what* and *why* (not *how*). +# Wrap at 72 chars. ################################## which is here: # + +# At the end: Include Co-authored-by for all contributors. +# Include at least one empty line before it. Format: +# Co-authored-by: name + +# 1. Separate subject from body with a blank line +# 2. Limit the subject line to 50 characters +# 3. Capitalize the subject line +# 4. Do not end the subject line with a period +# 5. Use the imperative mood in the subject line +# 6. Wrap the body at 72 characters +# 7. Use the body to explain what and why vs. how diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d61fa1..3bdd18e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,3 +52,12 @@ A good commit should have: + +### Commit template +If you are not already using a commit template, consider doing so. The Seldon repository includes a template under `.gitmessage`. + +``` +git config --global commit.template .gitmessage +``` + +You can omit the `--global` to only use the template when committing to Seldon. \ No newline at end of file diff --git a/examples/ActivityDriven/conf.toml b/examples/ActivityDriven/conf.toml index e8f1c6b..2d95b48 100644 --- a/examples/ActivityDriven/conf.toml +++ b/examples/ActivityDriven/conf.toml @@ -6,6 +6,8 @@ model = "ActivityDriven" n_output_network = 20 # Write the network every 20 iterations n_output_agents = 1 # Write the opinions of agents after every iteration print_progress = true # Print the iteration time ; if not set, then always print +print_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 2 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. [model] max_iterations = 500 # If not set, max iterations is infinite diff --git a/examples/ActivityDrivenBot/conf.toml b/examples/ActivityDrivenBot/conf.toml index 0ee31f2..f9c18eb 100644 --- a/examples/ActivityDrivenBot/conf.toml +++ b/examples/ActivityDrivenBot/conf.toml @@ -6,6 +6,8 @@ rng_seed = 120 # Leaving this empty will pick a random seed n_output_network = 1 # Write the network every 20 iterations n_output_agents = 1 # Write the opinions of agents after every iteration print_progress = true # Print the iteration time; if not set, then always print +print_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. [model] max_iterations = 1000 # If not set, max iterations is infinite diff --git a/examples/ActivityDrivenMeanField/conf.toml b/examples/ActivityDrivenMeanField/conf.toml index 734518b..aaf5279 100644 --- a/examples/ActivityDrivenMeanField/conf.toml +++ b/examples/ActivityDrivenMeanField/conf.toml @@ -6,6 +6,8 @@ rng_seed = 12345678 # Leaving this empty will pick a random seed n_output_network = 20 # Write the network every 20 iterations n_output_agents = 1 # Write the opinions of agents after every iteration print_progress = true # Print the iteration time ; if not set, then always print +print_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. [model] max_iterations = 2000 # If not set, max iterations is infinite diff --git a/examples/ActivityDrivenReluctance/conf.toml b/examples/ActivityDrivenReluctance/conf.toml new file mode 100644 index 0000000..7ba5655 --- /dev/null +++ b/examples/ActivityDrivenReluctance/conf.toml @@ -0,0 +1,32 @@ +[simulation] +model = "ActivityDriven" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +n_output_network = 20 # Write the network every 20 iterations +n_output_agents = 1 # Write the opinions of agents after every iteration +print_progress = true # Print the iteration time ; if not set, then always print + +[model] +max_iterations = 500 # If not set, max iterations is infinite + +[ActivityDriven] +dt = 0.01 # Timestep for the integration of the coupled ODEs +m = 10 # Number of agents contacted, when the agent is active +eps = 0.01 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 0.65 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 1.0 # aka beta. if zero, agents pick their interaction partners at random +alpha = 3.0 # Controversialness of the issue, must be greater than 0. +K = 2.0 +mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = false # Use the meanfield approximation of the network edges + +reluctances = true # Assigns a "reluctance" (m_i) to each agent. By default; false and every agent has a reluctance of 1 +reluctance_mean = 1.0 # Mean of distribution before drawing from a truncated normal distribution (default set to 1.0) +reluctance_sigma = 0.25 # Width of normal distribution (before truncating) +reluctance_eps = 0.01 # Minimum such that the normal distribution is truncated at this value + +[network] +number_of_agents = 1000 +connections_per_agent = 10 diff --git a/examples/DeGroot/conf.toml b/examples/DeGroot/conf.toml index 8395673..3d06f0e 100644 --- a/examples/DeGroot/conf.toml +++ b/examples/DeGroot/conf.toml @@ -6,6 +6,8 @@ model = "DeGroot" n_output_network = 20 # Write the network every 20 iterations n_output_agents = 1 # Write the opinions of agents after every iteration print_progress = false # Print the iteration time ; if not set, then always prints +print_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. [model] max_iterations = 20 # If not set, max iterations is infinite diff --git a/include/agent.hpp b/include/agent.hpp index 92613ac..bf200ef 100644 --- a/include/agent.hpp +++ b/include/agent.hpp @@ -1,5 +1,4 @@ #pragma once -#include "agent_base.hpp" #include namespace Seldon { @@ -8,17 +7,18 @@ namespace Seldon (which contains an opinion and perhaps some other things), it needs to implement to_string and from_string*/ template -class Agent : public AgentBase +class Agent { public: using data_t = T; data_t data; Agent() = default; Agent( data_t data ) : data( data ) {} + virtual ~Agent() = default; void from_string( const std::string & str ); - std::string to_string() const override + virtual std::string to_string() const { return fmt::format( "{:.16f}", data ); } diff --git a/include/agent_base.hpp b/include/agent_base.hpp deleted file mode 100644 index 88c23b0..0000000 --- a/include/agent_base.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -namespace Seldon -{ - -// AGENTS -/* Agent Base is the abstract interface, for every part of the code that is not the actual computation, IO etc. */ -class AgentBase -{ -public: - virtual std::string to_string() const = 0; - virtual ~AgentBase() = default; - // TODO: eventually a from_string might also be needed - // virtual void from_string() = 0; -}; - -} // namespace Seldon \ No newline at end of file diff --git a/include/agent_generation.hpp b/include/agent_generation.hpp index 373d3f1..b7d129a 100644 --- a/include/agent_generation.hpp +++ b/include/agent_generation.hpp @@ -26,7 +26,7 @@ std::vector generate_from_file( const std::string & file ) // Get the current line as a substring auto line = file_contents.substr( start_of_line, end_of_line - start_of_line ); start_of_line = end_of_line + 1; - // TODO: check if empty or comment + if( line.empty() ) { break; diff --git a/include/config_parser.hpp b/include/config_parser.hpp index d2dfefe..a420989 100644 --- a/include/config_parser.hpp +++ b/include/config_parser.hpp @@ -32,6 +32,8 @@ struct OutputSettings std::optional n_output_agents = std::nullopt; std::optional n_output_network = std::nullopt; bool print_progress = true; // Print the iteration time, by default always prints + bool print_initial = true; // Output initial opinions and network, by default always outputs. + int start_output = 1; // Start printing opinion and/or network files from this iteration number }; struct DeGrootSettings @@ -58,6 +60,11 @@ struct ActivityDrivenSettings std::vector bot_activity = std::vector( 0 ); std::vector bot_opinion = std::vector( 0 ); std::vector bot_homophily = std::vector( 0 ); + bool use_reluctances = false; + double reluctance_mean = 1.0; + double reluctance_sigma = 0.25; + double reluctance_eps = 0.01; + double covariance_factor = 0.0; }; struct InitialNetworkSettings diff --git a/include/model.hpp b/include/model.hpp index 13dfc3b..e13f663 100644 --- a/include/model.hpp +++ b/include/model.hpp @@ -1,5 +1,5 @@ #pragma once -#include "model_base.hpp" +#include #include namespace Seldon @@ -8,26 +8,33 @@ namespace Seldon /* Model is a base class from which the acutal models would derive. They have efficient access to a vector of AgentT, * without any pointer indirections */ template -class Model : public ModelBase +class Model { public: using AgentT = AgentT_; - std::vector agents; - std::optional max_iterations = std::nullopt; - Model( size_t n_agents ) : agents( std::vector( int( n_agents ), AgentT() ) ) {} - Model( std::vector && agents ) : agents( agents ) {} + std::optional max_iterations = std::nullopt; - void iteration() override + virtual void initialize_iterations() { - n_iterations++; + _n_iterations = 0; + } + + virtual void iteration() + { + _n_iterations++; }; - bool finished() override + size_t n_iterations() + { + return _n_iterations; + } + + virtual bool finished() { if( max_iterations.has_value() ) { - return max_iterations.value() <= n_iterations; + return max_iterations.value() <= n_iterations(); } else { @@ -35,10 +42,10 @@ class Model : public ModelBase } }; - AgentBase * get_agent( int idx ) override // For this to work AgentT needs to be a subclass of AgentBase - { - return &agents[idx]; - } + virtual ~Model() = default; + +private: + size_t _n_iterations{}; }; } // namespace Seldon \ No newline at end of file diff --git a/include/model_base.hpp b/include/model_base.hpp deleted file mode 100644 index 0a096c8..0000000 --- a/include/model_base.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "agent.hpp" -#include -#include - -#pragma once - -namespace Seldon -{ - -// The abstract model interface -class ModelBase -{ -public: - int n_iterations = 0; - virtual AgentBase * get_agent( int idx ) = 0; // Use this to get an abstract representation of the agent at idx - - template - AgentT * get_agent_as( int idx ) - { - return dynamic_cast( get_agent( idx ) ); - } - - virtual void iteration() = 0; - virtual bool finished() = 0; - virtual ~ModelBase() = default; -}; - -} // namespace Seldon \ No newline at end of file diff --git a/include/models/ActivityDrivenModel.hpp b/include/models/ActivityDrivenModel.hpp index ab578a4..c642fa6 100644 --- a/include/models/ActivityDrivenModel.hpp +++ b/include/models/ActivityDrivenModel.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -15,32 +15,49 @@ namespace Seldon struct ActivityAgentData { - double opinion = 0; // x_i - double activity = 0; // a_i + double opinion = 0; // x_i + double activity = 0; // a_i + double reluctance = 1.0; // m_i }; template<> inline std::string Agent::to_string() const { - return fmt::format( "{}, {}", data.opinion, data.activity ); + return fmt::format( "{}, {}, {}", data.opinion, data.activity, data.reluctance ); }; template<> inline void Agent::from_string( const std::string & str ) { - auto pos_comma = str.find_first_of( ',' ); - data.opinion = std::stod( str.substr( 0, pos_comma ) ); - data.activity = std::stod( str.substr( pos_comma + 1, str.size() ) ); + auto pos_comma = str.find_first_of( ',' ); + auto pos_next_comma = str.find( ',', pos_comma + 1 ); + + data.opinion = std::stod( str.substr( 0, pos_comma ) ); + + if( pos_next_comma == std::string::npos ) + { + data.activity = std::stod( str.substr( pos_comma + 1, str.size() ) ); + } + else + { + data.activity = std::stod( str.substr( pos_comma + 1, pos_next_comma ) ); + } + + if( pos_next_comma != std::string::npos ) + { + data.reluctance = std::stod( str.substr( pos_next_comma + 1, str.size() ) ); + } }; -class ActivityAgentModel : public Model> +class ActivityDrivenModel : public Model> { public: - using AgentT = Agent; + using AgentT = Agent; + using NetworkT = Network; private: - Network & network; - std::vector> contact_prob_list; // Probability of choosing i in 1 to m rounds + NetworkT & network; + std::vector> contact_prob_list; // Probability of choosing i in 1 to m rounds // Random number generation std::mt19937 & gen; // reference to simulation Mersenne-Twister engine std::set> reciprocal_edge_buffer{}; @@ -68,7 +85,8 @@ class ActivityAgentModel : public Model> for( size_t j = 0; j < neighbour_buffer.size(); j++ ) { j_index = neighbour_buffer[j]; - k_buffer[idx_agent] += K * weight_buffer[j] * std::tanh( alpha * opinion( j_index ) ); + k_buffer[idx_agent] += 1.0 / network.agents[idx_agent].data.reluctance * K * weight_buffer[j] + * std::tanh( alpha * opinion( j_index ) ); } // Multiply by the timestep k_buffer[idx_agent] *= dt; @@ -100,6 +118,12 @@ class ActivityAgentModel : public Model> double convergence_tol = 1e-12; // TODO: ?? + bool use_reluctances = false; + double reluctance_mean = 1.0; + double reluctance_sigma = 0.25; + double reluctance_eps = 0.01; + double covariance_factor = 0.0; + // bot @TODO: less hacky size_t n_bots = 0; // The first n_bots agents are bots @@ -113,14 +137,11 @@ class ActivityAgentModel : public Model> return n_bots > 0; } - ActivityAgentModel( int n_agents, Network & network, std::mt19937 & gen ); + ActivityDrivenModel( NetworkT & network, std::mt19937 & gen ); void get_agents_from_power_law(); // This needs to be called after eps and gamma have been set void iteration() override; - - // bool finished() overteration() override; - // bool finished() override; }; } // namespace Seldon \ No newline at end of file diff --git a/include/models/DeGroot.hpp b/include/models/DeGroot.hpp index c763a60..b7a9f0a 100644 --- a/include/models/DeGroot.hpp +++ b/include/models/DeGroot.hpp @@ -1,4 +1,5 @@ #pragma once +#include "agent.hpp" #include "model.hpp" #include "network.hpp" #include @@ -8,19 +9,20 @@ namespace Seldon class DeGrootModel : public Model> { - -private: - double max_opinion_diff = 0; - Network & network; - std::vector agents_current_copy; - public: + using AgentT = Agent; + using NetworkT = Network; double convergence_tol = 1e-12; - DeGrootModel( int n_agents, Network & network ); + DeGrootModel( NetworkT & network ); void iteration() override; bool finished() override; + +private: + double max_opinion_diff = 0; + NetworkT & network; + std::vector agents_current_copy; }; } // namespace Seldon \ No newline at end of file diff --git a/include/network.hpp b/include/network.hpp index f6cbc33..997ad70 100644 --- a/include/network.hpp +++ b/include/network.hpp @@ -1,7 +1,12 @@ #pragma once +#include "connectivity.hpp" +#include +#include #include +#include #include #include +#include #include namespace Seldon @@ -22,6 +27,7 @@ namespace Seldon Note: switch is equivalent to toggle + transpose, but much cheaper! */ +template class Network { public: @@ -31,85 +37,196 @@ class Network Outgoing }; - using WeightT = double; + using WeightT = WeightType; + using AgentT = AgentType; + // @TODO: Make this private later + std::vector agents{}; // List of agents of type AgentType + + Network() = default; Network( std::vector> && neighbour_list, std::vector> && weight_list, - EdgeDirection direction ); + EdgeDirection direction ) + : agents( std::vector( neighbour_list.size() ) ), + neighbour_list( neighbour_list ), + weight_list( weight_list ), + _direction( direction ) + { + } /* Gives the total number of nodes in the network */ - [[nodiscard]] std::size_t n_agents() const; + [[nodiscard]] std::size_t n_agents() const + { + return neighbour_list.size(); + } /* Gives the number of edges going out/coming in at agent_idx, depending on the value of direction(). If agent_idx is nullopt, gives the total number of edges */ - [[nodiscard]] std::size_t n_edges( std::optional agent_idx = std::nullopt ) const; + [[nodiscard]] std::size_t n_edges( std::optional agent_idx = std::nullopt ) const + { + if( agent_idx.has_value() ) + { + return neighbour_list[agent_idx.value()].size(); + } + else + { + return std::transform_reduce( + neighbour_list.cbegin(), neighbour_list.cend(), 0, std::plus{}, + []( const auto & neigh_list ) { return neigh_list.size(); } ); + } + } /* Returns the current directionality of the adjacency list. That is if incoming or outgoing edges are stored. */ - [[nodiscard]] const EdgeDirection & direction() const; + [[nodiscard]] const EdgeDirection & direction() const + { + return _direction; + } /* Gives the strongly connected components in the graph */ - [[nodiscard]] std::vector> strongly_connected_components() const; + [[nodiscard]] std::vector> strongly_connected_components() const + { + // Now that we have the neighbour list (or adjacency list) + // Run Tarjan's algorithm for strongly connected components + auto tarjan_scc = TarjanConnectivityAlgo( neighbour_list ); + return tarjan_scc.scc_list; + } /* - Gives a view into the neighbour indices going out/coming in at agent_idx + Gives a view into the neighbour indices going out/coming in at agent_idx */ - [[nodiscard]] std::span get_neighbours( std::size_t agent_idx ) const; - [[nodiscard]] std::span get_neighbours( std::size_t agent_idx ); + [[nodiscard]] std::span get_neighbours( std::size_t agent_idx ) const + { + return std::span( neighbour_list[agent_idx].data(), neighbour_list[agent_idx].size() ); + } + + [[nodiscard]] std::span get_neighbours( std::size_t agent_idx ) + { + return std::span( neighbour_list[agent_idx].data(), neighbour_list[agent_idx].size() ); + } /* Gives a view into the edge weights going out/coming in at agent_idx */ - [[nodiscard]] std::span get_weights( std::size_t agent_idx ) const; - [[nodiscard]] std::span get_weights( std::size_t agent_idx ); + [[nodiscard]] std::span get_weights( std::size_t agent_idx ) const + { + return std::span( weight_list[agent_idx].data(), weight_list[agent_idx].size() ); + } + + [[nodiscard]] std::span get_weights( std::size_t agent_idx ) + { + return std::span( weight_list[agent_idx].data(), weight_list[agent_idx].size() ); + } /* Gives a view into the edge weights going out/coming in at agent_idx */ - void set_weights( std::size_t agent_idx, std::span weights ); + void set_weights( std::size_t agent_idx, const std::span weights ) + { + if( neighbour_list[agent_idx].size() != weights.size() ) + { + throw std::runtime_error( "Network::set_weights: tried to set weights of the wrong size!" ); + } + weight_list[agent_idx].assign( weights.begin(), weights.end() ); + } /* Sets the neighbour indices and sets the weight to a constant value at agent_idx */ void set_neighbours_and_weights( - std::size_t agent_idx, std::span buffer_neighbours, const WeightT & weight ); + std::size_t agent_idx, std::span buffer_neighbours, const WeightT & weight ) + { + neighbour_list[agent_idx].assign( buffer_neighbours.begin(), buffer_neighbours.end() ); + weight_list[agent_idx].resize( buffer_neighbours.size() ); + std::fill( weight_list[agent_idx].begin(), weight_list[agent_idx].end(), weight ); + } /* Sets the neighbour indices and weights at agent_idx */ void set_neighbours_and_weights( - std::size_t agent_idx, std::span buffer_neighbours, std::span buffer_weights ); + std::size_t agent_idx, std::span buffer_neighbours, std::span buffer_weights ) + { + if( buffer_neighbours.size() != buffer_weights.size() ) + { + throw std::runtime_error( + "Network::set_neighbours_and_weights: both buffers need to have the same length!" ); + } + + neighbour_list[agent_idx].assign( buffer_neighbours.begin(), buffer_neighbours.end() ); + weight_list[agent_idx].assign( buffer_weights.begin(), buffer_weights.end() ); + } /* Adds an edge between agent_idx_i and agent_idx_j with weight w */ - void push_back_neighbour_and_weight( size_t agent_idx_i, size_t agent_idx_j, WeightT w ); + void push_back_neighbour_and_weight( size_t agent_idx_i, size_t agent_idx_j, WeightT w ) + { + neighbour_list[agent_idx_i].push_back( agent_idx_j ); + weight_list[agent_idx_i].push_back( w ); + } /* Transposes the network, without switching the direction flag (expensive). Example: N(inc) -> N(inc)^T */ - void transpose(); + void transpose() + { + toggle_incoming_outgoing(); + switch_direction_flag(); + } /* Switches the direction flag *without* transposing the network (expensive) Example: N(inc) -> N(out) */ - void toggle_incoming_outgoing(); + void toggle_incoming_outgoing() + { + std::vector> neighbour_list_transpose( n_agents(), std::vector( 0 ) ); + std::vector> weight_list_transpose( n_agents(), std::vector( 0 ) ); + + for( size_t i_agent = 0; i_agent < n_agents(); i_agent++ ) + { + for( size_t i_neighbour = 0; i_neighbour < neighbour_list[i_agent].size(); i_neighbour++ ) + { + const auto neighbour = neighbour_list[i_agent][i_neighbour]; + const auto weight = weight_list[i_agent][i_neighbour]; + neighbour_list_transpose[neighbour].push_back( i_agent ); + weight_list_transpose[neighbour].push_back( weight ); + } + } + + neighbour_list = std::move( neighbour_list_transpose ); + weight_list = std::move( weight_list_transpose ); + + // Swap the edge direction + switch_direction_flag(); + } /* Only switches the direction flag. This effectively transposes the network and, simultaneously, changes its representation. Example: N(inc) -> N^T(out) */ - void switch_direction_flag(); + void switch_direction_flag() + { + // Swap the edge direction + if( direction() == EdgeDirection::Incoming ) + { + _direction = EdgeDirection::Outgoing; + } + else + { + _direction = EdgeDirection::Incoming; + } + } private: std::vector> neighbour_list; // Neighbour list for the connections diff --git a/include/network_generation.hpp b/include/network_generation.hpp index df93567..9025785 100644 --- a/include/network_generation.hpp +++ b/include/network_generation.hpp @@ -1,17 +1,237 @@ #pragma once #include "network.hpp" #include -#include #include +#include +#include namespace Seldon::NetworkGeneration { /* Constructs a new network with n_connections per agent If self_interaction=true, a connection of the agent with itself is included, which is *not* counted in n_connections */ -std::unique_ptr -generate_n_connections( size_t n_agents, size_t n_connections, bool self_interaction, std::mt19937 & gen ); -std::unique_ptr generate_fully_connected( size_t n_agents, Network::WeightT weight = 0.0 ); -std::unique_ptr generate_fully_connected( size_t n_agents, std::mt19937 & gen ); -std::unique_ptr generate_from_file( const std::string & file ); +template +Network +generate_n_connections( size_t n_agents, size_t n_connections, bool self_interaction, std::mt19937 & gen ) +{ + using NetworkT = Network; + using WeightT = typename NetworkT::WeightT; + + std::vector> neighbour_list; // Neighbour list for the connections + std::vector> weight_list; // List for the interaction weights of each connection + std::uniform_real_distribution<> dis( 0.0, 1.0 ); // Values don't matter, will be normalized + auto incoming_neighbour_buffer + = std::vector(); // for the j_agents indices connected to i_agent (adjacencies/neighbours) + auto incoming_neighbour_weights = std::vector(); // Vector of weights of the j neighbours of i + WeightT outgoing_norm_weight = 0; + + // Loop through all the agents and create the neighbour_list and weight_list + for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) + { + outgoing_norm_weight = 0.0; + + incoming_neighbour_buffer.clear(); + incoming_neighbour_weights.clear(); + + // Get the vector of sorted adjacencies, excluding i (include i later) + // TODO: option for making the n_connections variable + draw_unique_k_from_n( i_agent, n_connections, n_agents, incoming_neighbour_buffer, gen ); + + incoming_neighbour_weights.resize( incoming_neighbour_buffer.size() ); + for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) + { + incoming_neighbour_weights[j] = dis( gen ); // Draw the weight + outgoing_norm_weight += incoming_neighbour_weights[j]; + } + + if( self_interaction ) + { + // Put the self-interaction as the last entry + auto self_interaction_weight = dis( gen ); + outgoing_norm_weight += self_interaction_weight; + // outgoing_norm_weights += self_interaction_weight; + incoming_neighbour_buffer.push_back( i_agent ); // Add the agent itself + incoming_neighbour_weights.push_back( self_interaction_weight ); + } + + // --------- + // Normalize the weights so that the row sums to 1 + // Might be specific to the DeGroot model? + for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) + { + incoming_neighbour_weights[j] /= outgoing_norm_weight; + } + + // Add the neighbour vector for i_agent to the neighbour list + neighbour_list.push_back( incoming_neighbour_buffer ); + // Add the weight interactions for the neighbours of i_agent + weight_list.push_back( incoming_neighbour_weights ); + + } // end of loop through n_agents + + return NetworkT( std::move( neighbour_list ), std::move( weight_list ), NetworkT::EdgeDirection::Incoming ); +} + +template +Network generate_fully_connected( size_t n_agents, typename Network::WeightT weight = 0.0 ) +{ + using NetworkT = Network; + using WeightT = typename NetworkT::WeightT; + + std::vector> neighbour_list; // Neighbour list for the connections + std::vector> weight_list; // List for the interaction weights of each connection + auto incoming_neighbour_buffer + = std::vector( n_agents ); // for the j_agents indices connected to i_agent (adjacencies/neighbours) + auto incoming_neighbour_weights + = std::vector( n_agents, weight ); // Vector of weights of the j neighbours of i + + // Create the incoming_neighbour_buffer once. This will contain all agents, including itself + for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) + { + incoming_neighbour_buffer[i_agent] = i_agent; + } + + // Loop through all the agents and update the neighbour_list and weight_list + for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) + { + // Add the neighbour vector for i_agent to the neighbour list + neighbour_list.push_back( incoming_neighbour_buffer ); + // Add the weight interactions for the neighbours of i_agent + weight_list.push_back( incoming_neighbour_weights ); + + } // end of loop through n_agents + + return NetworkT( std::move( neighbour_list ), std::move( weight_list ), NetworkT::EdgeDirection::Incoming ); +} + +template +Network generate_fully_connected( size_t n_agents, std::mt19937 & gen ) +{ + using NetworkT = Network; + using WeightT = typename NetworkT::WeightT; + + std::vector> neighbour_list; // Neighbour list for the connections + std::vector> weight_list; // List for the interaction weights of each connection + auto incoming_neighbour_buffer + = std::vector( n_agents ); // for the j_agents indices connected to i_agent (adjacencies/neighbours) + std::uniform_real_distribution<> dis( 0.0, 1.0 ); // Values don't matter, will be normalized + auto incoming_neighbour_weights = std::vector( n_agents ); // Vector of weights of the j neighbours of i + WeightT outgoing_norm_weight = 0; + + // Create the incoming_neighbour_buffer once. This will contain all agents, including itself + for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) + { + incoming_neighbour_buffer[i_agent] = i_agent; + } + + // Loop through all the agents and create the neighbour_list and weight_list + for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) + { + + outgoing_norm_weight = 0.0; + + // Initialize the weights + for( size_t j = 0; j < n_agents; ++j ) + { + incoming_neighbour_weights[j] = dis( gen ); // Draw the weight + outgoing_norm_weight += incoming_neighbour_weights[j]; + } + + // --------- + // Normalize the weights so that the row sums to 1 + // Might be specific to the DeGroot model? + for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) + { + incoming_neighbour_weights[j] /= outgoing_norm_weight; + } + + // Add the neighbour vector for i_agent to the neighbour list + neighbour_list.push_back( incoming_neighbour_buffer ); + // Add the weight interactions for the neighbours of i_agent + weight_list.push_back( incoming_neighbour_weights ); + + } // end of loop through n_agents + + return NetworkT( std::move( neighbour_list ), std::move( weight_list ), NetworkT::EdgeDirection::Incoming ); +} + +template +Network generate_from_file( const std::string & file ) +{ + using NetworkT = Network; + using WeightT = typename NetworkT::WeightT; + std::vector> neighbour_list; // Neighbour list for the connections + std::vector> weight_list; // List for the interaction weights of each connection + + std::string file_contents = get_file_contents( file ); + + // bool finished = false; + size_t start_of_line = 0; + bool finished = false; + while( !finished ) + { + // Find the end of the current line + auto end_of_line = file_contents.find( '\n', start_of_line ); + if( end_of_line == std::string::npos ) + { + finished = true; + } + + // Get the current line as a substring + auto line = file_contents.substr( start_of_line, end_of_line - start_of_line ); + start_of_line = end_of_line + 1; + + if( line.empty() ) + { + break; + } + + if( line[0] == '#' ) + { + continue; + } + + // Parse the columns + neighbour_list.emplace_back( 0 ); + weight_list.emplace_back( 0 ); + + size_t start_of_column = 0; + bool finished_row = false; + size_t idx_column = 0; + size_t n_neighbours = 0; + while( !finished_row ) + { + auto end_of_column = line.find( ',', start_of_column ); + + if( end_of_column == std::string::npos ) + { + finished_row = true; + } + + auto column_substring = line.substr( start_of_column, end_of_column - start_of_column ); + start_of_column = end_of_column + 1; + + // First column is idx_agent (does not get used) + if( idx_column == 1 ) // Second column contains the number of incoming neighbours + { + n_neighbours = std::stoi( column_substring ); + } + else if( + ( idx_column >= 2 ) + && ( idx_column < 2 + n_neighbours ) ) // The next n_neighbours columsn contain the neighbour indices + { + const auto idx_neighbour = std::stoi( column_substring ); + neighbour_list.back().push_back( idx_neighbour ); + } + else if( idx_column >= 2 + n_neighbours ) + { // The rest of the columns are the weights + const auto weight = std::stod( column_substring ); + weight_list.back().push_back( weight ); + } + idx_column++; + } + } + + return NetworkT( std::move( neighbour_list ), std::move( weight_list ), NetworkT::EdgeDirection::Incoming ); +} } // namespace Seldon::NetworkGeneration \ No newline at end of file diff --git a/include/simulation.hpp b/include/simulation.hpp index 778cead..5f9124a 100644 --- a/include/simulation.hpp +++ b/include/simulation.hpp @@ -1,31 +1,208 @@ #pragma once #include "config_parser.hpp" -#include "model_base.hpp" #include "network.hpp" +#include +#include +#include #include #include +#include +#include +#include #include #include +#include namespace fs = std::filesystem; namespace Seldon { -class Simulation +class SimulationInterface +{ +public: + virtual void run( const fs::path & output_dir_path ) = 0; + virtual ~SimulationInterface() = default; +}; + +template +class Simulation : public SimulationInterface { private: std::mt19937 gen; public: - std::unique_ptr model; - std::unique_ptr network; + std::unique_ptr> model; + Network network; + Config::OutputSettings output_settings; + Simulation( const Config::SimulationOptions & options, const std::optional & cli_network_file, - const std::optional & cli_agent_file ); - void run( fs::path output_dir_path ); + const std::optional & cli_agent_file ) + : output_settings( options.output_settings ) + { + // Initialize the rng + gen = std::mt19937( options.rng_seed ); + + // Construct the network + std::optional file = cli_network_file; + if( !file.has_value() ) // Check if toml file should be superceded by cli_network_file + file = options.network_settings.file; + + if( file.has_value() ) + { + network = NetworkGeneration::generate_from_file( file.value() ); + } + else + { + int n_agents = options.network_settings.n_agents; + auto n_connections = options.network_settings.n_connections; + + //@TODO figure this out + if( options.model == Config::Model::ActivityDrivenModel ) + { + auto model_settings = std::get( options.model_settings ); + if( model_settings.mean_weights ) + { + network = NetworkGeneration::generate_fully_connected( n_agents ); + } + else + { + network + = NetworkGeneration::generate_n_connections( n_agents, n_connections, true, gen ); + } + } + else + { + network = NetworkGeneration::generate_n_connections( n_agents, n_connections, true, gen ); + } + } + + if constexpr( std::is_same_v ) + { + auto degroot_settings = std::get( options.model_settings ); + + // DeGroot specific parameters + model = [&]() + { + auto model = std::make_unique( network ); + model->max_iterations = degroot_settings.max_iterations; + model->convergence_tol = degroot_settings.convergence_tol; + return model; + }(); + + if( cli_agent_file.has_value() ) + { + network.agents = AgentGeneration::generate_from_file( cli_agent_file.value() ); + } + } + else if constexpr( std::is_same_v ) + { + auto activitydriven_settings = std::get( options.model_settings ); + + model = [&]() + { + auto model = std::make_unique( network, gen ); + model->dt = activitydriven_settings.dt; + model->m = activitydriven_settings.m; + model->eps = activitydriven_settings.eps; + model->gamma = activitydriven_settings.gamma; + model->homophily = activitydriven_settings.homophily; + model->reciprocity = activitydriven_settings.reciprocity; + model->alpha = activitydriven_settings.alpha; + model->K = activitydriven_settings.K; + model->mean_activities = activitydriven_settings.mean_activities; + model->mean_weights = activitydriven_settings.mean_weights; + model->max_iterations = activitydriven_settings.max_iterations; + // Reluctance + model->use_reluctances = activitydriven_settings.use_reluctances; + model->reluctance_mean = activitydriven_settings.reluctance_mean; + model->reluctance_sigma = activitydriven_settings.reluctance_sigma; + model->reluctance_eps = activitydriven_settings.reluctance_eps; + // bot + model->n_bots = activitydriven_settings.n_bots; + model->bot_opinion = activitydriven_settings.bot_opinion; + model->bot_m = activitydriven_settings.bot_m; + model->bot_homophily = activitydriven_settings.bot_homophily; + model->bot_activity = activitydriven_settings.bot_activity; + model->get_agents_from_power_law(); + return model; + }(); + + if( cli_agent_file.has_value() ) + { + network.agents + = AgentGeneration::generate_from_file( cli_agent_file.value() ); + } + } + } + + void run( const fs::path & output_dir_path ) override + { + auto n_output_agents = this->output_settings.n_output_agents; + auto n_output_network = this->output_settings.n_output_network; + auto print_iter_start = this->output_settings.start_output; + auto print_initial = this->output_settings.print_initial; + + fmt::print( "-----------------------------------------------------------------\n" ); + fmt::print( "Starting simulation\n" ); + fmt::print( "-----------------------------------------------------------------\n" ); + + if( print_initial ) + { + Seldon::IO::network_to_file( network, ( output_dir_path / fs::path( "network_0.txt" ) ).string() ); + auto filename = fmt::format( "opinions_{}.txt", 0 ); + Seldon::IO::opinions_to_file( network, ( output_dir_path / fs::path( filename ) ).string() ); + } + this->model->initialize_iterations(); + + typedef std::chrono::milliseconds ms; + auto t_simulation_start = std::chrono::high_resolution_clock::now(); + while( !this->model->finished() ) + { + auto t_iter_start = std::chrono::high_resolution_clock::now(); + + this->model->iteration(); + + auto t_iter_end = std::chrono::high_resolution_clock::now(); + auto iter_time = std::chrono::duration_cast( t_iter_end - t_iter_start ); + + // Print the iteration time? + if( this->output_settings.print_progress ) + { + fmt::print( + "Iteration {} iter_time = {:%Hh %Mm %Ss} \n", this->model->n_iterations(), + std::chrono::floor( iter_time ) ); + } + + // Write out the opinion? + if( n_output_agents.has_value() && ( this->model->n_iterations() >= print_iter_start ) + && ( this->model->n_iterations() % n_output_agents.value() == 0 ) ) + { + auto filename = fmt::format( "opinions_{}.txt", this->model->n_iterations() ); + Seldon::IO::opinions_to_file( network, ( output_dir_path / fs::path( filename ) ).string() ); + } + + // Write out the network? + if( n_output_network.has_value() && ( this->model->n_iterations() >= print_iter_start ) + && ( this->model->n_iterations() % n_output_network.value() == 0 ) ) + { + auto filename = fmt::format( "network_{}.txt", this->model->n_iterations() ); + Seldon::IO::network_to_file( network, ( output_dir_path / fs::path( filename ) ).string() ); + } + } + + auto t_simulation_end = std::chrono::high_resolution_clock::now(); + auto total_time = std::chrono::duration_cast( t_simulation_end - t_simulation_start ); + + fmt::print( "-----------------------------------------------------------------\n" ); + fmt::print( + "Finished after {} iterations, total time = {:%Hh %Mm %Ss}\n", this->model->n_iterations(), + std::chrono::floor( total_time ) ); + fmt::print( "=================================================================\n" ); + } }; } // namespace Seldon \ No newline at end of file diff --git a/include/util/io.hpp b/include/util/io.hpp index 0369550..a7f135c 100644 --- a/include/util/io.hpp +++ b/include/util/io.hpp @@ -1,7 +1,6 @@ #pragma once #include "fstream" #include "network.hpp" -#include "simulation.hpp" #include #include #include @@ -12,7 +11,8 @@ namespace Seldon namespace IO { -inline void network_to_dot_file( const Network & network, const std::string & file_path ) +template +void network_to_dot_file( const Network & network, const std::string & file_path ) { std::fstream fs; fs.open( file_path, std::fstream::in | std::fstream::out | std::fstream::trunc ); @@ -37,30 +37,29 @@ inline void network_to_dot_file( const Network & network, const std::string & fi fs.close(); } -inline void opinions_to_file( Simulation & simulation, const std::string & file_path ) +template +void opinions_to_file( const Network & network, const std::string & file_path ) { std::fstream fs; fs.open( file_path, std::fstream::in | std::fstream::out | std::fstream::trunc ); - auto & network = simulation.network; - auto & model = simulation.model; - size_t n_agents = network->n_agents(); + size_t n_agents = network.n_agents(); fmt::print( fs, "# idx_agent, opinion[...]\n" ); for( size_t idx_agent = 0; idx_agent < n_agents; idx_agent++ ) { - std::string row = fmt::format( "{:>5}, {:>25}\n", idx_agent, model->get_agent( idx_agent )->to_string() ); + std::string row = fmt::format( "{:>5}, {:>25}\n", idx_agent, network.agents[idx_agent].to_string() ); fs << row; } fs.close(); } -inline void network_to_file( Simulation & simulation, const std::string & file_path ) +template +void network_to_file( const Network & network, const std::string & file_path ) { std::fstream fs; fs.open( file_path, std::fstream::in | std::fstream::out | std::fstream::trunc ); - auto & network = *simulation.network; size_t n_agents = network.n_agents(); fmt::print( fs, "# idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...]\n" ); diff --git a/include/util/math.hpp b/include/util/math.hpp index dcd0690..607fe72 100644 --- a/include/util/math.hpp +++ b/include/util/math.hpp @@ -138,4 +138,38 @@ class power_law_distribution } }; +/** + * @brief Truncated normal distribution + * A continuous random distribution on the range [eps, infty) + * with p(x) ~ e^(-(x-mean)^2/(2 sigma^2)) + */ +template +class truncated_normal_distribution +{ +private: + ScalarT mean{}; + ScalarT sigma{}; + ScalarT eps{}; + std::normal_distribution normal_dist{}; + size_t max_tries = 5000; + +public: + truncated_normal_distribution( ScalarT mean, ScalarT sigma, ScalarT eps ) + : mean( mean ), sigma( sigma ), eps( eps ), normal_dist( std::normal_distribution( mean, sigma ) ) + { + } + + template + ScalarT operator()( Generator & gen ) + { + for( size_t i = 0; i < max_tries; i++ ) + { + auto sample = normal_dist( gen ); + if( sample > eps ) + return sample; + } + return eps; + } +}; + } // namespace Seldon \ No newline at end of file diff --git a/meson.build b/meson.build index 8b3e7fb..d39583c 100644 --- a/meson.build +++ b/meson.build @@ -7,9 +7,6 @@ add_global_arguments('-Wno-unused-local-typedefs', language : 'cpp') incdir = include_directories('include') sources_seldon = [ - 'src/simulation.cpp', - 'src/network.cpp', - 'src/network_generation.cpp', 'src/config_parser.cpp', 'src/model.cpp', 'src/models/DeGroot.cpp', @@ -27,9 +24,9 @@ exe = executable('seldon', sources_seldon + 'src/main.cpp', tests = [ ['Test_Tarjan', 'test/test_tarjan.cpp'], ['Test_DeGroot', 'test/test_deGroot.cpp'], - ['Test_Activity Driven', 'test/test_activity.cpp'], + ['Test_Activity_Driven', 'test/test_activity.cpp'], ['Test_Network', 'test/test_network.cpp'], - ['Test_Network Generation', 'test/test_network_generation.cpp'], + ['Test_Network_Generation', 'test/test_network_generation.cpp'], ['Test_Sampling', 'test/test_sampling.cpp'], ['Test_IO', 'test/test_io.cpp'], ] diff --git a/src/config_parser.cpp b/src/config_parser.cpp index 0397e45..bf498e8 100644 --- a/src/config_parser.cpp +++ b/src/config_parser.cpp @@ -39,6 +39,10 @@ SimulationOptions parse_config_file( std::string_view config_file_path ) options.output_settings.n_output_agents = tbl["io"]["n_output_agents"].value(); options.output_settings.print_progress = tbl["io"]["print_progress"].value_or( bool( options.output_settings.print_progress ) ); + options.output_settings.print_initial + = tbl["io"]["print_initial"].value_or( bool( options.output_settings.print_initial ) ); + // @TODO: default value should not be hard-coded here + options.output_settings.start_output = tbl["io"]["start_output"].value_or( 1 ); // Check if the 'model' keyword exists std::optional model_string = tbl["simulation"]["model"].value(); @@ -70,6 +74,11 @@ SimulationOptions parse_config_file( std::string_view config_file_path ) model_settings.K = tbl["ActivityDriven"]["K"].value_or( 3.0 ); model_settings.mean_activities = tbl["ActivityDriven"]["mean_activities"].value_or( false ); model_settings.mean_weights = tbl["ActivityDriven"]["mean_weights"].value_or( false ); + // Reluctances + model_settings.use_reluctances = tbl["ActivityDriven"]["reluctances"].value_or( false ); + model_settings.reluctance_mean = tbl["ActivityDriven"]["reluctance_mean"].value_or( 1.0 ); + model_settings.reluctance_sigma = tbl["ActivityDriven"]["reluctance_sigma"].value_or( 0.25 ); + model_settings.reluctance_eps = tbl["ActivityDriven"]["reluctance_eps"].value_or( 0.01 ); model_settings.max_iterations = tbl["model"]["max_iterations"].value(); @@ -147,6 +156,9 @@ void validate_settings( const SimulationOptions & options ) auto g_zero = []( auto x ) { return x > 0; }; auto geq_zero = []( auto x ) { return x >= 0; }; + // @TODO: Check that start_output is less than the max_iterations? + check( name_and_var( options.output_settings.start_output ), g_zero ); + if( options.model == Model::ActivityDrivenModel ) { auto model_settings = std::get( options.model_settings ); @@ -158,7 +170,12 @@ void validate_settings( const SimulationOptions & options ) check( name_and_var( model_settings.alpha ), geq_zero ); // check( name_and_var( model_settings.homophily ), geq_zero ); check( name_and_var( model_settings.reciprocity ), geq_zero ); - + // Reluctance options + check( name_and_var( model_settings.reluctance_mean ), g_zero ); + check( name_and_var( model_settings.reluctance_sigma ), g_zero ); + check( name_and_var( model_settings.reluctance_eps ), g_zero ); + check( name_and_var( model_settings.covariance_factor ), geq_zero ); + // Bot options size_t n_bots = model_settings.n_bots; auto check_bot_size = [&]( auto x ) { return x.size() >= n_bots; }; const std::string bot_msg = "Length needs to be >= n_bots"; @@ -179,7 +196,7 @@ void print_settings( const SimulationOptions & options ) fmt::print( "INFO: Seeding with seed {}\n", options.rng_seed ); fmt::print( "Model type: {}\n", options.model_string ); fmt::print( "Network has {} agents\n", options.network_settings.n_agents ); - + // @TODO: Optionally print *all* settings to the console, including defaults that were set if( options.model == Model::ActivityDrivenModel ) { auto model_settings = std::get( options.model_settings ); diff --git a/src/main.cpp b/src/main.cpp index 20ceaf1..d026801 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,8 +5,9 @@ #include #include #include +#include +#include #include -#include namespace fs = std::filesystem; int main( int argc, char * argv[] ) @@ -56,12 +57,20 @@ int main( int argc, char * argv[] ) fmt::print( "Reading agents from file {}\n", agent_file.value() ); } - auto simulation = Seldon::Simulation( simulation_options, network_file, agent_file ); - fmt::print( "Finished model setup\n" ); + std::unique_ptr simulation; - // Seldon::IO::network_to_dot_file( *simulation.network, ( output_dir_path / fs::path( "network.dot" ) ).string() ); - // Perform the iterations using the model - simulation.run( output_dir_path ); + if( simulation_options.model == Seldon::Config::Model::DeGroot ) + { + simulation = std::make_unique>( + simulation_options, network_file, agent_file ); + } + else if( simulation_options.model == Seldon::Config::Model::ActivityDrivenModel ) + { + simulation = std::make_unique>( + simulation_options, network_file, agent_file ); + } + + simulation->run( output_dir_path ); return 0; -} \ No newline at end of file +} diff --git a/src/models/ActivityDrivenModel.cpp b/src/models/ActivityDrivenModel.cpp index 5aebac3..c2f4a0a 100644 --- a/src/models/ActivityDrivenModel.cpp +++ b/src/models/ActivityDrivenModel.cpp @@ -8,15 +8,15 @@ namespace Seldon { -ActivityAgentModel::ActivityAgentModel( int n_agents, Network & network, std::mt19937 & gen ) - : Model( n_agents ), +ActivityDrivenModel::ActivityDrivenModel( NetworkT & network, std::mt19937 & gen ) + : Model(), network( network ), - contact_prob_list( std::vector>( n_agents ) ), + contact_prob_list( std::vector>( network.n_agents() ) ), gen( gen ) { } -double ActivityAgentModel::homophily_weight( size_t idx_contacter, size_t idx_contacted ) +double ActivityDrivenModel::homophily_weight( size_t idx_contacter, size_t idx_contacted ) { double homophily = this->homophily; @@ -27,31 +27,39 @@ double ActivityAgentModel::homophily_weight( size_t idx_contacter, size_t idx_co homophily = this->bot_homophily[idx_contacter]; constexpr double tolerance = 1e-10; - auto opinion_diff = std::abs( this->agents[idx_contacter].data.opinion - this->agents[idx_contacted].data.opinion ); - opinion_diff = std::max( tolerance, opinion_diff ); + auto opinion_diff + = std::abs( network.agents[idx_contacter].data.opinion - network.agents[idx_contacted].data.opinion ); + opinion_diff = std::max( tolerance, opinion_diff ); return std::pow( opinion_diff, -homophily ); } -void ActivityAgentModel::get_agents_from_power_law() +void ActivityDrivenModel::get_agents_from_power_law() { std::uniform_real_distribution<> dis_opinion( -1, 1 ); // Opinion initial values power_law_distribution<> dist_activity( eps, gamma ); + truncated_normal_distribution<> dist_reluctance( reluctance_mean, reluctance_sigma, reluctance_eps ); + auto mean_activity = dist_activity.mean(); // Initial conditions for the opinions, initialize to [-1,1] // The activities should be drawn from a power law distribution - for( size_t i = 0; i < agents.size(); i++ ) + for( size_t i = 0; i < network.agents.size(); i++ ) { - agents[i].data.opinion = dis_opinion( gen ); // Draw the opinion value + network.agents[i].data.opinion = dis_opinion( gen ); // Draw the opinion value if( !mean_activities ) { // Draw from a power law distribution (1-gamma)/(1-eps^(1-gamma)) * a^(-gamma) - agents[i].data.activity = dist_activity( gen ); + network.agents[i].data.activity = dist_activity( gen ); } else { - agents[i].data.activity = mean_activity; + network.agents[i].data.activity = mean_activity; + } + + if( use_reluctances ) + { + network.agents[i].data.reluctance = dist_reluctance( gen ); } } @@ -59,13 +67,13 @@ void ActivityAgentModel::get_agents_from_power_law() { for( size_t bot_idx = 0; bot_idx < n_bots; bot_idx++ ) { - agents[bot_idx].data.opinion = bot_opinion[bot_idx]; - agents[bot_idx].data.activity = bot_activity[bot_idx]; + network.agents[bot_idx].data.opinion = bot_opinion[bot_idx]; + network.agents[bot_idx].data.activity = bot_activity[bot_idx]; } } } -void ActivityAgentModel::update_network_probabilistic() +void ActivityDrivenModel::update_network_probabilistic() { network.switch_direction_flag(); @@ -76,7 +84,7 @@ void ActivityAgentModel::update_network_probabilistic() for( size_t idx_agent = 0; idx_agent < network.n_agents(); idx_agent++ ) { // Test if the agent is activated - bool activated = dis_activation( gen ) < agents[idx_agent].data.activity; + bool activated = dis_activation( gen ) < network.agents[idx_agent].data.activity; if( activated ) { @@ -132,9 +140,9 @@ void ActivityAgentModel::update_network_probabilistic() network.toggle_incoming_outgoing(); // switch direction, so that we have incoming edges } -void ActivityAgentModel::update_network_mean() +void ActivityDrivenModel::update_network_mean() { - using WeightT = Network::WeightT; + using WeightT = NetworkT::WeightT; std::vector weights( network.n_agents(), 0.0 ); // Set all weights to zero in the beginning @@ -144,7 +152,8 @@ void ActivityAgentModel::update_network_mean() contact_prob_list[idx_agent] = weights; // set to zero } - auto probability_helper = []( double omega, size_t m ) { + auto probability_helper = []( double omega, size_t m ) + { double p = 0; for( size_t i = 1; i <= m; i++ ) p += ( std::pow( -omega, i + 1 ) + omega ) / ( omega + 1 ); @@ -171,7 +180,7 @@ void ActivityAgentModel::update_network_mean() m_temp = bot_m[idx_agent]; } - double activity = std::max( 1.0, agents[idx_agent].data.activity ); + double activity = std::max( 1.0, network.agents[idx_agent].data.activity ); for( size_t j = 0; j < network.n_agents(); j++ ) { double omega = homophily_weight( idx_agent, j ) / normalization; @@ -203,7 +212,7 @@ void ActivityAgentModel::update_network_mean() } } -void ActivityAgentModel::update_network() +void ActivityDrivenModel::update_network() { if( !mean_weights ) @@ -216,7 +225,7 @@ void ActivityAgentModel::update_network() } } -void ActivityAgentModel::iteration() +void ActivityDrivenModel::iteration() { Model::iteration(); @@ -224,21 +233,21 @@ void ActivityAgentModel::iteration() // Integrate the ODE using 4th order Runge-Kutta // k_1 = hf(x_n,y_n) - get_euler_slopes( k1_buffer, [this]( size_t i ) { return this->agents[i].data.opinion; } ); + get_euler_slopes( k1_buffer, [this]( size_t i ) { return network.agents[i].data.opinion; } ); // k_2 = hf(x_n+1/2h,y_n+1/2k_1) get_euler_slopes( - k2_buffer, [this]( size_t i ) { return this->agents[i].data.opinion + 0.5 * this->k1_buffer[i]; } ); + k2_buffer, [this]( size_t i ) { return network.agents[i].data.opinion + 0.5 * this->k1_buffer[i]; } ); // k_3 = hf(x_n+1/2h,y_n+1/2k_2) get_euler_slopes( - k3_buffer, [this]( size_t i ) { return this->agents[i].data.opinion + 0.5 * this->k2_buffer[i]; } ); + k3_buffer, [this]( size_t i ) { return network.agents[i].data.opinion + 0.5 * this->k2_buffer[i]; } ); // k_4 = hf(x_n+h,y_n+k_3) - get_euler_slopes( k4_buffer, [this]( size_t i ) { return this->agents[i].data.opinion + this->k3_buffer[i]; } ); + get_euler_slopes( k4_buffer, [this]( size_t i ) { return network.agents[i].data.opinion + this->k3_buffer[i]; } ); // Update the agent opinions for( size_t idx_agent = 0; idx_agent < network.n_agents(); ++idx_agent ) { // y_(n+1) = y_n+1/6k_1+1/3k_2+1/3k_3+1/6k_4+O(h^5) - agents[idx_agent].data.opinion + network.agents[idx_agent].data.opinion += ( k1_buffer[idx_agent] + 2 * k2_buffer[idx_agent] + 2 * k3_buffer[idx_agent] + k4_buffer[idx_agent] ) / 6.0; } @@ -247,7 +256,7 @@ void ActivityAgentModel::iteration() { for( size_t bot_idx = 0; bot_idx < n_bots; bot_idx++ ) { - agents[bot_idx].data.opinion = bot_opinion[bot_idx]; + network.agents[bot_idx].data.opinion = bot_opinion[bot_idx]; } } } diff --git a/src/models/DeGroot.cpp b/src/models/DeGroot.cpp index c0be310..db4c5ad 100644 --- a/src/models/DeGroot.cpp +++ b/src/models/DeGroot.cpp @@ -2,8 +2,11 @@ #include #include -Seldon::DeGrootModel::DeGrootModel( int n_agents, Network & network ) - : Model( n_agents ), network( network ), agents_current_copy( std::vector( n_agents ) ) +namespace Seldon +{ + +DeGrootModel::DeGrootModel( NetworkT & network ) + : Model(), network( network ), agents_current_copy( network.agents ) { // For a strongly connected network, the number of SCCs should be 1 // Print a warning if this is not true @@ -13,20 +16,20 @@ Seldon::DeGrootModel::DeGrootModel( int n_agents, Network & network ) fmt::print( "WARNING: You have {} strongly connected components in your network!\n", n_components ); } - for( size_t i = 0; i < agents.size(); i++ ) + for( size_t i = 0; i < network.agents.size(); i++ ) { - agents[i].data = double( i ) / double( agents.size() ); + network.agents[i].data = double( i ) / double( network.agents.size() ); } } -void Seldon::DeGrootModel::iteration() +void DeGrootModel::iteration() { Model::iteration(); size_t j_index = 0; double weight = 0.0; - for( size_t i = 0; i < agents.size(); i++ ) + for( size_t i = 0; i < network.agents.size(); i++ ) { auto neighbour_buffer = network.get_neighbours( i ); auto weight_buffer = network.get_weights( i ); @@ -35,21 +38,24 @@ void Seldon::DeGrootModel::iteration() { j_index = neighbour_buffer[j]; weight = weight_buffer[j]; - agents_current_copy[i].data += weight * agents[j_index].data; + agents_current_copy[i].data += weight * network.agents[j_index].data; } } max_opinion_diff = 0; // Update the original agent opinions - for( std::size_t i = 0; i < agents.size(); i++ ) + for( std::size_t i = 0; i < network.agents.size(); i++ ) { - max_opinion_diff = std::max( max_opinion_diff, std::abs( agents[i].data - agents_current_copy[i].data ) ); - agents[i] = agents_current_copy[i]; + max_opinion_diff + = std::max( max_opinion_diff, std::abs( network.agents[i].data - agents_current_copy[i].data ) ); + network.agents[i] = agents_current_copy[i]; } } -bool Seldon::DeGrootModel::finished() +bool DeGrootModel::finished() { bool converged = max_opinion_diff < convergence_tol; return Model::finished() || converged; -} \ No newline at end of file +} + +} // namespace Seldon \ No newline at end of file diff --git a/src/network.cpp b/src/network.cpp deleted file mode 100644 index 9543e76..0000000 --- a/src/network.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "network.hpp" -#include "connectivity.hpp" -#include -#include -#include -#include -#include -#include - -Seldon::Network::Network( - std::vector> && neighbour_list, std::vector> && weight_list, - EdgeDirection direction ) - : neighbour_list( neighbour_list ), weight_list( weight_list ), _direction( direction ) -{ -} - -size_t Seldon::Network::n_agents() const -{ - return neighbour_list.size(); -} - -std::size_t Seldon::Network::n_edges( std::optional agent_idx ) const -{ - if( agent_idx.has_value() ) - { - return neighbour_list[agent_idx.value()].size(); - } - else - { - return std::transform_reduce( - neighbour_list.cbegin(), neighbour_list.cend(), 0, std::plus{}, - []( const auto & neigh_list ) { return neigh_list.size(); } ); - } -} - -const Seldon::Network::EdgeDirection & Seldon::Network::direction() const -{ - return _direction; -} - -std::vector> Seldon::Network::strongly_connected_components() const -{ - // Now that we have the neighbour list (or adjacency list) - // Run Tarjan's algorithm for strongly connected components - auto tarjan_scc = TarjanConnectivityAlgo( neighbour_list ); - return tarjan_scc.scc_list; -} - -std::span Seldon::Network::get_neighbours( std::size_t agent_idx ) const -{ - return std::span( neighbour_list[agent_idx].data(), neighbour_list[agent_idx].size() ); -} - -std::span Seldon::Network::get_neighbours( std::size_t agent_idx ) -{ - return std::span( neighbour_list[agent_idx].data(), neighbour_list[agent_idx].size() ); -} - -void Seldon::Network::set_neighbours_and_weights( - std::size_t agent_idx, std::span buffer_neighbours, const WeightT & weight ) -{ - neighbour_list[agent_idx].assign( buffer_neighbours.begin(), buffer_neighbours.end() ); - weight_list[agent_idx].resize( buffer_neighbours.size() ); - std::fill( weight_list[agent_idx].begin(), weight_list[agent_idx].end(), weight ); -} - -void Seldon::Network::set_neighbours_and_weights( - std::size_t agent_idx, std::span buffer_neighbours, - std::span buffer_weights ) -{ - if( buffer_neighbours.size() != buffer_weights.size() ) - { - throw std::runtime_error( "Network::set_neighbours_and_weights: both buffers need to have the same length!" ); - } - - neighbour_list[agent_idx].assign( buffer_neighbours.begin(), buffer_neighbours.end() ); - weight_list[agent_idx].assign( buffer_weights.begin(), buffer_weights.end() ); -} - -void Seldon::Network::push_back_neighbour_and_weight( size_t i, size_t j, WeightT w ) -{ - neighbour_list[i].push_back( j ); - weight_list[i].push_back( w ); -} - -std::span Seldon::Network::get_weights( std::size_t agent_idx ) const -{ - return std::span( weight_list[agent_idx].data(), weight_list[agent_idx].size() ); -} - -std::span Seldon::Network::get_weights( std::size_t agent_idx ) -{ - return std::span( weight_list[agent_idx].data(), weight_list[agent_idx].size() ); -} - -void Seldon::Network::set_weights( std::size_t agent_idx, std::span weights ) -{ - if( neighbour_list[agent_idx].size() != weights.size() ) - { - throw std::runtime_error( "Network::set_weights: tried to set weights of the wrong size!" ); - } - weight_list[agent_idx].assign( weights.begin(), weights.end() ); -} - -void Seldon::Network::toggle_incoming_outgoing() -{ - std::vector> neighbour_list_transpose( n_agents(), std::vector( 0 ) ); - std::vector> weight_list_transpose( n_agents(), std::vector( 0 ) ); - - for( size_t i_agent = 0; i_agent < n_agents(); i_agent++ ) - { - for( size_t i_neighbour = 0; i_neighbour < neighbour_list[i_agent].size(); i_neighbour++ ) - { - const auto neighbour = neighbour_list[i_agent][i_neighbour]; - const auto weight = weight_list[i_agent][i_neighbour]; - neighbour_list_transpose[neighbour].push_back( i_agent ); - weight_list_transpose[neighbour].push_back( weight ); - } - } - - neighbour_list = std::move( neighbour_list_transpose ); - weight_list = std::move( weight_list_transpose ); - - // Swap the edge direction - switch_direction_flag(); -} - -void Seldon::Network::transpose() -{ - toggle_incoming_outgoing(); - switch_direction_flag(); -} - -void Seldon::Network::switch_direction_flag() -{ - // Swap the edge direction - if( direction() == EdgeDirection::Incoming ) - { - _direction = EdgeDirection::Outgoing; - } - else - { - _direction = EdgeDirection::Incoming; - } -} \ No newline at end of file diff --git a/src/network_generation.cpp b/src/network_generation.cpp deleted file mode 100644 index fbeb36c..0000000 --- a/src/network_generation.cpp +++ /dev/null @@ -1,243 +0,0 @@ -#include "network_generation.hpp" -#include "network.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Seldon::NetworkGeneration -{ - -std::unique_ptr -generate_n_connections( size_t n_agents, size_t n_connections, bool self_interaction, std::mt19937 & gen ) -{ - using WeightT = Network::WeightT; - - std::vector> neighbour_list; // Neighbour list for the connections - std::vector> weight_list; // List for the interaction weights of each connection - std::uniform_real_distribution<> dis( 0.0, 1.0 ); // Values don't matter, will be normalized - auto incoming_neighbour_buffer - = std::vector(); // for the j_agents indices connected to i_agent (adjacencies/neighbours) - auto incoming_neighbour_weights = std::vector(); // Vector of weights of the j neighbours of i - WeightT outgoing_norm_weight = 0; - - // Loop through all the agents and create the neighbour_list and weight_list - for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) - { - outgoing_norm_weight = 0.0; - - incoming_neighbour_buffer.clear(); - incoming_neighbour_weights.clear(); - - // Get the vector of sorted adjacencies, excluding i (include i later) - // TODO: option for making the n_connections variable - draw_unique_k_from_n( i_agent, n_connections, n_agents, incoming_neighbour_buffer, gen ); - - incoming_neighbour_weights.resize( incoming_neighbour_buffer.size() ); - for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) - { - incoming_neighbour_weights[j] = dis( gen ); // Draw the weight - outgoing_norm_weight += incoming_neighbour_weights[j]; - } - - if( self_interaction ) - { - // Put the self-interaction as the last entry - auto self_interaction_weight = dis( gen ); - outgoing_norm_weight += self_interaction_weight; - // outgoing_norm_weights += self_interaction_weight; - incoming_neighbour_buffer.push_back( i_agent ); // Add the agent itself - incoming_neighbour_weights.push_back( self_interaction_weight ); - } - - // --------- - // Normalize the weights so that the row sums to 1 - // Might be specific to the DeGroot model? - for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) - { - incoming_neighbour_weights[j] /= outgoing_norm_weight; - } - - // Add the neighbour vector for i_agent to the neighbour list - neighbour_list.push_back( incoming_neighbour_buffer ); - // Add the weight interactions for the neighbours of i_agent - weight_list.push_back( incoming_neighbour_weights ); - - } // end of loop through n_agents - - return std::make_unique( - std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming ); -} - -std::unique_ptr generate_from_file( const std::string & file ) -{ - using WeightT = Network::WeightT; - std::vector> neighbour_list; // Neighbour list for the connections - std::vector> weight_list; // List for the interaction weights of each connection - - std::string file_contents = get_file_contents( file ); - - // bool finished = false; - size_t start_of_line = 0; - bool finished = false; - while( !finished ) - { - // Find the end of the current line - auto end_of_line = file_contents.find( '\n', start_of_line ); - if( end_of_line == std::string::npos ) - { - finished = true; - } - - // Get the current line as a substring - auto line = file_contents.substr( start_of_line, end_of_line - start_of_line ); - start_of_line = end_of_line + 1; - - // TODO: check if empty or comment - if( line.empty() ) - { - break; - } - - if( line[0] == '#' ) - { - continue; - } - - // Parse the columns - neighbour_list.emplace_back( 0 ); - weight_list.emplace_back( 0 ); - - size_t start_of_column = 0; - bool finished_row = false; - size_t idx_column = 0; - size_t n_neighbours = 0; - while( !finished_row ) - { - auto end_of_column = line.find( ',', start_of_column ); - - if( end_of_column == std::string::npos ) - { - finished_row = true; - } - - auto column_substring = line.substr( start_of_column, end_of_column - start_of_column ); - start_of_column = end_of_column + 1; - - // First column is idx_agent (does not get used) - if( idx_column == 1 ) // Second column contains the number of incoming neighbours - { - n_neighbours = std::stoi( column_substring ); - } - else if( - ( idx_column >= 2 ) - && ( idx_column < 2 + n_neighbours ) ) // The next n_neighbours columsn contain the neighbour indices - { - const auto idx_neighbour = std::stoi( column_substring ); - neighbour_list.back().push_back( idx_neighbour ); - } - else if( idx_column >= 2 + n_neighbours ) - { // The rest of the columns are the weights - const auto weight = std::stod( column_substring ); - weight_list.back().push_back( weight ); - } - idx_column++; - } - } - - return std::make_unique( - std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming ); -} - -// Create a fully connected network, with each weight initialized to the same user-defined -// value, weight. The neighbours include the agent itself as well. -std::unique_ptr generate_fully_connected( size_t n_agents, Network::WeightT weight ) -{ - using WeightT = Network::WeightT; - - std::vector> neighbour_list; // Neighbour list for the connections - std::vector> weight_list; // List for the interaction weights of each connection - auto incoming_neighbour_buffer - = std::vector( n_agents ); // for the j_agents indices connected to i_agent (adjacencies/neighbours) - auto incoming_neighbour_weights - = std::vector( n_agents, weight ); // Vector of weights of the j neighbours of i - - // Create the incoming_neighbour_buffer once. This will contain all agents, including itself - for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) - { - incoming_neighbour_buffer[i_agent] = i_agent; - } - - // Loop through all the agents and update the neighbour_list and weight_list - for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) - { - // Add the neighbour vector for i_agent to the neighbour list - neighbour_list.push_back( incoming_neighbour_buffer ); - // Add the weight interactions for the neighbours of i_agent - weight_list.push_back( incoming_neighbour_weights ); - - } // end of loop through n_agents - - return std::make_unique( - std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming ); -} - -// Create a fully connected network, with each incoming weight initialized to some value from a -// distribution, normalized to 1. -std::unique_ptr generate_fully_connected( size_t n_agents, std::mt19937 & gen ) -{ - using WeightT = Network::WeightT; - - std::vector> neighbour_list; // Neighbour list for the connections - std::vector> weight_list; // List for the interaction weights of each connection - auto incoming_neighbour_buffer - = std::vector( n_agents ); // for the j_agents indices connected to i_agent (adjacencies/neighbours) - std::uniform_real_distribution<> dis( 0.0, 1.0 ); // Values don't matter, will be normalized - auto incoming_neighbour_weights = std::vector( n_agents ); // Vector of weights of the j neighbours of i - WeightT outgoing_norm_weight = 0; - - // Create the incoming_neighbour_buffer once. This will contain all agents, including itself - for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) - { - incoming_neighbour_buffer[i_agent] = i_agent; - } - - // Loop through all the agents and create the neighbour_list and weight_list - for( size_t i_agent = 0; i_agent < n_agents; ++i_agent ) - { - - outgoing_norm_weight = 0.0; - - // Initialize the weights - for( size_t j = 0; j < n_agents; ++j ) - { - incoming_neighbour_weights[j] = dis( gen ); // Draw the weight - outgoing_norm_weight += incoming_neighbour_weights[j]; - } - - // --------- - // Normalize the weights so that the row sums to 1 - // Might be specific to the DeGroot model? - for( size_t j = 0; j < incoming_neighbour_buffer.size(); ++j ) - { - incoming_neighbour_weights[j] /= outgoing_norm_weight; - } - - // Add the neighbour vector for i_agent to the neighbour list - neighbour_list.push_back( incoming_neighbour_buffer ); - // Add the weight interactions for the neighbours of i_agent - weight_list.push_back( incoming_neighbour_weights ); - - } // end of loop through n_agents - - return std::make_unique( - std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming ); -} -} // namespace Seldon::NetworkGeneration \ No newline at end of file diff --git a/src/simulation.cpp b/src/simulation.cpp deleted file mode 100644 index aa3ef82..0000000 --- a/src/simulation.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "simulation.hpp" -#include "agent_generation.hpp" -#include "config_parser.hpp" -#include "models/ActivityDrivenModel.hpp" -#include "models/DeGroot.hpp" -#include "network_generation.hpp" -#include "util/tomlplusplus.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Seldon::Simulation::Simulation( - const Config::SimulationOptions & options, const std::optional & cli_network_file, - const std::optional & cli_agent_file ) - : output_settings( options.output_settings ) -{ - // Initialize the rng - gen = std::mt19937( options.rng_seed ); - - // Construct the network - std::optional file = cli_network_file; - if( !file.has_value() ) // Check if toml file should be superceded by cli_network_file - file = options.network_settings.file; - - if( file.has_value() ) - { - network = NetworkGeneration::generate_from_file( file.value() ); - } - else - { - int n_agents = options.network_settings.n_agents; - auto n_connections = options.network_settings.n_connections; - - //@TODO figure this out - if( options.model == Config::Model::ActivityDrivenModel ) - { - auto model_settings = std::get( options.model_settings ); - if( model_settings.mean_weights ) - { - network = NetworkGeneration::generate_fully_connected( n_agents ); - } - else - { - network = NetworkGeneration::generate_n_connections( n_agents, n_connections, true, gen ); - } - } - else - { - network = NetworkGeneration::generate_n_connections( n_agents, n_connections, true, gen ); - } - } - - if( options.model == Config::Model::DeGroot ) - { - auto degroot_settings = std::get( options.model_settings ); - - // DeGroot specific parameters - auto model_DeGroot = std::make_unique( network->n_agents(), *network ); - model_DeGroot->max_iterations = degroot_settings.max_iterations; - model_DeGroot->convergence_tol = degroot_settings.convergence_tol; - - if( cli_agent_file.has_value() ) - { - model_DeGroot->agents = AgentGeneration::generate_from_file( cli_agent_file.value() ); - } - - model = std::move( model_DeGroot ); - } - else if( options.model == Config::Model::ActivityDrivenModel ) - { - auto activitydriven_settings = std::get( options.model_settings ); - - auto model_activityDriven = std::make_unique( network->n_agents(), *network, gen ); - model_activityDriven->dt = activitydriven_settings.dt; - model_activityDriven->m = activitydriven_settings.m; - model_activityDriven->eps = activitydriven_settings.eps; - model_activityDriven->gamma = activitydriven_settings.gamma; - model_activityDriven->homophily = activitydriven_settings.homophily; - model_activityDriven->reciprocity = activitydriven_settings.reciprocity; - model_activityDriven->alpha = activitydriven_settings.alpha; - model_activityDriven->K = activitydriven_settings.K; - model_activityDriven->mean_activities = activitydriven_settings.mean_activities; - model_activityDriven->mean_weights = activitydriven_settings.mean_weights; - - model_activityDriven->max_iterations = activitydriven_settings.max_iterations; - - // bot - model_activityDriven->n_bots = activitydriven_settings.n_bots; - model_activityDriven->bot_opinion = activitydriven_settings.bot_opinion; - model_activityDriven->bot_m = activitydriven_settings.bot_m; - model_activityDriven->bot_homophily = activitydriven_settings.bot_homophily; - model_activityDriven->bot_activity = activitydriven_settings.bot_activity; - - model_activityDriven->get_agents_from_power_law(); - - if( cli_agent_file.has_value() ) - { - model_activityDriven->agents - = AgentGeneration::generate_from_file( cli_agent_file.value() ); - } - - model = std::move( model_activityDriven ); - } -} - -void Seldon::Simulation::run( fs::path output_dir_path ) -{ - auto n_output_agents = this->output_settings.n_output_agents; - auto n_output_network = this->output_settings.n_output_network; - - fmt::print( "-----------------------------------------------------------------\n" ); - fmt::print( "Starting simulation\n" ); - fmt::print( "-----------------------------------------------------------------\n" ); - - Seldon::IO::network_to_file( *this, ( output_dir_path / fs::path( "network_0.txt" ) ).string() ); - auto filename = fmt::format( "opinions_{}.txt", 0 ); - Seldon::IO::opinions_to_file( *this, ( output_dir_path / fs::path( filename ) ).string() ); - - typedef std::chrono::milliseconds ms; - auto t_simulation_start = std::chrono::high_resolution_clock::now(); - while( !this->model->finished() ) - { - auto t_iter_start = std::chrono::high_resolution_clock::now(); - - this->model->iteration(); - - auto t_iter_end = std::chrono::high_resolution_clock::now(); - auto iter_time = std::chrono::duration_cast( t_iter_end - t_iter_start ); - - // Print the iteration time? - if( this->output_settings.print_progress ) - { - fmt::print( - "Iteration {} iter_time = {:%Hh %Mm %Ss} \n", this->model->n_iterations, - std::chrono::floor( iter_time ) ); - } - - // Write out the opinion? - if( n_output_agents.has_value() && ( this->model->n_iterations % n_output_agents.value() == 0 ) ) - { - auto filename = fmt::format( "opinions_{}.txt", this->model->n_iterations ); - Seldon::IO::opinions_to_file( *this, ( output_dir_path / fs::path( filename ) ).string() ); - } - - // Write out the network? - if( n_output_network.has_value() && ( this->model->n_iterations % n_output_network.value() == 0 ) ) - { - auto filename = fmt::format( "network_{}.txt", this->model->n_iterations ); - Seldon::IO::network_to_file( *this, ( output_dir_path / fs::path( filename ) ).string() ); - } - } - - auto t_simulation_end = std::chrono::high_resolution_clock::now(); - auto total_time = std::chrono::duration_cast( t_simulation_end - t_simulation_start ); - - fmt::print( "-----------------------------------------------------------------\n" ); - fmt::print( - "Finished after {} iterations, total time = {:%Hh %Mm %Ss}\n", this->model->n_iterations, - std::chrono::floor( total_time ) ); - fmt::print( "=================================================================\n" ); -} \ No newline at end of file diff --git a/test/res/10_agents_meanfield_activity.toml b/test/res/10_agents_meanfield_activity.toml index 63191c8..66d8463 100644 --- a/test/res/10_agents_meanfield_activity.toml +++ b/test/res/10_agents_meanfield_activity.toml @@ -3,7 +3,7 @@ model = "ActivityDriven" # rng_seed = 120 # Leaving this empty will pick a random seed [io] -n_output_network = 1 # Write the network every 20 iterations +# n_output_network = 1 # Write the network every 20 iterations # n_output_agents = 1 print_progress = false # Print the iteration time ; if not set, then always print diff --git a/test/res/1bot_1agent_activity_prob.toml b/test/res/1bot_1agent_activity_prob.toml index d8e0f58..4c5b050 100644 --- a/test/res/1bot_1agent_activity_prob.toml +++ b/test/res/1bot_1agent_activity_prob.toml @@ -17,18 +17,23 @@ eps = 1 # Minimum activity epsilon; a_i belongs to [epsilon,1] gamma = 2.1 # Exponent of activity power law distribution of activities reciprocity = 1 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections homophily = 0.5 # aka beta. if zero, agents pick their interaction partners at random -alpha = 1.5 # Controversialness of the issue, must be greater than 0. +alpha = 1.5 # Controversialness of the issue, must be greater than 0. K = 2.0 # Social interaction strength mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents mean_weights = false # Use the meanfield approximation of the network edges +reluctances = true # Assigns a "reluctance" (m_i) to each agent. By default; false and every agent has a reluctance of 1 +reluctance_mean = 1.5 # Mean of distribution before drawing from a truncated normal distribution (default set to 1.0) +reluctance_sigma = 0.1 # Width of normal distribution (before truncating) +reluctance_eps = 0.01 # Minimum such that the normal distribution is truncated at this value + n_bots = 1 # The number of bots to be used; if not specified defaults to 0 (which means bots are deactivated) # Bots are agents with fixed opinions and different parameters, the parameters are specified in the following lists # If n_bots is smaller than the length of any of the lists, the first n_bots entries are used. If n_bots is greater the code will throw an exception. -bot_m = [1] # If not specified, defaults to `m` +bot_m = [1] # If not specified, defaults to `m` bot_homophily = [0.7] # If not specified, defaults to `homophily` bot_activity = [1.0] # If not specified, defaults to 0 -bot_opinion = [2] # The fixed opinions of the bots +bot_opinion = [2] # The fixed opinions of the bots [network] number_of_agents = 2 diff --git a/test/res/opinions.txt b/test/res/opinions.txt index 6669744..4cede8c 100644 --- a/test/res/opinions.txt +++ b/test/res/opinions.txt @@ -2,5 +2,5 @@ # comment 0, 2.1127107987061544, 0.044554683389757696 1,0.8088982488089491, 0.015813166022685163 -2,-0.8802809369462433 , 0.015863953902810535 +2,-0.8802809369462433 , 0.015863953902810535, 2.3 diff --git a/test/test_activity.cpp b/test/test_activity.cpp index a762e04..75e83c8 100644 --- a/test/test_activity.cpp +++ b/test/test_activity.cpp @@ -18,6 +18,7 @@ TEST_CASE( { using namespace Seldon; using namespace Catch::Matchers; + using AgentT = ActivityDrivenModel::AgentT; auto proj_root_path = fs::current_path(); @@ -25,7 +26,7 @@ TEST_CASE( auto options = Config::parse_config_file( input_file.string() ); - auto simulation = Simulation( options, std::nullopt, std::nullopt ); + auto simulation = Simulation( options, std::nullopt, std::nullopt ); fs::path output_dir_path = proj_root_path / fs::path( "test/output" ); @@ -49,13 +50,14 @@ TEST_CASE( "Test the probabilistic activity driven model for two agents", "[acti { using namespace Seldon; using namespace Catch::Matchers; + using AgentT = ActivityDrivenModel::AgentT; auto proj_root_path = fs::current_path(); auto input_file = proj_root_path / fs::path( "test/res/2_agents_activity_prob.toml" ); auto options = Config::parse_config_file( input_file.string() ); - auto simulation = Simulation( options, std::nullopt, std::nullopt ); + auto simulation = Simulation( options, std::nullopt, std::nullopt ); // We need an output path for Simulation, but we won't write anything out there fs::path output_dir_path = proj_root_path / fs::path( "test/output" ); @@ -75,36 +77,41 @@ TEST_CASE( "Test the probabilistic activity driven model for two agents", "[acti fmt::print( "analytical_x = {}\n", analytical_x ); - for( size_t idx_agent = 0; idx_agent < simulation.network->n_agents(); idx_agent++ ) + for( size_t idx_agent = 0; idx_agent < simulation.network.n_agents(); idx_agent++ ) { - auto * agent = simulation.model->get_agent_as( idx_agent ); - fmt::print( "{} \n", agent->data.opinion ); - REQUIRE_THAT( agent->data.opinion, WithinAbs( analytical_x, 1e-4 ) ); + auto & agent = simulation.network.agents[idx_agent]; + fmt::print( "{} \n", agent.data.opinion ); + REQUIRE_THAT( agent.data.opinion, WithinAbs( analytical_x, 1e-4 ) ); } } -TEST_CASE( "Test the probabilistic activity driven model with one bot and one agent", "[activity1Bot1Agent]" ) +TEST_CASE( + "Test the probabilistic activity driven model with one bot and one (reluctant) agent", + "[activity1Bot1AgentReluctance]" ) { using namespace Seldon; using namespace Catch::Matchers; + using AgentT = ActivityDrivenModel::AgentT; auto proj_root_path = fs::current_path(); auto input_file = proj_root_path / fs::path( "test/res/1bot_1agent_activity_prob.toml" ); auto options = Config::parse_config_file( input_file.string() ); + Config::print_settings( options ); - auto simulation = Simulation( options, std::nullopt, std::nullopt ); + auto simulation = Simulation( options, std::nullopt, std::nullopt ); // We need an output path for Simulation, but we won't write anything out there fs::path output_dir_path = proj_root_path / fs::path( "test/output" ); // Get the bot opinion (which won't change) - auto * bot = simulation.model->get_agent_as( 0 ); - auto x_bot = bot->data.opinion; // Bot opinion + auto bot = simulation.network.agents[0]; + auto x_bot = bot.data.opinion; // Bot opinion + fmt::print( "We have set the bot opinion = {}\n", x_bot ); + // Get the initial agent opinion - auto * agent = simulation.model->get_agent_as( 1 ); - agent->data.opinion = 1.0; - auto x_0 = agent->data.opinion; + auto & agent = simulation.network.agents[1]; + auto x_0 = agent.data.opinion; // Agent opinion fmt::print( "We have set agent x_0 = {}\n", x_0 ); simulation.run( output_dir_path ); @@ -117,8 +124,11 @@ TEST_CASE( "Test the probabilistic activity driven model with one bot and one ag auto time_elapsed = iterations * dt; // Final agent and bot opinions after the simulation run - auto x_t = agent->data.opinion; - auto x_t_bot = bot->data.opinion; + auto x_t = agent.data.opinion; + auto x_t_bot = bot.data.opinion; + auto reluctance = agent.data.reluctance; + + fmt::print( "Agent reluctance is = {}\n", reluctance ); // The bot opinion should not change during the simulation REQUIRE_THAT( x_t_bot, WithinAbs( x_bot, 1e-16 ) ); @@ -126,15 +136,17 @@ TEST_CASE( "Test the probabilistic activity driven model with one bot and one ag // Test that the agent opinion matches the analytical solution for an agent with a bot // Analytical solution is: // x_t = [x(0) - Ktanh(alpha*x_bot)]e^(-t) + Ktanh(alpha*x_bot) - auto x_t_analytical = ( x_0 - K * tanh( alpha * x_bot ) ) * exp( -time_elapsed ) + K * tanh( alpha * x_bot ); + auto x_t_analytical = ( x_0 - K / reluctance * tanh( alpha * x_bot ) ) * exp( -time_elapsed ) + + K / reluctance * tanh( alpha * x_bot ); - REQUIRE_THAT( x_t, WithinAbs( x_t_analytical, 1e-6 ) ); + REQUIRE_THAT( x_t, WithinAbs( x_t_analytical, 1e-5 ) ); } TEST_CASE( "Test the meanfield activity driven model with 10 agents", "[activityMeanfield10Agents]" ) { using namespace Seldon; using namespace Catch::Matchers; + using AgentT = ActivityDrivenModel::AgentT; auto proj_root_path = fs::current_path(); auto input_file = proj_root_path / fs::path( "test/res/10_agents_meanfield_activity.toml" ); @@ -161,8 +173,9 @@ TEST_CASE( "Test the meanfield activity driven model with 10 agents", "[activity auto mean_activity = dist.mean(); // Calculate the critical controversialness - auto set_opinions_and_run = [&]( bool above_critical_controversialness ) { - auto simulation = Simulation( options, std::nullopt, std::nullopt ); + auto set_opinions_and_run = [&]( bool above_critical_controversialness ) + { + auto simulation = Simulation( options, std::nullopt, std::nullopt ); auto initial_opinion_delta = 0.1; // Set the initial opinion in the interval [-delta, delta] fmt::print( "Set alpha to {}\n", model_settings.alpha ); @@ -170,15 +183,15 @@ TEST_CASE( "Test the meanfield activity driven model with 10 agents", "[activity // Check that all activities are indeed the expected mean activity and set the opinions in a small interval around 0 for( size_t idx_agent = 0; idx_agent < n_agents; idx_agent++ ) { - auto * agent = simulation.model->get_agent_as( idx_agent ); - REQUIRE_THAT( mean_activity, WithinAbs( agent->data.activity, 1e-16 ) ); - agent->data.opinion + auto & agent = simulation.network.agents[idx_agent]; + REQUIRE_THAT( mean_activity, WithinAbs( agent.data.activity, 1e-16 ) ); + agent.data.opinion = -initial_opinion_delta + 2.0 * idx_agent / ( n_agents - 1 ) * ( initial_opinion_delta ); } // We need an output path for Simulation, but we won't write anything out there fs::path output_dir_path = proj_root_path / fs::path( "test/output_meanfield_test" ); - fs::create_directories( output_dir_path ); + // fs::create_directories( output_dir_path ); // run that mofo simulation.run( output_dir_path ); @@ -188,13 +201,13 @@ TEST_CASE( "Test the meanfield activity driven model with 10 agents", "[activity double avg_deviation = 0.0; for( size_t idx_agent = 0; idx_agent < n_agents; idx_agent++ ) { - auto * agent = simulation.model->get_agent_as( idx_agent ); + auto & agent = simulation.network.agents[idx_agent]; if( above_critical_controversialness ) - REQUIRE( std::abs( agent->data.opinion ) > std::abs( initial_opinion_delta ) ); + REQUIRE( std::abs( agent.data.opinion ) > std::abs( initial_opinion_delta ) ); else - REQUIRE( std::abs( agent->data.opinion ) < std::abs( initial_opinion_delta ) ); + REQUIRE( std::abs( agent.data.opinion ) < std::abs( initial_opinion_delta ) ); - avg_deviation += std::abs( agent->data.opinion ); + avg_deviation += std::abs( agent.data.opinion ); } fmt::print( "Average deviation of agents = {}\n", avg_deviation / n_agents ); diff --git a/test/test_deGroot.cpp b/test/test_deGroot.cpp index f85c4c7..8d78d0a 100644 --- a/test/test_deGroot.cpp +++ b/test/test_deGroot.cpp @@ -1,6 +1,7 @@ #include #include +#include "config_parser.hpp" #include "models/DeGroot.hpp" #include "network.hpp" #include @@ -9,6 +10,7 @@ TEST_CASE( "Test the DeGroot Model Symmetric", "[DeGroot]" ) { using namespace Seldon; using namespace Catch::Matchers; + using Network = Network; size_t n_agents = 2; auto neighbour_list = std::vector>{ @@ -22,22 +24,22 @@ TEST_CASE( "Test the DeGroot Model Symmetric", "[DeGroot]" ) }; auto network = Network( std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming ); - auto model = DeGrootModel( n_agents, network ); + auto model = DeGrootModel( network ); - model.convergence_tol = 1e-6; - model.max_iterations = 100; - model.agents[0].data = 0.0; - model.agents[1].data = 1.0; + model.convergence_tol = 1e-6; + model.max_iterations = 100; + network.agents[0].data = 0.0; + network.agents[1].data = 1.0; do { model.iteration(); } while( !model.finished() ); - INFO( fmt::format( "N_iterations = {} (with convergence_tol {})\n", model.n_iterations, model.convergence_tol ) ); + INFO( fmt::format( "N_iterations = {} (with convergence_tol {})\n", model.n_iterations(), model.convergence_tol ) ); for( size_t i = 0; i < n_agents; i++ ) { - INFO( fmt::format( "Opinion {} = {}\n", i, model.agents[i].data ) ); - REQUIRE_THAT( model.agents[i].data, WithinAbs( 0.5, model.convergence_tol * 10.0 ) ); + INFO( fmt::format( "Opinion {} = {}\n", i, network.agents[i].data ) ); + REQUIRE_THAT( network.agents[i].data, WithinAbs( 0.5, model.convergence_tol * 10.0 ) ); } } \ No newline at end of file diff --git a/test/test_io.cpp b/test/test_io.cpp index 6551fb2..e79fd21 100644 --- a/test/test_io.cpp +++ b/test/test_io.cpp @@ -17,22 +17,24 @@ TEST_CASE( "Test reading in the network from a file", "[io_network]" ) { using namespace Seldon; using namespace Catch::Matchers; + using AgentT = ActivityDrivenModel::AgentT; + using Network = Network; auto proj_root_path = fs::current_path(); auto network_file = proj_root_path / fs::path( "test/res/network.txt" ); - auto network = Seldon::NetworkGeneration::generate_from_file( network_file ); + auto network = Seldon::NetworkGeneration::generate_from_file( network_file ); - REQUIRE( network->n_agents() == 3 ); + REQUIRE( network.n_agents() == 3 ); std::vector> neighbours_expected = { { 2, 1 }, {}, { 1 } }; std::vector> weights_expected = { { 0.1, -0.2 }, {}, { 1.2 } }; - for( size_t i = 0; i < network->n_agents(); i++ ) + for( size_t i = 0; i < network.n_agents(); i++ ) { fmt::print( "{}", i ); - REQUIRE_THAT( neighbours_expected[i], Catch::Matchers::UnorderedRangeEquals( network->get_neighbours( i ) ) ); - REQUIRE_THAT( weights_expected[i], Catch::Matchers::UnorderedRangeEquals( network->get_weights( i ) ) ); + REQUIRE_THAT( neighbours_expected[i], Catch::Matchers::UnorderedRangeEquals( network.get_neighbours( i ) ) ); + REQUIRE_THAT( weights_expected[i], Catch::Matchers::UnorderedRangeEquals( network.get_weights( i ) ) ); } } @@ -44,10 +46,11 @@ TEST_CASE( "Test reading in the agents from a file", "[io_agents]" ) auto proj_root_path = fs::current_path(); auto network_file = proj_root_path / fs::path( "test/res/opinions.txt" ); - auto agents = Seldon::AgentGeneration::generate_from_file( network_file ); + auto agents = Seldon::AgentGeneration::generate_from_file( network_file ); - std::vector opinions_expected = { 2.1127107987061544, 0.8088982488089491, -0.8802809369462433 }; - std::vector activities_expected = { 0.044554683389757696, 0.015813166022685163, 0.015863953902810535 }; + std::vector opinions_expected = { 2.1127107987061544, 0.8088982488089491, -0.8802809369462433 }; + std::vector activities_expected = { 0.044554683389757696, 0.015813166022685163, 0.015863953902810535 }; + std::vector reluctances_expected = { 1.0, 1.0, 2.3 }; REQUIRE( agents.size() == 3 ); @@ -56,5 +59,6 @@ TEST_CASE( "Test reading in the agents from a file", "[io_agents]" ) fmt::print( "{}", i ); REQUIRE_THAT( agents[i].data.opinion, Catch::Matchers::WithinAbs( opinions_expected[i], 1e-16 ) ); REQUIRE_THAT( agents[i].data.activity, Catch::Matchers::WithinAbs( activities_expected[i], 1e-16 ) ); + REQUIRE_THAT( agents[i].data.reluctance, Catch::Matchers::WithinAbs( reluctances_expected[i], 1e-16 ) ); } } \ No newline at end of file diff --git a/test/test_network.cpp b/test/test_network.cpp index 26670af..48d83da 100644 --- a/test/test_network.cpp +++ b/test/test_network.cpp @@ -9,24 +9,25 @@ TEST_CASE( "Testing the network class" ) { using namespace Seldon; + using Network = Network; // Generate some network const size_t n_agents = 20; const size_t n_connections = 10; std::mt19937 gen( 0 ); - auto network = NetworkGeneration::generate_n_connections( n_agents, n_connections, false, gen ); + auto network = NetworkGeneration::generate_n_connections( n_agents, n_connections, false, gen ); // Does n_agents work? - REQUIRE( network->n_agents() == n_agents ); + REQUIRE( network.n_agents() == n_agents ); // Does n_edges work? - REQUIRE( network->n_edges() == n_agents * n_connections ); + REQUIRE( network.n_edges() == n_agents * n_connections ); // Check that the function for setting neighbours and a single weight work // Agent 3 - std::vector neigh{ { 0, 10 } }; // new neighbours - std::vector weight{ 0.5, 0.5 }; // new weights (const) - network->set_neighbours_and_weights( 3, neigh, 0.5 ); - auto buffer_w_get = network->get_weights( 3 ); + std::vector neigh{ { 0, 10 } }; // new neighbours + std::vector weight{ 0.5, 0.5 }; // new weights (const) + network.set_neighbours_and_weights( 3, neigh, 0.5 ); + auto buffer_w_get = network.get_weights( 3 ); REQUIRE_THAT( weight, Catch::Matchers::UnorderedRangeEquals( buffer_w_get ) ); @@ -34,43 +35,43 @@ TEST_CASE( "Testing the network class" ) { weight = { 0.25, 0.55 }; - network->set_weights( 3, weight ); - auto buffer_w_get = network->get_weights( 3 ); + network.set_weights( 3, weight ); + auto buffer_w_get = network.get_weights( 3 ); REQUIRE_THAT( weight, Catch::Matchers::UnorderedRangeEquals( buffer_w_get ) ); - REQUIRE( network->n_edges( 3 ) == 2 ); + REQUIRE( network.n_edges( 3 ) == 2 ); - size_t & n = network->get_neighbours( 3 )[0]; + size_t & n = network.get_neighbours( 3 )[0]; REQUIRE( n == neigh[0] ); n = 2; - REQUIRE( network->get_neighbours( 3 )[0] == 2 ); + REQUIRE( network.get_neighbours( 3 )[0] == 2 ); - Seldon::Network::WeightT & w = network->get_weights( 3 )[1]; + Network::WeightT & w = network.get_weights( 3 )[1]; REQUIRE( w == 0.55 ); w = 0.9; - REQUIRE( network->get_weights( 3 )[1] == w ); + REQUIRE( network.get_weights( 3 )[1] == w ); } SECTION( "Checking that set_neighbours_and_weights works with a vector of weights, push_back and transpose" ) { // Change the connections for agent 3 - std::vector buffer_n{ { 0, 10, 15 } }; // new neighbours - std::vector buffer_w{ 0.1, 0.2, 0.3 }; // new weights - network->set_neighbours_and_weights( 3, buffer_n, buffer_w ); + std::vector buffer_n{ { 0, 10, 15 } }; // new neighbours + std::vector buffer_w{ 0.1, 0.2, 0.3 }; // new weights + network.set_neighbours_and_weights( 3, buffer_n, buffer_w ); // Make sure the changes worked - auto buffer_n_get = network->get_neighbours( 3 ); - auto buffer_w_get = network->get_weights( 3 ); + auto buffer_n_get = network.get_neighbours( 3 ); + auto buffer_w_get = network.get_weights( 3 ); REQUIRE_THAT( buffer_n_get, Catch::Matchers::UnorderedRangeEquals( buffer_n ) ); REQUIRE_THAT( buffer_w_get, Catch::Matchers::UnorderedRangeEquals( buffer_w ) ); // Check that the push_back function works for agent 3 - buffer_n.push_back( 5 ); // new neighbour - buffer_w.push_back( 1.0 ); // new weight for this new connection - network->push_back_neighbour_and_weight( 3, 5, 1.0 ); // new connection added with weight + buffer_n.push_back( 5 ); // new neighbour + buffer_w.push_back( 1.0 ); // new weight for this new connection + network.push_back_neighbour_and_weight( 3, 5, 1.0 ); // new connection added with weight // Check that the change worked for the push_back function - buffer_n_get = network->get_neighbours( 3 ); - buffer_w_get = network->get_weights( 3 ); + buffer_n_get = network.get_neighbours( 3 ); + buffer_w_get = network.get_weights( 3 ); REQUIRE_THAT( buffer_n_get, Catch::Matchers::UnorderedRangeEquals( buffer_n ) ); REQUIRE_THAT( buffer_w_get, Catch::Matchers::UnorderedRangeEquals( buffer_w ) ); @@ -78,10 +79,10 @@ TEST_CASE( "Testing the network class" ) // First record all the old edges as tuples (i,j,w) where this edge goes from j -> i with weight w std::set> old_edges; - for( size_t i_agent = 0; i_agent < network->n_agents(); i_agent++ ) + for( size_t i_agent = 0; i_agent < network.n_agents(); i_agent++ ) { - auto buffer_n = network->get_neighbours( i_agent ); - auto buffer_w = network->get_weights( i_agent ); + auto buffer_n = network.get_neighbours( i_agent ); + auto buffer_w = network.get_weights( i_agent ); for( size_t i_neighbour = 0; i_neighbour < buffer_n.size(); i_neighbour++ ) { @@ -92,18 +93,18 @@ TEST_CASE( "Testing the network class" ) } } - auto old_direction = network->direction(); - network->toggle_incoming_outgoing(); - auto new_direction = network->direction(); + auto old_direction = network.direction(); + network.toggle_incoming_outgoing(); + auto new_direction = network.direction(); // Direction should have changed as well REQUIRE( old_direction != new_direction ); // Now we go over the toggled network and try to re-identify all edges - for( size_t i_agent = 0; i_agent < network->n_agents(); i_agent++ ) + for( size_t i_agent = 0; i_agent < network.n_agents(); i_agent++ ) { - auto buffer_n = network->get_neighbours( i_agent ); - auto buffer_w = network->get_weights( i_agent ); + auto buffer_n = network.get_neighbours( i_agent ); + auto buffer_w = network.get_weights( i_agent ); for( size_t i_neighbour = 0; i_neighbour < buffer_n.size(); i_neighbour++ ) { diff --git a/test/test_network_generation.cpp b/test/test_network_generation.cpp index e7e6e39..5dc4baf 100644 --- a/test/test_network_generation.cpp +++ b/test/test_network_generation.cpp @@ -9,6 +9,7 @@ TEST_CASE( "Testing the network generation functions" ) { using namespace Seldon; + using Network = Network; using WeightT = Network::WeightT; std::vector buffer_n_get{}; // buffer for getting neighbours @@ -23,15 +24,15 @@ TEST_CASE( "Testing the network generation functions" ) { WeightT weight = 0.25; std::vector weights{ weight, weight, weight }; // Weights to set to - auto network = NetworkGeneration::generate_fully_connected( n_agents, weight ); + auto network = NetworkGeneration::generate_fully_connected( n_agents, weight ); // Make sure that the network has been generated correctly - REQUIRE( network->n_agents() == n_agents ); // There should be n_agents in the new network + REQUIRE( network.n_agents() == n_agents ); // There should be n_agents in the new network // All neighbours should be equal to neigh for( size_t i = 0; i < n_agents; ++i ) { - auto buffer_n_get = network->get_neighbours( i ); - auto buffer_w_get = network->get_weights( i ); + auto buffer_n_get = network.get_neighbours( i ); + auto buffer_w_get = network.get_weights( i ); REQUIRE_THAT( buffer_n_get, Catch::Matchers::UnorderedRangeEquals( neigh ) ); REQUIRE_THAT( buffer_w_get, Catch::Matchers::UnorderedRangeEquals( weights ) ); }