Mesa 3.0.0a5__py3-none-any.whl → 3.0.0b0__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 CHANGED
@@ -23,7 +23,7 @@ __all__ = [
23
23
  ]
24
24
 
25
25
  __title__ = "mesa"
26
- __version__ = "3.0.0a5"
26
+ __version__ = "3.0.0b0"
27
27
  __license__ = "Apache 2.0"
28
28
  _this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
29
29
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
mesa/agent.py CHANGED
@@ -46,39 +46,25 @@ class Agent:
46
46
  # so, unique_id is unique relative to a model, and counting starts from 1
47
47
  _ids = defaultdict(functools.partial(itertools.count, 1))
48
48
 
49
- def __init__(self, *args, **kwargs) -> None:
49
+ def __init__(self, model: Model, *args, **kwargs) -> None:
50
50
  """Create a new agent.
51
51
 
52
52
  Args:
53
53
  model (Model): The model instance in which the agent exists.
54
- args: currently ignored, to be fixed in 3.1
55
- kwargs: currently ignored, to be fixed in 3.1
56
- """
57
- # TODO: Cleanup in future Mesa version (3.1+)
58
- match args:
59
- # Case 1: Only the model is provided. The new correct behavior.
60
- case [model]:
61
- self.model = model
62
- self.unique_id = next(self._ids[model])
63
- # Case 2: Both unique_id and model are provided, deprecated
64
- case [_, model]:
65
- warnings.warn(
66
- "unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is "
67
- "deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.",
68
- DeprecationWarning,
69
- stacklevel=2,
70
- )
71
- self.model = model
72
- self.unique_id = next(self._ids[model])
73
- # Case 3: Anything else, raise an error
74
- case _:
75
- raise ValueError(
76
- "Invalid arguments provided to initialize the Agent. Only input a model: `super()__init__(model)`."
77
- )
54
+ args: passed on to super
55
+ kwargs: passed on to super
78
56
 
79
- self.pos: Position | None = None
57
+ Notes:
58
+ to make proper use of python's super, in each class remove the arguments and
59
+ keyword arguments you need and pass on the rest to super
80
60
 
61
+ """
62
+ super().__init__(*args, **kwargs)
63
+
64
+ self.model: Model = model
81
65
  self.model.register_agent(self)
66
+ self.unique_id: int = next(self._ids[model])
67
+ self.pos: Position | None = None
82
68
 
83
69
  def remove(self) -> None:
84
70
  """Remove and delete the agent from the model."""
mesa/batchrunner.py CHANGED
@@ -38,6 +38,9 @@ def batch_run(
38
38
  Returns:
39
39
  List[Dict[str, Any]]
40
40
 
41
+ Notes:
42
+ batch_run assumes the model has a `datacollector` attribute that has a DataCollector object initialized.
43
+
41
44
  """
42
45
  runs_list = []
43
46
  run_id = 0
@@ -173,6 +176,10 @@ def _collect_data(
173
176
  step: int,
174
177
  ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
175
178
  """Collect model and agent data from a model using mesas datacollector."""
179
+ if not hasattr(model, "datacollector"):
180
+ raise AttributeError(
181
+ "The model does not have a datacollector attribute. Please add a DataCollector to your model."
182
+ )
176
183
  dc = model.datacollector
177
184
 
178
185
  model_data = {param: values[step] for param, values in dc.model_vars.items()}
@@ -6,7 +6,11 @@ expressive that the default grids available in `mesa.space`.
6
6
  """
7
7
 
8
8
  from mesa.experimental.cell_space.cell import Cell
9
- from mesa.experimental.cell_space.cell_agent import CellAgent
9
+ from mesa.experimental.cell_space.cell_agent import (
10
+ CellAgent,
11
+ FixedAgent,
12
+ Grid2DMovingAgent,
13
+ )
10
14
  from mesa.experimental.cell_space.cell_collection import CellCollection
11
15
  from mesa.experimental.cell_space.discrete_space import DiscreteSpace
12
16
  from mesa.experimental.cell_space.grid import (
@@ -22,6 +26,8 @@ __all__ = [
22
26
  "CellCollection",
23
27
  "Cell",
24
28
  "CellAgent",
29
+ "Grid2DMovingAgent",
30
+ "FixedAgent",
25
31
  "DiscreteSpace",
26
32
  "Grid",
27
33
  "HexGrid",
@@ -2,15 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Callable
5
6
  from functools import cache, cached_property
6
7
  from random import Random
7
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, Any
8
9
 
10
+ from mesa.experimental.cell_space.cell_agent import CellAgent
9
11
  from mesa.experimental.cell_space.cell_collection import CellCollection
12
+ from mesa.space import PropertyLayer
10
13
 
11
14
  if TYPE_CHECKING:
12
15
  from mesa.agent import Agent
13
- from mesa.experimental.cell_space.cell_agent import CellAgent
14
16
 
15
17
  Coordinate = tuple[int, ...]
16
18
 
@@ -34,6 +36,7 @@ class Cell:
34
36
  "capacity",
35
37
  "properties",
36
38
  "random",
39
+ "_mesa_property_layers",
37
40
  "__dict__",
38
41
  ]
39
42
 
@@ -66,9 +69,10 @@ class Cell:
66
69
  self.agents: list[
67
70
  Agent
68
71
  ] = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
69
- self.capacity: int = capacity
72
+ self.capacity: int | None = capacity
70
73
  self.properties: dict[Coordinate, object] = {}
71
74
  self.random = random
75
+ self._mesa_property_layers: dict[str, PropertyLayer] = {}
72
76
 
73
77
  def connect(self, other: Cell, key: Coordinate | None = None) -> None:
74
78
  """Connects this cell to another cell.
@@ -117,7 +121,6 @@ class Cell:
117
121
 
118
122
  """
119
123
  self.agents.remove(agent)
120
- agent.cell = None
121
124
 
122
125
  @property
123
126
  def is_empty(self) -> bool:
@@ -133,7 +136,7 @@ class Cell:
133
136
  return f"Cell({self.coordinate}, {self.agents})"
134
137
 
135
138
  @cached_property
136
- def neighborhood(self) -> CellCollection:
139
+ def neighborhood(self) -> CellCollection[Cell]:
137
140
  """Returns the direct neighborhood of the cell.
138
141
 
139
142
  This is equivalent to cell.get_neighborhood(radius=1)
@@ -145,7 +148,7 @@ class Cell:
145
148
  @cache # noqa: B019
146
149
  def get_neighborhood(
147
150
  self, radius: int = 1, include_center: bool = False
148
- ) -> CellCollection:
151
+ ) -> CellCollection[Cell]:
149
152
  """Returns a list of all neighboring cells for the given radius.
150
153
 
151
154
  For getting the direct neighborhood (i.e., radius=1) you can also use
@@ -191,3 +194,20 @@ class Cell:
191
194
  if not include_center:
192
195
  neighborhood.pop(self, None)
193
196
  return neighborhood
197
+
198
+ # PropertyLayer methods
199
+ def get_property(self, property_name: str) -> Any:
200
+ """Get the value of a property."""
201
+ return self._mesa_property_layers[property_name].data[self.coordinate]
202
+
203
+ def set_property(self, property_name: str, value: Any):
204
+ """Set the value of a property."""
205
+ self._mesa_property_layers[property_name].set_cell(self.coordinate, value)
206
+
207
+ def modify_property(
208
+ self, property_name: str, operation: Callable, value: Any = None
209
+ ):
210
+ """Modify the value of a property."""
211
+ self._mesa_property_layers[property_name].modify_cell(
212
+ self.coordinate, operation, value
213
+ )
@@ -2,41 +2,132 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Protocol
6
6
 
7
- from mesa import Agent, Model
7
+ from mesa.agent import Agent
8
8
 
9
9
  if TYPE_CHECKING:
10
- from mesa.experimental.cell_space.cell import Cell
10
+ from mesa.experimental.cell_space import Cell
11
11
 
12
12
 
13
- class CellAgent(Agent):
14
- """Cell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces.
13
+ class HasCellProtocol(Protocol):
14
+ """Protocol for discrete space cell holders."""
15
15
 
16
- Attributes:
17
- unique_id (int): A unique identifier for this agent.
18
- model (Model): The model instance to which the agent belongs
19
- pos: (Position | None): The position of the agent in the space
20
- cell: (Cell | None): the cell which the agent occupies
21
- """
16
+ cell: Cell
22
17
 
23
- def __init__(self, model: Model) -> None:
24
- """Create a new agent.
25
18
 
26
- Args:
27
- model (Model): The model instance in which the agent exists.
28
- """
29
- super().__init__(model)
30
- self.cell: Cell | None = None
19
+ class HasCell:
20
+ """Descriptor for cell movement behavior."""
31
21
 
32
- def move_to(self, cell) -> None:
33
- """Move agent to cell.
22
+ _mesa_cell: Cell | None = None
34
23
 
35
- Args:
36
- cell: cell to which agent is to move
24
+ @property
25
+ def cell(self) -> Cell | None: # noqa: D102
26
+ return self._mesa_cell
37
27
 
38
- """
28
+ @cell.setter
29
+ def cell(self, cell: Cell | None) -> None:
30
+ # remove from current cell
39
31
  if self.cell is not None:
40
32
  self.cell.remove_agent(self)
33
+
34
+ # update private attribute
35
+ self._mesa_cell = cell
36
+
37
+ # add to new cell
38
+ if cell is not None:
39
+ cell.add_agent(self)
40
+
41
+
42
+ class BasicMovement:
43
+ """Mixin for moving agents in discrete space."""
44
+
45
+ def move_to(self: HasCellProtocol, cell: Cell) -> None:
46
+ """Move to a new cell."""
41
47
  self.cell = cell
48
+
49
+ def move_relative(self: HasCellProtocol, direction: tuple[int, ...]):
50
+ """Move to a cell relative to the current cell.
51
+
52
+ Args:
53
+ direction: The direction to move in.
54
+ """
55
+ new_cell = self.cell.connections.get(direction)
56
+ if new_cell is not None:
57
+ self.cell = new_cell
58
+ else:
59
+ raise ValueError(f"No cell in direction {direction}")
60
+
61
+
62
+ class FixedCell(HasCell):
63
+ """Mixin for agents that are fixed to a cell."""
64
+
65
+ @property
66
+ def cell(self) -> Cell | None: # noqa: D102
67
+ return self._mesa_cell
68
+
69
+ @cell.setter
70
+ def cell(self, cell: Cell) -> None:
71
+ if self.cell is not None:
72
+ raise ValueError("Cannot move agent in FixedCell")
73
+ self._mesa_cell = cell
74
+
42
75
  cell.add_agent(self)
76
+
77
+
78
+ class CellAgent(Agent, HasCell, BasicMovement):
79
+ """Cell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces.
80
+
81
+ Attributes:
82
+ cell (Cell): The cell the agent is currently in.
83
+ """
84
+
85
+ def remove(self):
86
+ """Remove the agent from the model."""
87
+ super().remove()
88
+ self.cell = None # ensures that we are also removed from cell
89
+
90
+
91
+ class FixedAgent(Agent, FixedCell):
92
+ """A patch in a 2D grid."""
93
+
94
+ def remove(self):
95
+ """Remove the agent from the model."""
96
+ super().remove()
97
+
98
+ # fixme we leave self._mesa_cell on the original value
99
+ # so you cannot hijack remove() to move patches
100
+ self.cell.remove_agent(self)
101
+
102
+
103
+ class Grid2DMovingAgent(CellAgent):
104
+ """Mixin for moving agents in 2D grids."""
105
+
106
+ # fmt: off
107
+ DIRECTION_MAP = {
108
+ "n": (-1, 0), "north": (-1, 0), "up": (-1, 0),
109
+ "s": (1, 0), "south": (1, 0), "down": (1, 0),
110
+ "e": (0, 1), "east": (0, 1), "right": (0, 1),
111
+ "w": (0, -1), "west": (0, -1), "left": (0, -1),
112
+ "ne": (-1, 1), "northeast": (-1, 1), "upright": (-1, 1),
113
+ "nw": (-1, -1), "northwest": (-1, -1), "upleft": (-1, -1),
114
+ "se": (1, 1), "southeast": (1, 1), "downright": (1, 1),
115
+ "sw": (1, -1), "southwest": (1, -1), "downleft": (1, -1)
116
+ }
117
+ # fmt: on
118
+
119
+ def move(self, direction: str, distance: int = 1):
120
+ """Move the agent in a cardinal direction.
121
+
122
+ Args:
123
+ direction: The cardinal direction to move in.
124
+ distance: The distance to move.
125
+ """
126
+ direction = direction.lower() # Convert direction to lowercase
127
+
128
+ if direction not in self.DIRECTION_MAP:
129
+ raise ValueError(f"Invalid direction: {direction}")
130
+
131
+ move_vector = self.DIRECTION_MAP[direction]
132
+ for _ in range(distance):
133
+ self.move_relative(move_vector)
@@ -2,12 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Callable
5
6
  from functools import cached_property
6
7
  from random import Random
7
- from typing import Generic, TypeVar
8
+ from typing import Any, Generic, TypeVar
8
9
 
9
10
  from mesa.experimental.cell_space.cell import Cell
10
11
  from mesa.experimental.cell_space.cell_collection import CellCollection
12
+ from mesa.space import PropertyLayer
11
13
 
12
14
  T = TypeVar("T", bound=Cell)
13
15
 
@@ -21,7 +23,7 @@ class DiscreteSpace(Generic[T]):
21
23
  random (Random): The random number generator
22
24
  cell_klass (Type) : the type of cell class
23
25
  empties (CellCollection) : collecction of all cells that are empty
24
-
26
+ property_layers (dict[str, PropertyLayer]): the property layers of the discrete space
25
27
  """
