diff --git a/examples/bank_reserves/bank_reserves/agents.py b/examples/bank_reserves/bank_reserves/agents.py index 1d561d5b..5d81a65c 100644 --- a/examples/bank_reserves/bank_reserves/agents.py +++ b/examples/bank_reserves/bank_reserves/agents.py @@ -10,18 +10,15 @@ Northwestern University, Evanston, IL. """ -from .random_walk import RandomWalker +import mesa +from .random_walk import RandomWalker -class Bank: - """Note that the Bank class is not a Mesa Agent, but just a regular Python - class. This is because there is only one bank in this model, and it does not - use any Mesa-specific features like the scheduler or the grid, and doesn't - have a step method. It is just used to keep track of the bank's reserves and - the amount it can loan out, for Person agents to interact with.""" - def __init__(self, model, reserve_percent=50): - self.model = model +class Bank(mesa.Agent): + def __init__(self, unique_id, model, reserve_percent=50): + # initialize the parent class with required parameters + super().__init__(unique_id, model) # for tracking total value of loans outstanding self.bank_loans = 0 """percent of deposits the bank must keep in reserves - this is set via @@ -176,6 +173,7 @@ def take_out_loan(self, amount): # increase the bank's outstanding loans self.bank.bank_loans += amount + # step is called for each agent in model.BankReservesModel.schedule.step() def step(self): # move to a cell in my Moore neighborhood self.random_move() diff --git a/examples/bank_reserves/bank_reserves/model.py b/examples/bank_reserves/bank_reserves/model.py index f7aab4cb..421854fb 100644 --- a/examples/bank_reserves/bank_reserves/model.py +++ b/examples/bank_reserves/bank_reserves/model.py @@ -26,14 +26,14 @@ def get_num_rich_agents(model): """return number of rich agents""" - rich_agents = [a for a in model.agents if a.savings > model.rich_threshold] + rich_agents = [a for a in model.schedule.agents if a.savings > model.rich_threshold] return len(rich_agents) def get_num_poor_agents(model): """return number of poor agents""" - poor_agents = [a for a in model.agents if a.loans > 10] + poor_agents = [a for a in model.schedule.agents if a.loans > 10] return len(poor_agents) @@ -41,7 +41,9 @@ def get_num_mid_agents(model): """return number of middle class agents""" mid_agents = [ - a for a in model.agents if a.loans < 10 and a.savings < model.rich_threshold + a + for a in model.schedule.agents + if a.loans < 10 and a.savings < model.rich_threshold ] return len(mid_agents) @@ -49,7 +51,7 @@ def get_num_mid_agents(model): def get_total_savings(model): """sum of all agents' savings""" - agent_savings = [a.savings for a in model.agents] + agent_savings = [a.savings for a in model.schedule.agents] # return the sum of agents' savings return np.sum(agent_savings) @@ -57,7 +59,7 @@ def get_total_savings(model): def get_total_wallets(model): """sum of amounts of all agents' wallets""" - agent_wallets = [a.wallet for a in model.agents] + agent_wallets = [a.wallet for a in model.schedule.agents] # return the sum of all agents' wallets return np.sum(agent_wallets) @@ -73,7 +75,7 @@ def get_total_money(model): def get_total_loans(model): # list of amounts of all agents' loans - agent_loans = [a.loans for a in model.agents] + agent_loans = [a.loans for a in model.schedule.agents] # return sum of all agents' loans return np.sum(agent_loans) @@ -116,7 +118,7 @@ def __init__( self.height = height self.width = width self.init_people = init_people - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold @@ -136,7 +138,7 @@ def __init__( ) # create a single bank for the model - self.bank = Bank(self, self.reserve_percent) + self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): @@ -146,13 +148,15 @@ def __init__( p = Person(i, self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) + # add the Person object to the model schedule + self.schedule.add(p) self.running = True self.datacollector.collect(self) def step(self): # tell all the agents in the model to run their step function - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/bank_reserves/batch_run.py b/examples/bank_reserves/batch_run.py index 9b06ce3c..4a0115bb 100644 --- a/examples/bank_reserves/batch_run.py +++ b/examples/bank_reserves/batch_run.py @@ -37,7 +37,7 @@ def get_num_rich_agents(model): """list of rich agents""" - rich_agents = [a for a in model.agents if a.savings > model.rich_threshold] + rich_agents = [a for a in model.schedule.agents if a.savings > model.rich_threshold] # return number of rich agents return len(rich_agents) @@ -45,7 +45,7 @@ def get_num_rich_agents(model): def get_num_poor_agents(model): """list of poor agents""" - poor_agents = [a for a in model.agents if a.loans > 10] + poor_agents = [a for a in model.schedule.agents if a.loans > 10] # return number of poor agents return len(poor_agents) @@ -54,7 +54,9 @@ def get_num_mid_agents(model): """list of middle class agents""" mid_agents = [ - a for a in model.agents if a.loans < 10 and a.savings < model.rich_threshold + a + for a in model.schedule.agents + if a.loans < 10 and a.savings < model.rich_threshold ] # return number of middle class agents return len(mid_agents) @@ -63,7 +65,7 @@ def get_num_mid_agents(model): def get_total_savings(model): """list of amounts of all agents' savings""" - agent_savings = [a.savings for a in model.agents] + agent_savings = [a.savings for a in model.schedule.agents] # return the sum of agents' savings return np.sum(agent_savings) @@ -71,7 +73,7 @@ def get_total_savings(model): def get_total_wallets(model): """list of amounts of all agents' wallets""" - agent_wallets = [a.wallet for a in model.agents] + agent_wallets = [a.wallet for a in model.schedule.agents] # return the sum of all agents' wallets return np.sum(agent_wallets) @@ -89,7 +91,7 @@ def get_total_money(model): def get_total_loans(model): """list of amounts of all agents' loans""" - agent_loans = [a.loans for a in model.agents] + agent_loans = [a.loans for a in model.schedule.agents] # return sum of all agents' loans return np.sum(agent_loans) @@ -127,7 +129,7 @@ def __init__( self.height = height self.width = width self.init_people = init_people - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold @@ -148,8 +150,8 @@ def __init__( agent_reporters={"Wealth": "wealth"}, ) - # create a single bank object for the model - self.bank = Bank(self, self.reserve_percent) + # create a single bank for the model + self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): @@ -160,6 +162,8 @@ def __init__( p = Person(i, (x, y), self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) + # add the Person object to the model schedule + self.schedule.add(p) self.running = True @@ -167,7 +171,7 @@ def step(self): # collect data self.datacollector.collect(self) # tell all the agents in the model to run their step function - self.agents.shuffle().do("step") + self.schedule.step() def run_model(self): for i in range(self.run_time): diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb index c757f3a8..82ecc47b 100644 --- a/examples/boid_flockers/Flocker Test.ipynb +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.agents:\n", + " for boid in model.schedule.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index ec670d7a..42b3e9dd 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.agents: + for obj in model.schedule.agents: portrayal = self.portrayal_method(obj) x, y = obj.pos x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 6b032c33..8ddfc11a 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - + self.schedule = mesa.time.RandomActivation(self) self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -144,6 +144,7 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) + self.schedule.add(boid) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index c34d0937..11a3e958 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.agents] + agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - + self.schedule = mesa.time.RandomActivation(self) self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - + self.schedule.add(a) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/boltzmann_wealth_model_experimental/model.py b/examples/boltzmann_wealth_model_experimental/model.py index c34d0937..11a3e958 100644 --- a/examples/boltzmann_wealth_model_experimental/model.py +++ b/examples/boltzmann_wealth_model_experimental/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.agents] + agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - + self.schedule = mesa.time.RandomActivation(self) self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - + self.schedule.add(a) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py index 73776e36..d7b43374 100644 --- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py +++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py @@ -3,7 +3,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.agents] + agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -19,7 +19,7 @@ def __init__(self, num_agents=7, num_nodes=10): self.num_nodes = num_nodes if num_nodes >= self.num_agents else self.num_agents self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0.5) self.grid = mesa.space.NetworkGrid(self.G) - + self.schedule = mesa.time.RandomActivation(self) self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": lambda _: _.wealth}, @@ -30,7 +30,7 @@ def __init__(self, num_agents=7, num_nodes=10): # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - + self.schedule.add(a) # Add the agent to a random node self.grid.place_agent(a, list_of_random_nodes[i]) @@ -38,7 +38,7 @@ def __init__(self, num_agents=7, num_nodes=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/caching_and_replay/model.py b/examples/caching_and_replay/model.py index 53b2a42c..8b04368b 100644 --- a/examples/caching_and_replay/model.py +++ b/examples/caching_and_replay/model.py @@ -70,6 +70,7 @@ def __init__( self.homophily = homophily self.radius = radius + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 @@ -86,6 +87,7 @@ def __init__( agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent(self.next_id(), self, agent_type) self.grid.place_agent(agent, pos) + self.schedule.add(agent) self.datacollector.collect(self) @@ -94,9 +96,9 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.agents.shuffle().do("step") + self.schedule.step() self.datacollector.collect(self) - if self.happy == len(self.agents): + if self.happy == self.schedule.get_agent_count(): self.running = False diff --git a/examples/charts/charts/agents.py b/examples/charts/charts/agents.py index 1d561d5b..5d81a65c 100644 --- a/examples/charts/charts/agents.py +++ b/examples/charts/charts/agents.py @@ -10,18 +10,15 @@ Northwestern University, Evanston, IL. """ -from .random_walk import RandomWalker +import mesa +from .random_walk import RandomWalker -class Bank: - """Note that the Bank class is not a Mesa Agent, but just a regular Python - class. This is because there is only one bank in this model, and it does not - use any Mesa-specific features like the scheduler or the grid, and doesn't - have a step method. It is just used to keep track of the bank's reserves and - the amount it can loan out, for Person agents to interact with.""" - def __init__(self, model, reserve_percent=50): - self.model = model +class Bank(mesa.Agent): + def __init__(self, unique_id, model, reserve_percent=50): + # initialize the parent class with required parameters + super().__init__(unique_id, model) # for tracking total value of loans outstanding self.bank_loans = 0 """percent of deposits the bank must keep in reserves - this is set via @@ -176,6 +173,7 @@ def take_out_loan(self, amount): # increase the bank's outstanding loans self.bank.bank_loans += amount + # step is called for each agent in model.BankReservesModel.schedule.step() def step(self): # move to a cell in my Moore neighborhood self.random_move() diff --git a/examples/charts/charts/model.py b/examples/charts/charts/model.py index 616cf8be..00b6239a 100644 --- a/examples/charts/charts/model.py +++ b/examples/charts/charts/model.py @@ -26,14 +26,14 @@ def get_num_rich_agents(model): """return number of rich agents""" - rich_agents = [a for a in model.agents if a.savings > model.rich_threshold] + rich_agents = [a for a in model.schedule.agents if a.savings > model.rich_threshold] return len(rich_agents) def get_num_poor_agents(model): """return number of poor agents""" - poor_agents = [a for a in model.agents if a.loans > 10] + poor_agents = [a for a in model.schedule.agents if a.loans > 10] return len(poor_agents) @@ -41,7 +41,9 @@ def get_num_mid_agents(model): """return number of middle class agents""" mid_agents = [ - a for a in model.agents if a.loans < 10 and a.savings < model.rich_threshold + a + for a in model.schedule.agents + if a.loans < 10 and a.savings < model.rich_threshold ] return len(mid_agents) @@ -49,7 +51,7 @@ def get_num_mid_agents(model): def get_total_savings(model): """sum of all agents' savings""" - agent_savings = [a.savings for a in model.agents] + agent_savings = [a.savings for a in model.schedule.agents] # return the sum of agents' savings return np.sum(agent_savings) @@ -57,7 +59,7 @@ def get_total_savings(model): def get_total_wallets(model): """sum of amounts of all agents' wallets""" - agent_wallets = [a.wallet for a in model.agents] + agent_wallets = [a.wallet for a in model.schedule.agents] # return the sum of all agents' wallets return np.sum(agent_wallets) @@ -73,7 +75,7 @@ def get_total_money(model): def get_total_loans(model): # list of amounts of all agents' loans - agent_loans = [a.loans for a in model.agents] + agent_loans = [a.loans for a in model.schedule.agents] # return sum of all agents' loans return np.sum(agent_loans) @@ -99,7 +101,7 @@ def __init__( self.height = height self.width = width self.init_people = init_people - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold @@ -119,7 +121,7 @@ def __init__( ) # create a single bank for the model - self.bank = Bank(self, self.reserve_percent) + self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): @@ -129,13 +131,15 @@ def __init__( p = Person(i, self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) + # add the Person object to the model schedule + self.schedule.add(p) self.running = True self.datacollector.collect(self) def step(self): # tell all the agents in the model to run their step function - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/color_patches/color_patches/model.py b/examples/color_patches/color_patches/model.py index d8787304..4e69b965 100644 --- a/examples/color_patches/color_patches/model.py +++ b/examples/color_patches/color_patches/model.py @@ -36,7 +36,7 @@ def get_state(self): """Return the current state (OPINION) of this cell.""" return self._state - def determine_opinion(self): + def step(self): """ Determines the agent opinion for the next step by polling its neighbors The opinion is determined by the majority of the 8 neighbors' opinion @@ -54,7 +54,7 @@ def determine_opinion(self): self._next_state = self.random.choice(tied_opinions)[0] - def assume_opinion(self): + def advance(self): """ Set the state of the agent to the next state """ @@ -73,6 +73,7 @@ def __init__(self, width=20, height=20): """ super().__init__() self._grid = mesa.space.SingleGrid(width, height, torus=False) + self.schedule = mesa.time.SimultaneousActivation(self) # self._grid.coord_iter() # --> should really not return content + col + row @@ -84,17 +85,23 @@ def __init__(self, width=20, height=20): (row, col), self, ColorCell.OPINIONS[self.random.randrange(0, 16)] ) self._grid.place_agent(cell, (row, col)) + self.schedule.add(cell) self.running = True def step(self): """ - Perform the model step in two stages: - - First, all agents determine their next opinion based on their neighbors current opinions - - Then, all agents update their opinion to the next opinion + Advance the model one step. """ - self.agents.do("determine_opinion") - self.agents.do("assume_opinion") + self.schedule.step() + + # the following is a temporary fix for the framework classes accessing + # model attributes directly + # I don't think it should + # --> it imposes upon the model builder to use the attributes names that + # the framework expects. + # + # Traceback included in docstrings @property def grid(self): diff --git a/examples/conways_game_of_life/conways_game_of_life/cell.py b/examples/conways_game_of_life/conways_game_of_life/cell.py index d9e0e7ba..8639288d 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def determine_state(self): + def step(self): """ Compute if the cell will be dead or alive at the next tick. This is based on the number of alive or dead neighbors. The state is not @@ -46,7 +46,7 @@ def determine_state(self): if live_neighbors == 3: self._nextState = self.ALIVE - def assume_state(self): + def advance(self): """ Set the state to the new computed state -- computed in step(). """ diff --git a/examples/conways_game_of_life/conways_game_of_life/model.py b/examples/conways_game_of_life/conways_game_of_life/model.py index 76d9ca9f..f6c9637a 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -14,6 +14,15 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() + + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -24,14 +33,12 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) + self.schedule.add(cell) self.running = True def step(self): """ - Perform the model step in two stages: - - First, all cells assume their next state (whether they will be dead or alive) - - Then, all cells change state to their next state + Have the scheduler advance each cell by one step """ - self.agents.do("determine_state") - self.agents.do("assume_state") + self.schedule.step() diff --git a/examples/el_farol/el_farol/agents.py b/examples/el_farol/el_farol/agents.py index 77742d40..638269d7 100644 --- a/examples/el_farol/el_farol/agents.py +++ b/examples/el_farol/el_farol/agents.py @@ -14,7 +14,7 @@ def __init__(self, unique_id, model, memory_size, crowd_threshold, num_strategie self.utility = 0 self.update_strategies() - def update_attendance(self): + def step(self): prediction = self.predict_attendance( self.best_strategy, self.model.history[-self.memory_size :] ) diff --git a/examples/el_farol/el_farol/model.py b/examples/el_farol/el_farol/model.py index 2eee8088..c9a3d6c0 100644 --- a/examples/el_farol/el_farol/model.py +++ b/examples/el_farol/el_farol/model.py @@ -15,6 +15,7 @@ def __init__( super().__init__() self.running = True self.num_agents = N + self.schedule = mesa.time.RandomActivation(self) # Initialize the previous attendance randomly so the agents have a history # to work with from the start. @@ -24,8 +25,8 @@ def __init__( self.history = np.random.randint(0, 100, size=memory_size * 2).tolist() self.attendance = self.history[-1] for i in range(self.num_agents): - BarCustomer(i, self, memory_size, crowd_threshold, num_strategies) - + a = BarCustomer(i, self, memory_size, crowd_threshold, num_strategies) + self.schedule.add(a) self.datacollector = mesa.DataCollector( model_reporters={"Customers": "attendance"}, agent_reporters={"Utility": "utility", "Attendance": "attend"}, @@ -34,9 +35,10 @@ def __init__( def step(self): self.datacollector.collect(self) self.attendance = 0 - self.agents.shuffle().do("update_attendance") + self.schedule.step() # We ensure that the length of history is constant # after each step. self.history.pop(0) self.history.append(self.attendance) - self.agents.shuffle().do("update_strategies") + for agent in self.schedule.agents: + agent.update_strategies() diff --git a/examples/epstein_civil_violence/epstein_civil_violence/model.py b/examples/epstein_civil_violence/epstein_civil_violence/model.py index 70b3a841..6bce24eb 100644 --- a/examples/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/epstein_civil_violence/epstein_civil_violence/model.py @@ -58,7 +58,7 @@ def __init__( self.movement = movement self.max_iters = max_iters self.iteration = 0 - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) model_reporters = { @@ -86,7 +86,7 @@ def __init__( cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[x][y] = cop - + self.schedule.add(cop) elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( unique_id, @@ -100,6 +100,7 @@ def __init__( ) unique_id += 1 self.grid[x][y] = citizen + self.schedule.add(citizen) self.running = True self.datacollector.collect(self) @@ -108,7 +109,7 @@ def step(self): """ Advance the model by one step and collect data. """ - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) self.iteration += 1 @@ -121,7 +122,7 @@ def count_type_citizens(model, condition, exclude_jailed=True): Helper method to count agents by Quiescent/Active. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence > 0: @@ -136,7 +137,7 @@ def count_jailed(model): Helper method to count jailed agents. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count @@ -147,7 +148,7 @@ def count_cops(model): Helper method to count jailed agents. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "cop": count += 1 return count diff --git a/examples/forest_fire/Forest Fire Model.ipynb b/examples/forest_fire/Forest Fire Model.ipynb index 28fc06f3..afe4142a 100644 --- a/examples/forest_fire/Forest Fire Model.ipynb +++ b/examples/forest_fire/Forest Fire Model.ipynb @@ -35,7 +35,8 @@ "from mesa import Agent, Model\n", "from mesa.batchrunner import BatchRunner\n", "from mesa.datacollection import DataCollector\n", - "from mesa.space import Grid" + "from mesa.space import Grid\n", + "from mesa.time import RandomActivation" ] }, { @@ -128,6 +129,7 @@ " density: What fraction of grid cells have a tree in them.\n", " \"\"\"\n", " # Set up model objects\n", + " self.schedule = RandomActivation(self)\n", " self.grid = Grid(width, height, torus=False)\n", " self.dc = DataCollector(\n", " {\n", @@ -147,13 +149,14 @@ " if x == 0:\n", " new_tree.condition = \"On Fire\"\n", " self.grid[x][y] = new_tree\n", + " self.schedule.add(new_tree)\n", " self.running = True\n", "\n", " def step(self):\n", " \"\"\"\n", " Advance the model by one step.\n", " \"\"\"\n", - " self.agents.shuffle().do(\"step\")\n", + " self.schedule.step()\n", " self.dc.collect(self)\n", " # Halt if no more fire\n", " if self.count_type(self, \"On Fire\") == 0:\n", @@ -165,7 +168,7 @@ " Helper method to count trees in a given condition in a given model.\n", " \"\"\"\n", " count = 0\n", - " for tree in model.agents:\n", + " for tree in model.schedule.agents:\n", " if tree.condition == tree_condition:\n", " count += 1\n", " return count" @@ -336,7 +339,9 @@ "source": [ "# At the end of each model run, calculate the fraction of trees which are Burned Out\n", "model_reporter = {\n", - " \"BurnedOut\": lambda m: (ForestFire.count_type(m, \"Burned Out\") / len(m.agents))\n", + " \"BurnedOut\": lambda m: (\n", + " ForestFire.count_type(m, \"Burned Out\") / m.schedule.get_agent_count()\n", + " )\n", "}" ] }, diff --git a/examples/forest_fire/forest_fire/model.py b/examples/forest_fire/forest_fire/model.py index 33bac16f..4b2d5b41 100644 --- a/examples/forest_fire/forest_fire/model.py +++ b/examples/forest_fire/forest_fire/model.py @@ -18,7 +18,7 @@ def __init__(self, width=100, height=100, density=0.65): """ super().__init__() # Set up model objects - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=False) self.datacollector = mesa.DataCollector( @@ -38,6 +38,7 @@ def __init__(self, width=100, height=100, density=0.65): if x == 0: new_tree.condition = "On Fire" self.grid.place_agent(new_tree, (x, y)) + self.schedule.add(new_tree) self.running = True self.datacollector.collect(self) @@ -46,7 +47,7 @@ def step(self): """ Advance the model by one step. """ - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) @@ -60,7 +61,7 @@ def count_type(model, tree_condition): Helper method to count trees in a given condition in a given model. """ count = 0 - for tree in model.agents: + for tree in model.schedule.agents: if tree.condition == tree_condition: count += 1 return count diff --git a/examples/hex_snowflake/hex_snowflake/cell.py b/examples/hex_snowflake/hex_snowflake/cell.py index 1c9c35df..a9fd64ec 100644 --- a/examples/hex_snowflake/hex_snowflake/cell.py +++ b/examples/hex_snowflake/hex_snowflake/cell.py @@ -29,7 +29,7 @@ def neighbors(self): def considered(self): return self.isConsidered is True - def determine_state(self): + def step(self): """ Compute if the cell will be dead or alive at the next tick. A dead cell will become alive if it has only one neighbor. The state is not @@ -53,8 +53,8 @@ def determine_state(self): for a in self.neighbors: a.isConsidered = True - def assume_state(self): + def advance(self): """ - Set the state to the new computed state + Set the state to the new computed state -- computed in step(). """ self.state = self._nextState diff --git a/examples/hex_snowflake/hex_snowflake/model.py b/examples/hex_snowflake/hex_snowflake/model.py index 349d41b0..329bfe11 100644 --- a/examples/hex_snowflake/hex_snowflake/model.py +++ b/examples/hex_snowflake/hex_snowflake/model.py @@ -14,6 +14,14 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + # Use a hexagonal grid, where edges wrap around. self.grid = mesa.space.HexSingleGrid(width, height, torus=True) @@ -21,6 +29,7 @@ def __init__(self, width=50, height=50): for contents, pos in self.grid.coord_iter(): cell = Cell(pos, self) self.grid.place_agent(cell, pos) + self.schedule.add(cell) # activate the center(ish) cell. centerishCell = self.grid[width // 2][height // 2] @@ -33,9 +42,6 @@ def __init__(self, width=50, height=50): def step(self): """ - Perform the model step in two stages: - - First, all cells assume their next state (whether they will be dead or alive) - - Then, all cells change state to their next state + Have the scheduler advance each cell by one step """ - self.agents.do("determine_state") - self.agents.do("assume_state") + self.schedule.step() diff --git a/examples/hotelling_law/app.py b/examples/hotelling_law/app.py index 3b1d8ef2..60c3dbca 100644 --- a/examples/hotelling_law/app.py +++ b/examples/hotelling_law/app.py @@ -121,7 +121,7 @@ def space_drawer(model, agent_portrayal): cell_store_contents = {} # Track store agents in each cell jitter_amount = 0.3 # Jitter for visual separation - for agent in model.agents: + for agent in model.schedule.agents: portrayal = agent_portrayal(agent) # Track store agents for cell coloring @@ -150,7 +150,7 @@ def space_drawer(model, agent_portrayal): ax.add_patch(rect) # Jittered scatter plot for all agents - for agent in model.agents: + for agent in model.schedule.agents: portrayal = agent_portrayal(agent) jitter_x = np.random.uniform(-jitter_amount, jitter_amount) + agent.pos[0] + 0.5 jitter_y = np.random.uniform(-jitter_amount, jitter_amount) + agent.pos[1] + 0.5 @@ -177,7 +177,9 @@ def make_market_share_and_price_chart(model): # Get store agents and sort them by their unique_id # to ensure consistent order - store_agents = [agent for agent in model.agents if isinstance(agent, StoreAgent)] + store_agents = [ + agent for agent in model.schedule.agents if isinstance(agent, StoreAgent) + ] store_agents_sorted = sorted(store_agents, key=lambda agent: agent.unique_id) # Now gather market shares, prices, and labels using the sorted list @@ -239,7 +241,7 @@ def make_price_changes_line_chart(model): # Retrieve agent colors based on their portrayal agent_colors = { f"Store_{agent.unique_id}_Price": agent_portrayal(agent)["color"] - for agent in model.agents + for agent in model.schedule.agents if isinstance(agent, StoreAgent) } @@ -275,7 +277,7 @@ def make_market_share_line_chart(model): # Retrieve agent colors based on their portrayal agent_colors = { f"Store_{agent.unique_id}_Market Share": agent_portrayal(agent)["color"] - for agent in model.agents + for agent in model.schedule.agents if isinstance(agent, StoreAgent) } @@ -311,7 +313,7 @@ def make_revenue_line_chart(model): # Retrieve agent colors based on their portrayal agent_colors = { f"Store_{agent.unique_id}_Revenue": agent_portrayal(agent)["color"] - for agent in model.agents + for agent in model.schedule.agents if isinstance(agent, StoreAgent) } diff --git a/examples/hotelling_law/hotelling_law/agents.py b/examples/hotelling_law/hotelling_law/agents.py index f4300711..62d8cc8d 100644 --- a/examples/hotelling_law/hotelling_law/agents.py +++ b/examples/hotelling_law/hotelling_law/agents.py @@ -119,7 +119,7 @@ def adjust_price(self): def identify_competitors(self): competitors = [] - for agent in self.model.agents: + for agent in self.model.schedule.agents: if isinstance(agent, StoreAgent) and agent.unique_id != self.unique_id: # Estimate market overlap as a measure of competition overlap = self.estimate_market_overlap(agent) diff --git a/examples/hotelling_law/hotelling_law/model.py b/examples/hotelling_law/hotelling_law/model.py index dd1ad704..40c19b81 100644 --- a/examples/hotelling_law/hotelling_law/model.py +++ b/examples/hotelling_law/hotelling_law/model.py @@ -5,6 +5,7 @@ from mesa.agent import AgentSet from mesa.datacollection import DataCollector from mesa.space import MultiGrid +from mesa.time import RandomActivation from .agents import ConsumerAgent, StoreAgent @@ -93,6 +94,8 @@ def __init__( self.consumer_preferences = consumer_preferences # Type of environment ('grid' or 'line'). self.environment_type = environment_type + # Scheduler to activate agents one at a time, in random order. + self.schedule = RandomActivation(self) # Initialize AgentSets for store and consumer agents self.store_agents = AgentSet([], self) self.consumer_agents = AgentSet([], self) @@ -185,7 +188,7 @@ def _initialize_agents(self): mobile_agents_assigned += 1 agent = StoreAgent(unique_id, self, can_move=can_move, strategy=strategy) - + self.schedule.add(agent) self.store_agents.add(agent) # Randomly place agents on the grid for a grid environment. @@ -197,7 +200,7 @@ def _initialize_agents(self): for i in range(self.num_consumers): # Ensure unique ID across all agents consumer = ConsumerAgent(self.num_agents + i, self) - + self.schedule.add(consumer) self.consumer_agents.add(consumer) # Place consumer randomly on the grid x = self.random.randrange(self.grid.width) @@ -215,8 +218,8 @@ def step(self): """Advance the model by one step.""" # Collect data for the current step. self.datacollector.collect(self) - # Activate all agents in random order - self.agents.shuffle().do("step") + # Activate the next agent in the schedule. + self.schedule.step() # Update market dynamics based on the latest actions self.recalculate_market_share() diff --git a/examples/schelling/model.py b/examples/schelling/model.py index 5aa71415..dfba4efb 100644 --- a/examples/schelling/model.py +++ b/examples/schelling/model.py @@ -68,6 +68,7 @@ def __init__( self.homophily = homophily self.radius = radius + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 @@ -84,6 +85,7 @@ def __init__( agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent(self.next_id(), self, agent_type) self.grid.place_agent(agent, pos) + self.schedule.add(agent) self.datacollector.collect(self) @@ -92,9 +94,9 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.agents.shuffle().do("step") + self.schedule.step() self.datacollector.collect(self) - if self.happy == len(self.agents): + if self.happy == self.schedule.get_agent_count(): self.running = False diff --git a/examples/shape_example/shape_example/model.py b/examples/shape_example/shape_example/model.py index 85678bce..d1fdbe97 100644 --- a/examples/shape_example/shape_example/model.py +++ b/examples/shape_example/shape_example/model.py @@ -14,7 +14,7 @@ def __init__(self, N=2, width=20, height=10): self.N = N # num of agents self.headings = ((1, 0), (0, 1), (-1, 0), (0, -1)) # tuples are fast self.grid = mesa.space.SingleGrid(width, height, torus=False) - + self.schedule = mesa.time.RandomActivation(self) self.make_walker_agents() self.running = True @@ -31,9 +31,9 @@ def make_walker_agents(self): if self.grid.is_cell_empty(pos): print(f"Creating agent {unique_id} at ({x}, {y})") a = Walker(unique_id, self, heading) - + self.schedule.add(a) self.grid.place_agent(a, pos) unique_id += 1 def step(self): - self.agents.shuffle().do("step") + self.schedule.step() diff --git a/examples/virus_on_network/virus_on_network/model.py b/examples/virus_on_network/virus_on_network/model.py index 7c69bdcd..a33e7545 100644 --- a/examples/virus_on_network/virus_on_network/model.py +++ b/examples/virus_on_network/virus_on_network/model.py @@ -47,7 +47,7 @@ def __init__( prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = mesa.space.NetworkGrid(self.G) - + self.schedule = mesa.time.RandomActivation(self) self.initial_outbreak_size = ( initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes ) @@ -75,7 +75,7 @@ def __init__( self.recovery_chance, self.gain_resistance_chance, ) - + self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) @@ -96,7 +96,7 @@ def resistant_susceptible_ratio(self): return math.inf def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self)