Skip to content

Commit

Permalink
[pre-commit.ci] auto fixes from pre-commit.com hooks
Browse files Browse the repository at this point in the history
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed Dec 20, 2024
1 parent d1baeeb commit 5031937
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 88 deletions.
63 changes: 39 additions & 24 deletions mesa/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ def __init__(self, model: Model, *args, **kwargs) -> None:
self.model.register_agent(self)
# Private attribute to track parent agent if metaagents are created
# Uses name mangling to prevent name clashes
self.__metaagent = None # Currently restricted to one parent agent
self.__metaagent = None # Currently restricted to one parent agent

def remove(self) -> None:
"""Remove and delete the agent from the model.
Expand Down Expand Up @@ -135,9 +135,13 @@ def __getitem__(self, i):
agent = cls(model, *instance_args, **instance_kwargs)
agents.append(agent)
return AgentSet(agents, random=model.random)

def create_metaagent(self, new_agent_class: str, agents: Iterable[Agent], **unique_attributes_functions) -> None:

def create_metaagent(
self,
new_agent_class: str,
agents: Iterable[Agent],
**unique_attributes_functions,
) -> None:
"""Dynamically create a new meta-agent class and instantiate agents in that class.
Args:
Expand Down Expand Up @@ -172,63 +176,74 @@ def create_metaagent(self, new_agent_class: str, agents: Iterable[Agent], **uniq
def update_agents_metaagent(agents, metaagent):
for agent in agents:
agent._Agent__metaagent = metaagent

# Path 1 - Add agents to existing meta-agent
subagents = [agent for agent in agents if agent._Agent__metaagent is not None]

if len(subagents) > 0:
if len(subagents) == 1:
if len(subagents) == 1:
# Update metaagents agent set with new agents
subagents[0]._Agent__metaagent.agents.update(agents)
# Update subagents with metaagent
update_agents_metaagent(agents, subagents[0]._Agent__metaagent)
else:
else:
# If there are multiple subagents, one is chosen at random to be the parent metaagent
subagent = self.random.choice(subagents)

Check warning on line 191 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L191

Added line #L191 was not covered by tests
# Remove agent who are already part of metaagent
agents = set(agents) - set(subagents)
subagent._Agent__metaagent.agents.update(agents)
update_agents_metaagent(agents, subagent._Agent__metaagent)

Check warning on line 195 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L193-L195

Added lines #L193 - L195 were not covered by tests
# TODO: Add way for user to add function to specify how agents join metaagent
else:

else:
# Path 2 - Create a new instance of an exsiting meta-agent class
agent_class = next((agent_type for agent_type in self.model.agent_types if agent_type.__name__ == new_agent_class), None)
agent_class = next(
(
agent_type
for agent_type in self.model.agent_types
if agent_type.__name__ == new_agent_class
),
None,
)
if agent_class:
# Create an instance of the meta-agent class
meta_agent_instance = agent_class(self.model, **unique_attributes_functions)
meta_agent_instance = agent_class(

Check warning on line 210 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L210

Added line #L210 was not covered by tests
self.model, **unique_attributes_functions
)
# Add agents to meta-agent instance
meta_agent_instance.agents = agents

Check warning on line 214 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L214

Added line #L214 was not covered by tests
# Update subagents Agent.__metaagent attribute
update_agents_metaagent(agents, meta_agent_instance)
update_agents_metaagent(agents, meta_agent_instance)

Check warning on line 216 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L216

Added line #L216 was not covered by tests
# Register the new meta-agent instance
self.model.register_agent(meta_agent_instance)

Check warning on line 218 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L218

Added line #L218 was not covered by tests

return meta_agent_instance
return meta_agent_instance

Check warning on line 220 in mesa/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/agent.py#L220

Added line #L220 was not covered by tests

# Path 3 - Create a new meta-agent class
else:
else:
# Get agent types of subagents to create the new meta-agent class
agent_types = tuple(set((type(agent) for agent in agents)))
agent_types = tuple({type(agent) for agent in agents})

meta_agent_class = type(
new_agent_class,
agent_types,
{
"unique_id": None,
"agents": agents,
}
"agents": agents,
},
)

# Create an instance of the meta-agent class
meta_agent_instance = meta_agent_class(self.model, **unique_attributes_functions)
meta_agent_instance = meta_agent_class(
self.model, **unique_attributes_functions
)
# Register the new meta-agent instance
self.model.register_agent(meta_agent_instance)
# Update subagents Agent.__metaagent attribute
update_agents_metaagent(agents, meta_agent_instance)
update_agents_metaagent(agents, meta_agent_instance)

return meta_agent_instance

return meta_agent_instance

@property
def random(self) -> Random:
"""Return a seeded stdlib rng."""
Expand Down
10 changes: 5 additions & 5 deletions mesa/examples/basic/alliance_formation_model/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## 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.
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.
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 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
In its current configuration, agents being part of multiple meta-agents is not supported

## Installation

Expand Down Expand Up @@ -36,6 +36,6 @@ To run the model interactively, in this directory, run the following command
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:
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)

78 changes: 44 additions & 34 deletions mesa/examples/basic/alliance_formation_model/agent.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,68 @@
import mesa

Check warning on line 1 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L1

Added line #L1 was not covered by tests


def calculate_shapley_value(self, other_agent):

Check warning on line 4 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L4

Added line #L4 was not covered by tests
"""
Calculate the Shapley value of the two agents
"""
other_agent.hierarchy = other_agent.hierarchy
self.hierarchy = self.hierarchy
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)
"""
Calculate the Shapley value of the two agents
"""
other_agent.hierarchy = other_agent.hierarchy
self.hierarchy = self.hierarchy
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)

Check warning on line 13 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L8-L13

Added lines #L8 - L13 were not covered by tests

# 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

Check warning on line 18 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L18

Added line #L18 was not covered by tests
elif other_agent.hierarchy == self.hierarchy:
hierarchy = self.hierarchy + 1

Check warning on line 20 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L20

Added line #L20 was not covered by tests
else:
return None
hierarchy = self.hierarchy

Check warning on line 22 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L22

Added line #L22 was not covered by tests

return (potential_utility, new_position, hierarchy)

Check warning on line 24 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L24

Added line #L24 was not covered by tests
else:
return None

Check warning on line 26 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L26

Added line #L26 was not covered by tests


class alliance_agent(mesa.Agent):

Check warning on line 29 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L29

