Mesa 3.0.0a5__py3-none-any.whl → 3.0.0b1__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 (91) hide show
  1. examples/README.md +37 -0
  2. examples/__init__.py +0 -0
  3. examples/advanced/__init__.py +0 -0
  4. examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  5. examples/advanced/epstein_civil_violence/Readme.md +33 -0
  6. examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py +0 -0
  7. examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +158 -0
  8. examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +146 -0
  9. examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py +33 -0
  10. examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +81 -0
  11. examples/advanced/epstein_civil_violence/requirements.txt +3 -0
  12. examples/advanced/epstein_civil_violence/run.py +3 -0
  13. examples/advanced/pd_grid/analysis.ipynb +228 -0
  14. examples/advanced/pd_grid/pd_grid/__init__.py +0 -0
  15. examples/advanced/pd_grid/pd_grid/agent.py +50 -0
  16. examples/advanced/pd_grid/pd_grid/model.py +72 -0
  17. examples/advanced/pd_grid/pd_grid/portrayal.py +19 -0
  18. examples/advanced/pd_grid/pd_grid/server.py +21 -0
  19. examples/advanced/pd_grid/readme.md +42 -0
  20. examples/advanced/pd_grid/requirements.txt +3 -0
  21. examples/advanced/pd_grid/run.py +3 -0
  22. examples/advanced/sugarscape_g1mt/Readme.md +87 -0
  23. examples/advanced/sugarscape_g1mt/app.py +61 -0
  24. examples/advanced/sugarscape_g1mt/requirements.txt +6 -0
  25. examples/advanced/sugarscape_g1mt/run.py +105 -0
  26. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py +0 -0
  27. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +180 -0
  28. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +26 -0
  29. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +61 -0
  30. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt +50 -0
  31. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +321 -0
  32. examples/advanced/sugarscape_g1mt/tests.py +72 -0
  33. examples/advanced/wolf_sheep/Readme.md +57 -0
  34. examples/advanced/wolf_sheep/__init__.py +0 -0
  35. examples/advanced/wolf_sheep/requirements.txt +1 -0
  36. examples/advanced/wolf_sheep/run.py +3 -0
  37. examples/advanced/wolf_sheep/wolf_sheep/__init__.py +0 -0
  38. examples/advanced/wolf_sheep/wolf_sheep/agents.py +102 -0
  39. examples/advanced/wolf_sheep/wolf_sheep/model.py +136 -0
  40. examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png +0 -0
  41. examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png +0 -0
  42. examples/advanced/wolf_sheep/wolf_sheep/server.py +78 -0
  43. examples/basic/__init__.py +13 -0
  44. examples/basic/boid_flockers/Readme.md +43 -0
  45. examples/basic/boid_flockers/agents.py +71 -0
  46. examples/basic/boid_flockers/app.py +59 -0
  47. examples/basic/boid_flockers/model.py +70 -0
  48. examples/basic/boltzmann_wealth_model/Readme.md +60 -0
  49. examples/basic/boltzmann_wealth_model/agents.py +31 -0
  50. examples/basic/boltzmann_wealth_model/app.py +66 -0
  51. examples/basic/boltzmann_wealth_model/model.py +44 -0
  52. examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  53. examples/basic/conways_game_of_life/Readme.md +35 -0
  54. examples/basic/conways_game_of_life/agents.py +47 -0
  55. examples/basic/conways_game_of_life/model.py +32 -0
  56. examples/basic/conways_game_of_life/portrayal.py +18 -0
  57. examples/basic/conways_game_of_life/requirements.txt +1 -0
  58. examples/basic/conways_game_of_life/server.py +11 -0
  59. examples/basic/conways_game_of_life/st_app.py +71 -0
  60. examples/basic/schelling/README.md +47 -0
  61. examples/basic/schelling/agents.py +26 -0
  62. examples/basic/schelling/analysis.ipynb +205 -0
  63. examples/basic/schelling/app.py +43 -0
  64. examples/basic/schelling/model.py +60 -0
  65. examples/basic/virus_on_network/README.md +61 -0
  66. examples/basic/virus_on_network/agents.py +69 -0
  67. examples/basic/virus_on_network/app.py +133 -0
  68. examples/basic/virus_on_network/model.py +99 -0
  69. mesa/__init__.py +4 -1
  70. mesa/agent.py +24 -43
  71. mesa/batchrunner.py +7 -0
  72. mesa/examples.py +3 -0
  73. mesa/experimental/__init__.py +8 -2
  74. mesa/experimental/cell_space/__init__.py +7 -1
  75. mesa/experimental/cell_space/cell.py +35 -6
  76. mesa/experimental/cell_space/cell_agent.py +114 -23
  77. mesa/experimental/cell_space/discrete_space.py +70 -3
  78. mesa/experimental/cell_space/grid.py +13 -0
  79. mesa/experimental/cell_space/network.py +3 -0
  80. mesa/experimental/devs/examples/wolf_sheep.py +2 -1
  81. mesa/model.py +71 -21
  82. mesa/time.py +7 -5
  83. mesa/visualization/components/matplotlib.py +184 -90
  84. mesa/visualization/solara_viz.py +25 -61
  85. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/METADATA +55 -13
  86. mesa-3.0.0b1.dist-info/RECORD +114 -0
  87. mesa-3.0.0b1.dist-info/licenses/LICENSE +202 -0
  88. mesa-3.0.0a5.dist-info/licenses/LICENSE → mesa-3.0.0b1.dist-info/licenses/NOTICE +2 -2
  89. mesa-3.0.0a5.dist-info/RECORD +0 -44
  90. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/WHEEL +0 -0
  91. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,69 @@
