Project
Loading...
Searching...
No Matches
TPCLoopers.cxx
Go to the documentation of this file.
1// Copyright 2024-2025 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
13
16#include "CCDB/CcdbApi.h"
18#include <SimConfig/SimConfig.h>
19#include "TF1.h"
20#include <filesystem>
21#include <algorithm>
22#include <limits>
23#include <memory>
26#include <iostream>
27#include <fstream>
28#include "TDatabasePDG.h"
29
30// Static Ort::Env instance for multiple onnx model loading
31Ort::Env global_env(ORT_LOGGING_LEVEL_WARNING, "GlobalEnv");
32
33// This class is responsible for loading the scaler parameters from a JSON file
34// and applying the inverse transformation to the generated data.
35
36void Scaler::load(const std::string& filename)
37{
38 std::ifstream file(filename);
39 if (!file.is_open()) {
40 throw std::runtime_error("Error: Could not open scaler file!");
41 }
42
43 std::string json_str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
44 file.close();
45
46 rapidjson::Document doc;
47 doc.Parse(json_str.c_str());
48
49 if (doc.HasParseError()) {
50 throw std::runtime_error("Error: JSON parsing failed!");
51 }
52
53 normal_min = jsonArrayToVector(doc["normal"]["min"]);
54 normal_max = jsonArrayToVector(doc["normal"]["max"]);
55 outlier_center = jsonArrayToVector(doc["outlier"]["center"]);
56 outlier_scale = jsonArrayToVector(doc["outlier"]["scale"]);
57}
58
59std::vector<double> Scaler::inverse_transform(const std::vector<double>& input)
60{
61 std::vector<double> output;
62 for (int i = 0; i < input.size(); ++i) {
63 if (i < input.size() - 2) {
64 output.push_back(input[i] * (normal_max[i] - normal_min[i]) + normal_min[i]);
65 } else {
66 output.push_back(input[i] * outlier_scale[i - (input.size() - 2)] + outlier_center[i - (input.size() - 2)]);
67 }
68 }
69
70 return output;
71}
72
73std::vector<double> Scaler::jsonArrayToVector(const rapidjson::Value& jsonArray)
74{
75 std::vector<double> vec;
76 for (int i = 0; i < jsonArray.Size(); ++i) {
77 vec.push_back(jsonArray[i].GetDouble());
78 }
79 return vec;
80}
81
82// This class loads the ONNX model and generates samples using it.
83
84ONNXGenerator::ONNXGenerator(Ort::Env& shared_env, const std::string& model_path)
85 : env(shared_env), session(nullptr)
86{
87 Ort::SessionOptions session_options;
88 session_options.SetIntraOpNumThreads(1);
89 session = Ort::Session(env, model_path.c_str(), session_options);
90}
91
92std::vector<double> ONNXGenerator::generate_sample()
93{
94 Ort::AllocatorWithDefaultOptions allocator;
95
96 // Generate a latent vector (z)
97 std::vector<float> z(100);
98 for (auto& v : z) {
99 v = rand_gen.Gaus(0.0, 1.0);
100 }
101
102 // Prepare input tensor
103 std::vector<int64_t> input_shape = {1, 100};
104 // Get memory information
105 Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
106
107 // Create input tensor correctly
108 Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
109 memory_info, z.data(), z.size(), input_shape.data(), input_shape.size());
110 // Run inference
111 const char* input_names[] = {"z"};
112 const char* output_names[] = {"output"};
113 auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor, 1, output_names, 1);
114
115 // Extract output
116 float* output_data = output_tensors.front().GetTensorMutableData<float>();
117 // Get the size of the output tensor
118 auto output_tensor_info = output_tensors.front().GetTensorTypeAndShapeInfo();
119 size_t output_data_size = output_tensor_info.GetElementCount(); // Total number of elements in the tensor
120 std::vector<double> output;
121 for (int i = 0; i < output_data_size; ++i) {
122 output.push_back(output_data[i]);
123 }
124
125 return output;
126}
127
128namespace o2
129{
130namespace eventgen
131{
132
133namespace
134{
135// Radial limits of the region from which a looper can still reach the TPC
136// sensitive gas. The field cage positions are those used by the
137// "ExcludeFCGap" selection in o2::tpc::Detector::ProcessHits()
138// A looper can enter the sensitive gas from just outside it, so an additional margin is set
139// with a factor two over the largest radial excursion which was observed in validation (~4.2 cm).
140//
141// No cut is applied on z because loopers spiral the field lines and a vertex as far as |z| = 283 cm
142// feeds hits into the gas. A cut here would discard loopers that produce TPC signals.
143constexpr double kFcLxIn = 82.428409; // cm, inner field cage strips
144constexpr double kRodROut = 254.25 + 2.2; // cm, outer field cage rods plus their radial size
145constexpr double kLooperRadialReach = 10.; // cm, margin for the helix sweep
146constexpr double kTPCActiveRMin = kFcLxIn - kLooperRadialReach;
147constexpr double kTPCActiveRMax = kRodROut + kLooperRadialReach;
148} // namespace
149
150bool GenTPCLoopers::isInTPCActiveVolume(double vx, double vy) const
151{
152 const double vt = std::sqrt(vx * vx + vy * vy);
153 return (vt >= kTPCActiveRMin && vt <= kTPCActiveRMax);
154}
155
156void GenTPCLoopers::setGeomProtection(bool protect)
157{
158 mGeomProtection = protect;
159 if (mGeomProtection) {
160 LOG(debug) << "TPC loopers geometrical protection: ON (accepting vertices with "
161 << kTPCActiveRMin << " <= Vt <= " << kTPCActiveRMax << " cm)";
162 } else {
163 LOG(warning) << "TPC loopers geometrical protection: OFF - loopers will be generated outside the TPC active volume as well.";
164 }
165}
166
167bool GenTPCLoopers::ownsCollision(const std::vector<o2::steer::EventPart>& parts, int source)
168{
169 // A collision is owned by the source with the highest ID among the event parts it holds, so that
170 // exactly one of the simulations feeding a collision context (i.e. bkg, sgn) generates its loopers.
171 // The signal comes after the background and has one event per collision, so it tiles the time
172 // exactly; a reused background event can only cover the average of its gaps. This assumes that
173 // signal events are not reused. The rule only looks at the collision itself, because the
174 // background reads the global context and each signal its per-timeframe one.
175 int highest = std::numeric_limits<int>::min();
176 for (const auto& part : parts) {
177 highest = std::max(highest, part.sourceID);
178 }
179 return highest == source;
180}
181
182bool GenTPCLoopers::ownsContextCollisions()
183{
184 // Loopers are a detector background which is continuous in time: event by event this generator
185 // covers the time interval between consecutive collisions of the collision context, so a single
186 // simulation tiles the whole timeframe exactly once. When several transport simulations feed the
187 // same context (embedding), each of them would tile that same time axis again and the loopers
188 // would end up in the digits as many times as there are simulations.
189 // Every collision is therefore owned by exactly one source, the one with the highest source ID
190 // among the event parts present, and only the owner generates loopers for it. Embedding without
191 // replacement ("@" patterns) gives the signal the collisions it is embedded into and the
192 // background the others; when the signal replaces the background ("r" patterns) each source
193 // owns the collisions it is present in alone.
194 const auto& simConfig = o2::conf::SimConfig::Instance();
195 const auto contextAndPrefix = simConfig.getCollContextFilenameAndEventPrefix();
196 const std::string contextName = contextAndPrefix.first.empty() ? "collisioncontext.root" : contextAndPrefix.first;
197 const std::string& simPrefix = contextAndPrefix.second;
198 if (!std::filesystem::exists(contextName)) {
199 return true; // no context to share with anybody
200 }
201 std::unique_ptr<o2::steer::DigitizationContext> context(o2::steer::DigitizationContext::loadFromFile(contextName));
202 if (!context) {
203 LOG(warn) << "Could not read the collision context from " << contextName
204 << "; loopers will be generated without checking for other sources";
205 return true;
206 }
207 const auto source = context->findSimPrefix(simPrefix);
208 if (source < 0) {
209 if (context->getSimPrefixes().size() <= 1) {
210 return true; // a single source: nobody to share the loopers with
211 }
212 LOG(warn) << "Simulation prefix " << simPrefix << " not found in the collision context " << contextName
213 << ", which is shared by " << context->getSimPrefixes().size() << " sources; loopers will remain OFF";
214 return false;
215 }
216 // count the collisions this source takes part in and those it owns
217 unsigned int nPresent = 0, nOwned = 0;
218 for (const auto& parts : context->getEventParts()) {
219 if (ownsCollision(parts, source)) {
220 nOwned++;
221 }
222 if (std::find_if(parts.begin(), parts.end(), [source](const auto& part) { return part.sourceID == source; }) != parts.end()) {
223 nPresent++;
224 }
225 }
226 LOG(info) << "Loopers: source " << simPrefix << " owns " << nOwned << " of the " << nPresent
227 << " collisions it takes part in, out of " << context->getEventParts().size() << " in " << contextName;
228 return nOwned > 0;
229}
230
231void GenTPCLoopers::setupContextCoverage()
232{
233 // Work out how much of the time axis every event of this simulation has to cover with loopers.
234 // A collision stands for the time from itself to the next one (the last one to the end of the
235 // frame), and an event covers the time of the collisions it is used at: when it is used at
236 // several of them, as the background of an embedding production is, the loopers it carries are
237 // digitised once per use, so it covers their average. Collisions owned by another source count
238 // as zero. The loopers reaching the digits then add up to the reference number per orbit times
239 // the time this source owns, however its events are spread over the collision context.
240 mEventLooperTime.clear();
241 mSourceID = mCollisionContext ? mCollisionContext->findSimPrefix(o2::conf::SimConfig::Instance().getCollContextFilenameAndEventPrefix().second) : -1;
242 if (mSourceID < 0) {
243 return; // no context information: every event keeps the interval of the collision of same index
244 }
245 const auto& parts = mCollisionContext->getEventParts();
246 std::vector<int> nUses;
247 double ownedTime = 0.;
248 for (size_t c = 0; c < parts.size() && c < mInteractionTimeRecords.size(); ++c) {
249 const double gap = (c + 1 < mInteractionTimeRecords.size())
250 ? mInteractionTimeRecords[c + 1].bc2ns() - mInteractionTimeRecords[c].bc2ns()
251 : mTimeEnd - mInteractionTimeRecords[c].bc2ns();
252 const double owned = ownsCollision(parts[c], mSourceID) ? gap : 0.;
253 ownedTime += owned;
254 for (const auto& part : parts[c]) {
255 if (part.sourceID != mSourceID) {
256 continue;
257 }
258 if (part.entryID >= (int)mEventLooperTime.size()) { // entryIDs are dense, see SimConfig
259 mEventLooperTime.resize(part.entryID + 1, 0.);
260 nUses.resize(part.entryID + 1, 0);
261 }
262 mEventLooperTime[part.entryID] += owned;
263 nUses[part.entryID]++;
264 }
265 }
266 for (size_t e = 0; e < mEventLooperTime.size(); ++e) {
267 mEventLooperTime[e] /= std::max(nUses[e], 1);
268 }
269 LOG(info) << "Loopers: the " << mEventLooperTime.size() << " events of this simulation share the "
270 << ownedTime << " ns of the timeframe it owns";
271}
272
273GenTPCLoopers::GenTPCLoopers(std::string model_pairs, std::string model_compton,
274 std::string poisson, std::string gauss, std::string scaler_pair,
275 std::string scaler_compton)
276{
277 // Checking if the model files exist and are not empty
278 std::ifstream model_file[2];
279 model_file[0].open(model_pairs);
280 model_file[1].open(model_compton);
281 if (!model_file[0].is_open() || model_file[0].peek() == std::ifstream::traits_type::eof()) {
282 LOG(fatal) << "Error: Pairs model file is empty or does not exist!";
283 exit(1);
284 }
285 if (!model_file[1].is_open() || model_file[1].peek() == std::ifstream::traits_type::eof()) {
286 LOG(fatal) << "Error: Compton model file is empty or does not exist!";
287 exit(1);
288 }
289 model_file[0].close();
290 model_file[1].close();
291 // Checking if the scaler files exist and are not empty
292 std::ifstream scaler_file[2];
293 scaler_file[0].open(scaler_pair);
294 scaler_file[1].open(scaler_compton);
295 if (!scaler_file[0].is_open() || scaler_file[0].peek() == std::ifstream::traits_type::eof()) {
296 LOG(fatal) << "Error: Pairs scaler file is empty or does not exist!";
297 exit(1);
298 }
299 if (!scaler_file[1].is_open() || scaler_file[1].peek() == std::ifstream::traits_type::eof()) {
300 LOG(fatal) << "Error: Compton scaler file is empty or does not exist!";
301 exit(1);
302 }
303 scaler_file[0].close();
304 scaler_file[1].close();
305 // Checking if the poisson file exists and it's not empty
306 if (poisson != "" && poisson != "None" && poisson != "none") {
307 std::ifstream poisson_file(poisson);
308 if (!poisson_file.is_open() || poisson_file.peek() == std::ifstream::traits_type::eof()) {
309 LOG(fatal) << "Error: Poisson file is empty or does not exist!";
310 exit(1);
311 } else {
312 poisson_file >> mPoisson[0] >> mPoisson[1] >> mPoisson[2];
313 poisson_file.close();
314 mPoissonSet = true;
315 }
316 }
317 // Checking if the gauss file exists and it's not empty
318 if (gauss != "" && gauss != "None" && gauss != "none") {
319 std::ifstream gauss_file(gauss);
320 if (!gauss_file.is_open() || gauss_file.peek() == std::ifstream::traits_type::eof()) {
321 LOG(fatal) << "Error: Gauss file is empty or does not exist!";
322 exit(1);
323 } else {
324 gauss_file >> mGauss[0] >> mGauss[1] >> mGauss[2] >> mGauss[3];
325 gauss_file.close();
326 mGaussSet = true;
327 }
328 }
329 mONNX_pair = std::make_unique<ONNXGenerator>(global_env, model_pairs);
330 mScaler_pair = std::make_unique<Scaler>();
331 mScaler_pair->load(scaler_pair);
332 mONNX_compton = std::make_unique<ONNXGenerator>(global_env, model_compton);
333 mScaler_compton = std::make_unique<Scaler>();
334 mScaler_compton->load(scaler_compton);
335}
336
337Bool_t GenTPCLoopers::generateEvent()
338{
339 // Clear the vector of pairs
340 mGenPairs.clear();
341 // Clear the vector of compton electrons
342 mGenElectrons.clear();
343 if (mFlatGas) {
344 unsigned int nLoopers, nLoopersPairs, nLoopersCompton;
345 LOG(debug) << "mCurrentEvent is " << mCurrentEvent;
346 // the share of the timeframe this event has to cover, or, with no collision context to look
347 // at, the interval between the collision of same index and the next one
348 mTimeLimit = (mCurrentEvent < (int)mEventLooperTime.size())
349 ? mEventLooperTime[mCurrentEvent]
350 : ((mCurrentEvent < mInteractionTimeRecords.size() - 1) ? mInteractionTimeRecords[mCurrentEvent + 1].bc2ns() - mInteractionTimeRecords[mCurrentEvent].bc2ns() : mTimeEnd - mInteractionTimeRecords[mCurrentEvent].bc2ns());
351 LOG(debug) << "Current event time: " << mTimeLimit << " ns";
352 // With flat gas the number of loopers are adapted based on time interval widths
353 // The denominator is either the LHC orbit (if mFlatGasOrbit is true) or the mean interaction time record interval
354 nLoopers = mFlatGasOrbit ? (mFlatGasNumber * (mTimeLimit / o2::constants::lhc::LHCOrbitNS)) : (mFlatGasNumber * (mTimeLimit / mIntTimeRecMean));
355 nLoopersPairs = static_cast<unsigned int>(std::round(nLoopers * mLoopsFractionPairs));
356 nLoopersCompton = nLoopers - nLoopersPairs;
357 SetNLoopers(nLoopersPairs, nLoopersCompton);
358 LOG(info) << "Flat gas loopers: " << nLoopers << " (pairs: " << nLoopersPairs << ", compton: " << nLoopersCompton << ")";
359 generateEvent(mTimeLimit);
360 mCurrentEvent++;
361 } else {
362 // Set number of loopers if poissonian params are available
363 if (mPoissonSet) {
364 mNLoopersPairs = static_cast<unsigned int>(std::round(mMultiplier[0] * PoissonPairs()));
365 LOG(debug) << "Generated loopers pairs (Poisson): " << mNLoopersPairs;
366 }
367 if (mGaussSet) {
368 mNLoopersCompton = static_cast<unsigned int>(std::round(mMultiplier[1] * GaussianElectrons()));
369 LOG(debug) << "Generated compton electrons (Gauss): " << mNLoopersCompton;
370 }
371 // Generate pairs
372 for (int i = 0; i < mNLoopersPairs; ++i) {
373 std::vector<double> pair = mONNX_pair->generate_sample();
374 // Apply the inverse transformation using the scaler
375 std::vector<double> transformed_pair = mScaler_pair->inverse_transform(pair);
376 mGenPairs.push_back(transformed_pair);
377 }
378 // Generate compton electrons
379 for (int i = 0; i < mNLoopersCompton; ++i) {
380 std::vector<double> electron = mONNX_compton->generate_sample();
381 // Apply the inverse transformation using the scaler
382 std::vector<double> transformed_electron = mScaler_compton->inverse_transform(electron);
383 mGenElectrons.push_back(transformed_electron);
384 }
385 }
386 return true;
387}
388
389Bool_t GenTPCLoopers::generateEvent(double time_limit)
390{
391 LOG(info) << "Time constraint for loopers: " << time_limit << " ns";
392 // Generate pairs
393 for (int i = 0; i < mNLoopersPairs; ++i) {
394 std::vector<double> pair = mONNX_pair->generate_sample();
395 // Apply the inverse transformation using the scaler
396 std::vector<double> transformed_pair = mScaler_pair->inverse_transform(pair);
397 transformed_pair[9] = gRandom->Uniform(0., time_limit); // Regenerate time, scaling is not needed because time_limit is already in nanoseconds
398 mGenPairs.push_back(transformed_pair);
399 }
400 // Generate compton electrons
401 for (int i = 0; i < mNLoopersCompton; ++i) {
402 std::vector<double> electron = mONNX_compton->generate_sample();
403 // Apply the inverse transformation using the scaler
404 std::vector<double> transformed_electron = mScaler_compton->inverse_transform(electron);
405 transformed_electron[6] = gRandom->Uniform(0., time_limit); // Regenerate time, scaling is not needed because time_limit is already in nanoseconds
406 mGenElectrons.push_back(transformed_electron);
407 }
408 LOG(info) << "Generated Particles with time limit";
409 return true;
410}
411
412std::vector<TParticle> GenTPCLoopers::importParticles()
413{
414 std::vector<TParticle> particles;
415 const double mass_e = TDatabasePDG::Instance()->GetParticle(11)->Mass();
416 const double mass_p = TDatabasePDG::Instance()->GetParticle(-11)->Mass();
417 mNSkippedPairs = 0;
418 mNSkippedCompton = 0;
419 // Get looper pairs from the event
420 for (auto& pair : mGenPairs) {
421 double px_e, py_e, pz_e, px_p, py_p, pz_p;
422 double vx, vy, vz, time;
423 double e_etot, p_etot;
424 // The generative model is not currently fully constrained to the TPC geometry, so it places
425 // significant fraction of the vertices outside the drift gas.
426 // These are now dropped before they reach the transport.
427 if (mGeomProtection && !isInTPCActiveVolume(pair[6], pair[7])) {
428 mNSkippedPairs++;
429 continue;
430 }
431 px_e = pair[0];
432 py_e = pair[1];
433 pz_e = pair[2];
434 px_p = pair[3];
435 py_p = pair[4];
436 pz_p = pair[5];
437 vx = pair[6];
438 vy = pair[7];
439 vz = pair[8];
440 time = pair[9];
441 e_etot = TMath::Sqrt(px_e * px_e + py_e * py_e + pz_e * pz_e + mass_e * mass_e);
442 p_etot = TMath::Sqrt(px_p * px_p + py_p * py_p + pz_p * pz_p + mass_p * mass_p);
443 // Push the electron
444 TParticle electron(11, 1, -1, -1, -1, -1, px_e, py_e, pz_e, e_etot, vx, vy, vz, time / 1e9);
445 // Setting HepMC status code != 1 to avoid detecting the loopers as physical primaries
446 electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding);
447 electron.SetBit(ParticleStatus::kToBeDone, true);
448 particles.push_back(electron);
449 // Push the positron
450 TParticle positron(-11, 1, -1, -1, -1, -1, px_p, py_p, pz_p, p_etot, vx, vy, vz, time / 1e9);
451 positron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding);
452 positron.SetBit(ParticleStatus::kToBeDone, true);
453 particles.push_back(positron);
454 }
455 // Get compton electrons from the event
456 for (auto& compton : mGenElectrons) {
457 double px, py, pz;
458 double vx, vy, vz, time;
459 double etot;
460 if (mGeomProtection && !isInTPCActiveVolume(compton[3], compton[4])) {
461 mNSkippedCompton++;
462 continue;
463 }
464 px = compton[0];
465 py = compton[1];
466 pz = compton[2];
467 vx = compton[3];
468 vy = compton[4];
469 vz = compton[5];
470 time = compton[6];
471 etot = TMath::Sqrt(px * px + py * py + pz * pz + mass_e * mass_e);
472 // Push the electron
473 TParticle electron(11, 1, -1, -1, -1, -1, px, py, pz, etot, vx, vy, vz, time / 1e9);
474 // Setting HepMC status code != 1 to avoid detecting the loopers as physical primaries
475 electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding);
476 electron.SetBit(ParticleStatus::kToBeDone, true);
477 particles.push_back(electron);
478 }
479
480 return particles;
481}
482
483unsigned int GenTPCLoopers::PoissonPairs()
484{
485 unsigned int poissonValue;
486 do {
487 // Generate a Poisson-distributed random number with mean mPoisson[0]
488 poissonValue = mRandGen.Poisson(mPoisson[0]);
489 } while (poissonValue < mPoisson[1] || poissonValue > mPoisson[2]); // Regenerate if out of range
490
491 return poissonValue;
492}
493
494unsigned int GenTPCLoopers::GaussianElectrons()
495{
496 unsigned int gaussValue;
497 do {
498 // Generate a Normal-distributed random number with mean mGass[0] and stddev mGauss[1]
499 gaussValue = mRandGen.Gaus(mGauss[0], mGauss[1]);
500 } while (gaussValue < mGauss[2] || gaussValue > mGauss[3]); // Regenerate if out of range
501
502 return gaussValue;
503}
504
505void GenTPCLoopers::SetNLoopers(unsigned int nsig_pair, unsigned int nsig_compton)
506{
507 if (mFlatGas) {
508 mNLoopersPairs = nsig_pair;
509 mNLoopersCompton = nsig_compton;
510 } else {
511 if (mPoissonSet) {
512 LOG(info) << "Poissonian parameters correctly loaded.";
513 } else {
514 mNLoopersPairs = nsig_pair;
515 }
516 if (mGaussSet) {
517 LOG(info) << "Gaussian parameters correctly loaded.";
518 } else {
519 mNLoopersCompton = nsig_compton;
520 }
521 }
522}
523
524void GenTPCLoopers::SetMultiplier(const std::array<float, 2>& mult)
525{
526 // Multipliers will work only if the poissonian and gaussian parameters are set
527 // otherwise they will be ignored
528 if (mult[0] < 0 || mult[1] < 0) {
529 LOG(fatal) << "Error: Multiplier values must be non-negative!";
530 exit(1);
531 } else {
532 LOG(info) << "Multiplier values set to: Pair = " << mult[0] << ", Compton = " << mult[1];
533 mMultiplier[0] = mult[0];
534 mMultiplier[1] = mult[1];
535 }
536}
537
538void GenTPCLoopers::setFlatGas(Bool_t flat, Int_t number, Int_t nloopers_orbit)
539{
540 mFlatGas = flat;
541 if (mFlatGas) {
542 if (nloopers_orbit > 0) {
543 mFlatGasOrbit = true;
544 mFlatGasNumber = nloopers_orbit;
545 LOG(info) << "Flat gas loopers will be generated using orbit reference.";
546 } else {
547 mFlatGasOrbit = false;
548 if (number < 0) {
549 LOG(warn) << "Warning: Number of loopers per event must be non-negative! Switching option off.";
550 mFlatGas = false;
551 mFlatGasNumber = -1;
552 } else {
553 mFlatGasNumber = number;
554 }
555 }
556 if (mFlatGas) {
557 mContextFile = std::filesystem::exists("collisioncontext.root") ? TFile::Open("collisioncontext.root") : nullptr;
558 mCollisionContext = mContextFile ? (o2::steer::DigitizationContext*)mContextFile->Get("DigitizationContext") : nullptr;
559 mInteractionTimeRecords = mCollisionContext ? mCollisionContext->getEventRecords() : std::vector<o2::InteractionTimeRecord>{};
560 const auto& hbfUtils = o2::raw::HBFUtils::Instance();
561 if (mInteractionTimeRecords.empty()) {
562 // A timeframe can legitimately contain no collision at all when the interaction rate is
563 // low. No event is transported in that case, so nothing below is ever used; take the
564 // extent of the timeframe from HBFUtils rather than from the (absent) collisions.
565 LOG(warn) << "No interaction time records in the collision context; this timeframe holds no collision";
566 o2::InteractionRecord tfEndIR(0, hbfUtils.orbitFirstSampled + hbfUtils.nHBFPerTF);
567 mTimeEnd = tfEndIR.bc2ns();
568 } else {
569 LOG(info) << "Interaction Time records has " << mInteractionTimeRecords.size() << " entries.";
570 mCollisionContext->printCollisionSummary();
571 for (int c = 0; c < (int)mInteractionTimeRecords.size() - 1; c++) {
572 mIntTimeRecMean += mInteractionTimeRecords[c + 1].bc2ns() - mInteractionTimeRecords[c].bc2ns();
573 }
574 if (mInteractionTimeRecords.size() > 1) {
575 mIntTimeRecMean /= (mInteractionTimeRecords.size() - 1); // Average interaction time record used as reference
576 } else {
577 // a single collision gives no spacing to average; use the one implied by the rate
578 auto rate = mCollisionContext->getDigitizerInteractionRate();
579 mIntTimeRecMean = rate > 0. ? 1.e9 / rate : (double)o2::constants::lhc::LHCOrbitNS;
580 LOG(info) << "Only one collision in this timeframe; taking " << mIntTimeRecMean
581 << " ns as the mean interaction spacing from the interaction rate";
582 }
583 // Get the start time of the second orbit after the last interaction record
584 const auto& lastIR = mInteractionTimeRecords.back();
585 o2::InteractionRecord finalOrbitIR(0, lastIR.orbit + 2); // Final orbit, BC = 0
586 mTimeEnd = finalOrbitIR.bc2ns();
587 LOG(debug) << "Final orbit start time: " << mTimeEnd << " ns while last interaction record time is " << mInteractionTimeRecords.back().bc2ns() << " ns";
588 setupContextCoverage();
589 }
590 }
591 } else {
592 mFlatGasNumber = -1;
593 }
594 LOG(info) << "Flat gas loopers: " << (mFlatGas ? "ON" : "OFF") << ", Reference loopers number per " << (mFlatGasOrbit ? "orbit " : "event ") << mFlatGasNumber;
595}
596
597void GenTPCLoopers::setFractionPairs(float fractionPairs)
598{
599 if (fractionPairs < 0 || fractionPairs > 1) {
600 LOG(fatal) << "Error: Loops fraction for pairs must be in the range [0, 1].";
601 exit(1);
602 }
603 mLoopsFractionPairs = fractionPairs;
604 LOG(info) << "Pairs fraction set to: " << mLoopsFractionPairs;
605}
606
607void GenTPCLoopers::SetRate(const std::string& rateFile, bool isPbPb = true, int intRate)
608{
609 // Checking if the rate file exists and is not empty
610 TFile rate_file(rateFile.c_str(), "READ");
611 if (!rate_file.IsOpen() || rate_file.IsZombie()) {
612 LOG(fatal) << "Error: Rate file is empty or does not exist!";
613 exit(1);
614 }
615 const char* fitName = isPbPb ? "fitPbPb" : "fitpp";
616 auto fit = (TF1*)rate_file.Get(fitName);
617 if (!fit) {
618 LOG(fatal) << "Error: Could not find fit function '" << fitName << "' in rate file!";
619 exit(1);
620 }
621 mInteractionRate = intRate;
622 if (mInteractionRate < 0) {
623 mContextFile = std::filesystem::exists("collisioncontext.root") ? TFile::Open("collisioncontext.root") : nullptr;
624 if (!mContextFile || mContextFile->IsZombie()) {
625 LOG(fatal) << "Error: Interaction rate not provided and collision context file not found!";
626 exit(1);
627 }
628 mCollisionContext = (o2::steer::DigitizationContext*)mContextFile->Get("DigitizationContext");
629 mInteractionRate = std::floor(mCollisionContext->getDigitizerInteractionRate());
630 LOG(info) << "Interaction rate retrieved from collision context: " << mInteractionRate << " Hz";
631 if (mInteractionRate < 0) {
632 LOG(fatal) << "Error: Invalid interaction rate retrieved from collision context!";
633 exit(1);
634 }
635 }
636 auto ref = static_cast<int>(std::floor(fit->Eval(mInteractionRate / 1000.))); // fit expects rate in kHz
637 rate_file.Close();
638 if (ref <= 0) {
639 LOG(fatal) << "Computed flat gas number reference per orbit is <=0";
640 exit(1);
641 } else {
642 LOG(info) << "Set flat gas number to " << ref << " loopers per orbit using " << fitName << " from " << mInteractionRate << " Hz interaction rate.";
643 auto flat = true;
644 setFlatGas(flat, -1, ref);
645 }
646}
647
648void GenTPCLoopers::SetAdjust(float adjust)
649{
650 if (mFlatGas && mFlatGasOrbit && adjust >= -1.f && adjust != 0.f) {
651 LOG(info) << "Adjusting flat gas number per orbit by " << adjust * 100.f << "%";
652 mFlatGasNumber = static_cast<int>(std::round(mFlatGasNumber * (1.f + adjust)));
653 LOG(info) << "New flat gas number per orbit: " << mFlatGasNumber;
654 }
655}
656
657} // namespace eventgen
658} // namespace o2
std::ostringstream debug
int16_t time
Definition RawEventData.h:4
int32_t i
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines the history is shortened to this when it reaches twice the size Time in seconds an info message fades out after setInfoTime h OpenGL display display Font size Do not use Freetype for font Renderer for event Frontend to use for event ROOT macros used as track filter Console script file
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
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
@ kToBeDone
uint32_t c
Definition RawData.h:2
Ort::Env global_env(ORT_LOGGING_LEVEL_WARNING, "GlobalEnv")
static SimConfig & Instance()
Definition SimConfig.h:112
static DigitizationContext * loadFromFile(std::string_view filename="")
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLenum * rate
Definition glcorearb.h:5735
const GLdouble * v
Definition glcorearb.h:832
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLint ref
Definition glcorearb.h:291
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
uint8_t int statusCode int
const track::TrackFitContext< NLayers > & fit
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string filename()
std::vector< o2::ctf::BufferType > vec
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"