Skip to content

Snow

dissmodel.models.ca.snow.Snow

Bases: CellularAutomaton

Cellular automaton simulating snowfall and accumulation.

Snow falls from the top row and moves downward one cell per step. When a snowflake reaches the bottom or lands on top of another snowflake, it accumulates. New snowflakes appear at the top row with probability :attr:probability.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame with geometries and a state attribute. Must be created with dim=(n, n) so row/column indices are available via :func:~dissmodel.geo.parse_idx.

required
**kwargs Any

Extra keyword arguments forwarded to :class:~dissmodel.geo.CellularAutomaton.

{}
Notes

This model does not use a spatial neighborhood strategy — cell relationships are computed directly from the grid index format 'row-col' via :func:~dissmodel.geo.parse_idx. Therefore, :meth:execute is overridden to skip the neighborhood check that the base class enforces.

The number of simulation steps must be greater than the grid size for snow to have enough time to fall and accumulate. Snow stops falling dim steps before end_time to allow flakes already in motion to reach the ground.

A good rule of thumb: end_time > 2 * dim.

Examples:

>>> from dissmodel.geo import regular_grid
>>> from dissmodel.core import Environment
>>> gdf = regular_grid(dimension=(20, 20), resolution=1, attrs={"state": 0})
>>> env = Environment(end_time=50)  # steps must be greater than grid_size
>>> snow = Snow(gdf=gdf, dim=20)
Source code in dissmodel/models/ca/snow.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 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
 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
class Snow(CellularAutomaton):
    """
    Cellular automaton simulating snowfall and accumulation.

    Snow falls from the top row and moves downward one cell per step.
    When a snowflake reaches the bottom or lands on top of another
    snowflake, it accumulates. New snowflakes appear at the top row
    with probability :attr:`probability`.

    Parameters
    ----------
    gdf : geopandas.GeoDataFrame
        GeoDataFrame with geometries and a ``state`` attribute.
        Must be created with ``dim=(n, n)`` so row/column indices
        are available via :func:`~dissmodel.geo.parse_idx`.
    **kwargs :
        Extra keyword arguments forwarded to
        :class:`~dissmodel.geo.CellularAutomaton`.

    Notes
    -----
    This model does not use a spatial neighborhood strategy — cell
    relationships are computed directly from the grid index format
    ``'row-col'`` via :func:`~dissmodel.geo.parse_idx`.
    Therefore, :meth:`execute` is overridden to skip the neighborhood
    check that the base class enforces.

    The number of simulation steps must be greater than the grid size
    for snow to have enough time to fall and accumulate. Snow stops
    falling ``dim`` steps before ``end_time`` to allow flakes already
    in motion to reach the ground.

    A good rule of thumb: ``end_time > 2 * dim``.

    Examples
    --------
    >>> from dissmodel.geo import regular_grid
    >>> from dissmodel.core import Environment
    >>> gdf = regular_grid(dimension=(20, 20), resolution=1, attrs={"state": 0})
    >>> env = Environment(end_time=50)  # steps must be greater than grid_size
    >>> snow = Snow(gdf=gdf, dim=20)
    """

    #: Probability of a new snowflake appearing at the top row each step.
    probability: float

    def setup(self, probability: float = 0.02) -> None:
        """
        Configure the model.

        Parameters
        ----------
        probability : float, optional
            Probability of a new snowflake appearing at the top row
            each step, by default 0.02.

        Notes
        -----
        For visible accumulation, use ``end_time > 2 * dim`` when
        creating the :class:`~dissmodel.core.Environment`.
        """
        self.probability = probability

    def execute(self) -> None:
        """
        Execute one simulation step by applying :meth:`rule` to every cell.

        Overrides the base class implementation to skip the neighborhood
        check — this model computes cell relationships directly from
        grid indices via :func:`~dissmodel.geo.parse_idx`.
        """
        self.gdf[self.state_attr] = self.gdf.index.map(self.rule)

    def rule(self, idx: Any) -> int:
        """
        Apply the snow fall and accumulation rule to cell ``idx``.

        Parameters
        ----------
        idx : any
            Index of the cell being evaluated.

        Returns
        -------
        int
            New state for the cell:

            - Top row → ``SNOW`` with probability :attr:`probability`
              if empty and within the active snowfall window.
            - Snowy cell at bottom row → stays ``SNOW`` (accumulates).
            - Snowy cell with empty cell below → becomes ``EMPTY``
              (snow moves down).
            - Snowy cell with occupied cell below → stays ``SNOW``
              (accumulates).
            - Empty cell with snowy cell above → becomes ``SNOW``
              (snow arrives).
            - Otherwise → ``EMPTY``.

        Notes
        -----
        Snow stops falling ``dim`` steps before ``end_time`` to allow
        flakes already in motion to reach the ground before the
        simulation ends.
        """
        assert self.dim is not None, "dim must be set — pass dim=N when instantiating"

        cell = self.gdf.loc[idx]
        x, y = parse_idx(idx)
        t = self.env.now()

        # Top row — snowflakes appear here
        if y == self.dim - 1:
            if (
                cell.state == SnowState.EMPTY
                and t < (self.end_time - self.dim)
                and random.random() < self.probability
            ):
                return SnowState.SNOW
            return SnowState.EMPTY

        # Snow movement — check cell below
        below_idx = f"{y - 1}-{x}" if y - 1 >= 0 else None

        if cell.state == SnowState.SNOW:
            if y == 0:
                return SnowState.SNOW  # bottom row — accumulates
            if below_idx:
                below_state = self.gdf.loc[below_idx, "state"]
                if below_state == SnowState.EMPTY:
                    return SnowState.EMPTY  # snow moves down
                return SnowState.SNOW      # blocked — accumulates

        # Snow arrival — check cell above
        above_idx = f"{y + 1}-{x}" if y + 1 < self.dim else None
        if above_idx and self.gdf.loc[above_idx, "state"] == SnowState.SNOW:
            return SnowState.SNOW

        return SnowState.EMPTY

