Project
Loading...
Searching...
No Matches
CollisionContextTool.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
12#include <boost/program_options.hpp>
13#include <string>
14#include <iostream>
16#include <regex>
25#include <cmath>
26#include <TRandom.h>
27#include <numeric>
28#include <fairlogger/Logger.h>
33#include "SimConfig/SimConfig.h"
34#include <filesystem>
35#include <sstream>
36#include <vector>
37#include <numeric>
38
39//
40// Created by Sandro Wenzel on 13.07.21.
41//
42
43// A utility to create/engineer (later modify/display) collision contexts
44
45// options struct filled from command line
46struct Options {
47 std::vector<std::string> interactionRates;
48 std::string qedInteraction; // specification for QED contribution
49 std::string outfilename; //
50 int orbits; // number of orbits to generate (can be a multiple of orbitsPerTF --> determine fraction or multiple of timeframes)
51 long seed; //
52 bool printContext = false;
53 std::string bcpatternfile;
54 int tfid = 0; // tfid -> used to calculate start orbit for collisions
55 double orbitsEarly = 0.; // how many orbits from a prev timeframe should still be kept in the current timeframe
56 double firstFractionalOrbit; // capture orbit and bunch crossing via decimal number
57 uint32_t firstOrbit = 0; // first orbit in run (orbit offset)
58 uint32_t firstBC = 0; // first bunch crossing (relative to firstOrbit) of the first interaction;
59 int orbitsPerTF = 256; // number of orbits per timeframe --> used to calculate start orbit for collisions
61 bool noEmptyTF = false; // prevent empty timeframes; the first interaction will be shifted backwards to fall within the range given by Options.orbits
62 bool failOnEmptyTF = false; // stop rather than continue when a timeframe holds no collision
63 int maxCollsPerTF = -1; // the maximal number of hadronic collisions per TF (can be used to constrain number of collisions per timeframe to some maximal value)
64 std::string configKeyValues = ""; // string to init config key values
65 long timestamp = -1; // timestamp for CCDB queries
66 std::string individualTFextraction = ""; // triggers extraction of individuel timeframe components when non-null
67 // format is path prefix
68 std::string vertexModeString{"kNoVertex"}; // Vertex Mode; vertices will be assigned to collisions of mode != kNoVertex
70 std::string external_path = ""; // optional external path where we can directly take the collision contexts
71 // This is useful when someone else is creating the contexts (MC-data embedding) and we
72 // merely want to pass these through. If this is given, we simply take the timeframe ID, number of orbits
73 // and copy the right amount of timeframes into the destination folder (implies individualTFextraction)
74 std::string nontrivial_mu_distribution = ""; // path to fetch a non-uniform MC(BC) distribution for the interaction sampler
75 // can be: (a) ccdb, (b) a ROOT file with the histogram included
76};
77
79 NOLOCK,
80 EVERYN,
82};
83
84struct CcdbUrl {
85 std::string server; // may include http:// or https://
86 std::string port; // empty if none
87 std::string fullPath; // everything after server[:port]/
88};
89
90std::optional<CcdbUrl> parseCcdbRegex(const std::string& url)
91{
92 static const std::regex re(
93 R"(^(?:ccdb://)(https?://[^/:]+|[^/:]+)(?::(\d+))?/(.+)$)");
94 std::smatch m;
95 if (!std::regex_match(url, m, re)) {
96 return std::nullopt;
97 }
98
99 CcdbUrl out;
100 out.server = m[1].str(); // server (may include http:// or https://)
101 out.port = m[2].str(); // optional port
102 out.fullPath = m[3].str(); // remainder
103 return out;
104}
105
107 std::string name; // name (prefix for transport simulation); may also serve as unique identifier
109 std::pair<int, float> synconto; // if this interaction locks on another interaction; takes precedence over interactionRate
111 char syncmodeop = 0; // syncmode operation ("@" --> embedd; "r" --> replace)
112 int mcnumberasked = -1; // number of MC events asked (but can be left -1) in which case it will be determined from timeframelength
113 int mcnumberavail = -1; // number of MC events avail (but can be left -1); if avail < asked there will be reuse of events
114 bool randomizeorder = false; // whether order of events will be randomized
115};
116
117InteractionSpec parseInteractionSpec(std::string const& specifier, std::vector<InteractionSpec> const& existingPatterns, bool adjustEventCount)
118{
119 // An interaction specification is a command-separated string
120 // of the following form:
121 // SPEC=NAMESTRING,INTERACTIONSTRING[,MCNUMBERSTRING]
122 //
123 // where
124 //
125 // NAMESTRING : a simple named specifier for the interaction; matching to a simulation prefix used by o2-sim
126 //
127 // INTERACTIONSTRING: irate | @ID:[ed]FLOATVALUE
128 // - either: a simple number irate specifying the interaction rate in kHz
129 // - or: a string such as @0:e5, saying that this interaction should match/sync
130 // with collisions of the 0-th interaction, but inject only every 5 collisions.
131 // Alternatively @0:d10000 means to inject but leaving a timedistance of at least 10000ns between signals
132 // - or: a string r0:e5, saying that this interaction should sync with collisions of the 0-th interaction but
133 // **overwrite** every 5-th interaction with a collision from this interaction name
134 // MCNUMBERSTRING: NUMBER1:r?NUMBER2 can specify how many collisions NUMBER1 to produce, taking from a sample of NUMBER2 available collisions
135 // - this option is only supported on the first interaction which is supposed to be the background interaction
136 // - if the 'r' character is present we randomize the order of the MC events
137
138 // tokens are separated by comma
139 std::vector<std::string> tokens = o2::RangeTokenizer::tokenize<std::string>(specifier);
140
141 float rate = -1.;
142 std::pair<int, float> synconto(-1, 1);
143
144 // extract (kinematics prefix) name
145 std::string name = tokens[0];
146
147 // extract the MC number spec if given
148 int collisionsasked = -1;
149 int collisionsavail = -1;
150 bool randomizeorder = false;
151 if (tokens.size() > 2) {
152 auto mctoken = tokens[2];
153 std::regex re("([0-9]*):(r?)([0-9]*)$", std::regex_constants::extended);
154
155 std::cmatch m;
156 if (std::regex_match(mctoken.c_str(), m, re)) {
157 collisionsasked = std::atoi(m[1].str().c_str());
158 if (m[2].str().compare("r") == 0) {
159 randomizeorder = true;
160 }
161 collisionsavail = std::atoi(m[3].str().c_str());
162 } else {
163 LOG(error) << "Could not parse " << mctoken << " as MCNUMBERSTRING";
164 exit(1);
165 }
166 }
167
168 if (adjustEventCount) {
169 // if the number of collisionsavail has not been specified, we should
170 // try to extract it from the kinematics directly
172 if (collisionsavail > 0) {
173 collisionsavail = std::min((size_t)collisionsavail, (size_t)mcreader.getNEvents(0));
174 } else {
175 collisionsavail = mcreader.getNEvents(0);
176 }
177 }
178 LOG(info) << "Collisions avail for " << name << " " << collisionsavail;
179
180 // extract interaction rate ... or locking
181 auto& interactionToken = tokens[1];
182 if (interactionToken[0] == '@' || interactionToken[0] == 'r') {
183 try {
184 // locking onto some other interaction
185 std::regex re("[@r]([0-9]*):([ed])([0-9]*[.]?[0-9]?)$", std::regex_constants::extended);
186
187 std::cmatch m;
188 if (std::regex_match(interactionToken.c_str(), m, re)) {
189 auto crossindex = std::atoi(m[1].str().c_str());
190 auto mode = m[2].str();
191 auto modevalue = std::atof(m[3].str().c_str());
192
193 if (crossindex > existingPatterns.size()) {
194 LOG(error) << "Reference to non-existent interaction spec";
195 exit(1);
196 }
197 synconto = std::pair<int, float>(crossindex, modevalue);
198
199 InteractionLockMode lockMode;
200 if (mode.compare("e") == 0) {
202 }
203 if (mode.compare("d") == 0) {
205 }
206 return InteractionSpec{name, rate, synconto, lockMode, interactionToken[0], collisionsasked, collisionsavail, randomizeorder};
207 } else {
208 LOG(error) << "Could not parse " << interactionToken << " as INTERACTIONSTRING";
209 exit(1);
210 }
211 } catch (std::regex_error e) {
212 LOG(error) << "Exception during regular expression match " << e.what();
213 exit(1);
214 }
215 } else {
216 rate = std::atof(interactionToken.c_str());
217 return InteractionSpec{name, rate, synconto, InteractionLockMode::NOLOCK, 0, collisionsasked, collisionsavail, randomizeorder};
218 }
219}
220
221// The number of QED events which the collision context may cycle through.
222// The QED events are reused in a round robin over the sampled QED interactions, so the wrap has to
223// happen at the number of events that exist in the QED kinematics. Wrapping later hands out event
224// IDs which were never simulated: the hits are read modulo the file size while the MC labels keep
225// the unwrapped ID. See https://its.cern.ch/jira/browse/O2-7132
227{
228 if (qedSpec.mcnumberavail <= 0) {
229 LOG(warn) << "No number of available QED events given (the MCNUMBERSTRING of --QEDinteraction); "
230 << "QED event IDs may name events which are not in the QED kinematics";
231 return qedSpec.mcnumberasked;
232 }
233 if (qedSpec.mcnumberavail > (int)o2::MCEventLabel::MaxEventID()) {
234 LOG(warn) << "The QED production has " << qedSpec.mcnumberavail << " events, more than the "
235 << o2::MCEventLabel::MaxEventID() << " an MCEventLabel can encode; QED event IDs will be truncated";
236 }
237 return qedSpec.mcnumberavail;
238}
239
240bool parseOptions(int argc, char* argv[], Options& optvalues)
241{
242 namespace bpo = boost::program_options;
243 bpo::options_description options(
244 "A utility to create and manipulate digitization contexts (MC collision structure within a timeframe).\n\n"
245 "Allowed options");
246
247 options.add_options()(
248 "interactions,i", bpo::value<std::vector<std::string>>(&optvalues.interactionRates)->multitoken(), "name,IRate|LockSpecifier")(
249 "QEDinteraction", bpo::value<std::string>(&optvalues.qedInteraction)->default_value(""), "Interaction specifier for QED contribution (name,IRATE,maxeventnumber)")(
250 "outfile,o", bpo::value<std::string>(&optvalues.outfilename)->default_value("collisioncontext.root"), "Outfile of collision context")(
251 "orbits", bpo::value<int>(&optvalues.orbits)->default_value(-1),
252 "Number of orbits to generate maximally (if given, can be used to determine the number of timeframes). "
253 "Otherwise, the context will be generated by using collision numbers from the interaction specification.")(
254 "seed", bpo::value<long>(&optvalues.seed)->default_value(0L), "Seed for random number generator (for time sampling etc). Default 0: Random")(
255 "show-context", "Print generated collision context to terminal.")(
256 "bcPatternFile", bpo::value<std::string>(&optvalues.bcpatternfile)->default_value(""), "Interacting BC pattern file (e.g. from CreateBCPattern.C); Use \"ccdb\" when fetching from CCDB.")(
257 "orbitsPerTF", bpo::value<int>(&optvalues.orbitsPerTF)->default_value(256), "Orbits per timeframes")(
258 "orbitsEarly", bpo::value<double>(&optvalues.orbitsEarly)->default_value(0.), "Number of orbits with extra collisions prefixed to each timeframe")(
259 "use-existing-kine", "Read existing kinematics to adjust event counts")(
260 "timeframeID", bpo::value<int>(&optvalues.tfid)->default_value(0), "Timeframe id of the first timeframe int this context. Allows to generate contexts for different start orbits")(
261 "first-orbit", bpo::value<double>(&optvalues.firstFractionalOrbit)->default_value(0), "First (fractional) orbit in the run (HBFUtils.firstOrbit + BC from decimal)")(
262 "maxCollsPerTF", bpo::value<int>(&optvalues.maxCollsPerTF)->default_value(-1), "Maximal number of MC collisions to put into one timeframe. By default no constraint.")(
263 "noEmptyTF", bpo::bool_switch(&optvalues.noEmptyTF), "Shift the first collision backwards so that it falls within the sampled orbit range")(
264 "failOnEmptyTF", bpo::bool_switch(&optvalues.failOnEmptyTF), "Stop instead of continuing when one of the timeframes asked for ends up without a collision")(
265 "configKeyValues", bpo::value<std::string>(&optvalues.configKeyValues)->default_value(""), "Semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...')")(
266 "with-vertices", bpo::value<std::string>(&optvalues.vertexModeString)->default_value("kNoVertex"), "Assign vertices to collisions. Argument is the vertex mode. Defaults to no vertexing applied")(
267 "timestamp", bpo::value<long>(&optvalues.timestamp)->default_value(-1L), "Timestamp for CCDB queries / anchoring")(
268 "extract-per-timeframe", bpo::value<std::string>(&optvalues.individualTFextraction)->default_value(""),
269 "Extract individual timeframe contexts. Format required: time_frame_prefix[:comma_separated_list_of_signals_to_offset]")(
270 "import-external", bpo::value<std::string>(&optvalues.external_path)->default_value(""), "Take collision contexts (per timeframe) from external files for instance for data-anchoring use-case. Needs timeframeID and number of orbits to be given as well.")(
271 "nontrivial-mu-distribution", bpo::value<std::string>(&optvalues.nontrivial_mu_distribution)->default_value(""), "Distribution for MU(BC)");
272
273 options.add_options()("help,h", "Produce help message.");
274
275 bpo::variables_map vm;
276 try {
277 bpo::store(bpo::command_line_parser(argc, argv).options(options).run(), vm);
278 bpo::notify(vm);
279
280 // help
281 if (vm.count("help")) {
282 std::cout << options << std::endl;
283 return false;
284 }
285 if (vm.count("show-context")) {
286 optvalues.printContext = true;
287 }
288 if (vm.count("use-existing-kine")) {
289 optvalues.useexistingkinematics = true;
290 }
291
293
294 // fix the first orbit and bunch crossing
295 // auto orbitbcpair = parseOrbitAndBC(optvalues.firstIRString);
296 optvalues.firstOrbit = (uint32_t)optvalues.firstFractionalOrbit;
297 optvalues.firstBC = (uint32_t)((optvalues.firstFractionalOrbit - 1. * optvalues.firstOrbit) * o2::constants::lhc::LHCMaxBunches);
298 LOG(info) << "First orbit " << optvalues.firstOrbit;
299 LOG(info) << "First BC " << optvalues.firstBC;
300
301 } catch (const bpo::error& e) {
302 std::cerr << e.what() << "\n\n";
303 std::cerr << "Error parsing options; Available options:\n";
304 std::cerr << options << std::endl;
305 return false;
306 }
307 return true;
308}
309
310bool copy_collision_context(const std::string& external_path, int this_tf_id, int target_tf_id)
311{
312 namespace fs = std::filesystem;
313 try {
314 fs::path filename;
315 if (fs::exists(external_path) && fs::is_regular_file(external_path)) {
316 std::cout << "external_path is an existing file: " << external_path << "\n";
317 // use it directly
318 filename = fs::path(external_path);
319 } else {
320 // Construct source file path
321 filename = fs::path(external_path) / ("collission_context_" + std::to_string(this_tf_id) + ".root");
322 }
323
324 LOG(info) << "Checking existence of file: " << filename;
325
326 if (fs::exists(filename)) {
327 // Build destination path
328 std::string path_prefix = "tf"; // Can be made configurable
329 std::stringstream destination_path_stream;
330 destination_path_stream << path_prefix << (target_tf_id) << "/collisioncontext.root";
331 fs::path destination_path = destination_path_stream.str();
332
333 // Ensure parent directory exists
334 fs::path destination_dir = destination_path.parent_path();
335 if (!fs::exists(destination_dir)) {
336 fs::create_directories(destination_dir);
337 LOG(info) << "Created directory: " << destination_dir;
338 }
339
340 // Copy file
341 fs::copy_file(filename, destination_path, fs::copy_options::overwrite_existing);
342 LOG(info) << "Copied file to: " << destination_path;
343 return true;
344 } else {
345 LOG(warning) << "Source file does not exist: " << filename;
346 return false;
347 }
348 } catch (const fs::filesystem_error& e) {
349 LOG(error) << "Filesystem error: " << e.what();
350 return false;
351 } catch (const std::exception& e) {
352 LOG(error) << "Unexpected error: " << e.what();
353 return false;
354 }
355 return true;
356}
357
358int main(int argc, char* argv[])
359{
360 Options options;
361 if (!parseOptions(argc, argv, options)) {
362 exit(1);
363 }
364
365 // init params
367
368 // See if this is external mode, which simplifies things
369 if (options.external_path.size() > 0) {
370 // in this mode, we don't actually have to do much work.
371 // all we do is to
372 // - determine how many timeframes are asked
373 // - check if the right files are present in the external path (someone else needs to create/put them there)
374 // - check if the given contexts are consistent with options given (orbitsPerTF, ...)
375 // - copy the files into the MC destination folder (this implies timeframeextraction mode)
376 // - return
377
378 if (options.orbits < 0) {
379 LOG(error) << "External mode; orbits need to be given";
380 return 1;
381 }
382
383 if (options.orbitsPerTF == 0) {
384 LOG(error) << "External mode; need to have orbitsPerTF";
385 return 1;
386 }
387
388 if (options.individualTFextraction.size() == 0) {
389 LOG(error) << "External mode: This requires --extract-per-timeframe";
390 return 1;
391 }
392
393 // calculate number of timeframes
394 auto num_timeframes = options.orbits / options.orbitsPerTF;
395 LOG(info) << "External mode for " << num_timeframes << " consecutive timeframes; starting from " << options.tfid;
396
397 // loop over all timeframe ids - check if file is present - (check consistency) - copy to final destination
398 for (int i = 0; i < num_timeframes; ++i) {
399 auto this_tf_id = options.tfid + i;
400 if (!copy_collision_context(options.external_path, this_tf_id, i + 1)) {
401 return 1;
402 }
403 }
404 return 0;
405 }
406
407 // init random generator
408 gRandom->SetSeed(options.seed);
409
410 std::vector<InteractionSpec> ispecs;
411 // building the interaction spec
412 for (auto& i : options.interactionRates) {
413 // this is created as output from
414 ispecs.push_back(parseInteractionSpec(i, ispecs, options.useexistingkinematics));
415 }
416
417 std::vector<std::pair<o2::InteractionTimeRecord, std::vector<o2::steer::EventPart>>> collisions;
418 std::vector<o2::BunchFilling> bunchFillings; // vector of bunch filling objects; generated by interaction samplers
419
420 // now we generate the collision structure (interaction type by interaction type)
421 bool usetimeframelength = options.orbits > 0;
422
423 auto setBCFillingHelper = [&options](auto& sampler, auto& bcPatternString) {
424 if (bcPatternString == "ccdb") {
425 LOG(info) << "Fetch bcPattern information from CCDB";
426 // fetch the GRP Object
428 ccdb.setCaching(false);
429 ccdb.setLocalObjectValidityChecking(true);
430 auto grpLHC = ccdb.getForTimeStamp<o2::parameters::GRPLHCIFData>("GLO/Config/GRPLHCIF", options.timestamp);
431 LOG(info) << "Fetched injection scheme " << grpLHC->getInjectionScheme() << " from CCDB";
432 sampler.setBunchFilling(grpLHC->getBunchFilling());
433 } else {
434 sampler.setBunchFilling(bcPatternString);
435 }
436 };
437
438 // this is the starting orbit from which on we construct interactions (it is possibly shifted by one tf to the left
439 // in order to generate eventual "earlyOrbits"
440 auto orbitstart = options.firstOrbit + options.tfid * options.orbitsPerTF;
441 auto orbits_total = options.orbits;
442 if (options.orbitsEarly > 0.) {
443 orbitstart -= options.orbitsPerTF;
444 orbits_total += options.orbitsPerTF;
445 }
446
447 for (int id = 0; id < ispecs.size(); ++id) {
448 auto mode = ispecs[id].syncmode;
450 auto sampler = std::make_unique<o2::steer::InteractionSampler>();
451 std::unique_ptr<TH1F> mu_hist;
452
453 // we check if there is a realistic bunch crossing distribution available
454 const auto& mu_distr_source = options.nontrivial_mu_distribution;
455 if (mu_distr_source.size() > 0) {
456 if (mu_distr_source.find("ccdb") == 0) {
457 auto ccdb_info_wrapper = parseCcdbRegex(mu_distr_source);
458 if (!ccdb_info_wrapper.has_value()) {
459 LOG(error) << "Could not parse CCDB path for mu(bc) distribution";
460 } else {
461 auto& ccdb_info = ccdb_info_wrapper.value();
462
463 // for now construct a specific CCDBManager for this query
464 o2::ccdb::CCDBManagerInstance ccdb_inst(ccdb_info.server + std::string(":") + ccdb_info.port);
465 ccdb_inst.setFatalWhenNull(false);
466 // this is a private instance, so it does not inherit the time-machine
467 // constraint that BasicCCDBManager picks up from the environment;
468 // carry it over explicitly (a 0 here means "unconstrained" anyway)
469 ccdb_inst.setCreatedNotAfter(o2::ccdb::BasicCCDBManager::instance().getCreatedNotAfter());
470 auto local_hist = ccdb_inst.getForTimeStamp<TH1F>(ccdb_info.fullPath, options.timestamp);
471 if (local_hist) {
472 // case in which CCDB object contains directly a ROOT histogram
473 mu_hist.reset((TH1F*)local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile
474 } else if (auto events_per_bc = ccdb_inst.getForTimeStamp<o2::ft0::EventsPerBc>(ccdb_info.fullPath, options.timestamp)) {
475 // case in which CCDB object is from FT0 EventsPerBC calib (will be default)
476 mu_hist = events_per_bc->toTH1F();
477 } else {
478 LOG(warn) << "No mu(bc) distribution found on CCDB. Using uniform one";
479 }
480 }
481 } else {
482 // we interpret the file as a ROOT file and open it to extract the wanted histogram
483 auto mudistr_file = TFile::Open(mu_distr_source.c_str(), "OPEN");
484 if (mudistr_file && !mudistr_file->IsZombie()) {
485 auto local_hist = mudistr_file->Get<TH1F>("hBcTVX");
486 mu_hist.reset((TH1F*)local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile
487 mudistr_file->Close();
488 }
489 }
490 if (mu_hist) {
491 LOG(info) << "Found an external mu distribution with mean BC value " << mu_hist->GetMean();
492
493 // do some checks
494
495 // reset to correct interaction Sampler type
497 }
498 }
499
500 // for debug purposes: allows to instantiate trivial sampler
501 if (const char* env = getenv("ALICEO2_ENFORCE_TRIVIAL_BC_SAMPLER")) {
502 std::string spec(env);
503 std::regex re(R"((\d+):(\d+))");
504 std::smatch match;
505 int every_n = 1, mult = 1;
506 if (std::regex_match(spec, match, re)) {
507 every_n = std::stoi(match[1]);
508 mult = std::stoi(match[2]);
509 } else {
510 LOG(error) << "ALICEO2_ENFORCE_TRIVIAL_BC_SAMPLER format invalid, expected NUMBER_1:NUMBER_2";
511 exit(1);
512 }
513 sampler.reset(new o2::steer::FixedSkipBC_InteractionSampler(every_n, mult));
514 }
515
516 sampler->setInteractionRate(ispecs[id].interactionRate);
517 if (!options.bcpatternfile.empty()) {
518 setBCFillingHelper(*sampler, options.bcpatternfile);
519 }
520 sampler->init();
521 if (auto sampler_cast = dynamic_cast<o2::steer::NonUniformMuInteractionSampler*>(sampler.get())) {
522 if (mu_hist) {
523 sampler_cast->setBCIntensityScales(*mu_hist);
524 }
525 }
526
528 // this loop makes sure that the first collision is within the range of orbits asked (if noEmptyTF is enabled)
529 do {
530 sampler->setFirstIR(o2::InteractionRecord(options.firstBC, orbitstart));
531 sampler->init();
532 record = sampler->generateCollisionTime();
533 } while (options.noEmptyTF && usetimeframelength && record.orbit >= orbitstart + orbits_total);
534 int count = 0;
535 do {
536 if (usetimeframelength && record.orbit >= orbitstart + orbits_total) {
537 break;
538 }
539 std::vector<o2::steer::EventPart> parts;
540 parts.emplace_back(id, count);
541
542 std::pair<o2::InteractionTimeRecord, std::vector<o2::steer::EventPart>> insertvalue(record, parts);
543 auto iter = std::lower_bound(collisions.begin(), collisions.end(), insertvalue, [](std::pair<o2::InteractionTimeRecord, std::vector<o2::steer::EventPart>> const& a, std::pair<o2::InteractionTimeRecord, std::vector<o2::steer::EventPart>> const& b) { return a.first < b.first; });
544 collisions.insert(iter, insertvalue);
545 record = sampler->generateCollisionTime();
546 count++;
547 } while ((ispecs[id].mcnumberasked > 0 && count < ispecs[id].mcnumberasked)); // TODO: this loop should probably be replaced by a condition with usetimeframelength and number of orbits
548
549 // we support randomization etc on non-injected/embedded interactions
550 // and we can apply them here
551 auto random_shuffle = [](auto first, auto last) {
552 auto n = last - first;
553 for (auto i = n - 1; i > 0; --i) {
554 using std::swap;
555 swap(first[i], first[(int)(gRandom->Rndm() * n)]);
556 }
557 };
558 std::vector<int> eventindices(count);
559 std::iota(eventindices.begin(), eventindices.end(), 0);
560 // apply randomization of order if any
561 if (ispecs[id].randomizeorder) {
562 random_shuffle(eventindices.begin(), eventindices.end());
563 }
564 if (ispecs[id].mcnumberavail > 0) {
565 // apply cutting to number of available entries
566 for (auto& e : eventindices) {
567 e = e % ispecs[id].mcnumberavail;
568 }
569 }
570 // make these transformations final:
571 for (auto& col : collisions) {
572 for (auto& part : col.second) {
573 if (part.sourceID == id) {
574 part.entryID = eventindices[part.entryID];
575 }
576 }
577 }
578
579 // keep bunch filling information produced by these samplers
580 bunchFillings.push_back(sampler->getBunchFilling());
581
582 } else {
583 // we are in some lock/sync mode and modify existing collisions
584 int lastcol = -1;
585 double lastcoltime = -1.;
586 auto distanceval = ispecs[id].synconto.second;
587 auto lockonto = ispecs[id].synconto.first;
588 int eventcount = 0;
589
590 for (int colid = 0; colid < collisions.size(); ++colid) {
591 auto& col = collisions[colid];
592 auto coltime = col.first.getTimeNS();
593
594 bool rightinteraction = false;
595 // we are locking only on collisions which have the referenced interaction present
596 // --> there must be an EventPart with the right sourceID
597 for (auto& eventPart : col.second) {
598 if (eventPart.sourceID == lockonto) {
599 rightinteraction = true;
600 break;
601 }
602 }
603 if (!rightinteraction) {
604 continue;
605 }
606
607 bool inject = false;
608 // we always start with first one
609 if (lastcol == -1) {
610 inject = true;
611 }
612 if (mode == InteractionLockMode::EVERYN && (colid - lastcol) >= distanceval) {
613 inject = true;
614 }
615 if (mode == InteractionLockMode::MINTIMEDISTANCE && (coltime - lastcoltime) >= distanceval) {
616 inject = true;
617 }
618
619 if (inject) {
620 if (ispecs[id].syncmodeop == 'r') {
621 LOG(debug) << "Replacing/overwriting another event ";
622 // Syncing is replacing; which means we need to take out the original
623 // event that we locked onto.
624 // We take out this event part immediately (and complain if there is a problem).
625 int index = 0;
626 auto iter = std::find_if(col.second.begin(), col.second.end(), [lockonto](auto val) { return lockonto == val.sourceID; });
627 if (iter != col.second.end()) {
628 col.second.erase(iter);
629 } else {
630 LOG(error) << "Expected to replace another event part but did not find one for source " << lockonto << " and collision " << colid;
631 }
632 }
633
634 if (ispecs[id].mcnumberavail >= 0) {
635 col.second.emplace_back(id, eventcount % ispecs[id].mcnumberavail);
636 } else {
637 col.second.emplace_back(id, eventcount);
638 }
639 eventcount++;
640 lastcol = colid;
641 lastcoltime = coltime;
642 }
643 }
644 }
645 }
646
647 // create DigitizationContext
649 // we can fill this container
650 auto& parts = digicontext.getEventParts();
651 // we can fill this container
652 auto& records = digicontext.getEventRecords();
653 // copy over information
654 size_t maxParts = 0;
655 for (auto& p : collisions) {
656 records.push_back(p.first);
657 parts.push_back(p.second);
658 maxParts = std::max(p.second.size(), maxParts);
659 }
660 digicontext.setNCollisions(collisions.size());
661 digicontext.setMaxNumberParts(maxParts);
662 // merge bunch filling info
663 for (int i = 1; i < bunchFillings.size(); ++i) {
664 bunchFillings[0].mergeWith(bunchFillings[i]);
665 }
666 digicontext.setBunchFilling(bunchFillings[0]);
667 std::vector<std::string> prefixes;
668 // Signal interaction rate
669 float sgnIRate = -1.;
670 for (auto& p : ispecs) {
671 prefixes.push_back(p.name);
672 // Set the interaction rate from the first pattern with a valid value.
673 // This handles both simple signal-only productions (where "sgn" has the rate)
674 // and embedding productions (where "bkg" has the rate and "sgn" syncs to it)
675 if (sgnIRate < 0 && p.interactionRate > 0) {
676 LOG(debug) << "Setting signal interaction rate to " << p.interactionRate << " Hz in the digitization context.";
677 sgnIRate = p.interactionRate;
678 digicontext.setDigitizerInteractionRate(p.interactionRate);
679 }
680 }
681 digicontext.setSimPrefixes(prefixes);
682
683 // <---- at this moment we have a dense collision context (not representing the final output we want)
684 LOG(info) << "<<------ DENSE CONTEXT ---------";
685 if (options.printContext) {
686 digicontext.printCollisionSummary();
687 }
688 LOG(info) << "-------- DENSE CONTEXT ------->>";
689
690 // the number of timeframes we were asked for; passing it makes sure that a timeframe without
691 // collisions keeps its own slot instead of shifting every later timeframe down by one
692 long const num_timeframes_asked = usetimeframelength ? (orbits_total / options.orbitsPerTF) : -1;
693 auto timeframeindices = digicontext.calcTimeframeIndices(orbitstart, options.orbitsPerTF, options.orbitsEarly, num_timeframes_asked);
694 LOG(info) << "Fixed " << timeframeindices.size() << " timeframes ";
695 for (auto p : timeframeindices) {
696 LOG(info) << std::get<0>(p) << " " << std::get<1>(p) << " " << std::get<2>(p);
697 }
698
699 // apply max collision per timeframe filters + reindexing of event id (linearisation and compactification)
700 digicontext.applyMaxCollisionFilter(timeframeindices, orbitstart, options.orbitsPerTF, options.maxCollsPerTF, options.orbitsEarly);
701
702 LOG(info) << "Timeframe indices after collision filter";
703 LOG(info) << "Fixed " << timeframeindices.size() << " timeframes ";
704 for (auto p : timeframeindices) {
705 LOG(info) << std::get<0>(p) << " " << std::get<1>(p) << " " << std::get<2>(p);
706 }
707
708 // <---- at this moment we have a dense collision context (not representing the final output we want)
709 LOG(info) << "<<------ FILTERED CONTEXT ---------";
710 if (options.printContext) {
711 digicontext.printCollisionSummary();
712 }
713 LOG(info) << "-------- FILTERED CONTEXT ------->>";
714
715 auto numTimeFrames = timeframeindices.size(); // digicontext.finalizeTimeframeStructure(orbitstart, options.orbitsPerTF, options.orbitsEarly);
716
717 // report - and, if asked, refuse - timeframes without a single collision. A timeframe with no
718 // collision cannot be simulated, and the rest of the MC workflow expects one collision context
719 // file per timeframe, so this has to be visible here and not five hours later in the simulation.
720 {
721 std::vector<int> empty_timeframes;
722 auto const first_real_tf = options.orbitsEarly > 0. ? 1 : 0;
723 for (int tf_id = first_real_tf; tf_id < (int)numTimeFrames; ++tf_id) {
724 if (std::get<0>(timeframeindices[tf_id]) > std::get<1>(timeframeindices[tf_id])) {
725 empty_timeframes.push_back(tf_id - first_real_tf + 1);
726 }
727 }
728 if (!empty_timeframes.empty()) {
729 std::stringstream tflist;
730 for (auto tf : empty_timeframes) {
731 tflist << " tf" << tf;
732 }
733 // the mean number of collisions in one timeframe, from the rate we were given
734 auto const tf_length_s = options.orbitsPerTF * o2::constants::lhc::LHCOrbitMUS * 1e-6;
735 double rate = 0.;
736 for (auto& p : ispecs) {
737 rate = std::max(rate, (double)p.interactionRate);
738 }
739 auto const mu_per_tf = rate * tf_length_s;
740 LOG(warn) << empty_timeframes.size() << " of " << (numTimeFrames - first_real_tf)
741 << " timeframes contain no collision:" << tflist.str();
742 LOG(warn) << "with interaction rate " << rate << " Hz and " << options.orbitsPerTF
743 << " orbits per timeframe there are only " << mu_per_tf
744 << " collisions per timeframe on average, so a fraction " << std::exp(-mu_per_tf)
745 << " of the timeframes comes out empty";
746 if (mu_per_tf > 0.) {
747 LOG(warn) << "use at least " << (int)std::ceil(8. / (rate * o2::constants::lhc::LHCOrbitMUS * 1e-6))
748 << " orbits per timeframe to keep that fraction below 1 per mille";
749 }
750 if (options.failOnEmptyTF) {
751 LOG(fatal) << "--failOnEmptyTF was requested and timeframes without collisions were produced; refusing to continue";
752 }
753 }
754 }
755
757 switch (options.vertexMode) {
759 // fetch mean vertex from CCDB
761 if (meanv) {
762 LOG(info) << "Applying vertexing using CCDB mean vertex " << *meanv;
763 digicontext.sampleInteractionVertices(*meanv);
764 } else {
765 LOG(fatal) << "No vertex available";
766 }
767 break;
768 }
769
771 // init this vertex from CCDB or InteractionDiamond parameter
773 o2::dataformats::MeanVertexObject meanv(dparam.position[0], dparam.position[1], dparam.position[2], dparam.width[0], dparam.width[1], dparam.width[2], dparam.slopeX, dparam.slopeY);
774 LOG(info) << "Applying vertexing using DiamondParam mean vertex " << meanv;
775 digicontext.sampleInteractionVertices(meanv);
776 break;
777 }
778 default: {
779 LOG(error) << "Unknown vertex mode ... Not generating vertices";
780 }
781 }
782 }
783
784 // we fill QED contributions to the context
785 if (options.qedInteraction.size() > 0) {
786 // TODO: use bcFilling information
787 auto qedSpec = parseInteractionSpec(options.qedInteraction, ispecs, options.useexistingkinematics);
788 std::cout << "### IRATE " << qedSpec.interactionRate << "\n";
789 digicontext.fillQED(qedSpec.name, getQEDRoundRobinSize(qedSpec), qedSpec.interactionRate);
790 }
791
792 if (options.printContext) {
793 digicontext.printCollisionSummary();
794 }
795 digicontext.saveToFile(options.outfilename);
796
797 // extract individual timeframes
798 if (options.individualTFextraction.size() > 0) {
799 // we are asked to extract individual timeframe components
800
801 LOG(info) << "Extracting individual timeframe collision contexts";
802 // extract prefix path to store these collision contexts
803 // Function to check the pattern and extract tokens from b
804 auto check_and_extract_tokens = [](const std::string& input, std::vector<std::string>& tokens) {
805 // the regular expression pattern for expected input format
806 const std::regex pattern(R"(^([a-zA-Z0-9]+)(:([a-zA-Z0-9]+(,[a-zA-Z0-9]+)*))?$)");
807 std::smatch matches;
808
809 // Check if the input matches the pattern
810 if (std::regex_match(input, matches, pattern)) {
811 // Clear any existing tokens in the vector
812 tokens.clear();
813
814 // matches[1] contains the part before the colon which we save first
815 tokens.push_back(matches[1].str());
816 // matches[2] contains the comma-separated list
817 std::string b = matches[2].str();
818 std::regex token_pattern(R"([a-zA-Z0-9]+)");
819 auto tokens_begin = std::sregex_iterator(b.begin(), b.end(), token_pattern);
820 auto tokens_end = std::sregex_iterator();
821
822 // Iterate over the tokens and add them to the vector
823 for (std::sregex_iterator i = tokens_begin; i != tokens_end; ++i) {
824 tokens.push_back((*i).str());
825 }
826 return true;
827 }
828 LOG(error) << "Argument for --extract-per-timeframe does not match specification";
829 return false;
830 };
831
832 std::vector<std::string> tokens;
833 if (check_and_extract_tokens(options.individualTFextraction, tokens)) {
834 auto path_prefix = tokens[0];
835 std::vector<int> sources_to_offset{};
836
837 LOG(info) << "PREFIX is " << path_prefix;
838
839 for (int i = 1; i < tokens.size(); ++i) {
840 LOG(info) << "Offsetting " << tokens[i];
841 sources_to_offset.push_back(digicontext.findSimPrefix(tokens[i]));
842 }
843
844 auto first_timeframe = options.orbitsEarly > 0. ? 1 : 0;
845 // now we are ready to loop over all timeframes
846 int tf_output_counter = 1;
847 for (int tf_id = first_timeframe; tf_id < numTimeFrames; ++tf_id) {
848 auto copy = digicontext.extractSingleTimeframe(tf_id, timeframeindices, sources_to_offset);
849
850 // each individual case gets QED interactions injected
851 // This should probably be done inside the extraction itself
852 if (digicontext.isQEDProvided()) {
853 auto qedSpec = parseInteractionSpec(options.qedInteraction, ispecs, options.useexistingkinematics);
854 copy.fillQED(qedSpec.name, getQEDRoundRobinSize(qedSpec), qedSpec.interactionRate);
855 }
856
857 std::stringstream str;
858 str << path_prefix << tf_output_counter++ << "/collisioncontext.root";
859 copy.saveToFile(str.str());
860 LOG(info) << "---- CollisionContext for timeframe " << tf_id << " -----";
861 copy.printCollisionSummary();
862 }
863 }
864 }
865
866 return 0;
867}
std::vector< o2::soa::IndexRecord > records
std::string url
bool copy_collision_context(const std::string &external_path, int this_tf_id, int target_tf_id)
bool parseOptions(int argc, char *argv[], Options &optvalues)
std::optional< CcdbUrl > parseCcdbRegex(const std::string &url)
int getQEDRoundRobinSize(InteractionSpec const &qedSpec)
InteractionSpec parseInteractionSpec(std::string const &specifier, std::vector< InteractionSpec > const &existingPatterns, bool adjustEventCount)
std::ostringstream debug
unsigned int uint32_t
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
container for the LHC InterFace data
char error[CURL_ERROR_SIZE]
Header to collect LHC related constants.
Helper function to tokenize sequences and ranges of integral numbers.
uint32_t eventcount
Definition RawData.h:1
uint32_t col
Definition RawData.h:4
static constexpr uint32_t MaxEventID()
static BasicCCDBManager & instance()
void setCreatedNotAfter(long v)
set the object upper validity limit
T * getForTimeStamp(std::string const &path, long timestamp, std::map< std::string, std::string > *headers=nullptr)
retrieve an object of type T from CCDB as stored under path and timestamp. Optional to get the header...
void setFatalWhenNull(bool b)
set the fatal property (when false; nullptr object responses will not abort)
static void updateFromString(std::string const &)
static bool parseVertexModeString(std::string const &vertexstring, o2::conf::VertexMode &mode)
const std::string & getInjectionScheme() const
DigitizationContext extractSingleTimeframe(int timeframeid, std::vector< std::tuple< int, int, int > > const &timeframeindices, std::vector< int > const &sources_to_offset)
void fillQED(std::string_view QEDprefix, int max_events, double qedrate)
add QED contributions to context, giving prefix; maximal event number and qed interaction rate
void printCollisionSummary(bool withQED=false, int truncateOutputTo=-1) const
int findSimPrefix(std::string const &prefix) const
void applyMaxCollisionFilter(std::vector< std::tuple< int, int, int > > &timeframeindices, long startOrbit, long orbitsPerTF, int maxColl, double orbitsEarly=0.)
void setSimPrefixes(std::vector< std::string > const &p)
std::vector< std::tuple< int, int, int > > calcTimeframeIndices(long startOrbit, long orbitsPerTF, double orbitsEarly=0., long nTimeframes=-1) const
std::vector< o2::InteractionTimeRecord > & getEventRecords(bool withQED=false)
void sampleInteractionVertices(o2::dataformats::MeanVertexObject const &v)
std::vector< std::vector< o2::steer::EventPart > > & getEventParts(bool withQED=false)
void saveToFile(std::string_view filename) const
void setBunchFilling(o2::BunchFilling const &bf)
void setDigitizerInteractionRate(float intRate)
size_t getNEvents(int source) const
Get number of events.
bool match(const std::vector< std::string > &queries, const char *pattern)
Definition dcs-ccdb.cxx:229
GLdouble n
Definition glcorearb.h:1982
const GLfloat * m
Definition glcorearb.h:4066
GLint GLsizei count
Definition glcorearb.h:399
GLuint GLenum * rate
Definition glcorearb.h:5735
GLuint sampler
Definition glcorearb.h:1630
GLuint const GLchar * name
Definition glcorearb.h:781
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLuint GLfloat * val
Definition glcorearb.h:1582
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint id
Definition glcorearb.h:650
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::string filename()
std::unique_ptr< GPUReconstructionTimeframe > tf
std::string port
std::string server
std::string fullPath
InteractionLockMode syncmode
std::pair< int, float > synconto
std::string nontrivial_mu_distribution
std::string vertexModeString
std::string configKeyValues
Definition GRPTool.cxx:67
o2::conf::VertexMode vertexMode
std::string individualTFextraction
std::string external_path
std::string bcpatternfile
std::vector< std::string > interactionRates
uint64_t timestamp
Definition GRPTool.cxx:68
std::string outfilename
double firstFractionalOrbit
int orbitsPerTF
Definition GRPTool.cxx:52
std::string qedInteraction
uint32_t orbit
LHC orbit.
void compare(std::string_view s1, std::string_view s2)
#define main
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::array< uint16_t, 5 > pattern
const std::string str