Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-10 10:14:24

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file celeritas/em/interactor/AtomicRelaxation.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include "corecel/Assert.hh"
0010 #include "corecel/Macros.hh"
0011 #include "corecel/Types.hh"
0012 #include "corecel/cont/IdStack.hh"
0013 #include "corecel/cont/Span.hh"
0014 #include "corecel/random/distribution/IsotropicDistribution.hh"
0015 #include "corecel/random/distribution/Selector.hh"
0016 #include "celeritas/Quantities.hh"
0017 #include "celeritas/Types.hh"
0018 #include "celeritas/em/data/AtomicRelaxationData.hh"
0019 #include "celeritas/phys/CutoffView.hh"
0020 #include "celeritas/phys/Secondary.hh"
0021 
0022 namespace celeritas
0023 {
0024 //---------------------------------------------------------------------------//
0025 /*!
0026  * Simulate particle emission from atomic deexcitation.
0027  *
0028  * The EADL radiative and non-radiative transition data is used to simulate the
0029  * emission of fluorescence photons and (optionally) Auger electrons given an
0030  * initial shell vacancy created by a primary process.
0031  */
0032 class AtomicRelaxation
0033 {
0034   public:
0035     //!@{
0036     //! \name Type aliases
0037     using Energy = units::MevEnergy;
0038     //!@}
0039 
0040     struct result_type
0041     {
0042         size_type count{};  //!< Number of secondaries created
0043         Energy energy{};  //!< Sum of the energies of the secondaries
0044     };
0045 
0046   public:
0047     // Construct with shared and state data
0048     inline CELER_FUNCTION AtomicRelaxation(AtomicRelaxParamsRef const& shared,
0049                                            CutoffView const& cutoffs,
0050                                            ElementId el_id,
0051                                            SubshellId shell_id,
0052                                            Span<Secondary> secondaries,
0053                                            Span<SubshellId> vacancies);
0054 
0055     // Simulate atomic relaxation with an initial vacancy in the given shell ID
0056     template<class Engine>
0057     inline CELER_FUNCTION result_type operator()(Engine& rng);
0058 
0059   private:
0060     // Shared EADL atomic relaxation data
0061     AtomicRelaxParamsRef const& shared_;
0062     // Photon production threshold [MeV]
0063     Energy gamma_cutoff_;
0064     // Electron production threshold [MeV]
0065     Energy electron_cutoff_;
0066     // Index in MaterialParams elements
0067     ElementId el_id_;
0068     // Shell ID of the initial vacancy
0069     SubshellId shell_id_;
0070     // Fluorescence photons and Auger electrons
0071     Span<Secondary> secondaries_;
0072     // Storage for stack of unprocessed subshell vacancies
0073     Span<SubshellId> vacancies_;
0074     // Angular distribution of secondaries
0075     IsotropicDistribution<real_type> sample_direction_;
0076 
0077   private:
0078     using TransitionId = OpaqueId<AtomicRelaxTransition>;
0079 };
0080 
0081 //---------------------------------------------------------------------------//
0082 // INLINE DEFINITIONS
0083 //---------------------------------------------------------------------------//
0084 /*!
0085  * Construct with shared and state data.
0086  *
0087  * The secondaries must have enough storage allocated for particles produced in
0088  * atomic relaxation and the vacancies must have enough storage allocated for
0089  * the stack of subshell IDs: this should be handled in code *before*
0090  * construction.
0091  *
0092  * The precondition of the element having relaxation data is satisfied by the
0093  * AtomicRelaxationHelper -- it is only "true" if a distribution can be
0094  * emitted.
0095  */
0096 CELER_FUNCTION AtomicRelaxation::AtomicRelaxation(
0097     AtomicRelaxParamsRef const& shared,
0098     CutoffView const& cutoffs,
0099     ElementId el_id,
0100     SubshellId shell_id,
0101     Span<Secondary> secondaries,
0102     Span<SubshellId> vacancies)
0103     : shared_(shared)
0104     , gamma_cutoff_(cutoffs.energy(shared_.ids.gamma))
0105     , electron_cutoff_(cutoffs.energy(shared_.ids.electron))
0106     , el_id_(el_id)
0107     , shell_id_(shell_id)
0108     , secondaries_(secondaries)
0109     , vacancies_(vacancies)
0110 {
0111     CELER_EXPECT(shared_ && el_id_ < shared_.elements.size()
0112                  && shared_.elements[el_id_]);
0113     CELER_EXPECT(shell_id);
0114 }
0115 
0116 //---------------------------------------------------------------------------//
0117 /*!
0118  * Simulate atomic relaxation with an initial vacancy in the given shell ID
0119  */
0120 template<class Engine>
0121 CELER_FUNCTION AtomicRelaxation::result_type AtomicRelaxation::operator()(
0122     Engine& rng)
0123 {
0124     using namespace celeritas::literals;
0125 
0126     AtomicRelaxElement const& el = shared_.elements[el_id_];
0127     auto const& shells = shared_.shells[el.shells];
0128     IdStack<SubshellId> vacancies(vacancies_);
0129 
0130     // Push the vacancy created by the primary process onto a stack.
0131     vacancies.push(shell_id_);
0132 
0133     // Total number of secondaries
0134     size_type count = 0;
0135     real_type sum_energy = 0;
0136 
0137     // Generate the shower of photons and electrons produced by radiative and
0138     // non-radiative transitions
0139     while (!vacancies.empty())
0140     {
0141         // Pop the vacancy off the stack and check if it has transition data
0142         if (!(vacancies.top() < shells.size()))
0143         {
0144             vacancies.pop();
0145             continue;
0146         }
0147 
0148         // Get the subshell at the top of the stack
0149         AtomicRelaxSubshell const& shell = shells[vacancies.top().get()];
0150         vacancies.pop();
0151 
0152         // Sample a transition using shell probabilities
0153         auto transitions = shared_.transitions[shell.transitions];
0154         TransitionId const trans_id = make_unnormalized_selector(
0155             [&transitions](TransitionId i) {
0156                 CELER_ASSERT(i < transitions.size());
0157                 return transitions[i.unchecked_get()].probability;
0158             },
0159             id_cast<TransitionId>(transitions.size()),
0160             1_r)(rng);
0161 
0162         if (trans_id == id_cast<TransitionId>(transitions.size()))
0163         {
0164             // No transition sampled (probability: 1 - sum of transitions)
0165             continue;
0166         }
0167 
0168         // Push the new vacancies onto the stack and create the secondary
0169         auto const& transition = transitions[trans_id.get()];
0170         vacancies.push(transition.initial_shell);
0171         if (transition.auger_shell)
0172         {
0173             vacancies.push(transition.auger_shell);
0174 
0175             if (transition.energy >= electron_cutoff_)
0176             {
0177                 // Sampled a non-radiative transition: create an Auger electron
0178                 CELER_ASSERT(count < secondaries_.size());
0179                 Secondary& secondary = secondaries_[count++];
0180                 secondary.direction = sample_direction_(rng);
0181                 secondary.energy = transition.energy;
0182                 secondary.particle_id = shared_.ids.electron;
0183 
0184                 // Accumulate the energy carried away by secondaries
0185                 sum_energy += value_as<Energy>(transition.energy);
0186             }
0187         }
0188         else if (transition.energy >= gamma_cutoff_)
0189         {
0190             // Sampled a radiative transition: create a fluorescence photon
0191             CELER_ASSERT(count < secondaries_.size());
0192             Secondary& secondary = secondaries_[count++];
0193             secondary.direction = sample_direction_(rng);
0194             secondary.energy = transition.energy;
0195             secondary.particle_id = shared_.ids.gamma;
0196 
0197             // Accumulate the energy carried away by secondaries
0198             sum_energy += value_as<Energy>(transition.energy);
0199         }
0200     }
0201 
0202     result_type result;
0203     result.count = count;
0204     result.energy = Energy{sum_energy};
0205     return result;
0206 }
0207 
0208 //---------------------------------------------------------------------------//
0209 }  // namespace celeritas