Skip to content

Directed reproduction and phase 6 config#20

Merged
StormKoper merged 4 commits into
mainfrom
storm
Jan 27, 2026
Merged

Directed reproduction and phase 6 config#20
StormKoper merged 4 commits into
mainfrom
storm

Conversation

@StormKoper

Copy link
Copy Markdown
Collaborator

No description provided.

Now chooses a random valid neighbor for reproduction instead of a random neighbor (without checking for validity) like in the undirected kernel
Another complete parameter sweep, but with directed reproduction
Copilot AI review requested due to automatic review settings January 27, 2026 15:32
@StormKoper StormKoper merged commit 7586061 into main Jan 27, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces "directed reproduction" behavior to the predator-prey model and updates the Phase 6 configuration. Directed reproduction means that organisms actively choose where to reproduce based on local availability: prey select empty neighboring cells for reproduction, while predators target prey cells. This contrasts with the previous random neighbor selection approach.

Changes:

  • Added a new (but unused) _pp_async_kernel_fast function with critical bugs
  • Modified _pp_async_kernel_directed to implement directed reproduction instead of directed hunting
  • Updated Phase 6 configuration to match Phase 4 parameters with directed_hunting flag enabled
  • Updated documentation to reflect the change from "directed movement" to "directed reproduction"

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 8 comments.

File Description
models/numba_optimized.py Added unused _pp_async_kernel_fast function (has bugs); modified _pp_async_kernel_directed to implement directed reproduction behavior
models/config.py Updated PHASE6_CONFIG to use Phase 4-like parameters with directed_hunting=True
docs/experiments.md Updated Phase 6 description to mention "directed reproduction" instead of "directed movement"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread models/numba_optimized.py
Comment on lines +114 to +115
grid = grid_copy
prey_death_arr = prey_death_arr_copy

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature indicates it should return a single np.ndarray (the grid), but the function also modifies prey_death_arr_copy. However, these modifications are lost because prey_death_arr_copy is never returned or used. The function should either return a tuple containing both arrays, or it should modify the input arrays in-place like the other kernel functions do.

Suggested change
grid = grid_copy
prey_death_arr = prey_death_arr_copy
# Copy results back into the original arrays so that updates are visible
grid[:, :] = grid_copy
prey_death_arr[:, :] = prey_death_arr_copy

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
nrs = (rs + dr_arr[nb]) % rows
ncs = (cs + dc_arr[nb]) % cols

for r, c, nr, nc in zip(rs, cs, nrs, ncs):

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop processes cells that may have already been marked as dead in grid_copy. The state variable reads from the original grid, but the reproduction logic checks nstate from grid_copy. This creates an inconsistency: a cell that was killed in the death phase (lines 81-86) can still attempt reproduction because its state is read from the original grid. Consider checking if grid_copy[r, c] == 0 before processing reproduction.

Suggested change
for r, c, nr, nc in zip(rs, cs, nrs, ncs):
for r, c, nr, nc in zip(rs, cs, nrs, ncs):
# Skip cells that have already died during this update step
if grid_copy[r, c] == 0:
continue

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
evolve_min: float,
evolve_max: float,
evolution_stopped: bool,
occupied_buffer: np.ndarray,

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The occupied_buffer parameter is declared but never used in this function. This is inconsistent with the other kernel functions which use it to track occupied cells. Either the parameter should be removed or the function should be refactored to use it.

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
grid: np.ndarray,
prey_death_arr: np.ndarray,
p_birth_val: float,
p_death_val: float,

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The p_death_val parameter is declared but never used in this function. Based on the pattern in other kernel functions, p_death_val should be used in the prey death logic at line 82, but instead prey_death_arr is used directly. This creates an inconsistency with the function signature and may not match the intended behavior.

