From e075bc13e4718afbe04d869b26fadcb786136aca Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 26 Aug 2025 13:40:21 +0200 Subject: [PATCH] Iteration on AODBcRewriter Rewrite of AODBcRewriter, now including resorting of TTree refering to fIndexBC. This is necessary since index columns need to be sorted. Also adding 2 tools to check for BC monotonicity in AO2D as well as fIndexBC monotonicity in all tables. Codes are AI co-generated. --- MC/bin/o2dpg_sim_workflow.py | 4 +- MC/utils/AODBcRewriter.C | 1155 +++++++++++++++++----- MC/utils/AOD_check_globalBC_monotonic.C | 141 +++ MC/utils/AOD_detect_unsorted_fIndexBCs.C | 260 +++++ 4 files changed, 1328 insertions(+), 232 deletions(-) create mode 100644 MC/utils/AOD_check_globalBC_monotonic.C create mode 100644 MC/utils/AOD_detect_unsorted_fIndexBCs.C diff --git a/MC/bin/o2dpg_sim_workflow.py b/MC/bin/o2dpg_sim_workflow.py index fa57b4aea..ee6de3209 100755 --- a/MC/bin/o2dpg_sim_workflow.py +++ b/MC/bin/o2dpg_sim_workflow.py @@ -1951,11 +1951,11 @@ def remove_json_prefix(path): AOD_merge_task['cmd'] = ' set -e ; [ -f aodmerge_input.txt ] && rm aodmerge_input.txt; ' AOD_merge_task['cmd'] += ' for i in `seq 1 ' + str(NTIMEFRAMES) + '`; do echo "tf${i}/AO2D.root" >> aodmerge_input.txt; done; ' AOD_merge_task['cmd'] += ' o2-aod-merger --input aodmerge_input.txt --output AO2D_pre.root' - # produce MonaLisa event stat file - AOD_merge_task['cmd'] += ' ; ${O2DPG_ROOT}/MC/bin/o2dpg_determine_eventstat.py' # reindex the BC + connected tables because it there could be duplicate BC entries due to the orbit-early treatment # see https://its.cern.ch/jira/browse/O2-6227 AOD_merge_task['cmd'] += ' ; root -q -b -l "${O2DPG_ROOT}/MC/utils/AODBcRewriter.C(\\\"AO2D_pre.root\\\",\\\"AO2D.root\\\")"' + # produce MonaLisa event stat file + AOD_merge_task['cmd'] += ' ; ${O2DPG_ROOT}/MC/bin/o2dpg_determine_eventstat.py' AOD_merge_task['alternative_alienv_package'] = "None" # we want latest software for this step workflow['stages'].append(AOD_merge_task) diff --git a/MC/utils/AODBcRewriter.C b/MC/utils/AODBcRewriter.C index 23cefe437..0aa48d8f6 100644 --- a/MC/utils/AODBcRewriter.C +++ b/MC/utils/AODBcRewriter.C @@ -1,306 +1,1001 @@ -// A utility to remove duplicate bunch crossing entries -// from the O2bc_ table/TTree and to adjust all tables refering -// to fIndexBC. -// Situations of duplicate BCs can arise in O2DPG MC and are harder to avoid -// directly in the AO2D creation. This tool provides a convenient -// post-processing step to rectify the situation. -// The tool might need adjustment whenever the AO2D data format changes, for -// instance when new tables are added which are directly joinable to the BC -// table. +// AODBcRewriter.C +// Usage: root -l -b -q 'AODBcRewriter.C("AO2D.root","AO2D_rewritten.root")' +// Fixes globalBC ordering and duplication problems in AO2D files; sorts and +// rewrites tables refering to the BC table generic branch code only; No +// knowledge of AOD dataformat used apart from the BC table. -// started by sandro.wenzel@cern.ch August 2025 - -// Usage: root -l -b -q 'AODBcRewriter.C("input.root","output.root")' - -#if !defined(__CLING__) || defined(__ROOTCLING__) +#ifndef __CLING__ +#include "RVersion.h" #include "TBranch.h" +#include "TBufferFile.h" +#include "TClass.h" #include "TDirectory.h" #include "TFile.h" #include "TKey.h" #include "TLeaf.h" +#include "TList.h" +#include "TMap.h" +#include "TObjString.h" +#include "TROOT.h" #include "TString.h" #include "TTree.h" #include +#include +#include #include +#include #include #include +#include +#include #include +#include #include #endif -/// Helper to manage branch buffers and copying -struct BranchHandler { - std::string name; - std::string type; - void *inBuf = nullptr; - void *outBuf = nullptr; - TBranch *inBranch = nullptr; - TBranch *outBranch = nullptr; - - BranchHandler(TBranch *br, TTree *outTree = nullptr) { - inBranch = br; - name = br->GetName(); - TLeaf *leaf = (TLeaf *)br->GetListOfLeaves()->At(0); - type = leaf->GetTypeName(); +// ----------------- small helpers ----------------- +static inline bool isDF(const char *name) { + return TString(name).BeginsWith("DF_"); +} +static inline bool isBCtree(const char *tname) { + return TString(tname).BeginsWith("O2bc_"); +} +static inline bool isFlagsTree(const char *tname) { + return TString(tname) == "O2bcflag" || TString(tname) == "O2bcflags" || + TString(tname).BeginsWith("O2bcflag"); +} +static const char *findIndexBranchName(TTree *t) { + if (!t) + return nullptr; + if (t->GetBranch("fIndexBCs")) + return "fIndexBCs"; + if (t->GetBranch("fIndexBC")) + return "fIndexBC"; + return nullptr; +} - if (type == "Int_t") { - inBuf = new Int_t; - if (outTree) { - outBuf = new Int_t; - outBranch = outTree->Branch(name.c_str(), (Int_t *)outBuf); - } - } else if (type == "ULong64_t") { - inBuf = new ULong64_t; - if (outTree) { - outBuf = new ULong64_t; - outBranch = outTree->Branch(name.c_str(), (ULong64_t *)outBuf); - } - } else if (type == "UChar_t") { - inBuf = new UChar_t; - if (outTree) { - outBuf = new UChar_t; - outBranch = outTree->Branch(name.c_str(), (UChar_t *)outBuf); - } - } else { - std::cerr << "Unsupported type " << type << " for branch " << name - << std::endl; +// Scalar type tag +enum class ScalarTag { + kInt, + kUInt, + kShort, + kUShort, + kLong64, + kULong64, + kFloat, + kDouble, + kChar, + kUChar, + kBool, + kUnknown +}; +static ScalarTag leafType(TLeaf *leaf) { + if (!leaf) + return ScalarTag::kUnknown; + TString tn = leaf->GetTypeName(); + if (tn == "Int_t") + return ScalarTag::kInt; + if (tn == "UInt_t") + return ScalarTag::kUInt; + if (tn == "Short_t") + return ScalarTag::kShort; + if (tn == "UShort_t") + return ScalarTag::kUShort; + if (tn == "Long64_t") + return ScalarTag::kLong64; + if (tn == "ULong64_t") + return ScalarTag::kULong64; + if (tn == "Float_t") + return ScalarTag::kFloat; + if (tn == "Double_t") + return ScalarTag::kDouble; + if (tn == "Char_t") + return ScalarTag::kChar; + if (tn == "UChar_t") + return ScalarTag::kUChar; + if (tn == "Bool_t") + return ScalarTag::kBool; + return ScalarTag::kUnknown; +} +static size_t scalarSize(ScalarTag t) { + switch (t) { + case ScalarTag::kInt: + return sizeof(Int_t); + case ScalarTag::kUInt: + return sizeof(UInt_t); + case ScalarTag::kShort: + return sizeof(Short_t); + case ScalarTag::kUShort: + return sizeof(UShort_t); + case ScalarTag::kLong64: + return sizeof(Long64_t); + case ScalarTag::kULong64: + return sizeof(ULong64_t); + case ScalarTag::kFloat: + return sizeof(Float_t); + case ScalarTag::kDouble: + return sizeof(Double_t); + case ScalarTag::kChar: + return sizeof(Char_t); + case ScalarTag::kUChar: + return sizeof(UChar_t); + case ScalarTag::kBool: + return sizeof(Bool_t); + default: + return 0; + } +} + +// small Buffer base for lifetime management +struct BufBase { + virtual ~BufBase() {} + virtual void *ptr() = 0; +}; +template struct ScalarBuf : BufBase { + T v; + void *ptr() override { return &v; } +}; +template struct ArrayBuf : BufBase { + std::vector a; + void *ptr() override { return a.data(); } +}; + +template static std::unique_ptr makeScalarBuf() { + return std::make_unique>(); +} +template static std::unique_ptr makeArrayBuf(size_t n) { + auto p = std::make_unique>(); + if (n == 0) + n = 1; + p->a.resize(n); + return p; +} + +// prescan the count branch to determine max length for a VLA +static Long64_t prescanMaxLen(TTree *src, TBranch *countBr, + ScalarTag countTag) { + if (!countBr) + return 1; + // temporary buffer + std::unique_ptr tmp; + switch (countTag) { + case ScalarTag::kInt: + tmp = makeScalarBuf(); + break; + case ScalarTag::kUInt: + tmp = makeScalarBuf(); + break; + case ScalarTag::kShort: + tmp = makeScalarBuf(); + break; + case ScalarTag::kUShort: + tmp = makeScalarBuf(); + break; + case ScalarTag::kLong64: + tmp = makeScalarBuf(); + break; + case ScalarTag::kULong64: + tmp = makeScalarBuf(); + break; + default: + tmp = makeScalarBuf(); + break; + } + countBr->SetAddress(tmp->ptr()); + Long64_t maxLen = 0; + Long64_t nEnt = src->GetEntries(); + for (Long64_t i = 0; i < nEnt; ++i) { + countBr->GetEntry(i); + Long64_t v = 0; + switch (countTag) { + case ScalarTag::kInt: + v = *(Int_t *)tmp->ptr(); + break; + case ScalarTag::kUInt: + v = *(UInt_t *)tmp->ptr(); + break; + case ScalarTag::kShort: + v = *(Short_t *)tmp->ptr(); + break; + case ScalarTag::kUShort: + v = *(UShort_t *)tmp->ptr(); + break; + case ScalarTag::kLong64: + v = *(Long64_t *)tmp->ptr(); + break; + case ScalarTag::kULong64: + v = *(ULong64_t *)tmp->ptr(); + break; + default: + v = *(Int_t *)tmp->ptr(); + break; } - if (inBuf) - inBranch->SetAddress(inBuf); + if (v > maxLen) + maxLen = v; + } + return maxLen; +} + +// bind scalar branch (in and out share same buffer) +static std::unique_ptr bindScalarBranch(TBranch *inBr, TBranch *outBr, + ScalarTag tag) { + switch (tag) { + case ScalarTag::kInt: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kUInt: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kShort: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kUShort: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kLong64: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kULong64: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kFloat: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kDouble: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kChar: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + case ScalarTag::kUChar: { + auto b = makeScalarBuf(); + inBr->SetAddress(b->ptr()); + outBr->SetAddress(b->ptr()); + return b; + } + default: + return nullptr; + } +} + +// bind VLA typed: returns data buffer and outputs count buffer (via +// outCountBuf) +template +static std::unique_ptr +bindArrayTyped(TBranch *inData, TBranch *outData, TBranch *inCount, + TBranch *outCount, ScalarTag countTag, Long64_t maxLen, + std::unique_ptr &outCountBuf) { + // create count buffer + std::unique_ptr countBuf; + switch (countTag) { + case ScalarTag::kInt: + countBuf = makeScalarBuf(); + break; + case ScalarTag::kUInt: + countBuf = makeScalarBuf(); + break; + case ScalarTag::kShort: + countBuf = makeScalarBuf(); + break; + case ScalarTag::kUShort: + countBuf = makeScalarBuf(); + break; + case ScalarTag::kLong64: + countBuf = makeScalarBuf(); + break; + case ScalarTag::kULong64: + countBuf = makeScalarBuf(); + break; + default: + countBuf = makeScalarBuf(); + break; + } + // data buffer (allocate maxLen) + auto dataBuf = makeArrayBuf((size_t)std::max(1, maxLen)); + + inCount->SetAddress(countBuf->ptr()); + outCount->SetAddress(countBuf->ptr()); + inData->SetAddress(dataBuf->ptr()); + outData->SetAddress(dataBuf->ptr()); + + outCountBuf = std::move(countBuf); + return dataBuf; +} + +// ----------------- BC maps builder ----------------- +struct BCMaps { + std::vector originalBCs; + std::vector indexMap; + std::vector uniqueBCs; + std::unordered_map> newIndexOrigins; +}; + +static BCMaps buildBCMaps(TTree *treeBCs) { + BCMaps maps; + if (!treeBCs) + return maps; + TBranch *br = treeBCs->GetBranch("fGlobalBC"); + if (!br) { + std::cerr << "ERROR: no fGlobalBC\n"; + return maps; + } + ULong64_t v = 0; + br->SetAddress(&v); + Long64_t n = treeBCs->GetEntries(); + maps.originalBCs.reserve(n); + for (Long64_t i = 0; i < n; ++i) { + treeBCs->GetEntry(i); + maps.originalBCs.push_back(v); } - void copyValue() { - if (inBuf && outBuf) { - TLeaf *leaf = (TLeaf *)inBranch->GetListOfLeaves()->At(0); - size_t sz = leaf->GetLenType(); - memcpy(outBuf, inBuf, sz); + std::vector order(n); + std::iota(order.begin(), order.end(), 0); + std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { + return maps.originalBCs[a] < maps.originalBCs[b]; + }); + + maps.indexMap.assign(n, -1); + Int_t newIdx = -1; + ULong64_t prev = ULong64_t(-1); + for (auto oldIdx : order) { + ULong64_t val = maps.originalBCs[oldIdx]; + if (newIdx < 0 || val != prev) { + ++newIdx; + prev = val; + maps.uniqueBCs.push_back(val); } + maps.indexMap[oldIdx] = newIdx; + maps.newIndexOrigins[newIdx].push_back(oldIdx); } + std::cout << " BCMaps: oldEntries=" << n + << " unique=" << maps.uniqueBCs.size() << "\n"; + return maps; +} - ~BranchHandler() { deleteBuffer(); } +// ----------------- small helper used for BC/flags copy ----------------- +/* + copyTreeSimple: + - inTree: input tree (assumed POD-only of types Int_t, ULong64_t, UChar_t) + - entryMap: list of input-entry indices to use, in desired output order; + (size_t)-1 entries are skipped + - outName: name for the output tree +*/ +static TTree *copyTreeSimple(TTree *inTree, const std::vector &entryMap, + const char *outName = nullptr) { + if (!inTree) + return nullptr; + TString tname = outName ? outName : inTree->GetName(); + TTree *outTree = new TTree(tname, "rebuilt tree"); + + std::vector inBufs, outBufs; + std::vector inBranches; + std::vector types; + std::vector leafCodes; + std::vector bnames; + + for (auto brObj : *inTree->GetListOfBranches()) { + TBranch *br = (TBranch *)brObj; + TString bname = br->GetName(); + TLeaf *leaf = (TLeaf *)br->GetListOfLeaves()->At(0); + if (!leaf) + continue; + TString type = leaf->GetTypeName(); + + void *inBuf = nullptr; + void *outBuf = nullptr; + TString leafCode; - void deleteBuffer() { if (type == "Int_t") { - delete (Int_t *)inBuf; - delete (Int_t *)outBuf; + inBuf = new Int_t; + outBuf = new Int_t; + leafCode = "I"; } else if (type == "ULong64_t") { - delete (ULong64_t *)inBuf; - delete (ULong64_t *)outBuf; + inBuf = new ULong64_t; + outBuf = new ULong64_t; + leafCode = "l"; } else if (type == "UChar_t") { - delete (UChar_t *)inBuf; - delete (UChar_t *)outBuf; + inBuf = new UChar_t; + outBuf = new UChar_t; + leafCode = "b"; + } else { + std::cerr << "Unsupported branch type " << type << " in " + << inTree->GetName() << " branch " << bname << " — skipping\n"; + continue; } - inBuf = outBuf = nullptr; + + br->SetAddress(inBuf); + outTree->Branch(bname, outBuf, bname + "/" + leafCode); + + inBufs.push_back(inBuf); + outBufs.push_back(outBuf); + inBranches.push_back(br); + types.push_back(type); + leafCodes.push_back(leafCode); + bnames.push_back(bname); } -}; -/// Copy any TObject (tree or dir handled recursively) -void copyObject(TObject *obj, TDirectory *outDir) { - if (!obj || !outDir) - return; - outDir->cd(); - if (obj->InheritsFrom("TDirectory")) { - TDirectory *srcDir = (TDirectory *)obj; - std::cout << "📂 Copying directory: " << srcDir->GetName() << std::endl; - TDirectory *newDir = outDir->mkdir(srcDir->GetName()); - TIter nextKey(srcDir->GetListOfKeys()); - TKey *key; - while ((key = (TKey *)nextKey())) { - TObject *subObj = key->ReadObj(); - copyObject(subObj, newDir); + // fill using entryMap (representative input indices) + for (size_t idx : entryMap) { + if (idx == (size_t)-1) + continue; + inTree->GetEntry((Long64_t)idx); + for (size_t ib = 0; ib < inBranches.size(); ++ib) { + if (types[ib] == "Int_t") + *(Int_t *)outBufs[ib] = *(Int_t *)inBufs[ib]; + else if (types[ib] == "ULong64_t") + *(ULong64_t *)outBufs[ib] = *(ULong64_t *)inBufs[ib]; + else if (types[ib] == "UChar_t") + *(UChar_t *)outBufs[ib] = *(UChar_t *)inBufs[ib]; } - } else if (obj->InheritsFrom("TTree")) { - TTree *t = (TTree *)obj; - std::cout << " Copying untouched TTree: " << t->GetName() << std::endl; - TTree *tnew = t->CloneTree(-1, "fast"); - tnew->SetDirectory(outDir); - tnew->Write(); - } else { - std::cout << " Copying object: " << obj->GetName() << " [" - << obj->ClassName() << "]" << std::endl; - obj->Write(); + outTree->Fill(); } + + return outTree; } -/// Process one DF_* directory -void processDF(TDirectory *dirIn, TDirectory *dirOut) { - std::cout << "\n====================================================" - << std::endl; - std::cout << "▶ Processing DF folder: " << dirIn->GetName() << std::endl; +// ----------------- Rebuild BCs and Flags (refactored) ----------------- +void rebuildBCsAndFlags(TDirectory *dirIn, TDirectory *dirOut, TTree *&outBCs, + BCMaps &maps) { + std::cout << "------------------------------------------------\n"; + std::cout << "Rebuild BCs+flags in " << dirIn->GetName() << "\n"; + // find O2bc_* (pick first matching) and O2bcflag TTree *treeBCs = nullptr; TTree *treeFlags = nullptr; - std::vector treesWithBCid; - std::vector otherObjects; - - // Inspect folder contents - for (auto subkeyObj : *(dirIn->GetListOfKeys())) { - TKey *subkey = (TKey *)subkeyObj; - TObject *obj = dirIn->Get(subkey->GetName()); - if (obj->InheritsFrom(TTree::Class())) { - TTree *tree = (TTree *)obj; - TString tname = tree->GetName(); - if (tname.BeginsWith("O2bc_")) { - treeBCs = tree; - std::cout << " Found O2bc: " << tname << std::endl; - } else if (tname == "O2bcflag") { - // this is a special table as it is directly joinable to O2bc - // according to the datamodel - treeFlags = tree; - std::cout << " Found O2bcflag" << std::endl; - } else if (tree->GetBranch("fIndexBCs")) { - treesWithBCid.push_back(tree); - std::cout << " Needs reindex: " << tname << std::endl; - } else { - otherObjects.push_back(tree); - std::cout << " Unaffected TTree: " << tname << std::endl; - } - } else { - otherObjects.push_back(obj); + + for (auto keyObj : *dirIn->GetListOfKeys()) { + TKey *key = (TKey *)keyObj; + TObject *obj = dirIn->Get(key->GetName()); + if (!obj) + continue; + if (!obj->InheritsFrom(TTree::Class())) + continue; + TTree *t = (TTree *)obj; + if (isBCtree(t->GetName())) { + treeBCs = t; + } else if (isFlagsTree(t->GetName())) { + treeFlags = t; } } if (!treeBCs) { - std::cout << "⚠ No O2bc found in " << dirIn->GetName() - << " → just copying objects" << std::endl; - for (auto obj : otherObjects) { - copyObject(obj, dirOut); - } + std::cerr << " No BCs tree found in " << dirIn->GetName() + << " — skipping\n"; + outBCs = nullptr; return; } - // Read fGlobalBC values - ULong64_t fGlobalBC; - treeBCs->SetBranchAddress("fGlobalBC", &fGlobalBC); - std::vector originalBCs(treeBCs->GetEntries()); - for (Long64_t i = 0; i < treeBCs->GetEntries(); i++) { - treeBCs->GetEntry(i); - originalBCs[i] = fGlobalBC; + // build maps (dedupe/sort) + maps = buildBCMaps(treeBCs); + + // build representative entryMap: one input entry per new BC index (use first + // contributor) + std::vector entryMap(maps.uniqueBCs.size(), (size_t)-1); + for (size_t newIdx = 0; newIdx < maps.uniqueBCs.size(); ++newIdx) { + const auto &vec = maps.newIndexOrigins.at(newIdx); + if (!vec.empty()) + entryMap[newIdx] = vec.front(); } - std::cout << " O2bc entries: " << originalBCs.size() << std::endl; + dirOut->cd(); + // copy BCs tree using representative entries + outBCs = copyTreeSimple(treeBCs, entryMap, treeBCs->GetName()); + if (outBCs) { + outBCs->SetDirectory(dirOut); + outBCs->Write(); + std::cout << " Wrote " << outBCs->GetName() << " with " + << outBCs->GetEntries() << " entries\n"; + } - // Build mapping - std::vector indexMap(originalBCs.size(), -1); - std::vector uniqueBCs; - std::vector order(originalBCs.size()); - std::iota(order.begin(), order.end(), 0); - std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { - return originalBCs[a] < originalBCs[b]; - }); - Int_t newIdx = -1; - ULong64_t prevVal = -1; - std::unordered_map> newIndexOrigins; - for (auto oldIdx : order) { - ULong64_t val = originalBCs[oldIdx]; - if (newIdx < 0 || val != prevVal) { - ++newIdx; - prevVal = val; - uniqueBCs.push_back(val); + // copy flags if present + if (treeFlags) { + TTree *outFlags = copyTreeSimple(treeFlags, entryMap, treeFlags->GetName()); + if (outFlags) { + outFlags->SetDirectory(dirOut); + outFlags->Write(); + std::cout << " Wrote " << outFlags->GetName() << " with " + << outFlags->GetEntries() << " entries\n"; } - indexMap[oldIdx] = newIdx; - newIndexOrigins[newIdx].push_back(oldIdx); } - std::cout << " Unique BCs after deduplication: " << uniqueBCs.size() - << std::endl; +} - // --- Rewrite O2bc --- - dirOut->cd(); - TTree *treeBCsOut = new TTree(treeBCs->GetName(), "fixed O2bc tree"); - std::vector> bcBranches; - for (auto brObj : *treeBCs->GetListOfBranches()) { - TBranch *br = (TBranch *)brObj; - if (TString(br->GetName()) == "fGlobalBC") +// ----------------- payload rewriting with VLA support ----------------- +struct SortKey { + Long64_t entry; + Long64_t newBC; +}; + +static bool isVLA(TBranch *br) { + if (!br) + return false; + TLeaf *leaf = (TLeaf *)br->GetListOfLeaves()->At(0); + return leaf && leaf->GetLeafCount(); +} + +// This is the VLA-aware rewritePayloadSorted implementation (keeps previous +// tested behavior) +static void rewritePayloadSorted(TDirectory *dirIn, TDirectory *dirOut, + const BCMaps &maps) { + std::unordered_set skipNames; // for count branches + TIter it(dirIn->GetListOfKeys()); + while (TKey *k = (TKey *)it()) { + if (TString(k->GetClassName()) != "TTree") continue; - bcBranches.emplace_back(std::make_unique(br, treeBCsOut)); - } - ULong64_t outBC; - treeBCsOut->Branch("fGlobalBC", &outBC, "fGlobalBC/l"); + std::unique_ptr holder(k->ReadObj()); // keep alive + TTree *src = dynamic_cast(holder.get()); + if (!src) + continue; + const char *tname = src->GetName(); - for (int newIdx = 0; newIdx < uniqueBCs.size(); newIdx++) { - auto &oldIndices = newIndexOrigins[newIdx]; - if (oldIndices.empty()) + if (isBCtree(tname) || isFlagsTree(tname)) { + std::cout << " skipping BC/flag tree " << tname << "\n"; continue; - size_t oldIdx = oldIndices.front(); - treeBCs->GetEntry(oldIdx); - outBC = originalBCs[oldIdx]; - for (auto &bh : bcBranches) { - bh->copyValue(); } - treeBCsOut->Fill(); - } - std::cout << " Wrote O2bc with " << treeBCsOut->GetEntries() << " entries" - << std::endl; - treeBCsOut->Write(); - // --- Rewrite O2bcflag --- - if (treeFlags) { - std::cout << " Rebuilding O2bcflag..." << std::endl; + const char *idxName = findIndexBranchName(src); + if (!idxName) { + dirOut->cd(); + std::cout << " [copy] " << tname << " (no index) -> cloning\n"; + TTree *c = src->CloneTree(-1, "fast"); + c->SetDirectory(dirOut); + c->Write(); + continue; + } + + std::cout << " [proc] reindex+SORT " << tname << " (index=" << idxName + << ")\n"; + // detect index type and bind input buffer + TBranch *inIdxBr = src->GetBranch(idxName); + if (!inIdxBr) { + std::cerr << " ERR no index branch found\n"; + continue; + } + TLeaf *idxLeaf = (TLeaf *)inIdxBr->GetListOfLeaves()->At(0); + TString idxType = idxLeaf->GetTypeName(); + + enum class IdKind { kI, kUi, kS, kUs, kUnknown }; + IdKind idk = IdKind::kUnknown; + Int_t oldI = 0, newI = 0; + UInt_t oldUi = 0, newUi = 0; + Short_t oldS = 0, newS = 0; + UShort_t oldUs = 0, newUs = 0; + + if (idxType == "Int_t") { + idk = IdKind::kI; + inIdxBr->SetAddress(&oldI); + } else if (idxType == "UInt_t") { + idk = IdKind::kUi; + inIdxBr->SetAddress(&oldUi); + } else if (idxType == "Short_t") { + idk = IdKind::kS; + inIdxBr->SetAddress(&oldS); + } else if (idxType == "UShort_t") { + idk = IdKind::kUs; + inIdxBr->SetAddress(&oldUs); + } else { + std::cerr << " unsupported index type " << idxType + << " -> cloning as-is\n"; + dirOut->cd(); + auto *c = src->CloneTree(-1, "fast"); + c->SetDirectory(dirOut); + c->Write(); + continue; + } + + // build keys vector + Long64_t nEnt = src->GetEntries(); + std::vector keys; + keys.reserve(nEnt); + for (Long64_t i = 0; i < nEnt; ++i) { + inIdxBr->GetEntry(i); + Long64_t oldIdx = 0; + switch (idk) { + case IdKind::kI: + oldIdx = oldI; + break; + case IdKind::kUi: + oldIdx = oldUi; + break; + case IdKind::kS: + oldIdx = oldS; + break; + case IdKind::kUs: + oldIdx = oldUs; + break; + default: + break; + } + Long64_t newBC = -1; + if (oldIdx >= 0 && (size_t)oldIdx < maps.indexMap.size()) + newBC = maps.indexMap[(size_t)oldIdx]; + keys.push_back({i, newBC}); + } + + std::stable_sort(keys.begin(), keys.end(), + [](const SortKey &a, const SortKey &b) { + bool ai = (a.newBC < 0), bi = (b.newBC < 0); + if (ai != bi) + return !ai && bi; // valid first + if (a.newBC != b.newBC) + return a.newBC < b.newBC; + return a.entry < b.entry; + }); + + // prepare output tree dirOut->cd(); - TTree *treeFlagsOut = treeFlags->CloneTree(0); - std::vector> flagBranches; - for (auto brObj : *treeFlags->GetListOfBranches()) { - TBranch *br = (TBranch *)brObj; - flagBranches.emplace_back( - std::make_unique(br, treeFlagsOut)); + TTree *out = src->CloneTree(0, "fast"); + // map branches + std::unordered_map inBranches, outBranches; + for (auto *bobj : *src->GetListOfBranches()) + inBranches[((TBranch *)bobj)->GetName()] = (TBranch *)bobj; + for (auto *bobj : *out->GetListOfBranches()) + outBranches[((TBranch *)bobj)->GetName()] = (TBranch *)bobj; + + // allocate buffers and bind: scalars & VLAs + std::vector> scalarBuffers; // shared in/out + std::vector> vlaDataBuffers; + std::vector> vlaCountBuffers; + std::vector vlaMaxLens; + std::vector vlaCountTags; + // bind index branch in output to new variable + TBranch *outIdxBr = out->GetBranch(idxName); + switch (idk) { + case IdKind::kI: + outIdxBr->SetAddress(&newI); + break; + case IdKind::kUi: + outIdxBr->SetAddress(&newUi); + break; + case IdKind::kS: + outIdxBr->SetAddress(&newS); + break; + case IdKind::kUs: + outIdxBr->SetAddress(&newUs); + break; + default: + break; } - for (int newIdx = 0; newIdx < uniqueBCs.size(); newIdx++) { - auto &oldIndices = newIndexOrigins[newIdx]; - if (oldIndices.empty()) + skipNames.clear(); + skipNames.insert(idxName); + + // loop inBranches and bind + for (auto &kv : inBranches) { + const std::string bname = kv.first; + if (skipNames.count(bname)) + continue; + TBranch *inBr = kv.second; + TBranch *ouBr = outBranches.count(bname) ? outBranches[bname] : nullptr; + if (!ouBr) { + std::cerr << " [warn] no out branch for " << bname << " -> skip\n"; continue; - size_t oldIdx = oldIndices.front(); - treeFlags->GetEntry(oldIdx); - for (auto &fh : flagBranches) { - fh->copyValue(); } - treeFlagsOut->Fill(); + TLeaf *leaf = (TLeaf *)inBr->GetListOfLeaves()->At(0); + if (!leaf) { + std::cerr << " [warn] branch w/o leaf " << bname << "\n"; + continue; + } + + if (!isVLA(inBr)) { + // scalar + ScalarTag tag = leafType(leaf); + if (tag == ScalarTag::kUnknown) { + std::cerr << " [warn] unknown scalar type " + << leaf->GetTypeName() << " for " << bname << "\n"; + continue; + } + auto sb = bindScalarBranch(inBr, ouBr, tag); + if (sb) + scalarBuffers.emplace_back(std::move(sb)); + } else { + // VLA -> find count leaf & branch + TLeaf *cntLeaf = leaf->GetLeafCount(); + if (!cntLeaf) { + std::cerr << " [warn] VLA " << bname + << " has no count leaf -> skip\n"; + continue; + } + TBranch *inCnt = cntLeaf->GetBranch(); + TBranch *outCnt = outBranches.count(inCnt->GetName()) + ? outBranches[inCnt->GetName()] + : nullptr; + if (!outCnt) { + std::cerr << " [warn] missing out count branch " + << inCnt->GetName() << " for VLA " << bname << "\n"; + continue; + } + // avoid double-binding count branch as scalar later + skipNames.insert(inCnt->GetName()); + // detect tags + ScalarTag dataTag = leafType(leaf); + ScalarTag cntTag = leafType(cntLeaf); + if (dataTag == ScalarTag::kUnknown || cntTag == ScalarTag::kUnknown) { + std::cerr << " [warn] unsupported VLA types for " << bname + << "\n"; + continue; + } + // prescan max len + Long64_t maxLen = prescanMaxLen(src, inCnt, cntTag); + if (maxLen <= 0) + maxLen = leaf->GetMaximum(); + if (maxLen <= 0) + maxLen = 1; + // bind typed + std::unique_ptr countBufLocal; + std::unique_ptr dataBufLocal; + switch (dataTag) { + case ScalarTag::kInt: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kUInt: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kShort: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kUShort: + dataBufLocal = bindArrayTyped( + inBr, ouBr, inCnt, outCnt, cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kLong64: + dataBufLocal = bindArrayTyped( + inBr, ouBr, inCnt, outCnt, cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kULong64: + dataBufLocal = bindArrayTyped( + inBr, ouBr, inCnt, outCnt, cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kFloat: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kDouble: + dataBufLocal = bindArrayTyped( + inBr, ouBr, inCnt, outCnt, cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kChar: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + case ScalarTag::kUChar: + dataBufLocal = bindArrayTyped(inBr, ouBr, inCnt, outCnt, + cntTag, maxLen, countBufLocal); + break; + default: + break; + } + if (dataBufLocal) + vlaDataBuffers.emplace_back(std::move(dataBufLocal)); + if (countBufLocal) { + vlaCountBuffers.emplace_back(std::move(countBufLocal)); + vlaMaxLens.push_back(maxLen); + vlaCountTags.push_back(cntTag); + } + } + } // end for branches + + // Now fill out in sorted order. For each key: src->GetEntry(entry) -> clamp + // counts -> set new index -> out->Fill() + Long64_t changed = 0; + for (const auto &sk : keys) { + src->GetEntry(sk.entry); + + // clamp count buffers before fill + for (size_t ic = 0; ic < vlaCountBuffers.size(); ++ic) { + void *p = vlaCountBuffers[ic]->ptr(); + Long64_t cnt = 0; + switch (vlaCountTags[ic]) { + case ScalarTag::kInt: + cnt = *(Int_t *)p; + break; + case ScalarTag::kUInt: + cnt = *(UInt_t *)p; + break; + case ScalarTag::kShort: + cnt = *(Short_t *)p; + break; + case ScalarTag::kUShort: + cnt = *(UShort_t *)p; + break; + case ScalarTag::kLong64: + cnt = *(Long64_t *)p; + break; + case ScalarTag::kULong64: + cnt = *(ULong64_t *)p; + break; + default: + cnt = *(Int_t *)p; + break; + } + if (cnt < 0) + cnt = 0; + if (cnt > vlaMaxLens[ic]) { + std::cerr << "WARNING: clamping VLA count " << cnt << " to max " + << vlaMaxLens[ic] << " for tree " << tname << "\n"; + // write back + if (vlaMaxLens[ic] <= std::numeric_limits::max()) { + *(Int_t *)p = (Int_t)vlaMaxLens[ic]; + } else { + *(Long64_t *)p = (Long64_t)vlaMaxLens[ic]; + } + } + } + + // set new index value in out buffer + switch (idk) { + case IdKind::kI: { + Int_t prev = oldI; + newI = (sk.newBC >= 0 ? (Int_t)sk.newBC : -1); + if (newI != prev) + ++changed; + } break; + case IdKind::kUi: { + UInt_t prev = oldUi; + newUi = (sk.newBC >= 0 ? (UInt_t)sk.newBC : 0u); + if (newUi != prev) + ++changed; + } break; + case IdKind::kS: { + Short_t prev = oldS; + newS = (sk.newBC >= 0 ? (Short_t)sk.newBC : (Short_t)-1); + if (newS != prev) + ++changed; + } break; + case IdKind::kUs: { + UShort_t prev = oldUs; + newUs = (sk.newBC >= 0 ? (UShort_t)sk.newBC : (UShort_t)0); + if (newUs != prev) + ++changed; + } break; + default: + break; + } + + out->Fill(); } - std::cout << " Wrote O2bcflag with " << treeFlagsOut->GetEntries() - << " entries" << std::endl; - treeFlagsOut->Write(); - } - // --- Rewrite trees with fIndexBCs --- - for (auto tree : treesWithBCid) { - std::cout << " Reindexing tree " << tree->GetName() << std::endl; + std::cout << " wrote " << out->GetEntries() << " rows; remapped " + << changed << " index values; sorted\n"; + out->Write(); + } // end while keys in dir + + // non-tree objects: copy as-is (but for TMap use WriteTObject to preserve + // class) + it.Reset(); + while (TKey *k = (TKey *)it()) { + if (TString(k->GetClassName()) == "TTree") + continue; + TObject *obj = k->ReadObj(); dirOut->cd(); - TTree *treeOut = tree->CloneTree(0); - Int_t oldBCid, newBCid; - tree->SetBranchAddress("fIndexBCs", &oldBCid); - treeOut->SetBranchAddress("fIndexBCs", &newBCid); - for (Long64_t i = 0; i < tree->GetEntries(); i++) { - tree->GetEntry(i); - newBCid = indexMap[oldBCid]; - treeOut->Fill(); + if (obj->IsA()->InheritsFrom(TMap::Class())) { + std::cout << " Copying TMap " << k->GetName() << " as a whole\n"; + dirOut->WriteTObject(obj, k->GetName(), "Overwrite"); + } else { + obj->Write(k->GetName(), TObject::kOverwrite); } - std::cout << " Wrote " << treeOut->GetEntries() << " entries" - << std::endl; - treeOut->Write(); } +} - // Copy unaffected objects - for (auto obj : otherObjects) { - copyObject(obj, dirOut); +// ----------------- per-DF driver ----------------- +static void processDF(TDirectory *dIn, TDirectory *dOut) { + std::cout << "------------------------------------------------\n"; + std::cout << "Processing DF: " << dIn->GetName() << "\n"; + + // 1) rebuild BCs & flags -> maps + TTree *bcOut = nullptr; + BCMaps maps; + rebuildBCsAndFlags(dIn, dOut, bcOut, maps); + + if (!bcOut) { + std::cout << " No BCs -> deep copying directory\n"; + TIter it(dIn->GetListOfKeys()); + while (TKey *k = (TKey *)it()) { + TObject *obj = k->ReadObj(); + dOut->cd(); + if (obj->InheritsFrom(TTree::Class())) { + TTree *t = (TTree *)obj; + TTree *c = t->CloneTree(-1, "fast"); + c->SetDirectory(dOut); + c->Write(); + } else { + if (obj->IsA()->InheritsFrom(TMap::Class())) { + dOut->WriteTObject(obj, k->GetName(), "Overwrite"); + } else { + obj->Write(k->GetName(), TObject::kOverwrite); + } + } + } + return; } + + // 2) rewrite payload tables (reindex+sort) + rewritePayloadSorted(dIn, dOut, maps); + + std::cout << "Finished DF: " << dIn->GetName() << "\n"; } -void AODBcRewriter(const char *inFileName = "input.root", - const char *outFileName = "output.root") { - TFile *fin = TFile::Open(inFileName, "READ"); - TFile *fout = TFile::Open(outFileName, "RECREATE"); - fout->SetCompressionSettings(fin->GetCompressionSettings()); - for (auto keyObj : *(fin->GetListOfKeys())) { - TKey *key = (TKey *)keyObj; +// ----------------- top-level driver ----------------- +void AODBcRewriter(const char *inFileName = "AO2D.root", + const char *outFileName = "AO2D_rewritten.root") { + std::cout << "Opening input file: " << inFileName << "\n"; + std::unique_ptr fin(TFile::Open(inFileName, "READ")); + if (!fin || fin->IsZombie()) { + std::cerr << "ERROR opening input\n"; + return; + } + + int algo = fin->GetCompressionAlgorithm(); + int lvl = fin->GetCompressionLevel(); + std::cout << "Input compression: algo=" << algo << " level=" << lvl << "\n"; + + // create output applying same compression level when available +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 30, 0) + std::unique_ptr fout(TFile::Open(outFileName, "RECREATE", "", lvl)); +#else + std::unique_ptr fout(TFile::Open(outFileName, "RECREATE")); +#endif + if (!fout || fout->IsZombie()) { + std::cerr << "ERROR creating output\n"; + return; + } + fout->SetCompressionAlgorithm(algo); + fout->SetCompressionLevel(lvl); + + // top-level keys + TIter top(fin->GetListOfKeys()); + while (TKey *key = (TKey *)top()) { + TString name = key->GetName(); TObject *obj = key->ReadObj(); - if (obj->InheritsFrom(TDirectory::Class()) && - TString(key->GetName()).BeginsWith("DF_")) - processDF((TDirectory *)obj, fout->mkdir(key->GetName())); - else { + if (obj->InheritsFrom(TDirectory::Class()) && isDF(name)) { + std::cout << "Found DF folder: " << name << "\n"; + TDirectory *din = (TDirectory *)obj; + TDirectory *dout = fout->mkdir(name); + processDF(din, dout); + } else { fout->cd(); - copyObject(obj, fout); + if (obj->IsA()->InheritsFrom(TMap::Class())) { + std::cout << "Copying top-level TMap: " << name << "\n"; + fout->WriteTObject(obj, name, "Overwrite"); + } else { + std::cout << "Copying top-level object: " << name << " [" + << obj->ClassName() << "]\n"; + obj->Write(name, TObject::kOverwrite); + } } } + + fout->Write("", TObject::kOverwrite); fout->Close(); fin->Close(); + std::cout << "All done. Output written to " << outFileName << "\n"; } diff --git a/MC/utils/AOD_check_globalBC_monotonic.C b/MC/utils/AOD_check_globalBC_monotonic.C new file mode 100644 index 000000000..b92472d5e --- /dev/null +++ b/MC/utils/AOD_check_globalBC_monotonic.C @@ -0,0 +1,141 @@ +// Scans all DF_* folders for O2bc_001 TTrees and checks that the +// fGlobalBC branch (ULong64_t) is monotonically non-decreasing. + +#ifndef __CLING__ +#include "TBranch.h" +#include "TDirectory.h" +#include "TFile.h" +#include "TKey.h" +#include "TLeaf.h" +#include "TTree.h" +#include +#include +#include +#endif + +struct BCReport { + bool hasBranch = false; + bool monotonic = true; + Long64_t entries = 0; + Long64_t firstViolationEntry = -1; + Long64_t nViolations = 0; + ULong64_t maxBackwardJump = 0; + std::vector>> samples; +}; + +static BCReport checkO2bcTree(TTree *t) { + BCReport r; + if (!t) + return r; + + TBranch *br = t->GetBranch("fGlobalBC"); + if (!br) + return r; + r.hasBranch = true; + + ULong64_t buf = 0; + br->SetAddress(&buf); + + r.entries = t->GetEntries(); + if (r.entries <= 1) + return r; + + bool havePrev = false; + ULong64_t prevVal = 0; + + for (Long64_t i = 0; i < r.entries; ++i) { + t->GetEntry(i); + ULong64_t v = buf; + + if (!havePrev) { + prevVal = v; + havePrev = true; + continue; + } + + if (v < prevVal) { + if (r.firstViolationEntry < 0) + r.firstViolationEntry = i; + ++r.nViolations; + r.monotonic = false; + ULong64_t jump = prevVal - v; + if (jump > r.maxBackwardJump) + r.maxBackwardJump = jump; + if (r.samples.size() < 5) + r.samples.push_back({i, {prevVal, v}}); + } + + prevVal = v; + } + + return r; +} + +void AOD_check_globalBC_monotonic(const char *inFileName = "AO2D.root") { + std::string inFileStr(inFileName); + if (inFileStr.find("alien:") != std::string::npos) { + TGrid::Connect("alien"); + } + + std::cout << "Opening file: " << inFileName << std::endl; + TFile *f = TFile::Open(inFileName, "READ"); + if (!f || f->IsZombie()) { + std::cerr << "ERROR: cannot open input file.\n"; + return; + } + + Long64_t totalTrees = 0; + Long64_t totalWithBranch = 0; + Long64_t totalViolations = 0; + + TIter topKeys(f->GetListOfKeys()); + while (TKey *k = (TKey *)topKeys()) { + TObject *obj = k->ReadObj(); + if (!obj->InheritsFrom(TDirectory::Class())) + continue; + + TDirectory *dir = (TDirectory *)obj; + TString dname = dir->GetName(); + if (!dname.BeginsWith("DF_")) + continue; + + TTree *t = (TTree *)dir->Get("O2bc_001"); + if (!t) + continue; + ++totalTrees; + + BCReport r = checkO2bcTree(t); + + if (!r.hasBranch) { + std::cout << "[skip] " << dir->GetName() + << "/O2bc_001 has no fGlobalBC\n"; + continue; + } + ++totalWithBranch; + + if (r.monotonic) { + std::cout << "[ OK ] " << dir->GetName() << "/O2bc_001 — " << r.entries + << " entries, monotonic\n"; + } else { + ++totalViolations; + std::cout << "[BAD] " << dir->GetName() << "/O2bc_001 — " << r.entries + << " entries, first violation at entry " + << r.firstViolationEntry + << ", total violations: " << r.nViolations + << ", max backward jump: " << r.maxBackwardJump << "\n"; + + for (auto &s : r.samples) { + std::cout << " entry " << s.first << ": " << s.second.first + << " -> " << s.second.second << "\n"; + } + } + } + + std::cout << "\n==================== SUMMARY ====================\n"; + std::cout << "O2bc_001 trees checked: " << totalTrees << "\n"; + std::cout << "With fGlobalBC branch: " << totalWithBranch << "\n"; + std::cout << "Trees NOT monotonic: " << totalViolations << "\n"; + std::cout << "=================================================\n"; + + f->Close(); +} diff --git a/MC/utils/AOD_detect_unsorted_fIndexBCs.C b/MC/utils/AOD_detect_unsorted_fIndexBCs.C new file mode 100644 index 000000000..13571ac85 --- /dev/null +++ b/MC/utils/AOD_detect_unsorted_fIndexBCs.C @@ -0,0 +1,260 @@ +// Scans DF_* folders, finds trees with an Int-like "fIndexBCs" branch, +// and reports those where fIndexBCs is not monotonically non-decreasing. +// Negative values (e.g. -1) are ignored for the monotonicity check. + +#ifndef __CLING__ +#include "TBranch.h" +#include "TDirectory.h" +#include "TFile.h" +#include "TKey.h" +#include "TLeaf.h" +#include "TString.h" +#include "TTree.h" +#include +#include +#include +#include +#include +#endif + +struct MonotonicReport { + bool hasBranch = false; // tree has fIndexBCs + bool monotonic = true; // true if non-decreasing + Long64_t entries = 0; + Long64_t firstViolationEntry = -1; // entry index of first backward step + Long64_t nViolations = 0; // count of backward steps + Long64_t maxBackwardJump = 0; // biggest (prevValid - curr) observed + std::vector>> + samples; // (entry, (prev, curr)) +}; + +/// Try to bind a branch named "fIndexBCs" with an integral buffer. +/// Supports common integer POD types (Int_t, UInt_t, Long64_t, ULong64_t). +/// Returns: pointer to bound buffer as int64_t-compatible view (value is +/// copied), and sets branch address appropriately. +class FIndexBinder { +public: + TBranch *br = nullptr; + std::string type; + // One of these will be used based on the branch's leaf type: + Int_t buf_i = 0; + UInt_t buf_ui = 0; + Long64_t buf_l = 0; + ULong64_t buf_ul = 0; + + // which is active + enum Kind { KNone, KInt, KUInt, KLong64, KULong64 } kind = KNone; + + bool bind(TTree *t, const char *name = "fIndexBCs") { + br = t->GetBranch(name); + if (!br) + return false; + if (br->GetListOfLeaves()->GetEntries() <= 0) + return false; + TLeaf *leaf = (TLeaf *)br->GetListOfLeaves()->At(0); + type = leaf->GetTypeName(); + + if (type == "Int_t") { + kind = KInt; + br->SetAddress(&buf_i); + } else if (type == "UInt_t") { + kind = KUInt; + br->SetAddress(&buf_ui); + } else if (type == "Long64_t") { + kind = KLong64; + br->SetAddress(&buf_l); + } else if (type == "ULong64_t") { + kind = KULong64; + br->SetAddress(&buf_ul); + } else { + // not an integer POD we handle + kind = KNone; + br = nullptr; + return false; + } + return true; + } + + // Read the current value as signed 64-bit (for comparisons). + // For unsigned, cast safely to signed domain if within range; otherwise + // clamp. + Long64_t valueAsI64() const { + switch (kind) { + case KInt: + return (Long64_t)buf_i; + case KUInt: + return (buf_ui <= (UInt_t)std::numeric_limits::max() + ? (Long64_t)buf_ui + : (Long64_t)std::numeric_limits::max()); + case KLong64: + return buf_l; + case KULong64: + return (buf_ul <= (ULong64_t)std::numeric_limits::max() + ? (Long64_t)buf_ul + : (Long64_t)std::numeric_limits::max()); + default: + return 0; + } + } +}; + +static MonotonicReport checkTreeMonotonic(TTree *t, bool verbose = false) { + MonotonicReport r; + if (!t) + return r; + + // Speed up: only read the target branch + t->SetBranchStatus("*", 0); + if (t->GetBranch("fIndexBCs")) + t->SetBranchStatus("fIndexBCs", 1); + + FIndexBinder binder; + if (!binder.bind(t, "fIndexBCs")) { + r.hasBranch = false; + // Re-enable all for safety if user continues using the tree later + t->SetBranchStatus("*", 1); + return r; + } + r.hasBranch = true; + + r.entries = t->GetEntries(); + if (r.entries <= 1) { + r.monotonic = true; + t->SetBranchStatus("*", 1); + return r; + } + + bool havePrev = false; + Long64_t prevValid = 0; + + for (Long64_t i = 0; i < r.entries; ++i) { + t->GetEntry(i); + Long64_t v = binder.valueAsI64(); + + // Ignore negatives (e.g. -1 sentinel values) + if (v < 0) + continue; + + if (!havePrev) { + prevValid = v; + havePrev = true; + continue; + } + + if (v < prevValid) { + // backward step + if (r.firstViolationEntry < 0) + r.firstViolationEntry = i; + ++r.nViolations; + r.monotonic = false; + Long64_t jump = prevValid - v; + if (jump > r.maxBackwardJump) + r.maxBackwardJump = jump; + if (r.samples.size() < 5) + r.samples.push_back({i, {prevValid, v}}); + // Do not update prevValid here; we keep comparing to last valid + // non-decreasing reference + continue; + } + + // normal non-decreasing step + prevValid = v; + } + + // Restore statuses + t->SetBranchStatus("*", 1); + return r; +} + +void AOD_detect_unsorted_fIndexBCs(const char *inFileName = "AO2D.root", + bool verbosePerTree = false) { + std::cout << "Opening file: " << inFileName << std::endl; + TFile *f = TFile::Open(inFileName, "READ"); + if (!f || f->IsZombie()) { + std::cerr << "ERROR: cannot open input file.\n"; + return; + } + + Long64_t totalTreesChecked = 0; + Long64_t totalWithBranch = 0; + Long64_t totalViolations = 0; + + std::cout << "Scanning top-level for DF_* folders...\n"; + + // Iterate top-level keys + TIter kIt(f->GetListOfKeys()); + while (TKey *k = (TKey *)kIt()) { + TString kname = k->GetName(); + TObject *obj = k->ReadObj(); + + if (!obj->InheritsFrom(TDirectory::Class()) || !kname.BeginsWith("DF_")) { + continue; + } + + auto *dir = (TDirectory *)obj; + std::cout << "\n====================================================\n"; + std::cout << "DF folder: " << dir->GetName() << "\n"; + + // Iterate all keys in this DF directory + TIter dIt(dir->GetListOfKeys()); + while (TKey *dk = (TKey *)dIt()) { + TObject *tobj = dir->Get(dk->GetName()); + if (!tobj->InheritsFrom(TTree::Class())) { + continue; // only trees are relevant + } + + TTree *t = (TTree *)tobj; + ++totalTreesChecked; + + // Only consider trees that *have* fIndexBCs + if (!t->GetBranch("fIndexBCs")) { + if (verbosePerTree) { + std::cout << " [skip] " << t->GetName() << " (no fIndexBCs)\n"; + } + continue; + } + ++totalWithBranch; + + MonotonicReport r = checkTreeMonotonic(t, verbosePerTree); + + if (!r.hasBranch) { + // Shouldn't happen due to prior check, but keep robust + if (verbosePerTree) { + std::cout << " [skip] " << t->GetName() + << " (failed to bind branch)\n"; + } + continue; + } + + if (r.monotonic) { + if (verbosePerTree) { + std::cout << " [ OK ] " << t->GetName() + << " — entries: " << r.entries << " (non-decreasing)\n"; + } + } else { + ++totalViolations; + std::cout << " [BAD] " << t->GetName() << " — entries: " << r.entries + << ", first violation at entry " << r.firstViolationEntry + << ", total backward steps: " << r.nViolations + << ", max backward jump: " << r.maxBackwardJump << "\n"; + + // Print a few examples of (prev, curr) causing violation + if (!r.samples.empty()) { + std::cout << " sample backward steps (entry: prev -> curr):\n"; + for (auto &s : r.samples) { + std::cout << " " << s.first << ": " << s.second.first + << " -> " << s.second.second << "\n"; + } + } + } + } + } + + std::cout << "\n==================== SUMMARY ====================\n"; + std::cout << "Trees visited: " << totalTreesChecked << "\n"; + std::cout << "Trees with fIndexBCs: " << totalWithBranch << "\n"; + std::cout << "Trees NOT monotonic: " << totalViolations << "\n"; + std::cout << "=================================================\n"; + + f->Close(); +}