Added line #L29 was not covered by tests
"""
Agent has three attirbutes 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

Check warning on line 39 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L35-L39

Added lines #L35 - L39 were not covered by tests


def form_alliance(self):

Check warning on line 41 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L41

Added line #L41 was not covered by tests
# Randomly select another agent of the same type
other_agents = [agent for agent in self.model.agents_by_type[type(self)] if agent != self]

other_agents = [

Check warning on line 43 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L43

Added line #L43 was not covered by tests
agent for agent in self.model.agents_by_type[type(self)] if agent != self
]

# Determine if there is a beneficial alliance
if other_agents:
if other_agents:
other_agent = self.random.choice(other_agents)
shapley_value = calculate_shapley_value(self, other_agent)

Check warning on line 50 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L49-L50

Added lines #L49 - L50 were not covered by tests
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])

meta = self.create_metaagent(

Check warning on line 53 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L52-L53

Added lines #L52 - L53 were not covered by tests
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
if meta:
self.model.network.add_node(

Check warning on line 63 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L63

Added line #L63 was not covered by tests
meta.unique_id, size=(meta.hierarchy + 1) * 200
)
self.model.new_agents = True

Check warning on line 66 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L66

Added line #L66 was not covered by tests
else:
self.model.new_agents=False
self.model.new_agents = False

Check warning on line 68 in mesa/examples/basic/alliance_formation_model/agent.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/agent.py#L68

Added line #L68 was not covered by tests
34 changes: 19 additions & 15 deletions mesa/examples/basic/alliance_formation_model/app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import networkx as nx
import matplotlib.pyplot as plt
import networkx as nx
import solara
from matplotlib.figure import Figure

Check warning on line 4 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L1-L4

Added lines #L1 - L4 were not covered by tests

from mesa.examples.basic.alliance_formation.model import alliance_model
import solara
from mesa.mesa_logging import DEBUG, log_to_stderr
from mesa.visualization import SolaraViz
from mesa.visualization.utils import update_counter

Check warning on line 9 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L6-L9

Added lines #L6 - L9 were not covered by tests
from mesa.visualization import (SolaraViz)

log_to_stderr(DEBUG)

Check warning on line 11 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L11

Added line #L11 was not covered by tests



model_params = {

Check warning on line 14 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L14

Added line #L14 was not covered by tests
"seed": {
"type": "InputText",
Expand All @@ -33,27 +33,31 @@
# 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()
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()]
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 46 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L37-L46

Added lines #L37 - L46 were not covered by tests

nx.draw(

Check warning on line 48 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L48

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

nx.draw(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 58 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L58

Added line #L58 was not covered by tests


# Create initial model instance
model = alliance_model(50)

Check warning on line 62 in mesa/examples/basic/alliance_formation_model/app.py

View check run for this annotation

Codecov / codecov/patch

mesa/examples/basic/alliance_formation_model/app.py#L62

Added line #L62 was not covered by tests

Expand Down
16 changes: 9 additions & 7 deletions mesa/examples/basic/alliance_formation_model/model.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import networkx as nx
import numpy as np
import mesa
from agent import alliance_agent

Check warning on line 3 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#L1-L3

Added lines #L1 - L3 were not covered by tests
import networkx as nx

import mesa

Check warning on line 5 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#L5

Added line #L5 was not covered by tests


class alliance_model(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"})

Check warning on line 13 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#L8-L13

Added lines #L8 - L13 were not covered by tests


# Create Agents
power = np.random.normal(mean, std_dev, n)
Expand All @@ -22,14 +23,15 @@ def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42):

def add_link(self, metaagent, agents):

Check warning on line 24 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#L24

Added line #L24 was not covered by tests
for agent in agents:
self.network.add_edge(metaagent.unique_id, agent.unique_id)
self.network.add_edge(metaagent.unique_id, agent.unique_id)

Check warning on line 26 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#L26

Added line #L26 was not covered by tests

def step(self):

Check warning on line 28 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#L28

Added line #L28 was not covered by tests
for agent_class in list(self.agent_types): # Convert to list to avoid modification during iteration
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")

Check warning on line 32 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#L32

Added line #L32 was not covered by tests

# Update graph
if agent_class is not alliance_agent:
for metaagent in self.agents_by_type[agent_class]:
self.add_link(metaagent, metaagent.agents)

self.add_link(metaagent, metaagent.agents)

Check warning on line 37 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#L37

Added line #L37 was not covered by tests
13 changes: 10 additions & 3 deletions tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ def __init__(self, model, power, position, hierarchy=0):
self.position = position
self.hierarchy = hierarchy


def test_create_metaagent():
model = Model()
agent1 = TestMetaAgent(model, power=0.5, position=0.5)
Expand All @@ -699,21 +700,27 @@ def test_create_metaagent():
agent4 = TestMetaAgent(model, power=0.8, position=0.8)

# Test creating a new meta-agent class
meta = agent1.create_metaagent("MetaAgentClass1", {agent1, agent2}, power=1.1, position=0.55, hierarchy=1)
meta = agent1.create_metaagent(
"MetaAgentClass1", {agent1, agent2}, power=1.1, position=0.55, hierarchy=1
)
assert len(model.agent_types) == 2
assert len(model.agents) == 5
assert meta.power == 1.1
assert meta.position == 0.55
assert meta.hierarchy == 1

# Test adding agents to an existing meta-agent
agent1.create_metaagent("MetaAgentClass1", {agent3, agent2}, power=1.8, position=0.65, hierarchy=1)
agent1.create_metaagent(
"MetaAgentClass1", {agent3, agent2}, power=1.8, position=0.65, hierarchy=1
)
assert len(model.agent_types) == 2
assert len(model.agents) == 5
assert len(meta.agents) == 3

# Test creating a new instance of an existing meta-agent class
meta2 = agent4.create_metaagent("MetaAgentClass2", {agent4}, power=0.8, position=0.8, hierarchy=2)
meta2 = agent4.create_metaagent(
"MetaAgentClass2", {agent4}, power=0.8, position=0.8, hierarchy=2
)
assert len(model.agent_types) == 3
assert len(model.agents) == 6
assert meta2.power == 0.8
Expand Down

0 comments on commit 5031937

Please sign in to comment.