#!/usr/bin/env python3 # -*- coding: utf-8 -*- import networkx as nx import random import math """Yields edges between each node and halfk neighbors. halfk: number of edges from each node """ def adjacent_edges(nodes, halfk): n = len(nodes) for i, u in enumerate(nodes): for j in range(i+1, i+halfk+1): v = nodes[j % n] yield u, v """Makes a ring lattice with n nodes and degree z. Note: this only works correctly if z is even. n: number of nodes z: degree of each node """ def make_ring_lattice(N, z): G = nx.Graph() nodes = range(N) G.add_nodes_from(nodes) G.add_edges_from(adjacent_edges(nodes, z//2)) return G """ This function performs the rewiring mechanism in order to construct an homogeneous random graph. The parameter f gives the fraction of edges to be rewired. """ def rewire(G,f): edges_used=set() edges_to_swap=set() num_swaps_to_be_made=int((f*G.number_of_edges())/2) num_swaps_done_so_far=0 while num_swaps_done_so_far1: while cond == 0 and len(neighbors_B)>1: C=random.choice(neighbors_B) if not(C in neighbors_A): graph.remove_edge(A,B) graph.add_edge(A,C) cond=1 else: neighbors_B.remove(C) return graph """ Given an edge with individuals A and B at the extremes, we say that A(B) is satisfied with the edge if the strategy of B(A) is a cooperator, being dissatisfied otherwise. If A is satisfied, she will decide to maintain the link. If dissatisfied, then she may compete with B to rewire the link, rewiring being attempted to a random neighbor of B. """ def structural_update(graph,A,B,cooperators,defectors,S,T): #If A is a cooperator and B is a cooperator, then both are satisfied and do nothing #Else they play the game if not (A in cooperators and B in cooperators): fitness_A=get_individual_fitness(A,graph,S,T,cooperators,defectors) fitness_B=get_individual_fitness(B,graph,S,T,cooperators,defectors) if A in defectors and B in defectors: if flip(fermi_distribution(fitness_A,fitness_B)): graph=rewire_A_to_random_neigh_B(graph,A,B) else: graph=rewire_A_to_random_neigh_B(graph,B,A) elif A in cooperators and B in defectors: if flip(fermi_distribution(fitness_A,fitness_B)): graph=rewire_A_to_random_neigh_B(graph,A,B) elif B in cooperators and A in defectors: if flip(fermi_distribution(fitness_A,fitness_B)): graph=rewire_A_to_random_neigh_B(graph,B,A) return graph """ Synchronous updating change the strategy update to all individuals (nodes) """ def synchronous_updating(graph,cooperators,defectors,S,T): N=len(graph.nodes()) new_cooperators=list() new_defectors=list() for indiv in range(N): neigh_indiv=random.choice(list(graph[indiv].keys())) strategy_update(graph,indiv,neigh_indiv,cooperators,defectors,new_cooperators,new_defectors,S,T) return graph,new_cooperators,new_defectors """ Asynchronous updating selects a random individual and, depending on a probability (where W is involved) a strategy update or a structural update take place """ def asynchronous_updating(graph,cooperators,defectors,S,T,W): indiv=random.choice(list(graph.nodes)) neigh_indiv=random.choice(list(graph[indiv].keys())) if(flip(1/(1+W))): cooperators,defectors=strategy_update2(graph,indiv,neigh_indiv,cooperators,defectors,S,T) else: graph=structural_update(graph,indiv,neigh_indiv,cooperators,defectors,S,T) return graph,cooperators,defectors """ Implements the simulation with the Watson's article approach - S,T values of the game (always S>T) - W 'wave of cooperation' (the higher it's value, the more cooperation prevails) - N number of nodes - z degree of each node - f percentage of rewired nodes after creating the ring lattice - St step between relaxations - NR number of relaxations """ def simulation_relaxations(S,T,W,N,z,f,St,NR): graph=initialize_HoSW(N,z,f) cooperators=list(random.sample(range(N),N//2)) defectors=list(set(range(N))-set(cooperators)) fraction_of_cooperators_per_generation=[] fraction_of_cooperators_to_print=[] mean_of_cooperators_per_relaxation=[] for relaxations in range(NR): for steps in range(St): #If W>0, evolution of strategy and structure proceed together #under asynchronous updating if W>0: graph,cooperators,defectors=asynchronous_updating(graph,cooperators,defectors,S,T,W) #fraction_of_cooperators_per_generation.append(len(cooperators)/N) #a=fraction_of_cooperators_per_generation[-1] #print(a) else: graph,cooperators,defectors=synchronous_updating(graph,cooperators,defectors,S,T) fraction_of_cooperators_per_generation.append(len(cooperators)/N) a=fraction_of_cooperators_per_generation[-1] fraction_of_cooperators_to_print.append(fraction_of_cooperators_per_generation[-1]) #mean_of_cooperators_per_relaxation.append(sum(fraction_of_cooperators_per_generation[-steps:])/steps) cooperators=list(random.sample(range(N),N//2)) # ******Here the relaxation take place***** defectors=list(set(range(N))-set(cooperators)) # ******Here the relaxation take place***** if len(cooperators)!=N: percentage_of_cooperators=fraction_of_cooperators_per_generation[-1] else: percentage_of_cooperators=1.0 return percentage_of_cooperators,k_max(graph),fraction_of_cooperators_to_print # Helper functions def degrees(graph): degs={} for node in graph.nodes(): deg=graph.degree(node) if deg not in degs: degs[deg]=0 degs[deg]+=1 return degs # Return the max degree of a node def k_max(graph): return max(degrees(graph).keys())