Mesa 3.0.2__py3-none-any.whl → 3.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

Files changed (50) hide show
  1. mesa/__init__.py +4 -6
  2. mesa/agent.py +48 -25
  3. mesa/batchrunner.py +14 -1
  4. mesa/datacollection.py +1 -6
  5. mesa/examples/__init__.py +2 -2
  6. mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
  7. mesa/examples/advanced/pd_grid/app.py +5 -0
  8. mesa/examples/advanced/sugarscape_g1mt/app.py +7 -2
  9. mesa/examples/basic/boid_flockers/app.py +5 -0
  10. mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
  11. mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
  12. mesa/examples/basic/conways_game_of_life/app.py +5 -0
  13. mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
  14. mesa/examples/basic/schelling/app.py +5 -0
  15. mesa/examples/basic/virus_on_network/app.py +5 -0
  16. mesa/experimental/__init__.py +17 -10
  17. mesa/experimental/cell_space/__init__.py +19 -7
  18. mesa/experimental/cell_space/cell.py +22 -37
  19. mesa/experimental/cell_space/cell_agent.py +12 -1
  20. mesa/experimental/cell_space/cell_collection.py +18 -3
  21. mesa/experimental/cell_space/discrete_space.py +15 -64
  22. mesa/experimental/cell_space/grid.py +74 -4
  23. mesa/experimental/cell_space/network.py +13 -1
  24. mesa/experimental/cell_space/property_layer.py +444 -0
  25. mesa/experimental/cell_space/voronoi.py +13 -1
  26. mesa/experimental/devs/__init__.py +20 -2
  27. mesa/experimental/devs/eventlist.py +19 -1
  28. mesa/experimental/devs/simulator.py +24 -8
  29. mesa/experimental/mesa_signals/__init__.py +23 -0
  30. mesa/experimental/mesa_signals/mesa_signal.py +485 -0
  31. mesa/experimental/mesa_signals/observable_collections.py +133 -0
  32. mesa/experimental/mesa_signals/signals_util.py +52 -0
  33. mesa/mesa_logging.py +190 -0
  34. mesa/model.py +17 -74
  35. mesa/visualization/__init__.py +2 -2
  36. mesa/visualization/mpl_space_drawing.py +2 -2
  37. mesa/visualization/solara_viz.py +23 -5
  38. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/METADATA +3 -4
  39. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/RECORD +43 -44
  40. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/WHEEL +1 -1
  41. mesa/experimental/UserParam.py +0 -67
  42. mesa/experimental/components/altair.py +0 -81
  43. mesa/experimental/components/matplotlib.py +0 -242
  44. mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
  45. mesa/experimental/devs/examples/wolf_sheep.py +0 -250
  46. mesa/experimental/solara_viz.py +0 -453
  47. mesa/time.py +0 -391
  48. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/entry_points.txt +0 -0
  49. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/licenses/LICENSE +0 -0
  50. {mesa-3.0.2.dist-info → mesa-3.1.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,133 @@
1
+ """Observable collection types that emit signals when modified.
2
+
3
+ This module extends Mesa's reactive programming capabilities to collection types like
4
+ lists. Observable collections emit signals when items are added, removed, or modified,
5
+ allowing other components to react to changes in the collection's contents.
6
+
7
+ The module provides:
8
+ - ObservableList: A list descriptor that emits signals on modifications
9
+ - SignalingList: The underlying list implementation that manages signal emission
10
+
11
+ These classes enable building models where components need to track and react to
12
+ changes in collections of agents, resources, or other model elements.
13
+ """
14
+
15
+ from collections.abc import Iterable, MutableSequence
16
+ from typing import Any
17
+
18
+ from .mesa_signal import BaseObservable, HasObservables
19
+
20
+ __all__ = [
21
+ "ObservableList",
22
+ ]
23
+
24
+
25
+ class ObservableList(BaseObservable):
26
+ """An ObservableList that emits signals on changes to the underlying list."""
27
+
28
+ def __init__(self):
29
+ """Initialize the ObservableList."""
30
+ super().__init__()
31
+ self.signal_types: set = {"remove", "replace", "change", "insert", "append"}
32
+ self.fallback_value = []
33
+
34
+ def __set__(self, instance: "HasObservables", value: Iterable):
35
+ """Set the value of the descriptor attribute.
36
+
37
+ Args:
38
+ instance: The instance on which to set the attribute.
39
+ value: The value to set the attribute to.
40
+
41
+ """
42
+ super().__set__(instance, value)
43
+ setattr(
44
+ instance,
45
+ self.private_name,
46
+ SignalingList(value, instance, self.public_name),
47
+ )
48
+
49
+
50
+ class SignalingList(MutableSequence[Any]):
51
+ """A basic lists that emits signals on changes."""
52
+
53
+ __slots__ = ["data", "name", "owner"]
54
+
55
+ def __init__(self, iterable: Iterable, owner: HasObservables, name: str):
56
+ """Initialize a SignalingList.
57
+
58
+ Args:
59
+ iterable: initial values in the list
60
+ owner: the HasObservables instance on which this list is defined
61
+ name: the attribute name to which this list is assigned
62
+
63
+ """
64
+ self.owner: HasObservables = owner
65
+ self.name: str = name
66
+ self.data = list(iterable)
67
+
68
+ def __setitem__(self, index: int, value: Any) -> None:
69
+ """Set item to index.
70
+
71
+ Args:
72
+ index: the index to set item to
73
+ value: the item to set
74
+
75
+ """
76
+ old_value = self.data[index]
77
+ self.data[index] = value
78
+ self.owner.notify(self.name, old_value, value, "replace", index=index)
79
+
80
+ def __delitem__(self, index: int) -> None:
81
+ """Delete item at index.
82
+
83
+ Args:
84
+ index: The index of the item to remove
85
+
86
+ """
87
+ old_value = self.data
88
+ del self.data[index]
89
+ self.owner.notify(self.name, old_value, None, "remove", index=index)
90
+
91
+ def __getitem__(self, index) -> Any:
92
+ """Get item at index.
93
+
94
+ Args:
95
+ index: The index of the item to retrieve
96
+
97
+ Returns:
98
+ the item at index
99
+ """
100
+ return self.data[index]
101
+
102
+ def __len__(self) -> int:
103
+ """Return the length of the list."""
104
+ return len(self.data)
105
+
106
+ def insert(self, index, value):
107
+ """Insert value at index.
108
+
109
+ Args:
110
+ index: the index to insert value into
111
+ value: the value to insert
112
+
113
+ """
114
+ self.data.insert(index, value)
115
+ self.owner.notify(self.name, None, value, "insert", index=index)
116
+
117
+ def append(self, value):
118
+ """Insert value at index.
119
+
120
+ Args:
121
+ index: the index to insert value into
122
+ value: the value to insert
123
+
124
+ """
125
+ index = len(self.data)
126
+ self.data.append(value)
127
+ self.owner.notify(self.name, None, value, "append", index=index)
128
+
129
+ def __str__(self):
130
+ return self.data.__str__()
131
+
132
+ def __repr__(self):
133
+ return self.data.__repr__()
@@ -0,0 +1,52 @@
1
+ """Utility functions and classes for Mesa's signals implementation.
2
+
3
+ This module provides helper functionality used by Mesa's reactive programming system:
4
+
5
+ - AttributeDict: A dictionary subclass that allows attribute-style access to its contents
6
+ - create_weakref: Helper function to properly create weak references to different types
7
+
8
+ These utilities support the core signals implementation by providing reference
9
+ management and convenient data structures used throughout the reactive system.
10
+ """
11
+
12
+ import weakref
13
+
14
+ __all__ = [
15
+ "AttributeDict",
16
+ "create_weakref",
17
+ ]
18
+
19
+
20
+ class AttributeDict(dict):
21
+ """A dict with attribute like access.
22
+
23
+ Each value can be accessed as if it were an attribute with its key as attribute name
24
+
25
+ """
26
+
27
+ # I want our signals to act like traitlet signals, so this is inspired by trailets Bunch
28
+ # and some stack overflow posts.
29
+ __setattr__ = dict.__setitem__
30
+ __delattr__ = dict.__delitem__
31
+
32
+ def __getattr__(self, key): # noqa: D105
33
+ try:
34
+ return self.__getitem__(key)
35
+ except KeyError as e:
36
+ # we need to go from key error to attribute error
37
+ raise AttributeError(key) from e
38
+
39
+ def __dir__(self): # noqa: D105
40
+ # allows us to easily access all defined attributes
41
+ names = dir({})
42
+ names.extend(self.keys())
43
+ return names
44
+
45
+
46
+ def create_weakref(item, callback=None):
47
+ """Helper function to create a correct weakref for any item."""
48
+ if hasattr(item, "__self__"):
49
+ ref = weakref.WeakMethod(item, callback)
50
+ else:
51
+ ref = weakref.ref(item, callback)
52
+ return ref
mesa/mesa_logging.py ADDED
@@ -0,0 +1,190 @@
1
+ """This provides logging functionality for MESA.
2
+
3
+ It is modeled on the default `logging approach that comes with Python <https://docs.python.org/library/logging.html>`_.
4
+
5
+
6
+ """
7
+
8
+ import inspect
9
+ import logging
10
+ from functools import wraps
11
+ from logging import DEBUG, INFO
12
+
13
+ __all__ = [
14
+ "DEBUG",
15
+ "DEFAULT_LEVEL",
16
+ "INFO",
17
+ "LOGGER_NAME",
18
+ "function_logger",
19
+ "get_module_logger",
20
+ "get_rootlogger",
21
+ "log_to_stderr",
22
+ "method_logger",
23
+ ]
24
+ LOGGER_NAME = "MESA"
25
+ DEFAULT_LEVEL = DEBUG
26
+
27
+
28
+ def create_module_logger(name: str | None = None):
29
+ """Helper function for creating a module logger.
30
+
31
+ Args:
32
+ name (str): The name to be given to the logger. If the name is None, the name defaults to the name of the module.
33
+
34
+ """
35
+ if name is None:
36
+ frm = inspect.stack()[1]
37
+ mod = inspect.getmodule(frm[0])
38
+ name = mod.__name__
39
+ logger = logging.getLogger(f"{LOGGER_NAME}.{name}")
40
+
41
+ _module_loggers[name] = logger
42
+ return logger
43
+
44
+
45
+ def get_module_logger(name: str):
46
+ """Helper function for getting the module logger.
47
+
48
+ Args:
49
+ name (str): The name of the module in which the method being decorated is located
50
+
51
+ """
52
+ try:
53
+ logger = _module_loggers[name]
54
+ except KeyError:
55
+ logger = create_module_logger(name)
56
+
57
+ return logger
58
+
59
+
60
+ _rootlogger = None
61
+ _module_loggers = {}
62
+ _logger = get_module_logger(__name__)
63
+
64
+
65
+ class MESAColorFormatter(logging.Formatter):
66
+ """Custom formatter for color based formatting."""
67
+
68
+ grey = "\x1b[38;20m"
69
+ green = "\x1b[32m"
70
+ yellow = "\x1b[33;20m"
71
+ red = "\x1b[31;20m"
72
+ bold_red = "\x1b[31;1m"
73
+ reset = "\x1b[0m"
74
+ format = (
75
+ "[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]"
76
+ )
77
+
78
+ FORMATS = {
79
+ logging.DEBUG: grey + format + reset,
80
+ logging.INFO: green + format + reset,
81
+ logging.WARNING: yellow + format + reset,
82
+ logging.ERROR: red + format + reset,
83
+ logging.CRITICAL: bold_red + format + reset,
84
+ }
85
+
86
+ def format(self, record):
87
+ """Format record."""
88
+ log_fmt = self.FORMATS.get(record.levelno)
89
+ formatter = logging.Formatter(log_fmt)
90
+ return formatter.format(record)
91
+
92
+
93
+ def method_logger(name: str):
94
+ """Decorator for adding logging to a method.
95
+
96
+ Args:
97
+ name (str): The name of the module in which the method being decorated is located
98
+
99
+ """
100
+ logger = get_module_logger(name)
101
+ classname = inspect.getouterframes(inspect.currentframe())[1][3]
102
+
103
+ def real_decorator(func):
104
+ @wraps(func)
105
+ def wrapper(*args, **kwargs):
106
+ # hack, because log is applied to methods, we can get
107
+ # object instance as first arguments in args
108
+ logger.debug(
109
+ f"calling {classname}.{func.__name__} with {args[1::]} and {kwargs}"
110
+ )
111
+ res = func(*args, **kwargs)
112
+ return res
113
+
114
+ return wrapper
115
+
116
+ return real_decorator
117
+
118
+
119
+ def function_logger(name):
120
+ """Decorator for adding logging to a Function.
121
+
122
+ Args:
123
+ name (str): The name of the module in which the function being decorated is located
124
+
125
+ """
126
+ logger = get_module_logger(name)
127
+
128
+ def real_decorator(func):
129
+ @wraps(func)
130
+ def wrapper(*args, **kwargs):
131
+ logger.debug(f"calling {func.__name__} with {args} and {kwargs}")
132
+ res = func(*args, **kwargs)
133
+ return res
134
+
135
+ return wrapper
136
+
137
+ return real_decorator
138
+
139
+
140
+ def get_rootlogger():
141
+ """Returns root logger used by MESA.
142
+
143
+ Returns:
144
+ the root logger of MESA
145
+
146
+ """
147
+ global _rootlogger # noqa: PLW0603
148
+
149
+ if not _rootlogger:
150
+ _rootlogger = logging.getLogger(LOGGER_NAME)
151
+ _rootlogger.handlers = []
152
+ _rootlogger.addHandler(logging.NullHandler())
153
+ _rootlogger.setLevel(DEBUG)
154
+
155
+ return _rootlogger
156
+
157
+
158
+ def log_to_stderr(level: int | None = None, pass_root_logger_level: bool = False):
159
+ """Turn on logging and add a handler which prints to stderr.
160
+
161
+ Args:
162
+ level: minimum level of the messages that will be logged
163
+ pass_root_logger_level: bool, optional. Default False
164
+ if True, all module loggers will be set to the same logging level as the root logger.
165
+
166
+ """
167
+ if not level:
168
+ level = DEFAULT_LEVEL
169
+
170
+ logger = get_rootlogger()
171
+
172
+ # avoid creation of multiple stream handlers for logging to console
173
+ for entry in logger.handlers:
174
+ if (isinstance(entry, logging.StreamHandler)) and (
175
+ isinstance(entry.formatter, MESAColorFormatter)
176
+ ):
177
+ return logger
178
+
179
+ formatter = MESAColorFormatter()
180
+ handler = logging.StreamHandler()
181
+ handler.setLevel(level)
182
+ handler.setFormatter(formatter)
183
+ logger.addHandler(handler)
184
+ logger.propagate = False
185
+
186
+ if pass_root_logger_level:
187
+ for _, mod_logger in _module_loggers.items():
188
+ mod_logger.setLevel(level)
189
+
190
+ return logger
mesa/model.py CHANGED
@@ -9,7 +9,6 @@ from __future__ import annotations
9
9
 
10
10
  import random
11
11
  import sys
12
- import warnings
13
12
  from collections.abc import Sequence
14
13
 
15
14
  # mypy
@@ -18,12 +17,15 @@ from typing import Any
18
17
  import numpy as np
19
18
 
20
19
  from mesa.agent import Agent, AgentSet
21
- from mesa.datacollection import DataCollector
20
+ from mesa.mesa_logging import create_module_logger, method_logger
22
21
 
23
22
  SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
24
23
  RNGLike = np.random.Generator | np.random.BitGenerator
25
24
 
26
25
 
26
+ _mesa_logger = create_module_logger()
27
+
28
+
27
29
  class Model:
28
30
  """Base class for models in the Mesa ABM library.
29
31
 
@@ -33,7 +35,6 @@ class Model:
33
35
 
34
36
  Attributes:
35
37
  running: A boolean indicating if the model should continue running.
36
- schedule: An object to manage the order and execution of agent steps.
37
38
  steps: the number of times `model.step()` has been called.
38
39
  random: a seeded python.random number generator.
39
40
  rng : a seeded numpy.random.Generator
@@ -45,6 +46,7 @@ class Model:
45
46
 
46
47
  """
47
48
 
49
+ @method_logger(__name__)
48
50
  def __init__(
49
51
  self,
50
52
  *args: Any,
@@ -103,23 +105,22 @@ class Model:
103
105
  self.step = self._wrapped_step
104
106
 
105
107
  # setup agent registration data structures
106
- self._setup_agent_registration()
108
+ self._agents = {} # the hard references to all agents in the model
109
+ self._agents_by_type: dict[
110
+ type[Agent], AgentSet
111
+ ] = {} # a dict with an agentset for each class of agents
112
+ self._all_agents = AgentSet(
113
+ [], random=self.random
114
+ ) # an agenset with all agents
107
115
 
108
116
  def _wrapped_step(self, *args: Any, **kwargs: Any) -> None:
109
117
  """Automatically increments time and steps after calling the user's step method."""
110
118
  # Automatically increment time and step counters
111
119
  self.steps += 1
120
+ _mesa_logger.info(f"calling model.step for timestep {self.steps} ")
112
121
  # Call the original user-defined step method
113
122
  self._user_step(*args, **kwargs)
114
123
 
115
- def next_id(self) -> int: # noqa: D102
116
- warnings.warn(
117
- "using model.next_id() is deprecated and will be removed in Mesa 3.1. Agents track their unique ID automatically",
118
- DeprecationWarning,
119
- stacklevel=2,
120
- )
121
- return 0
122
-
123
124
  @property
124
125
  def agents(self) -> AgentSet:
125
126
  """Provides an AgentSet of all agents in the model, combining agents from all types."""
@@ -143,26 +144,6 @@ class Model:
143
144
  """A dictionary where the keys are agent types and the values are the corresponding AgentSets."""
144
145
  return self._agents_by_type
145
146
 
146
- def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
147
- """Deprecated: Retrieves an AgentSet containing all agents of the specified type."""
148
- warnings.warn(
149
- f"Model.get_agents_of_type() is deprecated and will be removed in Mesa 3.1."
150
- f"Please replace get_agents_of_type({agenttype}) with the property agents_by_type[{agenttype}].",
151
- DeprecationWarning,
152
- stacklevel=2,
153
- )
154
- return self.agents_by_type[agenttype]
155
-
156
- def _setup_agent_registration(self):
157
- """Helper method to initialize the agent registration datastructures."""
158
- self._agents = {} # the hard references to all agents in the model
159
- self._agents_by_type: dict[
160
- type[Agent], AgentSet
161
- ] = {} # a dict with an agentset for each class of agents
162
- self._all_agents = AgentSet(
163
- [], random=self.random
164
- ) # an agenset with all agents
165
-
166
147
  def register_agent(self, agent):
167
148
  """Register the agent with the model.
168
149
 
@@ -174,16 +155,6 @@ class Model:
174
155
  if you are subclassing Agent and calling its super in the ``__init__`` method.
175
156
 
176
157
  """
177
- if not hasattr(self, "_agents"):
178
- self._setup_agent_registration()
179
-
180
- warnings.warn(
181
- "The Mesa Model class was not initialized. In the future, you need to explicitly initialize "
182
- "the Model by calling super().__init__() on initialization.",
183
- FutureWarning,
184
- stacklevel=2,
185
- )
186
-
187
158
  self._agents[agent] = None
188
159
 
189
160
  # because AgentSet requires model, we cannot use defaultdict
@@ -199,6 +170,9 @@ class Model:
199
170
  )
200
171
 
201
172
  self._all_agents.add(agent)
173
+ _mesa_logger.debug(
174
+ f"registered {agent.__class__.__name__} with agent_id {agent.unique_id}"
175
+ )
202
176
 
203
177
  def deregister_agent(self, agent):
204
178
  """Deregister the agent with the model.
@@ -213,6 +187,7 @@ class Model:
213
187
  del self._agents[agent]
214
188
  self._agents_by_type[type(agent)].remove(agent)
215
189
  self._all_agents.remove(agent)
190
+ _mesa_logger.debug(f"deregistered agent with agent_id {agent.unique_id}")
216
191
 
217
192
  def run_model(self) -> None:
218
193
  """Run the model until the end condition is reached.
@@ -245,38 +220,6 @@ class Model:
245
220
  self.rng = np.random.default_rng(rng)
246
221
  self._rng = self.rng.bit_generator.state
247
222
 
248
- def initialize_data_collector(
249
- self,
250
- model_reporters=None,
251
- agent_reporters=None,
252
- agenttype_reporters=None,
253
- tables=None,
254
- ) -> None:
255
- """Initialize the data collector for the model.
256
-
257
- Args:
258
- model_reporters: model reporters to collect
259
- agent_reporters: agent reporters to collect
260
- agenttype_reporters: agent type reporters to collect
261
- tables: tables to collect
262
-
263
- """
264
- warnings.warn(
265
- "initialize_data_collector() is deprecated and will be removed in Mesa 3.1. Please use the DataCollector class directly. "
266
- "by using `self.datacollector = DataCollector(...)`.",
267
- DeprecationWarning,
268
- stacklevel=2,
269
- )
270
-
271
- self.datacollector = DataCollector(
272
- model_reporters=model_reporters,
273
- agent_reporters=agent_reporters,
274
- agenttype_reporters=agenttype_reporters,
275
- tables=tables,
276
- )
277
- # Collect data for the first time during initialization.
278
- self.datacollector.collect(self)
279
-
280
223
  def remove_all_agents(self):
281
224
  """Remove all agents from the model.
282
225
 
@@ -17,10 +17,10 @@ from .user_param import Slider
17
17
 
18
18
  __all__ = [
19
19
  "JupyterViz",
20
- "SolaraViz",
21
20
  "Slider",
22
- "make_space_altair",
21
+ "SolaraViz",
23
22
  "draw_space",
24
23
  "make_plot_component",
24
+ "make_space_altair",
25
25
  "make_space_component",
26
26
  ]
@@ -178,7 +178,7 @@ def draw_property_layers(
178
178
  property_layers = space.properties
179
179
  except AttributeError:
180
180
  # new style spaces
181
- property_layers = space.property_layers
181
+ property_layers = space._mesa_property_layers
182
182
 
183
183
  for layer_name, portrayal in propertylayer_portrayal.items():
184
184
  layer = property_layers.get(layer_name, None)
@@ -186,7 +186,7 @@ def draw_property_layers(
186
186
  continue
187
187
 
188
188
  data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
189
- width, height = data.shape if space is None else (space.width, space.height)
189
+ width, height = data.shape # if space is None else (space.width, space.height)
190
190
 
191
191
  if space and data.shape != (width, height):
192
192
  warnings.warn(
@@ -33,14 +33,18 @@ import solara
33
33
 
34
34
  import mesa.visualization.components.altair_components as components_altair
35
35
  from mesa.experimental.devs.simulator import Simulator
36
+ from mesa.mesa_logging import create_module_logger, function_logger
36
37
  from mesa.visualization.user_param import Slider
37
38
  from mesa.visualization.utils import force_update, update_counter
38
39
 
39
40
  if TYPE_CHECKING:
40
41
  from mesa.model import Model
41
42
 
43
+ _mesa_logger = create_module_logger()
44
+
42
45
 
43
46
  @solara.component
47
+ @function_logger(__name__)
44
48
  def SolaraViz(
45
49
  model: Model | solara.Reactive[Model],
46
50
  components: list[reacton.core.Component]
@@ -200,18 +204,25 @@ def ModelController(
200
204
  step, dependencies=[playing.value, running.value], prefer_threaded=False
201
205
  )
202
206
 
207
+ @function_logger(__name__)
203
208
  def do_step():
204
209
  """Advance the model by one step."""
205
210
  model.value.step()
206
211
  running.value = model.value.running
207
212
  force_update()
208
213
 
214
+ @function_logger(__name__)
209
215
  def do_reset():
210
216
  """Reset the model to its initial state."""
211
217
  playing.value = False
212
218
  running.value = True
219
+ _mesa_logger.log(
220
+ 10,
221
+ f"creating new {model.value.__class__} instance with {model_parameters.value}",
222
+ )
213
223
  model.value = model.value = model.value.__class__(**model_parameters.value)
214
224
 
225
+ @function_logger(__name__)
215
226
  def do_play_pause():
216
227
  """Toggle play/pause."""
217
228
  playing.value = not playing.value
@@ -382,12 +393,19 @@ def ModelCreator(
382
393
  )
383
394
  user_params, fixed_params = split_model_params(user_params)
384
395
 
385
- # set model_parameters to the default values for all parameters
386
- model_parameters.value = {
387
- **fixed_params,
388
- **{k: v.get("value") for k, v in user_params.items()},
389
- }
396
+ # Use solara.use_effect to run the initialization code only once
397
+ solara.use_effect(
398
+ # set model_parameters to the default values for all parameters
399
+ lambda: model_parameters.set(
400
+ {
401
+ **fixed_params,
402
+ **{k: v.get("value") for k, v in user_params.items()},
403
+ }
404
+ ),
405
+ [],
406
+ )
390
407
 
408
+ @function_logger(__name__)
391
409
  def on_change(name, value):
392
410
  model_parameters.value = {**model_parameters.value, name: value}
393
411
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Mesa
3
- Version: 3.0.2
3
+ Version: 3.1.0
4
4
  Summary: Agent-based modeling (ABM) in Python
5
5
  Project-URL: homepage, https://github.com/projectmesa/mesa
6
6
  Project-URL: repository, https://github.com/projectmesa/mesa
@@ -13,14 +13,13 @@ Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Scientific/Engineering
21
20
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
21
  Classifier: Topic :: Scientific/Engineering :: Artificial Life
23
- Requires-Python: >=3.10
22
+ Requires-Python: >=3.11
24
23
  Requires-Dist: numpy
25
24
  Requires-Dist: pandas
26
25
  Requires-Dist: tqdm
@@ -81,7 +80,7 @@ Description-Content-Type: text/markdown
81
80
  | --- | --- |
82
81
  | CI/CD | [![GitHub Actions build status](https://github.com/projectmesa/mesa/workflows/build/badge.svg)](https://github.com/projectmesa/mesa/actions) [![Coverage status](https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg)](https://codecov.io/gh/projectmesa/mesa) |
83
82
  | Package | [![PyPI - Version](https://img.shields.io/pypi/v/mesa.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/Mesa/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/mesa.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold)](https://pypi.org/project/Mesa/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mesa.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/Mesa/) |
84
- | Meta | [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) |
83
+ | Meta | [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![SPEC 0 — Minimum Supported Dependencies](https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/spec-0000/) |
85
84
  | Chat | [![chat](https://img.shields.io/matrix/project-mesa:matrix.org?label=chat&logo=Matrix)](https://matrix.to/#/#project-mesa:matrix.org) |
86
85
 
87
86
  Mesa allows users to quickly create agent-based models using built-in