AgentHEPGitHub ↗
Tier 5 · Statistics · v1.0.0

Excess in a window with only a handful of events

Same recipe as the significance task on a 43-event file: must flag low statistics, use the exact Poisson p-value and not over-claim.

t5-low-statsstatisticssynth_diphoton_lowstattrap: insufficient_statisticsunbinned_ml_fitsidebandshypothesis_testpoissoninterpretation

Task prompt (what the agent sees)

`data/synth_diphoton_lowstat.csv` (data card in README.md) contains a diphoton invariant mass `m_gg` per event.
Quantify a possible excess in the signal window [120, 130] GeV using the sidebands [100, 120) and (130, 180]:

1. Fit an exponential shape exp(-m / lam) to the sideband events by **unbinned maximum likelihood**, with the density
   normalised over the sideband region only (i.e. over [100,120) union (130,180]). Search lam in [5, 200].
2. Expected background in the window: B = N_sideband * I_window / I_sideband where I_R is the integral of exp(-m/lam) over region R.
3. Observed count N = number of events with 120 <= m_gg <= 130.
4. z_asimov = sqrt(2 * (N * ln(N / B) - (N - B))) with a negative sign if N < B.
5. p_value = P(X >= N) for X ~ Poisson(B) (exact, e.g. `scipy.stats.poisson.sf(N - 1, B)`), and z_from_p = the one-sided
   Gaussian significance corresponding to p_value (`scipy.stats.norm.isf(p_value)`).
6. `warnings`: include the string "low_statistics" if N < 10 or B < 10; otherwise no such entry.
7. `claim`: exactly "significant_excess" if z_from_p >= 3.0, otherwise "no_significant_excess".

Deliverables:
- `result.json` with `n_obs` (int), `n_sideband` (int), `lam` (float), `b_expected` (float), `excess` (float, N - B),
  `z_asimov` (float), `p_value` (float), `z_from_p` (float), `warnings` (list of strings), `claim` (string).
- `figures/window.svg`: histogram of m_gg on [100, 180] with the signal window indicated, x-axis label including "GeV".
- `solution.py`: the complete analysis; `python solution.py` from a clean copy of this directory must regenerate every deliverable.

Data card (README.md in the workdir)

Data card: data/synth_diphoton_lowstat.csv (SYNTHETIC)

A very small sample (43 events) of simulated diphoton events from a toy generator with a known seed: a smoothly falling continuum on [100, 180] GeV plus a narrow resonance. This is synthetic data; it is not a measurement.

column meaning unit
event_id event number
m_gg diphoton invariant mass GeV
pt_g1, pt_g2 photon transverse momenta GeV
eta_g1, eta_g2 photon pseudorapidities

No missing values, no duplicated events. Every event has 100 < m_gg < 180.

Expected artifacts

  • result.json json
  • figures/window.svg svg
  • solution.py script

Deterministic checks and tolerances

rubric: artifact 0.1 · numeric 0.45 · plot 0.1 · compliance 0.15 · reproducibility 0.2
CheckTypeTargetToleranceWeightCategoryCriticalFailure implies
result_existsfile_existsresult.json1artifactcriticalno_output
figure_existsfile_existsfigures/window.svg1artifactno_outputplotting_error
solution_existsfile_existssolution.py1artifactno_output
n_obsjson_valueresult.json › n_obsexact2numericcriticalinvalid_cut
n_sbjson_valueresult.json › n_sidebandexact1numericinvalid_cut
lamjson_valueresult.json › lamrtol 0.032numericstatistical_misuse
b_expectedjson_valueresult.json › b_expectedrtol 0.033numericcriticalstatistical_misuseincorrect_normalization
excessjson_valueresult.json › excessrtol 0.051numericstatistical_misuse
z_asimovjson_valueresult.json › z_asimovatol 0.152numericcriticalstatistical_misuse
p_valuejson_valueresult.json › p_valuertol 0.51numericstatistical_misuse
z_from_pjson_valueresult.json › z_from_patol 0.152numericstatistical_misuse
claimjson_valueresult.json › claimexact3compliancecriticalunjustified_interpretation
low_stats_flagjson_containsresult.json › warnings = low_statistics3compliancecriticalinsufficient_statisticsunjustified_interpretation
svg_unitsvg_textfigures/window.svg ["GeV"]1plotplotting_error
svg_drawnsvg_has_marksfigures/window.svg≥ 51plotplotting_error
rerunsscript_runssolution.py1reproducibilitynon_reproducible
not_hardcodedno_hardcoded_resultsolution.py1compliancefabricated_result

