File indexing completed on 2025-09-17 08:53:36
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009 #include <cmath>
0010
0011 #include "corecel/Macros.hh"
0012 #include "corecel/Types.hh"
0013 #include "corecel/math/Algorithms.hh"
0014 #include "corecel/math/ArrayOperators.hh"
0015 #include "corecel/math/ArrayUtils.hh"
0016 #include "corecel/math/PolyEvaluator.hh"
0017 #include "corecel/random/distribution/BernoulliDistribution.hh"
0018 #include "corecel/random/distribution/GenerateCanonical.hh"
0019 #include "corecel/random/distribution/UniformRealDistribution.hh"
0020 #include "celeritas/Quantities.hh"
0021 #include "celeritas/Types.hh"
0022 #include "celeritas/Units.hh"
0023 #include "celeritas/em/data/UrbanMscData.hh"
0024 #include "celeritas/em/distribution/UrbanLargeAngleDistribution.hh"
0025 #include "celeritas/mat/MaterialView.hh"
0026 #include "celeritas/phys/Interaction.hh"
0027 #include "celeritas/phys/ParticleTrackView.hh"
0028 #include "celeritas/phys/PhysicsTrackView.hh"
0029
0030 #include "UrbanMscHelper.hh"
0031 #include "UrbanPositronCorrector.hh"
0032
0033 namespace celeritas
0034 {
0035 namespace detail
0036 {
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046 class UrbanMscScatter
0047 {
0048 public:
0049
0050
0051 using Energy = units::MevEnergy;
0052 using Mass = units::MevMass;
0053 using UrbanMscRef = NativeCRef<UrbanMscData>;
0054
0055
0056 public:
0057
0058 static inline CELER_FUNCTION real_type
0059 calc_displacement(real_type true_path, real_type geom_path);
0060
0061
0062 inline CELER_FUNCTION UrbanMscScatter(UrbanMscRef const& shared,
0063 UrbanMscHelper const& helper,
0064 ParticleTrackView const& particle,
0065 PhysicsTrackView const& physics,
0066 MaterialView const& material,
0067 Real3 const& dir,
0068 real_type safety,
0069 MscStep const& input);
0070
0071
0072 template<class Engine>
0073 inline CELER_FUNCTION MscInteraction operator()(Engine& rng);
0074
0075 private:
0076
0077
0078
0079 UrbanMscRef const& shared_;
0080
0081 UrbanMscMaterialData const& msc_;
0082
0083 UrbanMscHelper const& helper_;
0084
0085 MaterialView const& material_;
0086
0087 real_type inc_energy_;
0088 Real3 const& inc_direction_;
0089 real_type safety_;
0090
0091
0092 bool is_displaced_;
0093 real_type geom_path_;
0094 real_type true_path_;
0095 real_type limit_min_;
0096
0097
0098 bool skip_sampling_;
0099 real_type end_energy_;
0100 real_type tau_{0};
0101 real_type theta0_{-1};
0102
0103
0104
0105
0106 static CELER_CONSTEXPR_FUNCTION real_type geom_min()
0107 {
0108 return 5e-9 * units::centimeter;
0109 }
0110
0111
0112
0113
0114 template<class Engine>
0115 inline CELER_FUNCTION real_type sample_cos_theta(Engine& rng) const;
0116
0117
0118 template<class Engine>
0119 inline CELER_FUNCTION real_type simple_scattering(Engine& rng) const;
0120
0121
0122 inline CELER_FUNCTION real_type
0123 compute_theta0(ParticleTrackView const& particle) const;
0124
0125
0126 template<class Engine>
0127 inline CELER_FUNCTION Real3 sample_displacement_dir(Engine& rng,
0128 real_type phi) const;
0129 };
0130
0131
0132
0133
0134 CELER_FUNCTION real_type UrbanMscScatter::calc_displacement(real_type geom_path,
0135 real_type true_path)
0136 {
0137 CELER_EXPECT(true_path >= geom_path);
0138
0139
0140 real_type rmax2 = diffsq(true_path, geom_path);
0141
0142
0143
0144
0145 constexpr real_type mean_radius_frac{0.73};
0146
0147 return mean_radius_frac * std::sqrt(rmax2);
0148 }
0149
0150
0151
0152
0153
0154
0155
0156
0157 CELER_FUNCTION
0158 UrbanMscScatter::UrbanMscScatter(UrbanMscRef const& shared,
0159 UrbanMscHelper const& helper,
0160 ParticleTrackView const& particle,
0161 PhysicsTrackView const& physics,
0162 MaterialView const& material,
0163 Real3 const& dir,
0164 real_type safety,
0165 MscStep const& input)
0166 : shared_(shared)
0167 , msc_(shared.material_data[material.material_id()])
0168 , helper_(helper)
0169 , material_(material)
0170 , inc_energy_(value_as<Energy>(particle.energy()))
0171 , inc_direction_(dir)
0172 , safety_(safety)
0173 , is_displaced_(input.is_displaced)
0174 , geom_path_(input.geom_path)
0175 , true_path_(input.true_path)
0176 , limit_min_(physics.msc_range().limit_min)
0177 {
0178 CELER_EXPECT(safety_ >= 0);
0179 CELER_EXPECT(geom_path_ > 0);
0180 CELER_EXPECT(true_path_ >= geom_path_);
0181 CELER_EXPECT(limit_min_ >= UrbanMscParameters::min_step || !is_displaced_);
0182 CELER_EXPECT(!is_displaced_ || safety > 0);
0183
0184 skip_sampling_ = [this, &helper, &physics] {
0185 if (true_path_ == physics.dedx_range())
0186 {
0187
0188
0189 return true;
0190 }
0191 if (true_path_ < shared_.params.geom_limit)
0192 {
0193
0194
0195
0196 return true;
0197 }
0198
0199
0200 end_energy_ = value_as<Energy>(helper.calc_end_energy(true_path_));
0201
0202 if (Energy{end_energy_} < shared_.params.min_endpoint_energy)
0203 {
0204
0205 return true;
0206 }
0207 if (true_path_ <= helper_.msc_mfp() * shared_.params.tau_small)
0208 {
0209
0210 return true;
0211 }
0212 return false;
0213 }();
0214
0215
0216
0217
0218
0219
0220
0221
0222 if (!skip_sampling_)
0223 {
0224
0225 tau_ = true_path_ / [this, &helper] {
0226
0227
0228 real_type lambda = helper_.msc_mfp();
0229 real_type lambda_end = helper.calc_msc_mfp(Energy{end_energy_});
0230 if (std::fabs(lambda - lambda_end) < lambda * real_type(0.01))
0231 {
0232
0233
0234 return helper_.msc_mfp();
0235 }
0236 return (lambda - lambda_end) / std::log(lambda / lambda_end);
0237 }();
0238
0239 if (tau_ < shared_.params.tau_big)
0240 {
0241
0242 if (CELER_UNLIKELY(limit_min_ == 0))
0243 {
0244
0245
0246
0247 CELER_ASSERT(!is_displaced_);
0248 limit_min_ = UrbanMscParameters::min_step_fallback;
0249 }
0250 limit_min_ = min(limit_min_, physics.scalars().lambda_limit);
0251
0252
0253
0254 theta0_ = this->compute_theta0(particle);
0255
0256 if (theta0_ < real_type(1e-8))
0257 {
0258
0259
0260
0261 if (!is_displaced_)
0262 {
0263
0264 skip_sampling_ = true;
0265 }
0266 else
0267 {
0268 theta0_ = 0;
0269 }
0270 }
0271 }
0272 }
0273 }
0274
0275
0276
0277
0278
0279
0280 template<class Engine>
0281 CELER_FUNCTION auto UrbanMscScatter::operator()(Engine& rng) -> MscInteraction
0282 {
0283 if (skip_sampling_)
0284 {
0285
0286 return {inc_direction_, {0, 0, 0}, MscInteraction::Action::unchanged};
0287 }
0288
0289
0290 real_type costheta = [this, &rng] {
0291 if (theta0_ <= 0)
0292 {
0293
0294 return real_type{1};
0295 }
0296 if (tau_ >= shared_.params.tau_big)
0297 {
0298
0299 UniformRealDistribution<real_type> sample_isotropic(-1, 1);
0300 return sample_isotropic(rng);
0301 }
0302 if (2 * end_energy_ < inc_energy_ || theta0_ > constants::pi / 6)
0303 {
0304
0305
0306 return this->simple_scattering(rng);
0307 }
0308
0309 return this->sample_cos_theta(rng);
0310 }();
0311 CELER_ASSERT(std::fabs(costheta) <= 1);
0312
0313
0314 real_type phi = UniformRealDistribution<real_type>(
0315 0, real_type{2 * constants::pi})(rng);
0316
0317 MscInteraction result;
0318 result.action = MscInteraction::Action::scattered;
0319
0320
0321
0322 result.displacement = {0, 0, 0};
0323
0324 if (is_displaced_)
0325 {
0326
0327 real_type length = this->calc_displacement(geom_path_, true_path_);
0328
0329 length = min(length, (1 - shared_.params.safety_tol) * safety_);
0330
0331 if (length >= shared_.params.geom_limit)
0332 {
0333
0334 result.displacement = this->sample_displacement_dir(rng, phi);
0335 result.displacement *= length;
0336 result.action = MscInteraction::Action::displaced;
0337 }
0338 }
0339
0340
0341 result.direction = rotate(from_spherical(costheta, phi), inc_direction_);
0342 return result;
0343 }
0344
0345
0346
0347
0348
0349
0350
0351
0352
0353
0354
0355
0356
0357
0358
0359
0360
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371 template<class Engine>
0372 CELER_FUNCTION real_type UrbanMscScatter::sample_cos_theta(Engine& rng) const
0373 {
0374
0375 real_type xsi = [this] {
0376 using PolyQuad = PolyEvaluator<real_type, 2>;
0377
0378 real_type maxtau
0379 = true_path_ < limit_min_ ? limit_min_ / helper_.msc_mfp() : tau_;
0380
0381 real_type u = fastpow(maxtau, 1 / real_type(6));
0382
0383
0384 real_type radlen_mfp = true_path_
0385 / (tau_ * material_.radiation_length());
0386 real_type result = PolyQuad(msc_.tail_coeff)(u)
0387 + msc_.tail_corr * std::log(radlen_mfp);
0388
0389 return max(result, real_type(1.9));
0390 }();
0391
0392
0393
0394
0395
0396 real_type x = ipow<2>(2 * std::sin(real_type(0.5) * theta0_));
0397
0398
0399
0400
0401 real_type xmean_1 = 1 - x * (1 - xsi / (std::exp(xsi) - 1));
0402
0403
0404
0405 real_type xmean = std::exp(-tau_);
0406
0407
0408 if (xmean_1 <= real_type(0.999) * xmean)
0409 {
0410 return this->simple_scattering(rng);
0411 }
0412
0413
0414 real_type c = [xsi] {
0415 if (std::fabs(xsi - 3) < real_type(0.001))
0416 {
0417 return real_type(3.001);
0418 }
0419 else if (std::fabs(xsi - 2) < real_type(0.001))
0420 {
0421 return real_type(2.001);
0422 }
0423 return xsi;
0424 }();
0425 real_type b1 = 2 + (c - xsi) * x;
0426 real_type d = fastpow(c * x / b1, c - 1);
0427 real_type x0 = 1 - xsi * x;
0428
0429
0430 real_type xmean_2 = (x0 + d - (c * x - b1 * d) / (c - 2)) / (1 - d);
0431
0432 real_type prob = [xsi, c, d] {
0433 real_type f2x0 = (c - 1) / (c * (1 - d));
0434
0435 real_type ea_invm1 = std::exp(xsi) - 1;
0436 return 1 / (1 + 1 / (f2x0 * ea_invm1));
0437 }();
0438
0439
0440 real_type qprob = xmean / (prob * xmean_1 + (1 - prob) * xmean_2);
0441
0442
0443 if (generate_canonical(rng) >= qprob)
0444 {
0445
0446 return UniformRealDistribution<real_type>(-1, 1)(rng);
0447 }
0448
0449
0450 if (generate_canonical(rng) < prob)
0451 {
0452
0453 UniformRealDistribution<real_type> sample_inner(std::exp(-xsi), 1);
0454 return 1 + std::log(sample_inner(rng)) * x;
0455 }
0456 else
0457 {
0458
0459 real_type var = (1 - d) * generate_canonical(rng);
0460 if (var < real_type(0.01) * d)
0461 {
0462 var /= (d * (c - 1));
0463 return -1
0464 + var * (1 - real_type(0.5) * var * c) * (2 + (c - xsi) * x);
0465 }
0466 else
0467 {
0468 return x * (c - xsi - c * fastpow(var + d, -1 / (c - 1))) + 1;
0469 }
0470 }
0471 }
0472
0473
0474
0475
0476
0477
0478 template<class Engine>
0479 CELER_FUNCTION real_type UrbanMscScatter::simple_scattering(Engine& rng) const
0480 {
0481 UrbanLargeAngleDistribution dist(tau_);
0482 return dist(rng);
0483 }
0484
0485
0486
0487
0488
0489
0490
0491
0492
0493
0494
0495
0496
0497
0498
0499
0500
0501 CELER_FUNCTION
0502 real_type
0503 UrbanMscScatter::compute_theta0(ParticleTrackView const& particle) const
0504 {
0505 real_type const mass = value_as<Mass>(shared_.electron_mass);
0506 real_type true_path = max(limit_min_, true_path_);
0507 real_type y = true_path / material_.radiation_length();
0508
0509
0510 if (particle.particle_id() == shared_.ids.positron)
0511 {
0512 detail::UrbanPositronCorrector calc_correction{material_.zeff()};
0513 y *= calc_correction(std::sqrt(inc_energy_ * end_energy_) / mass);
0514 }
0515 CELER_ASSERT(y > 0);
0516
0517 real_type invbetacp
0518 = std::sqrt((inc_energy_ + mass) * (end_energy_ + mass)
0519 / (inc_energy_ * (inc_energy_ + 2 * mass) * end_energy_
0520 * (end_energy_ + 2 * mass)));
0521 constexpr units::MevEnergy c_highland{13.6};
0522 real_type theta0
0523 = c_highland.value()
0524 * std::abs(value_as<units::ElementaryCharge>(particle.charge()))
0525 * std::sqrt(y) * invbetacp;
0526
0527
0528 theta0 *= PolyEvaluator<real_type, 1>(msc_.theta_coeff)(std::log(y));
0529
0530 if (true_path_ < limit_min_)
0531 {
0532
0533 theta0 *= std::sqrt(true_path_ / limit_min_);
0534 }
0535
0536
0537
0538
0539 return clamp_to_nonneg(theta0);
0540 }
0541
0542
0543
0544
0545
0546
0547
0548
0549
0550
0551
0552
0553
0554
0555
0556
0557 template<class Engine>
0558 CELER_FUNCTION Real3
0559 UrbanMscScatter::sample_displacement_dir(Engine& rng, real_type phi) const
0560 {
0561
0562 constexpr real_type cbeta = 2.160;
0563
0564 constexpr real_type cbeta1 = 0.9988703417569197;
0565
0566 real_type psi = -std::log(1 - generate_canonical(rng) * cbeta1) / cbeta;
0567 phi += BernoulliDistribution(0.5)(rng) ? psi : -psi;
0568
0569 Real3 displacement{std::cos(phi), std::sin(phi), 0};
0570
0571
0572 displacement = rotate(displacement, inc_direction_);
0573 return displacement;
0574 }
0575
0576
0577 }
0578 }