Tutorial 5¶

Exercise 1¶

In [116]:
import matplotlib.pyplot as plt
import numpy as np
import networkx as nx
import random

Define OpinionNetwork Class¶

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.

In [117]:
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()
    

Create the OpinionNetwork object¶

In [146]:
set_seeds(8)
n = OpinionNetwork(N=100, d=0.5, mu=0.5, type='albert-barabasi', ab_arg=[5])
Number of edges 475

Draw graph for inspection¶

In [147]:
nx.draw(n.g, with_labels=True)
plt.show()
In [148]:
# Run evolution
history = n.run_evolution(T=30)
In [149]:
# Plot result
n.plot_evolution()

Exercise 2: Repeat for small world and Erdos-Renyi¶

In [150]:
# 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
In [151]:
# 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

Discussion¶

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.

Exercise 3: Repeat for different d numbers¶

In [165]:
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

Discussion¶

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.