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