15
15
"""
16
16
17
17
import numpy as np
18
- from typing import Set , Tuple , Dict , Optional
19
18
20
19
21
20
def create_random_grid (
22
- rows : int , columns : int , alive_probability : float , seed : Optional [ int ] = None
21
+ rows : int , columns : int , alive_probability : float , seed : int | None = None
23
22
) -> np .ndarray :
24
23
"""
25
24
Create initial grid with randomly distributed alive cells.
26
25
27
26
Args:
28
27
rows: Number of grid rows
29
- columns: Number of grid columns
28
+ columns: Number of grid columns
30
29
alive_probability: Probability (0.0-1.0) of each cell being initially alive
31
30
seed: Random seed for reproducibility
32
31
@@ -60,7 +59,9 @@ def create_random_grid(
60
59
raise ValueError ("alive_probability must be between 0.0 and 1.0" )
61
60
62
61
rng = np .random .default_rng (seed )
63
- alive_cells = (rng .random ((rows , columns )) < alive_probability ).astype (np .uint8 )
62
+ alive_cells = (rng .random ((rows , columns )) < alive_probability ).astype (
63
+ np .uint8
64
+ )
64
65
return alive_cells
65
66
66
67
@@ -88,11 +89,13 @@ def count_von_neumann_neighbors(
88
89
>>> counts = count_von_neumann_neighbors(mask, use_wraparound=False)
89
90
>>> int(counts[1, 1]) # center cell has 0 neighbors (all adjacent are 0)
90
91
0
91
- >>> int(counts[0, 1]) # top middle has 3 neighbors (down, left, right are 1 )
92
+ >>> int(counts[0, 1]) # top middle has 3 neighbors (down, left, right)
92
93
3
93
94
94
95
>>> mask_simple = np.array([[1, 1], [1, 0]], dtype=np.uint8)
95
- >>> counts_simple = count_von_neumann_neighbors(mask_simple, use_wraparound=False)
96
+ >>> counts_simple = count_von_neumann_neighbors(
97
+ ... mask_simple, use_wraparound=False
98
+ ... )
96
99
>>> int(counts_simple[0, 0]) # top-left has 2 neighbors (right and down)
97
100
2
98
101
@@ -115,23 +118,25 @@ def count_von_neumann_neighbors(
115
118
down_neighbors = np .roll (alive_mask , 1 , axis = 0 )
116
119
left_neighbors = np .roll (alive_mask , - 1 , axis = 1 )
117
120
right_neighbors = np .roll (alive_mask , 1 , axis = 1 )
118
- neighbor_counts = up_neighbors + down_neighbors + left_neighbors + right_neighbors
121
+ neighbor_counts = (
122
+ up_neighbors + down_neighbors + left_neighbors + right_neighbors
123
+ )
119
124
else :
120
125
# Manually count neighbors without wraparound
121
126
for r in range (rows ):
122
127
for c in range (cols ):
123
128
count = 0
124
129
# Check up
125
- if r > 0 and alive_mask [r - 1 , c ]:
130
+ if r > 0 and alive_mask [r - 1 , c ]:
126
131
count += 1
127
132
# Check down
128
- if r < rows - 1 and alive_mask [r + 1 , c ]:
133
+ if r < rows - 1 and alive_mask [r + 1 , c ]:
129
134
count += 1
130
135
# Check left
131
- if c > 0 and alive_mask [r , c - 1 ]:
136
+ if c > 0 and alive_mask [r , c - 1 ]:
132
137
count += 1
133
138
# Check right
134
- if c < cols - 1 and alive_mask [r , c + 1 ]:
139
+ if c < cols - 1 and alive_mask [r , c + 1 ]:
135
140
count += 1
136
141
neighbor_counts [r , c ] = count
137
142
@@ -140,8 +145,8 @@ def count_von_neumann_neighbors(
140
145
141
146
def apply_cellular_automaton_rules (
142
147
current_ages : np .ndarray ,
143
- birth_neighbor_counts : Set [int ],
144
- survival_neighbor_counts : Set [int ],
148
+ birth_neighbor_counts : set [int ],
149
+ survival_neighbor_counts : set [int ],
145
150
maximum_age : int = 5 ,
146
151
use_wraparound : bool = True ,
147
152
) -> np .ndarray :
@@ -168,22 +173,25 @@ def apply_cellular_automaton_rules(
168
173
Examples:
169
174
>>> ages = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.uint8)
170
175
>>> new_ages = apply_cellular_automaton_rules(
171
- ... ages, birth_neighbor_counts={2},
176
+ ... ages, birth_neighbor_counts={2},
172
177
... survival_neighbor_counts={2, 3}, use_wraparound=False
173
178
... )
174
- >>> bool(new_ages[0, 0] > 0) # corner should be born (2 neighbors: right and down)
179
+ >>> # corner should be born (2 neighbors: right and down)
180
+ >>> bool(new_ages[0, 0] > 0)
175
181
True
176
182
177
183
>>> # Test aging of dead cells
178
184
>>> dead_aging = np.array([[2, 0, 0]], dtype=np.uint8) # age 2, no survival
179
185
>>> result = apply_cellular_automaton_rules(
180
- ... dead_aging, birth_neighbor_counts=set(),
186
+ ... dead_aging, birth_neighbor_counts=set(),
181
187
... survival_neighbor_counts=set(), maximum_age=3
182
188
... )
183
189
>>> bool(result[0, 0] == 3) # should age from 2 to 3
184
190
True
185
191
186
- >>> apply_cellular_automaton_rules(np.array([1, 2]), {1}, {1}) # doctest: +IGNORE_EXCEPTION_DETAIL
192
+ >>> apply_cellular_automaton_rules(
193
+ ... np.array([1, 2]), {1}, {1}
194
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
187
195
Traceback (most recent call last):
188
196
ValueError: current_ages must be a 2D array
189
197
"""
@@ -198,8 +206,12 @@ def apply_cellular_automaton_rules(
198
206
)
199
207
200
208
# Determine which cells are born or survive
201
- birth_mask = (~ alive_cells_mask ) & np .isin (neighbor_counts , list (birth_neighbor_counts ))
202
- survival_mask = alive_cells_mask & np .isin (neighbor_counts , list (survival_neighbor_counts ))
209
+ birth_mask = (~ alive_cells_mask ) & np .isin (
210
+ neighbor_counts , list (birth_neighbor_counts )
211
+ )
212
+ survival_mask = alive_cells_mask & np .isin (
213
+ neighbor_counts , list (survival_neighbor_counts )
214
+ )
203
215
204
216
new_ages = current_ages .copy ()
205
217
@@ -223,11 +235,11 @@ def simulate_von_neumann_cellular_automaton(
223
235
grid_rows : int = 20 ,
224
236
grid_columns : int = 40 ,
225
237
initial_alive_probability : float = 0.25 ,
226
- birth_rules : Set [int ] = None ,
227
- survival_rules : Set [int ] = None ,
238
+ birth_rules : set [int ] | None = None ,
239
+ survival_rules : set [int ] | None = None ,
228
240
maximum_cell_age : int = 5 ,
229
241
generations : int = 100 ,
230
- random_seed : Optional [ int ] = None ,
242
+ random_seed : int | None = None ,
231
243
use_wraparound_edges : bool = True ,
232
244
) -> list [np .ndarray ]:
233
245
"""
@@ -262,7 +274,9 @@ def simulate_von_neumann_cellular_automaton(
262
274
>>> all(grid.shape == (5, 5) for grid in result)
263
275
True
264
276
265
- >>> simulate_von_neumann_cellular_automaton(generations=0) # doctest: +IGNORE_EXCEPTION_DETAIL
277
+ >>> simulate_von_neumann_cellular_automaton(
278
+ ... generations=0
279
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
266
280
Traceback (most recent call last):
267
281
ValueError: generations must be positive
268
282
"""
0 commit comments