Skip to content

Commit 1165ef2

Browse files
authored
Merge pull request #22 from codegithubka/kimon
Kimon
2 parents 7586061 + 725aa79 commit 1165ef2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+9571
-6274
lines changed

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@ results/
1212
data/
1313
.pytest_cache/
1414

15-
.DS_Store
15+
.DS_Store
16+
17+
hpc_data/phase4_18735304
18+
19+
hpc_data/phase6_18780164
20+
21+
22+
hpc_data/**/*.jsonl
23+
hpc_data/**/*.jsonl

README.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
## Predator-Prey Cellular Automaton: Model Documentation
2+
3+
### Overview
4+
5+
This project impelments a spatial predator-prey cellular automaton (CA) to investigate the Hydra effect, a counterintuitive ecological phenomenon where increased prey mortality paradoxically leads to higher prey population densities. The model explores the rise of emergent population dynamics through spatial structure and local interactions that differe fundemntally from well-mixed (mean-field) predictions.
6+
7+
The codebase uses Numba JIT compilation for computationally intensive kernels and is tuned for high performance computing environments. It supporrs parameter sweeps, finite size scaling analysis, and self-organized criticality exploration.
8+
9+
---
10+
11+
### Background
12+
13+
#### The Hydra Effect
14+
15+
In classical Lotka-Volterra dynamics, increasing prey mortality always reduces the prey population. However, theoretical work has identified conditions where the opposite effect can be observed. The Hydra effect emerges in spatially structured systems where
16+
17+
1. Local interactions prevent predators from immediately exploiting all available prey.
18+
2. Spatial refugia from naturally through predator-prey dynamics
19+
3. Increased prey mortality can disrupt predator populations disproportionally.
20+
21+
This study uses a cellular automaton framework toi study how spatial strcuture generates the Hydra effecr and whether the system exhibits signatures of self-organized criticality at the transition point.
22+
23+
#### Self-Organized Criticality (SOC)
24+
25+
SOC refers to systems that naturally evolve toward a critical state without external tuning. At criticality, such systems exhibit:
26+
27+
- Power-law cluster size distributions: $P(s) \sim s^{-\tau}$
28+
- Scale-free correlations: No characteristic length scale dominates
29+
- Critical slowing down: Perturbations decay slowly near the critical point
30+
- Fractal spatial patterns: Self-similar structure across scales
31+
32+
In the predator-prey context, SOC would manifest as the system self-tuning toward a critical prey mortality rate where population dynamics become scale-free and the Hydra effect is maximized.
33+
34+
---
35+
36+
### Model Description
37+
38+
The model uses a 2D lattice with periodic boundary conditions. Each cell occupies one of three states:
39+
40+
| State | Value | Description |
41+
|-------|-------|-------------|
42+
| Empty | 0 | Unoccupied cell |
43+
| Prey | 1 | Prey organism |
44+
| Predator | 2 | Predator organism |
45+
46+
The model uses asynchronous updates: cells are processed in random order each timestep, with state changes taking effect immediately. This prevents artificial synchronization artifacts common in parallel update schemes.
47+
48+
For each prey cell, in order:
49+
50+
- Death: With probability `prey_death`, the prey dies and the cell becomes empty
51+
- Reproduction: If alive and a randomly selected neighbor is empty, with probability `prey_birth`, a new prey is placed in that neighbor cell
52+
53+
For each predator cell, in order:
54+
55+
- Death: With probability `predator_death`, the predator dies (starvation)
56+
- Hunting/Reproduction: If alive and a randomly selected neighbor contains prey, with probability `predator_birth`, the predator consumes the prey and reproduces into that cell
57+
58+
The model supports both neighborhood types:
59+
60+
- Moore neighborhood: (default): 8 adjacent cells (including diagonals)
61+
- von Neumann neighborhood: 4 adjacent cells (cardinal directions only)
62+
63+
Moore neighborhoods are used throughout the experiments as they provide more realistic movement and interaction patterns.
64+
65+
---
66+
67+
### Hunting Modes
68+
69+
The model implements two distinct neighbor selection strategies that qualitatively affect dynamics.
70+
71+
#### Random Neighbor Selection (Default)
72+
73+
In the standard mode, each organism selects a single random neighbor for interaction:
74+
75+
```python
76+
# Prey: pick random neighbor, reproduce if empty
77+
neighbor_idx = np.random.randint(0, n_neighbors)
78+
if grid[neighbor] == EMPTY and random() < prey_birth:
79+
grid[neighbor] = PREY
80+
81+
# Predator: pick random neighbor, hunt if prey
82+
if grid[neighbor] == PREY and random() < predator_birth:
83+
grid[neighbor] = PREDATOR
84+
```
85+
This creates a blind interaction model where organisms are not aware of their surroundings.
86+
87+
#### Directed Hunting Mode
88+
89+
The directed mode implements "intelligent" neighbor selection:
90+
91+
- Prey: Scan all neighbors for empty cells, then randomly select one empty cell for reproduction
92+
- Predators: Scan all neighbors for prey, then randomly select one prey cell to hunt
93+
94+
```python
95+
# Directed prey reproduction
96+
empty_neighbors = [n for n in neighbors if grid[n] == EMPTY]
97+
if empty_neighbors and random() < prey_birth:
98+
target = random.choice(empty_neighbors)
99+
grid[target] = PREY
100+
101+
# Directed predator hunting
102+
prey_neighbors = [n for n in neighbors if grid[n] == PREY]
103+
if prey_neighbors and random() < predator_birth:
104+
target = random.choice(prey_neighbors)
105+
grid[target] = PREDATOR
106+
```
107+
108+
This increases the effective reproduction and predation rates without changing the nominal probability parameters.
109+
110+
---
111+
112+
### Implementation Architecture
113+
114+
#### Class Hierarchy
115+
116+
```
117+
CA (base class)
118+
└── PP (predator-prey specialization)
119+
└── PPKernel (Numba-optimized update kernel)
120+
```
121+
122+
The `CA` base class provides:
123+
- Grid initialization with specified densities
124+
- Neighborhood definitions (Moore/von Neumann)
125+
- Per-cell parameter evolution framework
126+
- Validation and run loop infrastructure
127+
128+
The `PP` class adds:
129+
- Predator-prey specific parameters and defaults
130+
- Synchronous/asynchronous update dispatch
131+
- Integration with Numba-optimized kernels
132+
133+
#### Basic Usage
134+
135+
```python
136+
from models.CA import PP
137+
138+
# Initialize model
139+
model = PP(
140+
rows=100,
141+
cols=100,
142+
densities=(0.30, 0.15), # (prey_fraction, predator_fraction)
143+
params={
144+
"prey_birth": 0.2,
145+
"prey_death": 0.05,
146+
"predator_birth": 0.8,
147+
"predator_death": 0.05,
148+
},
149+
seed=42,
150+
synchronous=False, # Always False for this study
151+
directed_hunting=False,
152+
)
153+
154+
# Run simulation
155+
for step in range(1000):
156+
model.update()
157+
158+
# Access grid state
159+
prey_count = np.sum(model.grid == 1)
160+
pred_count = np.sum(model.grid == 2)
161+
```
162+
163+
#### Evolution Mode
164+
165+
The model supports per-cell parameter evolution, where offspring inherit (with mutation) their parent's parameter values:
166+
167+
```python
168+
# Enable evolution of prey_death parameter
169+
model.evolve(
170+
"prey_death",
171+
sd=0.01, # Mutation standard deviation
172+
min_val=0.0, # Minimum allowed value
173+
max_val=0.20, # Maximum allowed value
174+
)
175+
```
176+
177+
When evolution is enabled, each prey cell maintains its own `prey_death` value. Upon reproduction, offspring inherit the parent's value plus Gaussian noise. This allows investigation of whether the population self-organizes toward a critical mortality rate.
178+
179+
---
180+
181+
### Numba Optimization
182+
183+
The computational bottleneck is the update kernel, which must process every occupied cell each timestep. For a 1000×1000 grid with 50% occupancy, this means ~500,000 cell updates per step.
184+
185+
Key optimizations:
186+
187+
- **JIT Compilation**: Core kernels use `@njit(cache=True)` for ahead-of-time compilation
188+
2. **Pre-allocated Buffers**: The `PPKernel` class maintains reusable arrays to avoid allocation overhead
189+
3. **Efficient Shuffling**: Fisher-Yates shuffle implemented in Numba for random cell ordering
190+
4. **Cell Lists for PCF**: Pair correlation functions use spatial hashing for O(N) instead of O(N²) complexity
191+
192+
#### PPKernel Class
193+
194+
```python
195+
class PPKernel:
196+
"""Wrapper for predator-prey kernel with pre-allocated buffers."""
197+
198+
def __init__(self, rows, cols, neighborhood="moore", directed_hunting=False):
199+
self.rows = rows
200+
self.cols = cols
201+
self.directed_hunting = directed_hunting
202+
self._occupied_buffer = np.empty((rows * cols, 2), dtype=np.int32)
203+
204+
# Neighbor offset arrays
205+
if neighborhood == "moore":
206+
self._dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1], dtype=np.int32)
207+
self._dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1], dtype=np.int32)
208+
else:
209+
self._dr = np.array([-1, 1, 0, 0], dtype=np.int32)
210+
self._dc = np.array([0, 0, -1, 1], dtype=np.int32)
211+
```
212+
---
213+
214+
### Cluster Detection
215+
216+
Clusters are contiguous groups of same-species cells (using Moore connectivity). The implementation uses stack-based flood fill with periodic boundary conditions.
217+
218+
```python
219+
from models.numba_optimized import get_cluster_stats_fast
220+
221+
stats = get_cluster_stats_fast(grid, species=1) # Prey clusters
222+
print(f"Number of clusters: {stats['n_clusters']}")
223+
print(f"Largest cluster: {stats['largest']} cells")
224+
print(f"Largest fraction: {stats['largest_fraction']:.3f}")
225+
```
226+
227+
Metrics collected:
228+
- Cluster size distribution: Power-law indicates criticality
229+
- Largest cluster fraction: Order parameter for percolation transition
230+
---
231+
232+
### Configuration System
233+
234+
The `Config` dataclass centralizes all experimental parameters:
235+
236+
```python
237+
from config import PHASE1_CONFIG, Config
238+
239+
# Use predefined phase config
240+
cfg = PHASE1_CONFIG
241+
242+
# Or create custom config
243+
cfg = Config(
244+
grid_size=500,
245+
n_replicates=20,
246+
warmup_steps=1000,
247+
measurement_steps=2000,
248+
collect_pcf=True,
249+
pcf_sample_rate=0.5,
250+
)
251+
252+
# Access parameters
253+
print(f"Grid: {cfg.grid_size}x{cfg.grid_size}")
254+
print(f"Estimate: {cfg.estimate_runtime(n_cores=32)}")
255+
```
256+
257+
Each phase has a predefined configuration (`PHASE1_CONFIG` through `PHASE5_CONFIG`) with appropriate defaults for that analysis.
258+
259+
---
260+
261+
### Output Format
262+
263+
Results are saved in JSONL format (one JSON object per line) for efficient streaming and parallel processing:
264+
265+
```json
266+
{"prey_birth": 0.2, "prey_death": 0.05, "seed": 12345, "final_prey": 28500, ...}
267+
{"prey_birth": 0.2, "prey_death": 0.06, "seed": 12346, "final_prey": 27200, ...}
268+
```
269+
270+
Each result dictionary contains:
271+
- Input parameters
272+
- Final population counts
273+
- Cluster statistics
274+
- Evolution statistics (if enabled)
275+
- Time series (if enabled)
276+
277+
Metadata files (`phase{N}_metadata.json`) accompany each results file with configuration details and runtime information.
278+
279+
---
280+
281+
### Dependencies
282+
283+
**Required:**
284+
- Python 3.8+
285+
- NumPy
286+
- Numba (for JIT compilation)
287+
- tqdm (progress bars)
288+
- joblib (parallelization)
289+
290+
**Optional:**
291+
- matplotlib (visualization)
292+
- scipy (additional analysis)
293+
294+
---
295+
296+
## References

0 commit comments

Comments
 (0)