33
44import mesa
55from mesa .examples .basic .alliance_formation_model .agents import AllianceAgent
6+ from mesa .experimental .meta_agents .meta_agent import find_combinations
7+ from mesa .experimental .meta_agents .multi_levels import multi_level_agents
68
79
8- class AllianceModel (mesa .Model ):
10+ class MultiLevelAllianceModel (mesa .Model ):
11+ """
12+ Model for simulating multi-level alliances among agents.
13+ """
14+
915 def __init__ (self , n = 50 , mean = 0.5 , std_dev = 0.1 , seed = 42 ):
16+ """
17+ Initialize the model.
18+
19+ Args:
20+ n (int): Number of agents.
21+ mean (float): Mean value for normal distribution.
22+ std_dev (float): Standard deviation for normal distribution.
23+ seed (int): Random seed.
24+ """
1025 super ().__init__ (seed = seed )
1126 self .population = n
1227 self .network = nx .Graph () # Initialize the network
@@ -19,21 +34,147 @@ def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42):
1934 position = np .clip (position , 0 , 1 )
2035 AllianceAgent .create_agents (self , n , power , position )
2136 agent_ids = [
22- (agent .unique_id , {"size" : 300 , "hierarchy " : 0 }) for agent in self .agents
37+ (agent .unique_id , {"size" : 300 , "level " : 0 }) for agent in self .agents
2338 ]
2439 self .network .add_nodes_from (agent_ids )
2540
2641 def add_link (self , meta_agent , agents ):
42+ """
43+ Add links between a meta agent and its constituent agents in the network.
44+
45+ Args:
46+ meta_agent (MetaAgent): The meta agent.
47+ agents (list): List of agents.
48+ """
2749 for agent in agents :
2850 self .network .add_edge (meta_agent .unique_id , agent .unique_id )
2951
52+ def calculate_shapley_value (self , agents ):
53+ """
54+ Calculate the Shapley value of the two agents.
55+
56+ Args:
57+ agents (list): List of agents.
58+
59+ Returns:
60+ tuple: Potential utility, new position, and level.
61+ """
62+ positions = agents .get ("position" )
63+ new_position = 1 - (max (positions ) - min (positions ))
64+ potential_utility = agents .agg ("power" , sum ) * 1.2 * new_position
65+
66+ value_0 = 0.5 * agents [0 ].power + 0.5 * (potential_utility - agents [1 ].power )
67+ value_1 = 0.5 * agents [1 ].power + 0.5 * (potential_utility - agents [0 ].power )
68+
69+ if value_0 > agents [0 ].power and value_1 > agents [1 ].power :
70+ if agents [0 ].level > agents [1 ].level :
71+ level = agents [0 ].level
72+ elif agents [0 ].level == agents [1 ].level :
73+ level = agents [0 ].level + 1
74+ else :
75+ level = agents [1 ].level
76+
77+ return potential_utility , new_position , level
78+
79+ def only_best_combination (self , combinations ):
80+ """
81+ Filter to keep only the best combination for each agent.
82+
83+ Args:
84+ combinations (list): List of combinations.
85+
86+ Returns:
87+ dict: Unique combinations.
88+ """
89+ best = {}
90+ # Determine best option for EACH agent
91+ for group , value in combinations :
92+ agent_ids = sorted (group .get ("unique_id" )) # by default is bilateral
93+ # Deal with all possibilities
94+ if (
95+ agent_ids [0 ] not in best and agent_ids [1 ] not in best
96+ ): # if neither in add both
97+ best [agent_ids [0 ]] = [group , value , agent_ids ]
98+ best [agent_ids [1 ]] = [group , value , agent_ids ]
99+ elif (
100+ agent_ids [0 ] in best and agent_ids [1 ] in best
101+ ): # if both in, see if both would be trading up
102+ if (
103+ value [0 ] > best [agent_ids [0 ]][1 ][0 ]
104+ and value [0 ] > best [agent_ids [1 ]][1 ][0 ]
105+ ):
106+ # Remove the old alliances
107+ del best [best [agent_ids [0 ]][2 ][1 ]]
108+ del best [best [agent_ids [1 ]][2 ][0 ]]
109+ # Add the new alliance
110+ best [agent_ids [0 ]] = [group , value , agent_ids ]
111+ best [agent_ids [1 ]] = [group , value , agent_ids ]
112+ elif (
113+ agent_ids [0 ] in best
114+ ): # if only agent_ids[0] in, see if it would be trading up
115+ if value [0 ] > best [agent_ids [0 ]][1 ][0 ]:
116+ # Remove the old alliance for agent_ids[0]
117+ del best [best [agent_ids [0 ]][2 ][1 ]]
118+ # Add the new alliance
119+ best [agent_ids [0 ]] = [group , value , agent_ids ]
120+ best [agent_ids [1 ]] = [group , value , agent_ids ]
121+ elif (
122+ agent_ids [1 ] in best
123+ ): # if only agent_ids[1] in, see if it would be trading up
124+ if value [0 ] > best [agent_ids [1 ]][1 ][0 ]:
125+ # Remove the old alliance for agent_ids[1]
126+ del best [best [agent_ids [1 ]][2 ][0 ]]
127+ # Add the new alliance
128+ best [agent_ids [0 ]] = [group , value , agent_ids ]
129+ best [agent_ids [1 ]] = [group , value , agent_ids ]
130+
131+ # Create a unique dictionary of the best combinations
132+ unique_combinations = {}
133+ for group , value , agents_nums in best .values ():
134+ unique_combinations [tuple (agents_nums )] = [group , value ]
135+
136+ return unique_combinations .values ()
137+
30138 def step (self ):
31- for agent_class in list (
32- self .agent_types
33- ): # Convert to list to avoid modification during iteration
34- self .agents_by_type [agent_class ].shuffle_do ("form_alliance" )
35-
36- # Update graph
37- if agent_class is not AllianceAgent :
38- for meta_agent in self .agents_by_type [agent_class ]:
39- self .add_link (meta_agent , meta_agent .agents )
139+ """
140+ Execute one step of the model.
141+ """
142+ # Get all other agents of the same type
143+ agent_types = list (self .agents_by_type .keys ())
144+
145+ for agent_type in agent_types :
146+ similar_agents = self .agents_by_type [agent_type ]
147+
148+ # Find the best combinations using find_combinations
149+ if (
150+ len (similar_agents ) > 1
151+ ): # only form alliances if there are more than 1 agent
152+ combinations = find_combinations (
153+ self ,
154+ similar_agents ,
155+ size = 2 ,
156+ evaluation_func = self .calculate_shapley_value ,
157+ filter_func = self .only_best_combination ,
158+ )
159+
160+ for alliance , attributes in combinations :
161+ class_name = f"MetaAgentLevel{ attributes [2 ]} "
162+ meta = multi_level_agents (
163+ self ,
164+ class_name ,
165+ alliance ,
166+ meta_attributes = {
167+ "level" : attributes [2 ],
168+ "power" : attributes [0 ],
169+ "position" : attributes [1 ],
170+ },
171+ )
172+
173+ # Update the network if a new meta agent instance created
174+ if meta :
175+ self .network .add_node (
176+ meta .unique_id ,
177+ size = (meta .level + 1 ) * 300 ,
178+ level = meta .level ,
179+ )
180+ self .add_link (meta , meta .subset )
0 commit comments