Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-22 10:31:16

0001 //----------------------------------*-C++-*----------------------------------//
0002 // Copyright 2022-2024 UT-Battelle, LLC, and other Celeritas developers.
0003 // See the top-level COPYRIGHT file for details.
0004 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0005 //---------------------------------------------------------------------------//
0006 //! \file celeritas/em/distribution/EnergyLossUrbanDistribution.hh
0007 //---------------------------------------------------------------------------//
0008 #pragma once
0009 
0010 #include <cmath>
0011 
0012 #include "corecel/Assert.hh"
0013 #include "corecel/Macros.hh"
0014 #include "corecel/math/Algorithms.hh"
0015 #include "celeritas/em/data/FluctuationData.hh"
0016 #include "celeritas/random/distribution/PoissonDistribution.hh"
0017 #include "celeritas/random/distribution/UniformRealDistribution.hh"
0018 
0019 #include "EnergyLossGaussianDistribution.hh"
0020 #include "EnergyLossHelper.hh"
0021 
0022 namespace celeritas
0023 {
0024 //---------------------------------------------------------------------------//
0025 /*!
0026  * Sample from the Urban model of energy loss fluctuations in thin layers.
0027  *
0028  * The Urban model is used to compute the energy loss fluctuation when \f$
0029  * \kappa \f$ is small. It assumes atoms have only two energy levels with
0030  * binding energies \f$ E_1 \f$ and \f$ E_2 \f$ and that the particle-atom
0031  * interaction will either be an excitation with energy loss \f$ E_1 \f$ or \f$
0032  * E_2 \f$ or an ionization with energy loss proportional to \f$ 1 / E^2 \f$.
0033  * The number of collisions for each interaction type has a Poisson
0034  * distribution with mean proportional to the interaction cross section. The
0035  * energy loss in a step will be the sum of the energy loss contributions from
0036  * excitation and ionization.
0037  *
0038  * When the number of ionizations is larger than a given threshold, a fast
0039  * sampling method can be used instead. The possible energy loss interval is
0040  * divided into two parts: a lower range in which the number of collisions is
0041  * large and the energy loss can be sampled from a Gaussian distribution, and
0042  * an upper range in which the energy loss is sampled for each collision.
0043  *
0044  * See section 7.3.2 of the Geant4 Physics Reference Manual and GEANT3 PHYS332
0045  * section 2.4 for details.
0046  */
0047 class EnergyLossUrbanDistribution
0048 {
0049   public:
0050     //!@{
0051     //! \name Type aliases
0052     using FluctuationRef = NativeCRef<FluctuationData>;
0053     using Energy = units::MevEnergy;
0054     using Mass = units::MevMass;
0055     //!@}
0056 
0057   public:
0058     // Construct from particle properties
0059     inline CELER_FUNCTION
0060     EnergyLossUrbanDistribution(FluctuationRef const& shared,
0061                                 MaterialTrackView const& cur_mat,
0062                                 Energy unscaled_mean_loss,
0063                                 Energy max_energy,
0064                                 Mass two_mebsgs,
0065                                 real_type beta_sq);
0066 
0067     // Construct from helper-calculated data
0068     explicit inline CELER_FUNCTION
0069     EnergyLossUrbanDistribution(EnergyLossHelper const& helper);
0070 
0071     // Sample energy loss according to the distribution
0072     template<class Generator>
0073     inline CELER_FUNCTION Energy operator()(Generator& rng);
0074 
0075   private:
0076     //// TYPES ////
0077 
0078     using Real2 = Array<real_type, 2>;
0079 
0080     //// DATA ////
0081 
0082     real_type max_energy_;
0083     real_type loss_scaling_;
0084     Real2 binding_energy_;
0085     Real2 xs_exc_;
0086     real_type xs_ion_;
0087 
0088     //// CONSTANTS ////
0089 
0090     //! Relative contribution of ionization to energy loss
0091     static CELER_CONSTEXPR_FUNCTION real_type rate() { return 0.56; }
0092 
0093     //! Number of collisions above which to use faster sampling from Gaussian
0094     static CELER_CONSTEXPR_FUNCTION size_type max_collisions() { return 8; }
0095 
0096     //! Threshold number of excitations used in width correction
0097     static CELER_CONSTEXPR_FUNCTION real_type exc_thresh() { return 42; }
0098 
0099     //! Ionization energy [MeV]
0100     static CELER_CONSTEXPR_FUNCTION real_type ionization_energy()
0101     {
0102         return value_as<Energy>(EnergyLossHelper::ionization_energy());
0103     }
0104 
0105     //! Energy point below which FWHM scaling doesn't change [MeV]
0106     static CELER_CONSTEXPR_FUNCTION real_type fwhm_min_energy()
0107     {
0108         return 1e-3;  // 1 keV
0109     }
0110 
0111     //// HELPER FUNCTIONS ////
0112 
0113     template<class Engine>
0114     CELER_FUNCTION real_type sample_excitation_loss(Engine& rng);
0115 
0116     template<class Engine>
0117     CELER_FUNCTION real_type sample_ionization_loss(Engine& rng);
0118 
0119     template<class Engine>
0120     static CELER_FUNCTION real_type sample_fast_urban(real_type mean,
0121                                                       real_type stddev,
0122                                                       Engine& rng);
0123 };
0124 
0125 //---------------------------------------------------------------------------//
0126 // INLINE DEFINITIONS
0127 //---------------------------------------------------------------------------//
0128 /*!
0129  * Construct from distribution parameters.
0130  */
0131 CELER_FUNCTION EnergyLossUrbanDistribution::EnergyLossUrbanDistribution(
0132     FluctuationRef const& shared,
0133     MaterialTrackView const& cur_mat,
0134     Energy unscaled_mean_loss,
0135     Energy max_energy,
0136     Mass two_mebsgs,
0137     real_type beta_sq)
0138     : max_energy_(max_energy.value())
0139 {
0140     CELER_EXPECT(unscaled_mean_loss > zero_quantity());
0141     CELER_EXPECT(two_mebsgs > zero_quantity());
0142     CELER_EXPECT(beta_sq > 0);
0143 
0144     // Width correction: the FWHM of the energy loss distribution in thin
0145     // layers is in most cases too small; this width correction is used to get
0146     // more accurate FWHM values while keeping the mean loss the same by
0147     // rescaling the energy levels and number of excitations. The width
0148     // correction algorithm is discussed (though not in much detail) in PRM
0149     // section 7.3.3
0150     loss_scaling_
0151         = real_type(0.5)
0152               * min(this->fwhm_min_energy() / max_energy_, real_type(1))
0153           + real_type(1);
0154     real_type const mean_loss = unscaled_mean_loss.value() / loss_scaling_;
0155 
0156     // Material-dependent data
0157     CELER_ASSERT(cur_mat.material_id() < shared.urban.size());
0158     UrbanFluctuationParameters const& params
0159         = shared.urban[cur_mat.material_id()];
0160     binding_energy_ = params.binding_energy;
0161     xs_exc_ = {0, 0};
0162 
0163     // Calculate the excitation macroscopic cross sections and apply the width
0164     // correction
0165     auto const& mat = cur_mat.make_material_view();
0166     if (max_energy_ > value_as<Energy>(mat.mean_excitation_energy()))
0167     {
0168         // Common term in the numerator and denominator of PRM Eq. 7.10
0169         // two_mebsgs = 2 * m_e c^2 * beta^2 * gamma^2
0170         real_type const w = std::log(value_as<units::MevMass>(two_mebsgs))
0171                             - beta_sq;
0172         real_type const w_0
0173             = value_as<units::LogMevEnergy>(mat.log_mean_excitation_energy());
0174         if (w > w_0)
0175         {
0176             if (w > params.log_binding_energy[1])
0177             {
0178                 real_type const c = mean_loss * (1 - this->rate()) / (w - w_0);
0179                 for (int i : range(2))
0180                 {
0181                     // Excitation macroscopic cross section (PRM Eq. 7.10)
0182                     xs_exc_[i] = c * params.oscillator_strength[i]
0183                                  * (w - params.log_binding_energy[i])
0184                                  / params.binding_energy[i];
0185                 }
0186             }
0187             else
0188             {
0189                 xs_exc_[0] = mean_loss * (1 - this->rate())
0190                              / params.binding_energy[0];
0191             }
0192 
0193             // Scale the binding energy and macroscopic cross section (i.e.,
0194             // the mean number of excitations)
0195             real_type scaling = 4;
0196             if (xs_exc_[0] < this->exc_thresh())
0197             {
0198                 scaling = real_type(0.5)
0199                           + (scaling - real_type(0.5))
0200                                 * std::sqrt(xs_exc_[0] / this->exc_thresh());
0201             }
0202             binding_energy_[0] *= scaling;
0203             xs_exc_[0] /= scaling;
0204         }
0205     }
0206 
0207     // Calculate the ionization macroscopic cross section (PRM Eq. 7.11)
0208     constexpr real_type e_0 = EnergyLossUrbanDistribution::ionization_energy();
0209     xs_ion_ = mean_loss * (max_energy_ - e_0)
0210               / (max_energy_ * e_0 * std::log(max_energy_ / e_0));
0211     if (xs_exc_[0] + xs_exc_[1] > 0)
0212     {
0213         // The contribution from excitation is nonzero, so scale the ionization
0214         // cross section accordingly
0215         xs_ion_ *= this->rate();
0216     }
0217 }
0218 
0219 //---------------------------------------------------------------------------//
0220 /*!
0221  * Construct from helper-calculated data.
0222  */
0223 CELER_FUNCTION EnergyLossUrbanDistribution::EnergyLossUrbanDistribution(
0224     EnergyLossHelper const& helper)
0225     : EnergyLossUrbanDistribution(helper.shared(),
0226                                   helper.material(),
0227                                   helper.mean_loss(),
0228                                   helper.max_energy(),
0229                                   helper.two_mebsgs(),
0230                                   helper.beta_sq())
0231 {
0232 }
0233 
0234 //---------------------------------------------------------------------------//
0235 /*!
0236  * Sample energy loss according to the distribution.
0237  */
0238 template<class Generator>
0239 CELER_FUNCTION auto
0240 EnergyLossUrbanDistribution::operator()(Generator& rng) -> Energy
0241 {
0242     // Calculate actual energy loss from the loss contributions from excitation
0243     // and ionization
0244     real_type result = this->sample_excitation_loss(rng)
0245                        + this->sample_ionization_loss(rng);
0246 
0247     CELER_ENSURE(result >= 0);
0248     return Energy{loss_scaling_ * result};
0249 }
0250 
0251 //---------------------------------------------------------------------------//
0252 /*!
0253  * Calculate the energy loss contribution from excitation for the Urban model.
0254  */
0255 template<class Engine>
0256 CELER_FUNCTION real_type
0257 EnergyLossUrbanDistribution::sample_excitation_loss(Engine& rng)
0258 {
0259     real_type result = 0;
0260 
0261     // Mean and variance for fast sampling from Gaussian
0262     real_type mean = 0;
0263     real_type variance = 0;
0264 
0265     for (int i : range(2))
0266     {
0267         if (xs_exc_[i] > this->max_collisions())
0268         {
0269             // When the number of collisions is large, use faster approach
0270             // of sampling from a Gaussian
0271             mean += xs_exc_[i] * binding_energy_[i];
0272             variance += xs_exc_[i] * ipow<2>(binding_energy_[i]);
0273         }
0274         else if (xs_exc_[i] > 0)
0275         {
0276             // The loss due to excitation is \f$ \Delta E_{exc} = n_1 E_1 + n_2
0277             // E_2 \f$, where the number of collisions \f$ n_i \f$ is sampled
0278             // from a Poisson distribution with mean \f$ \Sigma_i \f$
0279             unsigned int n = PoissonDistribution<real_type>(xs_exc_[i])(rng);
0280             if (n > 0)
0281             {
0282                 UniformRealDistribution<real_type> sample_fraction(n - 1,
0283                                                                    n + 1);
0284                 result += sample_fraction(rng) * binding_energy_[i];
0285             }
0286         }
0287     }
0288     if (variance > 0)
0289     {
0290         // Sample excitation energy loss contribution from a Gaussian
0291         result += this->sample_fast_urban(mean, std::sqrt(variance), rng);
0292     }
0293     return result;
0294 }
0295 
0296 //---------------------------------------------------------------------------//
0297 /*!
0298  * Calculate the energy loss contribution from ionization for the Urban model.
0299  */
0300 template<class Engine>
0301 CELER_FUNCTION real_type
0302 EnergyLossUrbanDistribution::sample_ionization_loss(Engine& rng)
0303 {
0304     real_type result = 0;
0305 
0306     constexpr real_type e_0 = EnergyLossUrbanDistribution::ionization_energy();
0307     real_type const energy_ratio = max_energy_ / e_0;
0308 
0309     // Parameter that determines the upper limit of the energy interval in
0310     // which the fast simulation is used
0311     real_type alpha = 1;
0312 
0313     // Mean number of collisions in the fast simulation interval
0314     real_type mean_num_coll = 0;
0315 
0316     if (xs_ion_ > this->max_collisions())
0317     {
0318         // When the number of collisions is large, fast sampling from a
0319         // Gaussian is used in the lower portion of the energy loss interval.
0320         // See PHYS332 section 2.4: Fast simulation for \f$ n_3 \ge 16 \f$
0321 
0322         // Calculate the maximum value of \f$ \alpha \f$ (Eq. 25)
0323         alpha = (xs_ion_ + this->max_collisions()) * energy_ratio
0324                 / (this->max_collisions() * energy_ratio + xs_ion_);
0325 
0326         // Mean energy loss for a single collision of this type (Eq. 14)
0327         real_type const mean_loss_coll = alpha * std::log(alpha) / (alpha - 1);
0328 
0329         // Mean number of collisions of this type (Eq. 16)
0330         mean_num_coll = xs_ion_ * energy_ratio * (alpha - 1)
0331                         / ((energy_ratio - 1) * alpha);
0332 
0333         // Mean and standard deviation of the total energy loss (Eqs. 18-19)
0334         real_type const mean = mean_num_coll * mean_loss_coll * e_0;
0335         real_type const stddev
0336             = e_0 * std::sqrt(xs_ion_ * (alpha - ipow<2>(mean_loss_coll)));
0337 
0338         // Sample energy loss from a Gaussian distribution
0339         result += this->sample_fast_urban(mean, stddev, rng);
0340     }
0341     if (xs_ion_ > 0 && energy_ratio > alpha)
0342     {
0343         // Sample number of ionizations from a Poisson distribution with mean
0344         // \f$ n_3 - n_A \f$, where \f$ n_3 \f$ is the number of ionizations
0345         // and \f$ n_A \f$ is the number of ionizations in the energy interval
0346         // in which the fast sampling from a Gaussian is used
0347         PoissonDistribution<real_type> sample_num_ioni(xs_ion_ - mean_num_coll);
0348 
0349         // Add the contribution from ionizations in the energy interval in
0350         // which the energy loss is sampled for each collision (Eq. 20)
0351         UniformRealDistribution<real_type> sample_fraction(
0352             alpha / energy_ratio, 1);
0353         for (auto n = sample_num_ioni(rng); n > 0; --n)
0354         {
0355             result += alpha * e_0 / sample_fraction(rng);
0356         }
0357     }
0358     return result;
0359 }
0360 
0361 //---------------------------------------------------------------------------//
0362 /*!
0363  * Fast sampling of energy loss in thin absorbers from a Gaussian distribution.
0364  */
0365 template<class Engine>
0366 CELER_FUNCTION real_type EnergyLossUrbanDistribution::sample_fast_urban(
0367     real_type mean, real_type stddev, Engine& rng)
0368 {
0369     if (stddev <= 4 * mean)
0370     {
0371         EnergyLossGaussianDistribution sample_eloss(Energy{mean},
0372                                                     Energy{stddev});
0373         return value_as<Energy>(sample_eloss(rng));
0374     }
0375     else
0376     {
0377         UniformRealDistribution<real_type> sample(0, 2 * mean);
0378         return sample(rng);
0379     }
0380 }
0381 
0382 //---------------------------------------------------------------------------//
0383 }  // namespace celeritas