26
28
 
27
29
  def __init__(
@@ -47,6 +49,7 @@ class DiscreteSpace(Generic[T]):
47
49
 
48
50
  self._empties: dict[tuple[int, ...], None] = {}
49
51
  self._empties_initialized = False
52
+ self.property_layers: dict[str, PropertyLayer] = {}
50
53
 
51
54
  @property
52
55
  def cutoff_empties(self): # noqa
@@ -73,3 +76,61 @@ class DiscreteSpace(Generic[T]):
73
76
  def select_random_empty_cell(self) -> T:
74
77
  """Select random empty cell."""
75
78
  return self.random.choice(list(self.empties))
79
+
80
+ # PropertyLayer methods
81
+ def add_property_layer(
82
+ self, property_layer: PropertyLayer, add_to_cells: bool = True
83
+ ):
84
+ """Add a property layer to the grid.
85
+
86
+ Args:
87
+ property_layer: the property layer to add
88
+ add_to_cells: whether to add the property layer to all cells (default: True)
89
+ """
90
+ if property_layer.name in self.property_layers:
91
+ raise ValueError(f"Property layer {property_layer.name} already exists.")
92
+ self.property_layers[property_layer.name] = property_layer
93
+ if add_to_cells:
94
+ for cell in self._cells.values():
95
+ cell._mesa_property_layers[property_layer.name] = property_layer
96
+
97
+ def remove_property_layer(self, property_name: str, remove_from_cells: bool = True):
98
+ """Remove a property layer from the grid.
99
+
100
+ Args:
101
+ property_name: the name of the property layer to remove
102
+ remove_from_cells: whether to remove the property layer from all cells (default: True)
103
+ """
104
+ del self.property_layers[property_name]
105
+ if remove_from_cells:
106
+ for cell in self._cells.values():
107
+ del cell._mesa_property_layers[property_name]
108
+
109
+ def set_property(
110
+ self, property_name: str, value, condition: Callable[[T], bool] | None = None
111
+ ):
112
+ """Set the value of a property for all cells in the grid.
113
+
114
+ Args:
115
+ property_name: the name of the property to set
116
+ value: the value to set
117
+ condition: a function that takes a cell and returns a boolean
118
+ """
119
+ self.property_layers[property_name].set_cells(value, condition)
120
+
121
+ def modify_properties(
122
+ self,
123
+ property_name: str,
124
+ operation: Callable,
125
+ value: Any = None,
126
+ condition: Callable[[T], bool] | None = None,
127
+ ):
128
+ """Modify the values of a specific property for all cells in the grid.
129
+
130
+ Args:
131
+ property_name: the name of the property to modify
132
+ operation: the operation to perform
133
+ value: the value to use in the operation
134
+ condition: a function that takes a cell and returns a boolean (used to filter cells)
135
+ """
136
+ self.property_layers[property_name].modify_cells(operation, value, condition)
@@ -1,6 +1,7 @@
1
1
  """Example of using ABM simulator for Wolf-Sheep Predation Model."""
2
2
 
3
3
  import mesa
4
+ from mesa.experimental.cell_space import FixedAgent
4
5
  from mesa.experimental.devs.simulator import ABMSimulator
5
6
 
6
7
 
@@ -90,7 +91,7 @@ class Wolf(Animal):
90
91
  sheep_to_eat.die()
91
92
 
92
93
 
93
- class GrassPatch(mesa.Agent):
94
+ class GrassPatch(FixedAgent):
94
95
  """A patch of grass that grows at a fixed rate and it is eaten by sheep."""
95
96
 
96
97
  @property
mesa/model.py CHANGED
@@ -48,8 +48,8 @@ class Model:
48
48
  seed: the seed for the random number generator
49
49
  kwargs: keyword arguments to pass onto super
50
50
  """
51
+ super().__init__(*args, **kwargs)
51
52
  self.running = True
52
- self.schedule = None
53
53
  self.steps: int = 0
54
54
 
55
55
  self._setup_agent_registration()
@@ -210,14 +210,13 @@ class Model:
210
210
  tables: tables to collect
211
211
 
212
212
  """
213
- if not hasattr(self, "schedule") or self.schedule is None:
214
- raise RuntimeError(
215
- "You must initialize the scheduler (self.schedule) before initializing the data collector."
216
- )
217
- if self.schedule.get_agent_count() == 0:
218
- raise RuntimeError(
219
- "You must add agents to the scheduler before initializing the data collector."
220
- )
213
+ warnings.warn(
214
+ "initialize_data_collector() is deprecated. Please use the DataCollector class directly. "
215
+ "by using `self.datacollector = DataCollector(...)`.",
216
+ DeprecationWarning,
217
+ stacklevel=2,
218
+ )
219
+
221
220
  self.datacollector = DataCollector(
222
221
  model_reporters=model_reporters,
223
222
  agent_reporters=agent_reporters,
mesa/time.py CHANGED
@@ -3,7 +3,7 @@
3
3
  .. warning::
4
4
  The time module and all its Schedulers are deprecated and will be removed in a future version.
5
5
  They can be replaced with AgentSet functionality. See the migration guide for details:
6
- https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers
6
+ https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers
7
7
 
8
8
  Objects for handling the time component of a model. In particular, this module
9
9
  contains Schedulers, which handle agent activation. A Scheduler is an object
@@ -65,7 +65,7 @@ class BaseScheduler:
65
65
  warnings.warn(
66
66
  "The time module and all its Schedulers are deprecated and will be removed in a future version. "
67
67
  "They can be replaced with AgentSet functionality. See the migration guide for details. "
68
- "https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers",
68
+ "https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers",
69
69
  DeprecationWarning,
70
70
  stacklevel=2,
71
71
  )
@@ -1,116 +1,190 @@
1
1
  """Matplotlib based solara components for visualization MESA spaces and plots."""
2
2
 
3
- from collections import defaultdict
3
+ import warnings
4
4
 
5
+ import matplotlib.pyplot as plt
5
6
  import networkx as nx
7
+ import numpy as np
6
8
  import solara
9
+ from matplotlib.cm import ScalarMappable
10
+ from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
7
11
  from matplotlib.figure import Figure
8
- from matplotlib.ticker import MaxNLocator
9
12
 
10
13
  import mesa
11
14
  from mesa.experimental.cell_space import VoronoiGrid
15
+ from mesa.space import PropertyLayer
12
16
  from mesa.visualization.utils import update_counter
13
17
 
14
18
 
15
- def make_space_matplotlib(agent_portrayal=None): # noqa: D103
19
+ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None):
20
+ """Create a Matplotlib-based space visualization component.
21
+
22
+ Args:
23
+ agent_portrayal (function): Function to portray agents
24
+ propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications
25
+
26
+ Returns:
27
+ function: A function that creates a SpaceMatplotlib component
28
+ """
16
29
  if agent_portrayal is None:
17
30
 
18
31
  def agent_portrayal(a):
19
32
  return {"id": a.unique_id}
20
33
 
21
34
  def MakeSpaceMatplotlib(model):
22
- return SpaceMatplotlib(model, agent_portrayal)
35
+ return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal)
23
36
 
24
37
  return MakeSpaceMatplotlib
25
38
 
26
39
 
27
40
  @solara.component
28
- def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None): # noqa: D103
41
+ def SpaceMatplotlib(
42
+ model,
43
+ agent_portrayal,
44
+ propertylayer_portrayal,
45
+ dependencies: list[any] | None = None,
46
+ ):
47
+ """Create a Matplotlib-based space visualization component."""
29
48
  update_counter.get()
30
49
  space_fig = Figure()
31
50
  space_ax = space_fig.subplots()
32
51
  space = getattr(model, "grid", None)
33
52
  if space is None:
34
- # Sometimes the space is defined as model.space instead of model.grid
35
- space = model.space
36
- if isinstance(space, mesa.space.NetworkGrid):
37
- _draw_network_grid(space, space_ax, agent_portrayal)
53
+ space = getattr(model, "space", None)
54
+
55
+ if isinstance(space, mesa.space._Grid):
56
+ _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model)
38
57
  elif isinstance(space, mesa.space.ContinuousSpace):
39
- _draw_continuous_space(space, space_ax, agent_portrayal)
58
+ _draw_continuous_space(space, space_ax, agent_portrayal, model)
59
+ elif isinstance(space, mesa.space.NetworkGrid):
60
+ _draw_network_grid(space, space_ax, agent_portrayal)
40
61
  elif isinstance(space, VoronoiGrid):
41
62
  _draw_voronoi(space, space_ax, agent_portrayal)
42
- else:
43
- _draw_grid(space, space_ax, agent_portrayal)
44
- solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
45
-
63
+ elif space is None and propertylayer_portrayal:
64
+ draw_property_layers(space_ax, space, propertylayer_portrayal, model)
46
65
 
47
- # matplotlib scatter does not allow for multiple shapes in one call
48
- def _split_and_scatter(portray_data, space_ax):
49
- grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
50
-
51
- # Extract data from the dictionary
52
- x = portray_data["x"]
53
- y = portray_data["y"]
54
- s = portray_data["s"]
55
- c = portray_data["c"]
56
- m = portray_data["m"]
57
-
58
- if not (len(x) == len(y) == len(s) == len(c) == len(m)):
59
- raise ValueError(
60
- "Length mismatch in portrayal data lists: "
61
- f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
62
- f"color: {len(c)}, marker: {len(m)}"
63
- )
64
-
65
- # Group the data by marker
66
- for i in range(len(x)):
67
- marker = m[i]
68
- grouped_data[marker]["x"].append(x[i])
69
- grouped_data[marker]["y"].append(y[i])
70
- grouped_data[marker]["s"].append(s[i])
71
- grouped_data[marker]["c"].append(c[i])
66
+ solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
72
67
 
73
- # Plot each group with the same marker
74
- for marker, data in grouped_data.items():
75
- space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
76
68
 
