Mesa 3.0.3__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.
- mesa/__init__.py +4 -6
- mesa/agent.py +48 -25
- mesa/batchrunner.py +14 -1
- mesa/datacollection.py +1 -6
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
- mesa/examples/advanced/pd_grid/app.py +5 -0
- mesa/examples/advanced/sugarscape_g1mt/app.py +7 -2
- mesa/examples/basic/boid_flockers/app.py +5 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
- mesa/examples/basic/conways_game_of_life/app.py +5 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
- mesa/examples/basic/schelling/app.py +5 -0
- mesa/examples/basic/virus_on_network/app.py +5 -0
- mesa/experimental/__init__.py +17 -10
- mesa/experimental/cell_space/__init__.py +19 -7
- mesa/experimental/cell_space/cell.py +22 -37
- mesa/experimental/cell_space/cell_agent.py +12 -1
- mesa/experimental/cell_space/cell_collection.py +14 -1
- mesa/experimental/cell_space/discrete_space.py +15 -64
- mesa/experimental/cell_space/grid.py +74 -4
- mesa/experimental/cell_space/network.py +13 -1
- mesa/experimental/cell_space/property_layer.py +444 -0
- mesa/experimental/cell_space/voronoi.py +13 -1
- mesa/experimental/devs/__init__.py +20 -2
- mesa/experimental/devs/eventlist.py +19 -1
- mesa/experimental/devs/simulator.py +24 -8
- mesa/experimental/mesa_signals/__init__.py +23 -0
- mesa/experimental/mesa_signals/mesa_signal.py +485 -0
- mesa/experimental/mesa_signals/observable_collections.py +133 -0
- mesa/experimental/mesa_signals/signals_util.py +52 -0
- mesa/mesa_logging.py +190 -0
- mesa/model.py +17 -74
- mesa/visualization/__init__.py +2 -2
- mesa/visualization/mpl_space_drawing.py +2 -2
- mesa/visualization/solara_viz.py +12 -0
- {mesa-3.0.3.dist-info → mesa-3.1.0.dist-info}/METADATA +3 -4
- {mesa-3.0.3.dist-info → mesa-3.1.0.dist-info}/RECORD +43 -44
- mesa/experimental/UserParam.py +0 -67
- mesa/experimental/components/altair.py +0 -81
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
- mesa/experimental/devs/examples/wolf_sheep.py +0 -250
- mesa/experimental/solara_viz.py +0 -453
- mesa/time.py +0 -391
- {mesa-3.0.3.dist-info → mesa-3.1.0.dist-info}/WHEEL +0 -0
- {mesa-3.0.3.dist-info → mesa-3.1.0.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.3.dist-info → mesa-3.1.0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.0.3.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.
|
|
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.
|
|
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
|
|
mesa/visualization/__init__.py
CHANGED
|
@@ -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.
|
|
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(
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -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
|
|
@@ -394,6 +405,7 @@ def ModelCreator(
|
|
|
394
405
|
[],
|
|
395
406
|
)
|
|
396
407
|
|
|
408
|
+
@function_logger(__name__)
|
|
397
409
|
def on_change(name, value):
|
|
398
410
|
model_parameters.value = {**model_parameters.value, name: value}
|
|
399
411
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.0
|
|
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.
|
|
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 | [](https://github.com/projectmesa/mesa/actions) [](https://codecov.io/gh/projectmesa/mesa) |
|
|
83
82
|
| Package | [](https://pypi.org/project/Mesa/) [](https://pypi.org/project/Mesa/) [](https://pypi.org/project/Mesa/) |
|
|
84
|
-
| Meta | [](https://github.com/astral-sh/ruff) [](https://github.com/psf/black) [](https://github.com/pypa/hatch) |
|
|
83
|
+
| Meta | [](https://github.com/astral-sh/ruff) [](https://github.com/psf/black) [](https://github.com/pypa/hatch) [](https://scientific-python.org/specs/spec-0000/) |
|
|
85
84
|
| Chat | [](https://matrix.to/#/#project-mesa:matrix.org) |
|
|
86
85
|
|
|
87
86
|
Mesa allows users to quickly create agent-based models using built-in
|