Skip to content

Commit a3d202c

Browse files
committed
seeding fix and usage guide of analysis file
1 parent 170fdf1 commit a3d202c

2 files changed

Lines changed: 58 additions & 30 deletions

File tree

docs/kimon_prompts.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,5 +359,7 @@ For parallel simulations, use different seeds per worker (e.g., base_seed + work
359359

360360
6. Write a final smoke test for the HPC simulation. Tests module imports. numba kernel, a full mock simulation, the pcf computation, cluster measurement, seeding and the binary roundtrip for saving output.
361361

362+
7. Use the attached legacy simulation function to compute benchmarking resukts for our optimization. Include functionality to save in a csv and plots showing the most significant results. Include flags to run with or without plots and csv output.
362363

363-
7. Use the attached legacy simulation function to compute benchmarking resukts for our optimization. Include functionality to save in a csv and plots showing the most significant results. Include flags to run with or without plots and csv output.
364+
365+
8. Write a few run mock tests for the analysis file to see that the plots render properly.

scripts/pp_analysis.py

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
python pp_analysis.py --mode plot # Only generate plots from saved data
2020
python pp_analysis.py --mode debug # Interactive visualization (local only)
2121
python scripts/pp_analysis.py --dry-run # Estimate runtime without running
22+
23+
24+
Stage 1: Discovery --mode sweep
25+
26+
Stage 2: Targeted FSS
27+
Obtain critical_prey_death and critical_prey_death.
28+
Update the config with target_prey_birth and target_birth_death
29+
30+
Run FSS mode: python pp_analysis.py --mode fss
2231
"""
2332

2433
import argparse
@@ -127,6 +136,10 @@ def cluster_interval(self) -> int:
127136
# Min density required for PCF/Clsuter Analysis
128137
min_analysis_density: float = 0.002 # FIXME: Minimum prey density (fraction of grid) to analyze clusters/PCF
129138

139+
140+
target_prey_birth: float = 0.22 # FIXME: Change after obtaining results
141+
target_prey_death: float = 0.04 # FIXME; Change after obtaining results
142+
130143
# Parallelization
131144
n_jobs: int = -1
132145

@@ -399,7 +412,7 @@ def run_single_simulation(
399412
pred_clusters.extend(measure_cluster_sizes_fast(model.grid, 2))
400413

401414
# Compute PCFs if enabled for this run
402-
if compute_pcf and prey > 20 and pred > 5:
415+
if compute_pcf:
403416
max_dist = min(grid_size / 2, cfg.pcf_max_distance)
404417
pcf_data = compute_all_pcfs_fast(model.grid, max_dist, cfg.pcf_n_bins)
405418
pcf_samples['prey_prey'].append(pcf_data['prey_prey'])
@@ -576,7 +589,7 @@ def run_single_simulation_fss(
576589
# =============================================================================
577590

578591
def run_2d_sweep(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[Dict]:
579-
"""Run full 2D parameter sweep."""
592+
"""Run full 2D parameter sweep with incremental JSONL saving."""
580593
from joblib import Parallel, delayed
581594

582595
if USE_NUMBA:
@@ -590,52 +603,65 @@ def run_2d_sweep(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[
590603
for pb in prey_births:
591604
for pd in prey_deaths:
592605
for rep in range(cfg.n_replicates):
606+
# Unique seed for standard run
593607
seed = generate_unique_seed(pb, pd, rep)
594-
# Both with and without evolution
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))
597-
598-
logger.info(f"2D Sweep: {len(jobs):,} simulations")
599-
logger.info(f" Grid: {len(prey_births)}×{len(prey_deaths)} parameters")
600-
logger.info(f" prey_birth: [{cfg.prey_birth_min:.3f}, {cfg.prey_birth_max:.3f}]")
601-
logger.info(f" prey_death: [{cfg.prey_death_min:.3f}, {cfg.prey_death_max:.3f}]")
602-
logger.info(f" Replicates: {cfg.n_replicates}")
603-
logger.info(f" PCF sample rate: {cfg.pcf_sample_rate:.0%}")
604-
605-
results = Parallel(n_jobs=cfg.n_jobs, verbose=0)(
606-
delayed(run_single_simulation)(pb, pd, gs, seed, evo, cfg)
607-
for pb, pd, gs, seed, evo in tqdm(jobs, desc="2D Sweep Progress", mininterval=30)
608-
)
608+
jobs.append((pb, pd, cfg.default_grid, seed, False))
609+
610+
# Different unique seed for evolutionary run
611+
evo_seed = generate_unique_seed(pb, pd, rep + 1000000)
612+
jobs.append((pb, pd, cfg.default_grid, evo_seed, True))
609613

610-
# Save results
611-
output_file = output_dir / "sweep_results.npz"
612-
save_sweep_binary(results, output_file)
614+
output_jsonl = output_dir / "sweep_results.jsonl"
615+
logger.info(f"Starting sweep: {len(jobs):,} simulations")
616+
logger.info(f"Incremental results will be saved to {output_jsonl}")
617+
618+
all_results = []
619+
620+
# Using 'return_as="generator"' allows us to save as each job finishes
621+
# This prevents data loss if the 72-hour limit is reached early
622+
with open(output_jsonl, "a", encoding="utf-8") as f:
623+
# Create the parallel executor
624+
executor = Parallel(n_jobs=cfg.n_jobs, return_as="generator")
625+
tasks = (delayed(run_single_simulation)(pb, pd, gs, seed, evo, cfg)
626+
for pb, pd, gs, seed, evo in jobs)
627+
628+
# Iterate through completed results
629+
for result in tqdm(executor(tasks), total=len(jobs), desc="2D Sweep Progress"):
630+
# 1. Save to JSONL immediately (Safety)
631+
f.write(json.dumps(result) + "\n")
632+
f.flush() # Force write to disk
633+
634+
# 2. Store in memory for return/binary save (Optimization)
635+
all_results.append(result)
636+
637+
output_npz = output_dir / "sweep_results.npz"
638+
save_sweep_binary(all_results, output_npz)
613639

614640
meta = {
615-
"n_sims": len(results),
641+
"n_sims": len(all_results),
616642
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
617643
"grid_size": cfg.default_grid,
618644
"pcf_sample_rate": cfg.pcf_sample_rate,
619645
}
620646
with open(output_dir / "sweep_metadata.json", "w") as f:
621647
json.dump(meta, f, indent=2)
622648

623-
logger.info(f"Saved sweep results to {output_file}")
624-
return results
649+
logger.info(f"Sweep complete. Binary data saved to {output_npz}")
650+
return all_results
625651

626652

627653
def run_sensitivity(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[Dict]:
628654
"""Run evolution parameter sensitivity analysis."""
629655
from joblib import Parallel, delayed
630656

631657
# Fixed parameters in transition zone
632-
pb_test = 0.20
633-
pd_test = 0.05
658+
pb_test = cfg.target_prey_birth
659+
pd_test = cfg.target_prey_death
634660

635661
jobs = []
636662
for sd in cfg.sensitivity_sd_values:
637663
for rep in range(cfg.sensitivity_replicates):
638-
seed = int(sd * 100000) + rep
664+
seed = generate_unique_seed(pb_test, pd_test, rep + 2000000)
639665
jobs.append((pb_test, pd_test, cfg.default_grid, seed, True, sd))
640666

641667
logger.info(f"Sensitivity: {len(jobs)} simulations")
@@ -659,8 +685,8 @@ def run_fss(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[Dict]
659685
from joblib import Parallel, delayed
660686

661687
# Fixed parameters near critical point
662-
pb_test = 0.20
663-
pd_test = 0.03
688+
pb_test = cfg.target_prey_birth
689+
pd_test = cfg.target_prey_death
664690

665691
# Validation
666692
logger.info("=" * 60)
@@ -690,7 +716,7 @@ def run_fss(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[Dict]
690716
measurement_steps = int(cfg.measurement_steps * warmup_factor)
691717

692718
for rep in range(cfg.fss_replicates):
693-
seed = L * 1000 + rep
719+
seed = generate_unique_seed(pb_test, pd_test, rep + 2000000)
694720
jobs.append((pb_test, pd_test, L, seed, warmup_steps, measurement_steps))
695721

696722
logger.info(f"FSS: {len(jobs)} simulations")

0 commit comments

Comments
 (0)