From 50319373fab2395a57e7f91d883625d90d0f2001 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:11:22 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/agent.py | 63 +++++++++------ .../basic/alliance_formation_model/Readme.md | 10 +-- .../basic/alliance_formation_model/agent.py | 78 +++++++++++-------- .../basic/alliance_formation_model/app.py | 34 ++++---- .../basic/alliance_formation_model/model.py | 16 ++-- tests/test_agent.py | 13 +++- 6 files changed, 126 insertions(+), 88 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index 62f5e6a8610..e24d2baea35 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -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. @@ -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: @@ -172,17 +176,17 @@ 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) # Remove agent who are already part of metaagent @@ -190,45 +194,56 @@ def update_agents_metaagent(agents, metaagent): subagent._Agent__metaagent.agents.update(agents) update_agents_metaagent(agents, subagent._Agent__metaagent) # 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( + self.model, **unique_attributes_functions + ) # Add agents to meta-agent instance meta_agent_instance.agents = agents # Update subagents Agent.__metaagent attribute - update_agents_metaagent(agents, meta_agent_instance) + update_agents_metaagent(agents, meta_agent_instance) # Register the new meta-agent instance self.model.register_agent(meta_agent_instance) - return meta_agent_instance - + return meta_agent_instance + # 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.""" diff --git a/mesa/examples/basic/alliance_formation_model/Readme.md b/mesa/examples/basic/alliance_formation_model/Readme.md index 195522eacd4..df1ef1dd5a0 100644 --- a/mesa/examples/basic/alliance_formation_model/Readme.md +++ b/mesa/examples/basic/alliance_formation_model/Readme.md @@ -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 @@ -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) diff --git a/mesa/examples/basic/alliance_formation_model/agent.py b/mesa/examples/basic/alliance_formation_model/agent.py index 4bc46776a64..64b82fafe4a 100644 --- a/mesa/examples/basic/alliance_formation_model/agent.py +++ b/mesa/examples/basic/alliance_formation_model/agent.py @@ -1,58 +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 - 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) + + # 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: - return None + hierarchy = self.hierarchy + + return (potential_utility, new_position, hierarchy) + else: + return None + class alliance_agent(mesa.Agent): """ 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 - 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] - + 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: + 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]) - + 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 + 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 + self.model.new_agents = False diff --git a/mesa/examples/basic/alliance_formation_model/app.py b/mesa/examples/basic/alliance_formation_model/app.py index 9ce0d80025d..86c13bfec9e 100644 --- a/mesa/examples/basic/alliance_formation_model/app.py +++ b/mesa/examples/basic/alliance_formation_model/app.py @@ -1,16 +1,16 @@ -import networkx as nx import matplotlib.pyplot as plt +import networkx as nx +import solara from matplotlib.figure import Figure + 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 -from mesa.visualization import (SolaraViz) log_to_stderr(DEBUG) - model_params = { "seed": { "type": "InputText", @@ -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()] + + nx.draw( + 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) + # Create initial model instance model = alliance_model(50) diff --git a/mesa/examples/basic/alliance_formation_model/model.py b/mesa/examples/basic/alliance_formation_model/model.py index 45bcd998b50..2b42b365e84 100644 --- a/mesa/examples/basic/alliance_formation_model/model.py +++ b/mesa/examples/basic/alliance_formation_model/model.py @@ -1,7 +1,9 @@ +import networkx as nx import numpy as np -import mesa from agent import alliance_agent -import networkx as nx + +import mesa + class alliance_model(mesa.Model): def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42): @@ -9,7 +11,6 @@ def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42): 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) @@ -22,14 +23,15 @@ def __init__(self, n=50, mean=0.5, std_dev=0.1, seed=42): def add_link(self, metaagent, agents): for agent in agents: - self.network.add_edge(metaagent.unique_id, agent.unique_id) + 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 + 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 alliance_agent: for metaagent in self.agents_by_type[agent_class]: - self.add_link(metaagent, metaagent.agents) - \ No newline at end of file + self.add_link(metaagent, metaagent.agents) diff --git a/tests/test_agent.py b/tests/test_agent.py index 01fcddfee0a..59e5f3dd609 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -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) @@ -699,7 +700,9 @@ 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 @@ -707,13 +710,17 @@ def test_create_metaagent(): 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