Files
musrsim/tests/compare_root_outputs.cc

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;
}