69
+ def draw_property_layers(ax, space, propertylayer_portrayal, model):
70
+ """Draw PropertyLayers on the given axes.
71
+
72
+ Args:
73
+ ax (matplotlib.axes.Axes): The axes to draw on.
74
+ space (mesa.space._Grid): The space containing the PropertyLayers.
75
+ propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications.
76
+ model (mesa.Model): The model instance.
77
+ """
78
+ for layer_name, portrayal in propertylayer_portrayal.items():
79
+ layer = getattr(model, layer_name, None)
80
+ if not isinstance(layer, PropertyLayer):
81
+ continue
82
+
83
+ data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
84
+ width, height = data.shape if space is None else (space.width, space.height)
85
+
86
+ if space and data.shape != (width, height):
87
+ warnings.warn(
88
+ f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
89
+ UserWarning,
90
+ stacklevel=2,
91
+ )
92
+
93
+ # Get portrayal properties, or use defaults
94
+ alpha = portrayal.get("alpha", 1)
95
+ vmin = portrayal.get("vmin", np.min(data))
96
+ vmax = portrayal.get("vmax", np.max(data))
97
+ colorbar = portrayal.get("colorbar", True)
98
+
99
+ # Draw the layer
100
+ if "color" in portrayal:
101
+ rgba_color = to_rgba(portrayal["color"])
102
+ normalized_data = (data - vmin) / (vmax - vmin)
103
+ rgba_data = np.full((*data.shape, 4), rgba_color)
104
+ rgba_data[..., 3] *= normalized_data * alpha
105
+ rgba_data = np.clip(rgba_data, 0, 1)
106
+ cmap = LinearSegmentedColormap.from_list(
107
+ layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
108
+ )
109
+ im = ax.imshow(
110
+ rgba_data.transpose(1, 0, 2),
111
+ extent=(0, width, 0, height),
112
+ origin="lower",
113
+ )
114
+ if colorbar:
115
+ norm = Normalize(vmin=vmin, vmax=vmax)
116
+ sm = ScalarMappable(norm=norm, cmap=cmap)
117
+ sm.set_array([])
118
+ ax.figure.colorbar(sm, ax=ax, orientation="vertical")
119
+
120
+ elif "colormap" in portrayal:
121
+ cmap = portrayal.get("colormap", "viridis")
122
+ if isinstance(cmap, list):
123
+ cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
124
+ im = ax.imshow(
125
+ data.T,
126
+ cmap=cmap,
127
+ alpha=alpha,
128
+ vmin=vmin,
129
+ vmax=vmax,
130
+ extent=(0, width, 0, height),
131
+ origin="lower",
132
+ )
133
+ if colorbar:
134
+ plt.colorbar(im, ax=ax, label=layer_name)
135
+ else:
136
+ raise ValueError(
137
+ f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
138
+ )
139
+
140
+
141
+ def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model):
142
+ if propertylayer_portrayal:
143
+ draw_property_layers(space_ax, space, propertylayer_portrayal, model)
144
+
145
+ agent_data = _get_agent_data(space, agent_portrayal)
146
+
147
+ space_ax.set_xlim(0, space.width)
148
+ space_ax.set_ylim(0, space.height)
149
+ _split_and_scatter(agent_data, space_ax)
150
+
151
+ # Draw grid lines
152
+ for x in range(space.width + 1):
153
+ space_ax.axvline(x, color="gray", linestyle=":")
154
+ for y in range(space.height + 1):
155
+ space_ax.axhline(y, color="gray", linestyle=":")
156
+
157
+
158
+ def _get_agent_data(space, agent_portrayal):
159
+ """Helper function to get agent data for visualization."""
160
+ x, y, s, c, m = [], [], [], [], []
161
+ for agents, pos in space.coord_iter():
162
+ if not agents:
163
+ continue
164
+ if not isinstance(agents, list):
165
+ agents = [agents] # noqa PLW2901
166
+ for agent in agents:
167
+ data = agent_portrayal(agent)
168
+ x.append(pos[0] + 0.5) # Center the agent in the cell
169
+ y.append(pos[1] + 0.5) # Center the agent in the cell
170
+ default_size = (180 / max(space.width, space.height)) ** 2
171
+ s.append(data.get("size", default_size))
172
+ c.append(data.get("color", "tab:blue"))
173
+ m.append(data.get("shape", "o"))
174
+ return {"x": x, "y": y, "s": s, "c": c, "m": m}
77
175
 
78
- def _draw_grid(space, space_ax, agent_portrayal):
79
- def portray(g):
80
- x = []
81
- y = []
82
- s = [] # size
83
- c = [] # color
84
- m = [] # shape
85
- for i in range(g.width):
86
- for j in range(g.height):
87
- content = g._grid[i][j]
88
- if not content:
89
- continue
90
- if not hasattr(content, "__iter__"):
91
- # Is a single grid
92
- content = [content]
93
- for agent in content:
94
- data = agent_portrayal(agent)
95
- x.append(i)
96
- y.append(j)
97
-
98
- # This is the default value for the marker size, which auto-scales
99
- # according to the grid area.
100
- default_size = (180 / max(g.width, g.height)) ** 2
101
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
102
- size = data.get("size", default_size)
103
- s.append(size)
104
- color = data.get("color", "tab:blue")
105
- c.append(color)
106
- mark = data.get("shape", "o")
107
- m.append(mark)
108
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
109
- return out
110
176
 
111
- space_ax.set_xlim(-1, space.width)
112
- space_ax.set_ylim(-1, space.height)
113
- _split_and_scatter(portray(space), space_ax)
177
+ def _split_and_scatter(portray_data, space_ax):
178
+ """Helper function to split and scatter agent data."""
179
+ for marker in set(portray_data["m"]):
180
+ mask = [m == marker for m in portray_data["m"]]
181
+ space_ax.scatter(
182
+ [x for x, show in zip(portray_data["x"], mask) if show],
183
+ [y for y, show in zip(portray_data["y"], mask) if show],
184
+ s=[s for s, show in zip(portray_data["s"], mask) if show],
185
+ c=[c for c, show in zip(portray_data["c"], mask) if show],
186
+ marker=marker,
187
+ )
114
188
 
115
189
 
116
190
  def _draw_network_grid(space, space_ax, agent_portrayal):
@@ -124,7 +198,7 @@ def _draw_network_grid(space, space_ax, agent_portrayal):
124
198
  )
125
199
 
126
200
 
127
- def _draw_continuous_space(space, space_ax, agent_portrayal):
201
+ def _draw_continuous_space(space, space_ax, agent_portrayal, model):
128
202
  def portray(space):
129
203
  x = []
130
204
  y = []
@@ -139,15 +213,13 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
139
213
 
140
214
  # This is matplotlib's default marker size
141
215
  default_size = 20
142
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
143
216
  size = data.get("size", default_size)
144
217
  s.append(size)
145
218
  color = data.get("color", "tab:blue")
146
219
  c.append(color)
147
220
  mark = data.get("shape", "o")
148
221
  m.append(mark)
149
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
150
- return out
222
+ return {"x": x, "y": y, "s": s, "c": c, "m": m}
151
223
 
152
224
  # Determine border style based on space.torus
153
225
  border_style = "solid" if not space.torus else (0, (5, 10))
@@ -186,8 +258,6 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
186
258
  if "color" in data:
187
259
  c.append(data["color"])
188
260
  out = {"x": x, "y": y}
189
- # This is the default value for the marker size, which auto-scales
190
- # according to the grid area.
191
261
  out["s"] = s
192
262
  if len(c) > 0:
193
263
  out["c"] = c
@@ -216,10 +286,19 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
216
286
  alpha=min(1, cell.properties[space.cell_coloring_property]),
217
287
  c="red",
218
288
  ) # Plot filled polygon
219
- space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
289
+ space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
290
+
291
+
292
+ def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
293
+ """Create a plotting function for a specified measure.
220
294
 
295
+ Args:
296
+ measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
297
+
298
+ Returns:
299
+ function: A function that creates a PlotMatplotlib component.
300
+ """
221
301
 
