File indexing completed on 2025-11-30 10:07:48
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 }