Skip to content

Commit 71711a5

Browse files
committed
Improvements for CollisionContextTool
* important step to create collision contexts for all timeframes in one step - complete digi context is created - individual tf-collisioncontexts can be extracted * needed to have "pregencollcontext" in O2DPG work with embedding * smaller fixes (firstOrbit)
1 parent 2c1efe4 commit 71711a5

File tree

3 files changed

+187
-11
lines changed

3 files changed

+187
-11
lines changed

DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ class DigitizationContext
113113
/// Check collision parts for vertex consistency.
114114
bool checkVertexCompatibility(bool verbose = false) const;
115115

116+
/// retrieves collision context for a single timeframe-id (which may be needed by simulation)
117+
/// (Only copies collision context without QED information. This can be added to the result with the fillQED method
118+
/// in a second step. As a pre-condition, one should have called finalizeTimeframeStructure)
119+
DigitizationContext extractSingleTimeframe(int timeframeid, std::vector<int> const& sources_to_offset);
120+
116121
/// function reading the hits from a chain (previously initialized with initSimChains
117122
/// The hits pointer will be initialized (what to we do about ownership??)
118123
template <typename T>
@@ -128,8 +133,9 @@ class DigitizationContext
128133
// apply collision number cuts and potential relabeling of eventID
129134
void applyMaxCollisionFilter(long startOrbit, long orbitsPerTF, int maxColl);
130135

131-
// finalize timeframe structure (fixes the indices in mTimeFrameStartIndex)
132-
void finalizeTimeframeStructure(long startOrbit, long orbitsPerTF);
136+
/// finalize timeframe structure (fixes the indices in mTimeFrameStartIndex)
137+
// returns the number of timeframes
138+
int finalizeTimeframeStructure(long startOrbit, long orbitsPerTF);
133139

134140
// Sample and fix interaction vertices (according to some distribution). Makes sure that same event ids
135141
// have to have same vertex, as well as event ids associated to same collision.
@@ -173,7 +179,7 @@ class DigitizationContext
173179
// for each collision we may record/fix the interaction vertex (to be used in event generation)
174180
std::vector<math_utils::Point3D<float>> mInteractionVertices;
175181

176-
// the collision records _with_ QED interleaved;
182+
// the collision records **with** QED interleaved;
177183
std::vector<o2::InteractionTimeRecord> mEventRecordsWithQED;
178184
std::vector<std::vector<o2::steer::EventPart>> mEventPartsWithQED;
179185

DataFormats/simulation/src/DigitizationContext.cxx

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <numeric> // for iota
2020
#include <MathUtils/Cartesian.h>
2121
#include <DataFormatsCalibration/MeanVertexObject.h>
22+
#include <filesystem>
2223

2324
using namespace o2::steer;
2425

@@ -196,10 +197,52 @@ o2::parameters::GRPObject const& DigitizationContext::getGRP() const
196197

197198
void DigitizationContext::saveToFile(std::string_view filename) const
198199
{
200+
// checks if the path content of filename exists ... otherwise it is created before creating the ROOT file
201+
auto ensure_path_exists = [](std::string_view filename) {
202+
try {
203+
// Extract the directory path from the filename
204+
std::filesystem::path file_path(filename);
205+
std::filesystem::path dir_path = file_path.parent_path();
206+
207+
// Check if the directory path is empty (which means filename was just a name without path)
208+
if (dir_path.empty()) {
209+
// nothing to do
210+
return true;
211+
}
212+
213+
// Create directories if they do not exist
214+
if (!std::filesystem::exists(dir_path)) {
215+
if (std::filesystem::create_directories(dir_path)) {
216+
// std::cout << "Directories created successfully: " << dir_path.string() << std::endl;
217+
return true;
218+
} else {
219+
std::cerr << "Failed to create directories: " << dir_path.string() << std::endl;
220+
return false;
221+
}
222+
}
223+
return true;
224+
} catch (const std::filesystem::filesystem_error& ex) {
225+
std::cerr << "Filesystem error: " << ex.what() << std::endl;
226+
return false;
227+
} catch (const std::exception& ex) {
228+
std::cerr << "General error: " << ex.what() << std::endl;
229+
return false;
230+
}
231+
};
232+
233+
if (!ensure_path_exists(filename)) {
234+
LOG(error) << "Filename contains path component which could not be created";
235+
return;
236+
}
237+
199238
TFile file(filename.data(), "RECREATE");
200-
auto cl = TClass::GetClass(typeid(*this));
201-
file.WriteObjectAny(this, cl, "DigitizationContext");
202-
file.Close();
239+
if (file.IsOpen()) {
240+
auto cl = TClass::GetClass(typeid(*this));
241+
file.WriteObjectAny(this, cl, "DigitizationContext");
242+
file.Close();
243+
} else {
244+
LOG(error) << "Could not write to file " << filename.data();
245+
}
203246
}
204247

