This guide contains breaking changes between major Mesa versions and how to resolve them.
Non-breaking changes aren't included, for those see our Release history.
Mesa 3.0 introduces significant changes to core functionalities, including agent and model initialization, scheduling, and visualization. The guide below outlines these changes and provides instructions for migrating your existing Mesa projects to version 3.0.
This guide is a work in progress. The development of it is tracked in Issue #2233.
We recommend the following upgrade strategy:
- Update to the latest Mesa 2.x release (
mesa<3
). - Update to the latest Mesa 3.0.x release (
mesa<3.1
). - Update to the latest Mesa 3.x release (
mesa<4
).
With each update, resolve all errors and warnings, before updating to the next one.
Currently, we have reserved the following variables:
- Model:
agents
,current_id
,random
,running
,steps
,time
. - Agent:
unique_id
,model
.
You can use (read) any reserved variable, but Mesa may update them automatically and rely on them, so modify/update at your own risk.
Any variables starting with an underscore (_
) are considered private and for Mesa's internal use. We might use any of those. Modifying or overwriting any private variable is at your own risk.
- Ref: Discussion #2230, PR #2225
The mesa.flat
namespace is removed. Use the full namespace for your imports.
- Ref: PR #2091
In Mesa 3.0, it is now mandatory to call super().__init__()
when initializing your model class. This ensures that all necessary Mesa model variables are correctly set up and agents are properly added to the model. If you want to control the seed of the random number generator, you have to pass this as a keyword argument to super as shown below.
Make sure all your model classes explicitly call super().__init__()
in their __init__
method:
class MyModel(mesa.Model):
def __init__(self, some_arg_I_need, seed=None, some_kwarg_I_need=True):
super().__init__(seed=seed) # Calling super is now required, passing seed is highly recommended
# Your model initialization code here
# this code uses some_arg_I_need and my_init_kwarg
This change ensures that all Mesa models are properly initialized, which is crucial for:
- Correctly adding agents to the model
- Setting up other essential Mesa model variables
- Maintaining consistency across all models
If you forget to call super().__init__()
, you'll now see this error:
RuntimeError: The Mesa Model class was not initialized. You must explicitly initialize the Model by calling super().__init__() on initialization.
In Mesa 3.0, unique_id
for agents is now automatically assigned, simplifying agent creation and ensuring unique IDs across all agents in a model.
-
Remove
unique_id
from agent initialization:# Old agent = MyAgent(unique_id=unique_id, model=self, ...) agent = MyAgent(unique_id, self, ...) agent = MyAgent(self.next_id(), self, ...) # New agent = MyAgent(model=self, ...) agent = MyAgent(self, ...)
-
Remove
unique_id
from Agent super() call:# Old class MyAgent(Agent): def __init__(self, unique_id, model, ...): super().__init__(unique_id, model) # New class MyAgent(Agent): def __init__(self, model, ...): super().__init__(model)
-
Important notes:
unique_id
is now automatically assigned relative to a Model instance and starts from 1Model.next_id()
is removed- If you previously used custom
unique_id
values, store that information in a separate attribute
- Ref: PR #2226, PR #2260, Mesa-examples PR #194, Issue #2213
In Mesa 3.0, the Model class internally manages agents using several data structures:
self._agents
: A dictionary containing hard references to all agents, indexed by theirunique_id
.self._agents_by_type
: A dictionary of AgentSets, organizing agents by their type.self._all_agents
: An AgentSet containing all agents in the model.
These internal structures are used to efficiently manage and access agents. Users should interact with agents through the public model.agents
property, which returns the self._all_agents
AgentSet.
- Attempting to set
model.agents
now raises anAttributeError
instead of a warning. This attribute is reserved for internal use by Mesa. - If you were previously setting
model.agents
in your code, you must update it to use a different attribute name for custom agent storage.
For example, replace:
model.agents = my_custom_agents
With:
model.custom_agents = my_custom_agents
The steps
counter is now automatically increased. With each call to Model.steps()
it's increased by 1, at the beginning of the step.
You can access it by Model.steps
, and it's internally in the datacollector, batchrunner and the visualisation.
Model._time
is removed. You can define your own time variable if needed.Model._steps
steps is renamed toModel.steps
.
- The
Model._advance_time()
method is removed. This now happens automatically.
The whole Time module in Mesa is deprecated and will be removed in Mesa 3.1. All schedulers should be replaced with AgentSet functionality and the internal Model.steps
counter. This allows much more flexibility in how to activate Agents and makes it explicit what's done exactly.
Here's how to replace each scheduler:
Replace:
self.schedule = BaseScheduler(self)
self.schedule.step()
With:
self.agents.do("step")
Replace:
self.schedule = RandomActivation(self)
self.schedule.step()
With:
self.agents.shuffle_do("step")
Replace:
self.schedule = SimultaneousActivation(self)
self.schedule.step()
With:
self.agents.do("step")
self.agents.do("advance")
Replace:
self.schedule = StagedActivation(self, ["stage1", "stage2", "stage3"])
self.schedule.step()
With:
for stage in ["stage1", "stage2", "stage3"]:
self.agents.do(stage)
If you were using the shuffle
and/or shuffle_between_stages
options:
stages = ["stage1", "stage2", "stage3"]
if shuffle:
self.random.shuffle(stages)
for stage in stages:
if shuffle_between_stages:
self.agents.shuffle_do(stage)
else:
self.agents.do(stage)
Replace:
self.schedule = RandomActivationByType(self)
self.schedule.step()
With:
for agent_class in self.agent_types:
self.agents_by_type[agent_class].shuffle_do("step")
The RandomActivationByType
scheduler had a step_type
method that allowed stepping only agents of a specific type. To replicate this functionality using AgentSet:
Replace:
self.schedule.step_type(AgentType)
With:
self.agents_by_type[AgentType].shuffle_do("step")
- The
Model.steps
counter is now automatically incremented. You don't need to manage it manually. - If you were using
self.schedule.agents
, replace it withself.agents
. - If you were using
self.schedule.get_agent_count()
, replace it withlen(self.agents)
. - If you were using
self.schedule.agents_by_type
, replace it withself.agents_by_type
. - Agents are now automatically added to or removed from the model's
AgentSet
(model.agents
) when they are created or deleted, eliminating the need to manually callself.schedule.add()
orself.schedule.remove()
.- However, you still need to explicitly remove the Agent itself by using
Agent.remove()
. Typically, this means:- Replace
self.schedule.remove(agent)
withagent.remove()
in the Model. - Replace
self.model.schedule.remove(self)
withself.remove()
within the Agent.
- Replace
- However, you still need to explicitly remove the Agent itself by using
From now on you're now not bound by 5 distinct schedulers, but can mix and match any combination of AgentSet methods (do
, shuffle
, select
, etc.) to get the desired Agent activation.
Ref: Original discussion #1912, decision discussion #2231, example updates #183 and #201, PR #2306
Mesa has adopted a new API for our frontend. If you already migrated to the experimental new SolaraViz you can still use the import from mesa.experimental. Otherwise here is a list of things you need to change.
Note: SolaraViz is experimental and still in active development for Mesa 3.0. While we attempt to minimize them, there might be API breaking changes between Mesa 3.0 and 3.1. There won't be breaking changes between Mesa 3.0.x patch releases.
Previously SolaraViz was initialized by providing a model_cls
and a model_params
. This has changed to expect a model instance model
. You can still provide (user-settable) model_params
, but only if users should be able to change them. It is now also possible to pass in a "reactive model" by first calling model = solara.reactive(model)
. This is useful for notebook environments. It allows you to pass the model to the SolaraViz Module, but continue to use the model. For example calling model.value.step()
(notice the extra .value) will automatically update the plots. This currently only automatically works for the step method, you can force visualization updates by calling model.value.force_update()
.
Previously we included a default space drawer that you could configure with an agent_portrayal
function. You now have to explicitly create a space drawer with the agent_portrayal
function
# old
from mesa.experimental import SolaraViz
SolaraViz(model_cls, model_params, agent_portrayal=agent_portrayal)
# new
from mesa.visualization import SolaraViz, make_space_component
SolaraViz(model, components=[make_space_component(agent_portrayal)])
"Measure" plots also need to be made explicit here. Previously, measure could either be 1) A function that receives a model and returns a solara component or 2) A string or list of string of variables that are collected by the datacollector and are to be plotted as a line plot. 1) still works, but you can pass that function to "components" directly. 2) needs to explicitly call the make_plot_measure()
function.
# old
from mesa.experimental import SolaraViz
def make_plot(model):
...
SolaraViz(model_cls, model_params, measures=[make_plot, "foo", ["bar", "baz"]])
# new
from mesa.visualization import SolaraViz, make_plot_component
SolaraViz(model, components=[make_plot, make_plot_component("foo"), make_plot_component("bar", "baz")])
To plot model-dependent text the experimental SolaraViz provided a make_text
function that wraps another functions that receives the model and turns its string return value into a solara text component. Again, this other function can now be passed directly to the new SolaraViz components array. It is okay if your function just returns a string.
# old
from mesa.experimental import SolaraViz, make_text
def show_steps(model):
return f"Steps: {model.steps}"
SolaraViz(model_cls, model_params, measures=make_text(show_steps))
# new
from mesa.visualisation import SolaraViz
def show_steps(model):
return f"Steps: {model.steps}"
SolaraViz(model, components=[show_steps])
The initialize_data_collector
in the Model class is removed. In the Model class, replace:
Replace:
self.initialize_data_collector(...)
With:
self.datacollector = DataCollector(...)