Skip to content

Commit 780d328

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 780d328

File tree

3 files changed

+196
-3
lines changed

3 files changed

+196
-3
lines changed

io/io/test/TFileMergerTests.cxx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,133 @@ 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+
TTree atree("atree", "atitle");
402+
int value;
403+
atree.Branch("a", &value);
404+
value = 11;
405+
atree.Fill();
406+
TTree abtree("abtree", "abtitle");
407+
abtree.Branch("a", &value);
408+
abtree.Branch("b", &value);
409+
value = 42;
410+
abtree.Fill();
411+
412+
TTree dummy("ztree", "zeroBranches");
413+
TList treelist;
414+
415+
// Case 1 - Static: ZeroBranches + 1 entry (1 branch) + 1 entry (2 branch)
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+
// Case 2 - this (ZeroBranches) + 1 entry (1 branch) + 1 entry (2 branch)
437+
treelist.Clear();
438+
treelist.Add(&atree);
439+
treelist.Add(&abtree);
440+
std::unique_ptr<TFile> file2(TFile::Open("c4716.root", "RECREATE"));
441+
TFileMergeInfo info2(file2.get());
442+
info2.fOptions += " ImportBranches";
443+
dummy.Merge(&treelist, &info2);
444+
file2->Write();
445+
ASSERT_TRUE(dummy.FindBranch("a"));
446+
EXPECT_EQ(dummy.FindBranch("a")->GetEntries(), 2);
447+
ASSERT_TRUE(dummy.FindBranch("b"));
448+
EXPECT_EQ(dummy.FindBranch("b")->GetEntries(), 2);
449+
EXPECT_EQ(atree.GetName(),
450+
"ztree"); // As a side effect of trees with zero branches being ignored but it's name kept since it's first
451+
// in list, the first tree with branches (name) gets ztree's name.
452+
ASSERT_TRUE(atree.FindBranch("a"));
453+
EXPECT_EQ(atree.FindBranch("a")->GetEntries(), 2); // As a side effect, the first with branches (atree) has been
454+
// modified and has now 2 entries instead of 1, and ztree's name
455+
ASSERT_TRUE(atree.FindBranch("b"));
456+
EXPECT_EQ(atree.FindBranch("b")->GetEntries(), 2); // As a side effect, the first with branches (atree) has been
457+
// modified and has now 2 entries instead of 1, and ztree's name
458+
ASSERT_TRUE(abtree.FindBranch("a"));
459+
ASSERT_TRUE(abtree.FindBranch("b"));
460+
EXPECT_EQ(abtree.FindBranch("a")->GetEntries(), 1);
461+
EXPECT_EQ(abtree.FindBranch("b")->GetEntries(), 1);
462+
463+
// Case 3 - this (0 entry / 1 branch) + 1 entry (1 branch) + 1 entry (2 branch)
464+
TTree atree_("atree", "atitle"); // Recreate original atree
465+
int value;
466+
atree_.Branch("a", &value);
467+
value = 11;
468+
atree_.Fill();
469+
treelist.Clear();
470+
treelist.Add(&atree_);
471+
treelist.Add(&abtree);
472+
TTree a0tree("a0tree", "a0title");
473+
a0tree.Branch("a", &value);
474+
std::unique_ptr<TFile> file3(TFile::Open("d4716.root", "RECREATE"));
475+
TFileMergeInfo info3(file3.get());
476+
info3.fOptions += " ImportBranches";
477+
a0tree.Merge(&treelist, &info3);
478+
file3->Write();
479+
ASSERT_TRUE(a0tree.FindBranch("a"));
480+
EXPECT_EQ(a0tree.FindBranch("a")->GetEntries(), 2);
481+
ASSERT_TRUE(a0tree.FindBranch("b"));
482+
EXPECT_EQ(a0tree.FindBranch("b")->GetEntries(), 2);
483+
ASSERT_TRUE(atree_.FindBranch("a"));
484+
ASSERT_FALSE(atree_.FindBranch("b"));
485+
ASSERT_TRUE(abtree.FindBranch("a"));
486+
ASSERT_TRUE(abtree.FindBranch("b"));
487+
EXPECT_EQ(atree_.FindBranch("a")->GetEntries(), 1);
488+
EXPECT_EQ(abtree.FindBranch("a")->GetEntries(), 1);
489+
EXPECT_EQ(abtree.FindBranch("b")->GetEntries(), 1);
490+
491+
// Case 4 - this 1 entry (3 branch) + 1 entry (1 branch) + (0 entry / 1 branch)
492+
TTree abctree("abctree", "abctitle");
493+
abctree.Branch("a", &value);
494+
abctree.Branch("b", &value);
495+
abctree.Branch("c", &value);
496+
value = 11;
497+
abctree.Fill();
498+
TTree ctree("ctree", "ctitle");
499+
ctree.Branch("c", &value);
500+
value = 42;
501+
ctree.Fill();
502+
TTree c0tree("c0tree", "c0title");
503+
c0tree.Branch("c", &value);
504+
std::unique_ptr<TFile> file4(TFile::Open("e4716.root", "RECREATE"));
505+
TFileMergeInfo info4(file4.get());
506+
info4.fOptions += " ImportBranches";
507+
treelist.Clear();
508+
treelist.Add(&ctree);
509+
treelist.Add(&c0tree);
510+
abctree.Merge(&treelist, &info4);
511+
file4->Write();
512+
ASSERT_TRUE(abctree.FindBranch("a"));
513+
ASSERT_TRUE(abctree.FindBranch("b");
514+
ASSERT_TRUE(abctree.FindBranch("c"));
515+
EXPECT_EQ(abctree.FindBranch("a")->GetEntries(), 2);
516+
EXPECT_EQ(abctree.FindBranch("b")->GetEntries(), 2);
517+
EXPECT_EQ(abctree.FindBranch("c")->GetEntries(), 2);
518+
ASSERT_FALSE(ctree.FindBranch("a"));
519+
ASSERT_FALSE(ctree.FindBranch("b"));
520+
ASSERT_TRUE(ctree.FindBranch("c"));
521+
EXPECT_EQ(ctree.FindBranch("c")->GetEntries(), 1);
522+
ASSERT_FALSE(c0tree.FindBranch("a"));
523+
ASSERT_FALSE(c0tree.FindBranch("b"));
524+
ASSERT_TRUE(c0tree.FindBranch("c"));
525+
EXPECT_EQ(c0tree.FindBranch("c")->GetEntries(), 0);
526+
}

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)