File indexing completed on 2025-10-31 08:58:40
0001 
0002 
0003 
0004 
0005 
0006 
0007 #pragma once
0008 
0009 #include "corecel/Macros.hh"
0010 #include "corecel/Types.hh"
0011 #include "corecel/data/StackAllocator.hh"
0012 #include "corecel/math/Algorithms.hh"
0013 #include "corecel/math/ArrayUtils.hh"
0014 #include "corecel/math/PolyEvaluator.hh"
0015 #include "celeritas/Quantities.hh"
0016 #include "celeritas/em/data/LivermorePEData.hh"
0017 #include "celeritas/em/xs/LivermorePEMicroXsCalculator.hh"
0018 #include "celeritas/grid/NonuniformGridCalculator.hh"
0019 #include "celeritas/phys/CutoffView.hh"
0020 #include "celeritas/phys/Interaction.hh"
0021 #include "celeritas/phys/InteractionUtils.hh"
0022 #include "celeritas/phys/ParticleTrackView.hh"
0023 #include "celeritas/phys/Secondary.hh"
0024 
0025 #include "AtomicRelaxationHelper.hh"
0026 
0027 namespace celeritas
0028 {
0029 
0030 
0031 
0032 
0033 
0034 
0035 
0036 
0037 
0038 
0039 
0040 
0041 
0042 
0043 
0044 
0045 
0046 
0047 
0048 
0049 
0050 class LivermorePEInteractor
0051 {
0052   public:
0053     
0054     
0055     using Energy = units::MevEnergy;
0056     
0057 
0058   public:
0059     
0060     inline CELER_FUNCTION
0061     LivermorePEInteractor(LivermorePERef const& shared,
0062                           AtomicRelaxationHelper const& relaxation,
0063                           ElementId el_id,
0064                           ParticleTrackView const& particle,
0065                           CutoffView const& cutoffs,
0066                           Real3 const& inc_direction,
0067                           StackAllocator<Secondary>& allocate);
0068 
0069     
0070     template<class Engine>
0071     inline CELER_FUNCTION Interaction operator()(Engine& rng);
0072 
0073   private:
0074     
0075 
0076     
0077     LivermorePERef const& shared_;
0078     
0079     AtomicRelaxationHelper const& relaxation_;
0080     
0081     ElementId el_id_;
0082     
0083     CutoffView const& cutoffs_;
0084     
0085     Real3 const& inc_direction_;
0086     
0087     real_type const inc_energy_;
0088     
0089     StackAllocator<Secondary>& allocate_;
0090     
0091     LivermorePEMicroXsCalculator calc_micro_xs_;
0092     
0093     real_type inv_energy_;
0094 
0095     
0096 
0097     
0098     template<class Engine>
0099     inline CELER_FUNCTION SubshellId sample_subshell(Engine& rng) const;
0100 
0101     
0102     template<class Engine>
0103     inline CELER_FUNCTION Real3 sample_direction(Engine& rng) const;
0104 };
0105 
0106 
0107 
0108 
0109 
0110 
0111 
0112 
0113 
0114 
0115 CELER_FUNCTION
0116 LivermorePEInteractor::LivermorePEInteractor(
0117     LivermorePERef const& shared,
0118     AtomicRelaxationHelper const& relaxation,
0119     ElementId el_id,
0120     ParticleTrackView const& particle,
0121     CutoffView const& cutoffs,
0122     Real3 const& inc_direction,
0123     StackAllocator<Secondary>& allocate)
0124     : shared_(shared)
0125     , relaxation_(relaxation)
0126     , el_id_(el_id)
0127     , cutoffs_(cutoffs)
0128     , inc_direction_(inc_direction)
0129     , inc_energy_(value_as<Energy>(particle.energy()))
0130     , allocate_(allocate)
0131     , calc_micro_xs_(shared, particle.energy())
0132 {
0133     CELER_EXPECT(particle.particle_id() == shared_.ids.gamma);
0134     CELER_EXPECT(inc_energy_ > 0);
0135 
0136     inv_energy_ = 1 / inc_energy_;
0137 }
0138 
0139 
0140 
0141 
0142 
0143 template<class Engine>
0144 CELER_FUNCTION Interaction LivermorePEInteractor::operator()(Engine& rng)
0145 {
0146     Span<Secondary> secondaries;
0147     size_type count = relaxation_ ? 1 + relaxation_.max_secondaries() : 1;
0148     if (Secondary* ptr = allocate_(count))
0149     {
0150         secondaries = {ptr, count};
0151     }
0152     else
0153     {
0154         
0155         return Interaction::from_failure();
0156     }
0157 
0158     
0159     SubshellId shell_id = this->sample_subshell(rng);
0160 
0161     
0162     
0163     
0164     if (CELER_UNLIKELY(!shell_id))
0165     {
0166         Interaction result = Interaction::from_absorption();
0167         result.energy_deposition = Energy{inc_energy_};
0168         return result;
0169     }
0170 
0171     real_type binding_energy;
0172     {
0173         auto const& el = shared_.xs.elements[el_id_];
0174         auto const& shells = shared_.xs.shells[el.shells];
0175         binding_energy
0176             = value_as<Energy>(shells[shell_id.get()].binding_energy);
0177     }
0178 
0179     
0180     CELER_ASSERT(!secondaries.empty());
0181     {
0182         Secondary& electron = secondaries.front();
0183         electron.particle_id = shared_.ids.electron;
0184 
0185         
0186         
0187         electron.energy = Energy{inc_energy_ - binding_energy};
0188 
0189         
0190         
0191         electron.direction = this->sample_direction(rng);
0192     }
0193 
0194     
0195     Interaction result = Interaction::from_absorption();
0196     if (relaxation_)
0197     {
0198         
0199         
0200         AtomicRelaxation sample_relaxation = relaxation_.build_distribution(
0201             cutoffs_, shell_id, secondaries.subspan(1));
0202 
0203         auto outgoing = sample_relaxation(rng);
0204         secondaries = {secondaries.data(), 1 + outgoing.count};
0205 
0206         
0207         
0208         
0209         result.energy_deposition
0210             = Energy{binding_energy - value_as<Energy>(outgoing.energy)};
0211     }
0212     else
0213     {
0214         result.energy_deposition = Energy{binding_energy};
0215     }
0216     result.secondaries = secondaries;
0217 
0218     CELER_ENSURE(result.energy_deposition >= zero_quantity());
0219     return result;
0220 }
0221 
0222 
0223 
0224 
0225 
0226 template<class Engine>
0227 CELER_FUNCTION SubshellId LivermorePEInteractor::sample_subshell(Engine& rng) const
0228 {
0229     LivermoreElement const& el = shared_.xs.elements[el_id_];
0230     auto const& shells = shared_.xs.shells[el.shells];
0231     size_type shell_id = 0;
0232 
0233     using Xs = RealQuantity<LivermoreSubshell::XsUnits>;
0234     real_type const cutoff = generate_canonical(rng)
0235                              * value_as<Xs>(calc_micro_xs_(el_id_));
0236     if (Energy{inc_energy_} < el.thresh_lo)
0237     {
0238         
0239         
0240         real_type xs = 0;
0241         real_type const inv_cube_energy = ipow<3>(inv_energy_);
0242         for (; shell_id < shells.size(); ++shell_id)
0243         {
0244             auto const& shell = shells[shell_id];
0245             if (Energy{inc_energy_} < shell.binding_energy)
0246             {
0247                 
0248                 
0249                 continue;
0250             }
0251 
0252             
0253             NonuniformGridCalculator calc_xs(shell.xs, shared_.xs.reals);
0254             xs += inv_cube_energy * calc_xs(inc_energy_);
0255 
0256             if (xs > cutoff)
0257             {
0258                 break;
0259             }
0260         }
0261 
0262         if (CELER_UNLIKELY(shell_id == shells.size()))
0263         {
0264             
0265             
0266             return {};
0267         }
0268     }
0269     else
0270     {
0271         
0272         
0273 
0274         
0275         int const pidx = Energy{inc_energy_} < el.thresh_hi ? 0 : 1;
0276         size_type const shell_end = shells.size() - 1;
0277 
0278         for (; shell_id < shell_end; ++shell_id)
0279         {
0280             PolyEvaluator<real_type, 5> eval_poly(shells[shell_id].param[pidx]);
0281 
0282             
0283             
0284             
0285             
0286             real_type xs = inv_energy_ * eval_poly(inv_energy_);
0287             if (xs > cutoff)
0288             {
0289                 break;
0290             }
0291         }
0292     }
0293 
0294     return SubshellId{shell_id};
0295 }
0296 
0297 
0298 
0299 
0300 
0301 
0302 
0303 
0304 
0305 
0306 
0307 template<class Engine>
0308 CELER_FUNCTION Real3 LivermorePEInteractor::sample_direction(Engine& rng) const
0309 {
0310     constexpr units::MevEnergy min_energy{1.e-6};
0311     constexpr units::MevEnergy max_energy{100.};
0312 
0313     if (inc_energy_ > value_as<Energy>(max_energy))
0314     {
0315         
0316         
0317         return inc_direction_;
0318     }
0319     
0320     real_type energy_per_mecsq = max(value_as<Energy>(min_energy), inc_energy_)
0321                                  * shared_.inv_electron_mass;
0322 
0323     
0324     real_type gamma = energy_per_mecsq + 1;
0325     real_type beta = std::sqrt(energy_per_mecsq * (gamma + 1)) / gamma;
0326     real_type a = (1 - beta) / beta;
0327 
0328     
0329     constexpr real_type half = 0.5;
0330     real_type b = half * beta * gamma * energy_per_mecsq * (gamma - 2);
0331 
0332     
0333     
0334     real_type g_max = 2 * (1 / a + b);
0335 
0336     
0337     real_type g;
0338     real_type nu;
0339     do
0340     {
0341         
0342         
0343         real_type u = generate_canonical(rng);
0344         nu = 2 * a * (2 * u + (a + 2) * std::sqrt(u))
0345              / (ipow<2>(a + 2) - 4 * u);
0346 
0347         
0348         g = (2 - nu) * (1 / (a + nu) + b);
0349     } while (g < g_max * generate_canonical(rng));
0350 
0351     
0352     
0353     return ExitingDirectionSampler{1 - nu, inc_direction_}(rng);
0354 }
0355 
0356 
0357 }