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
int32_t i
container for the LHC InterFace data
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
GLenum mode
Definition glcorearb.h:266
GLint GLsizei count
Definition glcorearb.h:399
GLuint GLenum * rate
Definition glcorearb.h:5735
GLuint sampler
Definition glcorearb.h:1630
GLuint index
Definition glcorearb.h:781
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
constexpr int LHCMaxBunches
constexpr double LHCOrbitMUS
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:80
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:81
std::string outfilename
double firstFractionalOrbit
int orbitsPerTF
Definition GRPTool.cxx:65
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