Skip to content

Commit

Permalink
refactor meta-agent
Browse files Browse the repository at this point in the history
- allow for deliberate meta-agents creation
- allow for combinatorics
- allow for dynamic agent creation

fix methods-functions; add tests
  • Loading branch information
tpike3 committed Jan 6, 2025
1 parent a87d382 commit be1fad7
Show file tree
Hide file tree
Showing 12 changed files with 612 additions and 266 deletions.
4 changes: 2 additions & 2 deletions mesa/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
from mesa.examples.advanced.pd_grid.model import PdGrid
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
from mesa.examples.basic.alliance_formation_model.model import AllianceModel
from mesa.examples.basic.alliance_formation_model.model import MultiLevelAllianceModel
from mesa.examples.basic.boid_flockers.model import BoidFlockers
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
from mesa.examples.basic.schelling.model import Schelling
from mesa.examples.basic.virus_on_network.model import VirusOnNetwork

__all__ = [
"AllianceModel",
"BoidFlockers",
"BoltzmannWealth",
"ConwaysGameOfLife",
"EpsteinCivilViolence",
"MultiLevelAllianceModel",
"PdGrid",
"Schelling",
"SugarscapeG1mt",
Expand Down
2 changes: 1 addition & 1 deletion mesa/examples/basic/alliance_formation_model/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ In its current configuration, agents being part of multiple meta-agents is not s

This model requires Mesa's recommended install and scipy
```
$ pip install mesa[rec] scipy
$ pip install mesa[rec]
```

## How to Run
Expand Down
10 changes: 0 additions & 10 deletions mesa/examples/basic/alliance_formation_model/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +0,0 @@
import logging

# Configure logging
logging.basicConfig(
level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

# Example usage of logging
logger = logging.getLogger(__name__)
logger.info("Logging is configured and ready to use.")
65 changes: 7 additions & 58 deletions mesa/examples/basic/alliance_formation_model/agents.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,20 @@
import mesa
from mesa.experimental.meta_agents import create_meta_agent


def calculate_shapley_value(calling_agent, other_agent):
"""
Calculate the Shapley value of the two agents
"""
new_position = 1 - abs(calling_agent.position - other_agent.position)
potential_utility = (calling_agent.power + other_agent.power) * 1.1 * new_position
value_me = 0.5 * calling_agent.power + 0.5 * (potential_utility - other_agent.power)
value_other = 0.5 * other_agent.power + 0.5 * (
potential_utility - calling_agent.power
)

# Determine if there is value in the alliance
if value_me > calling_agent.power and value_other > other_agent.power:
if other_agent.hierarchy > calling_agent.hierarchy:
hierarchy = other_agent.hierarchy
elif other_agent.hierarchy == calling_agent.hierarchy:
hierarchy = calling_agent.hierarchy + 1
else:
hierarchy = calling_agent.hierarchy

return (potential_utility, new_position, hierarchy)
else:
return None


class AllianceAgent(mesa.Agent):
"""
Agent has three attributes power (float), position (float) and hierarchy (int)
Agent has three attributes power (float), position (float) and level (int)
"""

def __init__(self, model, power, position, hierarchy=0):
def __init__(self, model, power, position, level=0):
super().__init__(model)
self.power = power
self.position = position
self.hierarchy = hierarchy

def form_alliance(self):
# Randomly select another agent of the same type
other_agents = [
agent for agent in self.model.agents_by_type[type(self)] if agent != self
]
self.level = level

# Determine if there is a beneficial alliance
if other_agents:
other_agent = self.random.choice(other_agents)
shapley_value = calculate_shapley_value(self, other_agent)
if shapley_value:
class_name = f"MetaAgentHierarchy{shapley_value[2]}"
meta = create_meta_agent(
self.model,
class_name,
{other_agent, self},
meta_attributes={
"hierarchy": shapley_value[2],
"power": shapley_value[0],
"position": shapley_value[1],
},
)
"""
For this demo model agent only need attributes.
# Update the network if a new meta agent instance created
if meta:
self.model.network.add_node(
meta.unique_id,
size=(meta.hierarchy + 1) * 300,
hierarchy=meta.hierarchy,
)
More complex models could have functions that define agent behavior.
"""
10 changes: 4 additions & 6 deletions mesa/examples/basic/alliance_formation_model/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
import solara
from matplotlib.figure import Figure

from mesa.examples.basic.alliance_formation_model.model import AllianceModel
from mesa.mesa_logging import DEBUG, log_to_stderr
from mesa.examples.basic.alliance_formation_model.model import MultiLevelAllianceModel
from mesa.visualization import SolaraViz
from mesa.visualization.utils import update_counter

log_to_stderr(DEBUG)

model_params = {
"seed": {
"type": "InputText",
Expand Down Expand Up @@ -37,7 +34,7 @@
def plot_network(model):
update_counter.get()
g = model.network
pos = nx.kamada_kawai_layout(g)
pos = nx.fruchterman_reingold_layout(g)
fig = Figure()
ax = fig.subplots()
labels = {agent.unique_id: agent.unique_id for agent in model.agents}
Expand All @@ -58,13 +55,14 @@ def plot_network(model):


# Create initial model instance
model = AllianceModel(50)
model = MultiLevelAllianceModel(50)

# Create the SolaraViz page. This will automatically create a server and display the
# visualization elements in a web browser.
# Display it using the following command in the example directory:
# solara run app.py
# It will automatically update and display any changes made to this file

page = SolaraViz(
model,
components=[plot_network],
Expand Down
163 changes: 152 additions & 11 deletions mesa/examples/basic/alliance_formation_model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,25 @@

import mesa
from mesa.examples.basic.alliance_formation_model.agents import AllianceAgent
from mesa.experimental.meta_agents.meta_agent import find_combinations
from mesa.experimental.meta_agents.multi_levels import multi_level_agents


class AllianceModel(mesa.Model):
class MultiLevelAllianceModel(mesa.Model):
"""
Model for simulating multi-level alliances among agents.
"""

def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42):
"""
Initialize the model.
Args:
n (int): Number of agents.
mean (float): Mean value for normal distribution.
std_dev (float): Standard deviation for normal distribution.
seed (int): Random seed.
"""
super().__init__(seed=seed)
self.population = n
self.network = nx.Graph() # Initialize the network
Expand All @@ -19,21 +34,147 @@ def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42):
position = np.clip(position, 0, 1)
AllianceAgent.create_agents(self, n, power, position)
agent_ids = [
(agent.unique_id, {"size": 300, "hierarchy": 0}) for agent in self.agents
(agent.unique_id, {"size": 300, "level": 0}) for agent in self.agents
]
self.network.add_nodes_from(agent_ids)

def add_link(self, meta_agent, agents):
"""
Add links between a meta agent and its constituent agents in the network.
Args:
meta_agent (MetaAgent): The meta agent.
agents (list): List of agents.
"""
for agent in agents:
self.network.add_edge(meta_agent.unique_id, agent.unique_id)

def calculate_shapley_value(self, agents):
"""
Calculate the Shapley value of the two agents.
Args:
agents (list): List of agents.
Returns:
tuple: Potential utility, new position, and level.
"""
positions = agents.get("position")
new_position = 1 - (max(positions) - min(positions))
potential_utility = agents.agg("power", sum) * 1.2 * new_position

value_0 = 0.5 * agents[0].power + 0.5 * (potential_utility - agents[1].power)
value_1 = 0.5 * agents[1].power + 0.5 * (potential_utility - agents[0].power)

if value_0 > agents[0].power and value_1 > agents[1].power:
if agents[0].level > agents[1].level:
level = agents[0].level

Check warning on line 71 in mesa/examples/basic/alliance_formation_model/model.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/model.py#L71

Added line #L71 was not covered by tests
elif agents[0].level == agents[1].level:
level = agents[0].level + 1
else:
level = agents[1].level

Check warning on line 75 in mesa/examples/basic/alliance_formation_model/model.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/model.py#L75

Added line #L75 was not covered by tests

return potential_utility, new_position, level

def only_best_combination(self, combinations):
"""
Filter to keep only the best combination for each agent.
Args:
combinations (list): List of combinations.
Returns:
dict: Unique combinations.
"""
best = {}
# Determine best option for EACH agent
for group, value in combinations:
agent_ids = sorted(group.get("unique_id")) # by default is bilateral
# Deal with all possibilities
if (
agent_ids[0] not in best and agent_ids[1] not in best
): # if neither in add both
best[agent_ids[0]] = [group, value, agent_ids]
best[agent_ids[1]] = [group, value, agent_ids]
elif (
agent_ids[0] in best and agent_ids[1] in best
): # if both in, see if both would be trading up
if (
value[0] > best[agent_ids[0]][1][0]
and value[0] > best[agent_ids[1]][1][0]
):
# Remove the old alliances
del best[best[agent_ids[0]][2][1]]
del best[best[agent_ids[1]][2][0]]
# Add the new alliance
best[agent_ids[0]] = [group, value, agent_ids]
best[agent_ids[1]] = [group, value, agent_ids]
elif (
agent_ids[0] in best
): # if only agent_ids[0] in, see if it would be trading up
if value[0] > best[agent_ids[0]][1][0]:
# Remove the old alliance for agent_ids[0]
del best[best[agent_ids[0]][2][1]]
# Add the new alliance
best[agent_ids[0]] = [group, value, agent_ids]
best[agent_ids[1]] = [group, value, agent_ids]
elif (
agent_ids[1] in best
): # if only agent_ids[1] in, see if it would be trading up
if value[0] > best[agent_ids[1]][1][0]:
# Remove the old alliance for agent_ids[1]
del best[best[agent_ids[1]][2][0]]
# Add the new alliance
best[agent_ids[0]] = [group, value, agent_ids]
best[agent_ids[1]] = [group, value, agent_ids]

# Create a unique dictionary of the best combinations
unique_combinations = {}
for group, value, agents_nums in best.values():
unique_combinations[tuple(agents_nums)] = [group, value]

return unique_combinations.values()

def step(self):
for agent_class in list(
self.agent_types
): # Convert to list to avoid modification during iteration
self.agents_by_type[agent_class].shuffle_do("form_alliance")

# Update graph
if agent_class is not AllianceAgent:
for meta_agent in self.agents_by_type[agent_class]:
self.add_link(meta_agent, meta_agent.agents)
"""
Execute one step of the model.
"""
# Get all other agents of the same type
agent_types = list(self.agents_by_type.keys())

for agent_type in agent_types:
similar_agents = self.agents_by_type[agent_type]

# Find the best combinations using find_combinations
if (
len(similar_agents) > 1
): # only form alliances if there are more than 1 agent
combinations = find_combinations(
self,
similar_agents,
size=2,
evaluation_func=self.calculate_shapley_value,
filter_func=self.only_best_combination,
)

for alliance, attributes in combinations:
class_name = f"MetaAgentLevel{attributes[2]}"
meta = multi_level_agents(
self,
class_name,
alliance,
meta_attributes={
"level": attributes[2],
"power": attributes[0],
"position": attributes[1],
},
)

# Update the network if a new meta agent instance created
if meta:
self.network.add_node(
meta.unique_id,
size=(meta.level + 1) * 300,
level=meta.level,
)
self.add_link(meta, meta.subset)
5 changes: 3 additions & 2 deletions mesa/experimental/meta_agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""

from .meta_agents import create_meta_agent
from .meta_agent import MetaAgent
from .multi_levels import multi_level_agents

__all__ = ["create_meta_agent"]
__all__ = ["MetaAgent", "multi_level_agents"]
Loading

0 comments on commit be1fad7

Please sign in to comment.