Space#
modelly.space holds the discrete cell spaces (grids, networks, Voronoi meshes), continuous
space, property layers and the integer cell index. The names most models need are exported by
the package itself: from modelly.space import OrthogonalMooreGrid, CellAgent.
Module |
Contents |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Cells#
Cell takes its neighbourhood methods (neighborhood, get_neighborhood) from
modelly.space.cells.neighborhood; they are listed under Cell.
Cells are positions in space that can have properties and contain agents.
A cell represents a location that can: - Have properties (like temperature or resources) - Track and limit the agents it contains - Connect to neighboring cells - Provide neighborhood information
Cells form the foundation of the cell space system, enabling rich spatial environments where both location properties and agent behaviors matter. They’re useful for modeling things like varying terrain, infrastructure capacity, or environmental conditions.
- class Cell(coordinate: tuple[int, ...], position: ndarray | None = None, capacity: int | None = None, random: Random | None = None)[source]#
The cell represents a position in a discrete space.
- Variables:
coordinate (Coordinate) – the logical position(or index) of the cell in the discrete space
position (np.ndarray | None) – the physical position of the cell in the discrete space
agents (List[Agent]) – the agents occupying the cell
capacity (int) – the maximum number of agents that can simultaneously occupy the cell
random (Random) – the random number generator
Notes
Neighborhoods are cached per cell, keyed on
(radius, include_center). The cache belongs to the cell (not to the class), so discarding a space frees its cells, and changing one cell’s connections only invalidates that cell’s own neighborhoods.Initialise the cell.
- Parameters:
coordinate – coordinates of the cell
position – physical coordinates of the cell
capacity (int) – the capacity of the cell. If None, the capacity is infinite
random (Random) – the random number generator to use
- property position: ndarray#
Get the physical position of the cell.
- Returns:
Physical position of the cell
- Return type:
np.ndarray
- connect(other: Cell, key: tuple[int, ...] | None = None) None[source]#
Connects this cell to another cell.
- disconnect(other: Cell) None[source]#
Disconnects this cell from another cell.
- Parameters:
other (Cell) – other cell to remove from connections
- add_agent(agent: CellAgent) None[source]#
Adds an agent to the cell.
- Parameters:
agent (CellAgent) – agent to add to this Cell
- remove_agent(agent: CellAgent) None[source]#
Removes an agent from the cell.
- Parameters:
agent (CellAgent) – agent to remove from this cell
- get_neighborhood(radius: int = 1, include_center: bool = False) CellCollection[Cell]#
Returns a CellCollection of all neighboring cells for the given radius.
For getting the direct neighborhood (i.e., radius=1) you can also use the neighborhood property.
- property neighborhood: CellCollection[Cell]#
Returns the direct neighborhood of the cell.
This is equivalent to cell.get_neighborhood(radius=1)
Collection class for managing and querying groups of cells.
The CellCollection class provides a consistent interface for operating on multiple cells, supporting: - Filtering and selecting cells based on conditions - Random cell and agent selection - Access to contained agents - Group operations
This is useful for implementing area effects, zones, or any operation that needs to work with multiple cells as a unit. The collection handles efficient iteration and agent access across cells. The class is used throughout the cell space implementation to represent neighborhoods, selections, and other cell groupings.
- class CellCollection(cells: Mapping[T, list[CellAgent]] | Iterable[T], random: Random | None = None)[source]#
An immutable collection of cells.
- Variables:
Notes
With random=None the collection takes the generator of its first cell that has one, else the model generator of its first agent. A UserWarning is issued only when there is neither; pass random=self.random from a Model or Agent to avoid it.
Initialize a CellCollection.
- Parameters:
cells – cells to add to the collection
random – a seeded random number generator; None takes the cells’ or their agents’ model generator.
- property empties: CellCollection[T]#
The cells of this collection that hold no agent, as a new collection.
Reads the live occupancy lists directly, so it costs one truth test per cell rather than a property call per cell.
- select_random_cell(default=<object object>) T | None[source]#
Select a random cell from the collection.
- Parameters:
default – Value to return if the collection is empty. If not provided, raises LookupError.
- Returns:
A random cell, or the default value if provided and collection is empty.
- Return type:
T
- Raises:
LookupError – If collection is empty and no default is provided.
- select_random_agent(default=<object object>) CellAgent | None[source]#
Select a random agent from the collection.
- Parameters:
default – Value to return if the collection is empty. If not provided, raises LookupError.
- Returns:
A random agent, or the default value if provided and collection is empty.
- Return type:
- Raises:
LookupError – If collection is empty and no default is provided.
- select(filter_func: Callable[[T], bool] | None = None, at_most: int | float = inf)[source]#
Select cells based on filter function.
- Parameters:
filter_func – filter function
at_most – The maximum amount of cells to select. Defaults to infinity. - If an integer, at most the first number of matching cells is selected. - If a float between 0 and 1, at most that fraction of original number of cells
- Returns:
CellCollection
- Raises:
TypeError – If at_most is not an integer or float, or is a boolean.
ValueError – If at_most is a negative integer, or a float not in the range (0.0, 1.0].
Cell agents#
Agents that understand how to exist in and move through cell spaces.
Provides specialized agent classes that handle cell occupation, movement, and proper registration: - CellAgent: Mobile agents that can move between cells - FixedAgent: Immobile agents permanently fixed to cells - Grid2DMovingAgent: Agents with grid-specific movement capabilities
These classes ensure consistent agent-cell relationships and proper state management as agents move through the space. They can be used directly or as examples for creating custom cell-aware agents.
- class HasCellProtocol(*args, **kwargs)[source]#
Bases:
ProtocolProtocol for discrete space cell holders.
- class FixedCell[source]#
Bases:
HasCellMixin for agents that are fixed to a cell.
Once assigned to a cell, the agent cannot be moved to a different cell. The assignment is atomic: if cell.add_agent() raises (e.g. capacity reached), the agent’s _modelly_cell reference is left unchanged.
- class CellAgent(model: M, *args, **kwargs)[source]#
Bases:
Agent,HasCell,BasicMovementCell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces.
- Variables:
cell (Cell) – The cell the agent is currently in.
Create a new agent.
- Parameters:
model (Model) – The model instance in which the agent exists.
args – Passed on to super.
kwargs – Passed on to super.
Notes
to make proper use of python’s super, in each class remove the arguments and keyword arguments you need and pass on the rest to super
- class FixedAgent(model: M, *args, **kwargs)[source]#
-
A patch in a 2D grid.
Create a new agent.
- Parameters:
model (Model) – The model instance in which the agent exists.
args – Passed on to super.
kwargs – Passed on to super.
Notes
to make proper use of python’s super, in each class remove the arguments and keyword arguments you need and pass on the rest to super
- remove()[source]#
Remove the agent from the model.
- Raises:
AgentRemovedError – when the agent has no cell: removed already, or never placed.
- class Grid2DMovingAgent(model: M, *args, **kwargs)[source]#
Bases:
CellAgentMixin for moving agents in 2D grids.
Create a new agent.
- Parameters:
model (Model) – The model instance in which the agent exists.
args – Passed on to super.
kwargs – Passed on to super.
Notes
to make proper use of python’s super, in each class remove the arguments and keyword arguments you need and pass on the rest to super
- class HexGridMovingAgent(model: M, *args, **kwargs)[source]#
Bases:
CellAgentA specialized agent for moving on a HexGrid (Even-R / Pointy-Topped).
This agent correctly handles the alternating connectivity of the HexGrid defined in modelly.space.grid.grids, where the Row Index (y) determines parity.
Create a new agent.
- Parameters:
model (Model) – The model instance in which the agent exists.
args – Passed on to super.
kwargs – Passed on to super.
Notes
to make proper use of python’s super, in each class remove the arguments and keyword arguments you need and pass on the rest to super
Discrete spaces#
Abstract Base class for building cell-based spatial environments.
DiscreteSpace provides the core functionality needed by all cell-based spaces: - Cell creation and tracking - Agent-cell relationship management - Property Layer support - Random selection capabilities - Capacity management
This serves as the foundation for specific space implementations like grids and networks, ensuring consistent behavior and shared functionality across different space types. All concrete cell space implementations (grids, networks, etc.) inherit from this class.
- space_agents(space: DiscreteSpace) Iterator[CellAgent][source]#
The agents in the cells’ lists, then those a CellIndex tracks by row column.
- class DiscreteSpace(capacity: int | None = None, cell_klass: type[T] = <class 'modelly.space.cells.cell.Cell'>, random: Random | None = None)[source]#
Base class for all discrete spaces.
- Variables:
capacity (int) – The capacity of the cells in the discrete space
all_cells (CellCollection) – The cells composing the discrete space
random (Random) – The random number generator
cell_klass (Type) – the type of cell class
empties (CellCollection) – collection of all cells that are empty
property_layers (dict[str, np.ndarray]) – property_layer of the discrete space
Notes
A UserWarning is issued if random=None. You can resolve this warning by explicitly passing a random number generator. In most cases, this will be the seeded random number generator in the model. So, you would do random=self.random in a Model or Agent instance.
Instantiate a DiscreteSpace.
- Parameters:
capacity – capacity of cells
cell_klass – base class for all cells
random – random number generator
- property agents: AgentSet#
Return an AgentSet with the agents in the space, row-placed ones included.
- abstractmethod find_nearest_cell(position: ndarray) T[source]#
Find the cell at or nearest to the given position.
- add_cell(cell: T)[source]#
Add a cell to the space.
- Parameters:
cell – cell to add
Note
Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared correctly if you are adding or removing cells and connections at runtime.
Warning
Coordinate Collision: If a cell already exists at the specified coordinates (e.g., cell1), it will be overwritten silently by the new cell (cell2).
Ensure the target coordinates are vacant before calling this method.
- remove_cell(cell: T)[source]#
Remove a cell from the space.
Note
Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared correctly if you are adding or removing cells and connections at runtime.
- add_connection(cell1: T, cell2: T)[source]#
Add a connection between the two cells.
Note
Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared correctly if you are adding or removing cells and connections at runtime.
- remove_connection(cell1: T, cell2: T)[source]#
Remove a connection between the two cells.
Note
Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared correctly if you are adding or removing cells and connections at runtime.
- property all_cells#
Return all cells in space.
- property empties: CellCollection[T]#
Return all empty in spaces.
- select_random_empty_cell() T[source]#
Select a random empty cell.
Sparse spaces are answered by a few random draws. The first time the draws all fail (or when
_try_randomis False) the space builds an index of its empty cells, kept current bycell.emptyfrom then on, so every later call is O(1) regardless of how full the space is.- Raises:
ValueError – If the space has no empty cell.
Grids#
The base class shared by every grid, plus its empty-cell index.
Concrete connection patterns (Moore, Von Neumann, hexagonal) live in
modelly.space.grid.grids.
- class Grid(dimensions: ~collections.abc.Sequence[int], torus: bool = False, capacity: float | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.grid.base.T] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
HasPropertyLayers,DiscreteSpace[T]Base class for all grid classes.
- Variables:
Notes
width and height are accessible via property_layers, higher dimensions can be retrieved via dimensions
Initialise the grid class.
- Parameters:
dimensions – the dimensions of the space
torus – whether the space wraps
capacity – capacity of the grid cell
random – a random number generator
cell_klass – the base class to use for the cells
- get_neighborhood_mask(coordinate, include_center: bool = True, radius: int = 1) ndarray[source]#
Return a boolean mask shaped like
self.dimensionsfor a neighborhood.
- find_nearest_cell(position: ndarray) T[source]#
Find the cell containing the given position.
- Parameters:
position – Physical position [x, y]
- Returns:
The cell containing the position
- Return type:
- Raises:
ValueError – If position is outside grid bounds and not a torus
- property cells_with_capacity: CellCollection[T]#
Return all cells that have available capacity (i.e. are not full).
A cell is considered available if
not cell.is_full.This is meaningfully different from
empties:emptiesonly includes cells with zero agents. If a cell hascapacity=5and currently holds 3 agents it is not empty, but it is still available.cells_with_capacityis therefore the correct API for models where agents share cells up to a finite limit.For cells with
capacity=None(unlimited), every cell is always available regardless of how many agents it holds.- Returns:
All cells where
len(agents) < capacity(or all cells when capacity is None).- Return type:
Example:
# Place an agent in any non-full cell agent.move_to(grid.select_random_cell_with_capacity()) # Count how many cells still have room len(list(grid.cells_with_capacity))
- select_random_cell_with_capacity() Cell[source]#
Select a random cell that has remaining capacity.
- Raises:
IndexError – If every cell in the grid is at full capacity.
Example:
# Safe placement that respects capacity limits free_cell = grid.select_random_cell_with_capacity() agent.move_to(free_cell)
Grid-based cell space implementations with different connection patterns.
Provides several grid types for organizing cells: - OrthogonalMooreGrid: 8 neighbors in 2D, (3^n)-1 in nD - OrthogonalVonNeumannGrid: 4 neighbors in 2D, 2n in nD - HexGrid: 6 neighbors in hexagonal pattern (2D only)
Each grid type supports optional wrapping (torus) and cell capacity limits. Choose based on how movement and connectivity should work in your model - Moore for unrestricted movement, Von Neumann for orthogonal-only movement, or Hex for more uniform distances.
The shared machinery (cell creation, property layers, empty-cell selection,
pickling) lives in modelly.space.grid.base; Grid is
re-exported here so from modelly.space.grid.grids import Grid keeps working.
- class Grid(dimensions: ~collections.abc.Sequence[int], torus: bool = False, capacity: float | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.grid.base.T] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
HasPropertyLayers,DiscreteSpace[T]Base class for all grid classes.
- Variables:
Notes
width and height are accessible via property_layers, higher dimensions can be retrieved via dimensions
Initialise the grid class.
- Parameters:
dimensions – the dimensions of the space
torus – whether the space wraps
capacity – capacity of the grid cell
random – a random number generator
cell_klass – the base class to use for the cells
- get_neighborhood_mask(coordinate, include_center: bool = True, radius: int = 1) ndarray[source]#
Return a boolean mask shaped like
self.dimensionsfor a neighborhood.
- find_nearest_cell(position: ndarray) T[source]#
Find the cell containing the given position.
- Parameters:
position – Physical position [x, y]
- Returns:
The cell containing the position
- Return type:
- Raises:
ValueError – If position is outside grid bounds and not a torus
- property cells_with_capacity: CellCollection[T]#
Return all cells that have available capacity (i.e. are not full).
A cell is considered available if
not cell.is_full.This is meaningfully different from
empties:emptiesonly includes cells with zero agents. If a cell hascapacity=5and currently holds 3 agents it is not empty, but it is still available.cells_with_capacityis therefore the correct API for models where agents share cells up to a finite limit.For cells with
capacity=None(unlimited), every cell is always available regardless of how many agents it holds.- Returns:
All cells where
len(agents) < capacity(or all cells when capacity is None).- Return type:
Example:
# Place an agent in any non-full cell agent.move_to(grid.select_random_cell_with_capacity()) # Count how many cells still have room len(list(grid.cells_with_capacity))
- select_random_cell_with_capacity() Cell[source]#
Select a random cell that has remaining capacity.
- Raises:
IndexError – If every cell in the grid is at full capacity.
Example:
# Safe placement that respects capacity limits free_cell = grid.select_random_cell_with_capacity() agent.move_to(free_cell)
- class HexGrid(dimensions: ~collections.abc.Sequence[int], torus: bool = False, capacity: float | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.grid.grids.T] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
Grid[T]A Grid with hexagonal tilling of the space.
Functions according to even-r rules. See https://www.redblobgames.com/grids/hexagons/#neighbors-offset for more.
Note
When torus=True, both width and height must be even.
- Raises:
ValueError – If torus=True and either width or height is odd.
Initialize the hex grid.
- Parameters:
dimensions – the dimensions of the space
torus – whether the space wraps
capacity – capacity of the grid cell
random – a random number generator
cell_klass – the base class to use for the cells
- class OrthogonalMooreGrid(dimensions: ~collections.abc.Sequence[int], torus: bool = False, capacity: float | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.grid.base.T] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
Grid[T]Grid where cells are connected to their 8 neighbors.
Example for two dimensions:
directions = [ (-1, -1), (-1, 0), (-1, 1), ( 0, -1), ( 0, 1), ( 1, -1), ( 1, 0), ( 1, 1), ]
Initialise the grid class.
- Parameters:
dimensions – the dimensions of the space
torus – whether the space wraps
capacity – capacity of the grid cell
random – a random number generator
cell_klass – the base class to use for the cells
- class OrthogonalVonNeumannGrid(dimensions: ~collections.abc.Sequence[int], torus: bool = False, capacity: float | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.grid.base.T] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
Grid[T]Grid where cells are connected to their 4 neighbors.
Example for two dimensions:
directions = [ (0, -1), (-1, 0), ( 1, 0), (0, 1), ]
Initialise the grid class.
- Parameters:
dimensions – the dimensions of the space
torus – whether the space wraps
capacity – capacity of the grid cell
random – a random number generator
cell_klass – the base class to use for the cells
Property layers#
Grids take create_property_layer, add_property_layer and remove_property_layer from this
mixin.
Property layers: numpy arrays attached to a grid and exposed on its cells.
A property layer is one np.ndarray shaped like the grid. The grid exposes it
as an attribute (grid.sugar) and every cell of that grid gets an accessor
(cell.sugar) that reads and writes the array at the cell’s coordinate, so
vectorised updates on the array and per-cell reads stay in sync for free.
- class HasPropertyLayers[source]#
Mixin giving a grid named property layers backed by numpy arrays.
Expects the host to define
dimensions(the array shape),cell_klass(a class private to the grid, since accessors are installed on it),property_layersand_read_only_layers.- create_property_layer(name: str, default_value=0.0, dtype=<class 'float'>, read_only: bool = False) ndarray[source]#
Create a property layer array and attach it to cells.
Warning: Do not reassign grid.name directly — this will detach it from property_layers and break cell-level access. Use in-place operations.
grid.sugar[:] = 0 # correct grid.sugar = array # wrong — breaks cell access
- add_property_layer(name: str, array: ndarray, read_only: bool = False) None[source]#
Attach an existing array as a property layer. Shape must match self.dimensions.
Warning: Do not reassign grid.name directly — this will detach it from property_layers and break cell-level access. Use in-place operations.
grid.sugar[:] = 0 # correct grid.sugar = array # wrong — breaks cell access
Network#
Network-based cell space using arbitrary connection patterns.
Creates spaces where cells connect based on network relationships rather than spatial proximity. Built on NetworkX graphs, this enables: - Arbitrary connectivity patterns between cells - Graph-based neighborhood definitions - Logical rather than physical distances - Dynamic connectivity changes - Integration with NetworkX’s graph algorithms
Useful for modeling systems like social networks, transportation systems, or any environment where connectivity matters more than physical location.
- class Network(G: Any, capacity: int | None = None, random: Random | None = None, cell_klass: type[Cell] = <class 'modelly.space.cells.cell.Cell'>, layout: Mapping | Callable | None = None)[source]#
Bases:
DiscreteSpace[Cell]A networked discrete space.
A Networked grid.
- Parameters:
G – a NetworkX Graph instance.
capacity (int) – the capacity of the cell
random (Random) – a random number generator
cell_klass (type[Cell]) – The base Cell class to use in the Network
layout – A dictionary mapping node IDs to physical positions (x, y), or a callable that generates them (e.g. nx.spring_layout). It defaults to nx.circular_layout This ensures all nodes possess physical (x, y) positions for visualization and spatial queries without introducing performance bottlenecks on large graphs
- find_nearest_cell(position: ndarray) Cell[source]#
Find the network node nearest to the given position.
Only works for spatial networks (networks with node positions).
Note
The KD-Tree index is rebuilt lazily. If cells were added or removed since the previous spatial query, this method rebuilds the KD-Tree before performing the nearest-neighbor lookup.
- Parameters:
position – Physical position [x, y]
- Returns:
The node closest to the position
- Return type:
- Raises:
ValueError – If network is not spatial
Voronoi#
Cell spaces based on Voronoi tessellation around seed points.
Creates irregular spatial divisions by building cells around seed points, where each cell contains the area closer to its seed than any other. Features: - Organic-looking spaces from point sets - Automatic neighbor determination - Area-based cell capacities - Natural regional divisions
Useful for models requiring irregular but mathematically meaningful spatial divisions, like territories, service areas, or natural regions.
- class VoronoiGrid(centroids_coordinates: ~collections.abc.Sequence[~collections.abc.Sequence[float]], capacity: int | ~collections.abc.Callable | None = None, random: ~random.Random | None = None, cell_klass: type[~modelly.space.cells.cell.Cell] = <class 'modelly.space.cells.cell.Cell'>)[source]#
Bases:
DiscreteSpaceVoronoi meshed GridSpace.
A Voronoi Tessellation Grid.
Given a set of points, this class creates a grid where a cell is centered in each point, its neighbors are given by Voronoi Tessellation cells neighbors and the capacity by the polygon area.
- Parameters:
centroids_coordinates – coordinates of centroids to build the tessellation space
capacity (int | Callable) – capacity of the cells in the discrete space, or a callable computing the (int) capacity of a cell from its (float) polygon area
random (Random) – random number generator
A 2D Delaunay triangulation (Bowyer-Watson), the basis of the Voronoi grid.
- class Delaunay(center: tuple = (0, 0), radius: int = 9999)[source]#
Class to compute a Delaunay triangulation in 2D.
Init and create a new frame to contain the triangulation.
- Parameters:
center – Optional position for the center of the frame. Default (0,0)
radius – Optional distance from corners to the center.
Integer cells: index, occupancy and row-placed agents#
Integer rows for the cells of a discrete space, and sparse operators over them.
Every discrete space (grid, network, Voronoi mesh) is a set of cells joined by
connections. Numbering the cells gives each one a row, and the connections
become a sparse adjacency matrix. Whole-population questions then cost one
numpy call each instead of one Python loop per agent:
counts(rows): how many agents, or how much of a quantity, sit in each cell;neighbor_sum(values): for every cell, the sum ofvaluesover its neighbours;random_neighbor(rows): one uniformly chosen neighbour for every row.
Agents that declare a Column for their cell row (row = Column(np.intp),
kept current with self.row = cell.index when they move) can combine these
with ColumnTable.draw into a vectorised step phase. See
docs/vectorised_phases.md for the equivalence arguments.
- class CellIndex(space: DiscreteSpace)[source]#
Rows for the cells of
spaceand the sparse adjacency between them.Building the index writes
cell.indexon every cell, in the order ofspace.all_cells. For grids that order is the C order of the coordinates, so a per-cell vector reshaped to the grid’s dimensions is a property layer.Number the cells of
space; the adjacency is built on first use.- track(model: Model, agent_class: type, column: str = 'row') None[source]#
Declare that
agent_classkeeps its cell as a rowcolumn.Such agents are in no cell’s agent list; tracking them makes the space list them (
space.agents) and lets the visualisation place them atindex.cells[agent.<column>].
- counts(rows: ndarray, weights: ndarray | None = None) ndarray[source]#
Per-cell total of
weights(or of ones) over the agents sitting inrows.
- property adjacency: csr_matrix#
The cell adjacency as a CSR matrix with one entry per connection.
- neighbor_sum(values: ndarray) ndarray[source]#
For every cell, the sum of
valuesover its neighbouring cells.
Who sits in which cell, from a row column, without per-cell agent lists.
Agents that keep their cell as a Column of rows (see CellIndex) move by
assigning the column; nothing else has to be updated. When a step needs to
know who shares a cell, Occupancy sorts the rows once (O(n log n)) and
answers per-cell questions from the sorted order. claim is the vectorised
form of “each claimant takes one of the members of its cell, if any is left”:
it has the law of the sequential version in which the claimants act in a
uniformly random order (see docs/vectorised_phases.md).
- class Occupancy(rows: ndarray, n_cells: int)[source]#
The agents of a table grouped by cell row, rebuilt from the row column.
Sort the agents (their table indices) by the cell row they occupy.
- claim(occupancy: Occupancy, cells: ndarray, rng: Generator) ndarray[source]#
For each claimant, one distinct member of its cell, or
NO_MATCH.cells[k]is the cell of claimantk. The matching has the law of the sequential rule “claimants act in a uniformly random order and each takes a uniformly random member of its cell not yet taken”: every cell ends up with min(claimants, members) matches, a uniform random subset of its members is taken and a uniform random subset of its claimants is served. Each round, every unserved claimant draws a member of its cell; draws of already taken members are rejected, collisions go to a random priority, and the losers draw again while their cell still has a free member.
Continuous space#
ContinuousSpace takes its neighbour queries (get_agents_in_radius, neighbors_within,
get_k_nearest_agents) from modelly.space.continuous.queries; they are listed under
ContinuousSpace.
A Continuous Space class.
- class ContinuousSpace(dimensions: ArrayLike, torus: bool = False, random: Random | None = None, n_agents: int = 100)[source]#
Continuous space where each agent can have an arbitrary position.
Create a new continuous space.
- Parameters:
dimensions – a numpy array like object where each row specifies the minimum and maximum value of that dimension.
torus – boolean for whether the space wraps around or not
random – a seeded stdlib random.Random instance
n_agents – the expected number of agents in the space
Internally, a numpy array is used to store the positions of all agents. This is resized if needed, but you can control the initial size explicitly by passing n_agents.
- calculate_difference_vector(point: ndarray, agents=None) ndarray[source]#
Calculate the difference vector between the point and all agenents.
- Parameters:
point – the point to calculate the difference vector for
agents – the agents to calculate the difference vector of point with. By default, all agents are considered.
- calculate_distances(point: ArrayLike, agents: Iterable[Agent] | None = None, **kwargs) tuple[ndarray, list][source]#
Calculate the distance between the point and all agents.
- Parameters:
point – the point to calculate the difference vector for
agents – the agents to calculate the difference vector of point with. By default, all agents are considered.
kwargs – any additional keyword arguments are passed to scipy’s cdist, which is used only if torus is False. This allows for non-Euclidian distance measures.
- get_agents_in_radius(point: ArrayLike, radius: float | int = 1) tuple[list, ndarray]#
Return the agents and their distances within a radius for the point.
- get_k_nearest_agents(point: ArrayLike, k: int = 1) tuple[list, ndarray]#
Return the k nearest agents and their distances to the point.
Notes
This method returns exactly k agents, ignoring ties. In case of ties, the earlier an agent is inserted the higher it will rank.
If fewer than k agents are present in the space, all agents are returned and a UserWarning is emitted to indicate that the requested k could not be satisfied. If the space is empty or k <= 0, an empty result is returned without a warning.
- neighbors_within(radius: float | int) dict[Agent, list[Agent]]#
Return, for every agent, the other agents within
radiusof it.One KD-tree is built from the positions as they are now and queried for the whole population in a single call, which is O(n log n) instead of the O(n²) of asking each agent in turn. On a torus the tree is periodic, so neighbours across the wrap are found. Use it at the start of a step for a simultaneous update: every agent then sees the same snapshot of positions.
The result is cached on the positions’ content, so asking again before any agent moves costs a hash of the position array instead of a new tree.
- Returns:
dict mapping each agent to a list of its neighbours (itself excluded).
Continuous space agents.
- class HasPositionProtocol(*args, **kwargs)[source]#
Bases:
ProtocolProtocol for continuous space position holders.
- class ContinuousSpaceAgent(space: ContinuousSpace, model)[source]#
Bases:
AgentA continuous space agent.
- Variables:
space (ContinuousSpace) – the continuous space in which the agent is located
position (np.ndarray) – the position of the agent
Initialize a continuous space agent.
- Parameters:
space – the continuous space in which the agent is located
model – the model to which the agent belongs
- property position: ndarray#
Position of the agent.
- remove() None[source]#
Remove and delete the agent from the model and continuous space.
- Raises:
AgentRemovedError – when the agent was already removed.
Whole-population operations on a continuous space.
neighbors_within on the space answers “who is near whom” with one KD-tree.
The two helpers here complete the vectorised step: pairs_within returns the
same neighbour relation as index pairs, ready for np.add.at or bincount
aggregation, and move_all writes every agent’s new position in one call.
- pairs_within(space: ContinuousSpace, radius: float) tuple[np.ndarray, np.ndarray][source]#
Every unordered pair of agents within
radius, as two row arrays(i, j).Rows index
space.agent_positionsandspace.active_agents;i < jand each pair appears once. On a torus the tree is periodic.
- difference_vectors(space: ContinuousSpace, i: np.ndarray, j: np.ndarray) np.ndarray[source]#
The vector from agent
ito agentjfor each pair, torus-corrected.
- move_all(space: ContinuousSpace, positions: np.ndarray) None[source]#
Set every agent’s position at once; wraps on a torus, clips otherwise.