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
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
char error[CURL_ERROR_SIZE]
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 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"