From 2672daebbd0bba974288dcadf9f14bc7cc1e8832 Mon Sep 17 00:00:00 2001 From: Vincenzo Eduardo Padulano Date: Mon, 22 Jun 2026 10:30:22 +0200 Subject: [PATCH 1/3] [tree] Add tests for GetMinimum,GetMaximum with friends This commit introduces testing for GetMinimum,GetMaximum in case the requested column belongs to a friend tree. Two tests are taken directly from the reproducer reported by a user at https://root-forum.cern.ch/t/ttree-getminimum-getmaximum-only-scan-one-file-of-a-friend-tchain/64905 A third test exercises in particular the correct updating of the branch addresses of the friend TChain when it switches to another file even though the main TChain is still traversing the same file. --- tree/tree/test/TTreeRegressions.cxx | 148 ++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tree/tree/test/TTreeRegressions.cxx b/tree/tree/test/TTreeRegressions.cxx index 5aba6fe4cddf8..46dceb194c568 100644 --- a/tree/tree/test/TTreeRegressions.cxx +++ b/tree/tree/test/TTreeRegressions.cxx @@ -1,5 +1,6 @@ #include "TMemFile.h" #include "TLeaf.h" +#include "TChain.h" #include "TTree.h" #include "TInterpreter.h" #include "TSystem.h" @@ -9,7 +10,9 @@ #include "gtest/gtest.h" +#include #include +#include // ROOT-10702 TEST(TTreeRegressions, CompositeTypeWithNameClash) @@ -322,3 +325,148 @@ TEST(TTreeRegressions, DrawAutoBinning) delete h; delete gROOT->FindObject("c1"); } + +// Regression for https://github.com/root-project/root/issues/22652 +struct RegressionGH22652 : public ::testing::Test { + + constexpr static auto fMainTreeName{"main"}; + constexpr static auto fFriendTreeName{"friend"}; + constexpr static auto fMainTreeFileName{"main_global.root"}; + constexpr static auto fNFiles{3}; + constexpr static auto fNEntriesPerFile{100}; + // file 0: w in [4,5) file 1: w in [0,1) file 2: w in [9,10) + constexpr static std::array fLowerBounds{4.0, 0.0, 9.0}; + constexpr static std::array fMainChainFileNames{"main_1.root", "main_2.root", "main_3.root"}; + constexpr static std::array fFriendChainFileNames{"friend_1.root", "friend_2.root", + "friend_3.root"}; + + constexpr static auto fNShortFiles{6}; + constexpr static auto fNEntriesPerShortFile{50}; + constexpr static std::array fShortFriendChainFileNames{ + "short_friend_1.root", "short_friend_2.root", "short_friend_3.root", + "short_friend_4.root", "short_friend_5.root", "short_friend_6.root"}; + // We set the minimum value in the second file to check that the branch address is updated by TChain::GetMinimum + constexpr static std::array fShortFriendChainValues{10, 0, 30, 40, 50, 60}; + + static void SetUpTestSuite() + { + { + // Main TTree with cumulated number of entries + auto fd = std::make_unique(fMainTreeFileName, "RECREATE"); + auto td = std::make_unique(fMainTreeName, fMainTreeName); + double x{}; + td->Branch("x", &x); + for (const auto &_ : fMainChainFileNames) + for (int i = 0; i < fNEntriesPerFile; ++i) + td->Fill(); + fd->Write(); + } + + for (int i = 0; i < fNFiles; i++) { + // Trees for the main chain + { + auto fd = std::make_unique(fMainChainFileNames[i], "RECREATE"); + auto td = std::make_unique(fMainTreeName, fMainTreeName); + double x{}; + td->Branch("x", &x); + for (int j = 0; j < fNEntriesPerFile; j++) { + // x in [-0.5, 0.495] + x = j * 0.01 - 0.5; + td->Fill(); + } + fd->Write(); + } + + // Trees for the friend chain + { + auto ff = std::make_unique(fFriendChainFileNames[i], "RECREATE"); + auto tf = std::make_unique(fFriendTreeName, fFriendTreeName); + double w{}; + tf->Branch("w", &w); + for (int j = 0; j < fNEntriesPerFile; j++) { + // w in [fLowerBounds[i], fLowerBounds[i] + 1) + w = fLowerBounds[i] + j * (1.0 / fNEntriesPerFile); + tf->Fill(); + } + ff->Write(); + } + } + + // A second friend chain with the total number of entries but twice as many files (half of the entries per file) + for (auto i = 0; i < fNShortFiles; i++) { + auto fd = std::make_unique(fShortFriendChainFileNames[i], "RECREATE"); + auto td = std::make_unique(fFriendTreeName, fFriendTreeName); + double w{}; + td->Branch("w", &w); + for (int j = 0; j < fNEntriesPerShortFile; j++) { + w = fShortFriendChainValues[i]; + td->Fill(); + } + fd->Write(); + } + } + + static void TearDownTestSuite() + { + for (const auto &f : fMainChainFileNames) + std::remove(f); + + for (const auto &f : fFriendChainFileNames) + std::remove(f); + } +}; + +TEST_F(RegressionGH22652, RunMainTChain) +{ + // Main is a TChain, friend is a TChain, entries are aligned + auto m = std::make_unique(fMainTreeName); + for (const auto &fn : fMainChainFileNames) + m->Add(fn); + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 9.99); +} + +TEST_F(RegressionGH22652, RunMainTTree) +{ + // Main is a TTree, friend is a TChain, entries are aligned + + auto fm = std::make_unique(fMainTreeFileName); + std::unique_ptr m{fm->Get(fMainTreeName)}; + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 9.99); +} + +TEST_F(RegressionGH22652, TChainFriendWithShorterFiles) +{ + // Main is a TChain, friend is a TChain, total number of entries is the same + // but the friend TChain has double the number of files and half the entries + // per file. This exercises in particular the correct updating of the branch + // addresses of the friend TChain when it switches to another file even though + // the main TChain is still traversing the same file. + auto m = std::make_unique(fMainTreeName); + for (const auto &fn : fMainChainFileNames) + m->Add(fn); + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fShortFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 60.0); +} \ No newline at end of file From 3b88dbae76e83c68bb30f5758d70e1840e6f158f Mon Sep 17 00:00:00 2001 From: Vincenzo Eduardo Padulano Date: Mon, 22 Jun 2026 10:37:58 +0200 Subject: [PATCH 2/3] [tree] Specialize leaf retrieval for self and friends Implement a similar strategy to what was done for GetBranch in https://github.com/root-project/root/commit/30860f32630f0ffbd3e3d00e6ddaebe333b92250 . This will be used in a later commit to dispatch a call to Get[Minimum,Maximum] to a friend if it has the input column name. --- tree/tree/inc/TTree.h | 6 ++ tree/tree/src/TTree.cxx | 224 ++++++++++++++++++++++------------------ 2 files changed, 129 insertions(+), 101 deletions(-) diff --git a/tree/tree/inc/TTree.h b/tree/tree/inc/TTree.h index 006b7b9fc7f3b..e0dc1427bfed9 100644 --- a/tree/tree/inc/TTree.h +++ b/tree/tree/inc/TTree.h @@ -218,6 +218,12 @@ class TTree : public TNamed, public TAttLine, public TAttFill, public TAttMarker virtual Int_t SetBranchAddress(const char *bname, void *add, TBranch **ptr, TClass *realClass, EDataType datatype, bool isptr, bool suppressMissingBranchError); + TBranch *FindBranchFromSelf(const char *branchName); + TBranch *FindBranchFromFriends(const char *branchName); + + TLeaf *GetLeafFromSelf(const char *branchName, const char *leafName); + TLeaf *GetLeafFromFriends(const char *branchName, const char *leafName); + class TFriendLock { // Helper class to prevent infinite recursion in the // usage of TTree Friends. Implemented in TTree.cxx. diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 8023b9b939bf3..7ea56fb379441 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -4882,64 +4882,47 @@ static TBranch *R__FindBranchHelper(TObjArray *list, const char *branchname) { return nullptr; } -//////////////////////////////////////////////////////////////////////////////// -/// Return the branch that correspond to the path 'branchname', which can -/// include the name of the tree or the omitted name of the parent branches. -/// In case of ambiguity, returns the first match. -/// \sa TTree::GetBranch - -TBranch* TTree::FindBranch(const char* branchname) +TBranch *TTree::FindBranchFromSelf(const char *branchName) { - // We already have been visited while recursively looking - // through the friends tree, let return - if (kFindBranch & fFriendLockStatus) { - return nullptr; - } - - if (!branchname) - return nullptr; - - TBranch* branch = nullptr; // If the first part of the name match the TTree name, look for the right part in the // list of branches. - // This will allow the branchname to be preceded by + // This will allow the branchName to be preceded by // the name of this tree. - if (strncmp(fName.Data(),branchname,fName.Length())==0 && branchname[fName.Length()]=='.') { - branch = R__FindBranchHelper( GetListOfBranches(), branchname + fName.Length() + 1); - if (branch) return branch; - } + if (strncmp(fName.Data(), branchName, fName.Length()) == 0 && branchName[fName.Length()] == '.') + if (auto *br = R__FindBranchHelper(GetListOfBranches(), branchName + fName.Length() + 1)) + return br; + // If we did not find it, let's try to find the full name in the list of branches. - branch = R__FindBranchHelper(GetListOfBranches(), branchname); - if (branch) return branch; + if (auto *br = R__FindBranchHelper(GetListOfBranches(), branchName)) + return br; - // If we still did not find, let's try to find it within each branch assuming it does not the branch name. - TIter next(GetListOfBranches()); - while ((branch = (TBranch*) next())) { - TBranch* nestedbranch = branch->FindBranch(branchname); - if (nestedbranch) { + // If we still did not find, let's try to find it within each branch assuming it does not contain the branch name. + for (auto *branch : ROOT::Detail::TRangeStaticCast(*GetListOfBranches())) + if (auto *nestedbranch = branch->FindBranch(branchName)) return nestedbranch; - } - } - // Search in list of friends. + return nullptr; +} + +TBranch *TTree::FindBranchFromFriends(const char *branchName) +{ if (!fFriends) { return nullptr; } + TFriendLock lock(this, kFindBranch); - TIter nextf(fFriends); - TFriendElement* fe = nullptr; - while ((fe = (TFriendElement*) nextf())) { - TTree* t = fe->GetTree(); + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) { + TTree *t = frEl->GetTree(); if (!t) { continue; } // If the alias is present replace it with the real name. - const char *subbranch = strstr(branchname, fe->GetName()); - if (subbranch != branchname) { + const char *subbranch = strstr(branchName, frEl->GetName()); + if (subbranch != branchName) { subbranch = nullptr; } if (subbranch) { - subbranch += strlen(fe->GetName()); + subbranch += strlen(frEl->GetName()); if (*subbranch != '.') { subbranch = nullptr; } else { @@ -4950,13 +4933,38 @@ TBranch* TTree::FindBranch(const char* branchname) if (subbranch) { name << t->GetName() << "." << subbranch; } else { - name << branchname; - } - branch = t->FindBranch(name.str().c_str()); - if (branch) { - return branch; + name << branchName; } + if (auto *br = t->FindBranch(name.str().c_str())) + return br; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return the branch that correspond to the path 'branchname', which can +/// include the name of the tree or the omitted name of the parent branches. +/// In case of ambiguity, returns the first match. +/// \sa TTree::GetBranch + +TBranch *TTree::FindBranch(const char *branchname) +{ + // We already have been visited while recursively looking + // through the friends tree, let return + if (kFindBranch & fFriendLockStatus) { + return nullptr; } + + if (!branchname) + return nullptr; + + if (auto *br = FindBranchFromSelf(branchname)) + return br; + + if (auto *br = FindBranchFromFriends(branchname)) + return br; + return nullptr; } @@ -6196,51 +6204,35 @@ TIterator* TTree::GetIteratorOnAllLeaves(bool dir) return new TTreeFriendLeafIter(this, dir); } -//////////////////////////////////////////////////////////////////////////////// -/// Return pointer to the 1st Leaf named name in any Branch of this -/// Tree or any branch in the list of friend trees. -/// -/// The leaf name can contain the name of a friend tree with the -/// syntax: friend_dir_and_tree.full_leaf_name -/// the friend_dir_and_tree can be of the form: -/// ~~~ {.cpp} -/// TDirectoryName/TreeName -/// ~~~ - -TLeaf* TTree::GetLeafImpl(const char* branchname, const char *leafname) +TLeaf *TTree::GetLeafFromSelf(const char *branchName, const char *leafName) { - TLeaf *leaf = nullptr; - if (branchname) { - TBranch *branch = FindBranch(branchname); - if (branch) { - leaf = branch->GetLeaf(leafname); - if (leaf) { + if (branchName) + if (auto *br = FindBranchFromSelf(branchName)) + if (auto *leaf = br->GetLeaf(leafName)) return leaf; - } - } - } - TIter nextl(GetListOfLeaves()); - while ((leaf = (TLeaf*)nextl())) { - if (strcmp(leaf->GetFullName(), leafname) != 0 && strcmp(leaf->GetName(), leafname) != 0) - continue; // leafname does not match GetName() nor GetFullName(), this is not the right leaf - if (branchname) { - // check the branchname is also a match + + for (auto *leaf : ROOT::Detail::TRangeStaticCast(*GetListOfLeaves())) { + if (strcmp(leaf->GetFullName(), leafName) != 0 && strcmp(leaf->GetName(), leafName) != 0) + continue; // leafName does not match GetName() nor GetFullName(), this is not the right leaf + if (branchName) { + // check the branchName is also a match TBranch *br = leaf->GetBranch(); // if a quick comparison with the branch full name is a match, we are done - if (!strcmp(br->GetFullName(), branchname)) + if (!strcmp(br->GetFullName(), branchName)) return leaf; - UInt_t nbch = strlen(branchname); + UInt_t nbch = strlen(branchName); const char* brname = br->GetName(); TBranch *mother = br->GetMother(); - if (strncmp(brname,branchname,nbch)) { + if (strncmp(brname, branchName, nbch)) { if (mother != br) { const char *mothername = mother->GetName(); UInt_t motherlen = strlen(mothername); - if (!strcmp(mothername, branchname)) { + if (!strcmp(mothername, branchName)) { return leaf; - } else if (nbch > motherlen && strncmp(mothername,branchname,motherlen)==0 && (mothername[motherlen-1]=='.' || branchname[motherlen]=='.')) { + } else if (nbch > motherlen && strncmp(mothername, branchName, motherlen) == 0 && + (mothername[motherlen - 1] == '.' || branchName[motherlen] == '.')) { // The left part of the requested name match the name of the mother, let's see if the right part match the name of the branch. - if (strncmp(brname,branchname+motherlen+1,nbch-motherlen-1)) { + if (strncmp(brname, branchName + motherlen + 1, nbch - motherlen - 1)) { // No it does not continue; } // else we have match so we can proceed. @@ -6262,35 +6254,65 @@ TLeaf* TTree::GetLeafImpl(const char* branchname, const char *leafname) } return leaf; } + + return nullptr; +} + +TLeaf *TTree::GetLeafFromFriends(const char *branchName, const char *leafName) +{ + if (branchName) + if (auto *br = FindBranchFromFriends(branchName)) + if (auto *leaf = br->GetLeaf(leafName)) + return leaf; + if (!fFriends) return nullptr; - TFriendLock lock(this,kGetLeaf); - TIter next(fFriends); - TFriendElement *fe; - while ((fe = (TFriendElement*)next())) { - TTree *t = fe->GetTree(); - if (t) { - leaf = t->GetLeaf(branchname, leafname); - if (leaf) return leaf; - } - } + TFriendLock lock(this, kGetLeaf); - //second pass in the list of friends when the leaf name - //is prefixed by the tree name + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) + if (auto *t = frEl->GetTree()) + if (auto *leaf = t->GetLeaf(branchName, leafName)) + return leaf; + + // Second pass in the list of friends when the leaf name is prefixed by the tree name TString strippedArg; - next.Reset(); - while ((fe = (TFriendElement*)next())) { - TTree *t = fe->GetTree(); + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) { + TTree *t = frEl->GetTree(); if (!t) continue; - const char *subname = strstr(leafname,fe->GetName()); - if (subname != leafname) continue; - Int_t l = strlen(fe->GetName()); - subname += l; - if (*subname != '.') continue; - subname++; - strippedArg += subname; - leaf = t->GetLeaf(branchname,subname); - if (leaf) return leaf; + const char *subLeafName = strstr(leafName, frEl->GetName()); + if (subLeafName != leafName) + continue; + Int_t l = strlen(frEl->GetName()); + subLeafName += l; + if (*subLeafName != '.') + continue; + subLeafName++; + strippedArg += subLeafName; + if (auto *leaf = t->GetLeaf(branchName, subLeafName)) + return leaf; } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return pointer to the 1st Leaf named name in any Branch of this +/// Tree or any branch in the list of friend trees. +/// +/// The leaf name can contain the name of a friend tree with the +/// syntax: friend_dir_and_tree.full_leaf_name +/// the friend_dir_and_tree can be of the form: +/// ~~~ {.cpp} +/// TDirectoryName/TreeName +/// ~~~ + +TLeaf *TTree::GetLeafImpl(const char *branchname, const char *leafname) +{ + if (auto *leaf = GetLeafFromSelf(branchname, leafname)) + return leaf; + + if (auto *leaf = GetLeafFromFriends(branchname, leafname)) + return leaf; + return nullptr; } From 9990ddf8e48759b9da675426f96c94ec41399aa3 Mon Sep 17 00:00:00 2001 From: Vincenzo Eduardo Padulano Date: Mon, 22 Jun 2026 10:42:07 +0200 Subject: [PATCH 3/3] [tree] Rework Get[Minimum,Maximum] to account for friends If the input column name belongs to a friend, the methods now dispatch the calculation to the friend itself to ensure the branch/leaf addresses are properly updated. --- tree/tree/src/TChain.cxx | 150 ++++++++++++++++++++++++++------------- tree/tree/src/TTree.cxx | 124 ++++++++++++++++++++++---------- 2 files changed, 187 insertions(+), 87 deletions(-) diff --git a/tree/tree/src/TChain.cxx b/tree/tree/src/TChain.cxx index 88da9fcc421c6..0bad1647000d3 100644 --- a/tree/tree/src/TChain.cxx +++ b/tree/tree/src/TChain.cxx @@ -1139,34 +1139,59 @@ TObjArray* TChain::GetListOfLeaves() Double_t TChain::GetMaximum(const char* columname) { - Double_t cmax = -DBL_MAX; - TLeaf *leaf = nullptr; - TBranch *branch = nullptr; - Int_t treenumber = -1; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) - break; - Long64_t localEntryNumber = this->LoadTree(entryNumber); - if (localEntryNumber < 0) - break; - if (treenumber != this->GetTreeNumber()) { - leaf = this->GetLeaf(columname); - if (leaf) - branch = leaf->GetBranch(); - } - treenumber = this->GetTreeNumber(); - if (!branch) - continue; - branch->GetEntry(localEntryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val > cmax) { - cmax = val; + constexpr auto errVal{std::numeric_limits::lowest()}; + + // If the requested column is in this TTree, compute the maximum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + TBranch *branch = nullptr; + Int_t treenumber = -1; + double cmax{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + Long64_t localEntryNumber = this->LoadTree(entryNumber); + if (localEntryNumber < 0) + break; + if (treenumber != this->GetTreeNumber()) { + leaf = GetLeafFromSelf(nullptr, columname); + if (leaf) + branch = leaf->GetBranch(); + } + if (!branch) + continue; + treenumber = this->GetTreeNumber(); + branch->GetEntry(localEntryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val > cmax) { + cmax = val; + } } } + return cmax; } - return cmax; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMaximum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// @@ -1174,34 +1199,59 @@ Double_t TChain::GetMaximum(const char* columname) Double_t TChain::GetMinimum(const char* columname) { - Double_t cmin = DBL_MAX; - TLeaf *leaf = nullptr; - TBranch *branch = nullptr; - Int_t treenumber = -1; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) - break; - Long64_t localEntryNumber = this->LoadTree(entryNumber); - if (localEntryNumber < 0) - break; - if (treenumber != this->GetTreeNumber()) { - leaf = this->GetLeaf(columname); - if (leaf) - branch = leaf->GetBranch(); - } - treenumber = this->GetTreeNumber(); - if (!branch) - continue; - branch->GetEntry(localEntryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val < cmin) { - cmin = val; + constexpr auto errVal{std::numeric_limits::max()}; + + // If the requested column is in this TTree, compute the minimum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + TBranch *branch = nullptr; + Int_t treenumber = -1; + double cmin{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + Long64_t localEntryNumber = this->LoadTree(entryNumber); + if (localEntryNumber < 0) + break; + if (treenumber != this->GetTreeNumber()) { + leaf = GetLeafFromSelf(nullptr, columname); + if (leaf) + branch = leaf->GetBranch(); + } + if (!branch) + continue; + treenumber = this->GetTreeNumber(); + branch->GetEntry(localEntryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val < cmin) { + cmin = val; + } } } + return cmin; } - return cmin; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMinimum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 7ea56fb379441..a0f4e1a70f960 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -6366,31 +6366,56 @@ TLeaf* TTree::GetLeaf(const char *name) /// if the Tree has an associated TEventList or TEntryList, the maximum /// is computed for the entries in this list. -Double_t TTree::GetMaximum(const char* columname) +Double_t TTree::GetMaximum(const char *columname) { - TLeaf* leaf = this->GetLeaf(columname); - if (!leaf) { - return 0; - } + constexpr auto errVal{std::numeric_limits::lowest()}; + if (!columname || strcmp(columname, "") == 0) + return errVal; - // create cache if wanted - if (fCacheDoAutoInit) - SetCacheSizeAux(); + // If the requested column is in this TTree, compute the maximum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} - TBranch* branch = leaf->GetBranch(); - Double_t cmax = -DBL_MAX; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) break; - branch->GetEntry(entryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val > cmax) { - cmax = val; + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + // create cache if wanted + if (fCacheDoAutoInit) + SetCacheSizeAux(); + + TBranch *branch = leaf->GetBranch(); + Double_t cmax{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + branch->GetEntry(entryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val > cmax) { + cmax = val; + } } } + return cmax; } - return cmax; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMaximum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// @@ -6408,29 +6433,54 @@ Long64_t TTree::GetMaxTreeSize() Double_t TTree::GetMinimum(const char* columname) { - TLeaf* leaf = this->GetLeaf(columname); - if (!leaf) { - return 0; - } + constexpr auto errVal{std::numeric_limits::max()}; + if (!columname || strcmp(columname, "") == 0) + return errVal; - // create cache if wanted - if (fCacheDoAutoInit) - SetCacheSizeAux(); + // If the requested column is in this TTree, compute the minimum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} - TBranch* branch = leaf->GetBranch(); - Double_t cmin = DBL_MAX; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) break; - branch->GetEntry(entryNumber); - for (Int_t j = 0;j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val < cmin) { - cmin = val; + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + // create cache if wanted + if (fCacheDoAutoInit) + SetCacheSizeAux(); + + TBranch *branch = leaf->GetBranch(); + Double_t cmin{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + branch->GetEntry(entryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val < cmin) { + cmin = val; + } } } + return cmin; } - return cmin; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMinimum(columname); + + return errVal; } ////////////////////////////////////////////////////////////////////////////////