Skip to content

Commit 102e2cb

Browse files
committed
[TTree] Add option to import branches not present in first tree when merging TTrees
Fixes #14558 Fixes #12510
1 parent 8b04b9f commit 102e2cb

File tree

3 files changed

+225
-3
lines changed

3 files changed

+225
-3
lines changed

io/io/test/TFileMergerTests.cxx

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,162 @@ TEST(TFileMerger, MergeSelectiveTutorial)
394394
EXPECT_NE(file.Get<TNtuple>("ntuple"), nullptr);
395395
}
396396
}
397+
398+
// https://github.com/root-project/root/issues/14558
399+
TEST(TFileMerger, ImportBranches)
400+
{
401+
{
402+
// Case 1 - Static: ZeroBranches + 1 entry (1 branch) + 1 entry (2 branch)
403+
TTree atree("atree", "atitle");
404+
int value;
405+
atree.Branch("a", &value);
406+
value = 11;
407+
atree.Fill();
408+
TTree abtree("abtree", "abtitle");
409+
abtree.Branch("a", &value);
410+
abtree.Branch("b", &value);
411+
value = 42;
412+
abtree.Fill();
413+
414+
TTree dummy("ztree", "zeroBranches");
415+
TList treelist;
416+
treelist.Add(&dummy);
417+
treelist.Add(&atree);
418+
treelist.Add(&abtree);
419+
std::unique_ptr<TFile> file1(TFile::Open("b4716.root", "RECREATE"));
420+
auto rtree = TTree::MergeTrees(&treelist, "ImportBranches");
421+
file1->Write();
422+
ASSERT_TRUE(rtree->FindBranch("a"));
423+
EXPECT_EQ(rtree->FindBranch("a")->GetEntries(), 2);
424+
ASSERT_TRUE(rtree->FindBranch("b"));
425+
EXPECT_EQ(rtree->FindBranch("b")->GetEntries(), 2);
426+
ASSERT_TRUE(atree.FindBranch("a"));
427+
ASSERT_FALSE(atree.FindBranch("b"));
428+
ASSERT_TRUE(abtree.FindBranch("a"));
429+
ASSERT_TRUE(abtree.FindBranch("b"));
430+
EXPECT_EQ(atree.FindBranch("a")->GetEntries(), 1);
431+
EXPECT_EQ(abtree.FindBranch("a")->GetEntries(), 1);
432+
EXPECT_EQ(abtree.FindBranch("b")->GetEntries(), 1);
433+
EXPECT_EQ(dummy.FindBranch("a"), nullptr);
434+
EXPECT_EQ(dummy.FindBranch("b"), nullptr);
435+
}
436+
{
437+
// Case 2 - this (ZeroBranches) + 1 entry (1 branch) + 1 entry (2 branch)
438+
TTree atree("atree", "atitle");
439+
int value;
440+
atree.Branch("a", &value);
441+
value = 11;
442+
atree.Fill();
443+
TTree abtree("abtree", "abtitle");
444+
abtree.Branch("a", &value);
445+
abtree.Branch("b", &value);
446+
value = 42;
447+
abtree.Fill();
448+
449+
TTree dummy("ztree", "zeroBranches");
450+
TList treelist;
451+
treelist.Add(&atree);
452+
treelist.Add(&abtree);
453+
std::unique_ptr<TFile> file2(TFile::Open("c4716.root", "RECREATE"));
454+
TFileMergeInfo info2(file2.get());
455+
info2.fOptions += " ImportBranches";
456+
dummy.Merge(&treelist, &info2);
457+
file2->Write();
458+
ASSERT_TRUE(dummy.FindBranch("a"));
459+
EXPECT_EQ(dummy.FindBranch("a")->GetEntries(), 2);
460+
ASSERT_TRUE(dummy.FindBranch("b"));
461+
EXPECT_EQ(dummy.FindBranch("b")->GetEntries(), 2);
462+
EXPECT_EQ(TString(atree.GetName()),
463+
"ztree"); // As a side effect of trees with zero branches being ignored but it's name kept since it's first
464+
// in list, the first tree with branches (name) gets ztree's name.
465+
ASSERT_TRUE(atree.FindBranch("a"));
466+
EXPECT_EQ(atree.FindBranch("a")->GetEntries(), 2); // As a side effect, the first with branches (atree) has been
467+
// modified and has now 2 entries instead of 1, and ztree's name
468+
ASSERT_TRUE(atree.FindBranch("b"));
469+
EXPECT_EQ(atree.FindBranch("b")->GetEntries(), 2); // As a side effect, the first with branches (atree) has been
470+
// modified and has now 2 entries instead of 1, and ztree's name
471+
ASSERT_TRUE(abtree.FindBranch("a"));
472+
ASSERT_TRUE(abtree.FindBranch("b"));
473+
EXPECT_EQ(abtree.FindBranch("a")->GetEntries(), 1);
474+
EXPECT_EQ(abtree.FindBranch("b")->GetEntries(), 1);
475+
}
476+
{
477+
// Case 3 - this (0 entry / 1 branch) + 1 entry (1 branch) + 1 entry (2 branch)
478+
TTree atree("atree", "atitle");
479+
int value;
480+
atree.Branch("a", &value);
481+
value = 11;
482+
atree.Fill();
483+
TTree abtree("abtree", "abtitle");
484+
abtree.Branch("a", &value);
485+
abtree.Branch("b", &value);
486+
value = 42;
487+
abtree.Fill();
488+
489+
TList treelist;
490+
treelist.Add(&atree);
491+
treelist.Add(&abtree);
492+
TTree a0tree("a0tree", "a0title");
493+
a0tree.Branch("a", &value);
494+
std::unique_ptr<TFile> file3(TFile::Open("d4716.root", "RECREATE"));
495+
TFileMergeInfo info3(file3.get());
496+
info3.fOptions += " ImportBranches";
497+
a0tree.Merge(&treelist, &info3);
498+
file3->Write();
499+
ASSERT_TRUE(a0tree.FindBranch("a"));
500+
EXPECT_EQ(a0tree.FindBranch("a")->GetEntries(), 2);
501+
ASSERT_TRUE(a0tree.FindBranch("b"));
502+
EXPECT_EQ(a0tree.FindBranch("b")->GetEntries(), 2);
503+
ASSERT_TRUE(atree.FindBranch("a"));
504+
ASSERT_FALSE(atree.FindBranch("b"));
505+
ASSERT_TRUE(abtree.FindBranch("a"));
506+
ASSERT_TRUE(abtree.FindBranch("b"));
507+
EXPECT_EQ(atree.FindBranch("a")->GetEntries(), 1);
508+
EXPECT_EQ(abtree.FindBranch("a")->GetEntries(), 1);
509+
EXPECT_EQ(abtree.FindBranch("b")->GetEntries(), 1);
510+
}
511+
{
512+
// Case 4 - this 1 entry (3 branch) + 1 entry (1 branch) + (0 entry / 1 branch)
513+
TTree abctree("abctree", "abctitle");
514+
int value;
515+
abctree.Branch("a", &value);
516+
abctree.Branch("b", &value);
517+
abctree.Branch("c", &value);
518+
value = 11;
519+
abctree.Fill();
520+
TTree ctree("ctree", "ctitle");
521+
ctree.Branch("c", &value);
522+
value = 42;
523+
ctree.Fill();
524+
TTree c0tree("c0tree", "c0title");
525+
c0tree.Branch("c", &value);
526+
527+
std::unique_ptr<TFile> file4(TFile::Open("e4716.root", "RECREATE"));
528+
TFileMergeInfo info4(file4.get());
529+
info4.fOptions += " ImportBranches";
530+
TList treelist;
531+
treelist.Add(&ctree);
532+
treelist.Add(&c0tree);
533+
ROOT::TestSupport::CheckDiagsRAII diagRAII; // ctree and c0tree don't have a/b branch so warn since they will be auto-filled
534+
diagRAII.requiredDiag(kWarning, "TTree::CopyAddresses", "Could not find branch named 'a' in tree named 'ctree'");
535+
diagRAII.requiredDiag(kWarning, "TTree::CopyAddresses", "Could not find branch named 'b' in tree named 'ctree'");
536+
diagRAII.requiredDiag(kWarning, "TTree::CopyAddresses", "Could not find branch named 'a' in tree named 'c0tree'");
537+
diagRAII.requiredDiag(kWarning, "TTree::CopyAddresses", "Could not find branch named 'b' in tree named 'c0tree'");
538+
abctree.Merge(&treelist, &info4);
539+
file4->Write();
540+
ASSERT_TRUE(abctree.FindBranch("a"));
541+
ASSERT_TRUE(abctree.FindBranch("b"));
542+
ASSERT_TRUE(abctree.FindBranch("c"));
543+
EXPECT_EQ(abctree.FindBranch("a")->GetEntries(), 2);
544+
EXPECT_EQ(abctree.FindBranch("b")->GetEntries(), 2);
545+
EXPECT_EQ(abctree.FindBranch("c")->GetEntries(), 2);
546+
ASSERT_FALSE(ctree.FindBranch("a"));
547+
ASSERT_FALSE(ctree.FindBranch("b"));
548+
ASSERT_TRUE(ctree.FindBranch("c"));
549+
EXPECT_EQ(ctree.FindBranch("c")->GetEntries(), 1);
550+
ASSERT_FALSE(c0tree.FindBranch("a"));
551+
ASSERT_FALSE(c0tree.FindBranch("b"));
552+
ASSERT_TRUE(c0tree.FindBranch("c"));
553+
EXPECT_EQ(c0tree.FindBranch("c")->GetEntries(), 0);
554+
}
555+
}

