Skip to content

Geo — Raster

Raster substrate classes and utilities backed by NumPy arrays.

dissmodel.geo.raster.backend

dissmodel/geo/raster/backend.py

Vectorized engine for cellular automata on raster grids (NumPy 2D arrays).

Responsibility

Provide generic spatial operations (shift, dilate, focal_sum, snapshot) with no domain knowledge — no land-use classes, no CRS, no I/O, no project-specific constants.

Domain models (FloodRasterModel, MangroveRasterModel, …) import RasterBackend and operate on named arrays stored in self.arrays.

Minimal example
from dissmodel.geo.raster.backend import RasterBackend, DIRS_MOORE

b = RasterBackend(shape=(100, 100))
b.set("state", np.zeros((100, 100), dtype=np.int8))

state    = b.get("state").copy()          # equivalent to cell.past[attr]
contact  = b.neighbor_contact(state == 1)
for dr, dc in DIRS_MOORE:
    neighbour = RasterBackend.shift2d(state, dr, dc)
    ...
b.arrays["state"] = new_state

DIRS_MOORE = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] module-attribute

DIRS_VON_NEUMANN = [(-1, 0), (0, -1), (0, 1), (1, 0)] module-attribute

RasterBackend

Storage and vectorized operations for 2D raster grids.

Replaces TerraME's forEachCell / forEachNeighbor with pure NumPy operations. The backend is shared across multiple models running in the same Environment — each model reads and writes named arrays every step.

Arrays

Stored in self.arrays as np.ndarray of shape (rows, cols). No names are reserved — domain models define their own ("uso", "alt", "solo", "state", "temperature", …).

Parameters:

Name Type Description Default
shape tuple[int, int]

Grid shape as (rows, cols).

required
nodata_value float | int | None

Sentinel value used to mark cells outside the study extent. When provided, nodata_mask derives the extent mask automatically, so RasterMap renders those cells as transparent without any extra configuration. Default: None.

None

Examples:

