Skip to content

Commit

Permalink
Add scripts to compare TTrees of two ROOT files
Browse files Browse the repository at this point in the history
The macro compares the cbmsim trees of two ROOT files whose names are passed
as arguments. If any difference is found the script fails.
The script simply calls the macro for all relevant ROOT files which are
produced when running the test suite. This allows to compare results before
and after a change.
  • Loading branch information
fuhlig1 committed Jun 30, 2023
1 parent 5c616a0 commit 4d8743c
Show file tree
Hide file tree
Showing 2 changed files with 382 additions and 0 deletions.
309 changes: 309 additions & 0 deletions examples/scripts/TreeCompareAuto.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
/********************************************************************************
* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/

// The macro compares all the leaves of two TTree objects.
// The first and strongest comparison is that the entries in the two leaves
// to compare are idential.
// To check this one loops over the entries in the trees, calculate the
// difference between the two values and fills the result in a histogram.
// If all entries are absolutely identical the result in the histogram is a
// delta function at 0.
// If the first check for identical entries fails it is checked if there are
// only some changes in the order of the entries. In this case the two
// produced histgrams (one for each tree) are identical.
// The last check which again is only done if both previous test fails uses a
// Kolmogorov test to check if the produced hostograms are comparable on a
// statistical base.
// If at least for one leaf all theree comparisons fail the complete test
// fails.

#include <RtypesCore.h>
#include <TBranch.h>
#include <TFile.h>
#include <TH1.h>
#include <TMath.h>
#include <TObjArray.h>
#include <TString.h>
#include <TTree.h>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

std::vector<std::pair<TString, TString>> GetLeafNames(TTree*);

Bool_t CheckEntriesIdentical(TH1* compHist, std::stringstream& outstream);
Bool_t CheckHistogramIdentical(TH1* origHist, TH1* newHist, std::stringstream& outstream);
Bool_t CheckHistogramKolmogorov(TH1* origHist, TH1* newHist, std::stringstream& outstream);

int TreeCompareAuto(TString fileName1 = "", TString fileName2 = "")
{
if (fileName1.IsNull() || fileName2.IsNull()) {
cout << "Filenames are not defined" << endl;
exit(42);
}

// Get the output tree from the original file
TFile* file1 = TFile::Open(fileName1, "READ");
if (nullptr == file1)
return 42;
TTree* tree1 = (TTree*)file1->Get("cbmsim");

// Get the output tree from the file whcih should be compared
TFile* file2 = TFile::Open(fileName2, "READ");
if (nullptr == file2)
return 42;
TTree* tree2 = (TTree*)file2->Get("cbmsim");

// Add the output tree from the file to compare as friend to the tree
// of the original file. This allows to access a data member of the
// original file by e.g. StsHit.fX and the data element of the second tree
// by tree2.StsHit.fX
tree1->AddFriend(tree2, "tree2");

// Define pairs of data members to compare. One from each tree.
// This allows to compare them also if names or the structure of
// the classses change.
std::vector<std::pair<TString, TString>> leaves = GetLeafNames(tree1);

std::stringstream outstream;
Bool_t okay{kTRUE};
Int_t numTestedLeaves{0};
Int_t numEmptyLeaves{0};
Int_t numLeaves{0};
Int_t numFailedLeaves{0};
Int_t numIdenticalEntries{0};
Int_t numIdenticalHistograms{0};
Int_t numKolmogorovHistograms{0};

for (auto leaf : leaves) {
TString leafName = leaf.first;
TString leafName1 = leaf.second;

TString command1 = leafName + ">>htemp";
tree1->Draw(command1);
int entries1{0};
float low1{0.};
float high1{0.};
int nBins1{0};
auto htemp = (TH1F*)gPad->GetPrimitive("htemp");
if (htemp) {
entries1 = htemp->GetEntries();
nBins1 = htemp->GetNbinsX();
low1 = htemp->GetXaxis()->GetXmin();
high1 = htemp->GetXaxis()->GetXmax();
}

command1 = leafName1 + ">>hist1(" + nBins1 + ", " + low1 + ", " + high1 + ")";
// cout << command1 << endl;
tree1->Draw(command1);
auto hist1 = (TH1F*)gPad->GetPrimitive("hist1");
int entries2{0};
float low2{0.};
float high2{0.};
int nBins2{0};
if (hist1) {
entries2 = hist1->GetEntries();
nBins2 = hist1->GetNbinsX();
low2 = hist1->GetXaxis()->GetXmin();
high2 = hist1->GetXaxis()->GetXmax();
}

if ((0 == entries1 && 0 != entries2) || (0 != entries1 && 0 == entries2)) {
std::cout << "One of the distributions is empty" << std::endl;
okay = kFALSE;
}
if (0 == entries1 && 0 == entries2) {
outstream << "Both Histograms are empty." << std::endl;

hist1->Clear();
htemp->Clear();
// numTestedLeaves++;
// numEmptyLeaves++;
continue;
}

// When executing the draw command "Leaf1 - Leaf2" the subtraction is
// executed entry by entry. If the content of the class members are
// identical the result is a histogram with a delta function at 0
// If the content is differnt one gets a distribution which is
// detected.
outstream << "Comparing " << leafName << " and " << leafName1 << std::endl;
TString command = leafName + "-" + leafName1 + ">>hist(20, -10.,10.)";
tree1->Draw(command);
auto hist = (TH1F*)gPad->GetPrimitive("hist");
numTestedLeaves++;

// Check if the entries in the tree are identical
Bool_t leafIsIdentical = CheckEntriesIdentical(hist, outstream);

// If the entries are not identical check if the histograms are
// identical. This is the case if the entries in the tree are sorted
// differently
if (leafIsIdentical) {
numIdenticalEntries++;
outstream << "**********************" << std::endl;
hist1->Clear();
hist->Clear();
htemp->Clear();
continue;
} else {
outstream << "CHecking for identical histograms" << endl;
leafIsIdentical = CheckHistogramIdentical(htemp, hist1, outstream);
}

if (leafIsIdentical) {
numIdenticalHistograms++;
outstream << "**********************" << std::endl;
hist1->Clear();
hist->Clear();
htemp->Clear();
continue;
} else {
outstream << "CHecking Kolmogorov" << endl;
leafIsIdentical = CheckHistogramKolmogorov(htemp, hist1, outstream);
}

if (leafIsIdentical) {
numKolmogorovHistograms++;
outstream << "**********************" << std::endl;
hist1->Clear();
hist->Clear();
htemp->Clear();
continue;
} else {
outstream << "Data are differnt" << std::endl;
outstream << "**********************" << std::endl;
outstream << "Entries: " << hist->GetEntries() << std::endl;
outstream << "Mean: " << hist->GetMean() << std::endl;
outstream << "RMS: " << hist->GetRMS() << std::endl;
outstream << "Underflow: " << hist->GetBinContent(0) << std::endl;
outstream << "Overflow: " << hist->GetBinContent(hist->GetNbinsX() + 1) << std::endl;
outstream << "**********************" << std::endl;
okay = kFALSE;
numFailedLeaves++;
}
}

if (!okay) {
std::cout << outstream.str();
std::cout << "Test failed." << std::endl;
std::cout << numFailedLeaves << " of " << numTestedLeaves << " leaves are different." << std::endl;
return 1;
}
// std::cout << outstream.str();
std::cout << "Tested leaves: " << numTestedLeaves << std::endl;
// std::cout << "Empty leaves: " << numEmptyLeaves << std::endl;
std::cout << "Leaves with identical entries: " << numIdenticalEntries << std::endl;
std::cout << "Leaves with identical histograms: " << numIdenticalHistograms << std::endl;
std::cout << "Leaves with kolmo histograms: " << numKolmogorovHistograms << std::endl;
std::cout << "Test passed. All leaves of all branches are exactly identical." << std::endl;

return 0;
}

Bool_t CheckEntriesIdentical(TH1* compHist, std::stringstream& outstream)
{
if ((TMath::Abs(compHist->GetMean()) > 0.000001 && TMath::Abs(compHist->GetRMS()) > 0.000001)
|| (0 != compHist->GetBinContent(0)) || (0 != compHist->GetBinContent(compHist->GetNbinsX() + 1))) {
return kFALSE;
} else {
outstream << "Entries are identical." << std::endl;
outstream << "**********************" << std::endl;
return kTRUE;
}
}

Bool_t CheckHistogramIdentical(TH1* origHist, TH1* newHist, std::stringstream& outstream)
{
if (origHist && newHist) {
outstream << "Comparing histograms" << std::endl;
for (int x = 0; x < origHist->GetNbinsX() + 1; ++x) {
if (origHist->GetBinContent(x) != newHist->GetBinContent(x)) {
return kFALSE;
}
}
outstream << "Histograms are identical." << std::endl;
outstream << "**********************" << std::endl;
return kTRUE;
}
return kFALSE;
}

Bool_t CheckHistogramKolmogorov(TH1* origHist, TH1* newHist, std::stringstream& outstream)
{
Double_t kolmo = origHist->KolmogorovTest(newHist);

outstream << "Result of Kolmogorov test: " << kolmo << endl;
if (kolmo > 0.99) {
return kTRUE;
}

return kFALSE;
}

std::vector<std::pair<TString, TString>> GetLeafNames(TTree* cbmsim)
{

std::vector<TString> ListOfLeaves;

if (cbmsim) {
TObjArray* bla1 = cbmsim->GetListOfLeaves();
TIter myiter1(bla1);
TBranch* branch;
while ((branch = (TBranch*)myiter1.Next())) {
TString mystring = branch->GetName();
// cout << "Branch Name: " << mystring << endl;

// Generate leaf names for points, digis and hits
if (mystring.Contains("Point") || mystring.Contains("Digi") || mystring.Contains("Hit")) {
TObjArray* bla2 = mystring.Tokenize(".");
if (bla2->GetEntriesFast() == 4) {
TString _branch = ((TObjString*)(bla2->At(2)))->GetString();
TString _leaf = ((TObjString*)(bla2->At(3)))->GetString();
if (_leaf.EqualTo("fLink")) {
TString name = _branch + "." + _leaf + ".fLinks";
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fLinks.fFile");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fLinks.fType");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fLinks.fEntry");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fLinks.fIndex");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fLinks.fWeight");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fEntryNr.fFile");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fEntryNr.fType");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fEntryNr.fEntry");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fEntryNr.fIndex");
ListOfLeaves.emplace_back(_branch + "." + _leaf + ".fEntryNr.fWeight");
} else {
ListOfLeaves.emplace_back(_branch + "." + _leaf);
}
}
}

if (mystring.Contains("MCTrack")) {
TObjArray* bla2 = mystring.Tokenize(".");
if (bla2->GetEntriesFast() == 4) {
TString _branch = ((TObjString*)(bla2->At(2)))->GetString();
TString _leaf = ((TObjString*)(bla2->At(3)))->GetString();
ListOfLeaves.emplace_back(_branch + "." + _leaf);
}
}

if (mystring.Contains("GeoTracks")) {
continue;
}
}
}

std::vector<std::pair<TString, TString>> leaves;
for (auto const element : ListOfLeaves) {
TString nameTree2 = "tree2." + element;
leaves.emplace_back(make_pair(element, nameTree2));
}
return leaves;
}
73 changes: 73 additions & 0 deletions examples/scripts/checkfiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/bin/bash

# The script compares files with the same name from two different directories.
# The names of the files are defined in the scripts, the names of the two different
# directories are passed as parameters when executing the script.
# The listed files are produced when executing the test suite of FairRoot
# The script immediately fails if a difference between files is found
# The actaul comparison is done using a ROOT macro which comapares the leaves of
# the ROOT tree ony by one.

if [[ "$#" -ne 2 ]]; then
echo "Wrong number of arguments"
echo "The script expects two directories as arguments"
exit 1
fi

dir1=$1
dir2=$2

files=(
simulation/Tutorial1/macros/tutorial1_TGeant4_pions.mc_p2.000_t0_n1.root
simulation/Tutorial1/macros/tutorial1_TGeant4_pions.mc_p2.000_t0_n10.root
simulation/Tutorial1/macros/tutorial1_pythia8_TGeant4_pions.mc_p2.000_t0_n10.root
simulation/Tutorial1/macros/tutorial1_pythia6_TGeant4_pions.mc_p2.000_t0_n10.root
simulation/Tutorial1/macros/tutorial1_urqmd_TGeant4.mc.root

simulation/Tutorial2/macros/tutorial2_pions.mc_p2.000_t0_n10.root
simulation/Tutorial2/macros/tutorial2_pions.mc_p2.000_t0_n10.sg1.root
simulation/Tutorial2/macros/tutorial2_pions.mc_p2.000_t0_n20.sg2.root
simulation/Tutorial2/macros/tutorial2_pions.mc_p2.000_t0_n130.bg.root
#simulation/Tutorial2/macros/digis.mc.root
#simulation/Tutorial2/macros/digis.mix.mc.root

simulation/Tutorial4/macros/data/testrun_align_TGeant4.root
simulation/Tutorial4/macros/data/testreco_align_TGeant4.root
#simulation/Tutorial4/macros/data/test.ana.root

simulation/rutherford/macros/data/test_TGeant4.mc.root
simulation/rutherford/macros/data/test1_TGeant4.mc.root

advanced/propagator/macros/prop.mc.root
#advanced/propagator/macros/prop.rk.cal.root

advanced/Tutorial3/macro/data/testrun_TGeant4.root
advanced/Tutorial3/macro/data/testdigi_TGeant4.root
advanced/Tutorial3/macro/data/testreco_TGeant4.root
advanced/Tutorial3/macro/data/testDiRePr_TGeant4.root
#advanced/Tutorial3/macro/data/testrecotimebased_TGeant4.root

#MQ/serialization/data_io/testinput1.root
#MQ/serialization/data_io/outputEx1Test.root
#MQ/serialization/data_io/testinput2.root
#MQ/serialization/data_io/outputEx2Test.root
#MQ/pixelDetector/macros/pixel_TGeant4.mc.root
#MQ/pixelDetector/macros/pixel_TGeant4.digi.root
#MQ/pixelDetector/macros/pixel_TGeant4.digiToBin.root
#MQ/pixelDetector/macros/MQ.pixel_TGeant4.hits.root
#MQ/pixelSimSplit/run/MQ.simulation_TGeant4.data.root
)

for i in "${files[@]}"; do
file1=$dir1/$i
file2=$dir2/$i

root -l -b -q "$VMCWORKDIR/scripts/TreeCompareAuto.C(\"$file1\", \"$file2\")"
bla=$?
if [ "$bla" != "0" ]; then
echo "Files $file1 and $file2 are different"
exit 1
else
echo "Files $file1 and $file2 are identical"
fi
done

0 comments on commit 4d8743c

Please sign in to comment.