Skip to content

Commit 2ab81bf

Browse files
committed
Create base CA class
See prompts.md for prompt and output
1 parent 7a4f1ba commit 2ab81bf

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__/
2+
results/

CA.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from typing import Tuple, Dict, Any, Optional
2+
import numpy as np
3+
4+
class CA:
5+
"""Cellular Automaton container.
6+
7+
Attributes
8+
- `grid` (np.ndarray): 2D integer array with cell states (0 = empty).
9+
- `neighborhood` (str): either "neumann" or "moore".
10+
- `generator` (np.random.Generator): RNG used for all random operations.
11+
- `params` (dict): user-provided parameters.
12+
"""
13+
14+
def __init__(
15+
self,
16+
rows: int,
17+
cols: int,
18+
densities: Tuple[float, ...],
19+
neighborhood: str = "neumann",
20+
params: Optional[Dict[str, Any]] = None,
21+
seed: Optional[int] = None,
22+
) -> None:
23+
"""Initialize the CA.
24+
25+
Args:
26+
- `rows` (int): number of rows (> 0).
27+
- `cols` (int): number of columns (> 0).
28+
- `densities` (tuple of float): density fraction for each species (each >= 0).
29+
- `neighborhood` (str): either "neumann" or "moore".
30+
- `params` (dict, optional): additional parameters to store.
31+
- `seed` (int or None): seed for the RNG.
32+
33+
Returns:
34+
- None
35+
36+
Raises:
37+
- AssertionError if arguments are invalid.
38+
"""
39+
assert isinstance(rows, int) and rows > 0, "`rows` must be a positive int"
40+
assert isinstance(cols, int) and cols > 0, "`cols` must be a positive int"
41+
assert isinstance(densities, tuple) or isinstance(densities, list), "`densities` must be a tuple or list"
42+
densities = tuple(float(d) for d in densities)
43+
assert all(d >= 0 for d in densities), "all densities must be non-negative"
44+
assert sum(densities) <= 1.0 + 1e-12, "sum of densities must not exceed 1"
45+
assert neighborhood in ("neumann", "moore"), "neighborhood must be 'neumann' or 'moore'"
46+
47+
self.params: Dict[str, Any] = dict(params) if params is not None else {}
48+
self.generator: np.random.Generator = np.random.default_rng(seed)
49+
self.neighborhood: str = neighborhood
50+
51+
self.rows = rows
52+
self.cols = cols
53+
self.grid: np.ndarray = np.zeros((rows, cols), dtype=int)
54+
55+
total_cells = rows * cols
56+
57+
# Fill grid for each species (states 1..N) according to densities.
58+
# Use floor to avoid over-allocation; do not overwrite non-zero cells.
59+
for i, d in enumerate(densities):
60+
if d <= 0:
61+
continue
62+
desired = int(np.floor(total_cells * d))
63+
if desired <= 0:
64+
continue
65+
66+
# available positions (flat indices)
67+
available = np.flatnonzero(self.grid.ravel() == 0)
68+
if len(available) == 0:
69+
break
70+
71+
take = min(desired, len(available))
72+
chosen = self.generator.choice(available, size=take, replace=False)
73+
self.grid.ravel()[chosen] = i + 1
74+
75+
# store number of species expected based on densities length
76+
self._n_species = len(densities)
77+
78+
def count_neighbors(self) -> Tuple[np.ndarray, ...]:
79+
"""Count neighbors for each non-zero state.
80+
81+
Uses periodic boundary conditions and the neighborhood specified in the instance.
82+
83+
Returns:
84+
- Tuple of np.ndarray: each array has shape `(rows, cols)` and contains
85+
the count of neighbors of state k (for k = 1..N) at each cell.
86+
"""
87+
neighbors = []
88+
89+
if self.neighborhood == "moore":
90+
offsets = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
91+
else: # neumann
92+
offsets = [(-1, 0), (1, 0), (0, -1), (0, 1)]
93+
94+
for state in range(1, self._n_species + 1):
95+
mask = (self.grid == state).astype(int)
96+
count = np.zeros_like(self.grid, dtype=int)
97+
for dr, dc in offsets:
98+
shifted = np.roll(np.roll(mask, dr, axis=0), dc, axis=1)
99+
count += shifted
100+
neighbors.append(count)
101+
102+
return tuple(neighbors)
103+
104+
def update(self) -> None:
105+
"""Perform a single update of the CA.
106+
107+
This method is intentionally left as a stub; implement specific rule
108+
logic by overriding or editing this method.
109+
110+
Returns:
111+
- None
112+
"""
113+
pass
114+
115+
def run(self, steps: int) -> None:
116+
"""Run the CA for a number of iterations.
117+
118+
Args:
119+
- `steps` (int): number of iterations to execute (>= 0).
120+
121+
Returns:
122+
- None
123+
"""
124+
assert isinstance(steps, int) and steps >= 0, "`steps` must be a non-negative int"
125+
for _ in range(steps):
126+
self.update()

prompts.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Create base CA class
2+
Create a cellular automaton class named CA with an init function, a count neighbors function, an update function, and a run. The CA should consist of a numpy array called grid, a string containing the neighborhood type, and a numpy random number generator called generator. Use this generator for all random number generation inside the class. The CA class should also contain a dictionary called params.
3+
4+
The init function should take arguments for the grid size (rows, columns, both ints), the initial density of each species (a tuple of floats), the neighborhood type ("neumann" or "moore"), the parameters in the form of a dictionary, and the seed for the generator. It should assign the parameters dictionary to the params variable, create the generator object and assign it to the generator variable, as well as create the 2D array of zeros based on the grid size and assign it to the grid variable. This grid should then be filled with states dependent on the density tuple. Iterate over the elements i of this tuple, filling grid_size * density[i] elements of the grid with state i+1. Non-zero cell states should not be overwritten, ensuring that the specified percentage of the grid is filled with that state. It should also check if the neighborhood argument corresponds with a known neighborhood and return an error otherwise.
5+
6+
The count neighbors function should return a tuple of matrices (one for each defined non-zero state) containing the amount of neighbors of that state for each cell. It should use the neighborhood defined in the class. Ensure the logic works for both "neumann" and "moore". Use periodic boundaries.
7+
8+
The update function can remain empty, so fill it with a pass statement.
9+
10+
The run function should take a steps (int) argument. It should then run the CA for steps interations, calling the update function each time.
11+
12+
Finally, make sure to add an expected type for each argument and define the return types. Add this information, as well as a short description of the function to the docstring. Also add assert statements to ensure arguments "make sense". For example, the sum of densities should not exceed 1 and the rows, cols, densities should all be positive, and the neighborhood should be either "neumann" or "moore".

0 commit comments

Comments
 (0)