205248
DigitizationContext* DigitizationContext::loadFromFile(std::string_view filename)
@@ -391,13 +434,15 @@ void DigitizationContext::applyMaxCollisionFilter(long startOrbit, long orbitsPe
391434
mEventParts = newparts;
392435
}
393436

394-
void DigitizationContext::finalizeTimeframeStructure(long startOrbit, long orbitsPerTF)
437+
int DigitizationContext::finalizeTimeframeStructure(long startOrbit, long orbitsPerTF)
395438
{
396439
mTimeFrameStartIndex = getTimeFrameBoundaries(mEventRecords, startOrbit, orbitsPerTF);
397440
LOG(info) << "Fixed " << mTimeFrameStartIndex.size() << " timeframes ";
398441
for (auto p : mTimeFrameStartIndex) {
399442
LOG(info) << p.first << " " << p.second;
400443
}
444+
445+
return mTimeFrameStartIndex.size();
401446
}
402447

403448
std::unordered_map<int, int> DigitizationContext::getCollisionIndicesForSource(int source) const
@@ -483,3 +528,55 @@ void DigitizationContext::sampleInteractionVertices(o2::dataformats::MeanVertexO
483528
}
484529
}
485530
}
531+
532+
DigitizationContext DigitizationContext::extractSingleTimeframe(int timeframeid, std::vector<int> const& sources_to_offset)
533+
{
534+
DigitizationContext r; // make a return object
535+
if (mTimeFrameStartIndex.size() == 0) {
536+
LOG(error) << "No timeframe structure determined; Returning empty object. Please call ::finalizeTimeframeStructure before calling this function";
537+
return r;
538+
}
539+
r.mSimPrefixes = mSimPrefixes;
540+
r.mMuBC = mMuBC;
541+
try {
542+
auto startend = mTimeFrameStartIndex.at(timeframeid);
543+
544+
auto startindex = startend.first;
545+
auto endindex = startend.second;
546+
547+
std::copy(mEventRecords.begin() + startindex, mEventRecords.begin() + endindex, std::back_inserter(r.mEventRecords));
548+
std::copy(mEventParts.begin() + startindex, mEventParts.begin() + endindex, std::back_inserter(r.mEventParts));
549+
if (mInteractionVertices.size() > endindex) {
550+
std::copy(mInteractionVertices.begin() + startindex, mInteractionVertices.begin() + endindex, std::back_inserter(r.mInteractionVertices));
551+
}
552+
553+
// let's assume we want to fix the ids for source = source_id
554+
// Then we find the first index that has this source_id and take the corresponding number
555+
// as offset. Thereafter we subtract this offset from all known event parts.
556+
auto perform_offsetting = [&r](int source_id) {
557+
auto indices_for_source = r.getCollisionIndicesForSource(source_id);
558+
int minvalue = std::numeric_limits<int>::max();
559+
for (auto& p : indices_for_source) {
560+
if (p.first < minvalue) {
561+
minvalue = p.first;
562+
}
563+
}
564+
// now fix them
565+
for (auto& p : indices_for_source) {
566+
auto index_into_mEventParts = p.second;
567+
for (auto& part : r.mEventParts[index_into_mEventParts]) {
568+
if (part.sourceID == source_id) {
569+
part.entryID -= minvalue;
570+
}
571+
}
572+
}
573+
};
574+
for (auto source_id : sources_to_offset) {
575+
perform_offsetting(source_id);
576+
}
577+
578+
} catch (std::exception) {
579+
LOG(warn) << "No such timeframe id in collision context. Returing empty object";
580+
}
581+
return r;
582+
}

