Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-15 09:42:23

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 "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/TrackFitting/detail/GsfUtils.hpp"
0013 
0014 #include <algorithm>
0015 #include <array>
0016 #include <cassert>
0017 #include <cmath>
0018 #include <cstddef>
0019 #include <fstream>
0020 #include <mutex>
0021 #include <numbers>
0022 #include <random>
0023 #include <stdexcept>
0024 #include <string>
0025 #include <tuple>
0026 
0027 #include <boost/container/static_vector.hpp>
0028 
0029 namespace Acts {
0030 
0031 namespace detail {
0032 
0033 struct GaussianComponent {
0034   double weight = 0.0;
0035   double mean = 0.0;
0036   double var = 0.0;
0037 };
0038 
0039 /// Transform a gaussian component to a space where all values are defined from
0040 /// [-inf, inf]
0041 inline void transformComponent(const GaussianComponent &cmp,
0042                                double &transformed_weight,
0043                                double &transformed_mean,
0044                                double &transformed_var) {
0045   const auto &[weight, mean, var] = cmp;
0046 
0047   transformed_weight = std::log(weight) - std::log(1 - weight);
0048   transformed_mean = std::log(mean) - std::log(1 - mean);
0049   transformed_var = std::log(var);
0050 }
0051 
0052 /// Transform a gaussian component back from the [-inf, inf]-space to the usual
0053 /// space
0054 inline auto inverseTransformComponent(double transformed_weight,
0055                                       double transformed_mean,
0056                                       double transformed_var) {
0057   GaussianComponent cmp;
0058   cmp.weight = 1. / (1 + std::exp(-transformed_weight));
0059   cmp.mean = 1. / (1 + std::exp(-transformed_mean));
0060   cmp.var = std::exp(transformed_var);
0061 
0062   return cmp;
0063 }
0064 
0065 }  // namespace detail
0066 
0067 /// This class approximates the Bethe-Heitler with only one component. This is
0068 /// mainly inside @ref AtlasBetheHeitlerApprox, but can also be used as the
0069 /// only component approximation (then probably for debugging)
0070 struct BetheHeitlerApproxSingleCmp {
0071   /// Returns the number of components the returned mixture will have
0072   /// @return Number of components (always 1 for single component approximation)
0073   constexpr auto numComponents() const { return 1; }
0074 
0075   /// Checks if an input is valid for the parameterization. The threshold for
0076   /// x/x0 is 0.002 and orientates on the values used in ATLAS
0077   /// @param x The x/x0 value to check
0078   /// @return True if x/x0 is below the threshold for single component approximation
0079   constexpr bool validXOverX0(double x) const {
0080     return x < 0.002;
0081     ;
0082   }
0083 
0084   /// Returns array with length 1 containing a 1-component-representation of the
0085   /// Bethe-Heitler-Distribution
0086   ///
0087   /// @param x pathlength in terms of the radiation length
0088   /// @return Array containing single Gaussian component approximating Bethe-Heitler distribution
0089   static auto mixture(const double x) {
0090     std::array<detail::GaussianComponent, 1> ret{};
0091 
0092     ret[0].weight = 1.0;
0093 
0094     const double c = x / std::numbers::ln2;
0095     ret[0].mean = std::pow(2, -c);
0096     ret[0].var = std::pow(3, -c) - std::pow(4, -c);
0097 
0098     return ret;
0099   }
0100 };
0101 
0102 /// This class approximates the Bethe-Heitler distribution as a gaussian
0103 /// mixture. To enable an approximation for continuous input variables, the
0104 /// weights, means and variances are internally parametrized as a Nth order
0105 /// polynomial.
0106 /// @todo This class is rather inflexible: It forces two data representations,
0107 /// making it a bit awkward to add a single parameterization. It would be good
0108 /// to generalize this at some point.
0109 template <int NComponents, int PolyDegree>
0110 class AtlasBetheHeitlerApprox {
0111   static_assert(NComponents > 0);
0112   static_assert(PolyDegree > 0);
0113 
0114  public:
0115   struct PolyData {
0116     std::array<double, PolyDegree + 1> weightCoeffs;
0117     std::array<double, PolyDegree + 1> meanCoeffs;
0118     std::array<double, PolyDegree + 1> varCoeffs;
0119   };
0120 
0121   /// Type alias for array of polynomial data for all components
0122   using Data = std::array<PolyData, NComponents>;
0123 
0124  private:
0125   Data m_lowData;
0126   Data m_highData;
0127   bool m_lowTransform;
0128   bool m_highTransform;
0129 
0130   constexpr static double m_noChangeLimit = 0.0001;
0131   constexpr static double m_singleGaussianLimit = 0.002;
0132   double m_lowLimit = 0.10;
0133   double m_highLimit = 0.20;
0134   bool m_clampToRange = false;
0135 
0136  public:
0137   /// Construct the Bethe-Heitler approximation description with two
0138   /// parameterizations, one for lower ranges, one for higher ranges.
0139   /// Is it assumed that the lower limit of the high-x/x0 data is equal
0140   /// to the upper limit of the low-x/x0 data.
0141   ///
0142   /// @param lowData data for the lower x/x0 range
0143   /// @param highData data for the higher x/x0 range
0144   /// @param lowTransform whether the low data need to be transformed
0145   /// @param highTransform whether the high data need to be transformed
0146   /// @param lowLimit the upper limit for the low data
0147   /// @param highLimit the upper limit for the high data
0148   /// @param clampToRange whether to clamp the input x/x0 to the allowed range
0149   constexpr AtlasBetheHeitlerApprox(const Data &lowData, const Data &highData,
0150                                     bool lowTransform, bool highTransform,
0151                                     double lowLimit = 0.1,
0152                                     double highLimit = 0.2,
0153                                     bool clampToRange = false)
0154       : m_lowData(lowData),
0155         m_highData(highData),
0156         m_lowTransform(lowTransform),
0157         m_highTransform(highTransform),
0158         m_lowLimit(lowLimit),
0159         m_highLimit(highLimit),
0160         m_clampToRange(clampToRange) {}
0161 
0162   /// Returns the number of components the returned mixture will have
0163   /// @return Number of components in the mixture (template parameter NComponents)
0164   constexpr auto numComponents() const { return NComponents; }
0165 
0166   /// Checks if an input is valid for the parameterization
0167   ///
0168   /// @param x pathlength in terms of the radiation length
0169   /// @return True if x/x0 is within valid range for this parameterization
0170   constexpr bool validXOverX0(double x) const {
0171     if (m_clampToRange) {
0172       return true;
0173     } else {
0174       return x < m_highLimit;
0175     }
0176   }
0177 
0178   /// Generates the mixture from the polynomials and reweights them, so
0179   /// that the sum of all weights is 1
0180   ///
0181   /// @param x pathlength in terms of the radiation length
0182   /// @return Vector of Gaussian components representing the Bethe-Heitler distribution
0183   auto mixture(double x) const {
0184     using Array =
0185         boost::container::static_vector<detail::GaussianComponent, NComponents>;
0186 
0187     if (m_clampToRange) {
0188       x = std::clamp(x, 0.0, m_highLimit);
0189     }
0190 
0191     // Build a polynom
0192     auto poly = [](double xx,
0193                    const std::array<double, PolyDegree + 1> &coeffs) {
0194       double sum{0.};
0195       for (const auto c : coeffs) {
0196         sum = xx * sum + c;
0197       }
0198       assert((std::isfinite(sum) && "polynom result not finite"));
0199       return sum;
0200     };
0201 
0202     // Lambda which builds the components
0203     auto make_mixture = [&](const Data &data, double xx, bool transform) {
0204       // Value initialization should garanuee that all is initialized to zero
0205       Array ret(NComponents);
0206       double weight_sum = 0;
0207       for (int i = 0; i < NComponents; ++i) {
0208         // These transformations must be applied to the data according to ATHENA
0209         // (TrkGaussianSumFilter/src/GsfCombinedMaterialEffects.cxx:79)
0210         if (transform) {
0211           ret[i] = detail::inverseTransformComponent(
0212               poly(xx, data[i].weightCoeffs), poly(xx, data[i].meanCoeffs),
0213               poly(xx, data[i].varCoeffs));
0214         } else {
0215           ret[i].weight = poly(xx, data[i].weightCoeffs);
0216           ret[i].mean = poly(xx, data[i].meanCoeffs);
0217           ret[i].var = poly(xx, data[i].varCoeffs);
0218         }
0219 
0220         weight_sum += ret[i].weight;
0221       }
0222 
0223       for (int i = 0; i < NComponents; ++i) {
0224         ret[i].weight /= weight_sum;
0225       }
0226 
0227       return ret;
0228     };
0229 
0230     // Return no change
0231     if (x < m_noChangeLimit) {
0232       Array ret(1);
0233 
0234       ret[0].weight = 1.0;
0235       ret[0].mean = 1.0;  // p_initial = p_final
0236       ret[0].var = 0.0;
0237 
0238       return ret;
0239     }
0240     // Return single gaussian approximation
0241     if (x < m_singleGaussianLimit) {
0242       Array ret(1);
0243       ret[0] = BetheHeitlerApproxSingleCmp::mixture(x)[0];
0244       return ret;
0245     }
0246     // Return a component representation for lower x0
0247     if (x < m_lowLimit) {
0248       return make_mixture(m_lowData, x, m_lowTransform);
0249     }
0250     // Return a component representation for higher x0
0251     // Cap the x because beyond the parameterization goes wild
0252     const auto high_x = std::min(m_highLimit, x);
0253     return make_mixture(m_highData, high_x, m_highTransform);
0254   }
0255 
0256   /// Loads a parameterization from a file according to the Atlas file
0257   /// description
0258   ///
0259   /// @param low_parameters_path Path to the foo.par file that stores
0260   /// the parameterization for low x/x0
0261   /// @param high_parameters_path Path to the foo.par file that stores
0262   /// the parameterization for high x/x0
0263   /// @param lowLimit the upper limit for the low x/x0-data
0264   /// @param highLimit the upper limit for the high x/x0-data
0265   /// @param clampToRange forwarded to constructor
0266   /// @return AtlasBetheHeitlerApprox instance loaded from parameter files
0267   static auto loadFromFiles(const std::string &low_parameters_path,
0268                             const std::string &high_parameters_path,
0269                             double lowLimit = 0.1, double highLimit = 0.2,
0270                             bool clampToRange = false) {
0271     auto read_file = [](const std::string &filepath) {
0272       std::ifstream file(filepath);
0273 
0274       if (!file) {
0275         throw std::invalid_argument("Could not open '" + filepath + "'");
0276       }
0277 
0278       std::size_t n_cmps = 0, degree = 0;
0279       bool transform_code = false;
0280 
0281       file >> n_cmps >> degree >> transform_code;
0282 
0283       if (NComponents != n_cmps) {
0284         throw std::invalid_argument("Wrong number of components in '" +
0285                                     filepath + "'");
0286       }
0287 
0288       if (PolyDegree != degree) {
0289         throw std::invalid_argument("Wrong polynom order in '" + filepath +
0290                                     "'");
0291       }
0292 
0293       Data data;
0294 
0295       for (auto &cmp : data) {
0296         for (auto &coeff : cmp.weightCoeffs) {
0297           file >> coeff;
0298         }
0299         for (auto &coeff : cmp.meanCoeffs) {
0300           file >> coeff;
0301         }
0302         for (auto &coeff : cmp.varCoeffs) {
0303           file >> coeff;
0304         }
0305       }
0306 
0307       return std::make_tuple(data, transform_code);
0308     };
0309 
0310     const auto [lowData, lowTransform] = read_file(low_parameters_path);
0311     const auto [highData, highTransform] = read_file(high_parameters_path);
0312 
0313     return AtlasBetheHeitlerApprox(lowData, highData, lowTransform,
0314                                    highTransform, lowLimit, highLimit,
0315                                    clampToRange);
0316   }
0317 };
0318 
0319 /// Creates a @ref AtlasBetheHeitlerApprox object based on an ATLAS
0320 /// configuration, that are stored as static data in the source code.
0321 /// This may not be an optimal configuration, but should allow to run
0322 /// the GSF without the need to load files
0323 /// @param clampToRange Whether to clamp values to the valid range
0324 /// @return AtlasBetheHeitlerApprox with default ATLAS configuration parameters
0325 AtlasBetheHeitlerApprox<6, 5> makeDefaultBetheHeitlerApprox(
0326     bool clampToRange = false);
0327 
0328 }  // namespace Acts