Skip to content

Commit 170fdf1

Browse files
committed
cleaning up analysis file
1 parent 71c8402 commit 170fdf1

10 files changed

Lines changed: 215 additions & 44 deletions

File tree

docs/kimon_updates.md

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -217,22 +217,17 @@ As a resultl, we can probably use a 1000x1000 grid for our HPC simulation!
217217

218218
## Testing and HPC Run Update (23/1)
219219

220-
HPC Run Estimate (we are using 32 cores)
220+
HPC Run Estimate (we are using 32 cores).
221221

222-
bash
223-
```
224-
2026-01-22 21:09:55,625 [INFO] ============================================================
225-
2026-01-22 21:09:55,625 [INFO] PP Evolutionary Analysis - OPTIMIZED VERSION
226-
2026-01-22 21:09:55,625 [INFO] ============================================================
227-
2026-01-22 21:09:55,625 [INFO] Mode: full
228-
2026-01-22 21:09:55,626 [INFO] Output: results
229-
2026-01-22 21:09:55,626 [INFO] Cores: -1
230-
2026-01-22 21:09:55,626 [INFO] Numba: ENABLED
231-
2026-01-22 21:09:55,626 [INFO] Directed hunting: DISABLED
232-
2026-01-22 21:09:55,626 [INFO] Estimated: 23,000 sims, ~0.1h on 96 cores (~5 core-hours)
233-
2026-01-22 21:09:55,626 [INFO] Dry run - exiting
234-
(snellius_venv) [kanagnostopoul@int5 ~]$
235-
```
222+
1000 x 1000 grid -> 1 million cells
223+
224+
At each step: 500 million operations per simulation
225+
226+
This is multiplied by the number of replicates. 50 reps will result in 22,500 simulations.
227+
228+
By the benchmark, we have 1,182 steps per second (throuput) for a 100x100 grid. If we use a 1000x1000 grid, that implies 11.8 steps/second. So 1000x1000 grid with 50 reps
229+
230+
8.26 hours (not ideal!)
236231

237232
### Tests
238233

@@ -281,15 +276,27 @@ We have 48 tests cases validating the folloiwing:
281276
- Edge cases with extreme parameter values
282277

283278

284-
### Issues to be resolved
279+
## Issues to be resolved
280+
281+
1. Grid size for HPC run
282+
2. Number of replicates for statistal power
283+
3. Directed and/or undirected runs
284+
4. Evolving and non-evolving runs?
285+
5. Mean field baseline or non evolving basiline
286+
6. Warmup period and measurement steps (i.e how many steps do we need to avoid init bias?)
287+
7. Measurement frequency for statistical accuracy
288+
8. Default parameters (Need Storm's input on this one).
289+
290+
Options:
291+
292+
1. Asymmetric repliates for non-evolving runs
293+
2. Coarse initial parameter sweep grid
294+
3. Discard non-evo runs and use mean field baseline instead or the opposite
285295

286-
1. Grid size for Snellius run
296+
NOTE: Without the optimization kernels for a 1000x1000 grid the simulation (using 50 reps for statistical power) would run for 548 hours (approximately 23 days)
287297

288-
2. Number of replicates for statistical power
289298

290-
3. Directed and/or undirected runs
291299

292-
The above decisions will complicate our analysis in terms of noise reduction, finite size scaling, and capturing spatial correlations (hryda effect).
293300

294301

295302

mock_results/analysis.log

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2026-01-23 11:56:47,627 [INFO] ============================================================
2+
2026-01-23 11:56:47,627 [INFO] PP Evolutionary Analysis - OPTIMIZED VERSION
3+
2026-01-23 11:56:47,627 [INFO] ============================================================
4+
2026-01-23 11:56:47,627 [INFO] Mode: plot
5+
2026-01-23 11:56:47,627 [INFO] Output: mock_results
6+
2026-01-23 11:56:47,627 [INFO] Cores: -1
7+
2026-01-23 11:56:47,627 [INFO] Numba: ENABLED
8+
2026-01-23 11:56:47,627 [INFO] Directed hunting: DISABLED
9+
2026-01-23 11:56:47,627 [INFO] Estimated: 7,250 sims, ~0.1h on 8 cores (~1 core-hours)
10+
2026-01-23 11:56:48,037 [INFO] Loaded 400 results
11+
2026-01-23 11:56:48,779 [INFO] Saved phase_diagrams.png
12+
2026-01-23 11:56:49,001 [INFO] Saved hydra_analysis.png
13+
2026-01-23 11:56:49,226 [INFO] Saved pcf_analysis.png
14+
2026-01-23 11:56:49,226 [INFO] ============================================================
15+
2026-01-23 11:56:49,226 [INFO] ANALYSIS SUMMARY
16+
2026-01-23 11:56:49,226 [INFO] ============================================================
17+
2026-01-23 11:56:49,226 [INFO] Hydra region: 0 combinations
18+
2026-01-23 11:56:49,226 [INFO] Max Hydra strength: nan
19+
2026-01-23 11:56:49,226 [INFO] Mean segregation index: 1.000
20+
2026-01-23 11:56:49,226 [INFO] Critical point: pb=0.100, pd=0.100
21+
2026-01-23 11:56:49,227 [INFO] Total runtime: 0.00 hours
22+
2026-01-23 11:56:49,227 [INFO] Done!

mock_results/config.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"default_grid": 100,
3+
"densities": [
4+
0.3,
5+
0.15
6+
],
7+
"n_prey_birth": 15,
8+
"n_prey_death": 15,
9+
"prey_birth_min": 0.1,
10+
"prey_birth_max": 0.35,
11+
"prey_death_min": 0.001,
12+
"prey_death_max": 0.1,
13+
"predator_death": 0.1,
14+
"predator_birth": 0.2,
15+
"n_replicates": 15,
16+
"warmup_steps": 200.0,
17+
"measurement_steps": 300,
18+
"cluster_samples": 1,
19+
"collect_pcf": true,
20+
"pcf_sample_rate": 0.2,
21+
"pcf_max_distance": 20.0,
22+
"pcf_n_bins": 20,
23+
"evolve_sd": 0.1,
24+
"evolve_min": 0.001,
25+
"evolve_max": 0.1,
26+
"fss_grid_sizes": [
27+
50,
28+
75,
29+
100,
30+
150
31+
],
32+
"fss_replicates": 100,
33+
"sensitivity_sd_values": [
34+
0.02,
35+
0.05,
36+
0.1,
37+
0.15,
38+
0.2
39+
],
40+
"sensitivity_replicates": 20,
41+
"synchronous": false,
42+
"directed_hunting": false,
43+
"save_diagnostic_plots": false,
44+
"diagnostic_param_sets": 5,
45+
"min_analysis_density": 0.002,
46+
"n_jobs": -1
47+
}

