Skip to content

Commit

Permalink
Network: templated network to Simulation
Browse files Browse the repository at this point in the history
WIP commit, with simulation run functions and IO commented out. But hey
presto, it compiles!

Co-authored-by: Moritz Sallermann <[email protected]>
  • Loading branch information
amritagos and MSallermann committed Mar 14, 2024
1 parent 8cd3a2c commit 1371eb9
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 443 deletions.
3 changes: 2 additions & 1 deletion include/models/ActivityDrivenModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ inline void Agent<ActivityAgentData>::from_string( const std::string & str )
class ActivityAgentModel : public Model<Agent<ActivityAgentData>>
{
public:
using AgentT = Agent<ActivityAgentData>;
using AgentT = Agent<ActivityAgentData>;
using Network = Network<AgentT>;

private:
Network & network;
Expand Down
13 changes: 7 additions & 6 deletions include/models/DeGroot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ namespace Seldon

class DeGrootModel : public Model<Agent<double>>
{

private:
double max_opinion_diff = 0;
Network & network;
std::vector<AgentT> agents_current_copy;

public:
using AgentT = Agent<double>;
using Network = Network<AgentT>;
double convergence_tol = 1e-12;

DeGrootModel( int n_agents, Network & network );

void iteration() override;
bool finished() override;

private:
double max_opinion_diff = 0;
Network & network;
std::vector<AgentT> agents_current_copy;
};

} // namespace Seldon
9 changes: 7 additions & 2 deletions include/network.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Seldon
Note: switch is equivalent to toggle + transpose, but much cheaper!
*/
// template<typename AgentType>
template<typename AgentType>
class Network
{
public:
Expand All @@ -38,11 +38,15 @@ class Network
};

using WeightT = double;
using AgentT = AgentType;

Network(
std::vector<std::vector<size_t>> && neighbour_list, std::vector<std::vector<WeightT>> && weight_list,
EdgeDirection direction )
: neighbour_list( neighbour_list ), weight_list( weight_list ), _direction( direction )
: neighbour_list( neighbour_list ),
weight_list( weight_list ),
_direction( direction ),
agents( std::vector<AgentT>( neighbour_list.size() ) )
{
}

Expand Down Expand Up @@ -221,6 +225,7 @@ class Network
std::vector<std::vector<size_t>> neighbour_list; // Neighbour list for the connections
std::vector<std::vector<WeightT>> weight_list; // List for the interaction weights of each connection
EdgeDirection _direction;
std::vector<AgentT> agents; // List of agents of type AgentType
};

} // namespace Seldon
237 changes: 232 additions & 5 deletions include/network_generation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,242 @@
#include <cstddef>
#include <memory>
#include <random>
#include <util/math.hpp>
#include <util/misc.hpp>

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<Network>
generate_n_connections( size_t n_agents, size_t n_connections, bool self_interaction, std::mt19937 & gen );
std::unique_ptr<Network> generate_fully_connected( size_t n_agents, Network::WeightT weight = 0.0 );
std::unique_ptr<Network> generate_fully_connected( size_t n_agents, std::mt19937 & gen );
std::unique_ptr<Network> generate_from_file( const std::string & file );
template<typename AgentType>
std::unique_ptr<Network<AgentType>>
generate_n_connections( size_t n_agents, size_t n_connections, bool self_interaction, std::mt19937 & gen )
{
using Network = Network<AgentType>;
using WeightT = Network::WeightT;

std::vector<std::vector<size_t>> neighbour_list; // Neighbour list for the connections
std::vector<std::vector<WeightT>> 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<size_t>(); // for the j_agents indices connected to i_agent (adjacencies/neighbours)
auto incoming_neighbour_weights = std::vector<WeightT>(); // 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<Network>(
std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming );
}

template<typename AgentType>
std::unique_ptr<Network<AgentType>>
generate_fully_connected( size_t n_agents, typename Network<AgentType>::WeightT weight = 0.0 )
{
using Network = Network<AgentType>;
using WeightT = Network::WeightT;

std::vector<std::vector<size_t>> neighbour_list; // Neighbour list for the connections
std::vector<std::vector<WeightT>> weight_list; // List for the interaction weights of each connection
auto incoming_neighbour_buffer
= std::vector<size_t>( n_agents ); // for the j_agents indices connected to i_agent (adjacencies/neighbours)
auto incoming_neighbour_weights
= std::vector<WeightT>( 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<Network>(
std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming );
}

template<typename AgentType>
std::unique_ptr<Network<AgentType>> generate_fully_connected( size_t n_agents, std::mt19937 & gen )
{
using Network = Network<AgentType>;
using WeightT = Network::WeightT;

std::vector<std::vector<size_t>> neighbour_list; // Neighbour list for the connections
std::vector<std::vector<WeightT>> weight_list; // List for the interaction weights of each connection
auto incoming_neighbour_buffer
= std::vector<size_t>( 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<WeightT>( 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<Network>(
std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming );
}

template<typename AgentType>
std::unique_ptr<Network<AgentType>> generate_from_file( const std::string & file )
{
using Network = Network<AgentType>;
using WeightT = Network::WeightT;
std::vector<std::vector<size_t>> neighbour_list; // Neighbour list for the connections
std::vector<std::vector<WeightT>> 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<Network>(
std::move( neighbour_list ), std::move( weight_list ), Network::EdgeDirection::Incoming );
}
} // namespace Seldon::NetworkGeneration
Loading

0 comments on commit 1371eb9

Please sign in to comment.