222
- def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): # noqa: D103
223
302
  def MakePlotMeasure(model):
224
303
  return PlotMatplotlib(model, measure)
225
304
 
@@ -227,7 +306,17 @@ def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
227
306
 
228
307
 
229
308
  @solara.component
230
- def PlotMatplotlib(model, measure, dependencies: list[any] | None = None): # noqa: D103
309
+ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
310
+ """Create a Matplotlib-based plot for a measure or measures.
311
+
312
+ Args:
313
+ model (mesa.Model): The model instance.
314
+ measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
315
+ dependencies (list[any] | None): Optional dependencies for the plot.
316
+
317
+ Returns:
318
+ solara.FigureMatplotlib: A component for rendering the plot.
319
+ """
231
320
  update_counter.get()
232
321
  fig = Figure()
233
322
  ax = fig.subplots()
@@ -244,5 +333,5 @@ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None): # no
244
333
  ax.plot(df.loc[:, m], label=m)
245
334
  fig.legend()
246
335
  # Set integer x axis
247
- ax.xaxis.set_major_locator(MaxNLocator(integer=True))
336
+ ax.xaxis.set_major_locator(plt.MaxNLocator(integer=True))
248
337
  solara.FigureMatplotlib(fig, dependencies=dependencies)
@@ -225,6 +225,7 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
225
225
  play_interval (int, optional): Interval for playing the model steps in milliseconds.