tree/tree/inc/TTree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ class TTree : public TNamed, public TAttLine, public TAttFill, public TAttMarker
635635
bool MemoryFull(Int_t nbytes);
636636
virtual Long64_t Merge(TCollection* list, Option_t* option = "");
637637
virtual Long64_t Merge(TCollection* list, TFileMergeInfo *info);
638+
bool ImportBranches(TTree *tree);
638639
static TTree *MergeTrees(TList* list, Option_t* option = "");
639640
bool Notify() override;
640641
virtual void OptimizeBaskets(ULong64_t maxMemory=10000000, Float_t minComp=1.1, Option_t *option="");

tree/tree/src/TTree.cxx

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6924,21 +6924,71 @@ bool TTree::MemoryFull(Int_t nbytes)
69246924
return true;
69256925
}
69266926

6927+
////////////////////////////////////////////////////////////////////////////////
6928+
/// Function merging branch structure from an outside tree into the current one
6929+
///
6930+
/// If the same branch name already exists in "this", it's skipped, only new ones
6931+
/// are copied.
6932+
/// Entries are not copied, just branch name / type is cloned
6933+
/// Branches marked as DoNotProcess are not merged
6934+
/// If this tree had some entries already in other branches and the new tree incorporates a new branch,
6935+
/// when importing the branch, we backfill the branch value with default values until GetEntries is reached,
6936+
/// to prevent misalignments in the TTree structure between branches / entries.
6937+
/// @param tree the outside TTree whose branches will be copied into this tree
6938+
/// @return boolean true on sucess, false otherwise
6939+
6940+
bool TTree::ImportBranches(TTree *tree)
6941+
{
6942+
if (tree) {
6943+
TObjArray *newbranches = tree->GetListOfBranches();
6944+
Int_t nbranches = newbranches->GetEntriesFast();
6945+
const Long64_t nentries = GetEntries();
6946+
std::vector<TBranch *> importedCollection;
6947+
for (Int_t i = 0; i < nbranches; ++i) {
6948+
TBranch *nbranch = static_cast<TBranch *>(newbranches->UncheckedAt(i));
6949+
if (nbranch->TestBit(kDoNotProcess)) {
6950+
continue;
6951+
}
6952+
if (!GetListOfBranches()->FindObject(nbranch->GetName())) {
6953+
auto addbranch = static_cast<TBranch *>(nbranch->Clone());
6954+
addbranch->ResetAddress();
6955+
addbranch->Reset();
6956+
addbranch->SetTree(this);
6957+
fBranches.Add(addbranch);
6958+
importedCollection.push_back(addbranch);
6959+
}
6960+
}
6961+
// Backfill mechanism to realign with TTree
6962+
if (!importedCollection.empty()) {
6963+
for (Long64_t e = 0; e < nentries; ++e) {
6964+
for (auto branch : importedCollection) {
6965+
branch->BackFill();
6966+
}
6967+
}
6968+
}
6969+
return true;
6970+
}
6971+
return false;
6972+
}
6973+
69276974
////////////////////////////////////////////////////////////////////////////////
69286975
/// Static function merging the trees in the TList into a new tree.
69296976
///
69306977
/// Trees in the list can be memory or disk-resident trees.
69316978
/// The new tree is created in the current directory (memory if gROOT).
69326979
/// Trees with no branches will be skipped, the branch structure
69336980
/// will be taken from the first non-zero-branch Tree of {li}
6981+
/// Use "ImportBranches" option to incorporate branches from the (filled) trees in
6982+
/// the list that were not in the first TTree into the final result, backfilling
6983+
/// with default values to prevent misalignment
69346984

