EventCalc-SHiP generates decays of long-lived particles in the SHiP decay
volume and calculates their expected yields. The same calculation can be
adapted to another geometry through funcs/ship_setup.py.
For each selected mass and proper decay length, EventCalc
- interpolates the production probability and the angular--energy density of the long-lived particle;
- samples momenta directed toward the SHiP decay volume;
- samples decay positions from the exponential decay law within the longitudinal extent of the volume;
- applies its azimuthal geometry;
- generates two-, three-, or four-body decays in the particle rest frame;
- invokes Pythia 8 when partons or unstable decay products require showering, hadronization, or further decays;
- boosts the final-state particles to the laboratory frame;
- combines production, geometry, decay probability, and visible branching ratios to calculate the number of decays.
The generated event record contains truth-level momenta and decay positions. FairShip can use this record for particle transport and detector reconstruction.
The repository includes Higgs-like scalars, photophilic ALPs, dark photons,
heavy neutral leptons, the pure
Scalar-mixing;Scalar-quartic;ALP-photon;Dark-photons;HNL;ALP-SU2L;ALP-mixed.
The ALP-SU2L model uses
with
Four processes contribute to the ALP flux: rare
and the production probability is
EventCalc reads this combined density as one production model. Its forward
integral equals the polar acceptance; the angular restriction preserves the
normalization over the complete angular range. EventCalc multiplies
The charged-kaon flux is
The present decay model assumes
Distributions/ALP-SU2L/; users do not need table-generation code.
For a requested proper decay length
It then multiplies
The ALP-mixed model implements both operators coherently. Its two additional
inputs are constructive or destructive interference.
The convention is
and therefore
The amplitudes interfere before the decay width is calculated; the lifetime is not obtained from a sum of the two coupling squares. For the diphoton-only benchmark,
The c_taus values remain physical proper decay lengths. EventCalc infers
No spectrum generation is needed at launch. The code reconstructs the ALP-SU2L table and the installed
primary/cascade photon tables. Thus ALP-SU2L physics.
The common tabulated mass range is
The code has been tested on Linux. Install the Python dependencies with
pip3 install numpy sympy numba scipy pandas matplotlibPlotly is optional and is used only by the interactive Plotly event display:
pip3 install plotlyHepMC3 export from the runtime generator is optional:
pip3 install pyhepmcPythia 8 is needed for selected decay channels that
contain partons or unstable particles. Diphoton decays of ALP-photon,
ALP-SU2L, and ALP-mixed use the internal two-body generator and run
without Pythia 8.
To install the Python bindings for Pythia 8, configure and build it with
./configure --with-python-config=python3-config
makeSet PYTHIA8_LIB to the directory containing pythia8.so. The fallback path
is /home/name/Downloads/pythia8312/lib.
EventCalc supports a JSON launch card, explicit command-line arguments, and
the original interactive prompts. The first two interfaces are silent: every
physical and numerical choice is supplied before the calculation starts.
Card and command launches call no input prompts and use Matplotlib's
non-interactive Agg backend. They create phenomenology figures only when
plots or --plots is enabled, and write those figures to disk.
The example below launches the
{
"model": "ALP-SU2L",
"events": 200000,
"masses": [0.3, 1.0],
"c_taus": [0.01, 10000.0],
"decay_channels": ["2gamma"],
"seed": 12345,
"plots": false,
"export_events": true
}Run it with
python3 simulate.py --card cards/alp_su2l.jsonCheck the card, resolve its decay channels, and print the normalized configuration with
python3 simulate.py --card cards/alp_su2l.json --validate-onlyThis command exits before the numerical simulation is imported.
The required fields are model, events, masses, and c_taus.
decay_channels accepts names, one-based indices, or "all". A flat
c_taus list is used for every mass. A nested list gives a separate lifetime
list for each mass.
The optional common fields are:
-
seed, an integer between 0 and$2^{32}-1$ ; -
plots, with defaultfalsein silent mode; -
export_events, with defaulttrue; -
n_pot, with default$6\times10^{20}$ protons on target; -
min_events_threshold, with default$0.1$ expected decays.
Three models require an additional field:
-
HNL:mixing_pattern, containing$\xi_e$ ,$\xi_\mu$ , and$\xi_\tau$ ; -
Dark-photons:uncertainty, chosen fromlower,central, andupper; -
ALP-photon:alp_production_mode, chosen fromprimaryandcascades.
ALP-mixed requires xi, between 0 and 1, and interference, chosen from
constructive and destructive. For example:
{
"model": "ALP-mixed",
"xi": 0.35,
"interference": "constructive",
"events": 200000,
"masses": [0.3, 0.5, 1.0],
"c_taus": [0.01, 100.0, 10000.0],
"decay_channels": ["2gamma"],
"seed": 12345
}Run the supplied example with
python3 simulate.py --card cards/alp_mixed.jsonALP-SU2L already contains its
The same
python3 simulate.py \
--model ALP-SU2L \
--events 200000 \
--masses 0.3 1.0 \
--c-taus 0.01 10000 \
--decay-channels 2gamma \
--seed 12345Command-line arguments override fields from a card. python3 simulate.py --help lists all options.
The mixed example can equivalently be launched with --model ALP-mixed,
--xi 0.35, and --interference constructive, together with the common
arguments above.
Running
python3 simulate.pystarts the interactive interface. The model menu has a fixed order and uses the names listed above. It asks for the event sample size, model-specific parameters, decay channels, masses, and proper decay lengths. It generates the production-probability, lifetime, and branching-fraction figures.
runtime_generator.py is an event-by-event Python interface over the installed
EventCalc production and decay tables. It initializes the chosen model once,
constructs a two-dimensional inverse CDF for the tabulated
next(), next_event(), generate(),
yields(), and stat(). It works for every card-supported model, including
ALP-mixed.
The runtime generator does not create 10 * events proposal points. It samples
the precomputed CDF directly. The energy coordinate is mapped to a unit
interval between the lifetime-dependent lower bound and simulate.py workflow retains its proposal-and-resampling
algorithm.
The supplied experiment card describes the fiducial volume independently of
the model card. The default values are those in cards/ship.json:
python3 runtime_generator.py \
--card cards/alp_su2l.json \
--experiment-card cards/ship.json \
--mass 0.3 \
--ctau 100 \
--events 1000000 \
--batch-size 10000 \
--mode fiducial \
--output-format npz \
--output-dir outputs/runtime-alp-su2l-ma0p3-ctau100In fiducial mode, events is the requested number of returned decays inside
the fiducial volume. In attempted mode, every sampled parent is returned;
inside_volume identifies the trajectories inside the volume, and
decay_weights is zero outside it. Each command-line batch is a compressed
NumPy archive containing these arrays together with the standard EventCalc
event rows and decay-channel labels. metadata.json records the resolved
configuration and running yield estimate.
Set --output-format hepmc3 to write one HepMC3 ASCII event stream, or
--output-format both to retain the NumPy batches as well. HepMC events use
GeV and mm, contain the displaced LLP decay vertex, incoming LLP and final
state particles, and carry decay_weight as the named event weight.
The API performs no per-event file I/O:
from pathlib import Path
from runtime_generator import generator_from_card
generator = generator_from_card(
Path("cards/alp_su2l.json"),
experiment_card=Path("cards/ship.json"),
mass=0.3,
c_tau=100.0,
mode="fiducial",
seed=12345,
)
generator.init()
for _ in range(1_000_000):
generator.next()
analyze(generator.current_event)
summary = generator.yields()For several masses or lifetimes, load one scan context and compile the desired points before generating events:
from pathlib import Path
from runtime_generator import scan_from_card
points = [(0.3, 0.01), (0.3, 100.0), (0.5, 100.0)]
scan = scan_from_card(
Path("cards/alp_su2l.json"),
experiment_card=Path("cards/ship.json"),
seed=12345,
)
scan.compile_points(points)
for mass, c_tau in points:
generator = scan.generator(
mass=mass, c_tau=c_tau, mode="fiducial"
)
for batch in generator.generate(1_000_000):
analyze(batch)The model tables and dense interpolation grid are constructed once per scan. Each lifetime retains its own cached CDF because the survival-energy cutoff depends on lifetime; constructing that CDF takes only a few milliseconds.
yields() combines the production normalization and visible branching ratio
with the CDF integral and running Monte Carlo estimates of transverse
acceptance and decay probability. It reports statistical errors for the
sampled factors and expected event yield.
The current scope is a Python runtime interface over EventCalc. Parent kinematics and decay vertices use separate Philox streams and are bitwise invariant under a change in batch partition. Decay generation is seeded per batch, and Pythia-based channels retain Pythia's own random-number behavior. Event-index random access, binary physics cards, and a native FairShip adapter remain future extensions.
For each mass and lifetime, EventCalc samples events * 10 interpolation
candidates and resamples events momenta within the polar range. The
azimuthal selection reduces the number of stored decays, typically by a factor
of order
If the production probability per proton on target is below min_events_threshold, EventCalc stores its total quantities and skips the
generation of decay products.
Results are written under outputs/<model>/:
eventData/contains the generated event records when event export is enabled;total/contains mass, coupling, lifetime, production yield, polar and azimuthal acceptance, mean decay probability, visible branching ratio, and expected number of decays.
For ALP-mixed, the coupling_squared column is specifically
Each event record starts with the long-lived particle four-momentum, mass, identifier, decay probability, and decay coordinates. Every final-state particle contributes its four-momentum, mass, and identifier. Momenta, energies, and masses are in GeV; positions are in metres. The event weight is the decay probability stored with the parent particle.
Enable plots in a card or pass --plots to write the mass dependence of the
production probability, proper decay length, and selected branching ratios
under plots/<model>/phenomenology.
The main simulation reports decays whose parent LLP trajectory lies inside the
fiducial decay volume. With event export enabled, events_analysis.py can
add a model-independent, truth-level requirement on the visible decay
products. By default it projects straight downstream trajectories onto a
Run the analysis and select an existing event file interactively:
python3 events_analysis.py --signature two-photonThe available signatures are:
two-photon: exactly two visible particles, both photons, must intersect the plane;neutral-pair: at least one subset of two or more particles that intersects the plane must have zero total electric charge;all-visible: every visible particle must intersect the plane. Neutrinos are ignored.
The default is all-visible. Change the plane with --detector-z,
--detector-width, and --detector-height. An optional common energy threshold
is set with --min-energy E_MIN, in GeV. Every counted particle must satisfy
The script writes detector_acceptance.txt and
channels_detector_acceptance.pdf in the selected analysis directory. The
reported fractions are weighted by the parent decay probability. Multiplying
the generator's Total number of events by the overall fraction gives the
truth-level accepted yield. No detector efficiency, energy or position
smearing, reconstruction, or particle-identification inefficiency is applied.
EventCalc-SHiP has been compared with
SensCalc, which was also
tested against earlier FairShip calculations and other tools. The event yield,
geometric acceptance, mean decay probability, and kinematic distributions
agree at the 10% level or better; see the
comparison slides.
-
simulate.pycontains the launcher and simulation loop. -
runtime_generator.pyprovides the initialized event-by-event and batched Python runtime interface, attempted/fiducial modes, and running yields. -
hepmc_export.pyprovides the optional streaming HepMC3 writer. -
cards/ship.jsoncontains the default runtime fiducial geometry. -
funcs/simulation_config.pyvalidates cards and command-line arguments. -
funcs/initLLP.pyinterpolates production, lifetime, and decay inputs. -
funcs/ALPmerging.pyassembles a requested photon--$SU(2)_L$ mixture from the installed pure-source tables. -
funcs/kinematics.pysamples parent momenta and decay positions. -
funcs/TwoBodyDecay.py,ThreeBodyDecay.py, andFourBodyDecay.pygenerate rest-frame decays. -
funcs/decayProducts.pydecides whether Pythia 8 is required. -
funcs/boost.pytransforms decay products to the laboratory frame. -
funcs/mergeResults.pywrites event records and total yields. -
funcs/ship_setup.pydefines the SHiP geometry. -
events_analysis.py,total-plots.py, andevent_display.pyprovide additional analysis and visualization of completed simulations.
Further details of the implemented production and decay models are in
DETAILS.md.
This repository completes Josue Jaramillo's CERN student project.