File indexing completed on 2025-07-01 07:53:31
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025 #include <cstring>
0026 #include <map>
0027 #include <string>
0028 #include <utility>
0029 #include <vector>
0030
0031 #include "TBranch.h"
0032 #include "TEfficiency.h"
0033 #include "TFile.h"
0034 #include "TH2F.h"
0035 #include "TKey.h"
0036 #include "TObject.h"
0037 #include "TProfile.h"
0038 #include "TTree.h"
0039 #include "TTreeReader.h"
0040 #include "TVectorT.h"
0041 #include "compareRootFiles.hpp"
0042
0043
0044 #define ASSERT(pred, msg) \
0045 if (!(pred)) { \
0046 std::cout << msg << std::endl; \
0047 return 1; \
0048 }
0049
0050 #define ASSERT_EQUAL(v1, v2, msg) \
0051 ASSERT((v1) == (v2), msg << "(" << (v1) << " vs " << (v2) << ") ")
0052
0053 #define ASSERT_STR_EQUAL(s1, s2, msg) \
0054 ASSERT(strcmp((s1), (s2)) == 0, msg << " (" << (s1) << " vs " << (s2) << ") ")
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066 int compareRootFiles(const std::string& file1, const std::string& file2,
0067 bool dump_data_on_failure = false,
0068 bool skip_unsupported_branches = false) {
0069 std::cout << "Comparing ROOT files " << file1 << " and " << file2
0070 << std::endl;
0071
0072 std::cout << "* Opening the files..." << std::endl;
0073 HomogeneousPair<TFile> files{file1.c_str(), file2.c_str()};
0074 if (files.first.IsZombie()) {
0075 std::cout << " - Could not open file " << file1 << "!" << std::endl;
0076 return 2;
0077 } else if (files.second.IsZombie()) {
0078 std::cout << " - Could not open file " << file2 << "!" << std::endl;
0079 return 2;
0080 }
0081
0082 std::cout << "* Extracting file keys..." << std::endl;
0083 HomogeneousPair<std::vector<TKey*>> fileKeys;
0084 {
0085
0086 const auto loadKeys = [](const TFile& file, std::vector<TKey*>& target) {
0087 const int keyCount = file.GetNkeys();
0088 target.reserve(keyCount);
0089 TIter keyIter{file.GetListOfKeys()};
0090 for (int i = 0; i < keyCount; ++i) {
0091 target.emplace_back(dynamic_cast<TKey*>(keyIter()));
0092 }
0093 };
0094
0095
0096 loadKeys(files.first, fileKeys.first);
0097 loadKeys(files.second, fileKeys.second);
0098 }
0099
0100 std::cout << "* Selecting the latest key cycle..." << std::endl;
0101 std::vector<HomogeneousPair<TKey*>> keyPairs;
0102 {
0103
0104
0105 using KeyMetadata = std::pair<short, TKey*>;
0106 using FileMetadata = std::map<std::string, KeyMetadata>;
0107 HomogeneousPair<FileMetadata> metadata;
0108
0109
0110 const auto findLatestCycle = [](const std::vector<TKey*>& keys,
0111 FileMetadata& target) {
0112
0113 for (const auto key : keys) {
0114
0115 const std::string keyName{key->GetName()};
0116 const short newCycle{key->GetCycle()};
0117
0118
0119 auto latestCycleIter = target.find(keyName);
0120 if (latestCycleIter != target.end()) {
0121
0122 auto& latestCycleMetadata = latestCycleIter->second;
0123 if (newCycle > latestCycleMetadata.first) {
0124 latestCycleMetadata = {newCycle, key};
0125 }
0126 } else {
0127
0128
0129 target.emplace(keyName, KeyMetadata{newCycle, key});
0130 }
0131 }
0132 };
0133
0134
0135 std::cout << " - Finding the latest cycle for each file..." << std::endl;
0136 findLatestCycle(fileKeys.first, metadata.first);
0137 findLatestCycle(fileKeys.second, metadata.second);
0138
0139
0140
0141 std::cout << " - Grouping per-file latest keys..." << std::endl;
0142 {
0143
0144
0145 const auto f1KeyCount = metadata.first.size();
0146 const auto f2KeyCount = metadata.second.size();
0147 ASSERT_EQUAL(f1KeyCount, f2KeyCount,
0148 " o Number of keys does not match");
0149 keyPairs.reserve(f1KeyCount);
0150
0151
0152 for (auto f1MetadataIter = metadata.first.cbegin(),
0153 f2MetadataIter = metadata.second.cbegin();
0154 f1MetadataIter != metadata.first.cend();
0155 ++f1MetadataIter, ++f2MetadataIter) {
0156
0157 const auto& f1KeyName = f1MetadataIter->first;
0158 const auto& f2KeyName = f2MetadataIter->first;
0159 ASSERT_EQUAL(f1KeyName, f2KeyName, " o Key names do not match");
0160
0161
0162 keyPairs.emplace_back(f1MetadataIter->second.second,
0163 f2MetadataIter->second.second);
0164 }
0165 }
0166 }
0167
0168 std::cout << "* Comparing key metadata..." << std::endl;
0169 for (const auto& keyPair : keyPairs) {
0170 const auto& key1 = keyPair.first;
0171 const auto& key2 = keyPair.second;
0172
0173 ASSERT_STR_EQUAL(key1->GetClassName(), key2->GetClassName(),
0174 " - Class name does not match!");
0175 ASSERT_STR_EQUAL(key1->GetTitle(), key2->GetTitle(),
0176 " - Title does not match!");
0177 ASSERT_EQUAL(key1->GetVersion(), key2->GetVersion(),
0178 " - Key version does not match!");
0179 }
0180
0181
0182
0183 std::cout << "* Extracting TTrees..." << std::endl;
0184 std::vector<HomogeneousPair<TTree*>> treePairs;
0185 std::vector<HomogeneousPair<TVectorT<float>*>> vectorPairs;
0186 std::vector<HomogeneousPair<TEfficiency*>> efficiencyPairs;
0187 std::vector<HomogeneousPair<TProfile*>> profilePairs;
0188 std::vector<HomogeneousPair<TH2F*>> th2fPairs;
0189
0190 for (const auto& keyPair : keyPairs) {
0191 TObject* obj1 = keyPair.first->ReadObj();
0192 TObject* obj2 = keyPair.second->ReadObj();
0193
0194 ASSERT_STR_EQUAL(obj1->ClassName(), obj2->ClassName(),
0195 " - Object type does not match!");
0196
0197
0198 bool isTTree = (strcmp(obj1->ClassName(), "TTree") == 0);
0199
0200 if (isTTree) {
0201 TTree* tree1 = dynamic_cast<TTree*>(obj1);
0202 TTree* tree2 = dynamic_cast<TTree*>(obj2);
0203 if (tree1 != nullptr && tree2 != nullptr) {
0204 treePairs.emplace_back(tree1, tree2);
0205 }
0206 continue;
0207 }
0208
0209 bool isTVector = (strcmp(obj1->ClassName(), "TVectorT<float>") == 0);
0210
0211 if (isTVector) {
0212 TVectorT<float>* vector1 = dynamic_cast<TVectorT<float>*>(obj1);
0213 TVectorT<float>* vector2 = dynamic_cast<TVectorT<float>*>(obj2);
0214 if (vector1 != nullptr && vector2 != nullptr) {
0215 vectorPairs.emplace_back(vector1, vector2);
0216 }
0217 continue;
0218 }
0219
0220 bool isTEfficiency = (strcmp(obj1->ClassName(), "TEfficiency") == 0);
0221
0222 if (isTEfficiency) {
0223 TEfficiency* efficiency1 = dynamic_cast<TEfficiency*>(obj1);
0224 TEfficiency* efficiency2 = dynamic_cast<TEfficiency*>(obj2);
0225 if (efficiency1 != nullptr && efficiency2 != nullptr) {
0226 efficiencyPairs.emplace_back(efficiency1, efficiency2);
0227 }
0228 continue;
0229 }
0230
0231 bool isTProfile = (strcmp(obj1->ClassName(), "TProfile") == 0);
0232
0233 if (isTProfile) {
0234 TProfile* profile1 = dynamic_cast<TProfile*>(obj1);
0235 TProfile* profile2 = dynamic_cast<TProfile*>(obj2);
0236 if (profile1 != nullptr && profile2 != nullptr) {
0237 profilePairs.emplace_back(profile1, profile2);
0238 }
0239 continue;
0240 }
0241
0242 bool isTH2F = (strcmp(obj1->ClassName(), "TH2F") == 0);
0243
0244 if (isTH2F) {
0245 TH2F* th2f1 = dynamic_cast<TH2F*>(obj1);
0246 TH2F* th2f2 = dynamic_cast<TH2F*>(obj2);
0247 if (th2f1 != nullptr && th2f2 != nullptr) {
0248 th2fPairs.emplace_back(th2f1, th2f2);
0249 }
0250 continue;
0251 }
0252
0253 ASSERT(false, " - Input " << obj1->ClassName() << " is not supported!");
0254 }
0255
0256 std::cout << "* Comparing the trees..." << std::endl;
0257 for (const auto& treePair : treePairs) {
0258 const auto& tree1 = treePair.first;
0259 const auto& tree2 = treePair.second;
0260
0261 std::cout << " - Comparing tree " << tree1->GetName() << "..."
0262 << std::endl;
0263
0264 std::cout << " o Comparing tree-wide metadata..." << std::endl;
0265 const std::size_t t1EntryCount = tree1->GetEntries();
0266 {
0267 const std::size_t t2EntryCount = tree2->GetEntries();
0268 ASSERT_EQUAL(t1EntryCount, t2EntryCount,
0269 " ~ Number of entries does not match!");
0270 }
0271
0272 if (t1EntryCount == 0) {
0273 std::cout << " o Skipping empty tree!" << std::endl;
0274 continue;
0275 }
0276
0277 std::cout << " o Preparing for tree readout..." << std::endl;
0278 TTreeReader t1Reader(tree1);
0279 TTreeReader t2Reader(tree2);
0280 BranchComparisonHarness::TreeMetadata treeMetadata{t1Reader, t2Reader,
0281 t1EntryCount};
0282
0283 std::cout << " o Comparing branch metadata..." << std::endl;
0284 std::vector<HomogeneousPair<TBranch*>> branchPairs;
0285 {
0286
0287 const int t1BranchCount = tree1->GetNbranches();
0288 const int t2BranchCount = tree2->GetNbranches();
0289 ASSERT_EQUAL(t1BranchCount, t2BranchCount,
0290 " ~ Number of branches does not match!");
0291 branchPairs.reserve(t1BranchCount);
0292
0293
0294 TIter t1BranchIter{tree1->GetListOfBranches()};
0295 TIter t2BranchIter{tree2->GetListOfBranches()};
0296 for (int i = 0; i < t1BranchCount; ++i) {
0297 branchPairs.emplace_back(dynamic_cast<TBranch*>(t1BranchIter()),
0298 dynamic_cast<TBranch*>(t2BranchIter()));
0299 }
0300 }
0301
0302 std::cout << " o Setting up branch-specific processing..." << std::endl;
0303 std::vector<BranchComparisonHarness> branchComparisonHarnesses;
0304 branchComparisonHarnesses.reserve(branchPairs.size());
0305 for (const auto& branchPair : branchPairs) {
0306 const auto& branch1 = branchPair.first;
0307 const auto& branch2 = branchPair.second;
0308
0309 std::cout << " ~ Checking branch metadata..." << std::endl;
0310 std::string b1ClassName, b1BranchName;
0311 EDataType b1DataType{};
0312 {
0313 std::string b2ClassName, b2BranchName;
0314 EDataType b2DataType{};
0315 TClass* unused = nullptr;
0316
0317 b1ClassName = branch1->GetClassName();
0318 b2ClassName = branch2->GetClassName();
0319 ASSERT_EQUAL(b1ClassName, b2ClassName,
0320 " + Class name does not match!");
0321 branch1->GetExpectedType(unused, b1DataType);
0322 branch2->GetExpectedType(unused, b2DataType);
0323 ASSERT_EQUAL(b1DataType, b2DataType,
0324 " + Raw data type does not match!");
0325 const int b1LeafCount = branch1->GetNleaves();
0326 const int b2LeafCount = branch2->GetNleaves();
0327 ASSERT_EQUAL(b1LeafCount, b2LeafCount,
0328 " + Number of leaves does not match!");
0329 ASSERT_EQUAL(
0330 b1LeafCount, 1,
0331 " + Branches with several leaves are not supported!");
0332 b1BranchName = branch1->GetName();
0333 b2BranchName = branch2->GetName();
0334 ASSERT_EQUAL(b1BranchName, b2BranchName,
0335 " + Branch name does not match!");
0336 }
0337
0338 std::cout << " ~ Building comparison harness for branch "
0339 << b1BranchName << "..." << std::endl;
0340 try {
0341 auto branchHarness = BranchComparisonHarness::create(
0342 treeMetadata, b1BranchName, b1DataType, b1ClassName);
0343 branchComparisonHarnesses.emplace_back(std::move(branchHarness));
0344 } catch (const BranchComparisonHarness::UnsupportedBranchType&) {
0345
0346
0347 std::cout << " + Unsupported branch type! "
0348 << "(eDataType: " << b1DataType << ", ClassName: \""
0349 << b1ClassName << "\")" << std::endl;
0350 if (skip_unsupported_branches) {
0351 continue;
0352 } else {
0353 return 3;
0354 }
0355 }
0356 }
0357
0358 std::cout << " o Reading event data..." << std::endl;
0359 for (std::size_t i = 0; i < t1EntryCount; ++i) {
0360
0361 t1Reader.Next();
0362 t2Reader.Next();
0363
0364
0365 for (auto& branchHarness : branchComparisonHarnesses) {
0366 branchHarness.loadCurrentEvent();
0367 }
0368 }
0369
0370 std::cout << " o Sorting the first tree..." << std::endl;
0371 {
0372 std::cout << " ~ Defining event comparison operator..." << std::endl;
0373 IndexComparator t1CompareEvents = [&branchComparisonHarnesses](
0374 std::size_t i,
0375 std::size_t j) -> Ordering {
0376 for (auto& branchHarness : branchComparisonHarnesses) {
0377 const auto order = branchHarness.sortHarness.first.first(i, j);
0378 if (order != Ordering::EQUAL) {
0379 return order;
0380 }
0381 }
0382 return Ordering::EQUAL;
0383 };
0384
0385 std::cout << " ~ Defining event swapping operator..." << std::endl;
0386 IndexSwapper t1SwapEvents = [&branchComparisonHarnesses](std::size_t i,
0387 std::size_t j) {
0388 for (auto& branchHarness : branchComparisonHarnesses) {
0389 branchHarness.sortHarness.first.second(i, j);
0390 }
0391 };
0392
0393 std::cout << " ~ Running quicksort on the tree..." << std::endl;
0394 quickSort(0, t1EntryCount - 1, t1CompareEvents, t1SwapEvents);
0395 }
0396
0397 std::cout << " o Sorting the second tree..." << std::endl;
0398 {
0399 std::cout << " ~ Defining event comparison operator..." << std::endl;
0400 IndexComparator t2CompareEvents = [&branchComparisonHarnesses](
0401 std::size_t i,
0402 std::size_t j) -> Ordering {
0403 for (auto& branchHarness : branchComparisonHarnesses) {
0404 const auto order = branchHarness.sortHarness.second.first(i, j);
0405 if (order != Ordering::EQUAL) {
0406 return order;
0407 }
0408 }
0409 return Ordering::EQUAL;
0410 };
0411
0412 std::cout << " ~ Defining event swapping operator..." << std::endl;
0413 IndexSwapper t2SwapEvents = [&branchComparisonHarnesses](std::size_t i,
0414 std::size_t j) {
0415 for (auto& branchHarness : branchComparisonHarnesses) {
0416 branchHarness.sortHarness.second.second(i, j);
0417 }
0418 };
0419
0420 std::cout << " ~ Running quicksort on the tree..." << std::endl;
0421 quickSort(0, t1EntryCount - 1, t2CompareEvents, t2SwapEvents);
0422 }
0423
0424 std::cout << " o Checking that both trees are now equal..." << std::endl;
0425 for (auto& branchHarness : branchComparisonHarnesses) {
0426 std::cout << " ~ Comparing branch " << branchHarness.branchName
0427 << "..." << std::endl;
0428 if (!branchHarness.eventDataEqual()) {
0429 std::cout << " + Branch contents do not match!" << std::endl;
0430 if (dump_data_on_failure) {
0431 std::cout << " + Dumping branch contents:" << std::endl;
0432 branchHarness.dumpEventData();
0433 }
0434 return 4;
0435 }
0436 }
0437 }
0438
0439 std::cout << "* Comparing the vectors..." << std::endl;
0440 for (const auto& vectorPair : vectorPairs) {
0441 const auto& vector1 = vectorPair.first;
0442 const auto& vector2 = vectorPair.second;
0443
0444 std::cout << " - Comparing vector " << vector1->GetName() << "..."
0445 << std::endl;
0446
0447 std::cout << " o Comparing vector-wide metadata..." << std::endl;
0448 const std::size_t v1Size = vector1->GetNoElements();
0449 {
0450 const std::size_t v2Size = vector2->GetNoElements();
0451 ASSERT_EQUAL(v1Size, v2Size,
0452 " ~ Number of elements does not match!");
0453 }
0454
0455 if (v1Size == 0) {
0456 std::cout << " o Skipping empty vector!" << std::endl;
0457 continue;
0458 }
0459
0460 std::cout << " o Comparing vector data..." << std::endl;
0461 for (std::size_t i = 0; i < v1Size; ++i) {
0462 ASSERT_EQUAL(vector1->operator[](i), vector2->operator[](i),
0463 " ~ Vector elements do not match!");
0464 }
0465 }
0466
0467 std::cout << "* Comparing the efficiencies..." << std::endl;
0468 for (const auto& efficiencyPair : efficiencyPairs) {
0469 const auto& efficiency1 = efficiencyPair.first;
0470 const auto& efficiency2 = efficiencyPair.second;
0471
0472 std::cout << " - Comparing efficiency " << efficiency1->GetName() << "..."
0473 << std::endl;
0474
0475 std::cout << " o Comparing efficiency-wide metadata..." << std::endl;
0476 const auto e1Size = static_cast<std::size_t>(
0477 efficiency1->GetTotalHistogram()->GetEntries());
0478 {
0479 const auto e2Size = static_cast<std::size_t>(
0480 efficiency2->GetTotalHistogram()->GetEntries());
0481 ASSERT_EQUAL(e1Size, e2Size, " ~ Number of entries does not match!");
0482 }
0483
0484 if (e1Size == 0) {
0485 std::cout << " o Skipping empty efficiency!" << std::endl;
0486 continue;
0487 }
0488
0489 std::cout << " o Comparing efficiency data..." << std::endl;
0490 for (std::size_t i = 0; i < e1Size; ++i) {
0491 ASSERT_EQUAL(efficiency1->GetEfficiency(i), efficiency2->GetEfficiency(i),
0492 " ~ Efficiency elements do not match!");
0493 }
0494 }
0495
0496 std::cout << "* Comparing the profiles..." << std::endl;
0497 for (const auto& profilePair : profilePairs) {
0498 const auto& profile1 = profilePair.first;
0499 const auto& profile2 = profilePair.second;
0500
0501 std::cout << " - Comparing profile " << profile1->GetName() << "..."
0502 << std::endl;
0503
0504 std::cout << " o Comparing profile-wide metadata..." << std::endl;
0505 const auto p1Size = static_cast<std::size_t>(profile1->GetEntries());
0506 {
0507 const auto p2Size = static_cast<std::size_t>(profile2->GetEntries());
0508 ASSERT_EQUAL(p1Size, p2Size, " ~ Number of entries does not match!");
0509 }
0510
0511 if (p1Size == 0) {
0512 std::cout << " o Skipping empty profile!" << std::endl;
0513 continue;
0514 }
0515
0516 std::cout << " o Comparing profile data..." << std::endl;
0517 for (std::size_t i = 0; i < p1Size; ++i) {
0518 ASSERT_EQUAL(profile1->GetBinContent(i), profile2->GetBinContent(i),
0519 " ~ Profile elements do not match!");
0520 }
0521 }
0522
0523 std::cout << "* Comparing the TH2Fs..." << std::endl;
0524 for (const auto& th2fPair : th2fPairs) {
0525 const auto& th2f1 = th2fPair.first;
0526 const auto& th2f2 = th2fPair.second;
0527
0528 std::cout << " - Comparing TH2F " << th2f1->GetName() << "..."
0529 << std::endl;
0530
0531 std::cout << " o Comparing TH2F-wide metadata..." << std::endl;
0532 const auto th2f1Size = static_cast<std::size_t>(th2f1->GetEntries());
0533 {
0534 const auto th2f2Size = static_cast<std::size_t>(th2f2->GetEntries());
0535 ASSERT_EQUAL(th2f1Size, th2f2Size,
0536 " ~ Number of entries does not match!");
0537 }
0538
0539 if (th2f1Size == 0) {
0540 std::cout << " o Skipping empty TH2F!" << std::endl;
0541 continue;
0542 }
0543
0544 std::cout << " o Comparing TH2F data..." << std::endl;
0545 for (std::size_t i = 0; i < th2f1Size; ++i) {
0546 ASSERT_EQUAL(th2f1->GetBinContent(i), th2f2->GetBinContent(i),
0547 " ~ TH2F elements do not match!");
0548 }
0549 }
0550
0551 std::cout << "* Input files are equal, event order aside!" << std::endl;
0552 return 0;
0553 }
0554
0555 #ifndef __CLING__
0556 int main(int argc, char* argv[]) {
0557 std::string file1{};
0558 std::string file2{};
0559 bool dumpDataOnFailure = false;
0560 bool skipUnsupportedBranches = false;
0561
0562 std::vector<std::string> args(argv + 1, argv + argc);
0563
0564 if (args.size() < 2) {
0565 std::cerr << "Usage: " << argv[0]
0566 << " file1 file2 [--dump-data-on-failure] "
0567 "[--skip-unsupported-branches]\n";
0568 return 1;
0569 }
0570
0571 file1 = args[0];
0572 file2 = args[1];
0573
0574 for (size_t i = 2; i < args.size(); ++i) {
0575 if (args[i] == "--dump-data-on-failure") {
0576 dumpDataOnFailure = true;
0577 } else if (args[i] == "--skip-unsupported-branches") {
0578 skipUnsupportedBranches = true;
0579 } else {
0580 std::cerr << "Unknown option: " << args[i] << "\n";
0581 return 1;
0582 }
0583 }
0584
0585
0586 std::cout << "file1: " << file1 << "\n";
0587 std::cout << "file2: " << file2 << "\n";
0588 std::cout << "dumpDataOnFailure: " << (dumpDataOnFailure ? "true" : "false")
0589 << "\n";
0590 std::cout << "skipUnsupportedBranches: "
0591 << (skipUnsupportedBranches ? "true" : "false") << "\n";
0592
0593 const int result = compareRootFiles(file1, file2, dumpDataOnFailure,
0594 skipUnsupportedBranches);
0595
0596 return result;
0597 }
0598 #endif