Skip to content

Add Pythia8 nuclear modes: Cascade and Angantyr#260

Open
afedynitch wants to merge 21 commits intomainfrom
fix_pythia8_nuclei
Open

Add Pythia8 nuclear modes: Cascade and Angantyr#260
afedynitch wants to merge 21 commits intomainfrom
fix_pythia8_nuclei

Conversation

@afedynitch
Copy link
Copy Markdown
Member

Summary

  • Adds two new Pythia8 model classes for nuclear interactions: Pythia8Cascade (PythiaCascade plugin for single-collision h+A mode) and Pythia8Angantyr (Glauber heavy-ion model for hA/AA with precomputed cross-section tables)
  • Both models support CompositeTarget (e.g., Air), nuclear projectile decomposition, proper RNG state save/restore, and cosmic-ray slow-decay conventions
  • C++ bindings extended with PythiaCascadeForChromo wrapper, set_may_decay support on both internal Pythia instances, and RNG state serialization
  • Includes comprehensive tests (dedicated Cascade/Angantyr test suites, HepMC nuclear-target tests, updated generic generator tests), reference histograms, and a helper script for Angantyr table generation
  • Bumps version to 0.11.0 and data to v007

Test plan

  • python -m pytest tests/test_pythia8_cascade.py -v — Cascade-specific tests
  • python -m pytest tests/test_pythia8_angantyr.py -v — Angantyr-specific tests
  • python -m pytest tests/test_to_hepmc3.py -v — HepMC nuclear-target tests
  • python -m pytest tests/test_generators.py -v -k "Cascade or Angantyr" — generic generator tests
  • python -m pytest tests/test_cross_sections.py tests/test_mass_shell.py -v — cross-section and mass-shell tests
  • Full test suite passes: python -m pytest -vv -n 16

🤖 Generated with Claude Code

afedynitch and others added 21 commits August 5, 2025 00:04
…scade decays

- Bump Pythia8 data to v007 (adds setups/ dir with .cmnd files)
- Angantyr: pre-compute Glauber cross sections via GlauberOnly mode
  during init when precomputed tables are available, avoiding the need
  to generate events first; fall back to event-based accumulation
  otherwise with a clear error message
- Cascade: fix decay behavior to match default Pythia8 (rapidDecays=True,
  smallTau0=1000) so short-lived resonances decay properly
