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/Constants.hh"
0016 #include "celeritas/Quantities.hh"
0017 #include "celeritas/em/data/BetheHeitlerData.hh"
0018 #include "celeritas/em/distribution/TsaiUrbanDistribution.hh"
0019 #include "celeritas/em/xs/LPMCalculator.hh"
0020 #include "celeritas/mat/ElementView.hh"
0021 #include "celeritas/phys/Interaction.hh"
0022 #include "celeritas/phys/ParticleTrackView.hh"
0023 #include "celeritas/phys/Secondary.hh"
0024 #include "celeritas/random/distribution/BernoulliDistribution.hh"
0025 #include "celeritas/random/distribution/GenerateCanonical.hh"
0026 #include "celeritas/random/distribution/UniformRealDistribution.hh"
0027
0028 namespace celeritas
0029 {
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045 class BetheHeitlerInteractor
0046 {
0047 public:
0048
0049
0050 using Mass = units::MevMass;
0051 using Energy = units::MevEnergy;
0052
0053
0054 public:
0055
0056 inline CELER_FUNCTION
0057 BetheHeitlerInteractor(BetheHeitlerData const& shared,
0058 ParticleTrackView const& particle,
0059 Real3 const& inc_direction,
0060 StackAllocator<Secondary>& allocate,
0061 MaterialView const& material,
0062 ElementView const& element);
0063
0064
0065 template<class Engine>
0066 inline CELER_FUNCTION Interaction operator()(Engine& rng);
0067
0068 private:
0069
0070
0071
0072 struct ScreeningFunctions
0073 {
0074 real_type phi1;
0075 real_type phi2;
0076 };
0077
0078
0079
0080
0081 BetheHeitlerData const& shared_;
0082
0083 real_type const inc_energy_;
0084
0085 Real3 const& inc_direction_;
0086
0087 StackAllocator<Secondary>& allocate_;
0088
0089 ElementView const& element_;
0090
0091 bool const enable_lpm_;
0092
0093 LPMCalculator calc_lpm_functions_;
0094
0095 real_type epsilon0_;
0096
0097
0098
0099
0100 static CELER_CONSTEXPR_FUNCTION Energy coulomb_corr_threshold()
0101 {
0102 return units::MevEnergy{50};
0103 }
0104
0105
0106 static CELER_CONSTEXPR_FUNCTION Energy lpm_threshold()
0107 {
0108 return units::MevEnergy{1e5};
0109 }
0110
0111
0112
0113
0114 inline CELER_FUNCTION real_type impact_parameter(real_type eps) const;
0115
0116
0117 inline CELER_FUNCTION ScreeningFunctions
0118 screening_phi1_phi2(real_type delta) const;
0119
0120
0121 inline CELER_FUNCTION real_type screening_f1(real_type delta) const;
0122
0123
0124 inline CELER_FUNCTION real_type screening_f2(real_type delta) const;
0125 };
0126
0127
0128
0129
0130
0131
0132
0133
0134
0135 CELER_FUNCTION BetheHeitlerInteractor::BetheHeitlerInteractor(
0136 BetheHeitlerData const& shared,
0137 ParticleTrackView const& particle,
0138 Real3 const& inc_direction,
0139 StackAllocator<Secondary>& allocate,
0140 MaterialView const& material,
0141 ElementView const& element)
0142 : shared_(shared)
0143 , inc_energy_(value_as<Energy>(particle.energy()))
0144 , inc_direction_(inc_direction)
0145 , allocate_(allocate)
0146 , element_(element)
0147 , enable_lpm_(shared.enable_lpm
0148 && inc_energy_ > value_as<Energy>(lpm_threshold()))
0149 , calc_lpm_functions_(material,
0150 element_,
0151 shared_.dielectric_suppression(),
0152 Energy{inc_energy_})
0153 {
0154 CELER_EXPECT(particle.particle_id() == shared_.ids.gamma);
0155 CELER_EXPECT(inc_energy_ >= 2 * value_as<Mass>(shared_.electron_mass));
0156
0157 epsilon0_ = value_as<Mass>(shared_.electron_mass) / inc_energy_;
0158 }
0159
0160
0161
0162
0163
0164 template<class Engine>
0165 CELER_FUNCTION Interaction BetheHeitlerInteractor::operator()(Engine& rng)
0166 {
0167
0168 Secondary* secondaries = allocate_(2);
0169 if (secondaries == nullptr)
0170 {
0171
0172 return Interaction::from_failure();
0173 }
0174
0175 constexpr real_type half = 0.5;
0176
0177
0178 real_type epsilon;
0179 if (inc_energy_ < value_as<Energy>(units::MevEnergy{2}))
0180 {
0181 UniformRealDistribution<real_type> sample_eps(epsilon0_, half);
0182 epsilon = sample_eps(rng);
0183 }
0184 else
0185 {
0186
0187
0188
0189 real_type const delta_min = 4 * 136 / element_.cbrt_z() * epsilon0_;
0190 real_type f_z = real_type(8) / real_type(3) * element_.log_z();
0191 if (inc_energy_ > value_as<Energy>(coulomb_corr_threshold()))
0192 {
0193 f_z += 8 * element_.coulomb_correction();
0194 }
0195 real_type const delta_max
0196 = std::exp((real_type(42.038) - f_z) / real_type(8.29))
0197 - real_type(0.958);
0198 CELER_ASSERT(delta_min <= delta_max);
0199
0200
0201
0202
0203
0204
0205
0206 real_type const epsilon1
0207 = half - half * std::sqrt(1 - delta_min / delta_max);
0208 real_type const epsilon_min = celeritas::max(epsilon0_, epsilon1);
0209
0210
0211
0212
0213 real_type const f10 = this->screening_f1(delta_min) - f_z;
0214 real_type const f20 = this->screening_f2(delta_min) - f_z;
0215 BernoulliDistribution choose_f1g1(ipow<2>(half - epsilon_min) * f10,
0216 real_type(1.5) * f20);
0217
0218
0219
0220 real_type g;
0221 do
0222 {
0223 if (choose_f1g1(rng))
0224 {
0225
0226 epsilon = half
0227 - (half - epsilon_min)
0228 * std::cbrt(generate_canonical(rng));
0229 CELER_ASSERT(epsilon >= epsilon_min && epsilon <= half);
0230
0231
0232
0233 real_type delta = this->impact_parameter(epsilon);
0234 CELER_ASSERT(delta <= delta_max && delta >= delta_min);
0235
0236
0237 if (enable_lpm_)
0238 {
0239 auto screening = screening_phi1_phi2(delta);
0240 auto lpm = calc_lpm_functions_(epsilon);
0241 g = lpm.xi
0242 * ((2 * lpm.phi + lpm.g) * screening.phi1
0243 - lpm.g * screening.phi2 - lpm.phi * f_z)
0244 / f10;
0245 }
0246 else
0247 {
0248 g = (this->screening_f1(delta) - f_z) / f10;
0249 }
0250 CELER_ASSERT(g > 0);
0251 }
0252 else
0253 {
0254
0255 epsilon = epsilon_min
0256 + (half - epsilon_min) * generate_canonical(rng);
0257 CELER_ASSERT(epsilon >= epsilon_min && epsilon <= half);
0258
0259
0260
0261 real_type delta = this->impact_parameter(epsilon);
0262 CELER_ASSERT(delta <= delta_max && delta >= delta_min);
0263
0264
0265 if (enable_lpm_)
0266 {
0267 auto screening = screening_phi1_phi2(delta);
0268 auto lpm = calc_lpm_functions_(epsilon);
0269 g = lpm.xi
0270 * ((lpm.phi + half * lpm.g) * screening.phi1
0271 + half * lpm.g * screening.phi2
0272 - half * (lpm.g + lpm.phi) * f_z)
0273 / f20;
0274 }
0275 else
0276 {
0277 g = (this->screening_f2(delta) - f_z) / f20;
0278 }
0279 CELER_ASSERT(g > 0);
0280 }
0281 } while (g < generate_canonical(rng));
0282 }
0283
0284
0285 Interaction result = Interaction::from_absorption();
0286 result.secondaries = {secondaries, 2};
0287
0288
0289 secondaries[0].particle_id = shared_.ids.electron;
0290 secondaries[1].particle_id = shared_.ids.positron;
0291
0292 secondaries[0].energy = Energy{(1 - epsilon) * inc_energy_
0293 - value_as<Mass>(shared_.electron_mass)};
0294 secondaries[1].energy = Energy{epsilon * inc_energy_
0295 - value_as<Mass>(shared_.electron_mass)};
0296
0297
0298 if (BernoulliDistribution(half)(rng))
0299 {
0300 trivial_swap(secondaries[0].energy, secondaries[1].energy);
0301 }
0302
0303
0304
0305 real_type phi
0306 = UniformRealDistribution<real_type>(0, 2 * constants::pi)(rng);
0307
0308
0309 TsaiUrbanDistribution sample_electron_angle(secondaries[0].energy,
0310 shared_.electron_mass);
0311 real_type cost = sample_electron_angle(rng);
0312 secondaries[0].direction
0313 = rotate(from_spherical(cost, phi), inc_direction_);
0314
0315 TsaiUrbanDistribution sample_positron_angle(secondaries[1].energy,
0316 shared_.electron_mass);
0317 cost = sample_positron_angle(rng);
0318 secondaries[1].direction
0319 = rotate(from_spherical(cost, phi + constants::pi), inc_direction_);
0320 return result;
0321 }
0322
0323
0324
0325
0326
0327
0328
0329
0330 CELER_FUNCTION real_type
0331 BetheHeitlerInteractor::impact_parameter(real_type eps) const
0332 {
0333 return 136 / element_.cbrt_z() * epsilon0_ / (eps * (1 - eps));
0334 }
0335
0336
0337
0338
0339
0340 CELER_FUNCTION auto BetheHeitlerInteractor::screening_phi1_phi2(
0341 real_type delta) const -> ScreeningFunctions
0342 {
0343 using R = real_type;
0344
0345 ScreeningFunctions result;
0346 if (delta > R(1.4))
0347 {
0348 result.phi1 = R(21.0190) - R(4.145) * std::log(delta + R(0.958));
0349 result.phi2 = result.phi1;
0350 }
0351 else
0352 {
0353 result.phi1 = R(20.806) - delta * (R(3.190) - R(0.5710) * delta);
0354 result.phi2 = R(20.234) - delta * (R(2.126) - R(0.0903) * delta);
0355 }
0356 return result;
0357 }
0358
0359
0360
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371 CELER_FUNCTION real_type BetheHeitlerInteractor::screening_f1(real_type delta) const
0372 {
0373 using R = real_type;
0374
0375 return delta > R(1.4) ? R(42.038) - R(8.29) * std::log(delta + R(0.958))
0376 : R(42.184) - delta * (R(7.444) - R(1.623) * delta);
0377 }
0378
0379 CELER_FUNCTION real_type BetheHeitlerInteractor::screening_f2(real_type delta) const
0380 {
0381 using R = real_type;
0382
0383 return delta > R(1.4) ? R(42.038) - R(8.29) * std::log(delta + R(0.958))
0384 : R(41.326) - delta * (R(5.848) - R(0.902) * delta);
0385 }
0386
0387 }