1
+ from enum import Enum
2
+
3
+ from mesa import Agent
4
+
5
+
6
+ class State(Enum):
7
+ SUSCEPTIBLE = 0
8
+ INFECTED = 1
9
+ RESISTANT = 2
10
+
11
+
12
+ class VirusAgent(Agent):
13
+ """Individual Agent definition and its properties/interaction methods."""
14
+
15
+ def __init__(
16
+ self,
17
+ model,
18
+ initial_state,
19
+ virus_spread_chance,
20
+ virus_check_frequency,
21
+ recovery_chance,
22
+ gain_resistance_chance,
23
+ ):
24
+ super().__init__(model)
25
+
26
+ self.state = initial_state
27
+
28
+ self.virus_spread_chance = virus_spread_chance
29
+ self.virus_check_frequency = virus_check_frequency
30
+ self.recovery_chance = recovery_chance
31
+ self.gain_resistance_chance = gain_resistance_chance
32
+
33
+ def try_to_infect_neighbors(self):
34
+ neighbors_nodes = self.model.grid.get_neighborhood(
35
+ self.pos, include_center=False
36
+ )
37
+ susceptible_neighbors = [
38
+ agent
39
+ for agent in self.model.grid.get_cell_list_contents(neighbors_nodes)
40
+ if agent.state is State.SUSCEPTIBLE
41
+ ]
42
+ for a in susceptible_neighbors:
43
+ if self.random.random() < self.virus_spread_chance:
44
+ a.state = State.INFECTED
45
+
46
+ def try_gain_resistance(self):
47
+ if self.random.random() < self.gain_resistance_chance:
48
+ self.state = State.RESISTANT
49
+
50
+ def try_remove_infection(self):
51
+ # Try to remove
52
+ if self.random.random() < self.recovery_chance:
53
+ # Success
54
+ self.state = State.SUSCEPTIBLE
55
+ self.try_gain_resistance()
56
+ else:
57
+ # Failed
58
+ self.state = State.INFECTED
59
+
60
+ def try_check_situation(self):
61
+ if (self.random.random() < self.virus_check_frequency) and (
62
+ self.state is State.INFECTED
63
+ ):
64
+ self.try_remove_infection()
65
+
66
+ def step(self):
67
+ if self.state is State.INFECTED:
68
+ self.try_to_infect_neighbors()
69
+ self.try_check_situation()
@@ -0,0 +1,133 @@
1
+ import math
2
+
3
+ import solara
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.ticker import MaxNLocator
6
+
7
+ from mesa.visualization import Slider, SolaraViz, make_space_matplotlib
8
+
9
+ from .model import State, VirusOnNetwork, number_infected
10
+
11
+
12
+ def agent_portrayal(graph):
13
+ def get_agent(node):
14
+ return graph.nodes[node]["agent"][0]
15
+
16
+ edge_width = []
17
+ edge_color = []
18
+ for u, v in graph.edges():
19
+ agent1 = get_agent(u)
20
+ agent2 = get_agent(v)
21
+ w = 2
22
+ ec = "#e8e8e8"
23
+ if State.RESISTANT in (agent1.state, agent2.state):
24
+ w = 3
25
+ ec = "black"
26
+ edge_width.append(w)
27
+ edge_color.append(ec)
28
+ node_color_dict = {
29
+ State.INFECTED: "tab:red",
30
+ State.SUSCEPTIBLE: "tab:green",
31
+ State.RESISTANT: "tab:gray",
32
+ }
33
+ node_color = [node_color_dict[get_agent(node).state] for node in graph.nodes()]
34
+ return {
35
+ "width": edge_width,
36
+ "edge_color": edge_color,
37
+ "node_color": node_color,
38
+ }
39
+
40
+
41
+ def get_resistant_susceptible_ratio(model):
42
+ ratio = model.resistant_susceptible_ratio()
43
+ ratio_text = r"$\infty$" if ratio is math.inf else f"{ratio:.2f}"
44
+ infected_text = str(number_infected(model))
45
+
46
+ return f"Resistant/Susceptible Ratio: {ratio_text}<br>Infected Remaining: {infected_text}"
47
+
48
+
49
+ def make_plot(model):
50
+ # This is for the case when we want to plot multiple measures in 1 figure.
51
+ fig = Figure()
52
+ ax = fig.subplots()
53
+ measures = ["Infected", "Susceptible", "Resistant"]
54
+ colors = ["tab:red", "tab:green", "tab:gray"]
55
+ for i, m in enumerate(measures):
56
+ color = colors[i]
57
+ df = model.datacollector.get_model_vars_dataframe()
58
+ ax.plot(df.loc[:, m], label=m, color=color)
59
+ fig.legend()
60
+ # Set integer x axis
61
+ ax.xaxis.set_major_locator(MaxNLocator(integer=True))
62
+ ax.set_xlabel("Step")
63
+ ax.set_ylabel("Number of Agents")
64
+ return solara.FigureMatplotlib(fig)
65
+
66
+
67
+ model_params = {
68
+ "num_nodes": Slider(
69
+ label="Number of agents",
70
+ value=10,
71
+ min=10,
72
+ max=100,
73
+ step=1,
74
+ ),
75
+ "avg_node_degree": Slider(
76
+ label="Avg Node Degree",
77
+ value=3,
78
+ min=3,
79
+ max=8,
80
+ step=1,
81
+ ),
82
+ "initial_outbreak_size": Slider(
83
+ label="Initial Outbreak Size",
84
+ value=1,
85
+ min=1,
86
+ max=10,
87
+ step=1,
88
+ ),
89
+ "virus_spread_chance": Slider(
90
+ label="Virus Spread Chance",
91
+ value=0.4,
92
+ min=0.0,
93
+ max=1.0,
94
+ step=0.1,
95
+ ),
96
+ "virus_check_frequency": Slider(
97
+ label="Virus Check Frequency",
98
+ value=0.4,
99
+ min=0.0,
100
+ max=1.0,
101
+ step=0.1,
102
+ ),
103
+ "recovery_chance": Slider(
104
+ label="Recovery Chance",
105
+ value=0.3,
106
+ min=0.0,
107
+ max=1.0,
108
+ step=0.1,
109
+ ),
110
+ "gain_resistance_chance": Slider(
111
+ label="Gain Resistance Chance",
112
+ value=0.5,
113
+ min=0.0,
114
+ max=1.0,
115
+ step=0.1,
116
+ ),
117
+ }
118
+
119
+ SpacePlot = make_space_matplotlib(agent_portrayal)
120
+
121
+ model1 = VirusOnNetwork()
122
+
123
+ page = SolaraViz(
124
+ model1,
125
+ [
126
+ SpacePlot,
127
+ make_plot,
128
+ # get_resistant_susceptible_ratio, # TODO: Fix and uncomment
129
+ ],
130
+ model_params=model_params,
131
+ name="Virus Model",
132
+ )
133
+ page # noqa
@@ -0,0 +1,99 @@
1
+ import math
2
+
3
+ import networkx as nx
4
+
5
+ import mesa
6
+ from mesa import Model
7
+
8
+ from .agents import State, VirusAgent
9
+
10
+
11
+ def number_state(model, state):
12
+ return sum(1 for a in model.grid.get_all_cell_contents() if a.state is state)
13
+
14
+
15
+ def number_infected(model):
16
+ return number_state(model, State.INFECTED)
17
+
18
+
19
+ def number_susceptible(model):
20
+ return number_state(model, State.SUSCEPTIBLE)
21
+
22
+
23
+ def number_resistant(model):
24
+ return number_state(model, State.RESISTANT)
25
+
26
+
27
+ class VirusOnNetwork(Model):
28
+ """A virus model with some number of agents."""
29
+
30
+ def __init__(
31
+ self,
32
+ num_nodes=10,
33
+ avg_node_degree=3,
34
+ initial_outbreak_size=1,
35
+ virus_spread_chance=0.4,
36
+ virus_check_frequency=0.4,
37
+ recovery_chance=0.3,
38
+ gain_resistance_chance=0.5,
39
+ ):
40
+ super().__init__()
41
+ self.num_nodes = num_nodes
42
+ prob = avg_node_degree / self.num_nodes
43
+ self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob)
44
+ self.grid = mesa.space.NetworkGrid(self.G)
45
+
46
+ self.initial_outbreak_size = (
47
+ initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes
48
+ )
49
+ self.virus_spread_chance = virus_spread_chance
50
+ self.virus_check_frequency = virus_check_frequency
51
+ self.recovery_chance = recovery_chance
52
+ self.gain_resistance_chance = gain_resistance_chance
53
+
54
+ self.datacollector = mesa.DataCollector(
55
+ {
56
+ "Infected": number_infected,
57
+ "Susceptible": number_susceptible,
58
+ "Resistant": number_resistant,
59
+ }
60
+ )
61
+
62
+ # Create agents
63
+ for node in self.G.nodes():
64
+ a = VirusAgent(
65
+ self,
66
+ State.SUSCEPTIBLE,
67
+ self.virus_spread_chance,
68
+ self.virus_check_frequency,
69
+ self.recovery_chance,
70
+ self.gain_resistance_chance,
71
+ )
72
+
73
+ # Add the agent to the node
74
+ self.grid.place_agent(a, node)
75
+
76
+ # Infect some nodes
77
+ infected_nodes = self.random.sample(list(self.G), self.initial_outbreak_size)
78
+ for a in self.grid.get_cell_list_contents(infected_nodes):
79
+ a.state = State.INFECTED
80
+
81
+ self.running = True
82
+ self.datacollector.collect(self)
83
+
84
+ def resistant_susceptible_ratio(self):
85
+ try:
86
+ return number_state(self, State.RESISTANT) / number_state(
87
+ self, State.SUSCEPTIBLE
88
+ )
89
+ except ZeroDivisionError:
90
+ return math.inf
91
+
92
+ def step(self):
93
+ self.agents.shuffle_do("step")
94
+ # collect data
95
+ self.datacollector.collect(self)
96
+
97
+ def run_model(self, n):
98
+ for _ in range(n):
99
+ self.step()
mesa/__init__.py CHANGED
@@ -5,6 +5,8 @@ Core Objects: Model, and Agent.
5
5
 
