Mesa 3.0.0a3__py3-none-any.whl → 3.0.0a5__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 +2 -3
- mesa/agent.py +193 -75
- mesa/batchrunner.py +18 -23
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +2 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +1 -0
- mesa/datacollection.py +138 -27
- mesa/experimental/UserParam.py +67 -0
- mesa/experimental/__init__.py +5 -1
- mesa/experimental/cell_space/__init__.py +7 -0
- mesa/experimental/cell_space/cell.py +61 -20
- mesa/experimental/cell_space/cell_agent.py +12 -7
- mesa/experimental/cell_space/cell_collection.py +54 -17
- mesa/experimental/cell_space/discrete_space.py +16 -5
- mesa/experimental/cell_space/grid.py +19 -8
- mesa/experimental/cell_space/network.py +9 -7
- mesa/experimental/cell_space/voronoi.py +26 -33
- mesa/experimental/components/altair.py +81 -0
- mesa/experimental/components/matplotlib.py +242 -0
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +36 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
- mesa/experimental/devs/examples/wolf_sheep.py +43 -44
- mesa/experimental/devs/simulator.py +55 -15
- mesa/experimental/solara_viz.py +453 -0
- mesa/main.py +6 -4
- mesa/model.py +64 -61
- mesa/space.py +154 -123
- mesa/time.py +57 -67
- mesa/visualization/UserParam.py +19 -6
- mesa/visualization/__init__.py +14 -2
- mesa/visualization/components/altair.py +18 -1
- mesa/visualization/components/matplotlib.py +26 -2
- mesa/visualization/solara_viz.py +231 -225
- mesa/visualization/utils.py +9 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/METADATA +2 -1
- mesa-3.0.0a5.dist-info/RECORD +44 -0
- mesa-3.0.0a3.dist-info/RECORD +0 -39
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0a3.dist-info → mesa-3.0.0a5.dist-info}/licenses/LICENSE +0 -0
mesa/space.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Mesa Space Module
|
|
3
|
-
=================
|
|
1
|
+
"""Mesa Space Module.
|
|
4
2
|
|
|
5
3
|
Objects used to add a spatial component to a model.
|
|
6
4
|
|
|
7
|
-
Grid: base grid, which creates a rectangular grid.
|
|
8
|
-
SingleGrid: extension to Grid which strictly enforces one agent per cell.
|
|
9
|
-
MultiGrid: extension to Grid where each cell can contain a set of agents.
|
|
10
|
-
HexGrid: extension to Grid to handle hexagonal neighbors.
|
|
11
|
-
ContinuousSpace: a two-dimensional space where each agent has an arbitrary
|
|
12
|
-
|
|
13
|
-
NetworkGrid: a network where each node contains zero or more agents.
|
|
5
|
+
* Grid: base grid, which creates a rectangular grid.
|
|
6
|
+
* SingleGrid: extension to Grid which strictly enforces one agent per cell.
|
|
7
|
+
* MultiGrid: extension to Grid where each cell can contain a set of agents.
|
|
8
|
+
* HexGrid: extension to Grid to handle hexagonal neighbors.
|
|
9
|
+
* ContinuousSpace: a two-dimensional space where each agent has an arbitrary position of `float`'s.
|
|
10
|
+
* NetworkGrid: a network where each node contains zero or more agents.
|
|
14
11
|
"""
|
|
15
12
|
|
|
16
13
|
# Mypy; for the `|` operator purpose
|
|
@@ -54,9 +51,10 @@ F = TypeVar("F", bound=Callable[..., Any])
|
|
|
54
51
|
|
|
55
52
|
|
|
56
53
|
def accept_tuple_argument(wrapped_function: F) -> F:
|
|
57
|
-
"""Decorator to allow grid methods that take a list of (x, y) coord tuples
|
|
58
|
-
|
|
59
|
-
single-item list rather than forcing user to do it.
|
|
54
|
+
"""Decorator to allow grid methods that take a list of (x, y) coord tuples to also handle a single position.
|
|
55
|
+
|
|
56
|
+
Tuples are wrapped in a single-item list rather than forcing user to do it.
|
|
57
|
+
"""
|
|
60
58
|
|
|
61
59
|
def wrapper(grid_instance, positions) -> Any:
|
|
62
60
|
if len(positions) == 2 and not isinstance(positions[0], tuple):
|
|
@@ -67,11 +65,13 @@ def accept_tuple_argument(wrapped_function: F) -> F:
|
|
|
67
65
|
|
|
68
66
|
|
|
69
67
|
def is_integer(x: Real) -> bool:
|
|
70
|
-
|
|
68
|
+
"""Check if x is either a CPython integer or Numpy integer."""
|
|
71
69
|
return isinstance(x, _types_integer)
|
|
72
70
|
|
|
73
71
|
|
|
74
72
|
def warn_if_agent_has_position_already(placement_func):
|
|
73
|
+
"""Decorator to give warning if agent has position already set."""
|
|
74
|
+
|
|
75
75
|
def wrapper(self, agent, *args, **kwargs):
|
|
76
76
|
if agent.pos is not None:
|
|
77
77
|
warnings.warn(
|
|
@@ -102,7 +102,8 @@ class _Grid:
|
|
|
102
102
|
"""Create a new grid.
|
|
103
103
|
|
|
104
104
|
Args:
|
|
105
|
-
width
|
|
105
|
+
width: The grid's width.
|
|
106
|
+
height: The grid's height.
|
|
106
107
|
torus: Boolean whether the grid wraps or not.
|
|
107
108
|
"""
|
|
108
109
|
self.height = height
|
|
@@ -159,7 +160,6 @@ class _Grid:
|
|
|
159
160
|
|
|
160
161
|
def __getitem__(self, index):
|
|
161
162
|
"""Access contents from the grid."""
|
|
162
|
-
|
|
163
163
|
if isinstance(index, int):
|
|
164
164
|
# grid[x]
|
|
165
165
|
return self._grid[index]
|
|
@@ -192,8 +192,7 @@ class _Grid:
|
|
|
192
192
|
return [cell for rows in self._grid[x] for cell in rows[y]]
|
|
193
193
|
|
|
194
194
|
def __iter__(self) -> Iterator[GridContent]:
|
|
195
|
-
"""Create an iterator that chains the rows of the grid together
|
|
196
|
-
as if it is one list:"""
|
|
195
|
+
"""Create an iterator that chains the rows of the grid together as if it is one list."""
|
|
197
196
|
return itertools.chain(*self._grid)
|
|
198
197
|
|
|
199
198
|
def coord_iter(self) -> Iterator[tuple[GridContent, Coordinate]]:
|
|
@@ -209,8 +208,7 @@ class _Grid:
|
|
|
209
208
|
include_center: bool = False,
|
|
210
209
|
radius: int = 1,
|
|
211
210
|
) -> Iterator[Coordinate]:
|
|
212
|
-
"""Return an iterator over cell coordinates that are in the
|
|
213
|
-
neighborhood of a certain point.
|
|
211
|
+
"""Return an iterator over cell coordinates that are in the neighborhood of a certain point.
|
|
214
212
|
|
|
215
213
|
Args:
|
|
216
214
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -237,8 +235,7 @@ class _Grid:
|
|
|
237
235
|
include_center: bool = False,
|
|
238
236
|
radius: int = 1,
|
|
239
237
|
) -> Sequence[Coordinate]:
|
|
240
|
-
"""Return a list of cells that are in the neighborhood of a
|
|
241
|
-
certain point.
|
|
238
|
+
"""Return a list of cells that are in the neighborhood of a certain point.
|
|
242
239
|
|
|
243
240
|
Args:
|
|
244
241
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -381,8 +378,7 @@ class _Grid:
|
|
|
381
378
|
return pos[0] % self.width, pos[1] % self.height
|
|
382
379
|
|
|
383
380
|
def out_of_bounds(self, pos: Coordinate) -> bool:
|
|
384
|
-
"""Determines whether position is off the grid, returns the out of
|
|
385
|
-
bounds coordinate."""
|
|
381
|
+
"""Determines whether position is off the grid, returns the out of bounds coordinate."""
|
|
386
382
|
x, y = pos
|
|
387
383
|
return x < 0 or x >= self.width or y < 0 or y >= self.height
|
|
388
384
|
|
|
@@ -390,8 +386,7 @@ class _Grid:
|
|
|
390
386
|
def iter_cell_list_contents(
|
|
391
387
|
self, cell_list: Iterable[Coordinate]
|
|
392
388
|
) -> Iterator[Agent]:
|
|
393
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
394
|
-
in `cell_list`; cells with empty content are excluded.
|
|
389
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded.
|
|
395
390
|
|
|
396
391
|
Args:
|
|
397
392
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -407,8 +402,7 @@ class _Grid:
|
|
|
407
402
|
|
|
408
403
|
@accept_tuple_argument
|
|
409
404
|
def get_cell_list_contents(self, cell_list: Iterable[Coordinate]) -> list[Agent]:
|
|
410
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
411
|
-
in `cell_list`; cells with empty content are excluded.
|
|
405
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded.
|
|
412
406
|
|
|
413
407
|
Args:
|
|
414
408
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -441,8 +435,7 @@ class _Grid:
|
|
|
441
435
|
selection: str = "random",
|
|
442
436
|
handle_empty: str | None = None,
|
|
443
437
|
) -> None:
|
|
444
|
-
"""
|
|
445
|
-
Move an agent to one of the given positions.
|
|
438
|
+
"""Move an agent to one of the given positions.
|
|
446
439
|
|
|
447
440
|
Args:
|
|
448
441
|
agent: Agent object to move. Assumed to have its current location stored in a 'pos' tuple.
|
|
@@ -459,15 +452,21 @@ class _Grid:
|
|
|
459
452
|
elif selection == "closest":
|
|
460
453
|
current_pos = agent.pos
|
|
461
454
|
# Find the closest position without sorting all positions
|
|
462
|
-
|
|
455
|
+
# TODO: See if this method can be optimized further
|
|
456
|
+
closest_pos = []
|
|
463
457
|
min_distance = float("inf")
|
|
464
458
|
agent.random.shuffle(pos)
|
|
465
459
|
for p in pos:
|
|
466
460
|
distance = self._distance_squared(p, current_pos)
|
|
467
461
|
if distance < min_distance:
|
|
468
462
|
min_distance = distance
|
|
469
|
-
closest_pos
|
|
470
|
-
|
|
463
|
+
closest_pos.clear()
|
|
464
|
+
closest_pos.append(p)
|
|
465
|
+
elif distance == min_distance:
|
|
466
|
+
closest_pos.append(p)
|
|
467
|
+
|
|
468
|
+
chosen_pos = agent.random.choice(closest_pos)
|
|
469
|
+
|
|
471
470
|
else:
|
|
472
471
|
raise ValueError(
|
|
473
472
|
f"Invalid selection method {selection}. Choose 'random' or 'closest'."
|
|
@@ -488,9 +487,7 @@ class _Grid:
|
|
|
488
487
|
)
|
|
489
488
|
|
|
490
489
|
def _distance_squared(self, pos1: Coordinate, pos2: Coordinate) -> float:
|
|
491
|
-
"""
|
|
492
|
-
Calculate the squared Euclidean distance between two points for performance.
|
|
493
|
-
"""
|
|
490
|
+
"""Calculate the squared Euclidean distance between two points for performance."""
|
|
494
491
|
# Use squared Euclidean distance to avoid sqrt operation
|
|
495
492
|
dx, dy = abs(pos1[0] - pos2[0]), abs(pos1[1] - pos2[1])
|
|
496
493
|
if self.torus:
|
|
@@ -499,7 +496,7 @@ class _Grid:
|
|
|
499
496
|
return dx**2 + dy**2
|
|
500
497
|
|
|
501
498
|
def swap_pos(self, agent_a: Agent, agent_b: Agent) -> None:
|
|
502
|
-
"""Swap agents positions"""
|
|
499
|
+
"""Swap agents positions."""
|
|
503
500
|
agents_no_pos = []
|
|
504
501
|
if (pos_a := agent_a.pos) is None:
|
|
505
502
|
agents_no_pos.append(agent_a)
|
|
@@ -560,16 +557,16 @@ def is_single_argument_function(function):
|
|
|
560
557
|
)
|
|
561
558
|
|
|
562
559
|
|
|
563
|
-
def ufunc_requires_additional_input(ufunc):
|
|
560
|
+
def ufunc_requires_additional_input(ufunc): # noqa: D103
|
|
564
561
|
# NumPy ufuncs have a 'nargs' attribute indicating the number of input arguments
|
|
565
562
|
# For binary ufuncs (like np.add), nargs is 2
|
|
566
563
|
return ufunc.nargs > 1
|
|
567
564
|
|
|
568
565
|
|
|
569
566
|
class PropertyLayer:
|
|
570
|
-
"""
|
|
571
|
-
|
|
572
|
-
can store a value of a specified data type.
|
|
567
|
+
"""A class representing a layer of properties in a two-dimensional grid.
|
|
568
|
+
|
|
569
|
+
Each cell in the grid can store a value of a specified data type.
|
|
573
570
|
|
|
574
571
|
Attributes:
|
|
575
572
|
name (str): The name of the property layer.
|
|
@@ -577,13 +574,6 @@ class PropertyLayer:
|
|
|
577
574
|
height (int): The height of the grid (number of rows).
|
|
578
575
|
data (numpy.ndarray): A NumPy array representing the grid data.
|
|
579
576
|
|
|
580
|
-
Methods:
|
|
581
|
-
set_cell(position, value): Sets the value of a single cell.
|
|
582
|
-
set_cells(value, condition=None): Sets the values of multiple cells, optionally based on a condition.
|
|
583
|
-
modify_cell(position, operation, value): Modifies the value of a single cell using an operation.
|
|
584
|
-
modify_cells(operation, value, condition_function): Modifies the values of multiple cells using an operation.
|
|
585
|
-
select_cells(condition, return_list): Selects cells that meet a specified condition.
|
|
586
|
-
aggregate_property(operation): Performs an aggregate operation over all cells.
|
|
587
577
|
"""
|
|
588
578
|
|
|
589
579
|
propertylayer_experimental_warning_given = False
|
|
@@ -591,8 +581,7 @@ class PropertyLayer:
|
|
|
591
581
|
def __init__(
|
|
592
582
|
self, name: str, width: int, height: int, default_value, dtype=np.float64
|
|
593
583
|
):
|
|
594
|
-
"""
|
|
595
|
-
Initializes a new PropertyLayer instance.
|
|
584
|
+
"""Initializes a new PropertyLayer instance.
|
|
596
585
|
|
|
597
586
|
Args:
|
|
598
587
|
name (str): The name of the property layer.
|
|
@@ -643,14 +632,11 @@ class PropertyLayer:
|
|
|
643
632
|
self.__class__.propertylayer_experimental_warning_given = True
|
|
644
633
|
|
|
645
634
|
def set_cell(self, position: Coordinate, value):
|
|
646
|
-
"""
|
|
647
|
-
Update a single cell's value in-place.
|
|
648
|
-
"""
|
|
635
|
+
"""Update a single cell's value in-place."""
|
|
649
636
|
self.data[position] = value
|
|
650
637
|
|
|
651
638
|
def set_cells(self, value, condition=None):
|
|
652
|
-
"""
|
|
653
|
-
Perform a batch update either on the entire grid or conditionally, in-place.
|
|
639
|
+
"""Perform a batch update either on the entire grid or conditionally, in-place.
|
|
654
640
|
|
|
655
641
|
Args:
|
|
656
642
|
value: The value to be used for the update.
|
|
@@ -679,8 +665,8 @@ class PropertyLayer:
|
|
|
679
665
|
np.copyto(self.data, value, where=condition_result)
|
|
680
666
|
|
|
681
667
|
def modify_cell(self, position: Coordinate, operation, value=None):
|
|
682
|
-
"""
|
|
683
|
-
|
|
668
|
+
"""Modify a single cell using an operation, which can be a lambda function or a NumPy ufunc.
|
|
669
|
+
|
|
684
670
|
If a NumPy ufunc is used, an additional value should be provided.
|
|
685
671
|
|
|
686
672
|
Args:
|
|
@@ -701,8 +687,8 @@ class PropertyLayer:
|
|
|
701
687
|
raise ValueError("Invalid operation or missing value for NumPy ufunc.")
|
|
702
688
|
|
|
703
689
|
def modify_cells(self, operation, value=None, condition_function=None):
|
|
704
|
-
"""
|
|
705
|
-
|
|
690
|
+
"""Modify cells using an operation, which can be a lambda function or a NumPy ufunc.
|
|
691
|
+
|
|
706
692
|
If a NumPy ufunc is used, an additional value should be provided.
|
|
707
693
|
|
|
708
694
|
Args:
|
|
@@ -736,8 +722,7 @@ class PropertyLayer:
|
|
|
736
722
|
self.data = np.where(condition_array, modified_data, self.data)
|
|
737
723
|
|
|
738
724
|
def select_cells(self, condition, return_list=True):
|
|
739
|
-
"""
|
|
740
|
-
Find cells that meet a specified condition using NumPy's boolean indexing, in-place.
|
|
725
|
+
"""Find cells that meet a specified condition using NumPy's boolean indexing, in-place.
|
|
741
726
|
|
|
742
727
|
Args:
|
|
743
728
|
condition: A callable that returns a boolean array when applied to the data.
|
|
@@ -762,9 +747,9 @@ class PropertyLayer:
|
|
|
762
747
|
|
|
763
748
|
|
|
764
749
|
class _PropertyGrid(_Grid):
|
|
765
|
-
"""
|
|
766
|
-
|
|
767
|
-
the representation and manipulation of additional data layers on the grid. This class is
|
|
750
|
+
"""A private subclass of _Grid that supports the addition of property layers.
|
|
751
|
+
|
|
752
|
+
This enables the representation and manipulation of additional data layers on the grid. This class is
|
|
768
753
|
intended for internal use within the Mesa framework and is currently utilized by SingleGrid
|
|
769
754
|
and MultiGrid classes to provide enhanced grid functionality.
|
|
770
755
|
|
|
@@ -777,22 +762,15 @@ class _PropertyGrid(_Grid):
|
|
|
777
762
|
properties (dict): A dictionary mapping property layer names to PropertyLayer instances.
|
|
778
763
|
empty_mask (np.ndarray): A boolean array indicating empty cells on the grid.
|
|
779
764
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
specifies the cells to be considered (True) or ignored (False) in operations. Users can create custom masks,
|
|
790
|
-
including neighborhood masks, to apply specific conditions or constraints. Additionally, methods that deal with
|
|
791
|
-
cell selection or agent movement can return either a list of cell coordinates or a mask, based on the 'return_list'
|
|
792
|
-
parameter. This flexibility allows for more nuanced control and customization of grid operations, catering to a wide
|
|
793
|
-
range of modeling requirements and scenarios.
|
|
794
|
-
|
|
795
|
-
Note:
|
|
765
|
+
|
|
766
|
+
Several methods in this class accept a mask as an input, which is a NumPy ndarray of boolean values. This mask
|
|
767
|
+
specifies the cells to be considered (True) or ignored (False) in operations. Users can create custom masks,
|
|
768
|
+
including neighborhood masks, to apply specific conditions or constraints. Additionally, methods that deal with
|
|
769
|
+
cell selection or agent movement can return either a list of cell coordinates or a mask, based on the 'return_list'
|
|
770
|
+
parameter. This flexibility allows for more nuanced control and customization of grid operations, catering to a wide
|
|
771
|
+
range of modeling requirements and scenarios.
|
|
772
|
+
|
|
773
|
+
Notes:
|
|
796
774
|
This class is not intended for direct use in user models but is currently used by the SingleGrid and MultiGrid.
|
|
797
775
|
"""
|
|
798
776
|
|
|
@@ -803,8 +781,7 @@ class _PropertyGrid(_Grid):
|
|
|
803
781
|
torus: bool,
|
|
804
782
|
property_layers: None | PropertyLayer | list[PropertyLayer] = None,
|
|
805
783
|
):
|
|
806
|
-
"""
|
|
807
|
-
Initializes a new _PropertyGrid instance with specified dimensions and optional property layers.
|
|
784
|
+
"""Initializes a new _PropertyGrid instance with specified dimensions and optional property layers.
|
|
808
785
|
|
|
809
786
|
Args:
|
|
810
787
|
width (int): The width of the grid (number of columns).
|
|
@@ -833,15 +810,12 @@ class _PropertyGrid(_Grid):
|
|
|
833
810
|
|
|
834
811
|
@property
|
|
835
812
|
def empty_mask(self) -> np.ndarray:
|
|
836
|
-
"""
|
|
837
|
-
Returns a boolean mask indicating empty cells on the grid.
|
|
838
|
-
"""
|
|
813
|
+
"""Returns a boolean mask indicating empty cells on the grid."""
|
|
839
814
|
return self._empty_mask
|
|
840
815
|
|
|
841
816
|
# Add and remove properties to the grid
|
|
842
817
|
def add_property_layer(self, property_layer: PropertyLayer):
|
|
843
|
-
"""
|
|
844
|
-
Adds a new property layer to the grid.
|
|
818
|
+
"""Adds a new property layer to the grid.
|
|
845
819
|
|
|
846
820
|
Args:
|
|
847
821
|
property_layer (PropertyLayer): The PropertyLayer instance to be added to the grid.
|
|
@@ -859,8 +833,7 @@ class _PropertyGrid(_Grid):
|
|
|
859
833
|
self.properties[property_layer.name] = property_layer
|
|
860
834
|
|
|
861
835
|
def remove_property_layer(self, property_name: str):
|
|
862
|
-
"""
|
|
863
|
-
Removes a property layer from the grid by its name.
|
|
836
|
+
"""Removes a property layer from the grid by its name.
|
|
864
837
|
|
|
865
838
|
Args:
|
|
866
839
|
property_name (str): The name of the property layer to be removed.
|
|
@@ -875,8 +848,8 @@ class _PropertyGrid(_Grid):
|
|
|
875
848
|
def get_neighborhood_mask(
|
|
876
849
|
self, pos: Coordinate, moore: bool, include_center: bool, radius: int
|
|
877
850
|
) -> np.ndarray:
|
|
878
|
-
"""
|
|
879
|
-
|
|
851
|
+
"""Generate a boolean mask representing the neighborhood.
|
|
852
|
+
|
|
880
853
|
Helper method for select_cells_multi_properties() and move_agent_to_random_cell()
|
|
881
854
|
|
|
882
855
|
Args:
|
|
@@ -904,8 +877,7 @@ class _PropertyGrid(_Grid):
|
|
|
904
877
|
only_empty: bool = False,
|
|
905
878
|
return_list: bool = True,
|
|
906
879
|
) -> list[Coordinate] | np.ndarray:
|
|
907
|
-
"""
|
|
908
|
-
Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells.
|
|
880
|
+
"""Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells.
|
|
909
881
|
|
|
910
882
|
Args:
|
|
911
883
|
conditions (dict): A dictionary where keys are property names and values are callables that return a boolean when applied.
|
|
@@ -1058,7 +1030,7 @@ class MultiGrid(_PropertyGrid):
|
|
|
1058
1030
|
self._empty_mask[agent.pos] = False
|
|
1059
1031
|
agent.pos = None
|
|
1060
1032
|
|
|
1061
|
-
def iter_neighbors(
|
|
1033
|
+
def iter_neighbors( # noqa: D102
|
|
1062
1034
|
self,
|
|
1063
1035
|
pos: Coordinate,
|
|
1064
1036
|
moore: bool,
|
|
@@ -1073,8 +1045,9 @@ class MultiGrid(_PropertyGrid):
|
|
|
1073
1045
|
def iter_cell_list_contents(
|
|
1074
1046
|
self, cell_list: Iterable[Coordinate]
|
|
1075
1047
|
) -> Iterator[Agent]:
|
|
1076
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
1077
|
-
|
|
1048
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`.
|
|
1049
|
+
|
|
1050
|
+
Cells with empty content are excluded.
|
|
1078
1051
|
|
|
1079
1052
|
Args:
|
|
1080
1053
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -1112,9 +1085,9 @@ class _HexGrid:
|
|
|
1112
1085
|
def get_neighborhood(
|
|
1113
1086
|
self, pos: Coordinate, include_center: bool = False, radius: int = 1
|
|
1114
1087
|
) -> list[Coordinate]:
|
|
1115
|
-
"""Return a list of coordinates that are in the
|
|
1116
|
-
|
|
1117
|
-
for a HexGrid the parity of the x coordinate of the point is
|
|
1088
|
+
"""Return a list of coordinates that are in the neighborhood of a certain point.
|
|
1089
|
+
|
|
1090
|
+
To calculate the neighborhood for a HexGrid the parity of the x coordinate of the point is
|
|
1118
1091
|
important, the neighborhood can be sketched as:
|
|
1119
1092
|
|
|
1120
1093
|
Always: (0,-), (0,+)
|
|
@@ -1200,8 +1173,7 @@ class _HexGrid:
|
|
|
1200
1173
|
def iter_neighborhood(
|
|
1201
1174
|
self, pos: Coordinate, include_center: bool = False, radius: int = 1
|
|
1202
1175
|
) -> Iterator[Coordinate]:
|
|
1203
|
-
"""Return an iterator over cell coordinates that are in the
|
|
1204
|
-
neighborhood of a certain point.
|
|
1176
|
+
"""Return an iterator over cell coordinates that are in the neighborhood of a certain point.
|
|
1205
1177
|
|
|
1206
1178
|
Args:
|
|
1207
1179
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -1251,8 +1223,7 @@ class _HexGrid:
|
|
|
1251
1223
|
|
|
1252
1224
|
|
|
1253
1225
|
class HexSingleGrid(_HexGrid, SingleGrid):
|
|
1254
|
-
"""Hexagonal SingleGrid: a SingleGrid where neighbors are computed
|
|
1255
|
-
according to a hexagonal tiling of the grid.
|
|
1226
|
+
"""Hexagonal SingleGrid: a SingleGrid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1256
1227
|
|
|
1257
1228
|
Functions according to odd-q rules.
|
|
1258
1229
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
@@ -1268,8 +1239,7 @@ class HexSingleGrid(_HexGrid, SingleGrid):
|
|
|
1268
1239
|
|
|
1269
1240
|
|
|
1270
1241
|
class HexMultiGrid(_HexGrid, MultiGrid):
|
|
1271
|
-
"""Hexagonal MultiGrid: a MultiGrid where neighbors are computed
|
|
1272
|
-
according to a hexagonal tiling of the grid.
|
|
1242
|
+
"""Hexagonal MultiGrid: a MultiGrid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1273
1243
|
|
|
1274
1244
|
Functions according to odd-q rules.
|
|
1275
1245
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
@@ -1287,8 +1257,7 @@ class HexMultiGrid(_HexGrid, MultiGrid):
|
|
|
1287
1257
|
|
|
1288
1258
|
|
|
1289
1259
|
class HexGrid(HexSingleGrid):
|
|
1290
|
-
"""Hexagonal Grid: a Grid where neighbors are computed
|
|
1291
|
-
according to a hexagonal tiling of the grid.
|
|
1260
|
+
"""Hexagonal Grid: a Grid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1292
1261
|
|
|
1293
1262
|
Functions according to odd-q rules.
|
|
1294
1263
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
@@ -1299,6 +1268,13 @@ class HexGrid(HexSingleGrid):
|
|
|
1299
1268
|
"""
|
|
1300
1269
|
|
|
1301
1270
|
def __init__(self, width: int, height: int, torus: bool) -> None:
|
|
1271
|
+
"""Initializes a HexGrid, deprecated.
|
|
1272
|
+
|
|
1273
|
+
Args:
|
|
1274
|
+
width: the width of the grid
|
|
1275
|
+
height: the height of the grid
|
|
1276
|
+
torus: whether the grid wraps
|
|
1277
|
+
"""
|
|
1302
1278
|
super().__init__(width, height, torus)
|
|
1303
1279
|
warn(
|
|
1304
1280
|
(
|
|
@@ -1335,11 +1311,13 @@ class ContinuousSpace:
|
|
|
1335
1311
|
"""Create a new continuous space.
|
|
1336
1312
|
|
|
1337
1313
|
Args:
|
|
1338
|
-
x_max
|
|
1314
|
+
x_max: the maximum x-coordinate
|
|
1315
|
+
y_max: the maximum y-coordinate.
|
|
1339
1316
|
torus: Boolean for whether the edges loop around.
|
|
1340
|
-
x_min
|
|
1341
|
-
|
|
1342
|
-
|
|
1317
|
+
x_min: (default 0) If provided, set the minimum x -coordinate for the space. Below them, values loop to
|
|
1318
|
+
the other edge (if torus=True) or raise an exception.
|
|
1319
|
+
y_min: (default 0) If provided, set the minimum y -coordinate for the space. Below them, values loop to
|
|
1320
|
+
the other edge (if torus=True) or raise an exception.
|
|
1343
1321
|
"""
|
|
1344
1322
|
self.x_min = x_min
|
|
1345
1323
|
self.x_max = x_max
|
|
@@ -1442,11 +1420,13 @@ class ContinuousSpace:
|
|
|
1442
1420
|
self, pos_1: FloatCoordinate, pos_2: FloatCoordinate
|
|
1443
1421
|
) -> FloatCoordinate:
|
|
1444
1422
|
"""Get the heading vector between two points, accounting for toroidal space.
|
|
1423
|
+
|
|
1445
1424
|
It is possible to calculate the heading angle by applying the atan2 function to the
|
|
1446
1425
|
result.
|
|
1447
1426
|
|
|
1448
1427
|
Args:
|
|
1449
|
-
pos_1
|
|
1428
|
+
pos_1: Coordinate tuples for both points.
|
|
1429
|
+
pos_2: Coordinate tuples for both points.
|
|
1450
1430
|
"""
|
|
1451
1431
|
one = np.array(pos_1)
|
|
1452
1432
|
two = np.array(pos_2)
|
|
@@ -1472,7 +1452,8 @@ class ContinuousSpace:
|
|
|
1472
1452
|
"""Get the distance between two point, accounting for toroidal space.
|
|
1473
1453
|
|
|
1474
1454
|
Args:
|
|
1475
|
-
pos_1
|
|
1455
|
+
pos_1: Coordinate tuples for point1.
|
|
1456
|
+
pos_2: Coordinate tuples for point2.
|
|
1476
1457
|
"""
|
|
1477
1458
|
x1, y1 = pos_1
|
|
1478
1459
|
x2, y2 = pos_2
|
|
@@ -1519,7 +1500,7 @@ class NetworkGrid:
|
|
|
1519
1500
|
"""Create a new network.
|
|
1520
1501
|
|
|
1521
1502
|
Args:
|
|
1522
|
-
|
|
1503
|
+
g: a NetworkX graph instance.
|
|
1523
1504
|
"""
|
|
1524
1505
|
self.G = g
|
|
1525
1506
|
for node_id in self.G.nodes:
|
|
@@ -1539,7 +1520,16 @@ class NetworkGrid:
|
|
|
1539
1520
|
def get_neighborhood(
|
|
1540
1521
|
self, node_id: int, include_center: bool = False, radius: int = 1
|
|
1541
1522
|
) -> list[int]:
|
|
1542
|
-
"""Get all adjacent nodes within a certain radius
|
|
1523
|
+
"""Get all adjacent nodes within a certain radius.
|
|
1524
|
+
|
|
1525
|
+
Args:
|
|
1526
|
+
node_id: node id for which to get neighborhood
|
|
1527
|
+
include_center: boolean to include node itself or not
|
|
1528
|
+
radius: size of neighborhood
|
|
1529
|
+
|
|
1530
|
+
Returns:
|
|
1531
|
+
a list
|
|
1532
|
+
"""
|
|
1543
1533
|
if radius == 1:
|
|
1544
1534
|
neighborhood = list(self.G.neighbors(node_id))
|
|
1545
1535
|
if include_center:
|
|
@@ -1556,28 +1546,61 @@ class NetworkGrid:
|
|
|
1556
1546
|
def get_neighbors(
|
|
1557
1547
|
self, node_id: int, include_center: bool = False, radius: int = 1
|
|
1558
1548
|
) -> list[Agent]:
|
|
1559
|
-
"""Get all agents in adjacent nodes (within a certain radius).
|
|
1549
|
+
"""Get all agents in adjacent nodes (within a certain radius).
|
|
1550
|
+
|
|
1551
|
+
Args:
|
|
1552
|
+
node_id: node id for which to get neighbors
|
|
1553
|
+
include_center: whether to include node itself or not
|
|
1554
|
+
radius: size of neighborhood in which to find neighbors
|
|
1555
|
+
|
|
1556
|
+
Returns:
|
|
1557
|
+
list of agents in neighborhood.
|
|
1558
|
+
"""
|
|
1560
1559
|
neighborhood = self.get_neighborhood(node_id, include_center, radius)
|
|
1561
1560
|
return self.get_cell_list_contents(neighborhood)
|
|
1562
1561
|
|
|
1563
1562
|
def move_agent(self, agent: Agent, node_id: int) -> None:
|
|
1564
|
-
"""Move an agent from its current node to a new node.
|
|
1563
|
+
"""Move an agent from its current node to a new node.
|
|
1564
|
+
|
|
1565
|
+
Args:
|
|
1566
|
+
agent: agent instance
|
|
1567
|
+
node_id: id of node
|
|
1568
|
+
|
|
1569
|
+
"""
|
|
1565
1570
|
self.remove_agent(agent)
|
|
1566
1571
|
self.place_agent(agent, node_id)
|
|
1567
1572
|
|
|
1568
1573
|
def remove_agent(self, agent: Agent) -> None:
|
|
1569
|
-
"""Remove the agent from the network and set its pos attribute to None.
|
|
1574
|
+
"""Remove the agent from the network and set its pos attribute to None.
|
|
1575
|
+
|
|
1576
|
+
Args:
|
|
1577
|
+
agent: agent instance
|
|
1578
|
+
|
|
1579
|
+
"""
|
|
1570
1580
|
node_id = agent.pos
|
|
1571
1581
|
self.G.nodes[node_id]["agent"].remove(agent)
|
|
1572
1582
|
agent.pos = None
|
|
1573
1583
|
|
|
1574
1584
|
def is_cell_empty(self, node_id: int) -> bool:
|
|
1575
|
-
"""Returns a bool of the contents of a cell.
|
|
1585
|
+
"""Returns a bool of the contents of a cell.
|
|
1586
|
+
|
|
1587
|
+
Args:
|
|
1588
|
+
node_id: id of node
|
|
1589
|
+
|
|
1590
|
+
"""
|
|
1576
1591
|
return self.G.nodes[node_id]["agent"] == self.default_val()
|
|
1577
1592
|
|
|
1578
1593
|
def get_cell_list_contents(self, cell_list: list[int]) -> list[Agent]:
|
|
1579
|
-
"""Returns a list of the agents contained in the nodes identified
|
|
1580
|
-
|
|
1594
|
+
"""Returns a list of the agents contained in the nodes identified in `cell_list`.
|
|
1595
|
+
|
|
1596
|
+
Nodes with empty content are excluded.
|
|
1597
|
+
|
|
1598
|
+
Args:
|
|
1599
|
+
cell_list: list of cell ids.
|
|
1600
|
+
|
|
1601
|
+
Returns:
|
|
1602
|
+
list of the agents contained in the nodes identified in `cell_list`.
|
|
1603
|
+
|
|
1581
1604
|
"""
|
|
1582
1605
|
return list(self.iter_cell_list_contents(cell_list))
|
|
1583
1606
|
|
|
@@ -1586,8 +1609,16 @@ class NetworkGrid:
|
|
|
1586
1609
|
return self.get_cell_list_contents(self.G)
|
|
1587
1610
|
|
|
1588
1611
|
def iter_cell_list_contents(self, cell_list: list[int]) -> Iterator[Agent]:
|
|
1589
|
-
"""Returns an iterator of the agents contained in the nodes identified
|
|
1590
|
-
|
|
1612
|
+
"""Returns an iterator of the agents contained in the nodes identified in `cell_list`.
|
|
1613
|
+
|
|
1614
|
+
Nodes with empty content are excluded.
|
|
1615
|
+
|
|
1616
|
+
Args:
|
|
1617
|
+
cell_list: list of cell ids.
|
|
1618
|
+
|
|
1619
|
+
Returns:
|
|
1620
|
+
iterator of the agents contained in the nodes identified in `cell_list`.
|
|
1621
|
+
|
|
1591
1622
|
"""
|
|
1592
1623
|
return itertools.chain.from_iterable(
|
|
1593
1624
|
self.G.nodes[node_id]["agent"]
|