139 std::vector<std::string> tokens = o2::RangeTokenizer::tokenize<std::string>(specifier);
142 std::pair<int, float> synconto(-1, 1);
145 std::string
name = tokens[0];
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);
156 if (std::regex_match(mctoken.c_str(),
m, re)) {
157 collisionsasked = std::atoi(
m[1].
str().c_str());
159 randomizeorder =
true;
161 collisionsavail = std::atoi(
m[3].
str().c_str());
163 LOG(error) <<
"Could not parse " << mctoken <<
" as MCNUMBERSTRING";
168 if (adjustEventCount) {
172 if (collisionsavail > 0) {
173 collisionsavail = std::min((
size_t)collisionsavail, (
size_t)mcreader.
getNEvents(0));
178 LOG(info) <<
"Collisions avail for " <<
name <<
" " << collisionsavail;
181 auto& interactionToken = tokens[1];
182 if (interactionToken[0] ==
'@' || interactionToken[0] ==
'r') {
185 std::regex re(
"[@r]([0-9]*):([ed])([0-9]*[.]?[0-9]?)$", std::regex_constants::extended);
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());
193 if (crossindex > existingPatterns.size()) {
194 LOG(error) <<
"Reference to non-existent interaction spec";
197 synconto = std::pair<int, float>(crossindex, modevalue);
200 if (
mode.compare(
"e") == 0) {
203 if (
mode.compare(
"d") == 0) {
206 return InteractionSpec{
name,
rate, synconto, lockMode, interactionToken[0], collisionsasked, collisionsavail, randomizeorder};
208 LOG(error) <<
"Could not parse " << interactionToken <<
" as INTERACTIONSTRING";
211 }
catch (std::regex_error e) {
212 LOG(error) <<
"Exception during regular expression match " << e.what();
216 rate = std::atof(interactionToken.c_str());
243 bpo::options_description options(
244 "A utility to create and manipulate digitization contexts (MC collision structure within a timeframe).\n\n"
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")(
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)");
273 options.add_options()(
"help,h",
"Produce help message.");
275 bpo::variables_map vm;
277 bpo::store(bpo::command_line_parser(argc, argv).options(options).run(), vm);
281 if (vm.count(
"help")) {
282 std::cout << options << std::endl;
285 if (vm.count(
"show-context")) {
288 if (vm.count(
"use-existing-kine")) {
299 LOG(info) <<
"First BC " << optvalues.
firstBC;
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;
358int main(
int argc,
char* argv[])
379 LOG(error) <<
"External mode; orbits need to be given";
384 LOG(error) <<
"External mode; need to have orbitsPerTF";
389 LOG(error) <<
"External mode: This requires --extract-per-timeframe";
395 LOG(info) <<
"External mode for " << num_timeframes <<
" consecutive timeframes; starting from " << options.
tfid;
398 for (
int i = 0;
i < num_timeframes; ++
i) {
399 auto this_tf_id = options.
tfid +
i;
408 gRandom->SetSeed(options.
seed);
410 std::vector<InteractionSpec> ispecs;
417 std::vector<std::pair<o2::InteractionTimeRecord, std::vector<o2::steer::EventPart>>> collisions;
418 std::vector<o2::BunchFilling> bunchFillings;
421 bool usetimeframelength = options.
orbits > 0;
423 auto setBCFillingHelper = [&options](
auto&
sampler,
auto& bcPatternString) {
424 if (bcPatternString ==
"ccdb") {
425 LOG(info) <<
"Fetch bcPattern information from CCDB";
428 ccdb.setCaching(
false);
429 ccdb.setLocalObjectValidityChecking(
true);
432 sampler.setBunchFilling(grpLHC->getBunchFilling());
434 sampler.setBunchFilling(bcPatternString);
441 auto orbits_total = options.
orbits;
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;
455 if (mu_distr_source.size() > 0) {
456 if (mu_distr_source.find(
"ccdb") == 0) {
458 if (!ccdb_info_wrapper.has_value()) {
459 LOG(error) <<
"Could not parse CCDB path for mu(bc) distribution";
461 auto& ccdb_info = ccdb_info_wrapper.value();
473 mu_hist.reset((TH1F*)local_hist->Clone(
"h2"));
476 mu_hist = events_per_bc->toTH1F();
478 LOG(warn) <<
"No mu(bc) distribution found on CCDB. Using uniform one";
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"));
487 mudistr_file->Close();
491 LOG(info) <<
"Found an external mu distribution with mean BC value " << mu_hist->GetMean();
501 if (
const char* env = getenv(
"ALICEO2_ENFORCE_TRIVIAL_BC_SAMPLER")) {
502 std::string spec(env);
503 std::regex re(R
"((\d+):(\d+))");
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]);
510 LOG(error) <<
"ALICEO2_ENFORCE_TRIVIAL_BC_SAMPLER format invalid, expected NUMBER_1:NUMBER_2";
516 sampler->setInteractionRate(ispecs[
id].interactionRate);
523 sampler_cast->setBCIntensityScales(*mu_hist);
532 record =
sampler->generateCollisionTime();
533 }
while (options.
noEmptyTF && usetimeframelength && record.
orbit >= orbitstart + orbits_total);
536 if (usetimeframelength && record.
orbit >= orbitstart + orbits_total) {
539 std::vector<o2::steer::EventPart> parts;
540 parts.emplace_back(
id,
count);
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();
547 }
while ((ispecs[
id].mcnumberasked > 0 &&
count < ispecs[
id].mcnumberasked));
551 auto random_shuffle = [](
auto first,
auto last) {
553 for (
auto i =
n - 1;
i > 0; --
i) {
558 std::vector<int> eventindices(
count);
559 std::iota(eventindices.begin(), eventindices.end(), 0);
561 if (ispecs[
id].randomizeorder) {
562 random_shuffle(eventindices.begin(), eventindices.end());
564 if (ispecs[
id].mcnumberavail > 0) {
566 for (
auto& e : eventindices) {
567 e = e % ispecs[
id].mcnumberavail;
571 for (
auto&
col : collisions) {
572 for (
auto& part :
col.second) {
573 if (part.sourceID ==
id) {
574 part.entryID = eventindices[part.entryID];
580 bunchFillings.push_back(
sampler->getBunchFilling());
585 double lastcoltime = -1.;
586 auto distanceval = ispecs[
id].synconto.second;
587 auto lockonto = ispecs[
id].synconto.first;
590 for (
int colid = 0; colid < collisions.size(); ++colid) {
591 auto&
col = collisions[colid];
592 auto coltime =
col.first.getTimeNS();
594 bool rightinteraction =
false;
597 for (
auto& eventPart :
col.second) {
598 if (eventPart.sourceID == lockonto) {
599 rightinteraction =
true;
603 if (!rightinteraction) {
620 if (ispecs[
id].syncmodeop ==
'r') {
621 LOG(
debug) <<
"Replacing/overwriting another event ";
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);
630 LOG(error) <<
"Expected to replace another event part but did not find one for source " << lockonto <<
" and collision " << colid;
634 if (ispecs[
id].mcnumberavail >= 0) {
635 col.second.emplace_back(
id,
eventcount % ispecs[
id].mcnumberavail);
641 lastcoltime = coltime;
655 for (
auto& p : collisions) {
657 parts.push_back(p.second);
658 maxParts = std::max(p.second.size(), maxParts);
663 for (
int i = 1;
i < bunchFillings.size(); ++
i) {
664 bunchFillings[0].mergeWith(bunchFillings[
i]);
667 std::vector<std::string> prefixes;
669 float sgnIRate = -1.;
670 for (
auto& p : ispecs) {
671 prefixes.push_back(p.name);
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;
684 LOG(info) <<
"<<------ DENSE CONTEXT ---------";
688 LOG(info) <<
"-------- DENSE CONTEXT ------->>";
692 long const num_timeframes_asked = usetimeframelength ? (orbits_total / options.
orbitsPerTF) : -1;
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);
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);
709 LOG(info) <<
"<<------ FILTERED CONTEXT ---------";
713 LOG(info) <<
"-------- FILTERED CONTEXT ------->>";
715 auto numTimeFrames = timeframeindices.size();
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);
728 if (!empty_timeframes.empty()) {
729 std::stringstream tflist;
730 for (
auto tf : empty_timeframes) {
731 tflist <<
" tf" <<
tf;
736 for (
auto& p : ispecs) {
737 rate = std::max(
rate, (
double)p.interactionRate);
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();
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.) {
748 <<
" orbits per timeframe to keep that fraction below 1 per mille";
751 LOG(fatal) <<
"--failOnEmptyTF was requested and timeframes without collisions were produced; refusing to continue";
762 LOG(info) <<
"Applying vertexing using CCDB mean vertex " << *meanv;
765 LOG(fatal) <<
"No vertex available";
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;
779 LOG(error) <<
"Unknown vertex mode ... Not generating vertices";
788 std::cout <<
"### IRATE " << qedSpec.interactionRate <<
"\n";
801 LOG(info) <<
"Extracting individual timeframe collision contexts";
804 auto check_and_extract_tokens = [](
const std::string& input, std::vector<std::string>& tokens) {
806 const std::regex
pattern(R
"(^([a-zA-Z0-9]+)(:([a-zA-Z0-9]+(,[a-zA-Z0-9]+)*))?$)");
810 if (std::regex_match(input, matches,
pattern)) {
815 tokens.push_back(matches[1].
str());
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();
823 for (std::sregex_iterator
i = tokens_begin;
i != tokens_end; ++
i) {
824 tokens.push_back((*i).str());
828 LOG(error) <<
"Argument for --extract-per-timeframe does not match specification";
832 std::vector<std::string> tokens;
834 auto path_prefix = tokens[0];
835 std::vector<int> sources_to_offset{};
837 LOG(info) <<
"PREFIX is " << path_prefix;
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]));
844 auto first_timeframe = options.
orbitsEarly > 0. ? 1 : 0;
846 int tf_output_counter = 1;
847 for (
int tf_id = first_timeframe; tf_id < numTimeFrames; ++tf_id) {
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();
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 applyMaxCollisionFilter(std::vector< std::tuple< int, int, int > > &timeframeindices, long startOrbit, long orbitsPerTF, int maxColl, double orbitsEarly=0.)
std::vector< std::tuple< int, int, int > > calcTimeframeIndices(long startOrbit, long orbitsPerTF, double orbitsEarly=0., long nTimeframes=-1) const