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
|