>>> b = RasterBackend(shape=(10, 10))
>>> b.set("state", np.zeros((10, 10), dtype=np.int8))
>>> b.get("state").shape
(10, 10)
>>> b = RasterBackend(shape=(10, 10), nodata_value=-1)
>>> b.nodata_mask   # True = valid cell, False = outside extent
Source code in dissmodel/geo/raster/backend.py
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
class RasterBackend:
    """
    Storage and vectorized operations for 2D raster grids.

    Replaces TerraME's ``forEachCell`` / ``forEachNeighbor`` with pure NumPy
    operations. The backend is shared across multiple models running in the
    same ``Environment`` — each model reads and writes named arrays every step.

    Arrays
    ------
    Stored in ``self.arrays`` as ``np.ndarray`` of shape ``(rows, cols)``.
    No names are reserved — domain models define their own
    (``"uso"``, ``"alt"``, ``"solo"``, ``"state"``, ``"temperature"``, …).

    Parameters
    ----------
    shape : tuple[int, int]
        Grid shape as ``(rows, cols)``.
    nodata_value : float | int | None
        Sentinel value used to mark cells outside the study extent.
        When provided, ``nodata_mask`` derives the extent mask automatically,
        so ``RasterMap`` renders those cells as transparent without any extra
        configuration. Default: ``None``.

    Examples
    --------
    >>> b = RasterBackend(shape=(10, 10))
    >>> b.set("state", np.zeros((10, 10), dtype=np.int8))
    >>> b.get("state").shape
    (10, 10)

    >>> b = RasterBackend(shape=(10, 10), nodata_value=-1)
    >>> b.nodata_mask   # True = valid cell, False = outside extent
    """

    def __init__(
        self,
        shape: tuple[int, int],
        nodata_value: float | int | None = None,
    ) -> None:
        self.shape        = shape
        self.arrays: dict[str, np.ndarray] = {}
        self.nodata_value = nodata_value   # sentinel for out-of-extent cells

    # ── extent mask ───────────────────────────────────────────────────────────

    @property
    def nodata_mask(self) -> np.ndarray | None:
        """
        Boolean mask: ``True`` = valid cell, ``False`` = outside extent / nodata.

        Derived in priority order:
        1. ``arrays["mask"]``  — explicit mask band (dissluc / coastal convention:
                                 non-zero = valid).
        2. ``nodata_value``    — applied over the first available array.
        3. ``None``            — no information; ``RasterMap`` skips auto-masking.

        Used by ``RasterMap`` (``auto_mask=True``) to render out-of-extent pixels
        as transparent without any per-project configuration.
        """
        if "mask" in self.arrays:
            return self.arrays["mask"] != 0

        if self.nodata_value is not None and self.arrays:
            first = next(iter(self.arrays.values()))
            return first != self.nodata_value

        return None

    # ── read / write ──────────────────────────────────────────────────────────

    def set(self, name: str, array: np.ndarray) -> None:
        """Store a copy of ``array`` under ``name``."""
        self.arrays[name] = np.asarray(array).copy()

    def get(self, name: str) -> np.ndarray:
        """
        Return a direct reference to the named array.

        Use ``.copy()`` to obtain a snapshot equivalent to TerraME's ``.past``.

        Raises
        ------
        KeyError
            If ``name`` is not in ``self.arrays``.
        """
        return self.arrays[name]

    def snapshot(self) -> dict[str, np.ndarray]:
        """
        Return a deep copy of all arrays — equivalent to TerraME's ``.past`` mechanism.

        Typical usage::

            past     = backend.snapshot()
            uso_past = past["uso"]   # state at the beginning of the step

        Returns
        -------
        dict[str, np.ndarray]
            Dictionary mapping array names to independent copies.
        """
        return {k: v.copy() for k, v in self.arrays.items()}

    # ── spatial operations ────────────────────────────────────────────────────

    @staticmethod
    def shift2d(arr: np.ndarray, dr: int, dc: int) -> np.ndarray:
        """
        Shift ``arr`` by ``(dr, dc)`` rows/columns without wrap-around.
        Edges are filled with zero.

        Parameters
        ----------
        arr : np.ndarray
        dr : int
            Row offset (positive = down, negative = up).
        dc : int
            Column offset (positive = right, negative = left).

        Returns
        -------
        np.ndarray
            Shifted array of the same shape as ``arr``.
        """
        rows, cols = arr.shape
        out = np.zeros_like(arr)
        rs  = slice(max(0, -dr), min(rows, rows - dr))
        rd  = slice(max(0,  dr), min(rows, rows + dr))
        cs_ = slice(max(0, -dc), min(cols, cols - dc))
        cd  = slice(max(0,  dc), min(cols, cols + dc))
        out[rd, cd] = arr[rs, cs_]
        return out

    @staticmethod
    def neighbor_contact(
        condition: np.ndarray,
        neighborhood: list[tuple[int, int]] | None = None,
    ) -> np.ndarray:
        """
        Return a boolean mask where each cell has at least one neighbour
        satisfying ``condition``.

        Parameters
        ----------
        condition : np.ndarray
        neighborhood : list[tuple[int, int]] | None
            ``None`` uses Moore neighbourhood via ``binary_dilation``.

        Returns
        -------
        np.ndarray
            Boolean array.
        """
        if neighborhood is None:
            return binary_dilation(condition.astype(bool), structure=np.ones((3, 3)))
        result = np.zeros_like(condition, dtype=bool)
        for dr, dc in neighborhood:
            result |= RasterBackend.shift2d(condition.astype(np.int8), dr, dc) > 0
        return result

    def focal_sum(
        self,
        name: str,
        neighborhood: list[tuple[int, int]] = DIRS_MOORE,
    ) -> np.ndarray:
        """
        Focal sum: for each cell, sum the values of ``name`` across its neighbours.
        The cell itself is not included.

        Parameters
        ----------
        name : str
        neighborhood : list[tuple[int, int]]
            Default: ``DIRS_MOORE``.

        Returns
        -------
        np.ndarray
        """
        arr    = self.arrays[name]
        result = np.zeros_like(arr, dtype=float)
        for dr, dc in neighborhood:
            result += self.shift2d(arr, dr, dc)
        return result

    def focal_sum_mask(
        self,
        mask: np.ndarray,
        neighborhood: list[tuple[int, int]] = DIRS_MOORE,
    ) -> np.ndarray:
        """
        Count neighbours where ``mask`` is ``True``.

        Parameters
        ----------
        mask : np.ndarray
        neighborhood : list[tuple[int, int]]
            Default: ``DIRS_MOORE``.

        Returns
        -------
        np.ndarray
            Integer array with per-cell neighbour counts.
        """
        result = np.zeros(self.shape, dtype=int)
        m = mask.astype(np.int8)
        for dr, dc in neighborhood:
            result += self.shift2d(m, dr, dc)
        return result

    # ── utilities ─────────────────────────────────────────────────────────────

    def __repr__(self) -> str:
        bands = ", ".join(
            f"{k}:{v.dtype}[{v.shape}]" for k, v in self.arrays.items()
        )
        return f"RasterBackend(shape={self.shape}, arrays=[{bands}])"

