import matplotlib.pyplot as plt
import numpy as np
import networkx as nx
import random
The class will hold the agent network, the opinion distribution and run the evolution of the opinions. It will also contain a function for plotting the time evolution of the opinion distribution.
from typing import List
def set_seeds(seed=0):
random.seed(seed)
np.random.seed(seed)
class OpinionNetwork(object):
"""Class representing the network of agents with opinion dymanics
Agents opinionts are represented by an array of shape (N,) with floats.
Value at i-th index represents i-th agent's opinion.
Agent network is sampled according to the given network type:
- albert-barabasi (default)
network_arg is a list that contains a number of new edges
- small-world (Watts Strogatz Graph)
network_arg is a list that contains a number of edges for regular lattice and
a number representing rewire probability
- erdos-renyi (Erdos Renyi Graph)
network_arg is a list containing a number representing the edge creation
probability
"""
def __init__(self, N, d, mu, type='albert-barabasi', ab_arg=[5], sm_arg=[4,0.3], er_arg=[0.3]) -> None:
self.N = N
self.d = d
self.mu = mu
self.type =type
self.opinion_history = []
# Sampling agent array
self.agents = np.random.random(size=(N,))
# Sampling network
if type=='albert-barabasi':
self.g = nx.barabasi_albert_graph(N, ab_arg[0])
elif type=='small-world':
self.g = nx.watts_strogatz_graph(N, sm_arg[0], sm_arg[1])
elif type=='erdos-renyi':
self.g = nx.erdos_renyi_graph(N, er_arg[0])
else:
raise ValueError('Type of network graph invalid.\
Choose either "albert-barabasi", "small-world" or "erdos-renyi".')
print('Number of edges', self.g.number_of_edges())
def step(self) -> None:
"""Function for atomic step of the evolution
- sampling pair of two agents and updating their opinions
according to the rule:
o_i(t+1)=o_i(t) + mu(o_j(t)-o_i(t))
o_j(t+1)=o_j(t) + mu(o_i(t)-o_j(t))
"""
# Pick pair of agents
agent_i = np.random.randint(0,self.N)
agent_j = np.random.choice(list(self.g.adj[agent_i]))
# Extract agents opinions
o_i = self.agents[agent_i]
o_j = self.agents[agent_j]
# Check if opinions are close
if np.abs(o_i-o_j)<self.d:
# Perform opinion update
new_o_i = o_i + self.mu*(o_j-o_i)
new_o_j = o_j + self.mu*(o_i-o_j)
agents_copy = self.agents.copy()
self.agents[agent_i] = new_o_i
self.agents[agent_j] = new_o_j
else:
pass
def run_evolution(self, T) -> List:
"""Run evolution of the dynamics and save distribution of opinions
at each time step.
Return array with histories.
"""
# Initalize history
self.opinion_history = [self.agents.copy()]
# For each time step
for i in range(T):
# Perform update for N//2 pairs of agents
for j in range(self.N//2):
self.step()
# Append opinion distribution to the history
self.opinion_history.append(self.agents.copy())
return self.opinion_history
def plot_evolution(self, times=None, suffix=''):
"""Function to plot evolution of opinions.
The function plots the opinion distribution at
given indices of history (times list).
By default all.
"""
history = self.opinion_history
if times is None:
times = list(range(len(history)))
plt.figure()
for index in times:
time_index_ar = np.ones_like(history[index])*index
plt.scatter(time_index_ar, history[index], color='tab:blue', marker='.')
plt.xlabel('time')
plt.ylabel('opinions')
plt.title(self.type + suffix)
plt.show()
set_seeds(8)
n = OpinionNetwork(N=100, d=0.5, mu=0.5, type='albert-barabasi', ab_arg=[5])
Number of edges 475
nx.draw(n.g, with_labels=True)
plt.show()
# Run evolution
history = n.run_evolution(T=30)
# Plot result
n.plot_evolution()
# Small workd
set_seeds(8)
n = OpinionNetwork(N=100, d=0.5, mu=0.5, type='small-world', sm_arg=[8,0.3])
history = n.run_evolution(T=30)
n.plot_evolution()
Number of edges 400
# Erdos-Renyi
set_seeds(8)
n = OpinionNetwork(N=100, d=0.5, mu=0.5, type='erdos-renyi', er_arg=[0.09])
history = n.run_evolution(T=30)
n.plot_evolution()
Number of edges 418
To obtain comparable plots, the networks were initialized to have a similar number of edges, in this case around 400. The opinion convergence is highly dependend on the network structure and, when number of edges is low, on the specific initalization of the network and the random seed used to sample pairs of agents for the opinion update. That said, the convergence is similar for all types networks - all opinions converge to a value in $[0.4,0.6]$ within 20 time steps of the simulation.
for d in [0.1, 0.3, 0.5, 0.7 ,0.9]:
set_seeds(8)
n = OpinionNetwork(N=100, d=d, mu=0.5, type='albert-barabasi', ab_arg=[5])
history = n.run_evolution(T=100)
n.plot_evolution( suffix=' d=%.1f'%d)
Number of edges 475
Number of edges 475
Number of edges 475
Number of edges 475
Number of edges 475
The parameter "d" corresponds to the threshold for the opinion update. As the threshold gets smaller, agent who have different opinions are less likely to perform the opinion update. As a result, for small thresholds, the agents converge to multiple opinion values. For d=0.1, at the terminal time, there are numeruous groups with different opinions. For d=0.3, there can be a couple of mainstream opinion values. For d=0.5 and above, there is only one converged opinion level. In this regime of high thresholds, increasing the value of d seems to increase the convergence speed. This again is expected, as increasing d effectively increases the number of updates performed per time step.