Project
Loading...
Searching...
No Matches
GeneratorKrDecay.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
15
17#include "Framework/Logger.h"
18#include "TDatabasePDG.h"
19#include "TParticle.h"
20#include "TParticlePDG.h"
21#include "TRandom.h"
22#include "TMath.h"
23#include <cmath>
24#include <fstream>
25#include <memory>
26#include <sstream>
27#include <string>
28
29namespace o2::tpc
30{
31
32// ── 83mKr decay physics ──────────────────────────────────────────────────
33//
34// Energies and ICC values are read at runtime from $G4LEVELGAMMADATA/z36.a83
35// (set automatically by Geant4 in any O2 alienv session). The hardcoded
36// fallback values below are taken from PhotonEvaporation5.7/z36.a83 and are
37// used only if the file cannot be opened or parsed.
38//
39// Atomic constants (NIST) — stable across G4 data releases, always hardcoded:
40// Kr K-binding = 14.3256 keV
41// Kr L1-binding = 1.9210 keV
42// Kr Kα X-ray = 12.6000 keV
43// Kr K-shell fluorescence yield ω_K = 0.652 (Bambynek et al.)
44//
45// From the G4 file we read for two levels:
46// Level 2 (41.5569 keV, T1): E_gamma, ICC_total, K_shell_fraction
47// Level 1 ( 9.4053 keV, T2): E_gamma, ICC_total
48// Everything else is derived from these five numbers + the atomic constants.
49// ─────────────────────────────────────────────────────────────────────────
50
51// Parse $G4LEVELGAMMADATA/z36.a83.
52// Returns true and fills five values (energies in keV) on success.
53bool KrDecayTable::parseG4PhotonEvap(const char* path,
54 double& E_T1, // T1 gamma energy [keV]
55 double& ICC_T1, // T1 ICC_total
56 double& Kfrac_T1, // T1 K-shell fraction of ICC
57 double& E_T2, // T2 gamma energy [keV]
58 double& ICC_T2) // T2 ICC_total
59{
60 std::ifstream f(path);
61 if (!f.is_open()) {
62 return false;
63 }
64
65 bool gotT1 = false, gotT2 = false;
66 bool wantT1 = false, wantT2 = false;
67 std::string line;
68
69 while (std::getline(f, line)) {
70 if (line.empty()) {
71 continue;
72 }
73 std::istringstream ss(line);
74 int idx;
75 std::string tok;
76 double eLevel;
77
78 // Header line: " N - E_level halflife ..."
79 if ((ss >> idx >> tok >> eLevel) && tok == "-") {
80 wantT1 = (idx == 2); // 41.5569 keV metastable state -> T1 transition
81 wantT2 = (idx == 1); // 9.4053 keV metastable state -> T2 transition
82 continue;
83 }
84
85 if (!wantT1 && !wantT2) {
86 continue;
87 }
88
89 // Transition line: " daughter E_gamma intensity multipolarity delta ICC_total K_frac ..."
90 ss.clear();
91 ss.str(line);
92 int daughter, multi;
93 double Eg, inten, delta, icc, kfrac;
94 if (!(ss >> daughter >> Eg >> inten >> multi >> delta >> icc >> kfrac)) {
95 continue;
96 }
97
98 if (wantT1) {
99 E_T1 = Eg;
100 ICC_T1 = icc;
101 Kfrac_T1 = kfrac;
102 gotT1 = true;
103 wantT1 = false;
104 }
105 if (wantT2) {
106 E_T2 = Eg;
107 ICC_T2 = icc;
108 gotT2 = true;
109 wantT2 = false;
110 }
111
112 if (gotT1 && gotT2) {
113 break;
114 }
115 }
116
117 if (!gotT1 || !gotT2) {
118 return false;
119 }
120
121 // Sanity check — values far outside these ranges indicate a corrupt or wrong file
122 if (E_T1 < 25. || E_T1 > 40.) {
123 return false;
124 }
125 if (E_T2 < 5. || E_T2 > 15.) {
126 return false;
127 }
128 if (ICC_T1 < 100.) {
129 return false;
130 }
131 if (ICC_T2 < 5.) {
132 return false;
133 }
134 if (Kfrac_T1 < 0.1 || Kfrac_T1 > 0.5) {
135 return false;
136 }
137
138 return true;
139}
140
142{
143 // ── Atomic constants (NIST, keV, converted to GeV for ROOT) ──────────
144 static constexpr double kKbind = 14.3256e-6; // Kr K-shell binding
145 static constexpr double kL1bind = 1.9210e-6; // Kr L1-shell binding
146 static constexpr double kKalpha = 12.6000e-6; // Kr Kα X-ray
147 static constexpr double kKfluY = 0.652; // Kr K-shell fluorescence yield
148
149 // ── Fallback values from PhotonEvaporation5.7/z36.a83 ────────────────
150 double E_T1 = 32.1516e-6; // [GeV] T1 gamma energy
151 double ICC_T1 = 2035.0;
152 double Kfrac_T1 = 0.248; // fraction of ICC_T1 going through K-shell
153 double E_T2 = 9.4053e-6; // [GeV] T2 gamma energy
154 double ICC_T2 = 17.09;
155
156 // ── Try to load from installed G4 data (keV in file → convert to GeV) ─
157 const char* g4dir = std::getenv("G4LEVELGAMMADATA");
158 if (g4dir) {
159 std::string path = std::string(g4dir) + "/z36.a83";
160 double fE1, fICC1, fKf1, fE2, fICC2;
161 if (parseG4PhotonEvap(path.c_str(), fE1, fICC1, fKf1, fE2, fICC2)) {
162 E_T1 = fE1 * 1e-6;
163 ICC_T1 = fICC1;
164 Kfrac_T1 = fKf1;
165 E_T2 = fE2 * 1e-6;
166 ICC_T2 = fICC2;
167 LOG(info) << "[KrDecayTable] Loaded from " << path << " -- "
168 << "T1: E=" << fE1 << " keV ICC=" << ICC_T1 << " K_frac=" << Kfrac_T1 << ", "
169 << "T2: E=" << fE2 << " keV ICC=" << ICC_T2;
170 } else {
171 LOG(warning) << "[KrDecayTable] Could not parse " << path << " -- using hardcoded fallback values";
172 }
173 } else {
174 LOG(warning) << "[KrDecayTable] G4LEVELGAMMADATA not set -- using hardcoded fallback values";
175 }
176
177 // ── Derived probabilities ─────────────────────────────────────────────
178 const double P_T1_g = 1.0 / (1.0 + ICC_T1); // T1 gamma
179 const double P_T1_K_IC = Kfrac_T1 * ICC_T1 / (1.0 + ICC_T1); // T1 K-shell IC
180 const double P_T1_out = ICC_T1 / (1.0 + ICC_T1) - P_T1_K_IC; // T1 outer-shell IC
181 const double P_T2_g = 1.0 / (1.0 + ICC_T2); // T2 gamma
182 const double P_T2_IC = ICC_T2 / (1.0 + ICC_T2); // T2 IC
183
184 const double P_T1_Kf = P_T1_K_IC * kKfluY; // T1 K-IC → K-fluorescence
185 const double P_T1_Ka = P_T1_K_IC * (1.0 - kKfluY); // T1 K-IC → K-Auger
186
187 // ── Particle kinetic energies ─────────────────────────────────────────
188 const double E_L_CE_T1 = E_T1 - kL1bind; // T1 L1-shell CE
189 const double E_K_CE = E_T1 - kKbind; // T1 K-shell CE
190 const double E_KLL = kKbind - 2.0 * kL1bind; // KLL Auger
191 const double E_res_aug = kKbind - E_KLL; // residual Auger (K-Auger path)
192 const double E_Laug_Kf = kKbind - kKalpha; // L-Auger after Kα emission
193 const double E_L_CE_T2 = E_T2 - kL1bind; // T2 L1-shell CE
194
195 // ── Eight channels (T1-mode × T2-mode) ───────────────────────────────
196 int i = 0;
197
198 // Ch 0: T1 outer-IC + T2 IC → 41.6 keV local
199 channels[i] = {P_T1_out * P_T2_IC, 4, {{11, E_L_CE_T1}, {11, kL1bind}, {11, E_L_CE_T2}, {11, kL1bind}}};
200 i++;
201
202 // Ch 1: T1 outer-IC + T2 γ → 32.2 keV local + γ(T2) separate
203 channels[i] = {P_T1_out * P_T2_g, 3, {{11, E_L_CE_T1}, {11, kL1bind}, {22, E_T2}}};
204 i++;
205
206 // Ch 2: T1 K-IC + K-Auger + T2 IC → 41.6 keV local
207 channels[i] = {P_T1_Ka * P_T2_IC, 5, {{11, E_K_CE}, {11, E_KLL}, {11, E_res_aug}, {11, E_L_CE_T2}, {11, kL1bind}}};
208 i++;
209
210 // Ch 3: T1 K-IC + K-fluor + T2 IC → 29.1 keV local + Kα separate
211 channels[i] = {P_T1_Kf * P_T2_IC, 5, {{11, E_K_CE}, {11, E_Laug_Kf}, {22, kKalpha}, {11, E_L_CE_T2}, {11, kL1bind}}};
212 i++;
213
214 // Ch 4: T1 K-IC + K-fluor + T2 γ → 19.6 keV local + Kα + γ(T2) separate
215 channels[i] = {P_T1_Kf * P_T2_g, 4, {{11, E_K_CE}, {11, E_Laug_Kf}, {22, kKalpha}, {22, E_T2}}};
216 i++;
217
218 // Ch 5: T1 K-IC + K-Auger + T2 γ → 32.2 keV local + γ(T2) separate
219 channels[i] = {P_T1_Ka * P_T2_g, 4, {{11, E_K_CE}, {11, E_KLL}, {11, E_res_aug}, {22, E_T2}}};
220 i++;
221
222 // Ch 6: T1 γ + T2 IC → 9.4 keV local + γ(T1) separate
223 channels[i] = {P_T1_g * P_T2_IC, 3, {{22, E_T1}, {11, E_L_CE_T2}, {11, kL1bind}}};
224 i++;
225
226 // Ch 7: T1 γ + T2 γ → both photons escape
227 channels[i] = {P_T1_g * P_T2_g, 2, {{22, E_T1}, {22, E_T2}}};
228 i++;
229
230 double sum = 0.;
231 for (int j = 0; j < kNChannels; j++) {
233 }
234 double cum = 0.;
235 for (int j = 0; j < kNChannels; j++) {
236 cum += channels[j].fraction / sum;
237 cumulative[j] = cum;
238 }
239}
240
242{
243 double r = gRandom->Uniform();
244 for (int i = 0; i < kNChannels; i++) {
245 if (r <= cumulative[i]) {
246 return channels[i];
247 }
248 }
249 return channels[kNChannels - 1];
250}
251
252} // namespace o2::tpc
253
254// ── GeneratorKrDecay ─────────────────────────────────────────────────────
255
256namespace o2::eventgen
257{
258
259GeneratorKrDecay::GeneratorKrDecay() : Generator("KrDecay", "83mKr TPC calibration source")
260{
261}
262
264
265int GeneratorKrDecay::krO2EncodedStatus(int hepmc, int gen)
266{
267 return (5 << 29) | ((gen & 0x3FF) << 9) | (hepmc & 0x1FF);
268}
269
271{
272 if (const char* env = std::getenv("KR_N_PER_EVENT")) {
273 int n = std::atoi(env);
274 if (n > 0) {
275 mNPerEvent = n;
276 }
277 }
278 LOG(info) << "[GeneratorKrDecay] Init: rInner=" << kRInner << " rOuter=" << kROuter
279 << " halfZ=" << kHalfZ << " nPerEvent=" << mNPerEvent;
280
281 mTable = std::make_unique<o2::tpc::KrDecayTable>();
282 setPositionUnit(1.0); // coords in cm
283 return Generator::Init();
284}
285
287{
288 mVertices.clear();
289 // 1 cm safety margin inside field cage boundaries — avoids placing
290 // electrons exactly on sector boundaries which can cause hit coordinate
291 // transformation crashes in the merger when ROOT fills the TTree.
292 const double rInner = kRInner + 1.0;
293 const double rOuter = kROuter - 1.0;
294 const double halfZ = kHalfZ - 1.0;
295 const double r2Min = rInner * rInner;
296 const double r2Max = rOuter * rOuter;
297 for (int i = 0; i < mNPerEvent; ++i) {
298 double r = std::sqrt(gRandom->Uniform(r2Min, r2Max));
299 double phi = gRandom->Uniform(0., TMath::TwoPi());
300 double z = gRandom->Uniform(-halfZ, halfZ);
301 mVertices.push_back({{r * std::cos(phi), r * std::sin(phi), z}});
302 }
303 return kTRUE;
304}
305
307{
308 mParticles.clear();
309 // Reserve before any push_back to prevent std::vector reallocation.
310 // TParticle inherits from TObject (ROOT memory pool) and is not safe
311 // to move-construct via std::vector reallocation on macOS arm64 —
312 // ROOT's TStorage bookkeeping gets corrupted, causing malloc failures
313 // ~50-100 events later. Reserving eliminates all reallocations.
314 mParticles.reserve(mNPerEvent * o2::tpc::KrDecayTable::kNChannels);
315
316 const int status = krO2EncodedStatus(1, 0);
317
318 for (size_t iv = 0; iv < mVertices.size(); ++iv) {
319 double vx = mVertices[iv][0];
320 double vy = mVertices[iv][1];
321 double vz = mVertices[iv][2];
322
323 const o2::tpc::KrDecayTable::Channel& ch = mTable->sample();
324 for (int ip = 0; ip < ch.nProducts; ++ip) {
325 int pdg = ch.products[ip].pdg;
326 double eKin = ch.products[ip].eKin;
327 if (eKin < 0.1e-6) {
328 continue;
329 }
330
331 double mass = (pdg == 11) ? 0.000511 : 0.0;
332 double E = eKin + mass;
333 double pmag = std::sqrt(std::max(0., E * E - mass * mass));
334 double cosT = gRandom->Uniform(-1., 1.);
335 double sinT = std::sqrt(1. - cosT * cosT);
336 double phi = gRandom->Uniform(0., TMath::TwoPi());
337
338 TParticle part(pdg, status, -1, -1, -1, -1,
339 pmag * sinT * std::cos(phi),
340 pmag * sinT * std::sin(phi),
341 pmag * cosT,
342 E, vx, vy, vz, 0.);
343 mParticles.push_back(part);
344 // kToBeDone=BIT(16), kPrimary=BIT(17)
345 // Must be set AFTER push_back — copy constructor resets fBits
346 mParticles.back().SetBit(BIT(16));
347 mParticles.back().SetBit(BIT(17));
348 }
349 }
350 return kTRUE;
351}
352
353} // namespace o2::eventgen
default_random_engine gen(dev())
std::vector< double > sum
int32_t i
Generator for 83mKr decays, for TPC gain-map calibration simulation.
uint32_t j
Definition RawData.h:0
void setPositionUnit(double val)
Definition Generator.h:87
std::vector< TParticle > mParticles
Definition Generator.h:151
Bool_t Init() override
double cumulative[kNChannels]
const Channel & sample() const
static const int kNChannels
Channel channels[kNChannels]
GLdouble n
Definition glcorearb.h:1982
GLdouble f
Definition glcorearb.h:310
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLboolean r
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
int32_t const char int32_t line
Global TPC definitions and constants.
Definition SimTraits.h:172
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"