From cd3305758e091c7037a43282d5f6e1cc5fdefe32 Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Fri, 27 Dec 2024 20:00:59 -0500 Subject: [PATCH 1/5] meta_agents - Add create meta-agents to experimental - Add tests of meta-agents - Add example with an alliance formation model in basic examples --- .../basic/alliance_formation_model/Readme.md | 40 +++++ .../alliance_formation_model/__init__.py | 10 ++ .../basic/alliance_formation_model/agents.py | 71 ++++++++ .../basic/alliance_formation_model/app.py | 74 ++++++++ .../basic/alliance_formation_model/model.py | 39 +++++ mesa/experimental/__init__.py | 4 +- mesa/experimental/meta_agents/__init__.py | 25 +++ mesa/experimental/meta_agents/meta_agents.py | 159 ++++++++++++++++++ tests/test_meta_agents.py | 112 ++++++++++++ tests/test_solara_viz.py | 10 +- 10 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 mesa/examples/basic/alliance_formation_model/Readme.md create mode 100644 mesa/examples/basic/alliance_formation_model/__init__.py create mode 100644 mesa/examples/basic/alliance_formation_model/agents.py create mode 100644 mesa/examples/basic/alliance_formation_model/app.py create mode 100644 mesa/examples/basic/alliance_formation_model/model.py create mode 100644 mesa/experimental/meta_agents/__init__.py create mode 100644 mesa/experimental/meta_agents/meta_agents.py create mode 100644 tests/test_meta_agents.py diff --git a/mesa/examples/basic/alliance_formation_model/Readme.md b/mesa/examples/basic/alliance_formation_model/Readme.md new file mode 100644 index 00000000000..4cd0edab7b7 --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/Readme.md @@ -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 and scipy +``` + $ pip install mesa[rec] scipy +``` + +## 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) + diff --git a/mesa/examples/basic/alliance_formation_model/__init__.py b/mesa/examples/basic/alliance_formation_model/__init__.py new file mode 100644 index 00000000000..49a80b627ee --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/__init__.py @@ -0,0 +1,10 @@ +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.") diff --git a/mesa/examples/basic/alliance_formation_model/agents.py b/mesa/examples/basic/alliance_formation_model/agents.py new file mode 100644 index 00000000000..38ff5ff92f8 --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/agents.py @@ -0,0 +1,71 @@ +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) + + """ + + 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 = create_meta_agent( + self.model, + class_name, + {other_agent, self}, + meta_attributes={ + "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) * 300, + hierarchy=meta.hierarchy, + ) diff --git a/mesa/examples/basic/alliance_formation_model/app.py b/mesa/examples/basic/alliance_formation_model/app.py new file mode 100644 index 00000000000..cfdadcf2da9 --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/app.py @@ -0,0 +1,74 @@ +import matplotlib.pyplot as plt +import networkx as nx +import solara +from matplotlib.figure import Figure +from 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.kamada_kawai_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 diff --git a/mesa/examples/basic/alliance_formation_model/model.py b/mesa/examples/basic/alliance_formation_model/model.py new file mode 100644 index 00000000000..44e6cb709fd --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/model.py @@ -0,0 +1,39 @@ +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 = 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, "hierarchy": 0}) for agent in self.agents + ] + self.network.add_nodes_from(agent_ids) + + def add_link(self, meta_agent, agents): + for agent in agents: + self.network.add_edge(meta_agent.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 meta_agent in self.agents_by_type[agent_class]: + self.add_link(meta_agent, meta_agent.agents) diff --git a/mesa/experimental/__init__.py b/mesa/experimental/__init__.py index 946b2ba53fc..2f73264608e 100644 --- a/mesa/experimental/__init__.py +++ b/mesa/experimental/__init__.py @@ -15,6 +15,6 @@ - Features graduate from experimental status once their APIs are stabilized """ -from mesa.experimental import cell_space, devs, mesa_signals +from mesa.experimental import cell_space, devs, mesa_signals, meta_agents -__all__ = ["cell_space", "devs", "mesa_signals"] +__all__ = ["cell_space", "devs", "mesa_signals", "meta_agents"] diff --git a/mesa/experimental/meta_agents/__init__.py b/mesa/experimental/meta_agents/__init__.py new file mode 100644 index 00000000000..43f238bf253 --- /dev/null +++ b/mesa/experimental/meta_agents/__init__.py @@ -0,0 +1,25 @@ +"""This method is for dynamically creating new agents (meta-agents). + +Meta-agents are defined as agents composed of existing agents. + +Meta-agents are created dynamically with a pointer to the model, name of the meta-agent,, +iterable of agents to belong to the new meta-agents, any new functions for the meta-agent, +any new attributes for the meta-agent, whether to retain sub-agent functions, +whether to retain sub-agent attributes. + +Examples of meta-agents: +- An autonomous car where the subagents are the wheels, sensors, +battery, computer etc. and the meta-agent is the car itself. +- A company where the subagents are employees, departments, buildings, etc. +- A city where the subagents are people, buildings, streets, etc. + +Currently meta-agents are restricted to one parent agent for each subagent/ +one meta-agent per subagent. + +Goal is to assess usage and expand functionality. + +""" + +from .meta_agents import create_meta_agent + +__all__ = ["create_meta_agent"] diff --git a/mesa/experimental/meta_agents/meta_agents.py b/mesa/experimental/meta_agents/meta_agents.py new file mode 100644 index 00000000000..75048c46533 --- /dev/null +++ b/mesa/experimental/meta_agents/meta_agents.py @@ -0,0 +1,159 @@ +"""This method is for dynamically creating meta-agents that represent groups of agents with interdependent characteristics. + +The new meta-agent class is created dynamically using the provided name and +unique attributes and functions. + +Currently restricted to one parent agent and one meta-agent per agent. +Goal is to assess usage and expand functionality. + +Method has three paths of execution: +1. Add agents to existing meta-agent +2. Create new meta-agent instance of existing meta-agent class +3. Create new meta-agent class + +See alliance formation model in basic examples for usage. + +""" + +from types import MethodType + + +def create_meta_agent( + model, + new_agent_class: str, + agents, + meta_attributes=dict(), # noqa B006 + meta_functions=dict(), # noqa B006 + retain_subagent_functions=True, + retain_subagent_attributes=False, +): + """Dynamically create a new meta-agent class and instantiate agents in that class. + + Parameters: + model (Model): The model instance. + new_agent_class (str): The name of the new meta-agent class. + agents (Iterable[Agent]): The agents to be included in the meta-agent. + meta_attributes (dict): Attributes to be added to the meta-agent. + meta_functions (dict): Functions to be added to the meta-agent. + retain_subagent_functions (bool): Whether to retain functions from sub-agents. + retain_subagent_attributes (bool): Whether to retain attributes from sub-agents. + + Returns: + - None if adding agent(s) to existing class + - New class instance if created a new instance of a dynamically + created agent type + - New class instance if created a new dynamically created agent type + """ + from mesa import ( + Agent, # Import the Agent class from Mesa locally to avoid circular import + ) + + # Convert agents to set to ensure uniqueness + agents = set(agents) + + def add_agents(meta_agent, new_agents: set[Agent]): + """Update agents' meta-agent attribute and store agent's meta-agent. + + Parameters: + meta_agent (MetaAgent): The meta-agent instance. + new_agents (Set[Agent]): The new agents to be added. + """ + meta_agent.agents.update(new_agents) + for agent in new_agents: + agent.meta_agent = meta_agent + + def add_functions(meta_agent_instance, agents, meta_functions): + """Add functions to the meta-agent instance. + + Parameters: + meta_agent_instance (MetaAgent): The meta-agent instance. + agents (Iterable[Agent]): The agents to derive functions from. + meta_functions (dict): Functions to be added to the meta-agent. + """ + if retain_subagent_functions: + agent_classes = {type(agent) for agent in agents} + for agent_class in agent_classes: + for name in dir(agent_class): + if callable(getattr(agent_class, name)) and not name.startswith( + "__" + ): + original_method = getattr(agent_class, name) + meta_functions[name] = original_method + + if meta_functions: + for name, func in meta_functions.items(): + bound_method = MethodType(func, meta_agent_instance) + setattr(meta_agent_instance, name, bound_method) + + def add_attributes(meta_agent_instance, agents, meta_attributes): + """Add attributes to the meta-agent instance. + + Parameters: + meta_agent_instance (MetaAgent): The meta-agent instance. + agents (Iterable[Agent]): The agents to derive attributes from. + meta_attributes (dict): Attributes to be added to the meta-agent. + """ + if retain_subagent_attributes: + for agent in agents: + for name, value in agent.__dict__.items(): + if not callable(value): + meta_attributes[name] = value + + if meta_attributes: + for key, value in meta_attributes.items(): + setattr(meta_agent_instance, key, value) + + # Path 1 - Add agents to existing meta-agent + subagents = [a for a in agents if hasattr(a, "meta_agent")] + if len(subagents) > 0: + if len(subagents) == 1: + add_attributes(subagents[0].meta_agent, agents, meta_attributes) + add_functions(subagents[0].meta_agent, agents, meta_functions) + add_agents(subagents[0].meta_agent, agents) + else: + subagent = model.random.choice(subagents) + agents = set(agents) - set(subagents) + add_attributes(subagent.meta_agent, agents, meta_attributes) + add_functions(subagent.meta_agent, agents, meta_functions) + add_agents(subagent.meta_agent, agents) + # TODO: Add way for user to specify how agents join meta-agent instead of random choice + else: + # Path 2 - Create a new instance of an existing meta-agent class + agent_class = next( + ( + agent_type + for agent_type in model.agent_types + if agent_type.__name__ == new_agent_class + ), + None, + ) + + if agent_class: + meta_agent_instance = agent_class(model, agents) + add_attributes(meta_agent_instance, agents, meta_attributes) + add_functions(meta_agent_instance, agents, meta_functions) + add_agents(meta_agent_instance, agents) + model.register_agent(meta_agent_instance) + return meta_agent_instance + else: + # Path 3 - Create a new meta-agent class + class MetaAgentClass(Agent): + def __init__(self, model, agents): + super().__init__(model) + self.agents = agents + + meta_agent_class = type( + new_agent_class, + (MetaAgentClass,), + { + "unique_id": None, + "agents": None, + }, + ) + + meta_agent_instance = meta_agent_class(model=model, agents=agents) + add_attributes(meta_agent_instance, agents, meta_attributes) + add_functions(meta_agent_instance, agents, meta_functions) + model.register_agent(meta_agent_instance) + add_agents(meta_agent_instance, agents) + return meta_agent_instance diff --git a/tests/test_meta_agents.py b/tests/test_meta_agents.py new file mode 100644 index 00000000000..62284ff7177 --- /dev/null +++ b/tests/test_meta_agents.py @@ -0,0 +1,112 @@ +"""Tests for the meta_agents module.""" + +import pytest + +from mesa import Agent, Model +from mesa.experimental.meta_agents.meta_agents import create_meta_agent + + +@pytest.fixture +def setup_agents(): + """Set up the model and agents for testing. + + Returns: + tuple: A tuple containing the model and a list of agents. + """ + model = Model() + agent1 = Agent(model) + agent2 = Agent(model) + agent3 = Agent(model) + agent4 = Agent(model) + agent4.custom_attribute = "custom_value" + agents = [agent1, agent2, agent3, agent4] + return model, agents + + +def test_create_meta_agent_new_class(setup_agents): + """Test creating a new meta-agent class and test inclusion of attributes and functions. + + Args: + setup_agents (tuple): The model and agents fixture. + """ + model, agents = setup_agents + meta_agent = create_meta_agent( + model, + "MetaAgentClass", + agents, + meta_attributes={"attribute1": "value1"}, + meta_functions={"function1": lambda self: "function1"}, + retain_subagent_attributes=True, + ) + assert meta_agent is not None + assert meta_agent.attribute1 == "value1" + assert meta_agent.function1() == "function1" + assert meta_agent.agents == set(agents) + assert hasattr(meta_agent, "custom_attribute") + assert meta_agent.custom_attribute == "custom_value" + + +def test_create_meta_agent_existing_class(setup_agents): + """Test creating new meta-agent instance with an existing meta-agent class. + + Args: + setup_agents (tuple): The model and agents fixture. + """ + model, agents = setup_agents + + # Create Met Agent Class + meta_agent = create_meta_agent( + model, + "MetaAgentClass", + [agents[0], agents[2]], + meta_attributes={"attribute1": "value1"}, + meta_functions={"function1": lambda self: "function1"}, + ) + + # Create new meta-agent instance with existing class + meta_agent2 = create_meta_agent( + model, + "MetaAgentClass", + [agents[1], agents[3]], + meta_attributes={"attribute2": "value2"}, + meta_functions={"function2": lambda self: "function2"}, + retain_subagent_attributes=True, + ) + assert meta_agent is not None + assert meta_agent2.attribute2 == "value2" + assert meta_agent.function1() == "function1" + assert meta_agent.agents == {agents[2], agents[0]} + assert meta_agent2.function2() == "function2" + assert meta_agent2.agents == {agents[1], agents[3]} + assert hasattr(meta_agent2, "custom_attribute") + assert meta_agent2.custom_attribute == "custom_value" + + +def test_add_agents_to_existing_meta_agent(setup_agents): + """Test adding agents to an existing meta-agent instance. + + Args: + setup_agents (tuple): The model and agents fixture. + """ + model, agents = setup_agents + + meta_agent1 = create_meta_agent( + model, + "MetaAgentClass", + [agents[0], agents[3]], + meta_attributes={"attribute1": "value1"}, + meta_functions={"function1": lambda self: "function1"}, + retain_subagent_attributes=True, + ) + + create_meta_agent( + model, + "MetaAgentClass", + [agents[1], agents[0], agents[2]], + retain_subagent_attributes=True, + ) + assert meta_agent1.agents == {agents[0], agents[1], agents[2], agents[3]} + assert meta_agent1.function1() == "function1" + assert meta_agent1.attribute1 == "value1" + assert hasattr(meta_agent1, "custom_attribute") + assert meta_agent1.custom_attribute == "custom_value" diff --git a/tests/test_solara_viz.py b/tests/test_solara_viz.py index 3b8d82fb7bc..a84fc910364 100644 --- a/tests/test_solara_viz.py +++ b/tests/test_solara_viz.py @@ -92,7 +92,8 @@ def Test(user_params): assert slider_int.step is None -def test_call_space_drawer(mocker): # noqa: D103 +def test_call_space_drawer(mocker): + """Test the call to space drawer.""" mock_space_matplotlib = mocker.spy( mesa.visualization.components.matplotlib_components, "SpaceMatplotlib" ) @@ -153,7 +154,8 @@ def drawer(model): ) -def test_slider(): # noqa: D103 +def test_slider(): + """Test the Slider component.""" slider_float = Slider("Agent density", 0.8, 0.1, 1.0, 0.1) assert slider_float.is_float_slider assert slider_float.value == 0.8 @@ -167,7 +169,9 @@ def test_slider(): # noqa: D103 assert slider_dtype_float.is_float_slider -def test_model_param_checks(): # noqa: D103 +def test_model_param_checks(): + """Test the model parameter checks.""" + class ModelWithOptionalParams: def __init__(self, required_param, optional_param=10): pass From 5bc7b95ce499e3a146d665e100710a915afe7e8e Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Sun, 29 Dec 2024 12:11:24 -0500 Subject: [PATCH 2/5] Meta-Agents - add alliance formation model to example_tests --- mesa/examples/__init__.py | 2 ++ mesa/examples/basic/alliance_formation_model/app.py | 2 +- mesa/examples/basic/alliance_formation_model/model.py | 2 +- tests/test_examples.py | 11 +++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mesa/examples/__init__.py b/mesa/examples/__init__.py index beecd6a1b6a..d15e448ecbd 100644 --- a/mesa/examples/__init__.py +++ b/mesa/examples/__init__.py @@ -7,8 +7,10 @@ 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 +from mesa.examples.basic.alliance_formation_model.model import AllianceModel __all__ = [ + "AllianceModel", "BoidFlockers", "BoltzmannWealth", "ConwaysGameOfLife", diff --git a/mesa/examples/basic/alliance_formation_model/app.py b/mesa/examples/basic/alliance_formation_model/app.py index cfdadcf2da9..4ea70f17541 100644 --- a/mesa/examples/basic/alliance_formation_model/app.py +++ b/mesa/examples/basic/alliance_formation_model/app.py @@ -2,7 +2,7 @@ import networkx as nx import solara from matplotlib.figure import Figure -from model import AllianceModel +from mesa.examples.basic.alliance_formation_model.model import AllianceModel from mesa.mesa_logging import DEBUG, log_to_stderr from mesa.visualization import SolaraViz diff --git a/mesa/examples/basic/alliance_formation_model/model.py b/mesa/examples/basic/alliance_formation_model/model.py index 44e6cb709fd..50dc1ba5004 100644 --- a/mesa/examples/basic/alliance_formation_model/model.py +++ b/mesa/examples/basic/alliance_formation_model/model.py @@ -1,6 +1,6 @@ import networkx as nx import numpy as np -from agents import AllianceAgent +from mesa.examples.basic.alliance_formation_model.agents import AllianceAgent import mesa diff --git a/tests/test_examples.py b/tests/test_examples.py index 0e8a7edce42..b9536c588ef 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,5 +1,6 @@ # noqa: D100 from mesa.examples import ( + AllianceModel, BoidFlockers, BoltzmannWealth, ConwaysGameOfLife, @@ -106,3 +107,13 @@ def test_wolf_sheep(): # noqa: D103 simulator = ABMSimulator() WolfSheep(seed=42, simulator=simulator) simulator.run_for(10) + +def test_alliance_formation_model(): # noqa: D103 + from mesa.examples.basic.alliance_formation_model import app + + app.page # noqa: B018 + + model = AllianceModel(50, seed=42) + + for _i in range(10): + model.step() From a87d382df66324ae8cbb2347f7caeaebee28865e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 17:11:44 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/__init__.py | 2 +- mesa/examples/basic/alliance_formation_model/app.py | 2 +- mesa/examples/basic/alliance_formation_model/model.py | 2 +- tests/test_examples.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mesa/examples/__init__.py b/mesa/examples/__init__.py index d15e448ecbd..a2b9ff9080c 100644 --- a/mesa/examples/__init__.py +++ b/mesa/examples/__init__.py @@ -2,12 +2,12 @@ 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.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 -from mesa.examples.basic.alliance_formation_model.model import AllianceModel __all__ = [ "AllianceModel", diff --git a/mesa/examples/basic/alliance_formation_model/app.py b/mesa/examples/basic/alliance_formation_model/app.py index 4ea70f17541..ba536a92533 100644 --- a/mesa/examples/basic/alliance_formation_model/app.py +++ b/mesa/examples/basic/alliance_formation_model/app.py @@ -2,8 +2,8 @@ import networkx as nx import solara from matplotlib.figure import Figure -from mesa.examples.basic.alliance_formation_model.model import AllianceModel +from mesa.examples.basic.alliance_formation_model.model import AllianceModel from mesa.mesa_logging import DEBUG, log_to_stderr from mesa.visualization import SolaraViz from mesa.visualization.utils import update_counter diff --git a/mesa/examples/basic/alliance_formation_model/model.py b/mesa/examples/basic/alliance_formation_model/model.py index 50dc1ba5004..a3b05156813 100644 --- a/mesa/examples/basic/alliance_formation_model/model.py +++ b/mesa/examples/basic/alliance_formation_model/model.py @@ -1,8 +1,8 @@ import networkx as nx import numpy as np -from mesa.examples.basic.alliance_formation_model.agents import AllianceAgent import mesa +from mesa.examples.basic.alliance_formation_model.agents import AllianceAgent class AllianceModel(mesa.Model): diff --git a/tests/test_examples.py b/tests/test_examples.py index b9536c588ef..8e923e64419 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -108,6 +108,7 @@ def test_wolf_sheep(): # noqa: D103 WolfSheep(seed=42, simulator=simulator) simulator.run_for(10) + def test_alliance_formation_model(): # noqa: D103 from mesa.examples.basic.alliance_formation_model import app From 0fa42a8c793410b62ad7771e4017f69d770e0bc3 Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Mon, 30 Dec 2024 19:00:32 -0500 Subject: [PATCH 4/5] meta-agent plus --- mesa/examples/__init__.py | 2 + .../{ => alliance_formation}/__init__.py | 0 .../{ => alliance_formation}/agents.py | 31 ++++---- .../alliance_formation/app.py | 74 +++++++++++++++++++ .../{ => alliance_formation}/model.py | 6 +- .../multi_level_alliance/__init__.py | 10 +++ .../multi_level_alliance/agents.py | 72 ++++++++++++++++++ .../{ => multi_level_alliance}/app.py | 1 + .../multi_level_alliance/model.py | 39 ++++++++++ mesa/experimental/meta_agents/__init__.py | 4 +- mesa/experimental/meta_agents/meta_agent.py | 66 +++++++++++++++++ .../{meta_agents.py => multi_levels.py} | 53 ++++--------- tests/test_examples.py | 2 +- tests/test_meta_agents.py | 2 +- 14 files changed, 303 insertions(+), 59 deletions(-) rename mesa/examples/basic/alliance_formation_model/{ => alliance_formation}/__init__.py (100%) rename mesa/examples/basic/alliance_formation_model/{ => alliance_formation}/agents.py (70%) create mode 100644 mesa/examples/basic/alliance_formation_model/alliance_formation/app.py rename mesa/examples/basic/alliance_formation_model/{ => alliance_formation}/model.py (84%) create mode 100644 mesa/examples/basic/alliance_formation_model/multi_level_alliance/__init__.py create mode 100644 mesa/examples/basic/alliance_formation_model/multi_level_alliance/agents.py rename mesa/examples/basic/alliance_formation_model/{ => multi_level_alliance}/app.py (97%) create mode 100644 mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py create mode 100644 mesa/experimental/meta_agents/meta_agent.py rename mesa/experimental/meta_agents/{meta_agents.py => multi_levels.py} (76%) diff --git a/mesa/examples/__init__.py b/mesa/examples/__init__.py index a2b9ff9080c..0cb1814a1e3 100644 --- a/mesa/examples/__init__.py +++ b/mesa/examples/__init__.py @@ -1,3 +1,5 @@ +from multi_level_alliance.model import AllianceModel + 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 diff --git a/mesa/examples/basic/alliance_formation_model/__init__.py b/mesa/examples/basic/alliance_formation_model/alliance_formation/__init__.py similarity index 100% rename from mesa/examples/basic/alliance_formation_model/__init__.py rename to mesa/examples/basic/alliance_formation_model/alliance_formation/__init__.py diff --git a/mesa/examples/basic/alliance_formation_model/agents.py b/mesa/examples/basic/alliance_formation_model/alliance_formation/agents.py similarity index 70% rename from mesa/examples/basic/alliance_formation_model/agents.py rename to mesa/examples/basic/alliance_formation_model/alliance_formation/agents.py index 38ff5ff92f8..7a2e1f88c42 100644 --- a/mesa/examples/basic/alliance_formation_model/agents.py +++ b/mesa/examples/basic/alliance_formation_model/alliance_formation/agents.py @@ -1,5 +1,5 @@ import mesa -from mesa.experimental.meta_agents import create_meta_agent +from mesa.experimental.meta_agents import create_multi_levels def calculate_shapley_value(calling_agent, other_agent): @@ -15,29 +15,29 @@ def calculate_shapley_value(calling_agent, other_agent): # 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 + if other_agent.level > calling_agent.level: + level = other_agent.level + elif other_agent.level == calling_agent.level: + level = calling_agent.level + 1 else: - hierarchy = calling_agent.hierarchy + level = calling_agent.level - return (potential_utility, new_position, hierarchy) + return (potential_utility, new_position, level) 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 + self.level = level def form_alliance(self): # Randomly select another agent of the same type @@ -50,22 +50,23 @@ def form_alliance(self): 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( + class_name = f"MetaAgentLevel{shapley_value[2]}" + meta = create_multi_levels( self.model, class_name, {other_agent, self}, meta_attributes={ - "hierarchy": shapley_value[2], + "level": shapley_value[2], "power": shapley_value[0], "position": shapley_value[1], }, + retain_subagent_functions=True, ) # 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, + size=(meta.level + 1) * 300, + level=meta.level, ) diff --git a/mesa/examples/basic/alliance_formation_model/alliance_formation/app.py b/mesa/examples/basic/alliance_formation_model/alliance_formation/app.py new file mode 100644 index 00000000000..c981f5caebd --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/alliance_formation/app.py @@ -0,0 +1,74 @@ +import matplotlib.pyplot as plt +import networkx as nx +import solara +from matplotlib.figure import Figure +from multi_level_alliance.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.kamada_kawai_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 diff --git a/mesa/examples/basic/alliance_formation_model/model.py b/mesa/examples/basic/alliance_formation_model/alliance_formation/model.py similarity index 84% rename from mesa/examples/basic/alliance_formation_model/model.py rename to mesa/examples/basic/alliance_formation_model/alliance_formation/model.py index a3b05156813..349946de0a2 100644 --- a/mesa/examples/basic/alliance_formation_model/model.py +++ b/mesa/examples/basic/alliance_formation_model/alliance_formation/model.py @@ -1,8 +1,8 @@ import networkx as nx import numpy as np +from multi_level_alliance.agents import AllianceAgent import mesa -from mesa.examples.basic.alliance_formation_model.agents import AllianceAgent class AllianceModel(mesa.Model): @@ -19,7 +19,7 @@ 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) @@ -36,4 +36,4 @@ def step(self): # 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) + self.add_link(meta_agent, meta_agent.subset) diff --git a/mesa/examples/basic/alliance_formation_model/multi_level_alliance/__init__.py b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/__init__.py new file mode 100644 index 00000000000..49a80b627ee --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/__init__.py @@ -0,0 +1,10 @@ +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.") diff --git a/mesa/examples/basic/alliance_formation_model/multi_level_alliance/agents.py b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/agents.py new file mode 100644 index 00000000000..7a2e1f88c42 --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/agents.py @@ -0,0 +1,72 @@ +import mesa +from mesa.experimental.meta_agents import create_multi_levels + + +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.level > calling_agent.level: + level = other_agent.level + elif other_agent.level == calling_agent.level: + level = calling_agent.level + 1 + else: + level = calling_agent.level + + return (potential_utility, new_position, level) + else: + return None + + +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 + + 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"MetaAgentLevel{shapley_value[2]}" + meta = create_multi_levels( + self.model, + class_name, + {other_agent, self}, + meta_attributes={ + "level": shapley_value[2], + "power": shapley_value[0], + "position": shapley_value[1], + }, + retain_subagent_functions=True, + ) + + # Update the network if a new meta agent instance created + if meta: + self.model.network.add_node( + meta.unique_id, + size=(meta.level + 1) * 300, + level=meta.level, + ) diff --git a/mesa/examples/basic/alliance_formation_model/app.py b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/app.py similarity index 97% rename from mesa/examples/basic/alliance_formation_model/app.py rename to mesa/examples/basic/alliance_formation_model/multi_level_alliance/app.py index ba536a92533..a2a4bead137 100644 --- a/mesa/examples/basic/alliance_formation_model/app.py +++ b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/app.py @@ -7,6 +7,7 @@ from mesa.mesa_logging import DEBUG, log_to_stderr from mesa.visualization import SolaraViz from mesa.visualization.utils import update_counter +from multi_level_alliance.model import AllianceModel log_to_stderr(DEBUG) diff --git a/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py new file mode 100644 index 00000000000..d66b5090ece --- /dev/null +++ b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py @@ -0,0 +1,39 @@ +import networkx as nx +import numpy as np + +import mesa +from mesa.examples.basic.alliance_formation_model.multi_level_alliance.agents import AllianceAgent + + +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 = 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): + for agent in agents: + self.network.add_edge(meta_agent.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 meta_agent in self.agents_by_type[agent_class]: + self.add_link(meta_agent, meta_agent.subset) diff --git a/mesa/experimental/meta_agents/__init__.py b/mesa/experimental/meta_agents/__init__.py index 43f238bf253..aa3f4b4fc9d 100644 --- a/mesa/experimental/meta_agents/__init__.py +++ b/mesa/experimental/meta_agents/__init__.py @@ -20,6 +20,6 @@ """ -from .meta_agents import create_meta_agent +from .multi_levels import create_multi_levels -__all__ = ["create_meta_agent"] +__all__ = ["create_multi_levels"] diff --git a/mesa/experimental/meta_agents/meta_agent.py b/mesa/experimental/meta_agents/meta_agent.py new file mode 100644 index 00000000000..5b9719815ec --- /dev/null +++ b/mesa/experimental/meta_agents/meta_agent.py @@ -0,0 +1,66 @@ +"""Implementation of Mesa's meta agent capability.""" + +from mesa.agent import Agent, AgentSet + + +class MetaAgent(Agent): + """A MetaAgent is an agent that contains other agents as components.""" + + def __init__(self, model, agents): + """Create a new MetaAgent.""" + super().__init__(model) + self._subset = AgentSet(agents or [], random=model.random) + + # Add ref to meta_agent in subagents + for agent in self._subset: + agent.meta_agent = self # TODO: Make a set for meta_agents + + @property + def subset(self): + """Read-only access to components as an AgentSet.""" + return self._subset + + def add_subagents(self, new_agents: set[Agent]): + """Add an agent as a component. + + Args: + new_agents (Agent): The agents to add to MetaAgent subset + """ + for agent in new_agents: + self._subset.add(agent) + + for agent in new_agents: + agent.meta_agent = self # TODO: Make a set for meta_agents + + def remove_subagents(self, remove_agents: set[Agent]): + """Remove an agent component. + + Args: + remove_agents (Agent): The agents to remove from MetAgents + + + """ + for agent in remove_agents: + self._subset.discard(agent) + + for agent in remove_agents: + agent.meta_agent = None # TODO: Remove meta_agent from set + + def step(self): + """Perform the agent's step. + + Override this method to define the meta agent's behavior. + By default, does nothing. + """ + + def __len__(self): + """Return the number of components.""" + return len(self._subset) + + def __iter__(self): + """Iterate over components.""" + return iter(self._subset) + + def __contains__(self, agent): + """Check if an agent is a component.""" + return agent in self._subset diff --git a/mesa/experimental/meta_agents/meta_agents.py b/mesa/experimental/meta_agents/multi_levels.py similarity index 76% rename from mesa/experimental/meta_agents/meta_agents.py rename to mesa/experimental/meta_agents/multi_levels.py index 75048c46533..a56d40b8aa8 100644 --- a/mesa/experimental/meta_agents/meta_agents.py +++ b/mesa/experimental/meta_agents/multi_levels.py @@ -17,14 +17,16 @@ from types import MethodType +from mesa.experimental.meta_agents.meta_agent import MetaAgent -def create_meta_agent( + +def create_multi_levels( model, new_agent_class: str, agents, meta_attributes=dict(), # noqa B006 meta_functions=dict(), # noqa B006 - retain_subagent_functions=True, + retain_subagent_functions=False, retain_subagent_attributes=False, ): """Dynamically create a new meta-agent class and instantiate agents in that class. @@ -44,24 +46,9 @@ def create_meta_agent( created agent type - New class instance if created a new dynamically created agent type """ - from mesa import ( - Agent, # Import the Agent class from Mesa locally to avoid circular import - ) - # Convert agents to set to ensure uniqueness agents = set(agents) - def add_agents(meta_agent, new_agents: set[Agent]): - """Update agents' meta-agent attribute and store agent's meta-agent. - - Parameters: - meta_agent (MetaAgent): The meta-agent instance. - new_agents (Set[Agent]): The new agents to be added. - """ - meta_agent.agents.update(new_agents) - for agent in new_agents: - agent.meta_agent = meta_agent - def add_functions(meta_agent_instance, agents, meta_functions): """Add functions to the meta-agent instance. @@ -73,17 +60,16 @@ def add_functions(meta_agent_instance, agents, meta_functions): if retain_subagent_functions: agent_classes = {type(agent) for agent in agents} for agent_class in agent_classes: - for name in dir(agent_class): + for name in agent_class.__dict__: if callable(getattr(agent_class, name)) and not name.startswith( "__" ): original_method = getattr(agent_class, name) meta_functions[name] = original_method - if meta_functions: - for name, func in meta_functions.items(): - bound_method = MethodType(func, meta_agent_instance) - setattr(meta_agent_instance, name, bound_method) + for name, func in meta_functions.items(): + bound_method = MethodType(func, meta_agent_instance) + setattr(meta_agent_instance, name, bound_method) def add_attributes(meta_agent_instance, agents, meta_attributes): """Add attributes to the meta-agent instance. @@ -99,9 +85,8 @@ def add_attributes(meta_agent_instance, agents, meta_attributes): if not callable(value): meta_attributes[name] = value - if meta_attributes: - for key, value in meta_attributes.items(): - setattr(meta_agent_instance, key, value) + for key, value in meta_attributes.items(): + setattr(meta_agent_instance, key, value) # Path 1 - Add agents to existing meta-agent subagents = [a for a in agents if hasattr(a, "meta_agent")] @@ -109,13 +94,14 @@ def add_attributes(meta_agent_instance, agents, meta_attributes): if len(subagents) == 1: add_attributes(subagents[0].meta_agent, agents, meta_attributes) add_functions(subagents[0].meta_agent, agents, meta_functions) - add_agents(subagents[0].meta_agent, agents) + subagents[0].meta_agent.add_subagents(agents) + else: subagent = model.random.choice(subagents) agents = set(agents) - set(subagents) add_attributes(subagent.meta_agent, agents, meta_attributes) add_functions(subagent.meta_agent, agents, meta_functions) - add_agents(subagent.meta_agent, agents) + subagent.meta_agent.add_subagents(agents) # TODO: Add way for user to specify how agents join meta-agent instead of random choice else: # Path 2 - Create a new instance of an existing meta-agent class @@ -132,28 +118,21 @@ def add_attributes(meta_agent_instance, agents, meta_attributes): meta_agent_instance = agent_class(model, agents) add_attributes(meta_agent_instance, agents, meta_attributes) add_functions(meta_agent_instance, agents, meta_functions) - add_agents(meta_agent_instance, agents) model.register_agent(meta_agent_instance) return meta_agent_instance else: # Path 3 - Create a new meta-agent class - class MetaAgentClass(Agent): - def __init__(self, model, agents): - super().__init__(model) - self.agents = agents - meta_agent_class = type( new_agent_class, - (MetaAgentClass,), + (MetaAgent,), { "unique_id": None, - "agents": None, + "_subset": None, }, ) - meta_agent_instance = meta_agent_class(model=model, agents=agents) + meta_agent_instance = meta_agent_class(model, agents) add_attributes(meta_agent_instance, agents, meta_attributes) add_functions(meta_agent_instance, agents, meta_functions) model.register_agent(meta_agent_instance) - add_agents(meta_agent_instance, agents) return meta_agent_instance diff --git a/tests/test_examples.py b/tests/test_examples.py index 8e923e64419..48b52f3427e 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -110,7 +110,7 @@ def test_wolf_sheep(): # noqa: D103 def test_alliance_formation_model(): # noqa: D103 - from mesa.examples.basic.alliance_formation_model import app + from multi_level_alliance import app app.page # noqa: B018 diff --git a/tests/test_meta_agents.py b/tests/test_meta_agents.py index 62284ff7177..7b805327f2b 100644 --- a/tests/test_meta_agents.py +++ b/tests/test_meta_agents.py @@ -3,7 +3,7 @@ import pytest from mesa import Agent, Model -from mesa.experimental.meta_agents.meta_agents import create_meta_agent +from mesa.experimental.meta_agents.multi_levels import create_meta_agent @pytest.fixture From 84b9bf334eea16b86e3b7a00cd0e891127bc7cd3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 00:03:39 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../alliance_formation_model/multi_level_alliance/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py index d66b5090ece..4b4eb01a2bc 100644 --- a/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py +++ b/mesa/examples/basic/alliance_formation_model/multi_level_alliance/model.py @@ -2,7 +2,9 @@ import numpy as np import mesa -from mesa.examples.basic.alliance_formation_model.multi_level_alliance.agents import AllianceAgent +from mesa.examples.basic.alliance_formation_model.multi_level_alliance.agents import ( + AllianceAgent, +) class AllianceModel(mesa.Model):