Copilot uses AI. Check for mistakes.
Comment thread models/config.py
Comment on lines +266 to 276
# Phase 6: Model extensions (directed reproduction); same config as phase 4 but with directed reproduction
PHASE6_CONFIG = Config(
grid_size=1000,
n_prey_death=20,
prey_birth=0.2,
prey_death_range=(0.09, 0.12),
predator_birth=0.8,
predator_death=0.05,
n_replicates=30,
warmup_steps=1000,
measurement_steps=1000,
collect_pcf=True,
pcf_sample_rate=0.2,
save_timeseries=False,
grid_size=250,
n_replicates=10,
warmup_steps=500,
measurement_steps=500,
with_evolution=False,
collect_pcf=False,
save_timeseries=True,
timeseries_subsample=10,
directed_hunting=True,

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "directed reproduction" but the parameter name is still "directed_hunting". The PR changes the behavior from directed hunting to directed reproduction, so this parameter should be renamed throughout the codebase to "directed_reproduction" to accurately reflect its purpose. This inconsistency will confuse future developers about what this flag actually does.

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
@@ -170,19 +229,22 @@ def _pp_async_kernel_directed(
c = occupied_buffer[i, 1]

state = grid[r, c]

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state 0 check that was removed is actually needed. Without it, cells that have already been killed (set to 0) earlier in the shuffled iteration order could be processed again when they no longer exist. While the current code will skip processing due to the state checks below, removing this early exit makes the code less efficient and less clear. Consider restoring this check for clarity and efficiency.

Suggested change
state = grid[r, c]
state = grid[r, c]
if state == 0:
continue

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
Comment on lines +60 to 119
def _pp_async_kernel_fast(
grid: np.ndarray,
prey_death_arr: np.ndarray,
p_birth_val: float,
p_death_val: float,
pred_birth_val: float,
pred_death_val: float,
dr_arr: np.ndarray,
dc_arr: np.ndarray,
evolve_sd: float,
evolve_min: float,
evolve_max: float,
evolution_stopped: bool,
occupied_buffer: np.ndarray,
) -> np.ndarray:
"""Partially synchronous predator-prey update kernel."""
rows, cols = grid.shape
n_shifts = len(dr_arr)
grid_copy = grid.copy()
prey_death_arr_copy = prey_death_arr.copy()

prey_death = np.random.random(size=grid.shape)
grid_copy[(grid == 1) & (prey_death < prey_death_arr)] = 0
prey_death_arr_copy[(grid == 1) & (prey_death < prey_death_arr)] = np.nan

pred_death = np.random.random(size=grid.shape)
grid_copy[(grid == 2) & (pred_death < pred_death_val)] = 0

count = np.count_nonzero(grid)
indices = np.random.permutation(count)
rs = indices // cols
cs = indices % cols

nb = np.random.randint(0, n_shifts, size=count)
nrs = (rs + dr_arr[nb]) % rows
ncs = (cs + dc_arr[nb]) % cols

for r, c, nr, nc in zip(rs, cs, nrs, ncs):
state = grid[r, c]
nstate = grid[nr, nc]

if state == 1 and nstate == 0 and np.random.random() < p_birth_val:
grid_copy[nr, nc] = 1
parent_val = prey_death_arr[r, c]
if not evolution_stopped:
child_val = parent_val + np.random.normal(0, evolve_sd)
prey_death_arr_copy[nr, nc] = np.clip(child_val, evolve_min, evolve_max)
else:
prey_death_arr_copy[nr, nc] = parent_val

elif state == 2 and nstate == 1 and np.random.random() < pred_birth_val:
grid_copy[nr, nc] = 2
prey_death_arr_copy[nr, nc] = np.nan

grid = grid_copy
prey_death_arr = prey_death_arr_copy

return grid

@njit(cache=True)

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is defined but never called anywhere in the codebase. It appears to be dead code or work in progress. If this function is not needed, it should be removed. If it's intended for future use, it should be documented or marked as such. Additionally, the function has critical bugs that would prevent it from working correctly.

Suggested change
def _pp_async_kernel_fast(
grid: np.ndarray,
prey_death_arr: np.ndarray,
p_birth_val: float,
p_death_val: float,
pred_birth_val: float,
pred_death_val: float,
dr_arr: np.ndarray,
dc_arr: np.ndarray,
evolve_sd: float,
evolve_min: float,
evolve_max: float,
evolution_stopped: bool,
occupied_buffer: np.ndarray,
) -> np.ndarray:
"""Partially synchronous predator-prey update kernel."""
rows, cols = grid.shape
n_shifts = len(dr_arr)
grid_copy = grid.copy()
prey_death_arr_copy = prey_death_arr.copy()
prey_death = np.random.random(size=grid.shape)
grid_copy[(grid == 1) & (prey_death < prey_death_arr)] = 0
prey_death_arr_copy[(grid == 1) & (prey_death < prey_death_arr)] = np.nan
pred_death = np.random.random(size=grid.shape)
grid_copy[(grid == 2) & (pred_death < pred_death_val)] = 0
count = np.count_nonzero(grid)
indices = np.random.permutation(count)
rs = indices // cols
cs = indices % cols
nb = np.random.randint(0, n_shifts, size=count)
nrs = (rs + dr_arr[nb]) % rows
ncs = (cs + dc_arr[nb]) % cols
for r, c, nr, nc in zip(rs, cs, nrs, ncs):
state = grid[r, c]
nstate = grid[nr, nc]
if state == 1 and nstate == 0 and np.random.random() < p_birth_val:
grid_copy[nr, nc] = 1
parent_val = prey_death_arr[r, c]
if not evolution_stopped:
child_val = parent_val + np.random.normal(0, evolve_sd)
prey_death_arr_copy[nr, nc] = np.clip(child_val, evolve_min, evolve_max)
else:
prey_death_arr_copy[nr, nc] = parent_val
elif state == 2 and nstate == 1 and np.random.random() < pred_birth_val:
grid_copy[nr, nc] = 2
prey_death_arr_copy[nr, nc] = np.nan
grid = grid_copy
prey_death_arr = prey_death_arr_copy
return grid
@njit(cache=True)

Copilot uses AI. Check for mistakes.
Comment thread models/numba_optimized.py
Comment on lines +114 to +115
grid = grid_copy
prey_death_arr = prey_death_arr_copy

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variable assignments are useless because the function returns the original grid, not the modified grid_copy. The function should return both grid_copy and prey_death_arr_copy, or these assignments should be removed. Currently, all modifications to grid_copy and prey_death_arr_copy are discarded.

Suggested change
grid = grid_copy
prey_death_arr = prey_death_arr_copy
# Copy results back into the original arrays so the caller sees updates.
grid[:, :] = grid_copy
prey_death_arr[:, :] = prey_death_arr_copy

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants