Skip to content

Commit e85702a

Browse files
committed
Merge branch 'main' into kimon
2 parents 45adc9e + 7586061 commit e85702a

4 files changed

Lines changed: 101 additions & 167 deletions

File tree

docs/experiments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ These phases should be completed sequentially, deepening our understanding at ea
3232
- Look for critical slowing down: perturbations to states closer to the critical point should more slowly return to the steady state
3333
- This requires time series data
3434
### Phase 6: model extensions
35-
- Investigate whether hydra effect and SOC still occur with diffusion and directed movement
35+
- Investigate whether hydra effect and SOC still occur with diffusion and directed reproduction
3636

3737
# Todo
3838
The main functionality is all complete. Thus, the models folder should be relatively untouched.

models/config.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -264,20 +264,16 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
264264
timeseries_subsample=1, # Full resolution for autocorrelation
265265
)
266266

267-
# Phase 6: Model extensions (directed hunting); same config as phase 1 but with directed hunting
268-
PHASE6_CONFIG = Config(
269-
grid_size=1000, #FIXME: Verify config before running.
270-
n_prey_death=20,
271-
prey_birth=0.2,
272-
prey_death_range=(0.0, 0.2),
273-
predator_birth=0.8,
274-
predator_death=0.05,
275-
n_replicates=30,
276-
warmup_steps=1000,
277-
measurement_steps=1000,
278-
collect_pcf=True,
279-
pcf_sample_rate=0.2,
280-
save_timeseries=False,
267+
# Phase 6: Model extensions (directed reproduction); same config as phase 4 but with directed reproduction
268+
PHASE6_CONFIG = Config(
269+
grid_size=250,
270+
n_replicates=10,
271+
warmup_steps=500,
272+
measurement_steps=500,
273+
with_evolution=False,
274+
collect_pcf=False,
275+
save_timeseries=True,
276+
timeseries_subsample=10,
281277
directed_hunting=True,
282278
)
283279

models/numba_optimized.py

Lines changed: 82 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,65 @@ def set_numba_seed(seed: int) -> None:
5656
# ============================================================================
5757
# PREDATOR-PREY KERNELS
5858
# ============================================================================
59+
@njit(cache=True)
60+
def _pp_async_kernel_fast(
61+
grid: np.ndarray,
62+
prey_death_arr: np.ndarray,
63+
p_birth_val: float,
64+
p_death_val: float,
65+
pred_birth_val: float,
66+
pred_death_val: float,
67+
dr_arr: np.ndarray,
68+
dc_arr: np.ndarray,
69+
evolve_sd: float,
70+
evolve_min: float,
71+
evolve_max: float,
72+
evolution_stopped: bool,
73+
occupied_buffer: np.ndarray,
74+
) -> np.ndarray:
75+
"""Partially synchronous predator-prey update kernel."""
76+
rows, cols = grid.shape
77+
n_shifts = len(dr_arr)
78+
grid_copy = grid.copy()
79+
prey_death_arr_copy = prey_death_arr.copy()
80+
81+
prey_death = np.random.random(size=grid.shape)
82+
grid_copy[(grid == 1) & (prey_death < prey_death_arr)] = 0
83+
prey_death_arr_copy[(grid == 1) & (prey_death < prey_death_arr)] = np.nan
84+
85+
pred_death = np.random.random(size=grid.shape)
86+
grid_copy[(grid == 2) & (pred_death < pred_death_val)] = 0
87+
88+
count = np.count_nonzero(grid)
89+
indices = np.random.permutation(count)
90+
rs = indices // cols
91+
cs = indices % cols
92+
93+
nb = np.random.randint(0, n_shifts, size=count)
94+
nrs = (rs + dr_arr[nb]) % rows
95+
ncs = (cs + dc_arr[nb]) % cols
96+
97+
for r, c, nr, nc in zip(rs, cs, nrs, ncs):
98+
state = grid[r, c]
99+
nstate = grid[nr, nc]
100+
101+
if state == 1 and nstate == 0 and np.random.random() < p_birth_val:
102+
grid_copy[nr, nc] = 1
103+
parent_val = prey_death_arr[r, c]
104+
if not evolution_stopped:
105+
child_val = parent_val + np.random.normal(0, evolve_sd)
106+
prey_death_arr_copy[nr, nc] = np.clip(child_val, evolve_min, evolve_max)
107+
else:
108+
prey_death_arr_copy[nr, nc] = parent_val
109+
110+
elif state == 2 and nstate == 1 and np.random.random() < pred_birth_val:
111+
grid_copy[nr, nc] = 2
112+
prey_death_arr_copy[nr, nc] = np.nan
113+
114+
grid = grid_copy
115+
prey_death_arr = prey_death_arr_copy
116+
117+
return grid
59118

60119
@njit(cache=True)
61120
def _pp_async_kernel_random(
@@ -148,7 +207,7 @@ def _pp_async_kernel_directed(
148207
evolution_stopped: bool,
149208
occupied_buffer: np.ndarray,
150209
) -> np.ndarray:
151-
"""Async predator-prey update kernel with directed hunting."""
210+
"""Async predator-prey update kernel with directed reproduction."""
152211
rows, cols = grid.shape
153212
n_shifts = len(dr_arr)
154213

@@ -170,19 +229,22 @@ def _pp_async_kernel_directed(
170229
c = occupied_buffer[i, 1]
171230

172231
state = grid[r, c]
173-
if state == 0:
174-
continue
175232

176233
if state == 1: # PREY
177-
nbi = np.random.randint(0, n_shifts)
178-
nr = (r + dr_arr[nbi]) % rows
179-
nc = (c + dc_arr[nbi]) % cols
180-
181234
if np.random.random() < prey_death_arr[r, c]:
182235
grid[r, c] = 0
183236
prey_death_arr[r, c] = np.nan
184-
elif grid[nr, nc] == 0:
185-
if np.random.random() < p_birth_val:
237+
elif np.random.random() < p_birth_val:
238+
valid = []
239+
for k in range(n_shifts):
240+
nr = (r + dr_arr[k]) % rows
241+
nc = (c + dc_arr[k]) % cols
242+
if grid[nr, nc] == 0:
243+
valid.append(k)
244+
if len(valid) > 0:
245+
choice = np.random.choice(valid)
246+
nr = (r + dr_arr[choice]) % rows
247+
nc = (c + dc_arr[choice]) % cols
186248
grid[nr, nc] = 1
187249
parent_val = prey_death_arr[r, c]
188250
if not evolution_stopped:
@@ -195,44 +257,22 @@ def _pp_async_kernel_directed(
195257
else:
196258
prey_death_arr[nr, nc] = parent_val
197259

198-
elif state == 2: # PREDATOR - directed hunting
260+
elif state == 2: # PREDATOR
199261
if np.random.random() < pred_death_val:
200262
grid[r, c] = 0
201-
continue
202-
203-
prey_count = 0
204-
for k in range(n_shifts):
205-
check_r = (r + dr_arr[k]) % rows
206-
check_c = (c + dc_arr[k]) % cols
207-
if grid[check_r, check_c] == 1:
208-
prey_count += 1
209-
210-
if prey_count > 0:
211-
target_idx = np.random.randint(0, prey_count)
212-
found = 0
213-
nr, nc = 0, 0
263+
elif np.random.random() < pred_birth_val:
264+
valid = []
214265
for k in range(n_shifts):
215-
check_r = (r + dr_arr[k]) % rows
216-
check_c = (c + dc_arr[k]) % cols
217-
if grid[check_r, check_c] == 1:
218-
if found == target_idx:
219-
nr = check_r
220-
nc = check_c
221-
break
222-
found += 1
223-
224-
if np.random.random() < pred_birth_val:
266+
nr = (r + dr_arr[k]) % rows
267+
nc = (c + dc_arr[k]) % cols
268+
if grid[nr, nc] == 1:
269+
valid.append(k)
270+
if len(valid) > 0:
271+
choice = np.random.choice(valid)
272+
nr = (r + dr_arr[choice]) % rows
273+
nc = (c + dc_arr[choice]) % cols
225274
grid[nr, nc] = 2
226275
prey_death_arr[nr, nc] = np.nan
227-
else:
228-
nbi = np.random.randint(0, n_shifts)
229-
nr = (r + dr_arr[nbi]) % rows
230-
nc = (c + dc_arr[nbi]) % cols
231-
232-
if grid[nr, nc] == 1:
233-
if np.random.random() < pred_birth_val:
234-
grid[nr, nc] = 2
235-
prey_death_arr[nr, nc] = np.nan
236276

237277
return grid
238278

notebooks/phase1.ipynb

Lines changed: 8 additions & 110 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)