Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:12:11

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include <cmath>
0012 #include <exception>
0013 #include <functional>
0014 #include <sstream>
0015 #include <vector>
0016 
0017 #include "TDictionary.h"
0018 #include "TTreeReaderValue.h"
0019 
0020 // Pairs of elements of the same type
0021 template <typename T>
0022 using HomogeneousPair = std::pair<T, T>;
0023 
0024 // === TYPE ERASURE FOR CONCRETE DATA ===
0025 
0026 // Minimal type-erasure wrapper for std::vector<T>. This will be used as a
0027 // workaround to compensate for the absence of C++17's std::any in Cling.
0028 class AnyVector {
0029  public:
0030   // Create a type-erased vector<T>, using proposed constructor arguments.
0031   // Returns a pair containing the type-erased vector and a pointer to the
0032   // underlying concrete vector.
0033   template <typename T, typename... Args>
0034   static std::pair<AnyVector, std::vector<T>*> create(Args&&... args) {
0035     std::vector<T>* vector = new std::vector<T>(std::forward<Args>(args)...);
0036     std::function<void()> deleter = [vector] { delete vector; };
0037     return {AnyVector{static_cast<void*>(vector), std::move(deleter)}, vector};
0038   }
0039 
0040   // Default-construct a null type-erased vector
0041   AnyVector() = default;
0042 
0043   // Move-construct a type-erased vector
0044   AnyVector(AnyVector&& other)
0045       : m_vector{other.m_vector}, m_deleter{std::move(other.m_deleter)} {
0046     other.m_vector = nullptr;
0047   }
0048 
0049   // Move-assign a type-erased vector
0050   AnyVector& operator=(AnyVector&& other) {
0051     if (&other != this) {
0052       m_vector = other.m_vector;
0053       m_deleter = std::move(other.m_deleter);
0054       other.m_vector = nullptr;
0055     }
0056     return *this;
0057   }
0058 
0059   // Forbid copies of type-erased vectors
0060   AnyVector(const AnyVector&) = delete;
0061   AnyVector& operator=(const AnyVector&) = delete;
0062 
0063   // Delete a type-erased vector
0064   ~AnyVector() {
0065     if (m_vector != nullptr) {
0066       m_deleter();
0067     }
0068   }
0069 
0070  private:
0071   // Construct a type-erased vector from a concrete vector
0072   AnyVector(void* vector, std::function<void()>&& deleter)
0073       : m_vector{vector}, m_deleter{std::move(deleter)} {}
0074 
0075   void* m_vector{nullptr};          // Casted std::vector<T>*
0076   std::function<void()> m_deleter;  // Deletes the underlying vector
0077 };
0078 
0079 // === GENERIC DATA ORDERING ===
0080 
0081 // We want to check, in a single operation, how two pieces of data are ordered
0082 enum class Ordering { SMALLER, EQUAL, GREATER };
0083 
0084 // In general, any type which implements comparison operators that behave as a
0085 // mathematical total order can use this comparison function...
0086 template <typename T>
0087 Ordering compare(const T& x, const T& y) {
0088   if (x < y) {
0089     return Ordering::SMALLER;
0090   } else if (x == y) {
0091     return Ordering::EQUAL;
0092   } else {
0093     return Ordering::GREATER;
0094   }
0095 }
0096 
0097 // ...but we'll want to tweak that a little for floats, to handle NaNs better...
0098 template <typename T>
0099 Ordering compareFloat(const T& x, const T& y) {
0100   if (std::isless(x, y)) {
0101     return Ordering::SMALLER;
0102   } else if (std::isgreater(x, y)) {
0103     return Ordering::GREATER;
0104   } else {
0105     return Ordering::EQUAL;
0106   }
0107 }
0108 
0109 template <>
0110 Ordering compare(const float& x, const float& y) {
0111   return compareFloat(x, y);
0112 }
0113 
0114 template <>
0115 Ordering compare(const double& x, const double& y) {
0116   return compareFloat(x, y);
0117 }
0118 
0119 // ...and for vectors, where the default lexicographic comparison cannot
0120 // efficiently tell all of what we want in a single vector iteration pass.
0121 template <typename U>
0122 Ordering compare(const std::vector<U>& v1, const std::vector<U>& v2) {
0123   // First try to order by size...
0124   if (v1.size() < v2.size()) {
0125     return Ordering::SMALLER;
0126   } else if (v1.size() > v2.size()) {
0127     return Ordering::GREATER;
0128   }
0129   // ...if the size is identical...
0130   else {
0131     // ...then try to order by contents of increasing index...
0132     for (std::size_t i = 0; i < v1.size(); ++i) {
0133       if (v1[i] < v2[i]) {
0134         return Ordering::SMALLER;
0135       } else if (v1[i] > v2[i]) {
0136         return Ordering::GREATER;
0137       }
0138     }
0139 
0140     // ...and declare the vectors equal if the contents are equal
0141     return Ordering::EQUAL;
0142   }
0143 }
0144 
0145 // === GENERIC SORTING MECHANISM ===
0146 
0147 // The following functions are generic implementations of sorting algorithms,
0148 // which require only a comparison operator, a swapping operator, and an
0149 // inclusive range of indices to be sorted in order to operate
0150 using IndexComparator = std::function<Ordering(std::size_t, std::size_t)>;
0151 using IndexSwapper = std::function<void(std::size_t, std::size_t)>;
0152 
0153 // Selection sort has pertty bad asymptotic scaling, but it is non-recursive
0154 // and in-place, which makes it a good choice for smaller inputs
0155 void selectionSort(const std::size_t firstIndex, const std::size_t lastIndex,
0156                    const IndexComparator& compare, const IndexSwapper& swap) {
0157   for (std::size_t targetIndex = firstIndex; targetIndex < lastIndex;
0158        ++targetIndex) {
0159     std::size_t minIndex = targetIndex;
0160     for (std::size_t readIndex = targetIndex + 1; readIndex <= lastIndex;
0161          ++readIndex) {
0162       if (compare(readIndex, minIndex) == Ordering::SMALLER) {
0163         minIndex = readIndex;
0164       }
0165     }
0166     if (minIndex != targetIndex) {
0167       swap(minIndex, targetIndex);
0168     }
0169   }
0170 }
0171 
0172 // Quick sort is used as the top-level sorting algorithm for our datasets
0173 void quickSort(const std::size_t firstIndex, const std::size_t lastIndex,
0174                const IndexComparator& compare, const IndexSwapper& swap) {
0175   // We switch to non-recursive selection sort when the range becomes too small.
0176   // This optimization voids the need for detection of 0- and 1-element input.
0177   static const std::size_t NON_RECURSIVE_THRESHOLD = 25;
0178   if (lastIndex - firstIndex < NON_RECURSIVE_THRESHOLD) {
0179     selectionSort(firstIndex, lastIndex, compare, swap);
0180     return;
0181   }
0182 
0183   // We'll use the midpoint as a pivot. Later on, we can switch to more
0184   // elaborate pivot selection schemes if their usefulness for our use case
0185   // (pseudorandom events with thread-originated reordering) is demonstrated.
0186   std::size_t pivotIndex = firstIndex + (lastIndex - firstIndex) / 2;
0187 
0188   // Partition the data around the pivot using Hoare's scheme
0189   std::size_t splitIndex = 0;
0190   {
0191     // Start with two indices one step beyond each side of the array
0192     std::size_t i = firstIndex - 1;
0193     std::size_t j = lastIndex + 1;
0194     while (true) {
0195       // Move left index forward at least once, and until an element which is
0196       // greater than or equal to the pivot is detected.
0197       do {
0198         i = i + 1;
0199       } while (compare(i, pivotIndex) == Ordering::SMALLER);
0200 
0201       // Move right index backward at least once, and until an element which is
0202       // smaller than or equal to the pivot is detected
0203       do {
0204         j = j - 1;
0205       } while (compare(j, pivotIndex) == Ordering::GREATER);
0206 
0207       // By transitivity of inequality, the element at location i is greater
0208       // than or equal to the one at location j, and a swap could be required
0209       if (i < j) {
0210         // These elements are in the wrong order, swap them
0211         swap(i, j);
0212 
0213         // Don't forget to keep track the pivot's index along the way, as this
0214         // is currently the only way by which we can refer to the pivot element.
0215         if (i == pivotIndex) {
0216           pivotIndex = j;
0217         } else if (j == pivotIndex) {
0218           pivotIndex = i;
0219         }
0220       } else {
0221         // If i and j went past each other, our partitioning is done
0222         splitIndex = j;
0223         break;
0224       }
0225     }
0226   }
0227 
0228   // Now, we'll recursively sort both partitions using quicksort. We should
0229   // recurse in the smaller range first, so as to leverage compiler tail call
0230   // optimization if available.
0231   if (splitIndex - firstIndex <= lastIndex - splitIndex - 1) {
0232     quickSort(firstIndex, splitIndex, compare, swap);
0233     quickSort(splitIndex + 1, lastIndex, compare, swap);
0234   } else {
0235     quickSort(splitIndex + 1, lastIndex, compare, swap);
0236     quickSort(firstIndex, splitIndex, compare, swap);
0237   }
0238 }
0239 
0240 // === GENERIC TTREE BRANCH MANIPULATION MECHANISM ===
0241 
0242 // When comparing a pair of TTrees, we'll need to set up quite a few facilities
0243 // for each branch. Since this setup is dependent on the branch data type, which
0244 // is only known at runtime, it is quite involved, which is why we extracted it
0245 // to a separate struct and its constructor.
0246 struct BranchComparisonHarness {
0247   // We'll keep track of the branch name for debugging purposes
0248   std::string branchName;
0249 
0250   // Type-erased event data for the current branch, in both trees being compared
0251   HomogeneousPair<AnyVector> eventData;
0252 
0253   // Function which loads the active event data for the current branch. This is
0254   // to be performed for each branch and combined with TTreeReader-based event
0255   // iteration on both trees.
0256   void loadCurrentEvent() { (*m_eventLoaderPtr)(); }
0257 
0258   // Functors which compare two events within a given tree and order them
0259   // with respect to one another, and which swap two events. By combining such
0260   // functionality for each branch, a global tree order can be produced.
0261   HomogeneousPair<std::pair<IndexComparator, IndexSwapper>> sortHarness;
0262 
0263   // Functor which compares the current event data in *both* trees and tells
0264   // whether it is identical. The comparison is order-sensitive, so events
0265   // should previously have been sorted in a canonical order in both trees.
0266   // By combining the results for each branch, global tree equality is defined.
0267   using TreeComparator = std::function<bool()>;
0268   TreeComparator eventDataEqual;
0269 
0270   // Functor which dumps the event data for the active event side by side, in
0271   // two columns. This enables manual comparison during debugging.
0272   std::function<void()> dumpEventData;
0273 
0274   // General metadata about the tree which is identical for every branch
0275   struct TreeMetadata {
0276     TTreeReader& tree1Reader;
0277     TTreeReader& tree2Reader;
0278     const std::size_t entryCount;
0279   };
0280 
0281   // This exception will be thrown if an unsupported branch type is encountered
0282   class UnsupportedBranchType : public std::exception {};
0283 
0284   // Type-erased factory of branch comparison harnesses, taking ROOT run-time
0285   // type information as input in order to select an appropriate C++ constructor
0286   static BranchComparisonHarness create(TreeMetadata& treeMetadata,
0287                                         const std::string& branchName,
0288                                         const EDataType dataType,
0289                                         const std::string& className) {
0290     switch (dataType) {
0291       case kChar_t:
0292         return BranchComparisonHarness::create<char>(treeMetadata, branchName);
0293       case kUChar_t:
0294         return BranchComparisonHarness::create<unsigned char>(treeMetadata,
0295                                                               branchName);
0296       case kShort_t:
0297         return BranchComparisonHarness::create<short>(treeMetadata, branchName);
0298       case kUShort_t:
0299         return BranchComparisonHarness::create<unsigned short>(treeMetadata,
0300                                                                branchName);
0301       case kInt_t:
0302         return BranchComparisonHarness::create<int>(treeMetadata, branchName);
0303       case kUInt_t:
0304         return BranchComparisonHarness::create<unsigned int>(treeMetadata,
0305                                                              branchName);
0306       case kLong_t:
0307         return BranchComparisonHarness::create<long>(treeMetadata, branchName);
0308       case kULong_t:
0309         return BranchComparisonHarness::create<unsigned long>(treeMetadata,
0310                                                               branchName);
0311       case kULong64_t:
0312         return BranchComparisonHarness::create<unsigned long long>(treeMetadata,
0313                                                                    branchName);
0314 
0315       case kFloat_t:
0316         return BranchComparisonHarness::create<float>(treeMetadata, branchName);
0317       case kDouble_t:
0318         return BranchComparisonHarness::create<double>(treeMetadata,
0319                                                        branchName);
0320       case kBool_t:
0321         return BranchComparisonHarness::create<bool>(treeMetadata, branchName);
0322       case kOther_t:
0323         if (className.substr(0, 6) == "vector") {
0324           std::string elementType = className.substr(7, className.size() - 8);
0325           return BranchComparisonHarness::createVector(treeMetadata, branchName,
0326                                                        std::move(elementType));
0327         } else {
0328           throw UnsupportedBranchType();
0329         }
0330       default:
0331         throw UnsupportedBranchType();
0332     }
0333   }
0334 
0335  private:
0336   // Under the hood, the top-level factory calls the following function
0337   // template, parametrized with the proper C++ data type
0338   template <typename T>
0339   static BranchComparisonHarness create(TreeMetadata& treeMetadata,
0340                                         const std::string& branchName) {
0341     // Our result will eventually go there
0342     BranchComparisonHarness result;
0343 
0344     // Save the branch name for debugging purposes
0345     result.branchName = branchName;
0346 
0347     // Setup type-erased event data storage
0348     auto tree1DataStorage = AnyVector::create<T>();
0349     auto tree2DataStorage = AnyVector::create<T>();
0350     result.eventData = std::make_pair(std::move(tree1DataStorage.first),
0351                                       std::move(tree2DataStorage.first));
0352     std::vector<T>& tree1Data = *tree1DataStorage.second;
0353     std::vector<T>& tree2Data = *tree2DataStorage.second;
0354 
0355     // Use our advance knowledge of the event count to preallocate storage
0356     tree1Data.reserve(treeMetadata.entryCount);
0357     tree2Data.reserve(treeMetadata.entryCount);
0358 
0359     // Setup event data readout
0360     result.m_eventLoaderPtr.reset(
0361         new EventLoaderT<T>{treeMetadata.tree1Reader, treeMetadata.tree2Reader,
0362                             branchName, tree1Data, tree2Data});
0363 
0364     // Setup event comparison and swapping for each tree
0365     result.sortHarness = std::make_pair(
0366         std::make_pair(
0367             [&tree1Data](std::size_t i, std::size_t j) -> Ordering {
0368               return compare(tree1Data[i], tree1Data[j]);
0369             },
0370             [&tree1Data](std::size_t i, std::size_t j) {
0371               std::swap(tree1Data[i], tree1Data[j]);
0372             }),
0373         std::make_pair(
0374             [&tree2Data](std::size_t i, std::size_t j) -> Ordering {
0375               return compare(tree2Data[i], tree2Data[j]);
0376             },
0377             [&tree2Data](std::size_t i, std::size_t j) {
0378               std::swap(tree2Data[i], tree2Data[j]);
0379             }));
0380 
0381     // Setup order-sensitive tree comparison
0382     result.eventDataEqual = [&tree1Data, &tree2Data]() -> bool {
0383       for (std::size_t i = 0; i < tree1Data.size(); ++i) {
0384         if (compare(tree1Data[i], tree2Data[i]) != Ordering::EQUAL) {
0385           return false;
0386         }
0387       }
0388       return true;
0389     };
0390 
0391     // Add a debugging method to dump event data
0392     result.dumpEventData = [&tree1Data, &tree2Data] {
0393       std::cout << "File 1                \tFile 2" << std::endl;
0394       for (std::size_t i = 0; i < tree1Data.size(); ++i) {
0395         std::cout << toString(tree1Data[i]) << "      \t"
0396                   << toString(tree2Data[i]) << std::endl;
0397       }
0398     };
0399 
0400     // ...and we're good to go!
0401     return std::move(result);
0402   }
0403 
0404   // Because the people who created TTreeReaderValue could not bother to make it
0405   // movable (for moving it into a lambda), or even just virtually destructible
0406   // (for moving a unique_ptr into the lambda), loadEventData can only be
0407   // implemented through lots of unpleasant C++98-ish boilerplate.
0408   class IEventLoader {
0409    public:
0410     virtual ~IEventLoader() = default;
0411     virtual void operator()() = 0;
0412   };
0413 
0414   template <typename T>
0415   class EventLoaderT : public IEventLoader {
0416    public:
0417     EventLoaderT(TTreeReader& tree1Reader, TTreeReader& tree2Reader,
0418                  const std::string& branchName, std::vector<T>& tree1Data,
0419                  std::vector<T>& tree2Data)
0420         : branch1Reader{tree1Reader, branchName.c_str()},
0421           branch2Reader{tree2Reader, branchName.c_str()},
0422           branch1Data(tree1Data),
0423           branch2Data(tree2Data) {}
0424 
0425     void operator()() override {
0426       branch1Data.push_back(*branch1Reader);
0427       branch2Data.push_back(*branch2Reader);
0428     }
0429 
0430    private:
0431     TTreeReaderValue<T> branch1Reader, branch2Reader;
0432     std::vector<T>& branch1Data;
0433     std::vector<T>& branch2Data;
0434   };
0435 
0436   std::unique_ptr<IEventLoader> m_eventLoaderPtr;
0437 
0438   // This helper factory helps building branches associated with std::vectors
0439   // of data, which are the only STL collection that we support at the moment.
0440   static BranchComparisonHarness createVector(TreeMetadata& treeMetadata,
0441                                               const std::string& branchName,
0442                                               const std::string elemType) {
0443 // We support vectors of different types by switching across type (strings)
0444 #define CREATE_VECTOR__HANDLE_TYPE(type_name)                       \
0445   if (elemType == #type_name) {                                     \
0446     return BranchComparisonHarness::create<std::vector<type_name>>( \
0447         treeMetadata, branchName);                                  \
0448   }
0449 
0450     // Handle vectors of booleans
0451     CREATE_VECTOR__HANDLE_TYPE(bool)
0452 
0453     // Handle vectors of all standard floating-point types
0454     else CREATE_VECTOR__HANDLE_TYPE(float) else CREATE_VECTOR__HANDLE_TYPE(
0455         double)
0456 
0457 // For integer types, we'll want to handle both signed and unsigned versions
0458 #define CREATE_VECTOR__HANDLE_INTEGER_TYPE(integer_type_name) \
0459   CREATE_VECTOR__HANDLE_TYPE(integer_type_name)               \
0460   else CREATE_VECTOR__HANDLE_TYPE(unsigned integer_type_name)
0461 
0462         // Handle vectors of all standard integer types
0463         else CREATE_VECTOR__HANDLE_INTEGER_TYPE(char) else CREATE_VECTOR__HANDLE_INTEGER_TYPE(short) else CREATE_VECTOR__HANDLE_INTEGER_TYPE(
0464             int) else CREATE_VECTOR__HANDLE_INTEGER_TYPE(long) else CREATE_VECTOR__HANDLE_INTEGER_TYPE(long long)
0465 
0466         // Throw an exception if the vector element type is not recognized
0467         else throw UnsupportedBranchType();
0468   }
0469 
0470   // This helper method provides general string conversion for all supported
0471   // branch event data types.
0472   template <typename T>
0473   static std::string toString(const T& data) {
0474     std::ostringstream oss;
0475     oss << data;
0476     return oss.str();
0477   }
0478 
0479   template <typename U>
0480   static std::string toString(const std::vector<U>& vector) {
0481     std::ostringstream oss{"{ "};
0482     for (const auto& data : vector) {
0483       oss << data << "  \t";
0484     }
0485     oss << " }";
0486     return oss.str();
0487   }
0488 };