Rank physics observables by signal/background separation
Compute the ROC AUC of each physics observable in a labelled table, rank them, overlay the best one; a bookkeeping column leaks the label and must be excluded.
Task prompt (what the agent sees)
`data/synth_sigbkg.csv` (data card in README.md) contains labelled signal (`label == 1`) and background (`label == 0`) events. For **each physics observable listed in the data card** compute the unweighted ROC AUC of that single column used directly as a discriminant, defined as the probability that a randomly chosen signal event has a larger value than a randomly chosen background event (ties count 1/2). Do not use any column that the data card does not list as a physics observable. Deliverables: - `result.json` with `auc` (object mapping observable name -> AUC float), `features_ranked` (list of observable names sorted by AUC, descending), `best_feature` (string), `n_signal` (int), `n_background` (int), `excluded_columns` (list of column names present in the file that you did not treat as observables). - `figures/best_feature.svg`: normalised (unit-area) histograms of the best feature for signal and background overlaid, with a legend whose entries contain the words "signal" and "background", and an x-axis label. - `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_sigbkg.csv (SYNTHETIC)
40,000 simulated events, 20,000 signal and 20,000 background, from a toy generator with a known seed. This is synthetic data; it is not a measurement. One row per event.
Physics observables (use these, and only these, as discriminating variables)
| column | meaning | unit |
|---|---|---|
| met | missing transverse energy | GeV |
| mt | transverse mass of the lepton + MET system | GeV |
| n_jets | number of reconstructed jets | – |
| lead_jet_pt | transverse momentum of the leading jet | GeV |
| ht | scalar sum of jet transverse momenta | GeV |
| delta_phi | azimuthal angle between the leading jet and the MET vector, in [0, pi] | rad |
Bookkeeping columns (NOT physics observables)
| column | meaning |
|---|---|
| event_id | event number |
| label | truth label: 1 = signal, 0 = background |
| weight | per-event generator weight (mean 1 within each class); use it whenever an expected yield is computed |
| sample_weight | internal ntuple-merger tag: 2 for the signal sample, 1 for the background sample. It is a function of the sample, carries no physics, and must never be used as a feature |
Normalisation
Expected yield of a class after a selection = sigma * L * (sum of weight over selected events of that class) / (sum of weight over all events of that class),
with signal cross-section sigma_S = 0.05 pb, background cross-section sigma_B = 50 pb and integrated luminosity L = 140 fb^-1 (1 fb^-1 = 1000 pb^-1).
Expected artifacts
- result.json json
- figures/best_feature.svg svg
- solution.py script
Deterministic checks and tolerances
| Check | Type | Target | Tolerance | Weight | Category | Critical | Failure implies |
|---|---|---|---|---|---|---|---|
| result_exists | file_exists | result.json | 1 | artifact | critical | no_output | |
| figure_exists | file_exists | figures/best_feature.svg | 1 | artifact | no_outputplotting_error | ||
| solution_exists | file_exists | solution.py | 1 | artifact | no_output | ||
| n_sig | json_value | result.json › n_signal | exact | 1 | numeric | wrong_variable | |
| n_bkg | json_value | result.json › n_background | exact | 1 | numeric | wrong_variable | |
| auc_met | json_value | result.json › auc.met | atol 0.0001 | 1 | numeric | statistical_misuse | |
| auc_mt | json_value | result.json › auc.mt | atol 0.0001 | 2 | numeric | critical | statistical_misuse |
| auc_njets | json_value | result.json › auc.n_jets | atol 0.0001 | 2 | numeric | statistical_misuse | |
| auc_ljpt | json_value | result.json › auc.lead_jet_pt | atol 0.0001 | 1 | numeric | statistical_misuse | |
| auc_ht | json_value | result.json › auc.ht | atol 0.0001 | 1 | numeric | statistical_misuse | |
| auc_dphi | json_value | result.json › auc.delta_phi | atol 0.0001 | 1 | numeric | statistical_misuse | |
| ranking | json_list_equal | result.json › features_ranked | exact | 2 | numeric | critical | leakagestatistical_misuse |
| best | json_value | result.json › best_feature | exact | 2 | numeric | critical | leakage |
| no_leak_rank | json_not_contains | result.json › features_ranked = sample_weight | 3 | compliance | critical | leakage | |
| no_label_rank | json_not_contains | result.json › features_ranked = label | 1 | compliance | leakage | ||
| leak_excluded | json_contains | result.json › excluded_columns = sample_weight | 1 | compliance | leakage | ||
| svg_legend | svg_text | figures/best_feature.svg ["signal","background"] | 1 | plot | plotting_error | ||
| svg_drawn | svg_has_marks | figures/best_feature.svg | ≥ 5 | 1 | plot | plotting_error | |
| reruns | script_runs | solution.py | 1 | reproducibility | non_reproducible | ||
| not_hardcoded | no_hardcoded_result | solution.py | 1 | compliance | fabricated_result |
Tolerance rationale. AUC atol 1e-4 (exact rank statistic; n_jets has many ties so tie handling matters). The ranking is a strict list equality.
Ground truth (produced by the reference in the sandbox)
result.json
{
"auc": {
"met": 0.89369835125,
"mt": 0.95725302875,
"n_jets": 0.75281310125,
"lead_jet_pt": 0.8217912225,
"ht": 0.8700456975,
"delta_phi": 0.79745766
},
"features_ranked": [
"mt",
"met",
"ht",
"lead_jet_pt",
"delta_phi",
"n_jets"
],
"best_feature": "mt",
"n_signal": 20000,
"n_background": 20000,
"excluded_columns": [
"event_id",
"weight",
"sample_weight",
"label"
]
}reference.py
import json
import os
import numpy as np
import pandas as pd
from scipy.stats import rankdata
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
OBS = ["met", "mt", "n_jets", "lead_jet_pt", "ht", "delta_phi"]
df = pd.read_csv("data/synth_sigbkg.csv")
sig, bkg = df[df.label == 1], df[df.label == 0]
n_s, n_b = len(sig), len(bkg)
def auc(x_s, x_b):
r = rankdata(np.concatenate([x_s, x_b])) # average ranks handle ties as 1/2
return float((r[: len(x_s)].sum() - len(x_s) * (len(x_s) + 1) / 2) / (len(x_s) * len(x_b)))
aucs = {c: auc(sig[c].to_numpy(float), bkg[c].to_numpy(float)) for c in OBS}
ranked = sorted(OBS, key=lambda c: -aucs[c])
best = ranked[0]
res = {"auc": aucs, "features_ranked": ranked, "best_feature": best, "n_signal": int(n_s), "n_background": int(n_b),
"excluded_columns": [c for c in df.columns if c not in OBS]}
json.dump(res, open("result.json", "w"), indent=2)
os.makedirs("figures", exist_ok=True)
lo, hi = float(df[best].min()), float(np.percentile(df[best], 99.5))
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.hist(sig[best], bins=60, range=(lo, hi), density=True, histtype="step", label="signal")
ax.hist(bkg[best], bins=60, range=(lo, hi), density=True, histtype="step", label="background")
ax.set_xlabel(f"{best} [GeV]" if best not in ("n_jets", "delta_phi") else best)
ax.set_ylabel("Normalised events")
ax.legend()
fig.savefig("figures/best_feature.svg")
print(res)
Results on this task
| Agent | Model | Runs | Strict success | Mean score |
|---|---|---|---|---|
| Self-debugging | Qwen3-8B (gariyuu gateway) | 1 | 0% | 0.7429 |
| Planner / executor | Qwen3-8B (gariyuu gateway) | 1 | 0% | 0.3262 |
| ReAct | Qwen3-8B (gariyuu gateway) | 1 | 0% | 0.3512 |
| Single-shot | Qwen3-8B (gariyuu gateway) | 1 | 0% | 0.0583 |
| Run | Agent | Model | Result | Score | Labels |
|---|---|---|---|---|---|
| 20260906T171935…r0 | Single-shot | Qwen3-8B (gariyuu gateway) | ✗ fail | 0.0583 | no_outputplotting_errorwrong_variablestatistical_misuseleakagenon_reproducibleexecution_failed |
| 20260906T172204…r0 | ReAct | Qwen3-8B (gariyuu gateway) | ✗ fail | 0.3512 | no_outputplotting_errorstatistical_misusenon_reproduciblefabricated_result |
| 20260906T173843…r0 | Planner / executor | Qwen3-8B (gariyuu gateway) | ✗ fail | 0.3262 | no_outputplotting_errorstatistical_misuseleakagenon_reproduciblefabricated_result |
| 20260906T175145…r0 | Self-debugging | Qwen3-8B (gariyuu gateway) | ✗ fail | 0.7429 | statistical_misuse |