6
6
  import datetime
7
7
 
8
+ import mesa.examples as examples
9
+ import mesa.experimental as experimental
8
10
  import mesa.space as space
9
11
  import mesa.time as time
10
12
  from mesa.agent import Agent
@@ -20,10 +22,11 @@ __all__ = [
20
22
  "DataCollector",
21
23
  "batch_run",
22
24
  "experimental",
25
+ "examples",
23
26
  ]
24
27
 
25
28
  __title__ = "mesa"
26
- __version__ = "3.0.0a5"
29
+ __version__ = "3.0.0b1"
27
30
  __license__ = "Apache 2.0"
28
31
  _this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
29
32
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
mesa/agent.py CHANGED
@@ -46,38 +46,24 @@ 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
54
+ args: passed on to super
55
+ kwargs: passed on to super
56
+
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
60
+
56
61
  """
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
- )
62
+ super().__init__(*args, **kwargs)
78
63
 
64
+ self.model: Model = model
65
+ self.unique_id: int = next(self._ids[model])
79
66
  self.pos: Position | None = None
80
-
81
67
  self.model.register_agent(self)
82
68
 
83
69
  def remove(self) -> None:
@@ -113,14 +99,18 @@ class AgentSet(MutableSet, Sequence):
113
99
  which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
114
100
  """
