From f54924a96232e5ab0fd61f497810c487be57fa9a Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 22 Apr 2026 17:29:40 +0200 Subject: [PATCH 01/12] Add first version of collection selected collection parameters --- src/cpp/CMakeLists.txt | 1 + src/cpp/include/UTIL/CheckCollections.h | 28 ++++ src/cpp/src/EXAMPLE/check_col_params.cc | 185 ++++++++++++++++++++++++ src/cpp/src/UTIL/CheckCollections.cc | 62 ++++++++ 4 files changed, 276 insertions(+) create mode 100644 src/cpp/src/EXAMPLE/check_col_params.cc diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt index 6c304cb38..ac35da22f 100644 --- a/src/cpp/CMakeLists.txt +++ b/src/cpp/CMakeLists.txt @@ -460,6 +460,7 @@ ADD_LCIO_EXAMPLE( stdhepjob ) ADD_LCIO_EXAMPLE( stdhepjob_new ) ADD_LCIO_EXAMPLE( addRandomAccess ) ADD_LCIO_EXAMPLE( check_missing_cols ) +ADD_LCIO_EXAMPLE( check_col_params ) ADD_LCIO_EXAMPLE( patch_missing_cols ) ADD_LCIO_EXAMPLE( lcio_event_counter ) ADD_LCIO_EXAMPLE( lcio_check_col_elements ) diff --git a/src/cpp/include/UTIL/CheckCollections.h b/src/cpp/include/UTIL/CheckCollections.h index f15a374b8..797a5dfbd 100644 --- a/src/cpp/include/UTIL/CheckCollections.h +++ b/src/cpp/include/UTIL/CheckCollections.h @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace UTIL { @@ -77,6 +78,29 @@ class PIDHandler; */ Vector getConsistentCollections() const ; + /// Captured parameter values for one collection, split by the four + /// LCParameters types. Every value is a vector because LCParameters stores + /// them as vectors internally. + struct CollectionParamValues { + std::vector>> intParams{}; + std::vector>> floatParams{}; + std::vector>> doubleParams{}; + std::vector>> stringParams{}; + }; + + using CollectedParameters = + std::unordered_map; + + /// Register parameter keys to capture from every collection. + /// Must be called before checkFile/checkFiles. The first value observed + /// for (collection, key) is retained; callers are responsible for knowing + /// that their chosen keys are stable across events. + void setParametersToCollect(std::vector paramNames); + + /// Captured parameter values for every collection seen. Collections with + /// no captured values still appear with an empty CollectionParamValues. + CollectedParameters getCollectedParameters() const; + /** Add a collection with (name,type) that should be added to events in patchEvent(). * * Depending on the contents of name and type one of the following things @@ -141,6 +165,10 @@ class PIDHandler; /// meta information std::unordered_map> _particleIDMetas{}; CollectionVector _patchCols{}; + std::vector _paramsToCollect{}; + CollectedParameters _collectedParams{}; + std::unordered_map> + _capturedKeys{}; }; // class diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc new file mode 100644 index 000000000..8fead81f1 --- /dev/null +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -0,0 +1,185 @@ +#include "lcio.h" +#include "UTIL/CheckCollections.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::vector splitComma(const std::string& s) { + std::vector out; + std::string cur; + for (char c : s) { + if (c == ',') { if (!cur.empty()) out.push_back(std::move(cur)); cur.clear(); } + else { cur.push_back(c); } + } + if (!cur.empty()) out.push_back(std::move(cur)); + return out; +} + +void jsonEscape(std::ostream& os, const std::string& s) { + os << '"'; + for (char c : s) { + switch (c) { + case '"': os << "\\\""; break; + case '\\': os << "\\\\"; break; + case '\b': os << "\\b"; break; + case '\f': os << "\\f"; break; + case '\n': os << "\\n"; break; + case '\r': os << "\\r"; break; + case '\t': os << "\\t"; break; + default: + if (static_cast(c) < 0x20) { + os << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast(c) << std::dec << std::setfill(' '); + } else { + os << c; + } + } + } + os << '"'; +} + +template +void emitNumericArray(std::ostream& os, const std::vector& v) { + os << '['; + for (size_t i = 0; i < v.size(); ++i) { + if (i) os << ", "; + os << v[i]; + } + os << ']'; +} + +void emitFloatArray(std::ostream& os, const std::vector& v) { + os << '['; + os << std::setprecision(std::numeric_limits::max_digits10); + for (size_t i = 0; i < v.size(); ++i) { + if (i) os << ", "; + os << v[i]; + } + os << ']'; +} + +void emitDoubleArray(std::ostream& os, const std::vector& v) { + os << '['; + os << std::setprecision(std::numeric_limits::max_digits10); + for (size_t i = 0; i < v.size(); ++i) { + if (i) os << ", "; + os << v[i]; + } + os << ']'; +} + +void emitStringArray(std::ostream& os, const std::vector& v) { + os << '['; + for (size_t i = 0; i < v.size(); ++i) { + if (i) os << ", "; + jsonEscape(os, v[i]); + } + os << ']'; +} + +bool emitGroup(std::ostream& os, const char* label, + const EVENT::StringVec& keys, bool needsComma, + const std::function& emitVal) { + if (keys.empty()) return needsComma; + if (needsComma) os << ",\n"; + os << " \"" << label << "\": {"; + for (size_t i = 0; i < keys.size(); ++i) { + os << (i ? ",\n " : "\n "); + jsonEscape(os, keys[i]); + os << ": "; + emitVal(keys[i]); + } + os << "\n }"; + return true; +} + +void usage() { + std::cout << "usage: check_col_params --output --params " + " [input-file2 ...]\n" + " (use '-' as output path to write to stdout)\n"; +} + +} // namespace + +int main(int argc, char** argv) { + std::string outputPath; + std::vector paramNames; + std::vector inputs; + + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if ((a == "--output" || a == "-o") && i + 1 < argc) { + outputPath = argv[++i]; + } else if ((a == "--params" || a == "-p") && i + 1 < argc) { + paramNames = splitComma(argv[++i]); + } else if (a == "-h" || a == "--help") { + usage(); return 0; + } else { + inputs.push_back(a); + } + } + + if (outputPath.empty() || paramNames.empty() || inputs.empty()) { + usage(); + return 1; + } + + UTIL::CheckCollections cc; + cc.checkParameters(inputs, paramNames); + const auto collected = cc.getCollectedParameters(); + + std::ofstream fileOut; + std::ostream* out = &std::cout; + if (outputPath != "-") { + fileOut.open(outputPath); + if (!fileOut) { + std::cerr << "error: cannot open '" << outputPath << "' for writing\n"; + return 1; + } + out = &fileOut; + } + + *out << "{"; + bool firstColl = true; + for (const auto& [name, vals] : collected) { + if (!firstColl) *out << ","; + firstColl = false; + *out << "\n "; + jsonEscape(*out, name); + *out << ": {"; + + EVENT::StringVec intKeys, floatKeys, doubleKeys, stringKeys; + vals.getIntKeys(intKeys); + vals.getFloatKeys(floatKeys); + vals.getDoubleKeys(doubleKeys); + vals.getStringKeys(stringKeys); + + bool needsComma = false; + needsComma = emitGroup(*out, "int-params", intKeys, needsComma, [&](const std::string& k) { + EVENT::IntVec v; vals.getIntVals(k, v); emitNumericArray(*out, v); + }); + needsComma = emitGroup(*out, "float-params", floatKeys, needsComma, [&](const std::string& k) { + EVENT::FloatVec v; vals.getFloatVals(k, v); emitFloatArray(*out, v); + }); + needsComma = emitGroup(*out, "double-params", doubleKeys, needsComma, [&](const std::string& k) { + EVENT::DoubleVec v; vals.getDoubleVals(k, v); emitDoubleArray(*out, v); + }); + needsComma = emitGroup(*out, "string-params", stringKeys, needsComma, [&](const std::string& k) { + EVENT::StringVec v; vals.getStringVals(k, v); emitStringArray(*out, v); + }); + + if (needsComma) *out << "\n "; + *out << "}"; + } + *out << "\n}\n"; + + return 0; +} diff --git a/src/cpp/src/UTIL/CheckCollections.cc b/src/cpp/src/UTIL/CheckCollections.cc index fa605fd31..4892a8b76 100644 --- a/src/cpp/src/UTIL/CheckCollections.cc +++ b/src/cpp/src/UTIL/CheckCollections.cc @@ -10,6 +10,39 @@ #include #include +namespace { +bool captureParam(const EVENT::LCParameters& params, + const std::string& key, + UTIL::CheckCollections::CollectionParamValues& dest) { + bool found = false; + if (params.getNInt(key) > 0) { + EVENT::IntVec vals; + params.getIntVals(key, vals); + dest.intParams.emplace_back(key, std::move(vals)); + found = true; + } + if (params.getNFloat(key) > 0) { + EVENT::FloatVec vals; + params.getFloatVals(key, vals); + dest.floatParams.emplace_back(key, std::move(vals)); + found = true; + } + if (params.getNDouble(key) > 0) { + EVENT::DoubleVec vals; + params.getDoubleVals(key, vals); + dest.doubleParams.emplace_back(key, std::move(vals)); + found = true; + } + if (params.getNString(key) > 0) { + EVENT::StringVec vals; + params.getStringVals(key, vals); + dest.stringParams.emplace_back(key, std::move(vals)); + found = true; + } + return found; +} +} // namespace + namespace UTIL { void CheckCollections::checkFiles(const std::vector &fileNames, @@ -72,6 +105,26 @@ void CheckCollections::checkFile(const std::string &fileName, bool quiet) { } it->second.count++; + + if (!_paramsToCollect.empty()) { + auto& captured = _capturedKeys[name]; + auto& dest = _collectedParams[name]; + // Subset collections cannot be read individually (pointer resolution + // fails without the parent collection), so skip the full read for them. + if (!col->isSubset() && captured.size() < _paramsToCollect.size()) { + lcReader.setReadCollectionNames({name}); + auto fullEvt = lcReader.readEvent(evt->getRunNumber(), + evt->getEventNumber()); + lcReader.setReadCollectionNames({}); + const auto& params = fullEvt->getCollection(name)->getParameters(); + for (const auto& pname : _paramsToCollect) { + if (captured.count(pname)) continue; + if (captureParam(params, pname, dest)) { + captured.insert(pname); + } + } + } + } } lcReader.setReadCollectionNames(recoCollections); @@ -114,6 +167,15 @@ void CheckCollections::insertParticleIDMetas(const UTIL::PIDHandler &pidHandler, } } +void CheckCollections::setParametersToCollect(std::vector paramNames) { + _paramsToCollect = std::move(paramNames); +} + +CheckCollections::CollectedParameters +CheckCollections::getCollectedParameters() const { + return _collectedParams; +} + CheckCollections::Vector CheckCollections::getMissingCollections() const { Vector s; for (const auto &[name, coll] : _map) { From 0bb1def42a47522dd619f64db913bd73424d2a4b Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 11:16:45 +0200 Subject: [PATCH 02/12] Use existing structures for recording parameters --- src/cpp/include/UTIL/CheckCollections.h | 33 +++------ src/cpp/src/UTIL/CheckCollections.cc | 90 +++++++++++++------------ 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/src/cpp/include/UTIL/CheckCollections.h b/src/cpp/include/UTIL/CheckCollections.h index 797a5dfbd..8ff3c5109 100644 --- a/src/cpp/include/UTIL/CheckCollections.h +++ b/src/cpp/include/UTIL/CheckCollections.h @@ -2,11 +2,11 @@ #define CheckCollections_h 1 #include "lcio.h" +#include "IMPL/LCParametersImpl.h" #include #include #include -#include #include namespace UTIL { @@ -78,27 +78,19 @@ class PIDHandler; */ Vector getConsistentCollections() const ; - /// Captured parameter values for one collection, split by the four - /// LCParameters types. Every value is a vector because LCParameters stores - /// them as vectors internally. - struct CollectionParamValues { - std::vector>> intParams{}; - std::vector>> floatParams{}; - std::vector>> doubleParams{}; - std::vector>> stringParams{}; - }; - using CollectedParameters = - std::unordered_map; + std::unordered_map; - /// Register parameter keys to capture from every collection. - /// Must be called before checkFile/checkFiles. The first value observed - /// for (collection, key) is retained; callers are responsible for knowing - /// that their chosen keys are stable across events. - void setParametersToCollect(std::vector paramNames); + /// Walk all events in fileNames and capture the first observed value of + /// each key in paramNames for every collection. The first value observed + /// per (collection, key) pair is retained. Collections where none of the + /// requested keys are present still appear in the result with an empty + /// LCParametersImpl. + void checkParameters(const std::vector& fileNames, + const std::vector& paramNames); /// Captured parameter values for every collection seen. Collections with - /// no captured values still appear with an empty CollectionParamValues. + /// no captured values still appear with an empty LCParametersImpl. CollectedParameters getCollectedParameters() const; /** Add a collection with (name,type) that should be added to events in patchEvent(). @@ -165,10 +157,7 @@ class PIDHandler; /// meta information std::unordered_map> _particleIDMetas{}; CollectionVector _patchCols{}; - std::vector _paramsToCollect{}; - CollectedParameters _collectedParams{}; - std::unordered_map> - _capturedKeys{}; + CollectedParameters _collectedParams{}; }; // class diff --git a/src/cpp/src/UTIL/CheckCollections.cc b/src/cpp/src/UTIL/CheckCollections.cc index 4892a8b76..6f4f77cff 100644 --- a/src/cpp/src/UTIL/CheckCollections.cc +++ b/src/cpp/src/UTIL/CheckCollections.cc @@ -9,35 +9,28 @@ #include #include +#include namespace { -bool captureParam(const EVENT::LCParameters& params, +bool captureParam(const EVENT::LCParameters& src, const std::string& key, - UTIL::CheckCollections::CollectionParamValues& dest) { + IMPL::LCParametersImpl& dest) { bool found = false; - if (params.getNInt(key) > 0) { - EVENT::IntVec vals; - params.getIntVals(key, vals); - dest.intParams.emplace_back(key, std::move(vals)); - found = true; + if (src.getNInt(key) > 0) { + EVENT::IntVec vals; src.getIntVals(key, vals); + dest.setValues(key, vals); found = true; } - if (params.getNFloat(key) > 0) { - EVENT::FloatVec vals; - params.getFloatVals(key, vals); - dest.floatParams.emplace_back(key, std::move(vals)); - found = true; + if (src.getNFloat(key) > 0) { + EVENT::FloatVec vals; src.getFloatVals(key, vals); + dest.setValues(key, vals); found = true; } - if (params.getNDouble(key) > 0) { - EVENT::DoubleVec vals; - params.getDoubleVals(key, vals); - dest.doubleParams.emplace_back(key, std::move(vals)); - found = true; + if (src.getNDouble(key) > 0) { + EVENT::DoubleVec vals; src.getDoubleVals(key, vals); + dest.setValues(key, vals); found = true; } - if (params.getNString(key) > 0) { - EVENT::StringVec vals; - params.getStringVals(key, vals); - dest.stringParams.emplace_back(key, std::move(vals)); - found = true; + if (src.getNString(key) > 0) { + EVENT::StringVec vals; src.getStringVals(key, vals); + dest.setValues(key, vals); found = true; } return found; } @@ -105,26 +98,6 @@ void CheckCollections::checkFile(const std::string &fileName, bool quiet) { } it->second.count++; - - if (!_paramsToCollect.empty()) { - auto& captured = _capturedKeys[name]; - auto& dest = _collectedParams[name]; - // Subset collections cannot be read individually (pointer resolution - // fails without the parent collection), so skip the full read for them. - if (!col->isSubset() && captured.size() < _paramsToCollect.size()) { - lcReader.setReadCollectionNames({name}); - auto fullEvt = lcReader.readEvent(evt->getRunNumber(), - evt->getEventNumber()); - lcReader.setReadCollectionNames({}); - const auto& params = fullEvt->getCollection(name)->getParameters(); - for (const auto& pname : _paramsToCollect) { - if (captured.count(pname)) continue; - if (captureParam(params, pname, dest)) { - captured.insert(pname); - } - } - } - } } lcReader.setReadCollectionNames(recoCollections); @@ -167,8 +140,37 @@ void CheckCollections::insertParticleIDMetas(const UTIL::PIDHandler &pidHandler, } } -void CheckCollections::setParametersToCollect(std::vector paramNames) { - _paramsToCollect = std::move(paramNames); +void CheckCollections::checkParameters( + const std::vector &fileNames, + const std::vector ¶mNames) { + // keep track of the parameters for each collection that we have already + // recorded. Only record the first value (and simply assume they are always + // the same) + std::unordered_map> recorded; + for (const auto &fileName : fileNames) { + MT::LCReader lcReader(0); + lcReader.open(fileName); + while (const auto evt = lcReader.readNextEvent()) { + for (const auto &name : *evt->getCollectionNames()) { + auto &dest = _collectedParams[name]; + auto &done = recorded[name]; + if (done.size() == paramNames.size()) { + continue; + } + const auto ¶ms = evt->getCollection(name)->getParameters(); + for (const auto &pname : paramNames) { + if (done.count(pname)) { + continue; + + } + if (captureParam(params, pname, dest)) { + done.insert(pname); + } + } + } + } + lcReader.close(); + } } CheckCollections::CollectedParameters From ddb128eb093ba251edff34c475ed261b0066685d Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 11:38:52 +0200 Subject: [PATCH 03/12] Default implicitly generated destructors --- src/cpp/include/IMPL/LCParametersImpl.h | 2 +- src/cpp/include/pre-generated/EVENT/LCParameters.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/include/IMPL/LCParametersImpl.h b/src/cpp/include/IMPL/LCParametersImpl.h index 2d157ec23..e5bd1481b 100644 --- a/src/cpp/include/IMPL/LCParametersImpl.h +++ b/src/cpp/include/IMPL/LCParametersImpl.h @@ -44,7 +44,7 @@ namespace IMPL { LCParametersImpl() ; /// Destructor. - virtual ~LCParametersImpl() { /* nop */; } + virtual ~LCParametersImpl() = default; /** Returns the first integer value for the given key. */ diff --git a/src/cpp/include/pre-generated/EVENT/LCParameters.h b/src/cpp/include/pre-generated/EVENT/LCParameters.h index 8205c7f80..359024c02 100644 --- a/src/cpp/include/pre-generated/EVENT/LCParameters.h +++ b/src/cpp/include/pre-generated/EVENT/LCParameters.h @@ -29,7 +29,7 @@ class LCParameters { public: /// Destructor. - virtual ~LCParameters() { /* nop */; } + virtual ~LCParameters() = default; /** Returns the first integer value for the given key. */ From a2a4d3e6c83de79f6ac3c1ba15c131c6c2d3b5ee Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 11:44:50 +0200 Subject: [PATCH 04/12] Avoid expenive copies --- src/cpp/include/UTIL/CheckCollections.h | 2 +- src/cpp/src/UTIL/CheckCollections.cc | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cpp/include/UTIL/CheckCollections.h b/src/cpp/include/UTIL/CheckCollections.h index 8ff3c5109..daa253e4a 100644 --- a/src/cpp/include/UTIL/CheckCollections.h +++ b/src/cpp/include/UTIL/CheckCollections.h @@ -91,7 +91,7 @@ class PIDHandler; /// Captured parameter values for every collection seen. Collections with /// no captured values still appear with an empty LCParametersImpl. - CollectedParameters getCollectedParameters() const; + const CollectedParameters& getCollectedParameters() const; /** Add a collection with (name,type) that should be added to events in patchEvent(). * diff --git a/src/cpp/src/UTIL/CheckCollections.cc b/src/cpp/src/UTIL/CheckCollections.cc index 6f4f77cff..f3dd8b5cc 100644 --- a/src/cpp/src/UTIL/CheckCollections.cc +++ b/src/cpp/src/UTIL/CheckCollections.cc @@ -159,10 +159,9 @@ void CheckCollections::checkParameters( } const auto ¶ms = evt->getCollection(name)->getParameters(); for (const auto &pname : paramNames) { - if (done.count(pname)) { + if (done.count(pname)) { continue; - - } + } if (captureParam(params, pname, dest)) { done.insert(pname); } @@ -173,7 +172,7 @@ void CheckCollections::checkParameters( } } -CheckCollections::CollectedParameters +const CheckCollections::CollectedParameters& CheckCollections::getCollectedParameters() const { return _collectedParams; } From 1ef085165ff9f2eb7b26880fa5e4d1ada752c34d Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 11:45:09 +0200 Subject: [PATCH 05/12] Reduce code duplication and remove stdout fallback --- src/cpp/src/EXAMPLE/check_col_params.cc | 319 +++++++++++++----------- 1 file changed, 167 insertions(+), 152 deletions(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index 8fead81f1..26a922926 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -1,5 +1,5 @@ -#include "lcio.h" #include "UTIL/CheckCollections.h" +#include "lcio.h" #include #include @@ -8,178 +8,193 @@ #include #include #include +#include #include namespace { -std::vector splitComma(const std::string& s) { - std::vector out; - std::string cur; - for (char c : s) { - if (c == ',') { if (!cur.empty()) out.push_back(std::move(cur)); cur.clear(); } - else { cur.push_back(c); } - } - if (!cur.empty()) out.push_back(std::move(cur)); - return out; +std::vector splitComma(const std::string &s) { + std::vector out; + std::string cur; + for (char c : s) { + if (c == ',') { + if (!cur.empty()) + out.push_back(std::move(cur)); + cur.clear(); + } else { + cur.push_back(c); + } + } + if (!cur.empty()) + out.push_back(std::move(cur)); + return out; } -void jsonEscape(std::ostream& os, const std::string& s) { - os << '"'; - for (char c : s) { - switch (c) { - case '"': os << "\\\""; break; - case '\\': os << "\\\\"; break; - case '\b': os << "\\b"; break; - case '\f': os << "\\f"; break; - case '\n': os << "\\n"; break; - case '\r': os << "\\r"; break; - case '\t': os << "\\t"; break; - default: - if (static_cast(c) < 0x20) { - os << "\\u" << std::hex << std::setw(4) << std::setfill('0') - << static_cast(c) << std::dec << std::setfill(' '); - } else { - os << c; +void jsonEscape(std::ostream &os, const std::string &s) { + os << '"'; + for (char c : s) { + switch (c) { + case '"': + os << "\\\""; + break; + case '\\': + os << "\\\\"; + break; + case '\b': + os << "\\b"; + break; + case '\f': + os << "\\f"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + case '\t': + os << "\\t"; + break; + default: + if (static_cast(c) < 0x20) { + os << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast(c) << std::dec << std::setfill(' '); + } else { + os << c; + } } } - } - os << '"'; + os << '"'; } template -void emitNumericArray(std::ostream& os, const std::vector& v) { - os << '['; - for (size_t i = 0; i < v.size(); ++i) { - if (i) os << ", "; - os << v[i]; - } - os << ']'; +void emitArray(std::ostream &os, const std::vector &v) { + os << '['; + if constexpr (std::is_floating_point_v) { + os << std::setprecision(std::numeric_limits::max_digits10); + } + for (size_t i = 0; i < v.size(); ++i) { + if (i) { + os << ", "; + } + if constexpr (std::is_same_v) { + jsonEscape(os, v[i]); + } else { + os << v[i]; + } + } + os << ']'; } -void emitFloatArray(std::ostream& os, const std::vector& v) { - os << '['; - os << std::setprecision(std::numeric_limits::max_digits10); - for (size_t i = 0; i < v.size(); ++i) { - if (i) os << ", "; - os << v[i]; - } - os << ']'; +bool emitGroup(std::ostream &os, const char *label, + const EVENT::StringVec &keys, bool needsComma, + const std::function &emitVal) { + if (keys.empty()) { + return needsComma; + } + if (needsComma) { + os << ",\n"; + } + os << " \"" << label << "\": {"; + for (size_t i = 0; i < keys.size(); ++i) { + os << (i ? ",\n " : "\n "); + jsonEscape(os, keys[i]); + os << ": "; + emitVal(keys[i]); + } + os << "\n }"; + return true; } -void emitDoubleArray(std::ostream& os, const std::vector& v) { - os << '['; - os << std::setprecision(std::numeric_limits::max_digits10); - for (size_t i = 0; i < v.size(); ++i) { - if (i) os << ", "; - os << v[i]; - } - os << ']'; +void usage() { + std::cout + << "usage: check_col_params --output --params " + " [input-file2 ...]\n"; } -void emitStringArray(std::ostream& os, const std::vector& v) { - os << '['; - for (size_t i = 0; i < v.size(); ++i) { - if (i) os << ", "; - jsonEscape(os, v[i]); - } - os << ']'; -} +} // namespace -bool emitGroup(std::ostream& os, const char* label, - const EVENT::StringVec& keys, bool needsComma, - const std::function& emitVal) { - if (keys.empty()) return needsComma; - if (needsComma) os << ",\n"; - os << " \"" << label << "\": {"; - for (size_t i = 0; i < keys.size(); ++i) { - os << (i ? ",\n " : "\n "); - jsonEscape(os, keys[i]); - os << ": "; - emitVal(keys[i]); - } - os << "\n }"; - return true; -} +int main(int argc, char **argv) { + std::string outputPath; + std::vector paramNames; + std::vector inputs; + + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if ((a == "--output" || a == "-o") && i + 1 < argc) { + outputPath = argv[++i]; + } else if ((a == "--params" || a == "-p") && i + 1 < argc) { + paramNames = splitComma(argv[++i]); + } else if (a == "-h" || a == "--help") { + usage(); + return 0; + } else { + inputs.push_back(a); + } + } -void usage() { - std::cout << "usage: check_col_params --output --params " - " [input-file2 ...]\n" - " (use '-' as output path to write to stdout)\n"; -} + if (outputPath.empty() || paramNames.empty() || inputs.empty()) { + usage(); + return 1; + } -} // namespace + UTIL::CheckCollections cc; + cc.checkParameters(inputs, paramNames); + const auto &collected = cc.getCollectedParameters(); -int main(int argc, char** argv) { - std::string outputPath; - std::vector paramNames; - std::vector inputs; - - for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; - if ((a == "--output" || a == "-o") && i + 1 < argc) { - outputPath = argv[++i]; - } else if ((a == "--params" || a == "-p") && i + 1 < argc) { - paramNames = splitComma(argv[++i]); - } else if (a == "-h" || a == "--help") { - usage(); return 0; - } else { - inputs.push_back(a); + std::ofstream out(outputPath); + if (!out) { + std::cerr << "error: cannot open '" << outputPath << "' for writing\n"; + return 1; } - } - - if (outputPath.empty() || paramNames.empty() || inputs.empty()) { - usage(); - return 1; - } - - UTIL::CheckCollections cc; - cc.checkParameters(inputs, paramNames); - const auto collected = cc.getCollectedParameters(); - - std::ofstream fileOut; - std::ostream* out = &std::cout; - if (outputPath != "-") { - fileOut.open(outputPath); - if (!fileOut) { - std::cerr << "error: cannot open '" << outputPath << "' for writing\n"; - return 1; + + out << "{"; + bool firstColl = true; + for (const auto &[name, vals] : collected) { + if (!firstColl) { + out << ","; + } + firstColl = false; + out << "\n " << name << ": {"; + + EVENT::StringVec intKeys, floatKeys, doubleKeys, stringKeys; + vals.getIntKeys(intKeys); + vals.getFloatKeys(floatKeys); + vals.getDoubleKeys(doubleKeys); + vals.getStringKeys(stringKeys); + + bool needsComma = false; + needsComma = emitGroup(out, "int-params", intKeys, needsComma, + [&](const std::string &k) { + EVENT::IntVec v; + vals.getIntVals(k, v); + emitArray(out, v); + }); + needsComma = emitGroup(out, "float-params", floatKeys, needsComma, + [&](const std::string &k) { + EVENT::FloatVec v; + vals.getFloatVals(k, v); + emitArray(out, v); + }); + needsComma = emitGroup(out, "double-params", doubleKeys, needsComma, + [&](const std::string &k) { + EVENT::DoubleVec v; + vals.getDoubleVals(k, v); + emitArray(out, v); + }); + needsComma = emitGroup(out, "string-params", stringKeys, needsComma, + [&](const std::string &k) { + EVENT::StringVec v; + vals.getStringVals(k, v); + emitArray(out, v); + }); + + if (needsComma) { + out << "\n "; + } + out << "}"; } - out = &fileOut; - } - - *out << "{"; - bool firstColl = true; - for (const auto& [name, vals] : collected) { - if (!firstColl) *out << ","; - firstColl = false; - *out << "\n "; - jsonEscape(*out, name); - *out << ": {"; - - EVENT::StringVec intKeys, floatKeys, doubleKeys, stringKeys; - vals.getIntKeys(intKeys); - vals.getFloatKeys(floatKeys); - vals.getDoubleKeys(doubleKeys); - vals.getStringKeys(stringKeys); - - bool needsComma = false; - needsComma = emitGroup(*out, "int-params", intKeys, needsComma, [&](const std::string& k) { - EVENT::IntVec v; vals.getIntVals(k, v); emitNumericArray(*out, v); - }); - needsComma = emitGroup(*out, "float-params", floatKeys, needsComma, [&](const std::string& k) { - EVENT::FloatVec v; vals.getFloatVals(k, v); emitFloatArray(*out, v); - }); - needsComma = emitGroup(*out, "double-params", doubleKeys, needsComma, [&](const std::string& k) { - EVENT::DoubleVec v; vals.getDoubleVals(k, v); emitDoubleArray(*out, v); - }); - needsComma = emitGroup(*out, "string-params", stringKeys, needsComma, [&](const std::string& k) { - EVENT::StringVec v; vals.getStringVals(k, v); emitStringArray(*out, v); - }); - - if (needsComma) *out << "\n "; - *out << "}"; - } - *out << "\n}\n"; - - return 0; + out << "\n}\n"; + + return 0; } From fefd6fac0a43ed358a9372687d6d4aa3857b4385 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 11:50:28 +0200 Subject: [PATCH 06/12] Replace custom string splitting with LCTokenizer --- src/cpp/src/EXAMPLE/check_col_params.cc | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index 26a922926..da52177ef 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -1,6 +1,7 @@ +#include "UTIL/BitField64.h" #include "UTIL/CheckCollections.h" -#include "lcio.h" +#include #include #include #include @@ -13,22 +14,6 @@ namespace { -std::vector splitComma(const std::string &s) { - std::vector out; - std::string cur; - for (char c : s) { - if (c == ',') { - if (!cur.empty()) - out.push_back(std::move(cur)); - cur.clear(); - } else { - cur.push_back(c); - } - } - if (!cur.empty()) - out.push_back(std::move(cur)); - return out; -} void jsonEscape(std::ostream &os, const std::string &s) { os << '"'; @@ -124,7 +109,8 @@ int main(int argc, char **argv) { if ((a == "--output" || a == "-o") && i + 1 < argc) { outputPath = argv[++i]; } else if ((a == "--params" || a == "-p") && i + 1 < argc) { - paramNames = splitComma(argv[++i]); + const std::string ps = argv[++i]; + std::for_each(ps.begin(), ps.end(), UTIL::LCTokenizer(paramNames, ',')); } else if (a == "-h" || a == "--help") { usage(); return 0; From e6553330b02f5976b117093bab88484968abb0af Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 13:12:02 +0200 Subject: [PATCH 07/12] Do not emit collections without any collected parameters --- src/cpp/src/EXAMPLE/check_col_params.cc | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index da52177ef..a9657423d 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -14,7 +14,6 @@ namespace { - void jsonEscape(std::ostream &os, const std::string &s) { os << '"'; for (char c : s) { @@ -110,7 +109,8 @@ int main(int argc, char **argv) { outputPath = argv[++i]; } else if ((a == "--params" || a == "-p") && i + 1 < argc) { const std::string ps = argv[++i]; - std::for_each(ps.begin(), ps.end(), UTIL::LCTokenizer(paramNames, ',')); + std::for_each(ps.begin(), ps.end(), + UTIL::LCTokenizer(paramNames, ',')); } else if (a == "-h" || a == "--help") { usage(); return 0; @@ -136,19 +136,30 @@ int main(int argc, char **argv) { out << "{"; bool firstColl = true; - for (const auto &[name, vals] : collected) { - if (!firstColl) { - out << ","; - } - firstColl = false; - out << "\n " << name << ": {"; - + for (const auto &entry : collected) { +#if defined(__clang__) && __clang_major__ < 13 + const auto &name = entry.first; + const auto &vals = entry.second; +#else + const auto &[name, vals] = entry; +#endif EVENT::StringVec intKeys, floatKeys, doubleKeys, stringKeys; vals.getIntKeys(intKeys); vals.getFloatKeys(floatKeys); vals.getDoubleKeys(doubleKeys); vals.getStringKeys(stringKeys); + if (intKeys.empty() && floatKeys.empty() && doubleKeys.empty() && + stringKeys.empty()) { + continue; + } + + if (!firstColl) { + out << ","; + } + firstColl = false; + out << "\n " << name << ": {"; + bool needsComma = false; needsComma = emitGroup(out, "int-params", intKeys, needsComma, [&](const std::string &k) { From 2500bdb7bd906ed986ef43813e0e79455a4d9699 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 13:30:01 +0200 Subject: [PATCH 08/12] Refactor things a bit more for easier to read main loop --- src/cpp/src/EXAMPLE/check_col_params.cc | 61 +++++++++++++------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index a9657423d..7637b648b 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -1,10 +1,10 @@ +#include "EVENT/LCParameters.h" #include "UTIL/BitField64.h" #include "UTIL/CheckCollections.h" #include #include #include -#include #include #include #include @@ -70,9 +70,27 @@ void emitArray(std::ostream &os, const std::vector &v) { os << ']'; } -bool emitGroup(std::ostream &os, const char *label, - const EVENT::StringVec &keys, bool needsComma, - const std::function &emitVal) { +template +std::vector getValues(const EVENT::LCParameters ¶ms, + const std::string &key) { + std::vector values; + if constexpr (std::is_same_v) { + params.getIntVals(key, values); + } else if constexpr (std::is_same_v) { + params.getFloatVals(key, values); + } else if constexpr (std::is_same_v) { + params.getDoubleVals(key, values); + } else { + params.getStringVals(key, values); + } + + return values; +} + +template +bool emitGroup(std::ostream &os, const std::string &label, + const EVENT::LCParameters ¶ms, const EVENT::StringVec &keys, + bool needsComma) { if (keys.empty()) { return needsComma; } @@ -84,7 +102,7 @@ bool emitGroup(std::ostream &os, const char *label, os << (i ? ",\n " : "\n "); jsonEscape(os, keys[i]); os << ": "; - emitVal(keys[i]); + emitArray(os, getValues(params, keys[i])); } os << "\n }"; return true; @@ -161,30 +179,15 @@ int main(int argc, char **argv) { out << "\n " << name << ": {"; bool needsComma = false; - needsComma = emitGroup(out, "int-params", intKeys, needsComma, - [&](const std::string &k) { - EVENT::IntVec v; - vals.getIntVals(k, v); - emitArray(out, v); - }); - needsComma = emitGroup(out, "float-params", floatKeys, needsComma, - [&](const std::string &k) { - EVENT::FloatVec v; - vals.getFloatVals(k, v); - emitArray(out, v); - }); - needsComma = emitGroup(out, "double-params", doubleKeys, needsComma, - [&](const std::string &k) { - EVENT::DoubleVec v; - vals.getDoubleVals(k, v); - emitArray(out, v); - }); - needsComma = emitGroup(out, "string-params", stringKeys, needsComma, - [&](const std::string &k) { - EVENT::StringVec v; - vals.getStringVals(k, v); - emitArray(out, v); - }); + + needsComma = + emitGroup(out, "int-params", vals, intKeys, needsComma); + needsComma = + emitGroup(out, "float-params", vals, floatKeys, needsComma); + needsComma = emitGroup(out, "double-params", vals, doubleKeys, + needsComma); + needsComma = emitGroup(out, "string-params", vals, + stringKeys, needsComma); if (needsComma) { out << "\n "; From fc6008f705f4f1f38a5f781f95888f56cfadc571 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 13:33:58 +0200 Subject: [PATCH 09/12] Move templated function to LCParameters --- .../pre-generated/EVENT/LCParameters.h | 19 +++++++++++++++++++ src/cpp/src/EXAMPLE/check_col_params.cc | 19 +------------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/cpp/include/pre-generated/EVENT/LCParameters.h b/src/cpp/include/pre-generated/EVENT/LCParameters.h index 359024c02..6e1368d7a 100644 --- a/src/cpp/include/pre-generated/EVENT/LCParameters.h +++ b/src/cpp/include/pre-generated/EVENT/LCParameters.h @@ -67,6 +67,25 @@ class LCParameters { */ virtual StringVec & getStringVals(const std::string & key, StringVec & values) const = 0; + /** + * Get all values for the given key and type + */ + template + std::vector getVals(const std::string &key) const { + std::vector values; + if constexpr (std::is_same_v) { + getIntVals(key, values); + } else if constexpr (std::is_same_v) { + getFloatVals(key, values); + } else if constexpr (std::is_same_v) { + getDoubleVals(key, values); + } else { + getStringVals(key, values); + } + + return values; + } + /** Returns a list of all keys of integer parameters. */ virtual const StringVec & getIntKeys(StringVec & keys) const = 0; diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index 7637b648b..0a6eb5629 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -70,23 +70,6 @@ void emitArray(std::ostream &os, const std::vector &v) { os << ']'; } -template -std::vector getValues(const EVENT::LCParameters ¶ms, - const std::string &key) { - std::vector values; - if constexpr (std::is_same_v) { - params.getIntVals(key, values); - } else if constexpr (std::is_same_v) { - params.getFloatVals(key, values); - } else if constexpr (std::is_same_v) { - params.getDoubleVals(key, values); - } else { - params.getStringVals(key, values); - } - - return values; -} - template bool emitGroup(std::ostream &os, const std::string &label, const EVENT::LCParameters ¶ms, const EVENT::StringVec &keys, @@ -102,7 +85,7 @@ bool emitGroup(std::ostream &os, const std::string &label, os << (i ? ",\n " : "\n "); jsonEscape(os, keys[i]); os << ": "; - emitArray(os, getValues(params, keys[i])); + emitArray(os, params.getVals(keys[i])); } os << "\n }"; return true; From 7d19fba813ee34fa15d1fad1348fc663c7ad095b Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 15:02:20 +0200 Subject: [PATCH 10/12] Remove unnecessary clang12 workaround No longer necessary because capturing in lambda has been removed --- src/cpp/src/EXAMPLE/check_col_params.cc | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index 0a6eb5629..0c1459f4b 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -137,13 +137,7 @@ int main(int argc, char **argv) { out << "{"; bool firstColl = true; - for (const auto &entry : collected) { -#if defined(__clang__) && __clang_major__ < 13 - const auto &name = entry.first; - const auto &vals = entry.second; -#else - const auto &[name, vals] = entry; -#endif + for (const auto &[name, vals] : collected) { EVENT::StringVec intKeys, floatKeys, doubleKeys, stringKeys; vals.getIntKeys(intKeys); vals.getFloatKeys(floatKeys); From 3d29e445a9419a6b80b7a90519948808bbaa3c78 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 23 Apr 2026 15:06:00 +0200 Subject: [PATCH 11/12] Implement param capture in terms of new template function --- src/cpp/src/UTIL/CheckCollections.cc | 41 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/cpp/src/UTIL/CheckCollections.cc b/src/cpp/src/UTIL/CheckCollections.cc index f3dd8b5cc..56f4323a9 100644 --- a/src/cpp/src/UTIL/CheckCollections.cc +++ b/src/cpp/src/UTIL/CheckCollections.cc @@ -12,27 +12,26 @@ #include namespace { -bool captureParam(const EVENT::LCParameters& src, - const std::string& key, - IMPL::LCParametersImpl& dest) { - bool found = false; - if (src.getNInt(key) > 0) { - EVENT::IntVec vals; src.getIntVals(key, vals); - dest.setValues(key, vals); found = true; - } - if (src.getNFloat(key) > 0) { - EVENT::FloatVec vals; src.getFloatVals(key, vals); - dest.setValues(key, vals); found = true; - } - if (src.getNDouble(key) > 0) { - EVENT::DoubleVec vals; src.getDoubleVals(key, vals); - dest.setValues(key, vals); found = true; - } - if (src.getNString(key) > 0) { - EVENT::StringVec vals; src.getStringVals(key, vals); - dest.setValues(key, vals); found = true; - } - return found; +bool captureParam(const EVENT::LCParameters &src, const std::string &key, + IMPL::LCParametersImpl &dest) { + bool found = false; + if (const auto vals = src.getVals(key); !vals.empty()) { + dest.setValues(key, vals); + found = true; + } + if (const auto vals = src.getVals(key); !vals.empty()) { + dest.setValues(key, vals); + found = true; + } + if (const auto vals = src.getVals(key); !vals.empty()) { + dest.setValues(key, vals); + found = true; + } + if (const auto vals = src.getVals(key); !vals.empty()) { + dest.setValues(key, vals); + found = true; + } + return found; } } // namespace From c3d3737912c18bc28503c6b13646f1e5b58d6c37 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 28 Apr 2026 20:27:56 +0200 Subject: [PATCH 12/12] Quote all keys --- src/cpp/src/EXAMPLE/check_col_params.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/src/EXAMPLE/check_col_params.cc b/src/cpp/src/EXAMPLE/check_col_params.cc index 0c1459f4b..0c7bfc2de 100644 --- a/src/cpp/src/EXAMPLE/check_col_params.cc +++ b/src/cpp/src/EXAMPLE/check_col_params.cc @@ -153,7 +153,7 @@ int main(int argc, char **argv) { out << ","; } firstColl = false; - out << "\n " << name << ": {"; + out << "\n \"" << name << "\": {"; bool needsComma = false;