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