0
# Spatial Systems
1
2
Mesa provides two comprehensive spatial modeling approaches: the traditional `mesa.space` module (in maintenance mode) and the modern `mesa.discrete_space` module (under active development). Both systems enable positioning agents in space and managing spatial relationships, but with different design philosophies and capabilities.
3
4
## Imports
5
6
```python { .api }
7
# Traditional spatial system (maintenance mode)
8
from mesa.space import (
9
MultiGrid, SingleGrid, HexSingleGrid, HexMultiGrid,
10
ContinuousSpace, NetworkGrid, PropertyLayer
11
)
12
13
# Modern discrete spatial system (active development)
14
from mesa.discrete_space import (
15
Grid, HexGrid, Network, VoronoiGrid,
16
OrthogonalMooreGrid, OrthogonalVonNeumannGrid,
17
Cell, CellAgent, FixedAgent, Grid2DMovingAgent, CellCollection,
18
PropertyLayer, DiscreteSpace
19
)
20
21
# Type definitions
22
from typing import Tuple, List, Union, Optional, Iterable
23
import numpy as np
24
25
# Spatial coordinate types
26
Coordinate = tuple[int, int]
27
FloatCoordinate = tuple[float, float] | np.ndarray
28
NetworkCoordinate = int
29
Position = Coordinate | FloatCoordinate | NetworkCoordinate
30
GridContent = Agent | None
31
MultiGridContent = list[Agent]
32
```
33
34
## Traditional Spatial System (mesa.space)
35
36
The traditional spatial system provides well-established grid and space classes that have been the foundation of Mesa models. These classes are now in maintenance mode with new development focused on `mesa.discrete_space`.
37
38
### Grid Classes
39
40
#### SingleGrid
41
42
Grid where each cell contains at most one agent.
43
44
```python { .api }
45
from mesa.space import SingleGrid
46
47
class SingleGrid:
48
"""
49
Grid where each cell contains at most one agent.
50
51
Provides basic grid functionality with unique occupancy constraint.
52
"""
53
54
def __init__(self, width: int, height: int, torus: bool = False):
55
"""
56
Initialize a SingleGrid.
57
58
Parameters:
59
width: Grid width in cells
60
height: Grid height in cells
61
torus: Whether the grid wraps around at edges
62
"""
63
...
64
65
def place_agent(self, agent, pos: Coordinate):
66
"""
67
Place an agent at a specific position.
68
69
Parameters:
70
agent: Agent to place
71
pos: (x, y) coordinate tuple
72
"""
73
...
74
75
def move_agent(self, agent, pos: Coordinate):
76
"""
77
Move an agent to a new position.
78
79
Parameters:
80
agent: Agent to move
81
pos: New (x, y) coordinate tuple
82
"""
83
...
84
85
def remove_agent(self, agent):
86
"""Remove an agent from the grid."""
87
...
88
89
def get_cell_list_contents(self, cell_list: List[Coordinate]) -> List[Agent]:
90
"""Get all agents in specified cells."""
91
...
92
93
def get_neighbors(self, pos: Coordinate, moore: bool = True,
94
include_center: bool = False, radius: int = 1) -> List[Coordinate]:
95
"""
96
Get neighboring positions.
97
98
Parameters:
99
pos: Center position
100
moore: If True, use Moore neighborhood (8-connected), else Von Neumann (4-connected)
101
include_center: Whether to include the center position
102
radius: Neighborhood radius
103
"""
104
...
105
106
def get_neighborhood(self, pos: Coordinate, moore: bool = True,
107
include_center: bool = False, radius: int = 1) -> List[Coordinate]:
108
"""Get neighborhood positions (alias for get_neighbors)."""
109
...
110
111
def iter_neighbors(self, pos: Coordinate, moore: bool = True,
112
include_center: bool = False, radius: int = 1) -> Iterator[Agent]:
113
"""Iterate over agents in neighboring cells."""
114
...
115
116
def find_empty(self) -> Coordinate | None:
117
"""Find a random empty cell, or None if grid is full."""
118
...
119
120
def exists_empty_cells(self) -> bool:
121
"""Check if any empty cells exist."""
122
...
123
```
124
125
#### MultiGrid
126
127
Grid where each cell can contain multiple agents.
128
129
```python { .api }
130
from mesa.space import MultiGrid
131
132
class MultiGrid:
133
"""
134
Grid where each cell can contain multiple agents.
135
136
Extends SingleGrid functionality to support multiple agents per cell.
137
"""
138
139
def __init__(self, width: int, height: int, torus: bool = False):
140
"""
141
Initialize a MultiGrid.
142
143
Parameters:
144
width: Grid width in cells
145
height: Grid height in cells
146
torus: Whether the grid wraps around at edges
147
"""
148
...
149
150
def place_agent(self, agent, pos: Coordinate):
151
"""Add an agent to a cell (multiple agents allowed)."""
152
...
153
154
def move_agent(self, agent, pos: Coordinate):
155
"""Move an agent to a new position."""
156
...
157
158
def remove_agent(self, agent):
159
"""Remove an agent from the grid."""
160
...
161
162
def get_cell_list_contents(self, cell_list: List[Coordinate]) -> List[Agent]:
163
"""Get all agents in specified cells."""
164
...
165
166
# Inherits all SingleGrid methods with multi-agent behavior
167
```
168
169
### Hexagonal Grids
170
171
#### HexSingleGrid and HexMultiGrid
172
173
Hexagonal grid variants providing 6-neighbor connectivity.
174
175
```python { .api }
176
from mesa.space import HexSingleGrid, HexMultiGrid
177
178
class HexSingleGrid(SingleGrid):
179
"""Hexagonal grid with single agent per cell."""
180
181
def get_neighbors(self, pos: Coordinate, include_center: bool = False,
182
radius: int = 1) -> List[Coordinate]:
183
"""Get hexagonal neighbors (6-connected)."""
184
...
185
186
class HexMultiGrid(MultiGrid):
187
"""Hexagonal grid with multiple agents per cell."""
188
189
def get_neighbors(self, pos: Coordinate, include_center: bool = False,
190
radius: int = 1) -> List[Coordinate]:
191
"""Get hexagonal neighbors (6-connected)."""
192
...
193
```
194
195
### ContinuousSpace
196
197
Two-dimensional continuous space with real-valued coordinates.
198
199
```python { .api }
200
from mesa.space import ContinuousSpace
201
202
class ContinuousSpace:
203
"""
204
Two-dimensional continuous space with real-valued coordinates.
205
206
Enables positioning agents at any (x, y) coordinate within defined bounds.
207
"""
208
209
def __init__(self, x_max: float, y_max: float, torus: bool = False,
210
x_min: float = 0, y_min: float = 0):
211
"""
212
Initialize continuous space.
213
214
Parameters:
215
x_max: Maximum x coordinate
216
y_max: Maximum y coordinate
217
torus: Whether space wraps around at boundaries
218
x_min: Minimum x coordinate
219
y_min: Minimum y coordinate
220
"""
221
...
222
223
def place_agent(self, agent, pos: FloatCoordinate):
224
"""
225
Place an agent at continuous coordinates.
226
227
Parameters:
228
agent: Agent to place
229
pos: (x, y) coordinate tuple with float values
230
"""
231
...
232
233
def move_agent(self, agent, pos: FloatCoordinate):
234
"""Move an agent to new continuous coordinates."""
235
...
236
237
def remove_agent(self, agent):
238
"""Remove an agent from the space."""
239
...
240
241
def get_neighbors(self, pos: FloatCoordinate, radius: float,
242
include_center: bool = False) -> List[Agent]:
243
"""
244
Get agents within radius of position.
245
246
Parameters:
247
pos: Center position
248
radius: Search radius
249
include_center: Whether to include agent at exact center
250
"""
251
...
252
253
def get_heading(self, pos_1: FloatCoordinate, pos_2: FloatCoordinate) -> float:
254
"""Get heading from pos_1 to pos_2 in radians."""
255
...
256
257
def get_distance(self, pos_1: FloatCoordinate, pos_2: FloatCoordinate) -> float:
258
"""Get Euclidean distance between positions."""
259
...
260
261
def move_by(self, agent, dx: float, dy: float):
262
"""Move agent by relative displacement."""
263
...
264
265
def torus_adj(self, pos: FloatCoordinate) -> FloatCoordinate:
266
"""Adjust position for torus topology."""
267
...
268
```
269
270
### NetworkGrid
271
272
Network-based space where agents are positioned on graph nodes.
273
274
```python { .api }
275
from mesa.space import NetworkGrid
276
import networkx as nx
277
278
class NetworkGrid:
279
"""
280
Network-based space where agents are positioned on nodes.
281
282
Uses NetworkX graphs to define spatial structure and connectivity.
283
"""
284
285
def __init__(self, G: nx.Graph):
286
"""
287
Initialize network grid.
288
289
Parameters:
290
G: NetworkX graph defining the network structure
291
"""
292
...
293
294
def place_agent(self, agent, node_id: NetworkCoordinate):
295
"""
296
Place agent on a network node.
297
298
Parameters:
299
agent: Agent to place
300
node_id: Network node identifier
301
"""
302
...
303
304
def move_agent(self, agent, node_id: NetworkCoordinate):
305
"""Move agent to a different node."""
306
...
307
308
def remove_agent(self, agent):
309
"""Remove agent from the network."""
310
...
311
312
def get_neighbors(self, node_id: NetworkCoordinate) -> List[NetworkCoordinate]:
313
"""Get neighboring nodes in the network."""
314
...
315
316
def get_all_cell_contents(self) -> List[Agent]:
317
"""Get all agents in the network."""
318
...
319
320
def get_cell_list_contents(self, cell_list: List[NetworkCoordinate]) -> List[Agent]:
321
"""Get agents on specified nodes."""
322
...
323
324
def iter_neighbors(self, node_id: NetworkCoordinate) -> Iterator[Agent]:
325
"""Iterate over agents on neighboring nodes."""
326
...
327
```
328
329
## Modern Discrete Spatial System (mesa.discrete_space)
330
331
The modern discrete spatial system introduces a cell-centric approach with enhanced capabilities for property-rich spatial modeling. This system is under active development and represents the future of Mesa's spatial functionality.
332
333
### Core Classes
334
335
#### Cell
336
337
Active positions that can have properties and contain agents.
338
339
```python { .api }
340
from mesa.discrete_space import Cell
341
342
class Cell:
343
"""
344
Active positions that can have properties and contain agents.
345
346
Cells are first-class objects that can store properties, execute behaviors,
347
and manage their agent occupants.
348
"""
349
350
def __init__(self, coordinate: Coordinate, capacity: int | None = None):
351
"""
352
Initialize a cell.
353
354
Parameters:
355
coordinate: The cell's position coordinate
356
capacity: Maximum number of agents (None for unlimited)
357
"""
358
...
359
360
def add_agent(self, agent):
361
"""Add an agent to this cell."""
362
...
363
364
def remove_agent(self, agent):
365
"""Remove an agent from this cell."""
366
...
367
368
@property
369
def agents(self) -> List[Agent]:
370
"""Get all agents in this cell."""
371
...
372
373
@property
374
def coordinate(self) -> Coordinate:
375
"""Get the cell's coordinate."""
376
...
377
378
@property
379
def is_empty(self) -> bool:
380
"""Check if cell contains no agents."""
381
...
382
383
@property
384
def is_full(self) -> bool:
385
"""Check if cell is at capacity."""
386
...
387
388
def get_neighborhood(self, radius: int = 1) -> 'CellCollection':
389
"""Get neighboring cells within radius."""
390
...
391
```
392
393
#### CellAgent
394
395
Base agent class that understands cell-based spatial interactions.
396
397
```python { .api }
398
from mesa.discrete_space import CellAgent
399
400
class CellAgent(Agent):
401
"""
402
Base agent class that understands cell-based spatial interactions.
403
404
Extends the basic Agent class with cell-aware spatial behavior
405
and movement capabilities.
406
"""
407
408
def __init__(self, model, cell: Cell | None = None):
409
"""
410
Initialize a CellAgent.
411
412
Parameters:
413
model: The model instance
414
cell: Initial cell position (optional)
415
"""
416
super().__init__(model)
417
...
418
419
@property
420
def cell(self) -> Cell | None:
421
"""Get the cell this agent occupies."""
422
...
423
424
@property
425
def coordinate(self) -> Coordinate | None:
426
"""Get the agent's coordinate position."""
427
...
428
429
def move_to(self, cell: Cell):
430
"""
431
Move this agent to a different cell.
432
433
Parameters:
434
cell: Target cell to move to
435
"""
436
...
437
438
def get_neighbors(self, radius: int = 1, include_center: bool = False) -> List['CellAgent']:
439
"""
440
Get neighboring agents within radius.
441
442
Parameters:
443
radius: Search radius in cells
444
include_center: Whether to include agents in same cell
445
"""
446
...
447
448
def get_neighborhood_cells(self, radius: int = 1) -> 'CellCollection':
449
"""Get neighboring cells within radius."""
450
...
451
```
452
453
#### FixedAgent
454
455
Specialized agent class for immobile entities permanently fixed to cells.
456
457
```python { .api }
458
from mesa.discrete_space import FixedAgent
459
460
class FixedAgent(Agent):
461
"""
462
Agent permanently fixed to a cell position.
463
464
FixedAgent represents patches or immobile entities that are bound to
465
specific cells and cannot move. Useful for terrain features, buildings,
466
or other static environmental elements.
467
"""
468
469
def remove(self):
470
"""Remove the agent from the model and cell."""
471
...
472
```
473
474
#### Grid2DMovingAgent
475
476
Specialized agent class with enhanced 2D grid movement capabilities.
477
478
```python { .api }
479
from mesa.discrete_space import Grid2DMovingAgent
480
481
class Grid2DMovingAgent(CellAgent):
482
"""
483
Mixin for agents with directional movement in 2D grids.
484
485
Provides convenient movement methods using cardinal and ordinal directions,
486
with support for string-based direction commands like 'north', 'southeast', etc.
487
"""
488
489
DIRECTION_MAP = {
490
"n": (-1, 0), "north": (-1, 0), "up": (-1, 0),
491
"s": (1, 0), "south": (1, 0), "down": (1, 0),
492
"e": (0, 1), "east": (0, 1), "right": (0, 1),
493
"w": (0, -1), "west": (0, -1), "left": (0, -1),
494
"ne": (-1, 1), "northeast": (-1, 1), "upright": (-1, 1),
495
"nw": (-1, -1), "northwest": (-1, -1), "upleft": (-1, -1),
496
"se": (1, 1), "southeast": (1, 1), "downright": (1, 1),
497
"sw": (1, -1), "southwest": (1, -1), "downleft": (1, -1)
498
}
499
500
def move_by_direction(self, direction: str | tuple[int, int]):
501
"""
502
Move agent by direction string or coordinate offset.
503
504
Parameters:
505
direction: Direction string (e.g., 'north') or coordinate tuple
506
"""
507
...
508
```
509
510
### Grid Implementations
511
512
#### Grid
513
514
Base grid implementation for cell spaces.
515
516
```python { .api }
517
from mesa.discrete_space import Grid
518
519
class Grid(DiscreteSpace):
520
"""
521
Base grid implementation for cell spaces.
522
523
Provides orthogonal grid structure with configurable neighborhood types
524
and boundary conditions.
525
"""
526
527
def __init__(self, width: int, height: int, torus: bool = False,
528
capacity: int | None = None):
529
"""
530
Initialize a grid.
531
532
Parameters:
533
width: Grid width in cells
534
height: Grid height in cells
535
torus: Whether grid wraps at boundaries
536
capacity: Default cell capacity (None for unlimited)
537
"""
538
...
539
540
def place_agent(self, agent: CellAgent, cell: Cell):
541
"""Place an agent in a specific cell."""
542
...
543
544
def move_agent(self, agent: CellAgent, cell: Cell):
545
"""Move an agent to a different cell."""
546
...
547
548
def remove_agent(self, agent: CellAgent):
549
"""Remove an agent from the grid."""
550
...
551
552
def select_random_empty_cell(self) -> Cell:
553
"""Select a random empty cell."""
554
...
555
556
def get_cell(self, coordinate: Coordinate) -> Cell:
557
"""Get cell at specific coordinate."""
558
...
559
560
def get_cells(self) -> CellCollection:
561
"""Get all cells in the grid."""
562
...
563
564
def get_empty_cells(self) -> CellCollection:
565
"""Get all empty cells."""
566
...
567
568
@property
569
def width(self) -> int:
570
"""Grid width."""
571
...
572
573
@property
574
def height(self) -> int:
575
"""Grid height."""
576
...
577
```
578
579
#### OrthogonalMooreGrid
580
581
Grid with 8-neighbor (Moore) connectivity pattern.
582
583
```python { .api }
584
from mesa.discrete_space import OrthogonalMooreGrid
585
586
class OrthogonalMooreGrid(Grid):
587
"""
588
Grid with 8-neighbor (Moore) connectivity.
589
590
Provides Moore neighborhood where each cell has up to 8 neighbors
591
(including diagonal connections). In n-dimensional space, provides
592
(3^n)-1 neighbors per cell.
593
"""
594
595
def __init__(self, width: int, height: int, torus: bool = False, capacity: int | None = None):
596
"""
597
Initialize Moore grid.
598
599
Parameters:
600
width: Grid width
601
height: Grid height
602
torus: Whether grid wraps around edges
603
capacity: Maximum agents per cell (None for unlimited)
604
"""
605
...
606
```
607
608
#### OrthogonalVonNeumannGrid
609
610
Grid with 4-neighbor (Von Neumann) connectivity pattern.
611
612
```python { .api }
613
from mesa.discrete_space import OrthogonalVonNeumannGrid
614
615
class OrthogonalVonNeumannGrid(Grid):
616
"""
617
Grid with 4-neighbor (Von Neumann) connectivity.
618
619
Provides Von Neumann neighborhood where each cell has up to 4 neighbors
620
(only orthogonal connections, no diagonals). In n-dimensional space,
621
provides 2n neighbors per cell.
622
"""
623
624
def __init__(self, width: int, height: int, torus: bool = False, capacity: int | None = None):
625
"""
626
Initialize Von Neumann grid.
627
628
Parameters:
629
width: Grid width
630
height: Grid height
631
torus: Whether grid wraps around edges
632
capacity: Maximum agents per cell (None for unlimited)
633
"""
634
...
635
```
636
637
#### HexGrid
638
639
Hexagonal grid arrangement with 6-neighbor connectivity.
640
641
```python { .api }
642
from mesa.discrete_space import HexGrid
643
644
class HexGrid(DiscreteSpace):
645
"""
646
Hexagonal grid arrangement with 6-neighbor connectivity.
647
648
Provides hexagonal tessellation for models requiring 6-neighbor
649
spatial relationships.
650
"""
651
652
def __init__(self, width: int, height: int, torus: bool = False):
653
"""
654
Initialize hexagonal grid.
655
656
Parameters:
657
width: Grid width in hexagonal cells
658
height: Grid height in hexagonal cells
659
torus: Whether grid wraps at boundaries
660
"""
661
...
662
663
# Inherits base grid methods with hexagonal geometry
664
```
665
666
#### Network
667
668
Network-based cell arrangement using graph structures.
669
670
```python { .api }
671
from mesa.discrete_space import Network
672
import networkx as nx
673
674
class Network(DiscreteSpace):
675
"""
676
Network-based cell arrangement using graph structures.
677
678
Enables spatial modeling on arbitrary network topologies
679
using NetworkX graphs.
680
"""
681
682
def __init__(self, graph: nx.Graph):
683
"""
684
Initialize network space.
685
686
Parameters:
687
graph: NetworkX graph defining connectivity
688
"""
689
...
690
691
def get_cell(self, node_id) -> Cell:
692
"""Get cell corresponding to network node."""
693
...
694
695
def get_neighbors(self, cell: Cell) -> CellCollection:
696
"""Get neighboring cells in the network."""
697
...
698
```
699
700
### Property Layers
701
702
Property layers provide efficient storage and manipulation of spatial properties across cells.
703
704
```python { .api }
705
from mesa.discrete_space import PropertyLayer
706
707
class PropertyLayer:
708
"""
709
Efficient property storage and manipulation for spatial models.
710
711
Enables storing and updating scalar or array properties across
712
spatial cells with optimized operations.
713
"""
714
715
def __init__(self, name: str, width: int, height: int,
716
default_value: Any = 0, dtype=None):
717
"""
718
Initialize property layer.
719
720
Parameters:
721
name: Layer name identifier
722
width: Layer width
723
height: Layer height
724
default_value: Default cell value
725
dtype: NumPy data type
726
"""
727
...
728
729
def set_cell(self, coordinate: Coordinate, value: Any):
730
"""Set value at specific coordinate."""
731
...
732
733
def get_cell(self, coordinate: Coordinate) -> Any:
734
"""Get value at specific coordinate."""
735
...
736
737
def set_cells(self, coordinates: List[Coordinate], values: Any):
738
"""Set values at multiple coordinates."""
739
...
740
741
def get_cells(self, coordinates: List[Coordinate]) -> List[Any]:
742
"""Get values at multiple coordinates."""
743
...
744
745
def modify_cells(self, coordinates: List[Coordinate],
746
operation: Callable, *args, **kwargs):
747
"""Apply operation to modify cell values."""
748
...
749
750
def apply_operation(self, operation: Callable, *args, **kwargs):
751
"""Apply operation to all cells."""
752
...
753
754
@property
755
def data(self) -> np.ndarray:
756
"""Access underlying NumPy array."""
757
...
758
```
759
760
## Usage Examples
761
762
### Traditional Grid Usage
763
764
```python { .api }
765
from mesa import Agent, Model
766
from mesa.space import MultiGrid
767
from mesa.datacollection import DataCollector
768
769
class TraditionalAgent(Agent):
770
def __init__(self, model, energy=100):
771
super().__init__(model)
772
self.energy = energy
773
774
def step(self):
775
# Move to random neighboring cell
776
neighbors = self.model.grid.get_neighborhood(
777
self.pos, moore=True, include_center=False
778
)
779
new_pos = self.random.choice(neighbors)
780
self.model.grid.move_agent(self, new_pos)
781
782
# Interact with agents in same cell
783
cellmates = self.model.grid.get_cell_list_contents([self.pos])
784
for other in cellmates:
785
if other != self and isinstance(other, TraditionalAgent):
786
# Simple energy transfer
787
transfer = min(5, self.energy // 2)
788
self.energy -= transfer
789
other.energy += transfer
790
791
class TraditionalModel(Model):
792
def __init__(self, n_agents=50, width=20, height=20):
793
super().__init__()
794
795
# Create traditional grid
796
self.grid = MultiGrid(width, height, torus=True)
797
798
# Create agents and place randomly
799
for i in range(n_agents):
800
agent = TraditionalAgent(self, energy=100)
801
x = self.random.randrange(width)
802
y = self.random.randrange(height)
803
self.grid.place_agent(agent, (x, y))
804
805
self.running = True
806
807
def step(self):
808
self.agents.shuffle_do("step")
809
810
# Usage
811
model = TraditionalModel(n_agents=30, width=15, height=15)
812
for i in range(50):
813
model.step()
814
```
815
816
### Modern Cell-Space Usage
817
818
```python { .api }
819
from mesa import Model
820
from mesa.discrete_space import Grid, CellAgent, PropertyLayer
821
822
class ModernAgent(CellAgent):
823
def __init__(self, model, cell=None, energy=100):
824
super().__init__(model, cell)
825
self.energy = energy
826
827
def step(self):
828
# Get neighboring cells
829
neighbors = self.get_neighborhood_cells(radius=1)
830
empty_neighbors = neighbors.select_cells(lambda c: c.is_empty)
831
832
if empty_neighbors:
833
# Move to random empty neighboring cell
834
new_cell = self.random.choice(empty_neighbors)
835
self.move_to(new_cell)
836
837
# Interact with nearby agents
838
nearby_agents = self.get_neighbors(radius=1, include_center=True)
839
for other in nearby_agents:
840
if other != self:
841
# Energy sharing
842
avg_energy = (self.energy + other.energy) / 2
843
self.energy = avg_energy
844
other.energy = avg_energy
845
846
# Update cell property
847
if hasattr(self.model, 'temperature_layer'):
848
current_temp = self.model.temperature_layer.get_cell(self.coordinate)
849
# Agents increase local temperature
850
self.model.temperature_layer.set_cell(self.coordinate, current_temp + 0.1)
851
852
class ModernModel(Model):
853
def __init__(self, n_agents=50, width=20, height=20):
854
super().__init__()
855
856
# Create modern grid
857
self.space = Grid(width, height, torus=True)
858
859
# Create property layer for temperature
860
self.temperature_layer = PropertyLayer(
861
"temperature", width, height, default_value=20.0
862
)
863
864
# Create agents and place randomly
865
for i in range(n_agents):
866
agent = ModernAgent(self, energy=100)
867
empty_cell = self.space.select_random_empty_cell()
868
self.space.place_agent(agent, empty_cell)
869
870
self.running = True
871
872
def step(self):
873
# Execute agent behaviors
874
self.agents.shuffle_do("step")
875
876
# Update environment - cool down temperature
877
self.temperature_layer.apply_operation(
878
lambda temp: max(20.0, temp * 0.95) # Cool towards base temperature
879
)
880
881
# Usage
882
model = ModernModel(n_agents=30, width=15, height=15)
883
for i in range(50):
884
model.step()
885
886
# Access spatial data
887
hot_cells = []
888
for cell in model.space.get_cells():
889
temp = model.temperature_layer.get_cell(cell.coordinate)
890
if temp > 25.0:
891
hot_cells.append(cell)
892
893
print(f"Hot spots: {len(hot_cells)} cells above 25°C")
894
```
895
896
### Continuous Space Usage
897
898
```python { .api }
899
from mesa import Agent, Model
900
from mesa.space import ContinuousSpace
901
import math
902
903
class ContinuousAgent(Agent):
904
def __init__(self, model, pos, speed=1.0):
905
super().__init__(model)
906
self.pos = pos
907
self.speed = speed
908
self.heading = self.random.uniform(0, 2 * math.pi)
909
910
def step(self):
911
# Move in current direction
912
dx = math.cos(self.heading) * self.speed
913
dy = math.sin(self.heading) * self.speed
914
self.model.space.move_by(self, dx, dy)
915
916
# Avoid crowding - turn away from nearby agents
917
neighbors = self.model.space.get_neighbors(self.pos, radius=3.0)
918
if len(neighbors) > 3:
919
# Calculate average position of neighbors
920
avg_x = sum(n.pos[0] for n in neighbors) / len(neighbors)
921
avg_y = sum(n.pos[1] for n in neighbors) / len(neighbors)
922
923
# Turn away from crowd
924
away_heading = self.model.space.get_heading(
925
(avg_x, avg_y), self.pos
926
)
927
self.heading = away_heading + self.random.uniform(-0.5, 0.5)
928
929
# Random walk component
930
self.heading += self.random.uniform(-0.1, 0.1)
931
932
class ContinuousModel(Model):
933
def __init__(self, n_agents=100, width=50.0, height=50.0):
934
super().__init__()
935
936
# Create continuous space
937
self.space = ContinuousSpace(width, height, torus=True)
938
939
# Create agents at random positions
940
for i in range(n_agents):
941
x = self.random.uniform(0, width)
942
y = self.random.uniform(0, height)
943
agent = ContinuousAgent(self, (x, y), speed=self.random.uniform(0.5, 2.0))
944
self.space.place_agent(agent, (x, y))
945
946
self.running = True
947
948
def step(self):
949
self.agents.shuffle_do("step")
950
951
# Usage
952
model = ContinuousModel(n_agents=50, width=30.0, height=30.0)
953
for i in range(100):
954
model.step()
955
```
956
957
### Network Space Usage
958
959
```python { .api }
960
from mesa import Agent, Model
961
from mesa.space import NetworkGrid
962
import networkx as nx
963
964
class NetworkAgent(Agent):
965
def __init__(self, model, node_id):
966
super().__init__(model)
967
self.node_id = node_id
968
self.infection_status = "susceptible"
969
970
def step(self):
971
if self.infection_status == "infected":
972
# Spread infection to neighbors
973
neighbors = self.model.grid.get_neighbors(self.pos)
974
for neighbor_node in neighbors:
975
neighbor_agents = self.model.grid.get_cell_list_contents([neighbor_node])
976
for agent in neighbor_agents:
977
if (agent.infection_status == "susceptible" and
978
self.random.random() < 0.1): # 10% transmission rate
979
agent.infection_status = "infected"
980
981
elif self.infection_status == "susceptible":
982
# Random movement
983
neighbors = self.model.grid.get_neighbors(self.pos)
984
if neighbors:
985
new_node = self.random.choice(neighbors)
986
self.model.grid.move_agent(self, new_node)
987
988
class NetworkModel(Model):
989
def __init__(self, network_type="small_world", n_nodes=100):
990
super().__init__()
991
992
# Create network
993
if network_type == "small_world":
994
G = nx.watts_strogatz_graph(n_nodes, 6, 0.3)
995
elif network_type == "scale_free":
996
G = nx.barabasi_albert_graph(n_nodes, 3)
997
else:
998
G = nx.erdos_renyi_graph(n_nodes, 0.1)
999
1000
self.grid = NetworkGrid(G)
1001
1002
# Create agents on random nodes
1003
nodes = list(G.nodes())
1004
for i, node in enumerate(nodes):
1005
agent = NetworkAgent(self, node)
1006
self.grid.place_agent(agent, node)
1007
1008
# Patient zero
1009
if self.agents:
1010
patient_zero = self.random.choice(self.agents)
1011
patient_zero.infection_status = "infected"
1012
1013
self.running = True
1014
1015
def step(self):
1016
self.agents.shuffle_do("step")
1017
1018
# Check if infection has spread to everyone or died out
1019
infected = len([a for a in self.agents if a.infection_status == "infected"])
1020
if infected == 0 or infected == len(self.agents):
1021
self.running = False
1022
1023
# Usage
1024
model = NetworkModel("small_world", n_nodes=50)
1025
for i in range(100):
1026
model.step()
1027
if not model.running:
1028
break
1029
1030
infected_count = len([a for a in model.agents if a.infection_status == "infected"])
1031
print(f"Final infected count: {infected_count}/{len(model.agents)}")
1032
```
1033
1034
## Choosing Between Spatial Systems
1035
1036
### Use Traditional mesa.space When:
1037
- Working with existing Mesa models that use the traditional system
1038
- Need well-tested, stable spatial functionality
1039
- Building simple grid-based models without complex spatial properties
1040
- Prioritize compatibility with Mesa 2.x models
1041
1042
### Use Modern mesa.discrete_space When:
1043
- Building new models from scratch
1044
- Need property-rich spatial environments
1045
- Want cell-centric spatial modeling
1046
- Require advanced spatial operations and queries
1047
- Planning to use experimental Mesa features
1048
1049
### Migration Considerations
1050
1051
```python { .api }
1052
# Traditional approach
1053
from mesa.space import MultiGrid
1054
1055
class OldModel(Model):
1056
def __init__(self):
1057
self.grid = MultiGrid(10, 10, True)
1058
# Traditional grid operations...
1059
1060
# Modern approach - similar functionality with enhanced capabilities
1061
from mesa.discrete_space import Grid
1062
1063
class NewModel(Model):
1064
def __init__(self):
1065
self.space = Grid(10, 10, torus=True)
1066
# Modern grid operations with cells and property layers...
1067
```