-
Notifications
You must be signed in to change notification settings - Fork 932
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
meta-agent creation #2561
meta-agent creation #2561
Changes from all commits
2b0be46
7c4ae09
f603b01
5975e44
ed39706
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Alliance Formation Model | ||
|
||
## Summary | ||
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 its current configuration, agents being part of multiple meta-agents is not supported | ||
|
||
## Installation | ||
|
||
This model requires Mesa's recommended install | ||
``` | ||
$ 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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import mesa | ||
|
||
|
||
def calculate_shapley_value(self, other_agent): | ||
""" | ||
Calculate the Shapley value of the two agents | ||
""" | ||
other_agent.hierarchy = other_agent.hierarchy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this line makes no sense to me. |
||
self.hierarchy = self.hierarchy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, you set self.hierarchy to itself. |
||
new_position = 1 - abs(self.position - other_agent.position) | ||
potential_utility = (self.power + other_agent.power) * 1.1 * new_position | ||
value_me = 0.5 * self.power + 0.5 * (potential_utility - other_agent.power) | ||
value_other = 0.5 * other_agent.power + 0.5 * (potential_utility - self.power) | ||
|
||
# Determine ig there is value in the alliance | ||
if value_me > self.power and value_other > other_agent.power: | ||
if other_agent.hierarchy > self.hierarchy: | ||
hierarchy = other_agent.hierarchy | ||
elif other_agent.hierarchy == self.hierarchy: | ||
hierarchy = self.hierarchy + 1 | ||
else: | ||
hierarchy = self.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) | ||
|
||
""" | ||
|
||
def __init__(self, model, power, position, hierarchy=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 | ||
] | ||
|
||
# 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 = self.create_metaagent( | ||
class_name, | ||
{other_agent, self}, | ||
hierarchy=shapley_value[2], | ||
power=shapley_value[0], | ||
position=shapley_value[1], | ||
) | ||
|
||
# Update the network if a new meta agent instance created | ||
if meta: | ||
self.model.network.add_node( | ||
meta.unique_id, size=(meta.hierarchy + 1) * 200 | ||
) | ||
self.model.new_agents = True | ||
else: | ||
self.model.new_agents = False | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import matplotlib.pyplot as plt | ||
import networkx as nx | ||
import solara | ||
from matplotlib.figure import Figure | ||
|
||
from mesa.examples.basic.alliance_formation.model import AllianceModel | ||
from mesa.mesa_logging import DEBUG, log_to_stderr | ||
from mesa.visualization import SolaraViz | ||
from mesa.visualization.utils import update_counter | ||
|
||
log_to_stderr(DEBUG) | ||
|
||
|
||
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.spring_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()] | ||
|
||
nx.draw( | ||
g, | ||
pos, | ||
node_size=node_sizes, | ||
node_color=node_colors, | ||
cmap=plt.cm.coolwarm, | ||
labels=labels, | ||
ax=ax, | ||
) | ||
|
||
solara.FigureMatplotlib(fig) | ||
|
||
|
||
# Create initial model instance | ||
model = AllianceModel(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 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import networkx as nx | ||
import numpy as np | ||
from agents import AllianceAgent | ||
|
||
import mesa | ||
|
||
|
||
class AllianceModel(mesa.Model): | ||
def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42): | ||
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 = np.random.normal(mean, std_dev, n) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change this to use self.rng to get the seeded numpy random number generator |
||
power = np.clip(power, 0, 1) | ||
position = np.random.normal(mean, std_dev, n) | ||
position = np.clip(position, 0, 1) | ||
AllianceAgent.create_agents(self, n, power, position) | ||
agent_ids = [(agent.unique_id, {"size": 100}) for agent in self.agents] | ||
self.network.add_nodes_from(agent_ids) | ||
|
||
def add_link(self, metaagent, agents): | ||
for agent in agents: | ||
self.network.add_edge(metaagent.unique_id, agent.unique_id) | ||
|
||
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 metaagent in self.agents_by_type[agent_class]: | ||
self.add_link(metaagent, metaagent.agents) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems a function, so don't use self or make it a method on your agent.