- Cascade: expand projectiles to include strange/charm/bottom hadrons
- Update CI workflows for Pythia8_v007
- Enable Angantyr cross section tests (no longer need event generation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fast path (precomputed tables): re-init with GlauberOnly for accurate
  cross sections on each beam/energy change
- Slow path: use setBeamIDs + setKinematics for live switching without
  expensive re-init; cross sections accumulate during event generation
- Expose HIInfo.glauberReset() in pybind11 bindings
- Reset Glauber running average before each trial batch to avoid
  contamination from previous configurations
- Enable Angantyr cross section tests for non-air targets
- CompositeTarget (air) now works with Angantyr

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ecays

- Pythia8: extend _projectiles with strange/charm/bottom hadrons
  (Lambda, Sigma, Xi, Omega, D, B mesons/baryons)
- Pythia8Angantyr: remove proton/neutron from _targets (nuclear-only),
  set _ecm_min=20 GeV matching precomputed table range, use setBeamIDs
  for fast live target switching with lazy cross-section estimation
- Pythia8Cascade: add prod to _cross_section for CompositeTarget support,
  change slowDecays=True (cosmic-ray convention), add random_state
  save/restore via new C++ getRndmState/setRndmState bindings for both
  internal Pythia instances (pythiaMain + pythiaColl)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…odes

- Remove manual low-energy skips for Angantyr (replaced by _ecm_min=20 GeV)
- Remove Cascade/Angantyr CompositeTarget skips (both now supported)
- Add ecm_min to None-result assertions in test_generators and test_mass_shell
- Remove Cascade cross-section skip (CMS kinematics handled by framework)
- Update test_pythia8_angantyr to use nuclear targets (proton removed from
  Angantyr _targets), fix fixed-target energy to 250 GeV (above 20 GeV CMS)
- Delete stale reference files from broken model era

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_rng_state: use N14 target for Cascade/Angantyr, remove Cascade skip
  (RNG save/restore now implemented via C++ bindings)
- test_setstable: use N14 target, xfail Cascade stable=False (PythiaCascade
  does not force-decay long-lived particles)
- test_final_state: use N14 target for Cascade/Angantyr
- test_common: xfail beam test for Cascade (no beam particles in
  listFinalOnly events) and Angantyr (nuclear beam momentum mismatch),
  use N14 target for Angantyr
- test_decay_handler: use 250 GeV for Angantyr (above 20 GeV CMS minimum)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip generic p-p HepMC tests for Cascade/Angantyr (nuclear targets only)
- Add test_to_hepmc3_nuclear and test_io_hepmc_nuclear parametrized
  with p+N14 for both Pythia8Cascade and Pythia8Angantyr
- Add test_hepmc_io_nuclear with write/read round-trip verification
- Refactor test_hepmc_io shared logic into _run_hepmc_io_test helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covered by tests/test_pythia8_angantyr.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
28 reference files for probabilistic generator tests:
- Pythia8Cascade: pi-/p/He on N14, O16, air in ft/cms/cms2ft/ft2cms frames
- Pythia8Angantyr: pi-/p on N14, O16, air in cms/cms2ft/ft/ft2cms frames

Generated with slowDecays=True (Cascade) and live setBeamIDs switching
(Angantyr).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Version 0.10.1 → 0.11.0
- README: split Pythia8 into three entries (base, Cascade, Angantyr)
  with version 8.317 and specific collision system descriptions
- CLAUDE.md: add Pythia8 Nuclear Modes architecture section describing
  all three classes, C++ bindings, and CompositeTarget support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lint job (pre-commit) that must pass before test and cibuildwheel jobs start
- Add claude-code-review.yml with allowed_bots: '*' to accept pre-commit-ci bot triggers
- Sync cibuildwheel job from main (was missing on this branch)
- Update macos-14 to macos-latest to match main

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add # noqa: T201 to all print() calls in generate_angantyr_tables.py
  (CLI script, print is intentional)
- Restore claude-code-review.yml to match main exactly (action requires
  identical content to default branch)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace en-dash with hyphen-minus (RUF002)
- Extract f-string from exception to variable (EM102)
- Place noqa: T201 on print( lines in black-compatible multi-line form

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds dedicated Pythia8 nuclear-interaction models to chromo (Cascade single-collision h+A and Angantyr Glauber hA/AA), along with bindings, documentation, CI updates, and new/updated tests to validate HepMC conversion/IO, RNG state handling, and physics/regression outputs.

Changes:

  • Introduce Pythia8Cascade and Pythia8Angantyr Python model classes and expose them via chromo.models.
  • Extend the _pythia8 pybind module with a PythiaCascadeForChromo wrapper plus additional Pythia8/HIInfo bindings (RNG state, decay toggles, Glauber cross sections, settings IO, live beam/kinematics switching).
  • Expand/adjust the test suite and reference artifacts for nuclear targets; bump packaged Pythia8 data to v007 and project version to 0.11.0.

Reviewed changes

Copilot reviewed 23 out of 52 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/chromo/models/pythia8.py Adds Pythia8Cascade and Pythia8Angantyr, adjusts base Pythia8 scope, and implements nuclear-mode generation/cross sections/RNG handling.
src/cpp/_pythia8.cpp Adds PythiaCascadeForChromo wrapper and exposes additional Pythia8 APIs needed by Cascade/Angantyr.
src/chromo/models/__init__.py Exports new model classes via __all__.
scripts/generate_angantyr_tables.py Adds helper script to generate Angantyr precomputed initialization tables.
tests/test_pythia8_cascade.py New targeted unit tests for Cascade collisions/cross sections/projectile decomposition and event record consistency.
tests/test_pythia8_angantyr.py New targeted unit tests for Angantyr collisions/cross sections/impact parameter/wounded nucleons and live kinematics switching.
tests/test_to_hepmc3.py Skips nuclear-only models in generic tests and adds dedicated nuclear HepMC3 conversion + IO tests.
tests/test_hepmc_io.py Refactors shared HepMC IO logic and adds nuclear-target round-trip coverage.
tests/test_generators.py Adds probabilistic histogram regression tests for Cascade/Angantyr and adjusts expected “unsupported” assertions to include _ecm_min.
tests/test_rng_state.py Uses nuclear targets for Cascade/Angantyr RNG-state tests.
tests/test_setstable.py Adapts target choice for nuclear-only models; marks a known Cascade decay-mode limitation as xfail.
tests/test_final_state.py Uses nuclear target kinematics for Cascade/Angantyr in final-state tests.
tests/test_cross_sections.py Adds Angantyr-He skip and updates unsupported assertions to include _ecm_min.
tests/test_mass_shell.py Adds Angantyr-He skip and updates unsupported assertions to include _ecm_min.
tests/test_decay_handler.py Adds an Angantyr-specific fixed-target setup above the model’s minimum CMS energy.
tests/test_common.py Marks known beam-record mismatches for Cascade/Angantyr as xfail and sets Angantyr kinematics to nuclear target.
tests/test_cli.py Updates CLI ambiguity regex to include the new Pythia8 nuclear model names.
tests/test_pythia8.py Removes an old (skipped) nuclear collision test.
tests/data/test_generators/Pythia8Cascade_pi-_O16_ft.pkl.gz Adds reference histogram/covariance artifact for generator regression testing.
tests/data/test_generators/Pythia8Cascade_pi-_N14_ft.pkl.gz Adds reference histogram/covariance artifact for generator regression testing.
tests/data/test_generators/Pythia8Cascade_p_O16_ft.pkl.gz Adds reference histogram/covariance artifact for generator regression testing.
tests/data/test_generators/Pythia8Cascade_He_O16_ft.pkl.gz Adds reference histogram/covariance artifact for generator regression testing.
tests/data/test_generators/Pythia8Angantyr_p_air_ft.pkl.gz Adds reference histogram/covariance artifact for generator regression testing.
README.md Updates model list to distinguish Pythia8 base vs Cascade vs Angantyr; updates citation instructions.
pyproject.toml Bumps package version to 0.11.0.
CLAUDE.md Documents the new Pythia8 nuclear mode split and where bindings live.
.github/workflows/test.yml Adds a lint job and updates cached Pythia8 data bundle version to v007.
.github/workflows/release.yml Updates cached Pythia8 data bundle version to v007 for releases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +37
def _merge_cascade_results(results):
"""Concatenate multiple next_coll result tuples, remapping mother/daughter
indices so they remain valid in the merged particle array.

Each result is a 13-tuple (pid, status, px, py, pz, en, m, vx, vy, vz, vt,
mothers, daughters) where mothers/daughters are (N,2) int arrays with
1-based Pythia indices (0 = no parent/child).
"""
if len(results) == 1:
return results[0]
parts = [[] for _ in range(13)]
offset = 0
for result in results:
n = len(result[0])
for i in range(11): # scalar arrays: just append
parts[i].append(result[i])
for i in (11, 12): # index arrays: add offset to non-zero entries
arr = result[i].copy()
arr[arr > 0] += offset
parts[i].append(arr)
offset += n
return tuple(np.concatenate(parts[i]) for i in range(13))
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

_merge_cascade_results returns the original next_coll tuple unchanged when len(results) == 1. Those arrays are views into the internal Pythia8 event buffer, so the returned event can be mutated/overwritten by the next generated collision. Consider always returning fully-copied arrays (e.g., np.array(..., copy=True)/.copy() for each element) even in the single-result case to keep event data stable across generator calls.

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +452
# rapidDecays=True with smallTau0=1000 gives the default Pythia8
# decay setup (see PythiaCascade.h line 113-114).
# slowDecays=False keeps mu/pi/K/K0L stable, matching collider
# conventions and the default Pythia8 behavior.
if not self._cascade.init(
eKinMin,
enhanceSDtarget,
init_file,
rapidDecays=True,
smallTau0=1000.0,
slowDecays=True,
listFinalOnly=True,
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The inline comment says slowDecays=False keeps mu/pi/K/K0L stable, but the actual call passes slowDecays=True. Please align the comment and the chosen setting (and ideally explain why this differs from the default), so readers don’t misinterpret the decay configuration.

Copilot uses AI. Check for mistakes.
Event &ev = _cascade.nextColl(Z, A);
if (ev.size() == 0)
return py::none();
return _extract(ev);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The wrapper comment claims next_coll returns numpy arrays that are already copied out of the internal Event, but _extract() currently exposes views into the Event storage (via pointer+stride). This is easy to misuse and can lead to surprising mutation once another collision is generated. Either update the comment to reflect the actual behavior, or switch to returning copied arrays from C++ (or enforce copying on the Python side for all cases).

Suggested change
return _extract(ev);
py::tuple extracted = py::cast<py::tuple>(_extract(ev));
py::tuple copied(extracted.size());
for (py::ssize_t i = 0; i < extracted.size(); ++i)
copied[i] = extracted[i].attr("copy")();
return copied;

Copilot uses AI. Check for mistakes.
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