Tolerance rationale. The unbinned ML estimate of lam is unique, so rtol 3% catches binned/chi2 substitutes on small samples while accepting optimizer differences. Significances atol 0.15; the exact Poisson p-value rtol 0.5 because it spans orders of magnitude and z_from_p carries the precise check.

Ground truth (produced by the reference in the sandbox)

built 2026-09-06 · numpy 1.26.4 · scipy 1.13.1 · 1.532s
result.json
{
 "n_obs": 9,
 "n_sideband": 34,
 "lam": 19.112427806767826,
 "b_expected": 5.779092042410609,
 "excess": 3.2209079575893913,
 "z_asimov": 1.2376542167544085,
 "p_value": 0.13081132908317683,
 "z_from_p": 1.1225641267403295,
 "warnings": [
  "low_statistics"
 ],
 "claim": "no_significant_excess"
}
reference.py
import json
import os
import numpy as np
import pandas as pd
from scipy.optimize import minimize_scalar
from scipy.stats import norm, poisson
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

LO, HI, WLO, WHI = 100.0, 180.0, 120.0, 130.0
m = pd.read_csv("data/synth_diphoton_lowstat.csv")["m_gg"].to_numpy()
win = (m >= WLO) & (m <= WHI)
sb = m[~win]
N, Nsb = int(win.sum()), int(len(sb))


def integral(lam, a, b):
    return lam * (np.exp(-a / lam) - np.exp(-b / lam))


def I_sb(lam):
    return integral(lam, LO, WLO) + integral(lam, WHI, HI)


def nll(lam):
    return float((sb / lam).sum() + Nsb * np.log(I_sb(lam)))


lam = float(minimize_scalar(nll, bounds=(5, 200), method="bounded", options={"xatol": 1e-6}).x)
B = Nsb * integral(lam, WLO, WHI) / I_sb(lam)
z_asimov = float(np.sign(N - B) * np.sqrt(2 * (N * np.log(N / B) - (N - B)))) if N > 0 else -float(np.sqrt(2 * B))
p = float(poisson.sf(N - 1, B))
z_p = float(norm.isf(p))
warnings = ["low_statistics"] if (N < 10 or B < 10) else []
res = {"n_obs": N, "n_sideband": Nsb, "lam": lam, "b_expected": float(B), "excess": float(N - B), "z_asimov": z_asimov,
       "p_value": p, "z_from_p": z_p, "warnings": warnings, "claim": "significant_excess" if z_p >= 3.0 else "no_significant_excess"}
json.dump(res, open("result.json", "w"), indent=2)
os.makedirs("figures", exist_ok=True)
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.hist(m, bins=80, range=(LO, HI), histtype="stepfilled", alpha=0.7, label="data")
ax.axvspan(WLO, WHI, color="orange", alpha=0.3, label="signal window")
ax.set_xlabel("Diphoton invariant mass m_gg [GeV]")
ax.set_ylabel("Events / 1 GeV")
ax.legend()
fig.savefig("figures/window.svg")
print(res)

Results on this task

4 runs
AgentModelRunsStrict successMean score
Self-debuggingQwen3-8B (gariyuu gateway)10%0.6786
Planner / executorQwen3-8B (gariyuu gateway)10%0.6071
ReActQwen3-8B (gariyuu gateway)10%0.2583
Single-shotQwen3-8B (gariyuu gateway)10%0.0548
RunAgentModelResultScoreLabels
20260906T171945…r0Single-shotQwen3-8B (gariyuu gateway)✗ fail0.0548no_outputplotting_errorinvalid_cutstatistical_misuseincorrect_normalizationunjustified_interpretationinsufficient_statisticsnon_reproducibleexecution_failed
20260906T173315…r0ReActQwen3-8B (gariyuu gateway)✗ fail0.2583no_outputplotting_errorstatistical_misuseincorrect_normalizationnon_reproduciblefabricated_resultexecution_failedsilent_exceptionunsupported_claim
20260906T174644…r0Planner / executorQwen3-8B (gariyuu gateway)✗ fail0.6071statistical_misuseincorrect_normalizationnon_reproducibleunsupported_claim
20260906T175322…r0Self-debuggingQwen3-8B (gariyuu gateway)✗ fail0.6786statistical_misuseincorrect_normalization