3131from pathlib import Path
3232from typing import Dict , List , Tuple , Optional
3333import warnings
34+ from tqdm import tqdm
3435
3536project_root = str (Path (__file__ ).parents [1 ])
3637if project_root not in sys .path :
@@ -83,7 +84,7 @@ class Config:
8384 predator_birth : float = 0.2
8485
8586 # Replicates
86- n_replicates : int = 50
87+ n_replicates : int = 15
8788
8889 # Simulation timing
8990 warmup_steps : int = 200
@@ -130,22 +131,36 @@ def get_prey_births(self) -> np.ndarray:
130131 return np .linspace (self .prey_birth_min , self .prey_birth_max , self .n_prey_birth )
131132
132133 def estimate_runtime (self , n_cores : int = 32 ) -> str :
133- """Estimate total runtime."""
134+ """Estimate total runtime based on benchmark data ."""
134135 n_sweep = self .n_prey_birth * self .n_prey_death * self .n_replicates * 2
135136 n_sens = len (self .sensitivity_sd_values ) * self .sensitivity_replicates
136137
137- # Base time per simulation (with optimized PCF)
138- base_time_s = 0.8 # ~800ms with cell-list PCF
139- pcf_overhead = 0.1 if self .collect_pcf else 0 # Much smaller with cell-list
138+ # --- Scaling Logic ---
139+ # Benchmark shows 1182 steps/sec for 100x100 grid
140+ ref_size = 100
141+ ref_steps_per_sec = 1182
140142
143+ # Scale throughput by area (L^2)
144+ # A 1000x1000 grid is (1000/100)^2 = 100x slower per step
145+ size_scaling = (self .default_grid / ref_size ) ** 2
146+ actual_steps_per_sec = ref_steps_per_sec / size_scaling
147+
148+ # Calculate time for one full simulation (warmup + measurement)
149+ total_steps_per_sim = self .warmup_steps + self .measurement_steps
150+ base_time_s = total_steps_per_sim / actual_steps_per_sec
151+
152+ # Account for PCF overhead (Cell-list PCF is ~8ms for 100x100)
153+ pcf_time_s = (0.008 * size_scaling ) if self .collect_pcf else 0
154+ # ---------------------
155+
141156 # FSS with size scaling
142157 fss_time = 0
143158 for L in self .fss_grid_sizes :
144- scale = (L / self .default_grid ) ** 2
145- warmup_scale = L / self .default_grid
146- fss_time += self .fss_replicates * base_time_s * scale * warmup_scale
159+ l_scale = (L / self .default_grid ) ** 2
160+ l_warmup_scale = L / self .default_grid # Time also scales with warmup duration
161+ fss_time += self .fss_replicates * base_time_s * l_scale * l_warmup_scale
147162
148- sweep_time = n_sweep * (base_time_s + pcf_overhead * self .pcf_sample_rate )
163+ sweep_time = n_sweep * (base_time_s + pcf_time_s * self .pcf_sample_rate )
149164 sens_time = n_sens * base_time_s
150165
151166 total_seconds = (sweep_time + sens_time + fss_time ) / n_cores
@@ -154,8 +169,6 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
154169
155170 n_total = n_sweep + n_sens + sum (self .fss_replicates for _ in self .fss_grid_sizes )
156171 return f"{ n_total :,} sims, ~{ hours :.1f} h on { n_cores } cores (~{ core_hours :.0f} core-hours)"
157-
158-
159172# =============================================================================
160173# HELPER FUNCTIONS
161174# =============================================================================
@@ -571,10 +584,10 @@ def run_2d_sweep(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[
571584 logger .info (f" Replicates: { cfg .n_replicates } " )
572585 logger .info (f" PCF sample rate: { cfg .pcf_sample_rate :.0%} " )
573586
574- results = Parallel (n_jobs = cfg .n_jobs , verbose = 10 )(
587+ results = Parallel (n_jobs = cfg .n_jobs , verbose = 0 )(
575588 delayed (run_single_simulation )(pb , pd , gs , seed , evo , cfg )
576- for pb , pd , gs , seed , evo in jobs
577- )
589+ for pb , pd , gs , seed , evo in tqdm ( jobs , desc = "2D Sweep Progress" , mininterval = 30 )
590+ )
578591
579592 # Save results
580593 output_file = output_dir / "sweep_results.npz"
@@ -610,9 +623,9 @@ def run_sensitivity(cfg: Config, output_dir: Path, logger: logging.Logger) -> Li
610623 logger .info (f"Sensitivity: { len (jobs )} simulations" )
611624 logger .info (f" SD values: { cfg .sensitivity_sd_values } " )
612625
613- results = Parallel (n_jobs = cfg .n_jobs , verbose = 5 )(
626+ results = Parallel (n_jobs = cfg .n_jobs , verbose = 0 )(
614627 delayed (run_single_simulation )(pb , pd , gs , seed , evo , cfg , evolve_sd = sd , compute_pcf = True )
615- for pb , pd , gs , seed , evo , sd in jobs
628+ for pb , pd , gs , seed , evo , sd in tqdm ( jobs , desc = "Sensitivity Progress" , mininterval = 10 )
616629 )
617630
618631 output_file = output_dir / "sensitivity_results.json"
@@ -665,9 +678,9 @@ def run_fss(cfg: Config, output_dir: Path, logger: logging.Logger) -> List[Dict]
665678 logger .info (f"FSS: { len (jobs )} simulations" )
666679 logger .info (f" Grid sizes: { cfg .fss_grid_sizes } " )
667680
668- results = Parallel (n_jobs = cfg .n_jobs , verbose = 5 )(
681+ results = Parallel (n_jobs = cfg .n_jobs , verbose = 0 )(
669682 delayed (run_single_simulation_fss )(pb , pd , gs , seed , cfg , ws , ms )
670- for pb , pd , gs , seed , ws , ms in jobs
683+ for pb , pd , gs , seed , ws , ms in tqdm ( jobs , desc = "FSS Progress" , mininterval = 10 )
671684 )
672685
673686 output_file = output_dir / "fss_results.json"
0 commit comments