285 lines
10 KiB
C++
285 lines
10 KiB
C++
#include "TBranch.h"
|
|
#include "TFile.h"
|
|
#include "TH1.h"
|
|
#include "TKey.h"
|
|
#include "TLeaf.h"
|
|
#include "TTree.h"
|
|
#include "TVectorD.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
struct KeyDescription {
|
|
std::string name;
|
|
std::string className;
|
|
short cycle;
|
|
|
|
auto asTuple() const { return std::tie(name,className,cycle); }
|
|
};
|
|
|
|
bool operator<(const KeyDescription& lhs, const KeyDescription& rhs)
|
|
{
|
|
return lhs.asTuple()<rhs.asTuple();
|
|
}
|
|
|
|
bool operator==(const KeyDescription& lhs, const KeyDescription& rhs)
|
|
{
|
|
return lhs.asTuple()==rhs.asTuple();
|
|
}
|
|
|
|
std::vector<KeyDescription> describeKeys(TFile& file)
|
|
{
|
|
std::vector<KeyDescription> keys;
|
|
TIter next(file.GetListOfKeys());
|
|
while (const auto* key = dynamic_cast<TKey*>(next())) {
|
|
keys.push_back({key->GetName(),key->GetClassName(),key->GetCycle()});
|
|
}
|
|
std::sort(keys.begin(),keys.end());
|
|
return keys;
|
|
}
|
|
|
|
std::string keyPath(const KeyDescription& key)
|
|
{
|
|
std::ostringstream path;
|
|
path<<key.name<<';'<<key.cycle;
|
|
return path.str();
|
|
}
|
|
|
|
bool isIntegralLeaf(const std::string& type)
|
|
{
|
|
return type=="Bool_t" || type=="Char_t" || type=="UChar_t" ||
|
|
type=="Short_t" || type=="UShort_t" || type=="Int_t" ||
|
|
type=="UInt_t" || type=="Long_t" || type=="ULong_t" ||
|
|
type=="Long64_t" || type=="ULong64_t";
|
|
}
|
|
|
|
bool compareTrees(TTree& baseline, TTree& candidate, const std::string& path)
|
|
{
|
|
if (std::string(baseline.GetTitle())!=candidate.GetTitle()) {
|
|
std::cerr<<path<<": tree title differs\n";
|
|
return false;
|
|
}
|
|
if (baseline.GetEntries()!=candidate.GetEntries()) {
|
|
std::cerr<<path<<": entry count differs: baseline="
|
|
<<baseline.GetEntries()<<", candidate="<<candidate.GetEntries()<<'\n';
|
|
return false;
|
|
}
|
|
|
|
const auto* baselineBranches = baseline.GetListOfBranches();
|
|
const auto* candidateBranches = candidate.GetListOfBranches();
|
|
if (baselineBranches->GetEntries()!=candidateBranches->GetEntries()) {
|
|
std::cerr<<path<<": branch count differs: baseline="
|
|
<<baselineBranches->GetEntries()<<", candidate="
|
|
<<candidateBranches->GetEntries()<<'\n';
|
|
return false;
|
|
}
|
|
|
|
struct LeafPair {
|
|
TLeaf* baseline;
|
|
TLeaf* candidate;
|
|
std::string location;
|
|
bool integral;
|
|
};
|
|
std::vector<LeafPair> leaves;
|
|
|
|
for (int branchIndex=0; branchIndex<baselineBranches->GetEntries(); ++branchIndex) {
|
|
auto* baselineBranch = dynamic_cast<TBranch*>(baselineBranches->At(branchIndex));
|
|
auto* candidateBranch = dynamic_cast<TBranch*>(candidateBranches->At(branchIndex));
|
|
if (!baselineBranch || !candidateBranch) {
|
|
std::cerr<<path<<": invalid branch at index "<<branchIndex<<'\n';
|
|
return false;
|
|
}
|
|
if (std::string(baselineBranch->GetName())!=candidateBranch->GetName() ||
|
|
std::string(baselineBranch->GetTitle())!=candidateBranch->GetTitle() ||
|
|
std::string(baselineBranch->GetClassName())!=candidateBranch->GetClassName()) {
|
|
std::cerr<<path<<": branch schema differs at index "<<branchIndex
|
|
<<" (baseline "<<baselineBranch->GetName()<<", candidate "
|
|
<<candidateBranch->GetName()<<")\n";
|
|
return false;
|
|
}
|
|
|
|
const auto* baselineLeaves = baselineBranch->GetListOfLeaves();
|
|
const auto* candidateLeaves = candidateBranch->GetListOfLeaves();
|
|
if (baselineLeaves->GetEntries()!=candidateLeaves->GetEntries()) {
|
|
std::cerr<<path<<'/'<<baselineBranch->GetName()<<": leaf count differs\n";
|
|
return false;
|
|
}
|
|
|
|
for (int leafIndex=0; leafIndex<baselineLeaves->GetEntries(); ++leafIndex) {
|
|
auto* baselineLeaf = dynamic_cast<TLeaf*>(baselineLeaves->At(leafIndex));
|
|
auto* candidateLeaf = dynamic_cast<TLeaf*>(candidateLeaves->At(leafIndex));
|
|
if (!baselineLeaf || !candidateLeaf) {
|
|
std::cerr<<path<<'/'<<baselineBranch->GetName()
|
|
<<": invalid leaf at index "<<leafIndex<<'\n';
|
|
return false;
|
|
}
|
|
|
|
const std::string baselineType = baselineLeaf->GetTypeName();
|
|
const auto* baselineCount = baselineLeaf->GetLeafCount();
|
|
const auto* candidateCount = candidateLeaf->GetLeafCount();
|
|
const std::string baselineCountName = baselineCount ? baselineCount->GetName() : "";
|
|
const std::string candidateCountName = candidateCount ? candidateCount->GetName() : "";
|
|
if (std::string(baselineLeaf->GetName())!=candidateLeaf->GetName() ||
|
|
std::string(baselineLeaf->GetTitle())!=candidateLeaf->GetTitle() ||
|
|
baselineType!=candidateLeaf->GetTypeName() ||
|
|
baselineLeaf->GetLenStatic()!=candidateLeaf->GetLenStatic() ||
|
|
baselineCountName!=candidateCountName) {
|
|
std::cerr<<path<<'/'<<baselineBranch->GetName()
|
|
<<": leaf schema differs at index "<<leafIndex<<'\n';
|
|
return false;
|
|
}
|
|
|
|
leaves.push_back({
|
|
baselineLeaf,
|
|
candidateLeaf,
|
|
path+"/"+baselineBranch->GetName()+"/"+baselineLeaf->GetName(),
|
|
isIntegralLeaf(baselineType)
|
|
});
|
|
}
|
|
}
|
|
|
|
for (Long64_t entry=0; entry<baseline.GetEntries(); ++entry) {
|
|
if (baseline.GetEntry(entry)<0 || candidate.GetEntry(entry)<0) {
|
|
std::cerr<<path<<": failed to read entry "<<entry<<'\n';
|
|
return false;
|
|
}
|
|
for (const auto& leaf : leaves) {
|
|
const int baselineSize = leaf.baseline->GetNdata();
|
|
const int candidateSize = leaf.candidate->GetNdata();
|
|
if (baselineSize!=candidateSize) {
|
|
std::cerr<<leaf.location<<", entry "<<entry
|
|
<<": array size differs: baseline="<<baselineSize
|
|
<<", candidate="<<candidateSize<<'\n';
|
|
return false;
|
|
}
|
|
for (int index=0; index<baselineSize; ++index) {
|
|
if (leaf.integral) {
|
|
const auto baselineValue = leaf.baseline->GetValueLong64(index);
|
|
const auto candidateValue = leaf.candidate->GetValueLong64(index);
|
|
if (baselineValue!=candidateValue) {
|
|
std::cerr<<leaf.location<<", entry "<<entry<<", index "<<index
|
|
<<": baseline="<<baselineValue
|
|
<<", candidate="<<candidateValue<<'\n';
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
const double baselineValue = leaf.baseline->GetValue(index);
|
|
const double candidateValue = leaf.candidate->GetValue(index);
|
|
const bool bothNan = std::isnan(baselineValue) && std::isnan(candidateValue);
|
|
if (!bothNan && baselineValue!=candidateValue) {
|
|
std::cerr<<std::setprecision(17)<<leaf.location<<", entry "<<entry
|
|
<<", index "<<index<<": baseline="<<baselineValue
|
|
<<", candidate="<<candidateValue<<'\n';
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool compareHistograms(TH1& baseline, TH1& candidate, const std::string& path)
|
|
{
|
|
if (std::string(baseline.GetTitle())!=candidate.GetTitle() ||
|
|
baseline.GetDimension()!=candidate.GetDimension() ||
|
|
baseline.GetNcells()!=candidate.GetNcells() ||
|
|
baseline.GetEntries()!=candidate.GetEntries()) {
|
|
std::cerr<<path<<": histogram metadata differs\n";
|
|
return false;
|
|
}
|
|
for (int bin=0; bin<baseline.GetNcells(); ++bin) {
|
|
if (baseline.GetBinContent(bin)!=candidate.GetBinContent(bin) ||
|
|
baseline.GetBinError(bin)!=candidate.GetBinError(bin)) {
|
|
std::cerr<<std::setprecision(17)<<path<<", bin "<<bin
|
|
<<": baseline content/error="<<baseline.GetBinContent(bin)<<'/'
|
|
<<baseline.GetBinError(bin)<<", candidate content/error="
|
|
<<candidate.GetBinContent(bin)<<'/'<<candidate.GetBinError(bin)<<'\n';
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool compareVectors(TVectorD& baseline, TVectorD& candidate, const std::string& path)
|
|
{
|
|
if (baseline.GetLwb()!=candidate.GetLwb() || baseline.GetUpb()!=candidate.GetUpb()) {
|
|
std::cerr<<path<<": vector bounds differ\n";
|
|
return false;
|
|
}
|
|
for (int index=baseline.GetLwb(); index<=baseline.GetUpb(); ++index) {
|
|
if (baseline[index]!=candidate[index]) {
|
|
std::cerr<<std::setprecision(17)<<path<<", index "<<index
|
|
<<": baseline="<<baseline[index]
|
|
<<", candidate="<<candidate[index]<<'\n';
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc!=3) {
|
|
std::cerr<<"usage: musrRootOutputCompare <baseline.root> <candidate.root>\n";
|
|
return 2;
|
|
}
|
|
|
|
std::unique_ptr<TFile> baseline(TFile::Open(argv[1],"READ"));
|
|
std::unique_ptr<TFile> candidate(TFile::Open(argv[2],"READ"));
|
|
if (!baseline || baseline->IsZombie() || !candidate || candidate->IsZombie()) {
|
|
std::cerr<<"could not open both ROOT files\n";
|
|
return 2;
|
|
}
|
|
|
|
const auto baselineKeys = describeKeys(*baseline);
|
|
const auto candidateKeys = describeKeys(*candidate);
|
|
if (baselineKeys!=candidateKeys) {
|
|
std::cerr<<"ROOT key inventory differs\n";
|
|
return 1;
|
|
}
|
|
|
|
for (const auto& key : baselineKeys) {
|
|
const std::string path = keyPath(key);
|
|
auto* baselineObject = baseline->Get(path.c_str());
|
|
auto* candidateObject = candidate->Get(path.c_str());
|
|
if (!baselineObject || !candidateObject) {
|
|
std::cerr<<path<<": could not read both objects\n";
|
|
return 2;
|
|
}
|
|
|
|
if (auto* baselineTree = dynamic_cast<TTree*>(baselineObject)) {
|
|
auto* candidateTree = dynamic_cast<TTree*>(candidateObject);
|
|
if (!candidateTree || !compareTrees(*baselineTree,*candidateTree,path)) return 1;
|
|
}
|
|
else if (auto* baselineHistogram = dynamic_cast<TH1*>(baselineObject)) {
|
|
auto* candidateHistogram = dynamic_cast<TH1*>(candidateObject);
|
|
if (!candidateHistogram ||
|
|
!compareHistograms(*baselineHistogram,*candidateHistogram,path)) return 1;
|
|
}
|
|
else if (auto* baselineVector = dynamic_cast<TVectorD*>(baselineObject)) {
|
|
auto* candidateVector = dynamic_cast<TVectorD*>(candidateObject);
|
|
if (!candidateVector || !compareVectors(*baselineVector,*candidateVector,path)) return 1;
|
|
}
|
|
else {
|
|
std::cerr<<path<<": unsupported ROOT object class "<<key.className<<'\n';
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
std::cout<<"ROOT outputs are exactly equal\n";
|
|
return 0;
|
|
}
|