AgentHEPGitHub ↗
Tier 5 · Statistics · v1.0.0

Fit a Gaussian bump on an exponential background

Binned least-squares fit of signal + background to a diphoton mass spectrum with a fully specified model, binning and initial values.

t5-bump-fitstatisticssynth_diphotonbinned_fitcurve_fituncertaintieschi2plotting

Task prompt (what the agent sees)

`data/synth_diphoton.csv` (data card in README.md) contains a diphoton invariant mass `m_gg` per event.
Fit the spectrum with the following fully specified procedure:

- Histogram `m_gg` in 80 bins of width 1 GeV on [100, 180].
- Model for the expected count in a bin centred at m (bin width w = 1 GeV):
  f(m) = w * [ N_s * Gauss(m; mu, sigma) + N_b * Expo(m; lam) ]
  where Gauss is the normalised Gaussian density and Expo(m; lam) = exp(-m/lam) / (lam * (exp(-100/lam) - exp(-180/lam)))
  is the exponential density normalised on [100, 180].
- Minimise chi2 = sum over bins with n > 0 of (n - f)^2 / n (i.e. least squares with sigma = sqrt(n); bins with zero
  counts are excluded), starting from mu = 125, sigma = 2, N_s = 300, N_b = 30000, lam = 25.
- Parameter uncertainties from the covariance matrix of the least-squares fit (e.g. `scipy.optimize.curve_fit` with `absolute_sigma=True`).

Deliverables:
- `result.json` with `mu`, `mu_err`, `sigma`, `sigma_err`, `n_sig`, `n_sig_err`, `n_bkg`, `lam` (floats), `chi2` (float),
  `ndf` (int, number of fitted bins minus number of parameters) and `naive_significance` = n_sig / n_sig_err (float).
- `figures/fit.svg`: data points with error bars, the total fit curve and the background-only curve, legend entries containing
  "data" and "fit", 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.csv (SYNTHETIC)

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/fit.svg svg
  • solution.py script

Deterministic checks and tolerances

rubric: artifact 0.1 · numeric 0.55 · plot 0.1 · compliance 0.05 · reproducibility 0.2
CheckTypeTargetToleranceWeightCategoryCriticalFailure implies
result_existsfile_existsresult.json1artifactcriticalno_output
figure_existsfile_existsfigures/fit.svg1artifactno_outputplotting_error
solution_existsfile_existssolution.py1artifactno_output
mujson_valueresult.json › muatol 0.13numericcriticalstatistical_misuse
mu_errjson_valueresult.json › mu_errrtol 0.31numericstatistical_misuse
sigmajson_valueresult.json › sigmaatol 0.22numericstatistical_misuse
n_sigjson_valueresult.json › n_sigrtol 0.153numericcriticalstatistical_misuseincorrect_normalization
n_sig_errjson_valueresult.json › n_sig_errrtol 0.31numericstatistical_misuse
n_bkgjson_valueresult.json › n_bkgrtol 0.022numericincorrect_normalization
lamjson_valueresult.json › lamrtol 0.051numericstatistical_misuse
chi2json_valueresult.json › chi2rtol 0.11numericstatistical_misuse
ndfjson_valueresult.json › ndfexact1numericstatistical_misuse
significancejson_valueresult.json › naive_significancertol 0.31numericstatistical_misuse
svg_legendsvg_textfigures/fit.svg ["data","fit","GeV"]1plotplotting_error
svg_drawnsvg_has_marksfigures/fit.svg≥ 101plotplotting_error
rerunsscript_runssolution.py1reproducibilitynon_reproducible
not_hardcodedno_hardcoded_resultsolution.py1compliancefabricated_result

Tolerance rationale. The model, binning, error definition and starting values are fixed, so two correct implementations agree to well below the statistical uncertainty (sigma_mu ~ 0.1 GeV, sigma_Ns ~ 20%). Tolerances are set at roughly one statistical standard deviation: mu atol 0.1, n_sig rtol 0.15. Uncertainties rtol 0.3 (absolute_sigma vs. scaled covariance differ by sqrt(chi2/ndf)).

Ground truth (produced by the reference in the sandbox)

built 2026-09-06 · numpy 1.26.4 · scipy 1.13.1 · 1.434s
result.json
{
 "mu": 124.68620802311865,
 "mu_err": 0.28514381451768583,
 "sigma": 1.3609269906702666,
 "sigma_err": 0.28436457432303563,
 "n_sig": 339.27845666761533,
 "n_sig_err": 64.70185785333416,
 "n_bkg": 29919.33850157921,
 "lam": 24.889024177878664,
 "chi2": 43.39539663999595,
 "ndf": 75,
 "naive_significance": 5.243720473014701
}
reference.py
import json
import os
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

LO, HI, W = 100.0, 180.0, 1.0
m = pd.read_csv("data/synth_diphoton.csv")["m_gg"].to_numpy()
counts, edges = np.histogram(m, bins=80, range=(LO, HI))
x = 0.5 * (edges[:-1] + edges[1:])


def gauss(m, mu, sigma):
    return np.exp(-0.5 * ((m - mu) / sigma) ** 2) / (sigma * np.sqrt(2 * np.pi))


def expo(m, lam):
    return np.exp(-m / lam) / (lam * (np.exp(-LO / lam) - np.exp(-HI / lam)))


def model(m, mu, sigma, ns, nb, lam):
    return W * (ns * gauss(m, mu, sigma) + nb * expo(m, lam))


sel = counts > 0
p0 = [125.0, 2.0, 300.0, 30000.0, 25.0]
popt, pcov = curve_fit(model, x[sel], counts[sel], p0=p0, sigma=np.sqrt(counts[sel]), absolute_sigma=True, maxfev=20000)
err = np.sqrt(np.diag(pcov))
chi2 = float((((counts[sel] - model(x[sel], *popt)) ** 2) / counts[sel]).sum())
ndf = int(sel.sum() - len(popt))
res = {"mu": float(popt[0]), "mu_err": float(err[0]), "sigma": float(abs(popt[1])), "sigma_err": float(err[1]),
       "n_sig": float(popt[2]), "n_sig_err": float(err[2]), "n_bkg": float(popt[3]), "lam": float(popt[4]),
       "chi2": chi2, "ndf": ndf, "naive_significance": float(popt[2] / err[2])}
json.dump(res, open("result.json", "w"), indent=2)
os.makedirs("figures", exist_ok=True)
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.errorbar(x, counts, yerr=np.sqrt(counts), fmt="o", ms=3, label="data")
xx = np.linspace(LO, HI, 400)
ax.plot(xx, model(xx, *popt), label="fit (signal + background)")
ax.plot(xx, W * popt[3] * expo(xx, popt[4]), "--", label="background only")
ax.set_xlabel("Diphoton invariant mass m_gg [GeV]")
ax.set_ylabel("Events / 1 GeV")
ax.legend()
fig.savefig("figures/fit.svg")
print(res)

Results on this task

4 runs
AgentModelRunsStrict successMean score
Self-debuggingQwen3-8B (gariyuu gateway)10%0.8625
Planner / executorQwen3-8B (gariyuu gateway)10%0
ReActQwen3-8B (gariyuu gateway)10%0.4844
Single-shotQwen3-8B (gariyuu gateway)10%0.4844
RunAgentModelResultScoreLabels
20260906T171938…r0Single-shotQwen3-8B (gariyuu gateway)✗ fail0.4844statistical_misuseincorrect_normalization
20260906T172306…r0ReActQwen3-8B (gariyuu gateway)✗ fail0.4844statistical_misuseincorrect_normalizationexecution_failedstep_budget_exhausted
20260906T174058…r0Planner / executorQwen3-8B (gariyuu gateway)✗ fail0no_outputplotting_errorstatistical_misuseincorrect_normalizationnon_reproduciblefabricated_resultexecution_failedstep_budget_exhausted
20260906T175249…r0Self-debuggingQwen3-8B (gariyuu gateway)✗ core only0.8625statistical_misuse