Project
Loading...
Searching...
No Matches
EventDecayer.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
19#include <fairlogger/Logger.h>
20#include <TMCProcess.h>
21#include <TRandom.h>
22#include <TString.h>
23#include <TSystem.h>
24
25#include <cmath>
26#include <cstdlib>
27#include <fstream>
28#include <sstream>
29#include <stdexcept>
30
31namespace o2
32{
33namespace eventgen
34{
35
36namespace
37{
38// speed of light in mm/s, to convert Pythia times (mm/c) to seconds
39constexpr double kSpeedOfLightMMPerS = 2.99792458e11;
40
41// largest seed Pythia8 accepts for Random:seed
42constexpr long kMaxSeed = 900000000;
43
44// mixed into a seed taken from gRandom, so that it differs from the one of GeneratorPythia8
45constexpr unsigned int kGRandomSeedXor = 0x5eed0d3c;
46
47// Pythia settings that cut decays on the decay vertex, not supported here
48const char* const kVertexLimits[] = {"ParticleDecays:limitTau", "ParticleDecays:limitRadius", "ParticleDecays:limitCylinder"};
49
50std::string expand(std::string const& path)
51{
52 TString expanded = path;
53 gSystem->ExpandPathName(expanded);
54 return expanded.Data();
55}
56
57// sets the Pythia8 seed as GeneratorPythia8::seedGenerator, a seed of 0 derives it from gRandom
58bool setSeed(::Pythia8::Pythia& pythia, long seed)
59{
60 if (seed < 0) {
61 LOG(error) << "Failed to init \'EventDecayer\': negative seed " << seed;
62 return false;
63 }
64 if (seed == 0) {
65 seed = gRandom->TRandom::GetSeed() ^ kGRandomSeedXor; // from the seed set by o2-sim --seed
66 LOG(info) << "EventDecayer: using random seed from gRandom ^ " << kGRandomSeedXor << " % " << kMaxSeed + 1;
67 }
68 seed = seed % (kMaxSeed + 1);
69 if (!pythia.readString("Random:setSeed on") || !pythia.readString("Random:seed " + std::to_string(seed))) {
70 LOG(error) << "Failed to init \'EventDecayer\': cannot set the Pythia8 seed " << seed;
71 return false;
72 }
73 return true;
74}
75
76// parses one skipPdg entry into |pdg|, false if it is not an integer
77bool parsePdg(std::string const& code, int& pdg)
78{
79 try {
80 size_t used = 0;
81 pdg = std::abs(std::stoi(code, &used));
82 return used == code.size();
83 } catch (std::exception const&) {
84 return false;
85 }
86}
87
88// switches off the decays of the species listed in skipPdg
89bool skipDecays(::Pythia8::ParticleData& particleData, std::string const& skipPdg)
90{
91 std::istringstream skip(skipPdg);
92 std::string code;
93 while (skip >> code) {
94 int pdg = 0;
95 if (!parsePdg(code, pdg)) {
96 LOG(error) << "Failed to init \'EventDecayer\': skipPdg entry " << code << " is not a PDG code";
97 return false;
98 }
99 if (particleData.isParticle(pdg)) {
100 particleData.mayDecay(pdg, false);
101 } else {
102 LOG(info) << "EventDecayer: skipPdg entry " << pdg << " is unknown to Pythia8, so never decayed anyway";
103 }
104 }
105 return true;
106}
107} // namespace
108
109/*****************************************************************/
110
112{
113 auto add = [](std::map<int, long>& to, std::map<int, long> const& from) {
114 for (auto const& [pdg, count] : from) {
115 to[pdg] += count;
116 }
117 };
118 add(decayed, other.decayed);
119 add(failed, other.failed);
120 add(unknownInO2, other.unknownInO2);
121 add(unknownInG4, other.unknownInG4);
122 for (auto const& [pdg, lifetimes] : other.lifetimeMismatch) {
123 lifetimeMismatch[pdg] = lifetimes;
124 }
125}
126
128{
129 for (auto const& [pdg, count] : decayed) {
130 LOG(info) << "EventDecayer: decayed pdg " << pdg << ": " << count;
131 }
132 for (auto const& [pdg, count] : failed) {
133 LOG(warn) << "EventDecayer: Pythia8 failed to decay pdg " << pdg << ": " << count;
134 }
135 for (auto const& [pdg, count] : unknownInO2) {
136 LOG(warn) << "EventDecayer: pdg " << pdg << " left for transport is unknown to O2DatabasePDG: " << count;
137 }
138 for (auto const& [pdg, count] : unknownInG4) {
139 LOG(warn) << "EventDecayer: pdg " << pdg << " left for transport is not in the Geant4 table (o2-sim may define it at runtime): " << count;
140 }
141 for (auto const& [pdg, lifetimes] : lifetimeMismatch) {
142 LOG(warn) << "EventDecayer: lifetime of pdg " << pdg << " differs between Pythia8 (ctau " << lifetimes.first
143 << " mm) and Geant4 (ctau " << lifetimes.second << " mm)";
144 }
145}
146
147/*****************************************************************/
148
149std::unordered_map<int, G4ParticleInfo> loadG4ParticleTable(std::string const& path)
150{
151 std::unordered_map<int, G4ParticleInfo> table;
152 std::ifstream in(path);
153 if (!in) {
154 LOG(error) << "EventDecayer: cannot read Geant4 particle table " << path;
155 return table;
156 }
157 std::string line;
158 while (std::getline(in, line)) {
159 if (line.empty() || line[0] == '#') {
160 continue;
161 }
162 std::istringstream fields(line);
163 int pdg = 0;
164 G4ParticleInfo info;
165 if (!(fields >> pdg >> info.name >> info.ctauMM >> info.shortLived >> info.hasDecayTable)) {
166 LOG(warn) << "EventDecayer: skipping malformed line in " << path << ": " << line;
167 continue;
168 }
169 table[pdg] = info;
170 }
171 return table;
172}
173
174/*****************************************************************/
175
177{
178 mPythia.readString("ProcessLevel:all off");
179
181 LOG(info) << "Init \'EventDecayer\' with following parameters";
182 LOG(info) << param;
183
184 // before the config files, so that they can override it
185 if (!setSeed(mPythia, param.seed)) {
186 return false;
187 }
188
189 mConfigs.clear();
190 auto& fallback = DecayerPythia8Param::Instance();
191 for (int i = 0; i < 8; ++i) {
192 auto const& entry = param.config[i].empty() ? fallback.config[i] : param.config[i];
193 if (entry.empty()) {
194 continue;
195 }
196 auto config = expand(entry);
197 LOG(info) << "Reading configuration from file: " << config;
198 if (!mPythia.readFile(config, true)) {
199 LOG(error) << "Failed to init \'EventDecayer\': problems with configuration file " << config;
200 return false;
201 }
202 mConfigs.push_back(config);
203 }
204
205 for (auto name : kVertexLimits) {
206 if (mPythia.settings.flag(name)) {
207 LOG(error) << "EventDecayer does not support " << name << ", switching it off";
208 mPythia.settings.flag(name, false);
209 }
210 }
211 if (param.tau0Max > 0.) {
212 mPythia.settings.flag("ParticleDecays:limitTau0", true);
213 mPythia.settings.parm("ParticleDecays:tau0Max", param.tau0Max);
214 }
215
216 if (!skipDecays(mPythia.particleData, param.skipPdg)) {
217 return false;
218 }
219
220 mVerbose = param.verbose;
221 mPythia.readString(param.showChanged ? "Init:showChangedParticleData on" : "Init:showChangedParticleData off");
222
223 if (!mPythia.init()) {
224 LOG(error) << "Failed to init \'EventDecayer\': init returned with error";
225 return false;
226 }
227 mSeed = mPythia.settings.mode("Random:seed");
228 LOG(info) << "EventDecayer: Pythia8 seed " << mSeed;
229 mTau0Max = mPythia.settings.flag("ParticleDecays:limitTau0") ? mPythia.settings.parm("ParticleDecays:tau0Max") : 0.;
230 if (mTau0Max > 0.) {
231 LOG(info) << "EventDecayer: species with tau0 above " << mTau0Max << " mm/c stay undecayed";
232 }
233
234 mG4Particles.clear();
235 mHasG4Table = !param.g4ParticleTable.empty();
236 if (mHasG4Table) {
237 auto path = expand(param.g4ParticleTable);
238 mG4Particles = loadG4ParticleTable(path);
239 if (mG4Particles.empty()) {
240 LOG(error) << "Failed to init \'EventDecayer\': no particles in Geant4 particle table " << path;
241 return false;
242 }
243 LOG(info) << "EventDecayer: read " << mG4Particles.size() << " particles from Geant4 particle table " << path;
244 } else {
245 LOG(info) << "EventDecayer: no Geant4 particle table given, skipping the Geant4 PDG and lifetime check";
246 }
247 return true;
248}
249
250/*****************************************************************/
251
253{
254 std::string summary;
255 for (auto const& config : mConfigs) {
256 summary += (summary.empty() ? "" : ";") + config;
257 }
258 return summary;
259}
260
262{
263 for (auto name : kVertexLimits) {
264 if (mPythia.settings.flag(name)) {
265 return true;
266 }
267 }
268 return false;
269}
270
271/*****************************************************************/
272
273bool EventDecayer::decayable(int pdg) const
274{
275 auto& particleData = mPythia.particleData;
276 return particleData.mayDecay(pdg) && particleData.canDecay(pdg) && !(mTau0Max > 0. && particleData.tau0(pdg) > mTau0Max);
277}
278
279EventDecayerReport EventDecayer::decay(std::vector<TParticle>& particles)
280{
282 const int nOriginal = particles.size();
283 for (int i = 0; i < nOriginal; ++i) {
284 auto const& particle = particles[i];
285 auto pdg = particle.GetPdgCode();
286 if (!particle.TestBit(ParticleStatus::kToBeDone) || particle.GetUniqueID() != kPPrimary || !decayable(pdg)) {
287 continue;
288 }
289 if (decayOne(particles, i)) {
290 report.decayed[pdg]++;
291 } else {
292 report.failed[pdg]++;
293 }
294 }
295
296 checkParticles(particles, report);
297 checkLifetimes(report);
298 return report;
299}
300
301/*****************************************************************/
302
303bool EventDecayer::decayOne(std::vector<TParticle>& particles, int index)
304{
305 // a copy, since appending to particles may reallocate
306 const TParticle mother = particles[index];
307 auto& event = mPythia.event;
308 event.clear();
309 // the caller's units to GeV/c and GeV
310 const double px = mother.Px() * mMomentumUnit;
311 const double py = mother.Py() * mMomentumUnit;
312 const double pz = mother.Pz() * mMomentumUnit;
313 const double e = mother.Energy() * mEnergyUnit;
314 const double m2 = e * e - px * px - py * py - pz * pz;
315 event.append(mother.GetPdgCode(), 1, 0, 0, px, py, pz, e, m2 > 0. ? std::sqrt(m2) : -std::sqrt(-m2));
316 event[0].vProd(0., 0., 0., 0.);
317 event[0].tau(event[0].tau0() * mPythia.rndm.exp());
318 if (!mPythia.moreDecays() || event[0].isFinal()) {
319 return false;
320 }
321 if (mVerbose) {
322 event.list();
323 }
324 // partons (meMode 91-100) or a product that Pythia failed to decay make the whole decay fail
325 for (int k = 1; k < event.size(); ++k) {
326 auto const& entry = event[k];
327 if (entry.isFinal() && (entry.isParton() || decayable(entry.id()))) {
328 return false;
329 }
330 }
331
332 // Pythia entry 0 is the original particle, entry k > 0 goes to offset + k - 1
333 const int offset = particles.size();
334 auto toIndex = [index, offset](int k) { return k == 0 ? index : offset + k - 1; };
335 // Pythia mm and mm/c to the caller's units
336 const double toPosition = 0.1 / mPositionUnit;
337 const double toTime = 1. / (kSpeedOfLightMMPerS * mTimeUnit);
338
339 for (int k = 1; k < event.size(); ++k) {
340 auto const& entry = event[k];
341 int mother1 = toIndex(entry.mother1());
342 int mother2 = (entry.mother2() > 0 && entry.mother2() != entry.mother1()) ? toIndex(entry.mother2()) : -1;
343 int daughter1 = entry.daughter1() > 0 ? toIndex(entry.daughter1()) : -1;
344 int daughter2 = entry.daughter2() > 0 ? toIndex(entry.daughter2()) : daughter1;
345 TParticle product(entry.id(), 0, mother1, mother2, daughter1, daughter2,
346 entry.px() / mMomentumUnit, entry.py() / mMomentumUnit, entry.pz() / mMomentumUnit, entry.e() / mEnergyUnit,
347 mother.Vx() + entry.xProd() * toPosition, mother.Vy() + entry.yProd() * toPosition,
348 mother.Vz() + entry.zProd() * toPosition, mother.T() + entry.tProd() * toTime);
349 bool isFinal = entry.isFinal();
350 o2::mcutils::MCGenHelper::encodeParticleStatusAndTracking(product, isFinal ? 1 : 2, std::abs(entry.status()), isFinal);
351 product.SetWeight(mother.GetWeight());
352 product.SetUniqueID(kPPrimary);
353 particles.push_back(product);
354 }
355
356 auto& decayed = particles[index];
357 // the mother keeps its generator status code, only its HepMC status becomes decayed
359 decayed.SetFirstDaughter(toIndex(event[0].daughter1()));
360 decayed.SetLastDaughter(toIndex(event[0].daughter2() > 0 ? event[0].daughter2() : event[0].daughter1()));
361 return true;
362}
363
364/*****************************************************************/
365
366void EventDecayer::checkParticles(std::vector<TParticle> const& particles, EventDecayerReport& report) const
367{
368 auto pdgDB = o2::O2DatabasePDG::Instance();
369 for (auto const& particle : particles) {
370 if (!particle.TestBit(ParticleStatus::kToBeDone)) {
371 continue;
372 }
373 auto pdg = particle.GetPdgCode();
374 if (!pdgDB->GetParticle(pdg)) {
375 report.unknownInO2[pdg]++;
376 }
377 // ions (codes above 10^9) are created by Geant4 on demand, so they are not in the table
378 if (mHasG4Table && std::abs(pdg) < 1000000000 && mG4Particles.find(pdg) == mG4Particles.end()) {
379 report.unknownInG4[pdg]++;
380 }
381 }
382}
383
384void EventDecayer::checkLifetimes(EventDecayerReport& report) const
385{
386 if (!mHasG4Table) {
387 return;
388 }
389 for (auto const& [pdg, count] : report.decayed) {
390 auto g4 = mG4Particles.find(pdg);
391 if (g4 == mG4Particles.end() || g4->second.ctauMM == 0.) {
392 continue;
393 }
394 double pythiaCtau = mPythia.particleData.tau0(pdg);
395 double g4Ctau = g4->second.ctauMM;
396 if (std::abs(pythiaCtau - g4Ctau) / g4Ctau > 0.01) {
397 report.lifetimeMismatch[pdg] = {pythiaCtau, g4Ctau};
398 }
399 }
400}
401
402} // namespace eventgen
403} // namespace o2
std::vector< std::shared_ptr< arrow::Field > > fields
int32_t i
Utility functions for MC particles.
@ kToBeDone
static TDatabasePDG * Instance()
EventDecayerReport decay(std::vector< TParticle > &particles)
std::string configSummary() const
static void encodeParticleStatusAndTracking(TParticle &particle, bool wanttracking=true)
Definition MCUtils.cxx:211
struct _cl_event * event
Definition glcorearb.h:2982
GLint GLsizei count
Definition glcorearb.h:399
GLuint entry
Definition glcorearb.h:5735
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLintptr offset
Definition glcorearb.h:660
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLenum GLfloat param
Definition glcorearb.h:271
void report(gsl::span< o2::InteractionTimeRecord > irs, int threshold, bool verbose)
std::unordered_map< int, G4ParticleInfo > loadG4ParticleTable(std::string const &path)
Reads the Geant4 particle table written by o2-sim-g4-particle-table.
int const float const TrackSeed< NLayers > & seed
int getGenStatusCode(MCGenStatusEncoding enc)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
Counts of what one or more EventDecayer::decay calls did, per PDG code.
std::map< int, long > unknownInG4
std::map< int, std::pair< double, double > > lifetimeMismatch
std::map< int, long > failed
void merge(EventDecayerReport const &other)
std::map< int, long > unknownInO2
std::map< int, long > decayed
One entry of the Geant4 particle table.
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"