aegir
Phlex-based simulation framework for the SHiP experiment.
Loading...
Searching...
No Matches
pythia_common.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// pythia_common.hpp — helpers shared by the Pythia8-based generators
6//
7// Header-only and free of Phlex/data-model dependencies so the standalone
8// benchmark can share the same code. extract_particles() is templated on the
9// output particle type (any struct exposing the MCParticle fields), letting
10// the plugins emit SHiP::MCParticle while the benchmark keeps its local
11// stand-in.
12
13#pragma once
14
15#include <Pythia8/Pythia.h>
16
17#include <SHiP/Units.hpp>
18#include <cstdint>
19#include <stdexcept>
20#include <string>
21#include <string_view>
22#include <vector>
23
24namespace aegir {
25
26// Pythia8's native units: energies/momenta in GeV, positions in mm,
27// production times in mm/c.
28using PythiaTime = mp_units::quantity<ship::units::mm_per_c, double>;
29
30// Map a 32-bit base seed into Pythia's valid Random:seed range
31// [1, 900000000]. extra_streams reserves headroom for consecutive
32// per-instance seeds (PythiaParallel seeds helper i with Random:seed + i).
33inline int pythia_seed(std::uint32_t base, int extra_streams = 0) {
34 if (extra_streams < 0 || extra_streams >= 900000000)
35 throw std::invalid_argument("pythia_seed: extra_streams " +
36 std::to_string(extra_streams) +
37 " must be in [0, 900000000)");
38 auto const range = static_cast<std::uint32_t>(900000000 - extra_streams);
39 return static_cast<int>(base % range) + 1;
40}
41
42// Configure a fixed-target beam: beam A on a stationary target B (frameType 2,
43// eB = 0). Templated so it works for both Pythia8::Pythia and PythiaParallel.
44template <typename Pythia>
45void configure_beams(Pythia& pythia, int idA, int idB,
46 ship::Energy beam_energy) {
47 pythia.readString("Beams:idA = " + std::to_string(idA));
48 pythia.readString("Beams:idB = " + std::to_string(idB));
49 pythia.readString("Beams:frameType = 2");
50 pythia.readString(
51 "Beams:eA = " +
52 std::to_string(beam_energy.numerical_value_in(ship::units::GeV)));
53 pythia.readString("Beams:eB = 0.");
54}
55
56// Make long-lived particles (tau0 above threshold) stable so a downstream
57// simulation (e.g. Geant4) handles their decay. Guards against null
58// particleData entries.
59template <typename Pythia>
60void stabilise_long_lived(Pythia& pythia, PythiaTime tau0_threshold) {
61 double const threshold =
62 tau0_threshold.numerical_value_in(ship::units::mm_per_c);
63 for (auto it = pythia.particleData.begin(); it != pythia.particleData.end();
64 ++it) {
65 auto& entry = it->second; // ParticleDataEntryPtr (shared_ptr-like)
66 if (entry && entry->tau0() > threshold) entry->setMayDecay(false);
67 }
68}
69
70// Advance the generator to its next event, retrying transient failures.
71// Pythia8::next() can occasionally reject a trial event; persistent failure
72// is a hard error rather than a silently-empty event, so it cannot leak
73// empty entries into the output (same convention as Pythia8MTSource
74// exhaustion).
75template <typename Pythia>
76void next_event(Pythia& pythia, std::string_view source_name,
77 int max_attempts = 10) {
78 for (int attempt = 0; attempt < max_attempts; ++attempt)
79 if (pythia.next()) return;
80 throw std::runtime_error(std::string(source_name) +
81 ": Pythia8 event generation failed " +
82 std::to_string(max_attempts) + " times in a row");
83}
84
85// Extract final-state particles from a Pythia event record into a vector of
86// MCParticle (any type exposing pdgCode/vertex/momentum/energy/time/motherId/
87// status). vertex z is shifted by z_offset.
88//
89// motherId is remapped from the full Pythia-record index to the index within
90// the returned vector, or -1 when the mother was not itself written out — the
91// common case, since only final-state particles are kept and their mothers
92// generally are not. This makes motherId a valid index into the emitted
93// collection rather than a dangling reference into the discarded record.
94template <typename MCParticle>
95std::vector<MCParticle> extract_particles(
96 Pythia8::Event const& event, ship::Length z_offset = ship::Length::zero()) {
97 namespace su = ship::units;
98 std::vector<MCParticle> particles;
99 particles.reserve(event.size());
100 double const z_offset_mm = z_offset.numerical_value_in(su::mm);
101
102 // Pythia-record index -> output index for written (final-state) particles.
103 std::vector<int> out_index(static_cast<std::size_t>(event.size()), -1);
104
105 for (int i = 0; i < event.size(); ++i) {
106 auto const& p = event[i];
107 if (!p.isFinal()) continue;
108
109 out_index[static_cast<std::size_t>(i)] = static_cast<int>(particles.size());
110
111 MCParticle mc;
112 mc.pdgCode = p.id();
113 // Pythia positions and momenta are already in the canonical units.
114 mc.vertex = {p.xProd(), p.yProd(), p.zProd() + z_offset_mm}; // mm
115 mc.momentum = {p.px(), p.py(), p.pz()}; // GeV/c
116 mc.energy = p.e(); // GeV
117 // mm/c -> ns via the exact definition of c (no hand-typed constant).
118 mc.time = (p.tProd() * su::mm_per_c).numerical_value_in(su::ns);
119 mc.motherId = p.mother1(); // record index, remapped below
120 mc.status = p.statusHepMC();
121 particles.push_back(mc);
122 }
123
124 for (auto& mc : particles) {
125 int m = mc.motherId;
126 mc.motherId = (m >= 0 && m < static_cast<int>(out_index.size()))
127 ? out_index[static_cast<std::size_t>(m)]
128 : -1;
129 }
130 return particles;
131}
132
133} // namespace aegir
Definition math_utils.hpp:12
void configure_beams(Pythia &pythia, int idA, int idB, ship::Energy beam_energy)
Definition pythia_common.hpp:45
std::vector< MCParticle > extract_particles(Pythia8::Event const &event, ship::Length z_offset=ship::Length::zero())
Definition pythia_common.hpp:95
mp_units::quantity< ship::units::mm_per_c, double > PythiaTime
Definition pythia_common.hpp:28
void next_event(Pythia &pythia, std::string_view source_name, int max_attempts=10)
Definition pythia_common.hpp:76
int pythia_seed(std::uint32_t base, int extra_streams=0)
Definition pythia_common.hpp:33
void stabilise_long_lived(Pythia &pythia, PythiaTime tau0_threshold)
Definition pythia_common.hpp:60