execute()

Execute one simulation step by applying :meth:rule to every cell.

Overrides the base class implementation to skip the neighborhood check — this model computes cell relationships directly from grid indices via :func:~dissmodel.geo.parse_idx.

Source code in dissmodel/models/ca/snow.py
88
89
90
91
92
93
94
95
96
def execute(self) -> None:
    """
    Execute one simulation step by applying :meth:`rule` to every cell.

    Overrides the base class implementation to skip the neighborhood
    check — this model computes cell relationships directly from
    grid indices via :func:`~dissmodel.geo.parse_idx`.
    """
    self.gdf[self.state_attr] = self.gdf.index.map(self.rule)

rule(idx)

Apply the snow fall and accumulation rule to cell idx.

Parameters:

Name Type Description Default
idx any

Index of the cell being evaluated.

required

Returns:

Type Description
int

New state for the cell:

  • Top row → SNOW with probability :attr:probability if empty and within the active snowfall window.
  • Snowy cell at bottom row → stays SNOW (accumulates).
  • Snowy cell with empty cell below → becomes EMPTY (snow moves down).
  • Snowy cell with occupied cell below → stays SNOW (accumulates).
  • Empty cell with snowy cell above → becomes SNOW (snow arrives).
  • Otherwise → EMPTY.
Notes

Snow stops falling dim steps before end_time to allow flakes already in motion to reach the ground before the simulation ends.

Source code in dissmodel/models/ca/snow.py
 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
def rule(self, idx: Any) -> int:
    """
    Apply the snow fall and accumulation rule to cell ``idx``.

    Parameters
    ----------
    idx : any
        Index of the cell being evaluated.

    Returns
    -------
    int
        New state for the cell:

        - Top row → ``SNOW`` with probability :attr:`probability`
          if empty and within the active snowfall window.
        - Snowy cell at bottom row → stays ``SNOW`` (accumulates).
        - Snowy cell with empty cell below → becomes ``EMPTY``
          (snow moves down).
        - Snowy cell with occupied cell below → stays ``SNOW``
          (accumulates).
        - Empty cell with snowy cell above → becomes ``SNOW``
          (snow arrives).
        - Otherwise → ``EMPTY``.

    Notes
    -----
    Snow stops falling ``dim`` steps before ``end_time`` to allow
    flakes already in motion to reach the ground before the
    simulation ends.
    """
    assert self.dim is not None, "dim must be set — pass dim=N when instantiating"

    cell = self.gdf.loc[idx]
    x, y = parse_idx(idx)
    t = self.env.now()

    # Top row — snowflakes appear here
    if y == self.dim - 1:
        if (
            cell.state == SnowState.EMPTY
            and t < (self.end_time - self.dim)
            and random.random() < self.probability
        ):
            return SnowState.SNOW
        return SnowState.EMPTY

    # Snow movement — check cell below
    below_idx = f"{y - 1}-{x}" if y - 1 >= 0 else None

    if cell.state == SnowState.SNOW:
        if y == 0:
            return SnowState.SNOW  # bottom row — accumulates
        if below_idx:
            below_state = self.gdf.loc[below_idx, "state"]
            if below_state == SnowState.EMPTY:
                return SnowState.EMPTY  # snow moves down
            return SnowState.SNOW      # blocked — accumulates

    # Snow arrival — check cell above
    above_idx = f"{y + 1}-{x}" if y + 1 < self.dim else None
    if above_idx and self.gdf.loc[above_idx, "state"] == SnowState.SNOW:
        return SnowState.SNOW

    return SnowState.EMPTY

setup(probability=0.02)

Configure the model.

Parameters:

Name Type Description Default
probability float

Probability of a new snowflake appearing at the top row each step, by default 0.02.

0.02
Notes

For visible accumulation, use end_time > 2 * dim when creating the :class:~dissmodel.core.Environment.

Source code in dissmodel/models/ca/snow.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def setup(self, probability: float = 0.02) -> None:
    """
    Configure the model.

    Parameters
    ----------
    probability : float, optional
        Probability of a new snowflake appearing at the top row
        each step, by default 0.02.

    Notes
    -----
    For visible accumulation, use ``end_time > 2 * dim`` when
    creating the :class:`~dissmodel.core.Environment`.
    """
    self.probability = probability

dissmodel.models.ca.snow.SnowState

Bases: IntEnum

Possible states for a cell in :class:Snow.

Attributes:

Name Type Description
EMPTY int

Empty cell, no snow.

SNOW int

Cell occupied by snow.

Source code in dissmodel/models/ca/snow.py
10
11
12
13
14
15
16
17
18
19
20
21
22
class SnowState(IntEnum):
    """
    Possible states for a cell in :class:`Snow`.

    Attributes
    ----------
    EMPTY : int
        Empty cell, no snow.
    SNOW : int
        Cell occupied by snow.
    """
    EMPTY = 0
    SNOW  = 1