Skip to content

Meta agents #2748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cd33057
meta_agents
tpike3 Dec 28, 2024
593bbe7
Merge branch 'projectmesa:main' into meta-agents
tpike3 Dec 28, 2024
5bc7b95
Meta-Agents
tpike3 Dec 29, 2024
a87d382
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 29, 2024
c7c8311
refactor meta-agent
tpike3 Dec 31, 2024
3b9fa59
Merge branch 'main' into meta-agents
tpike3 Jan 6, 2025
3529b7d
Basic bugfix for bug ContinuousSpace.get_neighbors (#2599)
quaquel Jan 6, 2025
1b0ad15
[pre-commit.ci] pre-commit autoupdate (#2601)
pre-commit-ci[bot] Jan 7, 2025
70375d1
Reimplementation of Continuous Space (#2584)
quaquel Jan 10, 2025
804ea95
reimplementation of draw_voroinoi (#2608)
quaquel Jan 10, 2025
16261f5
Add render interval slider to control visualization update frequency …
HMNS19 Jan 10, 2025
80a4f2b
Add docs for continuous space (#2613)
quaquel Jan 11, 2025
3f460cf
Update release notes and version for 3.1.3 (#2612)
EwoutH Jan 11, 2025
291da0e
remove remnants of mesa cli (#2617)
quaquel Jan 14, 2025
7eacc72
remove any reference to using --pre (#2618)
quaquel Jan 14, 2025
6e44ad4
added color-bar
sanika-n Jan 16, 2025
4aa9513
Documentation (#2630)
Spartan-71 Jan 19, 2025
910e99b
benchmarks.yml: Install SciPy and use uv for pip install (#2633)
EwoutH Jan 20, 2025
c24f13c
Adding a copy option at the top of the code written in the docs
PrashantChoudhary13579 Jan 18, 2025
fbb3e58
Add sphinx-copybutton to documentation dependencies in pyproject.toml
PrashantChoudhary13579 Jan 20, 2025
8477af0
Change Hexgrid._connect_cells_2d to use x,y coordinates (#2632)
quaquel Jan 20, 2025
802aa63
bugfix for draw_property_layer (#2639)
quaquel Jan 23, 2025
76b0bc4
meta_agents
tpike3 Dec 28, 2024
f231add
Meta-Agents IP
tpike3 Mar 6, 2025
09fd684
Meta-Agents IP2
tpike3 Mar 6, 2025
a486d6c
integrate main
tpike3 Mar 16, 2025
e444a6e
update meta agents
tpike3 Mar 18, 2025
13caec5
Merge branch 'main' into meta-agents
tpike3 Mar 25, 2025
f732051
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 25, 2025
ffd4507
Updates
tpike3 Mar 26, 2025
47112d3
Merge branch 'main' into meta-agents
tpike3 Apr 3, 2025
0378bb5
update meta-agent
tpike3 Apr 8, 2025
eac8e91
Merge branch 'main' into meta-agents
tpike3 Apr 19, 2025
29988fc
Requested updates
tpike3 Apr 21, 2025
d66515f
Update Readme.md
tpike3 Apr 21, 2025
a587a9b
Merge branch 'main' into meta-agents
tpike3 Apr 21, 2025
e1492ea
Merge branch 'main' into meta-agents
tpike3 Apr 26, 2025
d9a67b4
Remove key_by_name
tpike3 Apr 26, 2025
70a4abf
Update model.py
tpike3 Apr 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/apis/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ This namespace contains experimental features. These are under development, and
.. automodule:: experimental.continuous_space.continuous_space_agents
:members:
```

## Continuous Space

```{eval-rst}
.. automodule:: experimental.continuous_space.continuous_space
:members:
```

```{eval-rst}
.. automodule:: experimental.continuous_space.continuous_space_agents
:members:
```
10 changes: 6 additions & 4 deletions mesa/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ def __init__(self, model: Model, *args, **kwargs) -> None:

Args:
model (Model): The model instance in which the agent exists.
args: passed on to super
kwargs: passed on to super
args: Passed on to super.
kwargs: Passed on to super.

Notes:
to make proper use of python's super, in each class remove the arguments and
keyword arguments you need and pass on the rest to super

"""
super().__init__(*args, **kwargs)

Expand Down Expand Up @@ -103,7 +102,10 @@ def create_agents(cls, model: Model, n: int, *args, **kwargs) -> AgentSet[Agent]
"""

class ListLike:
"""Helper class to make default arguments act as if they are in a list of length N."""
"""Make default arguments act as if they are in a list of length N.

This is a helper class.
"""

def __init__(self, value):
self.value = value
Expand Down
2 changes: 2 additions & 0 deletions mesa/examples/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from mesa.examples.advanced.alliance_formation.model import MultiLevelAllianceModel
from mesa.examples.advanced.epstein_civil_violence.model import EpsteinCivilViolence
from mesa.examples.advanced.pd_grid.model import PdGrid
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
Expand All @@ -13,6 +14,7 @@
"BoltzmannWealth",
"ConwaysGameOfLife",
"EpsteinCivilViolence",
"MultiLevelAllianceModel",
"PdGrid",
"Schelling",
"SugarscapeG1mt",
Expand Down
50 changes: 50 additions & 0 deletions mesa/examples/advanced/alliance_formation/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Alliance Formation Model (Meta-Agent Example)

## Summary

This model demonstrates Mesa's meta agent capability.

**Overview of meta agent:** Complex systems often have multiple levels of components. A city is not a single entity, but it is made of districts,neighborhoods, buildings, and people. A forest comprises an ecosystem of trees, plants, animals, and microorganisms. An organization is not one entity, but is made of departments, sub-departments, and people. A person is not a single entity, but it is made of micro biomes, organs and cells.

This reality is the motivation for meta-agents. It allows users to represent these multiple levels, where each level can have agents with sub-agents.

This model demonstrates Mesa's ability to dynamically create new classes of agents that are composed of existing agents. These meta-agents inherits functions and attributes from their sub-agents and users can specify new functionality or attributes they want the meta agent to have. For example, if a user is doing a factory simulation with autonomous systems, each major component of that system can be a sub-agent of the overall robot agent. Or, if someone is doing a simulation of an organization, individuals can be part of different organizational units that are working for some purpose.

To provide a simple demonstration of this capability is an alliance formation model.

In this simulation n agents are created, who have two attributes (1) power and (2) preference. Each attribute is a number between 0 and 1 over a gaussian distribution. Agents then randomly select other agents and use the [bilateral shapley value](https://en.wikipedia.org/wiki/Shapley_value) to determine if they should form an alliance. If the expected utility support an alliances, the agent creates a meta-agent. Subsequent steps may add agents to the meta-agent, create new instances of similar hierarchy, or create a new hierarchy level where meta-agents form an alliance of meta-agents. In this visualization of this model a new meta-agent hierarchy will be a larger node and a new color.

In MetaAgents current configuration, agents being part of multiple meta-agents is not supported.

If you would like to see an example of explicit meta-agent formation see the [warehouse model in the Mesa example's repository](https://github.com/projectmesa/mesa-examples/tree/main/examples/warehouse)


## Installation

This model requires Mesa's recommended install and scipy

```
$ pip install mesa[rec]
```

## How to Run

To run the model interactively, in this directory, run the following command

```
$ solara run app.py
```

## Files

- `model.py`: Contains creation of agents, the network and management of agent execution.
- `agents.py`: Contains logic for forming alliances and creation of new agents
- `app.py`: Contains the code for the interactive Solara visualization.

## Further Reading

The full tutorial describing how the model is built can be found at:
https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html

An example of the bilateral shapley value in another model:
[Techno-Social Energy Infrastructure Siting: Sustainable Energy Modeling Programming (SEMPro)](https://www.jasss.org/16/3/6.html)
Empty file.
20 changes: 20 additions & 0 deletions mesa/examples/advanced/alliance_formation/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import mesa


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

"""

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

"""
For this demo model agent only need attributes.

More complex models could have functions that define agent behavior.
"""
71 changes: 71 additions & 0 deletions mesa/examples/advanced/alliance_formation/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import matplotlib.pyplot as plt
import networkx as nx
import solara
from matplotlib.figure import Figure

from mesa.examples.advanced.alliance_formation.model import MultiLevelAllianceModel
from mesa.visualization import SolaraViz
from mesa.visualization.utils import update_counter

model_params = {
"seed": {
"type": "InputText",
"value": 42,
"label": "Random Seed",
},
"n": {
"type": "SliderInt",
"value": 50,
"label": "Number of agents:",
"min": 10,
"max": 100,
"step": 1,
},
}

# Create visualization elements. The visualization elements are solara components
# that receive the model instance as a "prop" and display it in a certain way.
# Under the hood these are just classes that receive the model instance.
# You can also author your own visualization elements, which can also be functions
# that receive the model instance and return a valid solara component.


@solara.component
def plot_network(model):
update_counter.get()
g = model.network
pos = nx.fruchterman_reingold_layout(g)
fig = Figure()
ax = fig.subplots()
labels = {agent.unique_id: agent.unique_id for agent in model.agents}
node_sizes = [g.nodes[node]["size"] for node in g.nodes]
node_colors = [g.nodes[node]["size"] for node in g.nodes()]

Check warning on line 42 in mesa/examples/advanced/alliance_formation/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/advanced/alliance_formation/app.py#L35-L42

Added lines #L35 - L42 were not covered by tests

nx.draw(

Check warning on line 44 in mesa/examples/advanced/alliance_formation/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/advanced/alliance_formation/app.py#L44

Added line #L44 was not covered by tests
g,
pos,
node_size=node_sizes,
node_color=node_colors,
cmap=plt.cm.coolwarm,
labels=labels,
ax=ax,
)

solara.FigureMatplotlib(fig)

Check warning on line 54 in mesa/examples/advanced/alliance_formation/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/advanced/alliance_formation/app.py#L54

Added line #L54 was not covered by tests


# Create initial model instance
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],
model_params=model_params,
name="Alliance Formation Model",
)
page # noqa
184 changes: 184 additions & 0 deletions mesa/examples/advanced/alliance_formation/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import networkx as nx
import numpy as np

import mesa
from mesa import Agent
from mesa.examples.advanced.alliance_formation.agents import AllianceAgent
from mesa.experimental.meta_agents.meta_agent import (
create_meta_agent,
find_combinations,
)


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
self.datacollector = mesa.DataCollector(model_reporters={"Network": "network"})

# Create Agents
power = self.rng.normal(mean, std_dev, n)
power = np.clip(power, 0, 1)
position = self.rng.normal(mean, std_dev, n)
position = np.clip(position, 0, 1)
AllianceAgent.create_agents(self, n, power, position)
agent_ids = [
(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 74 in mesa/examples/advanced/alliance_formation/model.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/advanced/alliance_formation/model.py#L74

Added line #L74 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 78 in mesa/examples/advanced/alliance_formation/model.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/advanced/alliance_formation/model.py#L78

Added line #L78 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):
"""
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 = create_meta_agent(
self,
class_name,
alliance,
Agent,
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.agents)
4 changes: 2 additions & 2 deletions mesa/experimental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
- Features graduate from experimental status once their APIs are stabilized
"""

from mesa.experimental import continuous_space, devs, mesa_signals
from mesa.experimental import continuous_space, devs, mesa_signals, meta_agents

__all__ = ["continuous_space", "devs", "mesa_signals"]
__all__ = ["continuous_space", "devs", "mesa_signals", "meta_agents"]
Loading
Loading