69356985
TTree* TTree::MergeTrees(TList* li, Option_t* options)
69366986
{
69376987
if (!li) return nullptr;
69386988
TIter next(li);
69396989
TTree *newtree = nullptr;
69406990
TObject *obj;
6941-
6991+
const bool importBranches = TString(options).Contains("ImportBranches", TString::ECaseCompare::kIgnoreCase);
69426992
while ((obj=next())) {
69436993
if (!obj->InheritsFrom(TTree::Class())) continue;
69446994
TTree *tree = (TTree*)obj;
@@ -6963,6 +7013,8 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
69637013
tree->ResetBranchAddresses();
69647014
newtree->ResetBranchAddresses();
69657015
continue;
7016+
} else if (importBranches) {
7017+
newtree->ImportBranches(tree);
69667018
}
69677019
if (nentries == 0)
69687020
continue;
@@ -6980,6 +7032,9 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
69807032
/// Returns the total number of entries in the merged tree.
69817033
/// Trees with no branches will be skipped, the branch structure
69827034
/// will be taken from the first non-zero-branch Tree of {this+li}
7035+
/// Use "ImportBranches" option to incorporate branches from the (filled) trees in
7036+
/// the list that were not in this TTree into the final result, backfilling
7037+
/// with default values to prevent misalignment
69837038

69847039
Long64_t TTree::Merge(TCollection* li, Option_t *options)
69857040
{
@@ -7016,6 +7071,7 @@ Long64_t TTree::Merge(TCollection* li, Option_t *options)
70167071
return 0; // All trees have empty branches
70177072
}
70187073
if (!li) return 0;
7074+
const bool importBranches = TString(options).Contains("ImportBranches", TString::ECaseCompare::kIgnoreCase);
70197075
Long64_t storeAutoSave = fAutoSave;
70207076
// Disable the autosave as the TFileMerge keeps a list of key and deleting the underlying
70217077
// key would invalidate its iteration (or require costly measure to not use the deleted keys).
@@ -7034,7 +7090,8 @@ Long64_t TTree::Merge(TCollection* li, Option_t *options)
70347090

70357091
Long64_t nentries = tree->GetEntries();
70367092
if (nentries == 0) continue;
7037-
7093+
if (importBranches)
7094+
ImportBranches(tree);
70387095
CopyEntries(tree, -1, options, true);
70397096
}
70407097
fAutoSave = storeAutoSave;
@@ -7049,6 +7106,9 @@ Long64_t TTree::Merge(TCollection* li, Option_t *options)
70497106
/// use for further merging).
70507107
/// Trees with no branches will be skipped, the branch structure
70517108
/// will be taken from the first non-zero-branch Tree of {this+li}
7109+
/// Use "ImportBranches" info-option to incorporate branches from the (filled) trees
7110+
/// in the list that were not in this TTree into the final result, backfilling
7111+
/// with default values to prevent misalignment
70527112
///
70537113
/// Returns the total number of entries in the merged tree.
70547114

@@ -7111,6 +7171,7 @@ Long64_t TTree::Merge(TCollection* li, TFileMergeInfo *info)
71117171
}
71127172
}
71137173
if (!li) return 0;
7174+
const bool importBranches = TString(options).Contains("ImportBranches", TString::ECaseCompare::kIgnoreCase);
71147175
Long64_t storeAutoSave = fAutoSave;
71157176
// Disable the autosave as the TFileMerge keeps a list of key and deleting the underlying
71167177
// key would invalidate its iteration (or require costly measure to not use the deleted keys).
@@ -7126,7 +7187,8 @@ Long64_t TTree::Merge(TCollection* li, TFileMergeInfo *info)
71267187
fAutoSave = storeAutoSave;
71277188
return -1;
71287189
}
7129-
7190+
if (importBranches)
7191+
ImportBranches(tree);
71307192
CopyEntries(tree, -1, options, true);
71317193
}
71327194
fAutoSave = storeAutoSave;

0 commit comments

Comments
 (0)