nodata_mask property

Boolean mask: True = valid cell, False = outside extent / nodata.

Derived in priority order: 1. arrays["mask"] — explicit mask band (dissluc / coastal convention: non-zero = valid). 2. nodata_value — applied over the first available array. 3. None — no information; RasterMap skips auto-masking.

Used by RasterMap (auto_mask=True) to render out-of-extent pixels as transparent without any per-project configuration.

focal_sum(name, neighborhood=DIRS_MOORE)

Focal sum: for each cell, sum the values of name across its neighbours. The cell itself is not included.

Parameters:

Name Type Description Default
name str
required
neighborhood list[tuple[int, int]]

Default: DIRS_MOORE.

DIRS_MOORE

Returns:

Type Description
ndarray
Source code in dissmodel/geo/raster/backend.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def focal_sum(
    self,
    name: str,
    neighborhood: list[tuple[int, int]] = DIRS_MOORE,
) -> np.ndarray:
    """
    Focal sum: for each cell, sum the values of ``name`` across its neighbours.
    The cell itself is not included.

    Parameters
    ----------
    name : str
    neighborhood : list[tuple[int, int]]
        Default: ``DIRS_MOORE``.

    Returns
    -------
    np.ndarray
    """
    arr    = self.arrays[name]
    result = np.zeros_like(arr, dtype=float)
    for dr, dc in neighborhood:
        result += self.shift2d(arr, dr, dc)
    return result

focal_sum_mask(mask, neighborhood=DIRS_MOORE)

Count neighbours where mask is True.

Parameters:

Name Type Description Default
mask ndarray
required
neighborhood list[tuple[int, int]]

Default: DIRS_MOORE.

DIRS_MOORE

Returns:

Type Description
ndarray

Integer array with per-cell neighbour counts.

Source code in dissmodel/geo/raster/backend.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def focal_sum_mask(
    self,
    mask: np.ndarray,
    neighborhood: list[tuple[int, int]] = DIRS_MOORE,
) -> np.ndarray:
    """
    Count neighbours where ``mask`` is ``True``.

    Parameters
    ----------
    mask : np.ndarray
    neighborhood : list[tuple[int, int]]
        Default: ``DIRS_MOORE``.

    Returns
    -------
    np.ndarray
        Integer array with per-cell neighbour counts.
    """
    result = np.zeros(self.shape, dtype=int)
    m = mask.astype(np.int8)
    for dr, dc in neighborhood:
        result += self.shift2d(m, dr, dc)
    return result

get(name)

Return a direct reference to the named array.

Use .copy() to obtain a snapshot equivalent to TerraME's .past.

Raises:

Type Description
KeyError

If name is not in self.arrays.

Source code in dissmodel/geo/raster/backend.py
123
124
125
126
127
128
129
130
131
132
133
134
def get(self, name: str) -> np.ndarray:
    """
    Return a direct reference to the named array.

    Use ``.copy()`` to obtain a snapshot equivalent to TerraME's ``.past``.

    Raises
    ------
    KeyError
        If ``name`` is not in ``self.arrays``.
    """
    return self.arrays[name]

neighbor_contact(condition, neighborhood=None) staticmethod

Return a boolean mask where each cell has at least one neighbour satisfying condition.

Parameters:

Name Type Description Default
condition ndarray
required
neighborhood list[tuple[int, int]] | None

None uses Moore neighbourhood via binary_dilation.

None

Returns:

Type Description
ndarray

Boolean array.

Source code in dissmodel/geo/raster/backend.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
@staticmethod
def neighbor_contact(
    condition: np.ndarray,
    neighborhood: list[tuple[int, int]] | None = None,
) -> np.ndarray:
    """
    Return a boolean mask where each cell has at least one neighbour
    satisfying ``condition``.

    Parameters
    ----------
    condition : np.ndarray
    neighborhood : list[tuple[int, int]] | None
        ``None`` uses Moore neighbourhood via ``binary_dilation``.

    Returns
    -------
    np.ndarray
        Boolean array.
    """
    if neighborhood is None:
        return binary_dilation(condition.astype(bool), structure=np.ones((3, 3)))
    result = np.zeros_like(condition, dtype=bool)
    for dr, dc in neighborhood:
        result |= RasterBackend.shift2d(condition.astype(np.int8), dr, dc) > 0
    return result