Steer/src/CollisionContextTool.cxx

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ struct Options {
5555
bool genVertices = false; // whether to assign vertices to collisions
5656
std::string configKeyValues = ""; // string to init config key values
5757
long timestamp = -1; // timestamp for CCDB queries
58+
std::string individualTFextraction = ""; // triggers extraction of individuel timeframe components when non-null
59+
// format is path prefix
5860
};
5961

6062
enum class InteractionLockMode {
@@ -200,7 +202,10 @@ bool parseOptions(int argc, char* argv[], Options& optvalues)
200202
"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")(
201203
"first-orbit", bpo::value<double>(&optvalues.firstFractionalOrbit)->default_value(0), "First (fractional) orbit in the run (HBFUtils.firstOrbit + BC from decimal)")(
202204
"maxCollsPerTF", bpo::value<int>(&optvalues.maxCollsPerTF)->default_value(-1), "Maximal number of MC collisions to put into one timeframe. By default no constraint.")(
203-
"noEmptyTF", bpo::bool_switch(&optvalues.noEmptyTF), "Enforce to have at least one collision")("configKeyValues", bpo::value<std::string>(&optvalues.configKeyValues)->default_value(""), "Semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...')")("with-vertices", "Assign vertices to collisions.")("timestamp", bpo::value<long>(&optvalues.timestamp)->default_value(-1L), "Timestamp for CCDB queries / anchoring");
205+
"noEmptyTF", bpo::bool_switch(&optvalues.noEmptyTF), "Enforce to have at least one collision")(
206+
"configKeyValues", bpo::value<std::string>(&optvalues.configKeyValues)->default_value(""), "Semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...')")("with-vertices", "Assign vertices to collisions.")("timestamp", bpo::value<long>(&optvalues.timestamp)->default_value(-1L), "Timestamp for CCDB queries / anchoring")(
207+
"extract-per-timeframe", bpo::value<std::string>(&optvalues.individualTFextraction)->default_value(""),
208+
"Extract individual timeframe contexts. Format required: time_frame_prefix[:comma_separated_list_of_signals_to_offset]");
204209

205210
options.add_options()("help,h", "Produce help message.");
206211

@@ -283,6 +288,8 @@ int main(int argc, char* argv[])
283288
}
284289
};
285290

