@@ -79,69 +79,6 @@ def average_min_peak_distance(population: NDArray, peaks: NDArray) -> float:
7979 return float (np .mean (nearest ))
8080
8181
82- def nearest_peak_stats (population : NDArray , peaks : NDArray ) -> tuple [NDArray , NDArray ]:
83- """Return nearest distances and peak indices for each individual."""
84- distances = np .linalg .norm (population [:, None , :] - peaks [None , :, :], axis = 2 )
85- nearest_idx = np .argmin (distances , axis = 1 )
86- nearest_dist = distances [np .arange (len (population )), nearest_idx ]
87- return nearest_dist , nearest_idx
88-
89-
90- def chi_square_p_value (statistic : float , dof : int ) -> float :
91- """Right-tail p-value for chi-square using normal approximation (mean=k, var=2k)."""
92- if dof <= 0 :
93- return 1.0
94- mean = dof
95- std = math .sqrt (2 * dof )
96- z = (statistic - mean ) / std
97- return 1.0 - 0.5 * (1.0 + math .erf (z / math .sqrt (2 )))
98-
99-
100- def estimate_expected_peak_probs (peaks : NDArray , * , sample_size : int = 50_000 , seed : int | None = 123 ) -> NDArray :
101- """Estimate nearest-peak probabilities for an initial N(0, I) population via Monte Carlo."""
102- rng = np .random .default_rng (seed )
103- samples = rng .standard_normal ((sample_size , peaks .shape [1 ]))
104- distances = np .linalg .norm (samples [:, None , :] - peaks [None , :, :], axis = 2 )
105- nearest_idx = np .argmin (distances , axis = 1 )
106- counts = np .bincount (nearest_idx , minlength = len (peaks ))
107- probs = counts / np .sum (counts )
108-
109- # Avoid zero probability due to sampling noise; renormalize after floor.
110- probs = np .maximum (probs , 1e-12 )
111- probs /= np .sum (probs )
112- return probs
113-
114-
115- def estimate_expected_peak_probs_conditioned (
116- peaks : NDArray ,
117- threshold : float ,
118- * ,
119- sample_size : int = 50_000 ,
120- seed : int | None = 123 ,
121- ) -> tuple [NDArray , int ]:
122- """Estimate nearest-peak probabilities conditioned on being within the threshold of a peak."""
123- rng = np .random .default_rng (seed )
124- samples = rng .standard_normal ((sample_size , peaks .shape [1 ]))
125- distances = np .linalg .norm (samples [:, None , :] - peaks [None , :, :], axis = 2 )
126- nearest_idx = np .argmin (distances , axis = 1 )
127- nearest_dist = distances [np .arange (len (samples )), nearest_idx ]
128- mask = nearest_dist <= threshold
129- kept_idx = nearest_idx [mask ]
130- kept_total = len (kept_idx )
131-
132- if kept_total == 0 :
133- raise RuntimeError (
134- "Fairness baseline failed: zero Monte Carlo samples fell within the assignment threshold. "
135- "Increase --fair-samples or loosen the threshold."
136- )
137-
138- counts = np .bincount (kept_idx , minlength = len (peaks ))
139- probs = counts / kept_total
140- probs = np .maximum (probs , 1e-12 )
141- probs /= np .sum (probs )
142- return probs , kept_total
143-
144-
14582def render_population (
14683 population : NDArray ,
14784 peaks : NDArray ,
@@ -191,9 +128,6 @@ def run_multi_peak(
191128 normalization : NormalType = NormalType .SUM_TO_ONE ,
192129 min_improvement_ratio : float = 2.0 ,
193130 temperature : float = 0.25 ,
194- fairness_alpha : float = 0.05 ,
195- min_expected_per_peak : int = 5 ,
196- fairness_sample_size : int = 50_000 ,
197131) -> None :
198132 """Run diffusion evolution and assert convergence for each target peak."""
199133 peaks = create_peak_positions (num_peaks , seed = peak_seed )
@@ -217,21 +151,19 @@ def run_multi_peak(
217151 initial_avg_distance = average_min_peak_distance (initial_population , peaks )
218152
219153 final_population = algo .run (initial_population )
220- final_distances , nearest_peaks = nearest_peak_stats (final_population , peaks )
221- final_avg_distance = float (np .mean (final_distances ))
222- final_std_distance = float (np .std (final_distances ))
154+ final_avg_distance = average_min_peak_distance (final_population , peaks )
223155 improvement_ratio = math .inf if final_avg_distance == 0 else initial_avg_distance / final_avg_distance
224156
225157 flags = verify_convergence (final_population , peaks , tolerance = convergence_radius )
226158
227159 for idx , success in enumerate (flags , start = 1 ):
228- status = "✅ " if success else "❌ "
160+ status = "PASS " if success else "FAIL "
229161 peak_coords = peaks [idx - 1 ]
230162 print (f"{ status } Peak { idx } : ({ peak_coords [0 ]:+.3f} , { peak_coords [1 ]:+.3f} )" )
231163
232164 print (
233165 "Average nearest-peak distance: "
234- f"start { initial_avg_distance :.3f} -> end { final_avg_distance :.3f} (std { final_std_distance :.3f } ) "
166+ f"start { initial_avg_distance :.3f} -> end { final_avg_distance :.3f} "
235167 )
236168 print (f"Improvement ratio (start/end): { improvement_ratio :.2f} x" )
237169
@@ -247,52 +179,14 @@ def run_multi_peak(
247179 f"[start { initial_avg_distance :.3f} , end { final_avg_distance :.3f} ]"
248180 )
249181
250- assignment_threshold = final_avg_distance + final_std_distance
251- assigned_mask = final_distances <= assignment_threshold
252- assigned_indices = nearest_peaks [assigned_mask ]
253- assigned_counts = np .bincount (assigned_indices , minlength = num_peaks )
254- assigned_total = int (np .sum (assigned_counts ))
255-
256- expected_probs , baseline_total = estimate_expected_peak_probs_conditioned (
257- peaks ,
258- assignment_threshold ,
259- sample_size = fairness_sample_size ,
260- seed = peak_seed ,
261- )
262- expected_counts = expected_probs * assigned_total
263-
264- if np .any (expected_counts < min_expected_per_peak ):
265- smallest = float (np .min (expected_counts ))
266- raise RuntimeError (
267- "Population too small for fair-spread check with biased expectations: "
268- f"smallest expected count { smallest :.2f} (<{ min_expected_per_peak } ). "
269- "Increase population size or lower --min-expected."
270- )
271-
272- chi_square_stat = float (np .sum ((assigned_counts - expected_counts ) ** 2 / expected_counts ))
273- chi_dof = num_peaks - 1
274- chi_p_value = chi_square_p_value (chi_square_stat , chi_dof )
275-
276- print (
277- f"Fairness check: threshold { assignment_threshold :.3f} , assigned { assigned_total } individuals; "
278- f"expected probs (conditioned) { np .round (expected_probs , 4 ).tolist ()} from { baseline_total } baseline samples; "
279- f"counts per peak { assigned_counts .tolist ()} , chi2={ chi_square_stat :.3f} , dof={ chi_dof } , p={ chi_p_value :.3f} "
280- )
281-
282- if chi_p_value < fairness_alpha :
283- raise RuntimeError (
284- f"Unbalanced allocation across peaks (p={ chi_p_value :.3f} < { fairness_alpha :.3f} ); "
285- "population collapsed unevenly relative to expected bias."
286- )
287-
288182 print (
289- f"Achieved { improvement_ratio :.2f} x improvement on average nearest-peak distance "
290- f"(target: { min_improvement_ratio :.2f} x) and passed fairness check (p= { chi_p_value :.3f } ) ."
183+ f"SUCCESS: Achieved { improvement_ratio :.2f} x improvement on average nearest-peak distance "
184+ f"(target: { min_improvement_ratio :.2f} x)."
291185 )
292186
293187
294188def parse_args (argv : Sequence [str ] | None = None ) -> argparse .Namespace :
295- parser = argparse .ArgumentParser (description = "Prime the diffusion evolution algorithm on N peaks." )
189+ parser = argparse .ArgumentParser (description = "Smoke test for diffusion evolution on N peaks." )
296190 parser .add_argument ("num_peaks" , type = int , help = "Number of target peaks (>=1)" )
297191 parser .add_argument ("-o" , "--plot" , type = Path , default = None , help = "Optional path to save a contour visualization." )
298192 parser .add_argument ("-p" , "--population" , type = int , default = 512 , help = "Population size (default: 512)" )
@@ -341,25 +235,6 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
341235 default = 2.0 ,
342236 help = "Required improvement ratio of average nearest-peak distance (start/end). Default: 2.0x" ,
343237 )
344- parser .add_argument (
345- "-a" ,
346- "--alpha" ,
347- type = float ,
348- default = 0.05 ,
349- help = "Significance level for chi-square fairness test (default: 0.05)." ,
350- )
351- parser .add_argument (
352- "--min-expected" ,
353- type = int ,
354- default = 5 ,
355- help = "Minimum expected count per peak to run chi-square test (default: 5)." ,
356- )
357- parser .add_argument (
358- "--fair-samples" ,
359- type = int ,
360- default = 50_000 ,
361- help = "Sample size for Monte Carlo estimation of biased peak probabilities (default: 50k)." ,
362- )
363238 return parser .parse_args (argv )
364239
365240
@@ -377,9 +252,6 @@ def main(argv: Sequence[str] | None = None) -> None:
377252 normalization = args .normalize ,
378253 min_improvement_ratio = args .improvement ,
379254 temperature = args .temperature ,
380- fairness_alpha = args .alpha ,
381- min_expected_per_peak = args .min_expected ,
382- fairness_sample_size = args .fair_samples ,
383255 )
384256 except RuntimeError as exc : # Ensure CI failure on missed peaks.
385257 print (exc )
0 commit comments