set(name, array)

Store a copy of array under name.

Source code in dissmodel/geo/raster/backend.py
119
120
121
def set(self, name: str, array: np.ndarray) -> None:
    """Store a copy of ``array`` under ``name``."""
    self.arrays[name] = np.asarray(array).copy()

shift2d(arr, dr, dc) staticmethod

Shift arr by (dr, dc) rows/columns without wrap-around. Edges are filled with zero.

Parameters:

Name Type Description Default
arr ndarray
required
dr int

Row offset (positive = down, negative = up).

required
dc int

Column offset (positive = right, negative = left).

required

Returns:

Type Description
ndarray

Shifted array of the same shape as arr.

Source code in dissmodel/geo/raster/backend.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@staticmethod
def shift2d(arr: np.ndarray, dr: int, dc: int) -> np.ndarray:
    """
    Shift ``arr`` by ``(dr, dc)`` rows/columns without wrap-around.
    Edges are filled with zero.

    Parameters
    ----------
    arr : np.ndarray
    dr : int
        Row offset (positive = down, negative = up).
    dc : int
        Column offset (positive = right, negative = left).

    Returns
    -------
    np.ndarray
        Shifted array of the same shape as ``arr``.
    """
    rows, cols = arr.shape
    out = np.zeros_like(arr)
    rs  = slice(max(0, -dr), min(rows, rows - dr))
    rd  = slice(max(0,  dr), min(rows, rows + dr))
    cs_ = slice(max(0, -dc), min(cols, cols - dc))
    cd  = slice(max(0,  dc), min(cols, cols + dc))
    out[rd, cd] = arr[rs, cs_]
    return out

snapshot()

Return a deep copy of all arrays — equivalent to TerraME's .past mechanism.

Typical usage::

past     = backend.snapshot()
uso_past = past["uso"]   # state at the beginning of the step

Returns:

Type Description
dict[str, ndarray]

Dictionary mapping array names to independent copies.

Source code in dissmodel/geo/raster/backend.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def snapshot(self) -> dict[str, np.ndarray]:
    """
    Return a deep copy of all arrays — equivalent to TerraME's ``.past`` mechanism.

    Typical usage::

        past     = backend.snapshot()
        uso_past = past["uso"]   # state at the beginning of the step

    Returns
    -------
    dict[str, np.ndarray]
        Dictionary mapping array names to independent copies.
    """
    return {k: v.copy() for k, v in self.arrays.items()}

dissmodel.geo.raster.raster_model

dissmodel/geo/raster/model.py

Base class for models backed by RasterBackend (NumPy 2D arrays).

Analogous to SpatialModel for the raster substrate — provides infrastructure without imposing a transition rule contract.

Class hierarchy
Model  (dissmodel.core)
  ├── SpatialModel     GeoDataFrame + Queen/Rook neighbourhood  (vector)
  └── RasterModel      RasterBackend + shift2d                  (raster)  ← this file
        ├── FloodRasterModel
        └── MangroveRasterModel
Usage
class MyRasterModel(RasterModel):
    def setup(self, backend, my_param=1.0):
        super().setup(backend)
        self.my_param = my_param

    def execute(self):
        uso = self.backend.get("uso").copy()
        ...
        self.backend.arrays["uso"] = new_uso

RasterModel

Bases: Model

Model backed by a RasterBackend.

Subclass of Model that adds raster infrastructure without imposing a transition rule contract. Can be subclassed directly by any model that operates on NumPy 2D arrays.

Parameters (setup)

backend : RasterBackend Backend shared across all models in the same Environment.

Attributes available in subclasses

backend : RasterBackend The shared array store. shape : tuple[int, int] Grid shape (rows, cols) — shortcut for self.backend.shape. shift : callable Shortcut for RasterBackend.shift2d (static method). dirs : list[tuple[int, int]] DIRS_MOORE — the 8 directions of the Moore neighbourhood.

Examples:

>>> class HeatDiffusion(RasterModel):
...     def execute(self):
...         temp = self.backend.get("temp").copy()
...         for dr, dc in self.dirs:
...             temp += 0.1 * self.shift(temp, dr, dc)
...         self.backend.arrays["temp"] = temp
Source code in dissmodel/geo/raster/raster_model.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class RasterModel(Model):
    """
    Model backed by a RasterBackend.

    Subclass of ``Model`` that adds raster infrastructure without imposing
    a transition rule contract. Can be subclassed directly by any model
    that operates on NumPy 2D arrays.

    Parameters (setup)
    ------------------
    backend : RasterBackend
        Backend shared across all models in the same ``Environment``.

    Attributes available in subclasses
    ------------------------------------
    backend : RasterBackend
        The shared array store.
    shape : tuple[int, int]
        Grid shape ``(rows, cols)`` — shortcut for ``self.backend.shape``.
    shift : callable
        Shortcut for ``RasterBackend.shift2d`` (static method).
    dirs : list[tuple[int, int]]
        ``DIRS_MOORE`` — the 8 directions of the Moore neighbourhood.

    Examples
    --------
    >>> class HeatDiffusion(RasterModel):
    ...     def execute(self):
    ...         temp = self.backend.get("temp").copy()
    ...         for dr, dc in self.dirs:
    ...             temp += 0.1 * self.shift(temp, dr, dc)
    ...         self.backend.arrays["temp"] = temp
    """

    def setup(self, backend: RasterBackend) -> None:
        self.backend = backend
        self.shape   = backend.shape
        self.shift   = RasterBackend.shift2d
        self.dirs    = DIRS_MOORE

dissmodel.geo.raster.cellular_automaton

dissmodel/geo/raster_cellular_automaton.py

Base class for cellular automata backed by RasterBackend (NumPy 2D arrays).

Analogous to CellularAutomaton (GeoDataFrame), but for the raster substrate.

Hierarchy
Model
  ├── SpatialModel
  │     └── CellularAutomaton       rule(idx) → value       (vector, pull)
  └── RasterModel
        └── RasterCellularAutomaton rule(arrays) → arrays   (raster, vectorized)
Why a different rule() contract

CellularAutomaton.rule(idx) returns a single value for one cell — it is called once per cell per step (O(n) Python calls). This is correct for the vector substrate where neighborhood lookup is the bottleneck.

For the raster substrate, the bottleneck is the Python loop itself. RasterCellularAutomaton.rule() receives the full snapshot of all arrays and returns a dict of updated arrays — one NumPy call covers the entire grid. This is the natural pattern for NumPy-based CA.

Comparison
# vector CA — rule called n times per step
class GameOfLife(CellularAutomaton):
    def rule(self, idx):
        alive = self.neighbor_values(idx, "state").sum()
        ...
        return new_state

# raster CA — rule called once per step
class GameOfLife(RasterCellularAutomaton):
    def rule(self, arrays):
        state = arrays["state"]
        alive = backend.focal_sum_mask(state == 1)
        ...
        return {"state": new_state}
Usage
from dissmodel.geo.raster_cellular_automaton import RasterCellularAutomaton
from dissmodel.geo.raster.backend import RasterBackend
from dissmodel.core import Environment
import numpy as np

class GameOfLife(RasterCellularAutomaton):
    def rule(self, arrays):
        state     = arrays["state"]
        neighbors = self.backend.focal_sum_mask(state == 1)
        born      = (state == 0) & (neighbors == 3)
        survive   = (state == 1) & np.isin(neighbors, [2, 3])
        return {"state": np.where(born | survive, 1, 0)}

b = RasterBackend(shape=(50, 50))
b.set("state", np.random.randint(0, 2, (50, 50)))

env = Environment(start_time=1, end_time=100)
GameOfLife(backend=b)
env.run()

RasterCellularAutomaton

Bases: RasterModel, ABC

Base class for NumPy-based cellular automata.

Extends :class:~dissmodel.geo.raster.model.RasterModel with a vectorized transition rule — rule() receives all arrays as a snapshot and returns a dict of updated arrays.

Parameters:

Name Type Description Default
backend RasterBackend

Shared backend with the simulation arrays.

required
state_attr str

Primary state array name, by default "state". Used only for introspection/logging — rule() can update any array.

required
**kwargs Any

Extra keyword arguments forwarded to RasterModel.

{}

Examples:

>>> class MyCA(RasterCellularAutomaton):
...     def rule(self, arrays):
...         state = arrays["state"]
...         # ... NumPy operations over full grid ...
...         return {"state": new_state}
Source code in dissmodel/geo/raster/cellular_automaton.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class RasterCellularAutomaton(RasterModel, ABC):
    """
    Base class for NumPy-based cellular automata.

    Extends :class:`~dissmodel.geo.raster.model.RasterModel` with a
    vectorized transition rule — ``rule()`` receives all arrays as a
    snapshot and returns a dict of updated arrays.

    Parameters
    ----------
    backend : RasterBackend
        Shared backend with the simulation arrays.
    state_attr : str, optional
        Primary state array name, by default ``"state"``.
        Used only for introspection/logging — rule() can update any array.
    **kwargs :
        Extra keyword arguments forwarded to RasterModel.

    Examples
    --------
    >>> class MyCA(RasterCellularAutomaton):
    ...     def rule(self, arrays):
    ...         state = arrays["state"]
    ...         # ... NumPy operations over full grid ...
    ...         return {"state": new_state}
    """

    def setup(
        self,
        backend:    RasterBackend,
        state_attr: str = "state"
    ) -> None:
        super().setup(backend)
        self.state_attr = state_attr

    @abstractmethod
    def rule(self, arrays: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
        """
        Vectorized transition rule applied to the full grid.

        Receives a snapshot of all arrays (equivalent to celula.past[] in
        TerraME) and returns a dict with the arrays to update.

        Only the arrays present in the returned dict are written back —
        arrays not returned are left unchanged.

        Parameters
        ----------
        arrays : dict[str, np.ndarray]
            Snapshot of backend arrays at the start of the step.
            Modifying these arrays does NOT affect the backend — they are
            copies (equivalent to .past semantics).

        Returns
        -------
        dict[str, np.ndarray]
            Dict mapping array name → new array. Partial updates allowed.

        Examples
        --------
        >>> def rule(self, arrays):
        ...     state     = arrays["state"]           # read from snapshot
        ...     neighbors = self.backend.focal_sum_mask(state == 1)
        ...     new_state = np.where(neighbors > 3, 0, state)
        ...     return {"state": new_state}           # write back
        """
        raise NotImplementedError("Subclasses must implement rule().")

    def execute(self) -> None:
        """
        Execute one simulation step by calling rule() once over the full grid.

        Takes a snapshot of all arrays (past state), passes it to rule(),
        and writes the returned arrays back to the backend.
        """
        past    = self.backend.snapshot()   # equivale a celula.past[]
        updates = self.rule(past)
        for name, arr in updates.items():
            self.backend.arrays[name] = arr

execute()

Execute one simulation step by calling rule() once over the full grid.

Takes a snapshot of all arrays (past state), passes it to rule(), and writes the returned arrays back to the backend.

Source code in dissmodel/geo/raster/cellular_automaton.py
145
146
147
148
149
150
151
152
153
154
155
def execute(self) -> None:
    """
    Execute one simulation step by calling rule() once over the full grid.

    Takes a snapshot of all arrays (past state), passes it to rule(),
    and writes the returned arrays back to the backend.
    """
    past    = self.backend.snapshot()   # equivale a celula.past[]
    updates = self.rule(past)
    for name, arr in updates.items():
        self.backend.arrays[name] = arr

rule(arrays) abstractmethod

Vectorized transition rule applied to the full grid.

Receives a snapshot of all arrays (equivalent to celula.past[] in TerraME) and returns a dict with the arrays to update.

Only the arrays present in the returned dict are written back — arrays not returned are left unchanged.

Parameters:

Name Type Description Default
arrays dict[str, ndarray]

Snapshot of backend arrays at the start of the step. Modifying these arrays does NOT affect the backend — they are copies (equivalent to .past semantics).

required

Returns:

Type Description
dict[str, ndarray]

Dict mapping array name → new array. Partial updates allowed.

Examples:

>>> def rule(self, arrays):
...     state     = arrays["state"]           # read from snapshot
...     neighbors = self.backend.focal_sum_mask(state == 1)
...     new_state = np.where(neighbors > 3, 0, state)
...     return {"state": new_state}           # write back
Source code in dissmodel/geo/raster/cellular_automaton.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@abstractmethod
def rule(self, arrays: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
    """
    Vectorized transition rule applied to the full grid.

    Receives a snapshot of all arrays (equivalent to celula.past[] in
    TerraME) and returns a dict with the arrays to update.

    Only the arrays present in the returned dict are written back —
    arrays not returned are left unchanged.

    Parameters
    ----------
    arrays : dict[str, np.ndarray]
        Snapshot of backend arrays at the start of the step.
        Modifying these arrays does NOT affect the backend — they are
        copies (equivalent to .past semantics).

    Returns
    -------
    dict[str, np.ndarray]
        Dict mapping array name → new array. Partial updates allowed.

    Examples
    --------
    >>> def rule(self, arrays):
    ...     state     = arrays["state"]           # read from snapshot
    ...     neighbors = self.backend.focal_sum_mask(state == 1)
    ...     new_state = np.where(neighbors > 3, 0, state)
    ...     return {"state": new_state}           # write back
    """
    raise NotImplementedError("Subclasses must implement rule().")

dissmodel.geo.raster.raster_grid

dissmodel/geo/raster_grid.py

Utilitário para criar RasterBackend sintético.

Análogo a vector_grid() (GeoDataFrame), mas para o substrato NumPy.

Uso
from dissmodel.geo.raster_grid import raster_grid
import numpy as np

# grade vazia com arrays zerados
b = raster_grid(rows=50, cols=50, attrs={"state": 0})

# grade com array inicial customizado
b = raster_grid(
    rows=50, cols=50,
    attrs={"state": np.random.randint(0, 2, (50, 50))}
)

raster_grid(rows, cols, attrs=None, dtype=None)

Create a RasterBackend with optional pre-filled arrays.

Analogous to :func:~dissmodel.geo.vector_grid for the raster substrate. Useful for tests, examples, and synthetic benchmarks.

Parameters:

Name Type Description Default
rows int

Number of rows in the grid.

required
cols int

Number of columns in the grid.

required
attrs dict

Mapping of array name → initial value. - scalar (int or float): fills the entire grid with that value. - np.ndarray of shape (rows, cols): used directly (a copy is stored). If not provided, an empty backend is returned.

None
dtype numpy dtype

Default dtype for scalar-initialized arrays. If None, inferred from the scalar type (int → np.int32, float → np.float64).

None

Returns:

Type Description
RasterBackend

Backend with shape (rows, cols) and the requested arrays.

Examples:

>>> b = raster_grid(10, 10, attrs={"state": 0})
>>> b.shape
(10, 10)
>>> b.get("state").shape
(10, 10)
>>> import numpy as np
>>> state = np.random.randint(0, 2, (10, 10))
>>> b = raster_grid(10, 10, attrs={"state": state})
Source code in dissmodel/geo/raster/raster_grid.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def raster_grid(
    rows:  int,
    cols:  int,
    attrs: dict[str, AttrValue] | None = None,
    dtype: np.dtype | None             = None,
) -> RasterBackend:
    """
    Create a RasterBackend with optional pre-filled arrays.

    Analogous to :func:`~dissmodel.geo.vector_grid` for the raster
    substrate. Useful for tests, examples, and synthetic benchmarks.

    Parameters
    ----------
    rows : int
        Number of rows in the grid.
    cols : int
        Number of columns in the grid.
    attrs : dict, optional
        Mapping of array name → initial value.
        - scalar (int or float): fills the entire grid with that value.
        - np.ndarray of shape (rows, cols): used directly (a copy is stored).
        If not provided, an empty backend is returned.
    dtype : numpy dtype, optional
        Default dtype for scalar-initialized arrays. If None, inferred
        from the scalar type (int → np.int32, float → np.float64).

    Returns
    -------
    RasterBackend
        Backend with shape (rows, cols) and the requested arrays.

    Examples
    --------
    >>> b = raster_grid(10, 10, attrs={"state": 0})
    >>> b.shape
    (10, 10)
    >>> b.get("state").shape
    (10, 10)

    >>> import numpy as np
    >>> state = np.random.randint(0, 2, (10, 10))
    >>> b = raster_grid(10, 10, attrs={"state": state})
    """
    b = RasterBackend(shape=(rows, cols))

    for name, value in (attrs or {}).items():
        if isinstance(value, np.ndarray):
            if value.shape != (rows, cols):
                raise ValueError(
                    f"Array '{name}' has shape {value.shape}, "
                    f"expected ({rows}, {cols})."
                )
            b.set(name, value)
        else:
            # scalar → infer dtype
            if dtype is not None:
                arr_dtype = dtype
            elif isinstance(value, float):
                arr_dtype = np.float64
            else:
                arr_dtype = np.int32
            b.set(name, np.full((rows, cols), value, dtype=arr_dtype))

    return b