115
101
 
116
- def __init__(self, agents: Iterable[Agent], model: Model):
102
+ def __init__(self, agents: Iterable[Agent], random: Random | None = None):
117
103
  """Initializes the AgentSet with a collection of agents and a reference to the model.
118
104
 
119
105
  Args:
120
106
  agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
121
- model (Model): The ABM model instance to which this AgentSet belongs.
107
+ random (Random): the random number generator
122
108
  """
123
- self.model = model
109
+ if random is None:
110
+ random = (
111
+ Random()
112
+ ) # FIXME see issue 1981, how to get the central rng from model
113
+ self.random = random
124
114
  self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
125
115
 
126
116
  def __len__(self) -> int:
@@ -191,7 +181,7 @@ class AgentSet(MutableSet, Sequence):
191
181
 
192
182
  agents = agent_generator(filter_func, agent_type, at_most)
193
183
 
194
- return AgentSet(agents, self.model) if not inplace else self._update(agents)
184
+ return AgentSet(agents, self.random) if not inplace else self._update(agents)
195
185
 
196
186
  def shuffle(self, inplace: bool = False) -> AgentSet:
197
187
  """Randomly shuffle the order of agents in the AgentSet.
@@ -214,7 +204,7 @@ class AgentSet(MutableSet, Sequence):
214
204
  return self
215
205
  else:
216
206
  return AgentSet(
217
- (agent for ref in weakrefs if (agent := ref()) is not None), self.model
207
+ (agent for ref in weakrefs if (agent := ref()) is not None), self.random
218
208
  )
219
209
 
220
210
  def sort(
@@ -239,7 +229,7 @@ class AgentSet(MutableSet, Sequence):
239
229
  sorted_agents = sorted(self._agents.keys(), key=key, reverse=not ascending)
240
230
 
241
231
  return (
242
- AgentSet(sorted_agents, self.model)
232
+ AgentSet(sorted_agents, self.random)
243
233
  if not inplace
244
234
  else self._update(sorted_agents)
245
235
  )
@@ -491,7 +481,7 @@ class AgentSet(MutableSet, Sequence):
491
481
  Returns:
492
482
  dict: A dictionary representing the state of the AgentSet.
493
483
  """
