"""
==============================================================
Basic Creation, (:mod:`f_abm.src.basic_creation`)
==============================================================
Description
-----------
This module contains all the basic creation functions. It is primarily aimed at creating opinion distributions
and agent parameters, since for digraph creation there is a separate module
Functions
---------
- a_random_digraph
- a_random_initial_opinion_distribution
- a_random_inner_trait_assignation
- create_inner_traits_local
- create_many_opinions
- create_many_inner_traits
"""
import random
import numpy as np
import matplotlib.pyplot as plt
from .auxiliary_functions import (create_random_numbers, modify_opinions_method_1, modify_opinions_method_2,
modify_mean, make_row_stochastic, histogram_classification)
from .digraph_creation import small_world_digraph
from .plot_functions import plot_inner_traits
[docs]def a_random_digraph(num_agents=10):
"""
This function returns a random digraph, NOT a digraph with random topology, but digraph with a topology chosen at
random, currently the possible topologies include.
Update: currently, this function only returns digraphs with the small-world topology.
Parameters
----------
num_agents: number of agents, by default 10
Returns
-------
a random digraph
"""
opinion_param_1 = [[0, -1.0, 1.0, 1]]
opinion_param_2 = [[0, 0.0, 1.0, 1]]
opinion_param_3 = [[0, -1.0, 0.0, 1]]
opinion_param_4 = [[1, 0.0, 1.0, 1]]
opinion_param_5 = [[1, -0.5, 0.5, 1], [1, 0.5, 0.5, 1]]
opinion_param_6 = [[0, -1.0, -0.5, 1], [0, 0.5, 1.0, 1]]
opinion_param_7 = [[0, -1.0, -0.7, 1], [1, 0.5, 0.5, 1]]
opinion_param_8 = [[1, -0.5, 0.5, 1], [0, 0.7, 1.0, 1]]
opinion_param_9 = [[0, -1.0, -0.7, 1], [0, -0.2, 0.2, 1], [0, 0.7, 1.0, 1]]
all_param = [opinion_param_1,
opinion_param_2,
opinion_param_3,
opinion_param_4,
opinion_param_5,
opinion_param_6,
opinion_param_7,
opinion_param_8,
opinion_param_9]
rng = np.random.default_rng() # this is for the random numbers creation
# Topology signature
all_signatures = [[0, 1, 3, -5],
[0, 5, 10, 20],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[0, 2, 4, 6, 8, 10, 12, 14, -2, -4, -6, -8, -10, -12, -14],
[0, 5, 10, 15, 20, 25, -5, -10, -15, -20, -25],
[0, 1, 2, 3, 4, 5, -5, -10, -15, -20, -25]
]
if rng.random(1)[0] < 0.5:
topology_sig = np.array(all_signatures[random.randint(0, len(all_signatures)-1)]) + random.randint(-10, 10)
else:
topology_sig = -1*np.array(all_signatures[random.randint(0, len(all_signatures) - 1)]) + random.randint(-10, 10)
# Change probability
if rng.random(1)[0] < 0.4:
change_prob = float(rng.random(1)[0])
else:
random_param = all_param[random.randint(0, 8)]
change_prob = create_random_numbers(num_agents=num_agents, number_parameters=random_param, limits=(0, 1))
# Reverse probability
if rng.random(1)[0] < 0.4:
reverse_prob = float(rng.random(1)[0])
else:
random_param = all_param[random.randint(0, 8)]
reverse_prob = create_random_numbers(num_agents=num_agents, number_parameters=random_param, limits=(0, 1))
# Bidirectional probability
if rng.random(1)[0] < 0.4:
bidirectional_prob = float(rng.random(1)[0])
else:
random_param = all_param[random.randint(0, 8)]
bidirectional_prob = create_random_numbers(num_agents=num_agents, number_parameters=random_param, limits=(0, 1))
# Number random edges iterations
digraph = small_world_digraph(num_agents=num_agents,
topology_signature=topology_sig,
positive_edge_ratio=0.5+(0.5*rng.random(1)[0]),
change_probability=change_prob,
reverse_probability=reverse_prob,
bidirectional_probability=bidirectional_prob,
num_random_edges_it=random.randint(0, int(np.round(0.4*num_agents*num_agents))))
return digraph
[docs]def a_random_initial_opinion_distribution(num_agents=10):
"""
This function returns a random initial opinion distribution
Parameters
----------
num_agents: number of agents, by default 10
Returns
-------
A random initial opinion distribution
"""
rng = np.random.default_rng() # this is for the random numbers creation
opinion_param_1 = [[0, -1.0, 1.0, 1]]
opinion_param_2 = [[0, 0.0, 1.0, 1]]
opinion_param_3 = [[0, -1.0, 0.0, 1]]
opinion_param_4 = [[1, 0.0, 1.0, 1]]
opinion_param_5 = [[1, -0.5, 0.5, 1], [1, 0.5, 0.5, 1]]
opinion_param_6 = [[0, -1.0, -0.5, 1], [0, 0.5, 1.0, 1]]
opinion_param_7 = [[0, -1.0, -0.7, 1], [1, 0.5, 0.5, 1]]
opinion_param_8 = [[1, -0.5, 0.5, 1], [0, 0.7, 1.0, 1]]
opinion_param_9 = [[0, -1.0, -0.7, 1], [0, -0.2, 0.2, 1], [0, 0.7, 1.0, 1]]
all_param = [opinion_param_1,
opinion_param_2,
opinion_param_3,
opinion_param_4,
opinion_param_5,
opinion_param_6,
opinion_param_7,
opinion_param_8,
opinion_param_9]
local_des_abs_mean = rng.random(1)[0]
local_des_mean = rng.random(1)[0]*local_des_abs_mean
initial_opinions = create_random_numbers(num_agents=num_agents, number_parameters=all_param[random.randint(0, 8)])
if rng.random(1)[0] > 0.5:
return modify_opinions_method_1(initial_opinions, des_mean=local_des_mean,
des_abs_mean=local_des_abs_mean, epsilon=0.02)
else:
return modify_opinions_method_2(initial_opinions, des_mean=local_des_mean,
des_abs_mean=local_des_abs_mean, epsilon=0.02)
[docs]def a_random_inner_trait_assignation(num_agents=10):
"""
This function returns a random inner trait assignation
Parameters
----------
num_agents: number of agents
Returns
-------
Inner trait assignation
"""
rng = np.random.default_rng() # this is for the random numbers creation
opinion_param_1 = [[0, -1.0, 1.0, 1]]
opinion_param_2 = [[0, 0.0, 1.0, 1]]
opinion_param_3 = [[0, -1.0, 0.0, 1]]
opinion_param_4 = [[1, 0.0, 1.0, 1]]
opinion_param_5 = [[1, -0.5, 0.5, 1], [1, 0.5, 0.5, 1]]
opinion_param_6 = [[0, -1.0, -0.5, 1], [0, 0.5, 1.0, 1]]
opinion_param_7 = [[0, -1.0, -0.7, 1], [1, 0.5, 0.5, 1]]
opinion_param_8 = [[1, -0.5, 0.5, 1], [0, 0.7, 1.0, 1]]
opinion_param_9 = [[0, -1.0, -0.7, 1], [0, -0.2, 0.2, 1], [0, 0.7, 1.0, 1]]
all_param = [opinion_param_1,
opinion_param_2,
opinion_param_3,
opinion_param_4,
opinion_param_5,
opinion_param_6,
opinion_param_7,
opinion_param_8,
opinion_param_9] # All parameters for the creation of the inner traits
# Create and modify three sets of numbers which will correspond to the three types of weights
weights_1 = create_random_numbers(num_agents=num_agents, number_parameters=all_param[random.randint(0, 8)],
limits=(0, 1))
weights_1 = modify_mean(weights_1, rng.random(1)[0], max_counter=10, epsilon=0.05, limits=(0, 1))
weights_2 = create_random_numbers(num_agents=num_agents, number_parameters=all_param[random.randint(0, 8)],
limits=(0, 1))
weights_2 = modify_mean(weights_2, rng.random(1)[0], max_counter=10, epsilon=0.05, limits=(0, 1))
weights_3 = create_random_numbers(num_agents=num_agents, number_parameters=all_param[random.randint(0, 8)],
limits=(0, 1))
weights_3 = modify_mean(weights_3, rng.random(1)[0], max_counter=10, epsilon=0.05, limits=(0, 1))
random_choice = random.randint(0, 5)
# Combine the three weights in all possible configurations and make them row-stochastic, then append only
# the first two columns, as a reminder, the first column is the conformist weight, and the second column is
# the radical weight. The truncation is added to make sure that the numbers are indeed between 0 and 1
if random_choice == 0:
inner_traits = np.concatenate((weights_1, weights_2, weights_3), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
elif random_choice == 1:
inner_traits = np.concatenate((weights_1, weights_3, weights_2), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
elif random_choice == 2:
inner_traits = np.concatenate((weights_2, weights_1, weights_3), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
elif random_choice == 3:
inner_traits = np.concatenate((weights_2, weights_3, weights_1), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
elif random_choice == 4:
inner_traits = np.concatenate((weights_3, weights_1, weights_2), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
elif random_choice == 5:
inner_traits = np.concatenate((weights_3, weights_2, weights_1), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
return inner_traits[:, 0:2]
[docs]def create_inner_traits_local(num_agents=100):
"""
Function to create randomly inner trait assignations, these are the agent parameters of the Classification-based
model
Parameters
----------
num_agents: number of agents, by default 100
Returns
-------
inner traits, it is a list of lists, the first element is alpha, the second is beta (the weights of the conformist
and radical trait, respectively)
"""
inner_traits = np.zeros((num_agents, 2))
rng = np.random.default_rng()
for id_agent in range(0, num_agents):
alpha = rng.random()
beta = rng.random()
gamma = rng.random()
total = alpha + beta + gamma
alpha = alpha/total
beta = beta / total
inner_traits[id_agent][0] = alpha
inner_traits[id_agent][1] = beta
return inner_traits
[docs]def create_many_opinions(num_agents=100, file_name='standard_initial_opinions', grid=None, show_result=False):
"""
This function creates and saves many initial opinions to be used later
Parameters
----------
num_agents: the number of agents, by default 100
file_name: name of the file created, by default 'standard_initial_opinions'
grid: it is the reference grid to create the initial opinions
show_result: show the Agreement Plot of the resulting opinions. By default, it is false
Returns
-------
"""
if grid is None:
grid = np.array([[x, y] for x in np.linspace(0, 1, 41) for y in np.linspace(-1, 1, 41) # 11 and 21
if (((y-x) < 0.000001) and ((y+x) > -0.000001))]).round(decimals=3)
# opinion_param_1 = [[0, -0.5, 0.0, 1], [1, 0.9, 0.5, 2], [1, -0.5, 0.2, 10]]
# opinion_param_2 = [[0, -0.5, 0.0, 1]]
# opinion_param_3 = [[0, -0.5, 0.0, 1], [1, -0.9, 0.5, 10]]
opinion_param_1 = [[0, -1.0, 1.0, 1]]
opinion_param_2 = [[0, 0.0, 1.0, 1]]
opinion_param_3 = [[0, -1.0, 0.0, 1]]
opinion_param_4 = [[1, 0.0, 1.0, 1]]
opinion_param_5 = [[1, -0.5, 0.5, 1], [1, 0.5, 0.5, 1]]
opinion_param_6 = [[0, -1.0, -0.5, 1], [0, 0.5, 1.0, 1]]
opinion_param_7 = [[0, -1.0, -0.7, 1], [1, 0.5, 0.5, 1]]
opinion_param_8 = [[1, -0.5, 0.5, 1], [0, 0.7, 1.0, 1]]
opinion_param_9 = [[0, -1.0, -0.7, 1], [0, -0.2, 0.2, 1], [0, 0.7, 1.0, 1]]
all_param = [opinion_param_1,
opinion_param_2,
opinion_param_3,
opinion_param_4,
opinion_param_5,
opinion_param_6,
opinion_param_7,
opinion_param_8,
opinion_param_9]
all_opinions = []
for local_des_abs_mean, local_des_mean in grid:
for oi_param in all_param:
initial_opinions = create_random_numbers(num_agents=num_agents, number_parameters=oi_param)
new_opinions = modify_opinions_method_1(initial_opinions, des_mean=local_des_mean,
des_abs_mean=local_des_abs_mean, epsilon=0.02)
all_opinions.append(new_opinions)
new_opinions = modify_opinions_method_2(initial_opinions, des_mean=local_des_mean,
des_abs_mean=local_des_abs_mean, epsilon=0.02)
all_opinions.append(new_opinions)
np.save(file_name, all_opinions) # save the file as "file_name.npy"
# to recover, use
# all_opinions = np.load('file_name.npy') # loads your saved array into variable all_opinions
# https://stackoverflow.com/questions/37996295/how-to-save-numpy-array-into-computer-for-later-use-in-python
if show_result:
point_colors = [(0.16862745, 0.34901961, 0.76470588),
(0.32941176, 0.54901961, 0.18431373),
(0.82745098, 0.39607843, 0.50980392),
(0.94509804, 0.56078431, 0.00392157),
(0.39607843, 0.05098039, 0.10588235)]
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111)
ax.plot([0, 1, 1, 0], [0, -1, 1, 0], linewidth=2, color=(0.2, 0.5, 0.8))
for opinion_distribution in all_opinions:
classification = histogram_classification(opinion_distribution)
ax.plot(np.absolute(opinion_distribution).mean(), opinion_distribution.mean(), 'o', linewidth=1.5,
markersize=2, color=point_colors[classification])
# for local_des_abs_mean, local_des_mean in grid:
# ax.plot(local_des_abs_mean, local_des_mean, 's',
# color=(0.9, 0.1, 0.3))
ax.grid()
plt.show()
return all_opinions
[docs]def create_many_inner_traits(num_agents=100, file_name='standard_inner_traits', grid=None, show_result=False):
"""
This function creates and saves many inner traits to be used later
Parameters
----------
num_agents: the number of agents
file_name: name of the file created
grid: it is the reference grid to create the inner traits
show_result: a boolean determining of the resulting inner traits are shown
Returns
-------
A list of numpy arrays with 'num_agents' rows and 2 columns
"""
all_inner_traits = []
if grid is None:
# Create the standard grid
grid = np.array([[x, y] for x in np.linspace(0, 1, 41) for y in np.linspace(0, 1, 41)
if ((y+x) < 1.000001)]).round(decimals=3)
opinion_param_1 = [[0, -1.0, 1.0, 1]]
opinion_param_2 = [[0, 0.0, 1.0, 1]]
opinion_param_3 = [[0, -1.0, 0.0, 1]]
opinion_param_4 = [[1, 0.0, 1.0, 1]]
opinion_param_5 = [[1, -0.5, 0.5, 1], [1, 0.5, 0.5, 1]]
opinion_param_6 = [[0, -1.0, -0.5, 1], [0, 0.5, 1.0, 1]]
opinion_param_7 = [[0, -1.0, -0.7, 1], [1, 0.5, 0.5, 1]]
opinion_param_8 = [[1, -0.5, 0.5, 1], [0, 0.7, 1.0, 1]]
opinion_param_9 = [[0, -1.0, -0.7, 1], [0, -0.2, 0.2, 1], [0, 0.7, 1.0, 1]]
all_param = [opinion_param_1,
opinion_param_2,
opinion_param_3,
opinion_param_4,
opinion_param_5,
opinion_param_6,
opinion_param_7,
opinion_param_8,
opinion_param_9] # All parameters for the creation of the inner traits
for w1_des, w2_des in grid: # For all the weights in the grid
w3_des = 1 - (w1_des + w2_des)
for oi_param in all_param: # for all the parameters
# Create and modify three sets of numbers which will correspond to the three types of weights
weights_1 = create_random_numbers(num_agents=num_agents, number_parameters=oi_param, limits=(0, 1))
weights_1 = modify_mean(weights_1, w1_des, max_counter=10, epsilon=0.05, limits=(0, 1))
weights_2 = create_random_numbers(num_agents=num_agents, number_parameters=oi_param, limits=(0, 1))
weights_2 = modify_mean(weights_2, w2_des, max_counter=10, epsilon=0.05, limits=(0, 1))
weights_3 = create_random_numbers(num_agents=num_agents, number_parameters=oi_param, limits=(0, 1))
weights_3 = modify_mean(weights_3, w3_des, max_counter=10, epsilon=0.05, limits=(0, 1))
# Combine the three weights in all possible configurations and make them row-stochastic, then append only
# the first two columns, as a reminder, the first column is the conformist weight, and the second column is
# the radical weight. The truncation is added to make sure that the numbers are indeed between 0 and 1
inner_traits = np.concatenate((weights_1, weights_2, weights_3), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
inner_traits = np.concatenate((weights_1, weights_3, weights_2), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
inner_traits = np.concatenate((weights_2, weights_1, weights_3), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
inner_traits = np.concatenate((weights_2, weights_3, weights_1), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
inner_traits = np.concatenate((weights_3, weights_1, weights_2), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
inner_traits = np.concatenate((weights_3, weights_2, weights_1), axis=1)
make_row_stochastic(inner_traits)
inner_traits = np.maximum(np.minimum(inner_traits, 1), 0)
all_inner_traits.append(inner_traits[:, 0:2])
np.save(file_name, all_inner_traits) # save the file as "file_name.npy"
# to recover, use
# all_opinions = np.load('file_name.npy') # loads your saved array into variable all_opinions
# https://stackoverflow.com/questions/37996295/how-to-save-numpy-array-into-computer-for-later-use-in-python
if show_result:
plot_inner_traits(file_name=file_name + '.npy')
return all_inner_traits