226
226
  """
227
227
  playing = solara.use_reactive(False)
228
+ running = solara.use_reactive(True)
228
229
  original_model = solara.use_reactive(None)
229
230
 
230
231
  def save_initial_model():
@@ -236,19 +237,23 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
236
237
  solara.use_effect(save_initial_model, [model.value])
237
238
 
238
239
  async def step():
239
- while playing.value:
240
+ while playing.value and running.value:
240
241
  await asyncio.sleep(play_interval / 1000)
241
242
  do_step()
242
243
 
243
- solara.lab.use_task(step, dependencies=[playing.value], prefer_threaded=False)
244
+ solara.lab.use_task(
245
+ step, dependencies=[playing.value, running.value], prefer_threaded=False
246
+ )
244
247
 
245
248
  def do_step():
246
249
  """Advance the model by one step."""
247
250
  model.value.step()
251
+ running.value = model.value.running
248
252
 
249
253
  def do_reset():
250
254
  """Reset the model to its initial state."""
251
255
  playing.value = False
256
+ running.value = True
252
257
  model.value = copy.deepcopy(original_model.value)
253
258
 
254
259
  def do_play_pause():
@@ -261,9 +266,13 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
261
266
  label="▶" if not playing.value else "❚❚",
262
267
  color="primary",
263
268
  on_click=do_play_pause,
269
+ disabled=not running.value,
264
270
  )
265
271
  solara.Button(
266
- label="Step", color="primary", on_click=do_step, disabled=playing.value
272
+ label="Step",
273
+ color="primary",
274
+ on_click=do_step,
275
+ disabled=playing.value or not running.value,
267
276
  )
268
277
 
269
278
 
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Mesa
3
- Version: 3.0.0a5
3
+ Version: 3.0.0b0
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
7
7
  Author-email: Project Mesa Team <projectmesa@googlegroups.com>
8
8
  License: Apache 2.0
9
9
  License-File: LICENSE
10
+ License-File: NOTICE
10
11
  Keywords: ABM,agent,based,model,modeling,multi-agent,simulation
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: Science/Research
@@ -22,30 +23,60 @@ Classifier: Topic :: Scientific/Engineering
22
23
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
24
  Classifier: Topic :: Scientific/Engineering :: Artificial Life
24
25
  Requires-Python: >=3.10
25
- Requires-Dist: click
26
- Requires-Dist: cookiecutter
27
- Requires-Dist: matplotlib
28
- Requires-Dist: networkx
29
26
  Requires-Dist: numpy
30
27
  Requires-Dist: pandas
31
- Requires-Dist: solara
32
28
  Requires-Dist: tqdm
29
+ Provides-Extra: all
30
+ Requires-Dist: cookiecutter; extra == 'all'
31
+ Requires-Dist: ipython; extra == 'all'
32
+ Requires-Dist: matplotlib; extra == 'all'
33
+ Requires-Dist: myst-nb; extra == 'all'
34
+ Requires-Dist: myst-parser; extra == 'all'
35
+ Requires-Dist: networkx; extra == 'all'
36
+ Requires-Dist: pydata-sphinx-theme; extra == 'all'
37
+ Requires-Dist: pytest; extra == 'all'
38
+ Requires-Dist: pytest-cov; extra == 'all'
39
+ Requires-Dist: pytest-mock; extra == 'all'
40
+ Requires-Dist: ruff; extra == 'all'
41
+ Requires-Dist: scipy; extra == 'all'
42
+ Requires-Dist: seaborn; extra == 'all'
43
+ Requires-Dist: solara; extra == 'all'
44
+ Requires-Dist: sphinx; extra == 'all'
33
45
  Provides-Extra: dev
46
+ Requires-Dist: cookiecutter; extra == 'dev'
47
+ Requires-Dist: matplotlib; extra == 'dev'
48
+ Requires-Dist: networkx; extra == 'dev'
49
+ Requires-Dist: pytest; extra == 'dev'
34
50
  Requires-Dist: pytest-cov; extra == 'dev'
35
51
  Requires-Dist: pytest-mock; extra == 'dev'
36
- Requires-Dist: pytest>=4.6; extra == 'dev'
37
- Requires-Dist: ruff~=0.1.1; extra == 'dev'
52
+ Requires-Dist: ruff; extra == 'dev'
53
+ Requires-Dist: solara; extra == 'dev'
38
54
  Requires-Dist: sphinx; extra == 'dev'
39
55
  Provides-Extra: docs
40
56
  Requires-Dist: ipython; extra == 'docs'
57
+ Requires-Dist: matplotlib; extra == 'docs'
41
58
  Requires-Dist: myst-nb; extra == 'docs'
42
59
  Requires-Dist: myst-parser; extra == 'docs'
60
+ Requires-Dist: networkx; extra == 'docs'
43
61
  Requires-Dist: pydata-sphinx-theme; extra == 'docs'
44
62
  Requires-Dist: seaborn; extra == 'docs'
63
+ Requires-Dist: solara; extra == 'docs'
45
64
  Requires-Dist: sphinx; extra == 'docs'
46
65
  Provides-Extra: examples
47
- Requires-Dist: pytest>=4.6; extra == 'examples'
66
+ Requires-Dist: matplotlib; extra == 'examples'
67
+ Requires-Dist: networkx; extra == 'examples'
68
+ Requires-Dist: pytest; extra == 'examples'
48
69
  Requires-Dist: scipy; extra == 'examples'
70
+ Requires-Dist: solara; extra == 'examples'
71
+ Provides-Extra: network
72
+ Requires-Dist: networkx; extra == 'network'
73
+ Provides-Extra: rec
74
+ Requires-Dist: matplotlib; extra == 'rec'
75
+ Requires-Dist: networkx; extra == 'rec'
76
+ Requires-Dist: solara; extra == 'rec'
77
+ Provides-Extra: viz
78
+ Requires-Dist: matplotlib; extra == 'viz'
79
+ Requires-Dist: solara; extra == 'viz'
49
80
  Description-Content-Type: text/markdown
50
81
 
51
82
  # Mesa: Agent-based modeling in Python
@@ -78,7 +109,7 @@ can be displayed in browser windows or Jupyter.*
78
109
 
79
110
  ## Using Mesa
80
111
 
81
- To install our latest stable release (2.3.x), run:
112
+ To install our latest stable release (2.4.x), run:
82
113
 
83
114
  ``` bash
84
115
  pip install -U mesa
@@ -89,6 +120,17 @@ To install our latest pre-release (3.0.0 alpha), run:
89
120
  ``` bash
90
121
  pip install -U --pre mesa
91
122
  ```
123
+ With Mesa 3.0, we don't install all our dependencies anymore by default.
124
+ ```bash
125
+ # You can customize the additional dependencies you need, if you want. Available are:
126
+ pip install -U --pre mesa[network,viz]
127
+
128
+ # This is equivalent to our recommended dependencies:
129
+ pip install -U --pre mesa[rec]
130
+
131
+ # To install all, including developer, dependencies:
132
+ pip install -U --pre mesa[all]
133
+ ```
92
134
 
93
135
  You can also use `pip` to install the latest GitHub version:
94
136
 
@@ -107,13 +149,13 @@ For resources or help on using Mesa, check out the following:
107
149
 
108
150
  - [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
109
151
  Wealth Model, for beginners or those new to Mesa.)
110
- - [Visualization Tutorial](https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
152
+ - [Visualization Tutorial](https://mesa.readthedocs.io/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
111
153
  - [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
112
154
  SugarScape with Traders, with instructional videos)
113
- - [Mesa Examples](https://github.com/projectmesa/mesa-examples/tree/main/examples) (A repository of seminal ABMs using Mesa and
155
+ - [Mesa Examples](https://github.com/projectmesa/mesa-examples) (A repository of seminal ABMs using Mesa and
114
156
  examples of employing specific Mesa Features)
115
157
  - [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
116
- - [Development version docs](https://mesa.readthedocs.io/en/latest/) (the latest version docs if you're using a pre-release Mesa version)
158
+ - [Development version docs](https://mesa.readthedocs.io/latest/) (the latest version docs if you're using a pre-release Mesa version)
117
159
  - [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
118
160
  - [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
119
161
 
@@ -1,11 +1,11 @@
1
- mesa/__init__.py,sha256=rKO9SY4p-VL83PFsk5hAbDCbhhUO8azgxc9buDoFCX4,618
2
- mesa/agent.py,sha256=-KwohXkdrj6CZMn7paf3Jv_7fCfDPhsJasRNm-jgTTY,25043
3
- mesa/batchrunner.py,sha256=YUYyCy8XBxbUj83BczXmxA6tsT6ttimIeqiD3-wkYL4,6103
1
+ mesa/__init__.py,sha256=GM836SFDxNMX8ePHbGQt5Yi4G3jiYLyOb3f-AUDLmaQ,618
2
+ mesa/agent.py,sha256=XeBMwbq7Ruhbt6xj9PPEDuDMtO185Bff9dOFagUxiq8,24254
3
+ mesa/batchrunner.py,sha256=0AqTcvjWNPp1aqn7zuUKSovx6Rnkk4M-KouCZ4Guqy0,6419
4
4
  mesa/datacollection.py,sha256=xyb07aBpd-HSDh5bk-XcVqGiDu5bfaLlxj5eDlGIwqY,16138
5
5
  mesa/main.py,sha256=_KgeVGbi0znzezjjoM09vhGdyaqcuDEwb9M7vH2c_O4,1668
6
- mesa/model.py,sha256=vUS64fuZI26b3TYX0BTK90S8lxPdmFI1tgxatNEYUI8,7981
6
+ mesa/model.py,sha256=zMs5_ZlLity7hYcrwhz1BY1ysK3y3aQa9hf1KIoxryM,7845
7
7
  mesa/space.py,sha256=1sVl78o5lYP6aEg32QIb9-tcv3V3UeFdC7A_h_8CgO8,62838
8
- mesa/time.py,sha256=kGXHDjnRn-Ixwgxt3fijEvLRybWQe0g9pE5ZpW4Kaaw,14983
8
+ mesa/time.py,sha256=FaVqRwWWdFH6iagfXrdmCN7YSvRpDmikyzfz6GJ8yU4,14977
9
9
  mesa/cookiecutter-mesa/cookiecutter.json,sha256=tBSWli39fOWUXGfiDCTKd92M7uKaBIswXbkOdbUufYY,337
10
10
  mesa/cookiecutter-mesa/hooks/post_gen_project.py,sha256=UKz12l6mKc7fILK0MvV5djsTKwkmD4DlH8LYjFO8ehI,316
11
11
  mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md,sha256=Yji4lGY-NtQSnW-oBj0_Jhs-XhCfZA8R1mBBM_IllGs,80
@@ -16,11 +16,11 @@ mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytem
16
16
  mesa/experimental/UserParam.py,sha256=f32nmFjroe76HpxU75ZCEOqFW2nAsDfmqIf8kQ1zt-E,2086
17
17
  mesa/experimental/__init__.py,sha256=1hxkjcZvdJhhQx4iLlTTUAPs_SqRyN-us4qR8U6JKkw,209
18
18
  mesa/experimental/solara_viz.py,sha256=uWrNQAX3oEWSftmyjNorN839dBCUp86hnhpL704dyGQ,15212
19
- mesa/experimental/cell_space/__init__.py,sha256=Nz1EkgzhvrTcfOJI1M_WkHsIgPkU8QIOCNJl7jLlYpw,908
20
- mesa/experimental/cell_space/cell.py,sha256=L1XMxq1mIaouviiPSgurj4uda0dAx7zMWnam6_aK79s,5907
21
- mesa/experimental/cell_space/cell_agent.py,sha256=06R_7djVlD2S-kS86ugxiaeualWaTrSUmF82t3UBi44,1169
19
+ mesa/experimental/cell_space/__init__.py,sha256=-NtSCT7mA-aszLSAdqbICGqeFe2vBdb-GrcW562legY,999
20
+ mesa/experimental/cell_space/cell.py,sha256=lR1lz9hMlfKsDoRzSPCqb0XTk2VIEFqxIiN_zwUaUUo,6738
21
+ mesa/experimental/cell_space/cell_agent.py,sha256=jvYOV9OIaBaqAsAG0YLV64X_f3BJe_wP7qfos_RXi0Y,3759
22
22
  mesa/experimental/cell_space/cell_collection.py,sha256=ZcyuPEevCZEXW7jFnX6StjBMw4UBDQvUspZRcFi2dFg,3426
23
- mesa/experimental/cell_space/discrete_space.py,sha256=q5MRPzMYzZb3hQW90L0PmVFJepXhjbZK_9hT4y6OLdY,2293
23
+ mesa/experimental/cell_space/discrete_space.py,sha256=bl8jmYnOLFaF5_0Wh7BFQetXr53cS-SNLzHTjtkOnVY,4845
24
24
  mesa/experimental/cell_space/grid.py,sha256=WT9AtQCgPzV8VonX0oSPRXgZNXoOR_mVgEAEOp5I4YA,7319
25
25
  mesa/experimental/cell_space/network.py,sha256=hzhxGipRyM1PWOQFlPz--tc3pA_RRBT8Xq4pXHx5sD8,1252
26
26
  mesa/experimental/cell_space/voronoi.py,sha256=lSY8zQhELvOy0RfDyZIek09UMwY9_20UY9SPqFWsNoM,10014
@@ -30,15 +30,16 @@ mesa/experimental/devs/__init__.py,sha256=EByaC66ikUIu9G9p1geLm6ESEMWZOPTO9r9627
30
30
  mesa/experimental/devs/eventlist.py,sha256=Trvc5S-NG5B792uuk_cY8Q_5Rw99zioUYDQXcXWVuCo,5972
31
31
  mesa/experimental/devs/simulator.py,sha256=wvqkLIDgbJNaem9nwMacyEYRp0W3ai5Oxptw3-QmbSw,10595
32
32
  mesa/experimental/devs/examples/epstein_civil_violence.py,sha256=E8YSV3O5ihKsntGtnltHM-4IyS8eg2DSRUqmIiw_1iU,10916
33
- mesa/experimental/devs/examples/wolf_sheep.py,sha256=aj5kiEWy-ezQXOomc5mgEdkup1yRYQknQm65ajPWBxs,8051
33
+ mesa/experimental/devs/examples/wolf_sheep.py,sha256=1eb1CfYNQoprqSJat-LPYPvwWH1ENQdj39viEqwSk0s,8103
34
34
  mesa/visualization/UserParam.py,sha256=Dl2WOwLYLf0pfLpabCZtIdFRyKZrK6Qtc3utZx5GPYg,2139
35
35
  mesa/visualization/__init__.py,sha256=sa8lqeLcDtte19SMzFiKP6K4CrVLxAPwrhDu_AsDWTs,395
36
- mesa/visualization/solara_viz.py,sha256=vVyAqp3aov8EKmYAvDELEdb5Fonr5JxLws6YZZ02GIk,16459
36
+ mesa/visualization/solara_viz.py,sha256=6Gq4r4-XFt9AG6G-v2Gvz_W-ykTC4gP0OQnqtYIhAxw,16717
37
37
  mesa/visualization/utils.py,sha256=lJHgRKF5BHLf72Tw3YpwyiWuRoIimaTKQ7xBCw_Rx3A,146
38
38
  mesa/visualization/components/altair.py,sha256=E-iblqpWhx72qrjkNz4Ie9c66Hh1OFpLVjuDIg9m2sA,2804
39
- mesa/visualization/components/matplotlib.py,sha256=wGPxZAS6c3HZgyrkoFKA6z5CqaTr2YVB6sDZtywTt_I,8277
40
- mesa-3.0.0a5.dist-info/METADATA,sha256=EqwMNNaGiRZhmZVkg032KxexPwvNlEYGmzF9jQ5N05s,8339
41
- mesa-3.0.0a5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
42
- mesa-3.0.0a5.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
43
- mesa-3.0.0a5.dist-info/licenses/LICENSE,sha256=OGUgret9fRrm8J3pdsPXETIjf0H8puK_Nmy970ZzT78,572
44
- mesa-3.0.0a5.dist-info/RECORD,,
39
+ mesa/visualization/components/matplotlib.py,sha256=6ttFOxfNj3YdNu7c1NB0YP6-F7Chy0Qq0kkMe02TF8c,11697
40
+ mesa-3.0.0b0.dist-info/METADATA,sha256=iqC2Ea-eu6tKAs5lwnnHS7Zoimp6ifPO8cJBb4o65UQ,9937
41
+ mesa-3.0.0b0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
42
+ mesa-3.0.0b0.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
43
+ mesa-3.0.0b0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
44
+ mesa-3.0.0b0.dist-info/licenses/NOTICE,sha256=GbsWoK0QWv1JyZ_xer2s-jNilv0RtWl-0UrtlJANHPg,578
45
+ mesa-3.0.0b0.dist-info/RECORD,,
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -1,10 +1,10 @@
1
- Copyright 2023 Core Mesa Team and contributors
1
+ Copyright 2014-2024 Core Mesa Team and contributors
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
5
5
  You may obtain a copy of the License at
6
6
 
7
- http://www.apache.org/licenses/LICENSE-2.0
7
+ https://www.apache.org/licenses/LICENSE-2.0
8
8
 
9
9
  Unless required by applicable law or agreed to in writing, software
10
10
  distributed under the License is distributed on an "AS IS" BASIS,
File without changes