494
- return {"agents": list(self._agents.keys()), "model": self.model}
484
+ return {"agents": list(self._agents.keys()), "random": self.random}
495
485
 
496
486
  def __setstate__(self, state):
497
487
  """Set the state of the AgentSet during deserialization.
@@ -499,18 +489,9 @@ class AgentSet(MutableSet, Sequence):
499
489
  Args:
500
490
  state (dict): A dictionary representing the state to restore.
501
491
  """
502
- self.model = state["model"]
492
+ self.random = state["random"]
503
493
  self._update(state["agents"])
504
494
 
505
- @property
506
- def random(self) -> Random:
507
- """Provide access to the model's random number generator.
508
-
509
- Returns:
510
- Random: The random number generator associated with the model.
511
- """
512
- return self.model.random
513
-
514
495
  def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
515
496
  """Group agents by the specified attribute or return from the callable.
516
497
 
@@ -543,7 +524,7 @@ class AgentSet(MutableSet, Sequence):
543
524
 
544
525
  if result_type == "agentset":
545
526
  return GroupBy(
546
- {k: AgentSet(v, model=self.model) for k, v in groups.items()}
527
+ {k: AgentSet(v, random=self.random) for k, v in groups.items()}
547
528
  )
548
529
  else:
549
530
  return GroupBy(groups)
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()}
mesa/examples.py ADDED
@@ -0,0 +1,3 @@
1
+ """This module is a collection of example models built using the Mesa framework."""
2
+
3
+ __path__ = ["examples"]
@@ -2,6 +2,12 @@
2
2
 
3
3
  from mesa.experimental import cell_space
4
4
 
5
- from .solara_viz import JupyterViz, Slider, SolaraViz, make_text
5
+ try:
6
+ from .solara_viz import JupyterViz, Slider, SolaraViz, make_text
6
7
 
7
- __all__ = ["cell_space", "JupyterViz", "SolaraViz", "make_text", "Slider"]
8
+ __all__ = ["cell_space", "JupyterViz", "Slider", "SolaraViz", "make_text"]
9
+ except ImportError:
10
+ print(
11
+ "Could not import SolaraViz. If you need it, install with 'pip install --pre mesa[viz]'"
12
+ )
13
+ __all__ = ["cell_space"]
@@ -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,29 @@ 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
+ )
214
+
215
+ def __getstate__(self):
216
+ """Return state of the Cell with connections set to empty."""
217
+ # fixme, once we shift to 3.11, replace this with super. __getstate__
218
+ state = (self.__dict__, {k: getattr(self, k) for k in self.__slots__})
219
+ state[1][
220
+ "connections"
221
+ ] = {} # replace this with empty connections to avoid infinite recursion error in pickle/deepcopy
222
+ return state