diff --git a/benchmarks/BoltzmannWealth/__init__.py b/benchmarks/BoltzmannWealth/__init__.py deleted file mode 100644 index b70e37fa100..00000000000 --- a/benchmarks/BoltzmannWealth/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""init file for BoltzmannWealth module.""" diff --git a/benchmarks/BoltzmannWealth/boltzmann_wealth.py b/benchmarks/BoltzmannWealth/boltzmann_wealth.py deleted file mode 100644 index e15aec20319..00000000000 --- a/benchmarks/BoltzmannWealth/boltzmann_wealth.py +++ /dev/null @@ -1,112 +0,0 @@ -"""boltmann wealth model for performance benchmarking. - -https://github.com/projectmesa/mesa-examples/blob/main/examples/boltzmann_wealth_model_experimental/model.py -""" - -import mesa - - -def compute_gini(model): - """Calculate gini for wealth in model. - - Args: - model: a Model instance - - Returns: - float: gini score - - """ - agent_wealths = [agent.wealth for agent in model.agents] - x = sorted(agent_wealths) - n = model.num_agents - b = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x)) - return 1 + (1 / n) - 2 * b - - -class BoltzmannWealth(mesa.Model): - """A simple model of an economy where agents exchange currency at random. - - All the agents begin with one unit of currency, and each time step can give - a unit of currency to another agent. Note how, over time, this produces a - highly skewed distribution of wealth. - """ - - def __init__(self, seed=None, n=100, width=10, height=10): - """Initializes the model. - - Args: - seed: the seed for random number generator - n: the number of agents - width: the width of the grid - height: the height of the grid - """ - super().__init__(seed=seed) - self.num_agents = n - self.grid = mesa.space.MultiGrid(width, height, True) - self.datacollector = mesa.DataCollector( - model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} - ) - # Create agents - for _ in range(self.num_agents): - a = MoneyAgent(self) - # Add the agent to a random grid cell - x = self.random.randrange(self.grid.width) - y = self.random.randrange(self.grid.height) - self.grid.place_agent(a, (x, y)) - - self.running = True - self.datacollector.collect(self) - - def step(self): - """Run the model for a single step.""" - self.agents.shuffle_do("step") - # collect data - self.datacollector.collect(self) - - def run_model(self, n): - """Run the model for n steps. - - Args: - n: the number of steps for which to run the model - - """ - for _ in range(n): - self.step() - - -class MoneyAgent(mesa.Agent): - """An agent with fixed initial wealth.""" - - def __init__(self, model): - """Instantiate an agent. - - Args: - model: a Model instance - """ - super().__init__(model) - self.wealth = 1 - - def move(self): - """Move the agent to a random neighboring cell.""" - possible_steps = self.model.grid.get_neighborhood( - self.pos, moore=True, include_center=False - ) - new_position = self.random.choice(possible_steps) - self.model.grid.move_agent(self, new_position) - - def give_money(self): - """Give money to a random cell mate.""" - cellmates = self.model.grid.get_cell_list_contents([self.pos]) - cellmates.pop( - cellmates.index(self) - ) # Ensure agent is not giving money to itself - if len(cellmates) > 0: - other = self.random.choice(cellmates) - other.wealth += 1 - self.wealth -= 1 - - def step(self): - """Run the agent for 1 step.""" - self.move() - if self.wealth > 0: - self.give_money() diff --git a/benchmarks/Flocking/__init__.py b/benchmarks/Flocking/__init__.py deleted file mode 100644 index 684c3743037..00000000000 --- a/benchmarks/Flocking/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""initi for flocking benchmark model.""" diff --git a/benchmarks/Flocking/flocking.py b/benchmarks/Flocking/flocking.py deleted file mode 100644 index d2d23b1ac79..00000000000 --- a/benchmarks/Flocking/flocking.py +++ /dev/null @@ -1,156 +0,0 @@ -"""A Mesa implementation of Craig Reynolds's Boids flocker model. - -Uses numpy arrays to represent vectors. -""" - -import numpy as np - -import mesa - - -class Boid(mesa.Agent): - """A Boid-style flocker agent. - - The agent follows three behaviors to flock: - - Cohesion: steering towards neighboring agents. - - Separation: avoiding getting too close to any other agent. - - Alignment: try to fly in the same direction as the neighbors. - - Boids have a vision that defines the radius in which they look for their - neighbors to flock with. Their speed (a scalar) and direction (a vector) - define their movement. Separation is their desired minimum distance from - any other Boid. - """ - - def __init__( - self, - model, - speed, - direction, - vision, - separation, - cohere=0.03, - separate=0.015, - match=0.05, - ): - """Create a new Boid flocker agent. - - Args: - model: a Model instance - speed: Distance to move per step. - direction: numpy vector for the Boid's direction of movement. - vision: Radius to look around for nearby Boids. - separation: Minimum distance to maintain from other Boids. - cohere: the relative importance of matching neighbors' positions - separate: the relative importance of avoiding close neighbors - match: the relative importance of matching neighbors' directions - - """ - super().__init__(model) - self.speed = speed - self.direction = direction - self.vision = vision - self.separation = separation - self.cohere_factor = cohere - self.separate_factor = separate - self.match_factor = match - - def step(self): - """Get the Boid's neighbors, compute the new vector, and move accordingly.""" - neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) - n = 0 - match_vector, separation_vector, cohere = np.zeros((3, 2)) - for neighbor in neighbors: - n += 1 - heading = self.model.space.get_heading(self.pos, neighbor.pos) - cohere += heading - if self.model.space.get_distance(self.pos, neighbor.pos) < self.separation: - separation_vector -= heading - match_vector += neighbor.direction - n = max(n, 1) - cohere = cohere * self.cohere_factor - separation_vector = separation_vector * self.separate_factor - match_vector = match_vector * self.match_factor - self.direction += (cohere + separation_vector + match_vector) / n - self.direction /= np.linalg.norm(self.direction) - new_pos = self.pos + self.direction * self.speed - self.model.space.move_agent(self, new_pos) - - -class BoidFlockers(mesa.Model): - """Flocker model class. Handles agent creation, placement and scheduling.""" - - def __init__( - self, - seed=None, - population=100, - width=100, - height=100, - vision=10, - speed=1, - separation=1, - cohere=0.03, - separate=0.015, - match=0.05, - simulator=None, - ): - """Create a new Flockers model. - - Args: - seed: seed for random number generator - population: Number of Boids - width: the width of the space - height: the height of the space - speed: How fast should the Boids move. - vision: How far around should each Boid look for its neighbors - separation: What's the minimum distance each Boid will attempt to keep from any other - cohere: the relative importance of matching neighbors' positions' - separate: the relative importance of avoiding close neighbors - match: factors for the relative importance of - the three drives. - simulator: a Simulator Instance - """ - super().__init__(seed=seed) - self.population = population - self.width = width - self.height = height - self.simulator = simulator - - self.space = mesa.space.ContinuousSpace(self.width, self.height, True) - self.factors = { - "cohere": cohere, - "separate": separate, - "match": match, - } - - for _ in range(self.population): - x = self.random.random() * self.space.x_max - y = self.random.random() * self.space.y_max - pos = np.array((x, y)) - direction = np.random.random(2) * 2 - 1 - boid = Boid( - model=self, - speed=speed, - direction=direction, - vision=vision, - separation=separation, - **self.factors, - ) - self.space.place_agent(boid, pos) - - def step(self): - """Run the model for one step.""" - self.agents.shuffle_do("step") - - -if __name__ == "__main__": - import time - - # model = BoidFlockers(seed=15, population=200, width=100, height=100, vision=5) - model = BoidFlockers(seed=15, population=400, width=100, height=100, vision=15) - - start_time = time.perf_counter() - for _ in range(100): - model.step() - - print(time.perf_counter() - start_time) diff --git a/benchmarks/Schelling/__init__.py b/benchmarks/Schelling/__init__.py deleted file mode 100644 index de8d0f1a187..00000000000 --- a/benchmarks/Schelling/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Schelling separation for performance benchmarking.""" diff --git a/benchmarks/Schelling/schelling.py b/benchmarks/Schelling/schelling.py deleted file mode 100644 index f4543cb4312..00000000000 --- a/benchmarks/Schelling/schelling.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Schelling separation for performance benchmarking.""" - -from __future__ import annotations - -from mesa import Model -from mesa.experimental.cell_space import Cell, CellAgent, OrthogonalMooreGrid - - -class SchellingAgent(CellAgent): - """Schelling segregation agent.""" - - def __init__( - self, - model: Schelling, - agent_type: int, - radius: int, - homophily: float, - cell: Cell, - ): - """Create a new Schelling agent. - - Args: - model: model instance - agent_type: type of agent (minority=1, majority=0) - radius: size of neighborhood of agent - homophily: fraction of neighbors of the same type that triggers movement - cell: the cell in which the agent is located - """ - super().__init__(model) - self.type = agent_type - self.radius = radius - self.homophily = homophily - self.cell = cell - - def step(self): - """Run one step of the agent.""" - neighbors = self.cell.get_neighborhood(radius=self.radius).agents - similar = len( - [neighbor for neighbor in neighbors if neighbor.type == self.type] - ) - - # If unhappy, move: - if similar < self.homophily: - self.cell = self.model.grid.select_random_empty_cell() - else: - self.model.happy += 1 - - -class Schelling(Model): - """Model class for the Schelling segregation model.""" - - def __init__( - self, - simulator=None, - height=40, - width=40, - homophily=3, - radius=1, - density=0.8, - minority_pc=0.5, - seed=None, - ): - """Create a new Schelling model. - - Args: - simulator: simulator instance - height: height of the grid - width: width of the grid - homophily: Minimum number of agents of same class needed to be happy - radius: Search radius for checking similarity - density: Initial Chance for a cell to populated - minority_pc: Chances for an agent to be in minority class - seed: the seed for the random number generator - simulator: a simulator instance - """ - super().__init__(seed=seed) - self.simulator = simulator - self.happy = 0 - - self.grid = OrthogonalMooreGrid( - [height, width], - torus=True, - capacity=1, - random=self.random, - ) - - # Set up agents - # We use a grid iterator that returns - # the coordinates of a cell as well as - # its contents. (coord_iter) - for cell in self.grid: - if self.random.random() < density: - agent_type = 1 if self.random.random() < minority_pc else 0 - SchellingAgent(self, agent_type, radius, homophily, cell) - - def step(self): - """Run one step of the model.""" - self.happy = 0 # Reset counter of happy agents - self.agents.shuffle_do("step") - - -if __name__ == "__main__": - import time - - # model = Schelling(seed=15, height=40, width=40, homophily=3, radius=1, density=0.625) - model = Schelling( - seed=15, height=100, width=100, homophily=8, radius=2, density=0.8 - ) - - start_time = time.perf_counter() - for _ in range(100): - model.step() - print(time.perf_counter() - start_time) diff --git a/benchmarks/WolfSheep/__init__.py b/benchmarks/WolfSheep/__init__.py deleted file mode 100644 index 89c18853af6..00000000000 --- a/benchmarks/WolfSheep/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Wolf-Sheep Predation Model for performance benchmarking.""" diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py deleted file mode 100644 index f085ce429df..00000000000 --- a/benchmarks/WolfSheep/wolf_sheep.py +++ /dev/null @@ -1,231 +0,0 @@ -"""Wolf-Sheep Predation Model for performance benchmarking. - -Replication of the model found in NetLogo: - Wilensky, U. (1997). NetLogo Wolf Sheep Predation model. - http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation. - Center for Connected Learning and Computer-Based Modeling, - Northwestern University, Evanston, IL. -""" - -import math - -from mesa import Model -from mesa.experimental.cell_space import CellAgent, FixedAgent, OrthogonalVonNeumannGrid -from mesa.experimental.devs import ABMSimulator - - -class Animal(CellAgent): - """The base animal class.""" - - def __init__(self, model, energy, p_reproduce, energy_from_food, cell): - """Initializes an animal. - - Args: - model: a model instance - energy: starting amount of energy - p_reproduce: probability of sexless reproduction - energy_from_food: energy obtained from 1 unit of food - cell: the cell in which the animal starts - """ - super().__init__(model) - self.energy = energy - self.p_reproduce = p_reproduce - self.energy_from_food = energy_from_food - self.cell = cell - - def spawn_offspring(self): - """Create offspring.""" - self.energy /= 2 - self.__class__( - self.model, - self.energy, - self.p_reproduce, - self.energy_from_food, - self.cell, - ) - - def feed(self): ... # noqa: D102 - - def step(self): - """One step of the agent.""" - self.cell = self.cell.neighborhood.select_random_cell() - self.energy -= 1 - - self.feed() - - if self.energy < 0: - self.remove() - elif self.random.random() < self.p_reproduce: - self.spawn_offspring() - - -class Sheep(Animal): - """A sheep that walks around, reproduces (asexually) and gets eaten.""" - - def feed(self): - """If possible eat the food in the current location.""" - # If there is grass available, eat it - grass_patch = next( - obj for obj in self.cell.agents if isinstance(obj, GrassPatch) - ) - if grass_patch.fully_grown: - self.energy += self.energy_from_food - grass_patch.fully_grown = False - - -class Wolf(Animal): - """A wolf that walks around, reproduces (asexually) and eats sheep.""" - - def feed(self): - """If possible eat the food in the current location.""" - sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)] - if len(sheep) > 0: - sheep_to_eat = self.random.choice(sheep) - self.energy += self.energy_from_food - - # Kill the sheep - sheep_to_eat.remove() - - -class GrassPatch(FixedAgent): - """A patch of grass that grows at a fixed rate and it is eaten by sheep.""" - - @property - def fully_grown(self): # noqa: D102 - return self._fully_grown - - @fully_grown.setter - def fully_grown(self, value: bool) -> None: - self._fully_grown = value - - if not value: - self.model.simulator.schedule_event_relative( - setattr, - self.grass_regrowth_time, - function_args=[self, "fully_grown", True], - ) - - def __init__(self, model, countdown, grass_regrowth_time, cell): - """Creates a new patch of grass. - - Args: - model: a model instance - countdown: Time for the patch of grass to be fully grown again - grass_regrowth_time : time to fully regrow grass - cell: the cell to which the patch of grass belongs - """ - super().__init__(model) - self._fully_grown = True if countdown == 0 else False # Noqa: SIM210 - self.grass_regrowth_time = grass_regrowth_time - self.cell = cell - - if not self.fully_grown: - self.model.simulator.schedule_event_relative( - setattr, countdown, function_args=[self, "fully_grown", True] - ) - - -class WolfSheep(Model): - """Wolf-Sheep Predation Model. - - A model for simulating wolf and sheep (predator-prey) ecosystem modelling. - """ - - def __init__( - self, - simulator, - height, - width, - initial_sheep, - initial_wolves, - sheep_reproduce, - wolf_reproduce, - grass_regrowth_time, - wolf_gain_from_food=13, - sheep_gain_from_food=5, - seed=None, - ): - """Create a new Wolf-Sheep model with the given parameters. - - Args: - simulator: ABMSimulator instance - width: width of the grid - height: height of the grid - initial_sheep: Number of sheep to start with - initial_wolves: Number of wolves to start with - sheep_reproduce: Probability of each sheep reproducing each step - wolf_reproduce: Probability of each wolf reproducing each step - grass_regrowth_time: How long it takes for a grass patch to regrow - once it is eaten - wolf_gain_from_food: Energy a wolf gains from eating a sheep - sheep_gain_from_food: Energy sheep gain from grass, if enabled. - seed : the random seed - """ - super().__init__(seed=seed) - # Set parameters - self.height = height - self.width = width - self.simulator = simulator - - self.initial_sheep = initial_sheep - self.initial_wolves = initial_wolves - - self.grid = OrthogonalVonNeumannGrid( - [self.height, self.width], - torus=False, - capacity=math.inf, - random=self.random, - ) - - # Create sheep: - for _ in range(self.initial_sheep): - pos = ( - self.random.randrange(self.width), - self.random.randrange(self.height), - ) - energy = self.random.randrange(2 * sheep_gain_from_food) - Sheep(self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[pos]) - - # Create wolves - for _ in range(self.initial_wolves): - pos = ( - self.random.randrange(self.width), - self.random.randrange(self.height), - ) - energy = self.random.randrange(2 * wolf_gain_from_food) - Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[pos]) - - # Create grass patches - possibly_fully_grown = [True, False] - for cell in self.grid: - fully_grown = self.random.choice(possibly_fully_grown) - countdown = 0 if fully_grown else self.random.randrange(grass_regrowth_time) - GrassPatch(self, countdown, grass_regrowth_time, cell) - - def step(self): - """Run one step of the model.""" - self.agents_by_type[Sheep].shuffle_do("step") - self.agents_by_type[Wolf].shuffle_do("step") - - -if __name__ == "__main__": - import time - - simulator = ABMSimulator() - model = WolfSheep( - simulator, - 25, - 25, - 60, - 40, - 0.2, - 0.1, - 20, - seed=15, - ) - - simulator.setup(model) - - start_time = time.perf_counter() - simulator.run(100) - print("Time:", time.perf_counter() - start_time) diff --git a/benchmarks/configurations.py b/benchmarks/configurations.py index 0f2be5410b2..95bb41c806a 100644 --- a/benchmarks/configurations.py +++ b/benchmarks/configurations.py @@ -1,9 +1,6 @@ """configurations for benchmarks.""" -from BoltzmannWealth.boltzmann_wealth import BoltzmannWealth -from Flocking.flocking import BoidFlockers -from Schelling.schelling import Schelling -from WolfSheep.wolf_sheep import WolfSheep +from mesa.examples import BoidFlockers, BoltzmannWealth, Schelling, WolfSheep configurations = { # Schelling Model Configurations diff --git a/benchmarks/global_benchmark.py b/benchmarks/global_benchmark.py index 41c2643f88c..c3f0c0716b2 100644 --- a/benchmarks/global_benchmark.py +++ b/benchmarks/global_benchmark.py @@ -28,21 +28,22 @@ def run_model(model_class, seed, parameters): Returns: startup time and run time """ - no_simulator = ["BoltzmannWealth"] + uses_simulator = ["WolfSheep"] start_init = timeit.default_timer() - if model_class.__name__ in no_simulator: - model = model_class(seed=seed, **parameters) - else: + if model_class.__name__ in uses_simulator: simulator = ABMSimulator() model = model_class(simulator=simulator, seed=seed, **parameters) simulator.setup(model) + else: + model = model_class(seed=seed, **parameters) end_init_start_run = timeit.default_timer() - if model_class.__name__ in no_simulator: - model.run_model(config["steps"]) - else: + if model_class.__name__ in uses_simulator: simulator.run_for(config["steps"]) + else: + for _ in range(config["steps"]): + model.step() end_run = timeit.default_timer() diff --git a/mesa/examples/__init__.py b/mesa/examples/__init__.py index 048138b7b00..d0a736fa773 100644 --- a/mesa/examples/__init__.py +++ b/mesa/examples/__init__.py @@ -3,14 +3,14 @@ from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.examples.basic.boid_flockers.model import BoidFlockers -from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealthModel +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 __all__ = [ "BoidFlockers", - "BoltzmannWealthModel", + "BoltzmannWealth", "ConwaysGameOfLife", "Schelling", "VirusOnNetwork", diff --git a/mesa/examples/advanced/wolf_sheep/agents.py b/mesa/examples/advanced/wolf_sheep/agents.py index 8e71988bc9a..e3f308aa479 100644 --- a/mesa/examples/advanced/wolf_sheep/agents.py +++ b/mesa/examples/advanced/wolf_sheep/agents.py @@ -5,14 +5,14 @@ class Animal(CellAgent): """The base animal class.""" def __init__(self, model, energy, p_reproduce, energy_from_food, cell): - """Initializes an animal. + """Initialize an animal. Args: - model: a model instance - energy: starting amount of energy - p_reproduce: probability of sexless reproduction - energy_from_food: energy obtained from 1 unit of food - cell: the cell in which the animal starts + model: Model instance + energy: Starting amount of energy + p_reproduce: Probability of reproduction (asexual) + energy_from_food: Energy obtained from 1 unit of food + cell: Cell in which the animal starts """ super().__init__(model) self.energy = energy @@ -21,7 +21,7 @@ def __init__(self, model, energy, p_reproduce, energy_from_food, cell): self.cell = cell def spawn_offspring(self): - """Create offspring.""" + """Create offspring by splitting energy and creating new instance.""" self.energy /= 2 self.__class__( self.model, @@ -31,15 +31,19 @@ def spawn_offspring(self): self.cell, ) - def feed(self): ... + def feed(self): + """Abstract method to be implemented by subclasses.""" def step(self): - """One step of the agent.""" + """Execute one step of the animal's behavior.""" + # Move to random neighboring cell self.cell = self.cell.neighborhood.select_random_cell() self.energy -= 1 + # Try to feed self.feed() + # Handle death and reproduction if self.energy < 0: self.remove() elif self.random.random() < self.p_reproduce: @@ -50,53 +54,63 @@ class Sheep(Animal): """A sheep that walks around, reproduces (asexually) and gets eaten.""" def feed(self): - """If possible eat the food in the current location.""" - # If there is grass available, eat it - if self.model.grass: - grass_patch = next( - obj for obj in self.cell.agents if isinstance(obj, GrassPatch) - ) - if grass_patch.fully_grown: - self.energy += self.energy_from_food - grass_patch.fully_grown = False + """If possible, eat grass at current location.""" + grass_patch = next( + obj for obj in self.cell.agents if isinstance(obj, GrassPatch) + ) + if grass_patch.fully_grown: + self.energy += self.energy_from_food + grass_patch.fully_grown = False class Wolf(Animal): """A wolf that walks around, reproduces (asexually) and eats sheep.""" def feed(self): - """If possible eat the food in the current location.""" + """If possible, eat a sheep at current location.""" sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)] - if len(sheep) > 0: + if sheep: # If there are any sheep present sheep_to_eat = self.random.choice(sheep) self.energy += self.energy_from_food - - # Kill the sheep sheep_to_eat.remove() class GrassPatch(FixedAgent): - """ - A patch of grass that grows at a fixed rate and it is eaten by sheep - """ + """A patch of grass that grows at a fixed rate and can be eaten by sheep.""" + + @property + def fully_grown(self): + """Whether the grass patch is fully grown.""" + return self._fully_grown + + @fully_grown.setter + def fully_grown(self, value: bool) -> None: + """Set grass growth state and schedule regrowth if eaten.""" + self._fully_grown = value + + if not value: # If grass was just eaten + self.model.simulator.schedule_event_relative( + setattr, + self.grass_regrowth_time, + function_args=[self, "fully_grown", True], + ) - def __init__(self, model, fully_grown, countdown): - """ - Creates a new patch of grass + def __init__(self, model, countdown, grass_regrowth_time, cell): + """Create a new patch of grass. Args: - grown: (boolean) Whether the patch of grass is fully grown or not - countdown: Time for the patch of grass to be fully grown again + model: Model instance + countdown: Time until grass is fully grown again + grass_regrowth_time: Time needed to regrow after being eaten + cell: Cell to which this grass patch belongs """ super().__init__(model) - self.fully_grown = fully_grown - self.countdown = countdown + self._fully_grown = countdown == 0 + self.grass_regrowth_time = grass_regrowth_time + self.cell = cell - def step(self): + # Schedule initial growth if not fully grown if not self.fully_grown: - if self.countdown <= 0: - # Set as fully grown - self.fully_grown = True - self.countdown = self.model.grass_regrowth_time - else: - self.countdown -= 1 + self.model.simulator.schedule_event_relative( + setattr, countdown, function_args=[self, "fully_grown", True] + ) diff --git a/mesa/examples/advanced/wolf_sheep/model.py b/mesa/examples/advanced/wolf_sheep/model.py index 2ee09d3f732..982fcc2809f 100644 --- a/mesa/examples/advanced/wolf_sheep/model.py +++ b/mesa/examples/advanced/wolf_sheep/model.py @@ -9,30 +9,20 @@ Northwestern University, Evanston, IL. """ -import mesa -from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf -from mesa.experimental.cell_space import OrthogonalMooreGrid - +import math -class WolfSheep(mesa.Model): - """ - Wolf-Sheep Predation Model - """ - - height = 20 - width = 20 - - initial_sheep = 100 - initial_wolves = 50 +from mesa import Model +from mesa.datacollection import DataCollector +from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf +from mesa.experimental.cell_space import OrthogonalVonNeumannGrid +from mesa.experimental.devs import ABMSimulator - sheep_reproduce = 0.04 - wolf_reproduce = 0.05 - wolf_gain_from_food = 20 +class WolfSheep(Model): + """Wolf-Sheep Predation Model. - grass = False - grass_regrowth_time = 30 - sheep_gain_from_food = 4 + A model for simulating wolf and sheep (predator-prey) ecosystem modelling. + """ description = ( "A model for simulating wolf and sheep (predator-prey) ecosystem modelling." @@ -40,22 +30,24 @@ class WolfSheep(mesa.Model): def __init__( self, - width=20, height=20, + width=20, initial_sheep=100, initial_wolves=50, sheep_reproduce=0.04, wolf_reproduce=0.05, wolf_gain_from_food=20, - grass=False, + grass=True, grass_regrowth_time=30, sheep_gain_from_food=4, seed=None, + simulator: ABMSimulator = None, ): - """ - Create a new Wolf-Sheep model with the given parameters. + """Create a new Wolf-Sheep model with the given parameters. Args: + height: Height of the grid + width: Width of the grid initial_sheep: Number of sheep to start with initial_wolves: Number of wolves to start with sheep_reproduce: Probability of each sheep reproducing each step @@ -63,75 +55,76 @@ def __init__( wolf_gain_from_food: Energy a wolf gains from eating a sheep grass: Whether to have the sheep eat grass for energy grass_regrowth_time: How long it takes for a grass patch to regrow - once it is eaten - sheep_gain_from_food: Energy sheep gain from grass, if enabled. + once it is eaten + sheep_gain_from_food: Energy sheep gain from grass, if enabled + seed: Random seed + simulator: ABMSimulator instance for event scheduling """ super().__init__(seed=seed) - # Set parameters - self.width = width + + # Initialize model parameters self.height = height - self.initial_sheep = initial_sheep - self.initial_wolves = initial_wolves + self.width = width self.grass = grass - self.grass_regrowth_time = grass_regrowth_time - - self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True) - - collectors = { + self.simulator = simulator + + # Create grid using experimental cell space + self.grid = OrthogonalVonNeumannGrid( + [self.height, self.width], + torus=True, + capacity=math.inf, + random=self.random, + ) + + # Set up data collection + model_reporters = { "Wolves": lambda m: len(m.agents_by_type[Wolf]), "Sheep": lambda m: len(m.agents_by_type[Sheep]), - "Grass": lambda m: len( + } + if grass: + model_reporters["Grass"] = lambda m: len( m.agents_by_type[GrassPatch].select(lambda a: a.fully_grown) ) - if m.grass - else -1, - } - self.datacollector = mesa.DataCollector(collectors) + self.datacollector = DataCollector(model_reporters) # Create sheep: - for _ in range(self.initial_sheep): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - energy = self.random.randrange(2 * self.sheep_gain_from_food) - Sheep( - self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[(x, y)] + for _ in range(initial_sheep): + pos = ( + self.random.randrange(width), + self.random.randrange(height), ) + energy = self.random.randrange(2 * sheep_gain_from_food) + Sheep(self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[pos]) # Create wolves - for _ in range(self.initial_wolves): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - energy = self.random.randrange(2 * self.wolf_gain_from_food) - Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)]) - - # Create grass patches - if self.grass: - for cell in self.grid.all_cells: - fully_grown = self.random.choice([True, False]) - - if fully_grown: - countdown = self.grass_regrowth_time - else: - countdown = self.random.randrange(self.grass_regrowth_time) - - patch = GrassPatch(self, fully_grown, countdown) - patch.cell = cell - + for _ in range(initial_wolves): + pos = ( + self.random.randrange(width), + self.random.randrange(height), + ) + energy = self.random.randrange(2 * wolf_gain_from_food) + Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[pos]) + + # Create grass patches if enabled + if grass: + possibly_fully_grown = [True, False] + for cell in self.grid: + fully_grown = self.random.choice(possibly_fully_grown) + countdown = ( + 0 if fully_grown else self.random.randrange(0, grass_regrowth_time) + ) + GrassPatch(self, countdown, grass_regrowth_time, cell) + + # Collect initial data self.running = True self.datacollector.collect(self) def step(self): - # This replicated the behavior of the old RandomActivationByType scheduler - # when using step(shuffle_types=True, shuffle_agents=True). - # Conceptually, it can be argued that this should be modelled differently. - self.random.shuffle(self.agent_types) - for agent_type in self.agent_types: - self.agents_by_type[agent_type].shuffle_do("step") - - # collect data - self.datacollector.collect(self) + """Execute one step of the model.""" + # First activate all sheep, then all wolves, both in random order + self.agents_by_type[Sheep].shuffle_do("step") + self.agents_by_type[Wolf].shuffle_do("step") - def run_model(self, step_count=200): - for _ in range(step_count): - self.step() + # Collect data + self.datacollector.collect(self) diff --git a/mesa/examples/basic/boid_flockers/agents.py b/mesa/examples/basic/boid_flockers/agents.py index 480d1b56f52..48ce2b5a868 100644 --- a/mesa/examples/basic/boid_flockers/agents.py +++ b/mesa/examples/basic/boid_flockers/agents.py @@ -1,3 +1,9 @@ +"""A Boid (bird-oid) agent for implementing Craig Reynolds's Boids flocking model. + +This implementation uses numpy arrays to represent vectors for efficient computation +of flocking behavior. +""" + import numpy as np from mesa import Agent @@ -7,9 +13,9 @@ class Boid(Agent): """A Boid-style flocker agent. The agent follows three behaviors to flock: - - Cohesion: steering towards neighboring agents. - - Separation: avoiding getting too close to any other agent. - - Alignment: try to fly in the same direction as the neighbors. + - Cohesion: steering towards neighboring agents + - Separation: avoiding getting too close to any other agent + - Alignment: trying to fly in the same direction as neighbors Boids have a vision that defines the radius in which they look for their neighbors to flock with. Their speed (a scalar) and direction (a vector) @@ -31,13 +37,14 @@ def __init__( """Create a new Boid flocker agent. Args: - speed: Distance to move per step. - direction: numpy vector for the Boid's direction of movement. - vision: Radius to look around for nearby Boids. - separation: Minimum distance to maintain from other Boids. - cohere: the relative importance of matching neighbors' positions - separate: the relative importance of avoiding close neighbors - match: the relative importance of matching neighbors' headings + model: Model instance the agent belongs to + speed: Distance to move per step + direction: numpy vector for the Boid's direction of movement + vision: Radius to look around for nearby Boids + separation: Minimum distance to maintain from other Boids + cohere: Relative importance of matching neighbors' positions (default: 0.03) + separate: Relative importance of avoiding close neighbors (default: 0.015) + match: Relative importance of matching neighbors' directions (default: 0.05) """ super().__init__(model) self.speed = speed @@ -47,25 +54,49 @@ def __init__( self.cohere_factor = cohere self.separate_factor = separate self.match_factor = match - self.neighbors = None def step(self): """Get the Boid's neighbors, compute the new vector, and move accordingly.""" - self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) - n = 0 - match_vector, separation_vector, cohere = np.zeros((3, 2)) - for neighbor in self.neighbors: - n += 1 + neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) + + # If no neighbors, maintain current direction + if not neighbors: + new_pos = self.pos + self.direction * self.speed + self.model.space.move_agent(self, new_pos) + return + + # Initialize vectors for the three flocking behaviors + cohere = np.zeros(2) # Cohesion vector + match_vector = np.zeros(2) # Alignment vector + separation_vector = np.zeros(2) # Separation vector + + # Calculate the contribution of each neighbor to the three behaviors + for neighbor in neighbors: heading = self.model.space.get_heading(self.pos, neighbor.pos) + distance = self.model.space.get_distance(self.pos, neighbor.pos) + + # Cohesion - steer towards the average position of neighbors cohere += heading - if self.model.space.get_distance(self.pos, neighbor.pos) < self.separation: + + # Separation - avoid getting too close + if distance < self.separation: separation_vector -= heading + + # Alignment - match neighbors' flying direction match_vector += neighbor.direction - n = max(n, 1) + + # Weight each behavior by its factor and normalize by number of neighbors + n = len(neighbors) cohere = cohere * self.cohere_factor separation_vector = separation_vector * self.separate_factor match_vector = match_vector * self.match_factor + + # Update direction based on the three behaviors self.direction += (cohere + separation_vector + match_vector) / n + + # Normalize direction vector self.direction /= np.linalg.norm(self.direction) + + # Move boid new_pos = self.pos + self.direction * self.speed self.model.space.move_agent(self, new_pos) diff --git a/mesa/examples/basic/boid_flockers/model.py b/mesa/examples/basic/boid_flockers/model.py index a05caab1880..5b4974f3a20 100644 --- a/mesa/examples/basic/boid_flockers/model.py +++ b/mesa/examples/basic/boid_flockers/model.py @@ -1,60 +1,81 @@ -"""Flockers. -============================================================= +""" +Boids Flocking Model +=================== A Mesa implementation of Craig Reynolds's Boids flocker model. Uses numpy arrays to represent vectors. """ import numpy as np -import mesa +from mesa import Model from mesa.examples.basic.boid_flockers.agents import Boid +from mesa.space import ContinuousSpace -class BoidFlockers(mesa.Model): +class BoidFlockers(Model): """Flocker model class. Handles agent creation, placement and scheduling.""" def __init__( self, - seed=None, population=100, width=100, height=100, - vision=10, speed=1, - separation=1, + vision=10, + separation=2, cohere=0.03, separate=0.015, match=0.05, + seed=None, ): - """Create a new Flockers model. + """Create a new Boids Flocking model. Args: - population: Number of Boids - width, height: Size of the space. - speed: How fast should the Boids move. - vision: How far around should each Boid look for its neighbors - separation: What's the minimum distance each Boid will attempt to - keep from any other - cohere, separate, match: factors for the relative importance of - the three drives. + population: Number of Boids in the simulation (default: 100) + width: Width of the space (default: 100) + height: Height of the space (default: 100) + speed: How fast the Boids move (default: 1) + vision: How far each Boid can see (default: 10) + separation: Minimum distance between Boids (default: 2) + cohere: Weight of cohesion behavior (default: 0.03) + separate: Weight of separation behavior (default: 0.015) + match: Weight of alignment behavior (default: 0.05) + seed: Random seed for reproducibility (default: None) """ super().__init__(seed=seed) + + # Model Parameters self.population = population self.vision = vision self.speed = speed self.separation = separation - self.space = mesa.space.ContinuousSpace(width, height, True) + # Set up the space + self.space = ContinuousSpace(width, height, torus=True) + + # Store flocking weights self.factors = {"cohere": cohere, "separate": separate, "match": match} + + # Create and place the Boid agents self.make_agents() + # For tracking statistics + self.average_heading = None + self.update_average_heading() + def make_agents(self): - """Create self.population agents, with random positions and starting headings.""" + """Create and place all Boid agents randomly in the space.""" for _ in range(self.population): + # Random position x = self.random.random() * self.space.x_max y = self.random.random() * self.space.y_max pos = np.array((x, y)) - direction = np.random.random(2) * 2 - 1 + + # Random initial direction + direction = np.random.random(2) * 2 - 1 # Random vector between -1 and 1 + direction /= np.linalg.norm(direction) # Normalize + + # Create and place the Boid boid = Boid( model=self, speed=self.speed, @@ -65,5 +86,20 @@ def make_agents(self): ) self.space.place_agent(boid, pos) + def update_average_heading(self): + """Calculate the average heading (direction) of all Boids.""" + if not self.agents: + self.average_heading = 0 + return + + headings = np.array([agent.direction for agent in self.agents]) + mean_heading = np.mean(headings, axis=0) + self.average_heading = np.arctan2(mean_heading[1], mean_heading[0]) + def step(self): + """Run one step of the model. + + All agents are activated in random order using the AgentSet shuffle_do method. + """ self.agents.shuffle_do("step") + self.update_average_heading() diff --git a/mesa/examples/basic/boltzmann_wealth_model/agents.py b/mesa/examples/basic/boltzmann_wealth_model/agents.py index 12abff186a9..35c8e6b1014 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/agents.py +++ b/mesa/examples/basic/boltzmann_wealth_model/agents.py @@ -2,13 +2,26 @@ class MoneyAgent(Agent): - """An agent with fixed initial wealth.""" + """An agent with fixed initial wealth. + + Each agent starts with 1 unit of wealth and can give 1 unit to other agents + if they occupy the same cell. + + Attributes: + wealth (int): The agent's current wealth (starts at 1) + """ def __init__(self, model): + """Create a new agent. + + Args: + model (Model): The model instance that contains the agent + """ super().__init__(model) self.wealth = 1 def move(self): + """Move the agent to a random neighboring cell.""" possible_steps = self.model.grid.get_neighborhood( self.pos, moore=True, include_center=False ) @@ -16,16 +29,21 @@ def move(self): self.model.grid.move_agent(self, new_position) def give_money(self): + """Give 1 unit of wealth to a random agent in the same cell.""" cellmates = self.model.grid.get_cell_list_contents([self.pos]) - cellmates.pop( - cellmates.index(self) - ) # Ensure agent is not giving money to itself - if len(cellmates) > 0: + # Remove self from potential recipients + cellmates.pop(cellmates.index(self)) + + if cellmates: # Only give money if there are other agents present other = self.random.choice(cellmates) other.wealth += 1 self.wealth -= 1 def step(self): + """Execute one step for the agent: + 1. Move to a neighboring cell + 2. If wealth > 0, maybe give money to another agent in the same cell + """ self.move() if self.wealth > 0: self.give_money() diff --git a/mesa/examples/basic/boltzmann_wealth_model/app.py b/mesa/examples/basic/boltzmann_wealth_model/app.py index 15663f69036..c03d1763619 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/app.py +++ b/mesa/examples/basic/boltzmann_wealth_model/app.py @@ -1,4 +1,4 @@ -from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealthModel +from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth from mesa.visualization import ( SolaraViz, make_plot_component, @@ -35,7 +35,7 @@ def post_process(ax): # Create initial model instance -model = BoltzmannWealthModel(50, 10, 10) +model = BoltzmannWealth(50, 10, 10) # Create visualization elements. The visualization elements are solara components # that receive the model instance as a "prop" and display it in a certain way. diff --git a/mesa/examples/basic/boltzmann_wealth_model/model.py b/mesa/examples/basic/boltzmann_wealth_model/model.py index 03ef5a21634..21dbaf63e19 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/model.py +++ b/mesa/examples/basic/boltzmann_wealth_model/model.py @@ -1,43 +1,78 @@ -import mesa +""" +Boltzmann Wealth Model +===================== + +A simple model of wealth distribution based on the Boltzmann-Gibbs distribution. +Agents move randomly on a grid, giving one unit of wealth to a random neighbor +when they occupy the same cell. +""" + +from mesa import Model +from mesa.datacollection import DataCollector from mesa.examples.basic.boltzmann_wealth_model.agents import MoneyAgent +from mesa.space import MultiGrid -class BoltzmannWealthModel(mesa.Model): +class BoltzmannWealth(Model): """A simple model of an economy where agents exchange currency at random. - All the agents begin with one unit of currency, and each time step can give - a unit of currency to another agent. Note how, over time, this produces a - highly skewed distribution of wealth. + All agents begin with one unit of currency, and each time step agents can give + a unit of currency to another agent in the same cell. Over time, this produces + a highly skewed distribution of wealth. + + Attributes: + num_agents (int): Number of agents in the model + grid (MultiGrid): The space in which agents move + running (bool): Whether the model should continue running + datacollector (DataCollector): Collects and stores model data """ def __init__(self, n=100, width=10, height=10, seed=None): + """Initialize the model. + + Args: + n (int, optional): Number of agents. Defaults to 100. + width (int, optional): Grid width. Defaults to 10. + height (int, optional): Grid height. Defaults to 10. + seed (int, optional): Random seed. Defaults to None. + """ super().__init__(seed=seed) + self.num_agents = n - self.grid = mesa.space.MultiGrid(width, height, True) + self.grid = MultiGrid(width, height, torus=True) - self.datacollector = mesa.DataCollector( + # Set up data collection + self.datacollector = DataCollector( model_reporters={"Gini": self.compute_gini}, agent_reporters={"Wealth": "wealth"}, ) - # Create agents + + # Create and place the agents for _ in range(self.num_agents): - a = MoneyAgent(self) + agent = MoneyAgent(self) - # Add the agent to a random grid cell + # Add agent to random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) - self.grid.place_agent(a, (x, y)) + self.grid.place_agent(agent, (x, y)) self.running = True self.datacollector.collect(self) def step(self): - self.agents.shuffle_do("step") - self.datacollector.collect(self) + self.agents.shuffle_do("step") # Activate all agents in random order + self.datacollector.collect(self) # Collect data def compute_gini(self): + """Calculate the Gini coefficient for the model's current wealth distribution. + + The Gini coefficient is a measure of inequality in distributions. + - A Gini of 0 represents complete equality, where all agents have equal wealth. + - A Gini of 1 represents maximal inequality, where one agent has all wealth. + """ agent_wealths = [agent.wealth for agent in self.agents] x = sorted(agent_wealths) n = self.num_agents + # Calculate using the standard formula for Gini coefficient b = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x)) return 1 + (1 / n) - 2 * b diff --git a/mesa/examples/basic/boltzmann_wealth_model/st_app.py b/mesa/examples/basic/boltzmann_wealth_model/st_app.py index 4e722935ede..7b925ca8494 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/st_app.py +++ b/mesa/examples/basic/boltzmann_wealth_model/st_app.py @@ -5,7 +5,7 @@ import altair as alt import pandas as pd import streamlit as st -from model import BoltzmannWealthModel +from model import BoltzmannWealth model = st.title("Boltzman Wealth Model") num_agents = st.slider( @@ -19,7 +19,7 @@ ) height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) -model = BoltzmannWealthModel(num_agents, height, width) +model = BoltzmannWealth(num_agents, height, width) status_text = st.empty() diff --git a/mesa/examples/basic/schelling/agents.py b/mesa/examples/basic/schelling/agents.py index dffb5679a50..67940b5654e 100644 --- a/mesa/examples/basic/schelling/agents.py +++ b/mesa/examples/basic/schelling/agents.py @@ -1,25 +1,29 @@ -from mesa import Agent, Model +from mesa import Agent class SchellingAgent(Agent): """Schelling segregation agent.""" - def __init__(self, model: Model, agent_type: int) -> None: + def __init__(self, model, agent_type: int) -> None: """Create a new Schelling agent. Args: - agent_type: Indicator for the agent's type (minority=1, majority=0) + model: The model instance the agent belongs to + agent_type: Indicator for the agent's type (minority=1, majority=0) """ super().__init__(model) self.type = agent_type def step(self) -> None: + """Determine if agent is happy and move if necessary.""" neighbors = self.model.grid.iter_neighbors( self.pos, moore=True, radius=self.model.radius ) - similar = sum(1 for neighbor in neighbors if neighbor.type == self.type) - # If unhappy, move: + # Count similar neighbors + similar = sum(neighbor.type == self.type for neighbor in neighbors) + + # If unhappy, move to a random empty cell: if similar < self.model.homophily: self.model.grid.move_to_empty(self) else: diff --git a/mesa/examples/basic/schelling/model.py b/mesa/examples/basic/schelling/model.py index aa390f14e96..3ee0746c073 100644 --- a/mesa/examples/basic/schelling/model.py +++ b/mesa/examples/basic/schelling/model.py @@ -1,6 +1,7 @@ -import mesa from mesa import Model +from mesa.datacollection import DataCollector from mesa.examples.basic.schelling.agents import SchellingAgent +from mesa.space import SingleGrid class Schelling(Model): @@ -8,52 +9,73 @@ class Schelling(Model): def __init__( self, - height=20, - width=20, - homophily=3, - radius=1, - density=0.8, - minority_pc=0.2, + height: int = 40, + width: int = 40, + density: float = 0.8, + minority_pc: float = 0.5, + homophily: int = 3, + radius: int = 1, seed=None, ): """Create a new Schelling model. Args: - width, height: Size of the space. - density: Initial Chance for a cell to populated - minority_pc: Chances for an agent to be in minority class - homophily: Minimum number of agents of same class needed to be happy - radius: Search radius for checking similarity - seed: Seed for Reproducibility + width: Width of the grid + height: Height of the grid + density: Initial chance for a cell to be populated (0-1) + minority_pc: Chance for an agent to be in minority class (0-1) + homophily: Minimum number of similar neighbors needed for happiness + radius: Search radius for checking neighbor similarity + seed: Seed for reproducibility """ super().__init__(seed=seed) + + # Model parameters + self.height = height + self.width = width + self.density = density + self.minority_pc = minority_pc self.homophily = homophily self.radius = radius - self.grid = mesa.space.SingleGrid(width, height, torus=True) + # Initialize grid + self.grid = SingleGrid(width, height, torus=True) + # Track happiness self.happy = 0 - self.datacollector = mesa.DataCollector( - model_reporters={"happy": "happy"}, # Model-level count of happy agents + + # Set up data collection + self.datacollector = DataCollector( + model_reporters={ + "happy": "happy", + "pct_happy": lambda m: (m.happy / len(m.agents)) * 100 + if len(m.agents) > 0 + else 0, + "population": lambda m: len(m.agents), + "minority_pct": lambda m: ( + sum(1 for agent in m.agents if agent.type == 1) + / len(m.agents) + * 100 + if len(m.agents) > 0 + else 0 + ), + }, + agent_reporters={"agent_type": "type"}, ) - # Set up agents - # We use a grid iterator that returns - # the coordinates of a cell as well as - # its contents. (coord_iter) + # Create agents and place them on the grid for _, pos in self.grid.coord_iter(): - if self.random.random() < density: + if self.random.random() < self.density: agent_type = 1 if self.random.random() < minority_pc else 0 agent = SchellingAgent(self, agent_type) self.grid.place_agent(agent, pos) + # Collect initial state self.datacollector.collect(self) def step(self): """Run one step of the model.""" self.happy = 0 # Reset counter of happy agents - self.agents.shuffle_do("step") - - self.datacollector.collect(self) - - self.running = self.happy != len(self.agents) + self.agents.shuffle_do("step") # Activate all agents in random order + self.datacollector.collect(self) # Collect data + self.running = self.happy < len(self.agents) # Continue until everyone is happy diff --git a/tests/test_examples.py b/tests/test_examples.py index ff5cd478e06..436bd805f70 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,7 +1,7 @@ # noqa: D100 from mesa.examples import ( BoidFlockers, - BoltzmannWealthModel, + BoltzmannWealth, ConwaysGameOfLife, EpsteinCivilViolence, PdGrid, @@ -13,7 +13,7 @@ def test_boltzmann_model(): # noqa: D103 - model = BoltzmannWealthModel(seed=42) + model = BoltzmannWealth(seed=42) for _i in range(10): model.step() @@ -66,7 +66,9 @@ def test_sugarscape_g1mt(): # noqa: D103 def test_wolf_sheep(): # noqa: D103 - model = WolfSheep(seed=42) + from mesa.experimental.devs import ABMSimulator - for _i in range(10): - model.step() + simulator = ABMSimulator() + model = WolfSheep(seed=42, simulator=simulator) + simulator.setup(model) + simulator.run_for(10)