Skip to content

Add committed i3 test fixture + generator for extractor edge cases#906

Open
sevmag wants to merge 1 commit into
graphnet-team:mainfrom
sevmag:add_i3_fixture
Open

Add committed i3 test fixture + generator for extractor edge cases#906
sevmag wants to merge 1 commit into
graphnet-team:mainfrom
sevmag:add_i3_fixture

Conversation

@sevmag

@sevmag sevmag commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a small, public i3 test fixture and the script that generates it, so the IceCube i3 extractors — starting with I3Calorimetry and the I3Extractor helpers it relies on — can finally be unit-tested. Motivated by #905.

  • data/tests/i3/i3_fixture/i3_fixture_events.i3.zst — 8 hand-seeded events, each built to realise one extractor edge case. Committed (~13 MB) and cached, so CI reads it directly and never has to run icetray/PROPOSAL.
  • tests/data/generate_i3_fixture.py — the generator that produces it (fixed RNG seed → reproducible; every frame validated at generation time).

It's a general extractor fixture, not calorimetry-specific — the current events happen to cover the I3Calorimetry edge cases, but the same file can back tests for other i3 extractors.

Why it's safe to commit (no IceCube-internal data)

  • GCD: GraphNeT's own committed test GCD (data/tests/i3/oscNext_genie_level7_v02/…) — the fixture reuses it, no new GCD added.
  • Propagation: PROPOSAL with open cross-section tables.
  • Deterministic seed → reproducible.

The 8 events

Event Extractor behaviour it exercises
through_going_muon through-going track; deposited < entrance energy
stopping_track_contained contained muon that ranges out inside the hull
starting_track interaction vertex inside the hull, muon exits
tau_to_mu_decay τ decays to a µ in-volume (track not erased)
nan_primary_energy primary ν with NaN energy → check_primary_energy falls back to daughters
non_top_level_in_ice_nu first in-ice ν is below the top of the tree → get_primaries recurses (find_in_ice_daughters)
no_descendants_reach_hull nothing reaches the array → e_total = 0 + warning; empty MMCTrackList
corsika_muon_bundle cosmic-ray muon bundle, extracted with is_corsika=True (empty target, full background)

Locations

  • The i3 fixture lives under data/tests/i3/, next to the other committed test i3 data and reachable via the same TEST_DATA_DIR mechanism (src/graphnet/constants.py).
  • The generator lives in tests/data/ (it's code, and data/ holds only data). Its paths are resolved relative to the file, so regeneration reads/writes the worktree regardless of which graphnet install is importable.

Running it

Inside the GraphNeT icetray Docker image:

python3 tests/data/generate_i3_fixture.py

The first run builds PROPOSAL's interpolation tables in a scratch dir (cached afterwards); full generation is a few minutes (dominated by the 10 PeV τ). The generator passes the full pre-commit suite (black/flake8/docformatter/pydocstyle/mypy).

Not in this PR

The unit tests themselves — those are the follow-up tracked in #905. This PR just lands the fixture + generator so the tests have data to read. The corsika_muon_bundle is an illustrative hand-seeded bundle (each muon is really propagated by PROPOSAL, but the multiplicity/spectrum are hand-chosen) — see #905 for the residual coverage gaps a fuller suite should still add.

Part of #905.

Adds an 8-event hand-seeded i3 fixture
(data/tests/i3/i3_fixture/i3_fixture_events.i3.zst) and its generator
(tests/data/generate_i3_fixture.py) so the IceCube i3 extractors -- starting
with I3Calorimetry and the I3Extractor helpers it relies on -- can be
unit-tested.

The fixture carries no IceCube-internal data (GraphNeT's own public GCD,
PROPOSAL open cross-section tables, fixed RNG seed) and is committed so CI
reads it directly and never has to run icetray/PROPOSAL. Motivated by graphnet-team#905.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ToS7BT8sSuyhzDGJFRGQuX
@sevmag sevmag changed the title Add committed i3 test fixture + generator for I3Calorimetry edge cases Add committed i3 test fixture + generator for extractor edge cases Jun 27, 2026

@Aske-Rosted Aske-Rosted left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put a few comments.

ptype = "MuMinus" if i % 2 == 0 else "MuPlus"
muon = _make_particle(ptype, energy, vertex, mu_dir)
tree.append_child(primary, muon)
return tree

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the wrong way of going about it. Here you end up with particles which have slightly different directions, however the muon bundles in ice are actually pretty much collinear with some positional spread.

Something like:

    if i == 0:
        offset = np.zeros(3)          # core muon on the axis
    else:
        phi = 2.0 * np.pi * (i - 1) / (n - 1)
        r = CORSIKA_BUNDLE_RADIUS     # transverse offset, in meters
        offset = r * (np.cos(phi) * u + np.sin(phi) * v)
    mu_pos = vertex + offset
    ptype = "MuMinus" if i % 2 == 0 else "MuPlus"
    muon = _make_particle(ptype, energy, mu_pos, direction)   # shared direction
    tree.append_child(primary, muon)```

With CORSIKA_BUNDLE_RADIUS ~10m 

tree.add_primary(neutrino)
tree.append_child(neutrino, hadrons)
tree.append_child(neutrino, lepton)
return tree

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event, going by the description, is not a type of event that we actually have. If there are no charge recording then nothing is triggered.

tree.add_primary(neutrino)
tree.append_child(neutrino, hadrons)
tree.append_child(neutrino, lepton)
return tree

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is expected to happen... If I remember correctly then the cases where I encountered a nan energy neutrino primary then it will have a neutrino daughter with a defined energy.

f"{ctx}: expected the first in-ice neutrino below the top of "
"the tree so get_primaries recurses (top-level in-ice nu: "
f"{has_top_in_ice_nu}, deeper in-ice nu: {has_below_in_ice_nu})"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this fail for the nan-energy event you generated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants