Skip to content

Commit 3361619

Browse files
committed
🧹 Simplify smoke test by removing fairness check
- Remove chi-square fairness check that was too strict for CI purposes - Keep improvement ratio as the primary correctness gate (start/end distance) - Remove unused functions: nearest_peak_stats, chi_square_p_value, estimate_expected_peak_probs - Clean up CLI args: remove --alpha, --min-expected, --fair-samples
1 parent 29cb159 commit 3361619

1 file changed

Lines changed: 6 additions & 134 deletions

File tree

‎tests/ci/n_peaks.py‎

Lines changed: 6 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
14582
def 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

294188
def 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

Comments
 (0)