291+
auto orbitstart = options.firstOrbit + options.tfid * options.orbitsPerTF;
292+
286293
for (int id = 0; id < ispecs.size(); ++id) {
287294
auto mode = ispecs[id].syncmode;
288295
if (mode == InteractionLockMode::NOLOCK) {
@@ -291,7 +298,6 @@ int main(int argc, char* argv[])
291298
if (!options.bcpatternfile.empty()) {
292299
setBCFillingHelper(sampler, options.bcpatternfile);
293300
}
294-
auto orbitstart = options.firstOrbit + options.tfid * options.orbitsPerTF;
295301
o2::InteractionTimeRecord record;
296302
// this loop makes sure that the first collision is within the range of orbits asked (if noEmptyTF is enabled)
297303
do {
@@ -439,9 +445,9 @@ int main(int argc, char* argv[])
439445
digicontext.setSimPrefixes(prefixes);
440446

441447
// apply max collision per timeframe filters + reindexing of event id (linearisation and compactification)
442-
digicontext.applyMaxCollisionFilter(options.tfid * options.orbitsPerTF, options.orbitsPerTF, options.maxCollsPerTF);
448+
digicontext.applyMaxCollisionFilter(orbitstart, options.orbitsPerTF, options.maxCollsPerTF);
443449

444-
digicontext.finalizeTimeframeStructure(options.tfid * options.orbitsPerTF, options.orbitsPerTF);
450+
auto numTimeFrames = digicontext.finalizeTimeframeStructure(orbitstart, options.orbitsPerTF);
445451

446452
if (options.genVertices) {
447453
// TODO: offer option taking meanVertex directly from CCDB ! "GLO/Calib/MeanVertex"
@@ -466,5 +472,72 @@ int main(int argc, char* argv[])
466472
}
467473
digicontext.saveToFile(options.outfilename);
468474

475+
// extract individual timeframes
476+
if (options.individualTFextraction.size() > 0) {
477+
// we are asked to extract individual timeframe components
478+
479+
LOG(info) << "Extracting individual timeframe collision contexts";
480+
// extract prefix path to store these collision contexts
481+
// Function to check the pattern and extract tokens from b
482+
auto check_and_extract_tokens = [](const std::string& input, std::vector<std::string>& tokens) {
483+
// the regular expression pattern for expected input format
484+
const std::regex pattern(R"(^([a-zA-Z0-9]+)(:([a-zA-Z0-9]+(,[a-zA-Z0-9]+)*))?$)");
485+
std::smatch matches;
486+
487+
// Check if the input matches the pattern
488+
if (std::regex_match(input, matches, pattern)) {
489+
// Clear any existing tokens in the vector
490+
tokens.clear();
491+
492+
// matches[1] contains the part before the colon which we save first
493+
tokens.push_back(matches[1].str());
494+
// matches[2] contains the comma-separated list
495+
std::string b = matches[2].str();
496+
std::regex token_pattern(R"([a-zA-Z0-9]+)");
497+
auto tokens_begin = std::sregex_iterator(b.begin(), b.end(), token_pattern);
498+
auto tokens_end = std::sregex_iterator();
499+
500+
// Iterate over the tokens and add them to the vector
501+
for (std::sregex_iterator i = tokens_begin; i != tokens_end; ++i) {
502+
tokens.push_back((*i).str());
503+
}
504+
return true;
505+
}
506+
LOG(error) << "Argument for --extract-per-timeframe does not match specification";
507+
return false;
508+
};
509+
510+
std::vector<std::string> tokens;
511+
if (check_and_extract_tokens(options.individualTFextraction, tokens)) {
512+
auto path_prefix = tokens[0];
513+
std::vector<int> sources_to_offset{};
514+
515+
LOG(info) << "PREFIX is " << path_prefix;
516+
517+
for (int i = 1; i < tokens.size(); ++i) {
518+
LOG(info) << "Offsetting " << tokens[i];
519+
sources_to_offset.push_back(digicontext.findSimPrefix(tokens[i]));
520+
}
521+
522+
// now we are ready to loop over all timeframes
523+
for (int tf_id = 0; tf_id < numTimeFrames; ++tf_id) {
524+
auto copy = digicontext.extractSingleTimeframe(tf_id, sources_to_offset);
525+
526+
// each individual case gets QED interactions injected
527+
// This should probably be done inside the extraction itself
528+
if (digicontext.isQEDProvided()) {
529+
auto qedSpec = parseInteractionSpec(options.qedInteraction, ispecs, options.useexistingkinematics);
530+
copy.fillQED(qedSpec.name, qedSpec.mcnumberasked, qedSpec.interactionRate);
531+
}
532+
533+
std::stringstream str;
534+
str << path_prefix << (tf_id + 1) << "/collisioncontext.root";
535+
copy.saveToFile(str.str());
536+
LOG(info) << "----";
537+
copy.printCollisionSummary(options.qedInteraction.size() > 0);
538+
}
539+
}
540+
}
541+
469542
return 0;
470543
}

0 commit comments

Comments
 (0)