Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-19 09:23:37

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