mock_results/hydra_analysis.png

71.1 KB
Loading

mock_results/pcf_analysis.png

59.7 KB
Loading

mock_results/phase_diagrams.png

140 KB
Loading

mock_results/summary.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"coexistence_no_evo": 0,
3+
"hydra_region_size": 0,
4+
"max_hydra_strength": NaN,
5+
"hydra_region_size_evo": 0,
6+
"mean_segregation_index": 1.0,
7+
"mean_prey_clustering": 1.5,
8+
"critical_prey_birth": 0.1,
9+
"critical_prey_death": 0.1,
10+
"critical_tau_prey": 2.009731666223138
11+
}

mock_results/sweep_results.npz

1.1 MB
Binary file not shown.

scripts/mock.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import numpy as np
2+
from pathlib import Path
3+
import json
4+
from dataclasses import asdict
5+
import sys
6+
import os
7+
8+
sys.path.append(os.getcwd())
9+
from scripts.pp_analysis import Config, save_sweep_binary
10+
11+
def generate_mock_data(output_dir: str):
12+
cfg = Config(default_grid=100, n_prey_birth=10, n_prey_death=10, n_replicates=2)
13+
out_path = Path(output_dir)
14+
out_path.mkdir(exist_ok=True)
15+
16+
prey_births = np.linspace(0.1, 0.4, 10)
17+
prey_deaths = np.linspace(0.01, 0.1, 10)
18+
results = []
19+
20+
for pb in prey_births:
21+
for pd in prey_deaths:
22+
for evo in [False, True]:
23+
for rep in range(cfg.n_replicates):
24+
hydra_factor = 5000 * np.exp(-(pd - 0.04)**2 / 0.001) if not evo else 6000
25+
base_pop = (pb * 10000) - (pd * 20000) + hydra_factor
26+
27+
# Ensure pop doesn't go negative
28+
prey_mean = max(50, base_pop + np.random.normal(0, 100))
29+
30+
# Mock PCF data
31+
dist = np.linspace(0.5, 20, 20)
32+
# C_cr < 1 indicates segregation
33+
seg_idx = 0.8 + (pd * 2)
34+
35+
res = {
36+
"prey_birth": float(pb),
37+
"prey_death": float(pd),
38+
"with_evolution": evo,
39+
"prey_mean": float(prey_mean),
40+
"pred_mean": float(prey_mean * 0.4),
41+
"prey_survived": bool(prey_mean > 100),
42+
"prey_tau": 2.05 + np.random.normal(0, 0.05),
43+
"evolved_prey_death_mean": float(pd * 1.2) if evo else np.nan,
44+
"segregation_index": float(seg_idx),
45+
"prey_clustering_index": 1.5,
46+
"pcf_distances": dist.tolist(),
47+
"pcf_prey_prey_mean": (1.0 + np.exp(-dist/2)).tolist()
48+
}
49+
results.append(res)
50+
51+
# Save as .npz to mimic the real output
52+
save_sweep_binary(results, out_path / "sweep_results.npz")
53+
54+
# Save a mock config
55+
with open(out_path / "config.json", "w") as f:
56+
json.dump(asdict(cfg), f, indent=2)
57+
58+
print(f"Mock data generated in {output_dir}")
59+
60+
if __name__ == "__main__":
61+
generate_mock_data("mock_results")

scripts/pp_analysis.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from typing import Dict, List, Tuple, Optional
3333
import warnings
3434
from tqdm import tqdm
35+
import hashlib
3536

3637
project_root = str(Path(__file__).parents[1])
3738
if project_root not in sys.path:
@@ -68,31 +69,33 @@ class Config:
6869
"""Central configuration for analysis."""
6970

7071
# Grid settings
71-
default_grid: int = 1000
72-
densities: Tuple[float, float] = (0.30, 0.15)
72+
default_grid: int = 100 #FIXME: Decide default configuration
73+
densities: Tuple[float, float] = (0.30, 0.15) #FIXME: Default densities
7374

7475
# 2D sweep resolution
75-
n_prey_birth: int = 15
76+
n_prey_birth: int = 15 # FIXME: Decide number of grid points along prey axes
7677
n_prey_death: int = 15
77-
prey_birth_min: float = 0.10
78+
prey_birth_min: float = 0.10 # FIXME: Range of prey death to sweep
7879
prey_birth_max: float = 0.35
7980
prey_death_min: float = 0.001
8081
prey_death_max: float = 0.10
8182

8283
# Fixed predator parameters
83-
predator_death: float = 0.1
84-
predator_birth: float = 0.2
84+
predator_death: float = 0.1 # FIXME: Default predator death rate
85+
predator_birth: float = 0.2 # FIXME: Default predator birth
8586

8687
# Replicates
87-
n_replicates: int = 15
88+
n_replicates: int = 15 # FIXME: Decide number of indep. runs per parameter config
8889

8990
# Simulation timing
90-
warmup_steps: int = 200
91-
measurement_steps: int = 300
91+
warmup_steps: int = 200 * (default_grid / 100) # FIXME: Steps to run before measuring
92+
measurement_steps: int = 300 # FIXME: Decide measurement steps
9293

9394
# Cluster/PCF sampling
9495
cluster_samples: int = 1 # Reduced from 3 - PCF is expensive
95-
cluster_interval: int = 299 # Sample near end of measurement
96+
@property
97+
def cluster_interval(self) -> int:
98+
return self.measurement_steps - 1 # Sample near end of measurement
9699

97100
# PCF settings
98101
collect_pcf: bool = True
@@ -101,26 +104,29 @@ class Config:
101104
pcf_n_bins: int = 20
102105

103106
# Evolution parameters
104-
evolve_sd: float = 0.10
107+
evolve_sd: float = 0.10 # FIXME: Tune evolution parameters
105108
evolve_min: float = 0.001
106109
evolve_max: float = 0.10
107110

108111
# Finite size scaling
109-
fss_grid_sizes: Tuple[int, ...] = (50, 75, 100, 150)
112+
fss_grid_sizes: Tuple[int, ...] = (50, 75, 100, 150) # FIXME: Grid sizes for FSS
110113
fss_replicates: int = 100
111114

112115
# Evolution sensitivity analysis
113-
sensitivity_sd_values: Tuple[float, ...] = (0.02, 0.05, 0.10, 0.15, 0.20)
116+
sensitivity_sd_values: Tuple[float, ...] = (0.02, 0.05, 0.10, 0.15, 0.20) # FIXME: SD values to test
114117
sensitivity_replicates: int = 20
115118

116119
# Update mode
117-
synchronous: bool = False
118-
directed_hunting: bool = False
120+
synchronous: bool = False # NOTE: This should always be False for PP model
121+
directed_hunting: bool = True # FIXME: With or without directed hunting functionality
119122

120123
# Diagnostic snapshots
121124
save_diagnostic_plots: bool = False
122125
diagnostic_param_sets: int = 5
123126

127+
# Min density required for PCF/Clsuter Analysis
128+
min_analysis_density: float = 0.002 # FIXME: Minimum prey density (fraction of grid) to analyze clusters/PCF
129+
124130
# Parallelization
125131
n_jobs: int = -1
126132

@@ -173,6 +179,15 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
173179
# HELPER FUNCTIONS
174180
# =============================================================================
175181

182+
183+
def generate_unique_seed(pb: float, pd:float, rep:int) -> int:
184+
"""Creates a unique, deterministic seed from parameters."""
185+
186+
identifier = f"{pb:.6f}_{pd:.6f}_{rep}".encode()
187+
hash_hex = hashlib.sha256(identifier).hexdigest()[:8]
188+
189+
return int(hash_hex, 16)
190+
176191
def count_populations(grid: np.ndarray) -> Tuple[int, int, int]:
177192
"""Count empty, prey, predator cells."""
178193
return int(np.sum(grid == 0)), int(np.sum(grid == 1)), int(np.sum(grid == 2))
@@ -363,6 +378,9 @@ def run_single_simulation(
363378

364379
sample_counter = 0
365380

381+
# Calculate threshold based on area
382+
min_count = int(cfg.min_analysis_density * (grid_size**2))
383+
366384
for step in range(cfg.measurement_steps):
367385
model.update()
368386
_, prey, pred = count_populations(model.grid)
@@ -376,7 +394,7 @@ def run_single_simulation(
376394

377395
# Cluster and PCF sampling
378396
if step >= cfg.cluster_interval and sample_counter < cfg.cluster_samples:
379-
if prey > 10:
397+
if prey >= min_count and pred >= (min_count // 4):
380398
prey_clusters.extend(measure_cluster_sizes_fast(model.grid, 1))
381399
pred_clusters.extend(measure_cluster_sizes_fast(model.grid, 2))
382400

@@ -572,10 +590,10 @@ def run_2d_sweep(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[
572590
for pb in prey_births:
573591
for pd in prey_deaths:
574592
for rep in range(cfg.n_replicates):
575-
seed_base = int(pb * 1000) + int(pd * 10000) + rep
593+
seed = generate_unique_seed(pb, pd, rep)
576594
# Both with and without evolution
577-
jobs.append((pb, pd, cfg.default_grid, seed_base, False))
578-
jobs.append((pb, pd, cfg.default_grid, seed_base, True))
595+
jobs.append((pb, pd, cfg.default_grid, seed, False)) #FIXME: Consider cutting non-evo runs
596+
jobs.append((pb, pd, cfg.default_grid, seed, True))
579597

580598
logger.info(f"2D Sweep: {len(jobs):,} simulations")
581599
logger.info(f" Grid: {len(prey_births)}×{len(prey_deaths)} parameters")
@@ -939,13 +957,18 @@ def generate_plots(cfg: Config, output_dir: Path, logger: logging.Logger):
939957

940958
ax = axes[2]
941959
mid_pb_idx = n_pb // 2
942-
ax.plot(prey_deaths, grids["prey_pop_no_evo"][:, mid_pb_idx], 'b-o',
943-
label=f'No Evo (pb={prey_births[mid_pb_idx]:.2f})', markersize=4)
944-
ax.plot(prey_deaths, grids["prey_pop_evo"][:, mid_pb_idx], 'g-s',
945-
label=f'With Evo (pb={prey_births[mid_pb_idx]:.2f})', markersize=4)
960+
target_pb = prey_births[mid_pb_idx]
961+
no_evo_slice = grids["prey_pop_no_evo"][:, mid_pb_idx]
962+
evo_slice = grids["prey_pop_evo"][:, mid_pb_idx]
963+
964+
ax.plot(prey_deaths, no_evo_slice, 'b-o',
965+
label=f'No Evo (pb={target_pb:.2f})', markersize=4)
966+
ax.plot(prey_deaths, evo_slice, 'g-s',
967+
label=f'With Evo (pb={target_pb:.2f})', markersize=4)
968+
946969
ax.set_xlabel("Prey Death Rate")
947970
ax.set_ylabel("Prey Population")
948-
ax.set_title("Prey Pop vs Death Rate Slice")
971+
ax.set_title(f"Prey Pop vs Death Rate (pb={target_pb:.2f})") # Dynamic title
949972
ax.legend()
950973
ax.grid(True, alpha=0.3)
951974

0 commit comments

Comments
 (0)