6464@dataclass
6565class Config :
6666 """Central configuration for all experiments."""
67-
67+
6868 # Grid settings
69- grid_size : int = 1000 #FIXME: Decide default configuration
70- densities : Tuple [float , float ] = (0.30 , 0.15 ) # (prey, predator) #FIXME: Default densities
71-
69+ grid_size : int = 1000 # FIXME: Decide default configuration
70+ densities : Tuple [float , float ] = (
71+ 0.30 ,
72+ 0.15 ,
73+ ) # (prey, predator) #FIXME: Default densities
74+
7275 # For FSS experiments: multiple grid sizes
7376 grid_sizes : Tuple [int , ...] = (50 , 100 , 250 , 500 , 1000 , 2500 )
74-
77+
7578 # Default/fixed parameters
7679 prey_birth : float = 0.2
7780 prey_death : float = 0.05
78- predator_birth : float = 0.8 # FIXME: Default predator death rate
79- predator_death : float = 0.05 # FIXME: Default predator death rate
80-
81+ predator_birth : float = 0.8 # FIXME: Default predator death rate
82+ predator_death : float = 0.05 # FIXME: Default predator death rate
83+
8184 # Critical point (UPDATE AFTER PHASE 1)
82- critical_prey_birth : float = 0.20
85+ critical_prey_birth : float = 0.20
8386 critical_prey_death : float = 0.947
84-
87+
8588 # Prey parameter sweep (Phase 1)
8689 prey_death_range : Tuple [float , float ] = (0.0 , 0.2 )
87- n_prey_birth : int = 15 # FIXME: Decide number of grid points along prey axes
90+ n_prey_birth : int = 15 # FIXME: Decide number of grid points along prey axes
8891 n_prey_death : int = 5
89-
92+
9093 # Predator parameter sweep (Phase 4 sensitivity)
91- predator_birth_values : Tuple [float , ...] = (0.15 , 0.20 , 0.25 , 0.30 ) #FIXME: Bogus values for now
92- predator_death_values : Tuple [float , ...] = (0.05 , 0.10 , 0.15 , 0.20 ) #FIXME: Bogus values for now
93-
94+ predator_birth_values : Tuple [float , ...] = (
95+ 0.15 ,
96+ 0.20 ,
97+ 0.25 ,
98+ 0.30 ,
99+ ) # FIXME: Bogus values for now
100+ predator_death_values : Tuple [float , ...] = (
101+ 0.05 ,
102+ 0.10 ,
103+ 0.15 ,
104+ 0.20 ,
105+ ) # FIXME: Bogus values for now
106+
94107 # Perturbation offsets from critical point (Phase 5)
95- prey_death_offsets : Tuple [float , ...] = (- 0.02 , - 0.01 , 0.0 , 0.01 , 0.02 ) #FIXME: Bogus values for now
108+ prey_death_offsets : Tuple [float , ...] = (
109+ - 0.02 ,
110+ - 0.01 ,
111+ 0.0 ,
112+ 0.01 ,
113+ 0.02 ,
114+ ) # FIXME: Bogus values for now
96115
97116 # Number of replicates per parameter configuration
98- n_replicates : int = 15 # FIXME: Decide number of indep. runs per parameter config
99-
117+ n_replicates : int = 15 # FIXME: Decide number of indep. runs per parameter config
118+
100119 # Simulation steps
101120 warmup_steps : int = 300 # FIXME: Steps to run before measuring
102- measurement_steps : int = 500 # FIXME: Decide measurement steps
103-
121+ measurement_steps : int = 500 # FIXME: Decide measurement steps
122+
104123 # Evo
105124 with_evolution : bool = False
106125 evolve_sd : float = 0.10
107126 evolve_min : float = 0.0
108127 evolve_max : float = 0.10
109-
128+
110129 # Sensitivity: mutation strength values to test
111- sensitivity_sd_values : Tuple [float , ...] = (0.02 , 0.05 , 0.10 , 0.15 , 0.20 ) #FIXME: Don't know if we use yet
112-
130+ sensitivity_sd_values : Tuple [float , ...] = (
131+ 0.02 ,
132+ 0.05 ,
133+ 0.10 ,
134+ 0.15 ,
135+ 0.20 ,
136+ ) # FIXME: Don't know if we use yet
137+
113138 # Update mode
114139 synchronous : bool = False # Always False for this model
115140 directed_hunting : bool = False
116-
141+
117142 # For Phase 6: compare model variants
118143 directed_hunting_values : Tuple [bool , ...] = (False , True )
119-
144+
120145 # Temporal data collection (time series)
121146 save_timeseries : bool = False
122147 timeseries_subsample : int = 10 # FIXME: Save every how many steps
123-
148+
124149 # PCF settings
125150 collect_pcf : bool = True
126151 pcf_sample_rate : float = 0.2 # Fraction of runs to compute PCF
127152 pcf_max_distance : float = 20.0
128153 pcf_n_bins : int = 20
129-
154+
130155 # Cluster analysis
131- min_density_for_analysis : float = 0.002 # FIXME: Minimum prey density (fraction of grid) to analyze clusters/PCF
132-
156+ min_density_for_analysis : float = (
157+ 0.002 # FIXME: Minimum prey density (fraction of grid) to analyze clusters/PCF
158+ )
159+
133160 # Perturbation settings (Phase 5)
134- perturbation_magnitude : float = 0.1 # FIXME: Fractional change to apply at perturbation time
135-
161+ perturbation_magnitude : float = (
162+ 0.1 # FIXME: Fractional change to apply at perturbation time
163+ )
164+
136165 # Parallelization
137- n_jobs : int = - 1 # Use all available cores by default
138-
166+ n_jobs : int = - 1 # Use all available cores by default
167+
139168 # Helpers
140169 def get_prey_births (self ) -> np .ndarray :
141170 """Generate prey birth rate sweep values."""
142- return np .linspace (self .prey_birth_range [0 ], self .prey_birth_range [1 ], self .n_prey_birth )
143-
171+ return np .linspace (
172+ self .prey_birth_range [0 ], self .prey_birth_range [1 ], self .n_prey_birth
173+ )
174+
144175 def get_prey_deaths (self ) -> np .ndarray :
145176 """Generate prey death rate sweep values."""
146- return np .linspace (self .prey_death_range [0 ], self .prey_death_range [1 ], self .n_prey_death )
147-
148- def get_warmup_steps (self , L : int ) -> int : #FIXME: This method will be updated depending on Sary's results.
177+ return np .linspace (
178+ self .prey_death_range [0 ], self .prey_death_range [1 ], self .n_prey_death
179+ )
180+
181+ def get_warmup_steps (
182+ self , L : int
183+ ) -> int : # FIXME: This method will be updated depending on Sary's results.
149184 """Scale warmup with grid size."""
150185 return self .warmup_steps
151-
186+
152187 def get_measurement_steps (self , L : int ) -> int :
153188 """Scale measurement with grid size."""
154189 return self .measurement_steps
155-
190+
156191 def estimate_runtime (self , n_cores : int = 32 ) -> str :
157192 """Estimate total runtime based on benchmark data."""
158193 # Benchmark: ~1182 steps/sec for 100x100 grid
159194 ref_size = 100
160195 ref_steps_per_sec = 1182
161-
196+
162197 size_scaling = (self .grid_size / ref_size ) ** 2
163198 actual_steps_per_sec = ref_steps_per_sec / size_scaling
164-
199+
165200 total_steps = self .warmup_steps + self .measurement_steps
166201 base_time_s = total_steps / actual_steps_per_sec
167-
202+
168203 # PCF overhead (~8ms for 100x100)
169204 pcf_time_s = (0.008 * size_scaling ) if self .collect_pcf else 0
170-
205+
171206 # Count simulations
172207 n_sims = self .n_prey_birth * self .n_prey_death * self .n_replicates
173208 if self .with_evolution :
174209 n_sims *= 2 # Both evo and non-evo runs
175-
210+
176211 total_seconds = n_sims * (base_time_s + pcf_time_s * self .pcf_sample_rate )
177212 total_seconds /= n_cores
178-
213+
179214 hours = total_seconds / 3600
180215 core_hours = n_sims * (base_time_s + pcf_time_s * self .pcf_sample_rate ) / 3600
181-
216+
182217 return f"{ n_sims :,} sims, ~{ hours :.1f} h on { n_cores } cores (~{ core_hours :.0f} core-hours)"
183218
184219
185220############################################################################################
186221# Experimental Phase Configurations
187222############################################################################################
188223
189- #FIXME: These configs are arbitraty and should be finalized before running experiments.
224+ # FIXME: These configs are arbitraty and should be finalized before running experiments.
190225
191226PHASE1_CONFIG = Config (
192227 grid_size = 1000 ,
193228 n_prey_death = 20 ,
194229 prey_birth = 0.2 ,
195230 prey_death_range = (0.0963 , 0.0973 ),
196- predator_birth = 0.8 ,
197- predator_death = 0.05 ,
231+ predator_birth = 0.8 ,
232+ predator_death = 0.05 ,
198233 n_replicates = 30 ,
199234 warmup_steps = 1000 ,
200235 measurement_steps = 1000 ,
@@ -209,17 +244,15 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
209244 grid_size = 1000 ,
210245 n_prey_birth = 1 , # Fixed at cfg.prey_birth (0.2)
211246 n_replicates = 10 ,
212- warmup_steps = 1000 , # Shorter warmup (evolution starts immediately)
213- measurement_steps = 10000 , # Longer measurement to see convergence
214-
247+ warmup_steps = 1000 , # Shorter warmup (evolution starts immediately)
248+ measurement_steps = 10000 , # Longer measurement to see convergence
215249 # Evolution settings
216250 with_evolution = True ,
217- evolve_sd = 0.01 , # Smaller mutation rate for smoother convergence
251+ evolve_sd = 0.01 , # Smaller mutation rate for smoother convergence
218252 evolve_min = 0.0 ,
219- evolve_max = 0.20 , # Allow full range
220-
253+ evolve_max = 0.20 , # Allow full range
221254 collect_pcf = False ,
222- save_timeseries = False , # Track evolution trajectory
255+ save_timeseries = False , # Track evolution trajectory
223256)
224257
225258# Phase 3: Finite-size scaling at critical point
@@ -228,8 +261,8 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
228261 n_replicates = 20 ,
229262 warmup_steps = 1000 ,
230263 measurement_steps = 1000 ,
231- critical_prey_birth = 0.20 , # Add explicitly
232- critical_prey_death = 0.947 , # Add explicitly - verify from Phase 1!
264+ critical_prey_birth = 0.20 , # Add explicitly
265+ critical_prey_death = 0.947 , # Add explicitly - verify from Phase 1!
233266 collect_pcf = True ,
234267 pcf_sample_rate = 1.0 ,
235268 save_timeseries = False ,
@@ -239,9 +272,9 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
239272
240273# Phase 4: Sensitivity analysis
241274PHASE4_CONFIG = Config (
242- grid_size = 250 , # As requested
243- n_replicates = 10 , # As requested
244- warmup_steps = 500 , # As requested
275+ grid_size = 250 , # As requested
276+ n_replicates = 10 , # As requested
277+ warmup_steps = 500 , # As requested
245278 measurement_steps = 500 , # As requested
246279 with_evolution = False ,
247280 collect_pcf = False ,
@@ -254,7 +287,7 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
254287# Phase 5: Perturbation analysis (critical slowing down)
255288PHASE5_CONFIG = Config (
256289 grid_size = 100 ,
257- prey_death_offsets = (- 0.02 , - 0.01 , 0.0 , 0.01 , 0.02 ), # FIXME: Is this what we vary?
290+ prey_death_offsets = (- 0.02 , - 0.01 , 0.0 , 0.01 , 0.02 ), # FIXME: Is this what we vary?
258291 n_replicates = 20 ,
259292 warmup_steps = 500 ,
260293 measurement_steps = 2000 ,
@@ -286,8 +319,11 @@ def estimate_runtime(self, n_cores: int = 32) -> str:
286319 6 : PHASE6_CONFIG ,
287320}
288321
322+
289323def get_phase_config (phase : int ) -> Config :
290324 """Get config for a specific phase."""
291325 if phase not in PHASE_CONFIGS :
292- raise ValueError (f"Unknown phase { phase } . Valid phases: { list (PHASE_CONFIGS .keys ())} " )
293- return PHASE_CONFIGS [phase ]
326+ raise ValueError (
327+ f"Unknown phase { phase } . Valid phases: { list (PHASE_CONFIGS .keys ())} "
328+ )
329+ return PHASE_CONFIGS [phase ]
0 commit comments