File indexing completed on 2026-01-07 10:01:43
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009 #include "corecel/Assert.hh"
0010 #include "corecel/Macros.hh"
0011 #include "corecel/Types.hh"
0012 #include "corecel/math/ArrayOperators.hh"
0013 #include "corecel/math/ArrayUtils.hh"
0014 #include "corecel/random/distribution/ExponentialDistribution.hh"
0015 #include "corecel/random/distribution/GenerateCanonical.hh"
0016 #include "corecel/random/distribution/NormalDistribution.hh"
0017 #include "corecel/random/distribution/RejectionSampler.hh"
0018 #include "corecel/random/distribution/Selector.hh"
0019 #include "corecel/random/distribution/UniformRealDistribution.hh"
0020 #include "celeritas/optical/detail/OpticalUtils.hh"
0021
0022 #include "GeneratorData.hh"
0023 #include "ScintillationData.hh"
0024 #include "../MaterialView.hh"
0025 #include "../TrackInitializer.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 class ScintillationGenerator
0049 {
0050 public:
0051
0052
0053 using Energy = units::MevEnergy;
0054
0055
0056 public:
0057
0058 inline CELER_FUNCTION
0059 ScintillationGenerator(optical::MaterialView const&,
0060 NativeCRef<ScintillationData> const& shared,
0061 GeneratorDistributionData const& dist);
0062
0063
0064 inline CELER_FUNCTION
0065 ScintillationGenerator(NativeCRef<ScintillationData> const& shared,
0066 GeneratorDistributionData const& dist);
0067
0068
0069 template<class Generator>
0070 inline CELER_FUNCTION optical::TrackInitializer operator()(Generator& rng);
0071
0072 private:
0073
0074
0075 using UniformRealDist = UniformRealDistribution<real_type>;
0076 using ExponentialDist = ExponentialDistribution<real_type>;
0077
0078
0079
0080 GeneratorDistributionData const& dist_;
0081 NativeCRef<ScintillationData> const& shared_;
0082
0083 UniformRealDist sample_cost_;
0084 UniformRealDist sample_phi_;
0085 NormalDistribution<real_type> sample_lambda_;
0086
0087 bool is_neutral_{};
0088 units::LightSpeed delta_speed_{};
0089 Real3 delta_pos_{};
0090 };
0091
0092
0093
0094
0095
0096
0097
0098 CELER_FUNCTION
0099 ScintillationGenerator::ScintillationGenerator(
0100 NativeCRef<ScintillationData> const& shared,
0101 GeneratorDistributionData const& dist)
0102 : dist_(dist)
0103 , shared_(shared)
0104 , sample_cost_(-1, 1)
0105 , sample_phi_(0, real_type(2 * constants::pi))
0106 , is_neutral_{dist_.charge == zero_quantity()}
0107 {
0108 if (shared_.scintillation_by_particle())
0109 {
0110
0111 CELER_ASSERT_UNREACHABLE();
0112 }
0113
0114 CELER_EXPECT(dist_);
0115 CELER_EXPECT(shared_);
0116
0117 auto const& pre_step = dist_.points[StepPoint::pre];
0118 auto const& post_step = dist_.points[StepPoint::post];
0119 delta_pos_ = post_step.pos - pre_step.pos;
0120 delta_speed_ = post_step.speed - pre_step.speed;
0121 }
0122
0123
0124
0125
0126
0127
0128
0129
0130 CELER_FUNCTION
0131 ScintillationGenerator::ScintillationGenerator(
0132 optical::MaterialView const&,
0133 NativeCRef<ScintillationData> const& shared,
0134 GeneratorDistributionData const& dist)
0135 : ScintillationGenerator(shared, dist)
0136 {
0137 }
0138
0139
0140
0141
0142
0143 template<class Generator>
0144 CELER_FUNCTION optical::TrackInitializer
0145 ScintillationGenerator::operator()(Generator& rng)
0146 {
0147
0148 ScintRecord const& component = [&] {
0149 auto const& mat = shared_.materials[dist_.material];
0150
0151 auto pdf = shared_.reals[mat.yield_pdf];
0152 auto select_idx = make_selector([&pdf](size_type i) { return pdf[i]; },
0153 mat.yield_pdf.size());
0154 size_type component_idx = select_idx(rng);
0155 CELER_ASSERT(component_idx < mat.components.size());
0156 return shared_.scint_records[mat.components[component_idx]];
0157 }();
0158
0159
0160
0161 sample_lambda_
0162 = NormalDistribution{component.lambda_mean, component.lambda_sigma};
0163 ExponentialDist sample_time(real_type{1} / component.fall_time);
0164
0165 optical::TrackInitializer photon;
0166 photon.energy = optical::detail::wavelength_to_energy(sample_lambda_(rng));
0167
0168
0169 real_type cost = sample_cost_(rng);
0170 real_type phi = sample_phi_(rng);
0171 photon.direction = from_spherical(cost, phi);
0172
0173
0174 photon.polarization = [&] {
0175 Real3 pol = from_spherical(
0176 (cost > 0 ? -1 : 1) * std::sqrt(1 - ipow<2>(cost)), phi);
0177 Real3 perp = {-std::sin(phi), std::cos(phi), 0};
0178 real_type sinphi, cosphi;
0179 sincospi(UniformRealDist{}(rng), &sinphi, &cosphi);
0180 for (auto j : range(3))
0181 {
0182 pol[j] = cosphi * pol[j] + sinphi * perp[j];
0183 }
0184
0185 return make_unit_vector(make_orthogonal(pol, photon.direction));
0186 }();
0187 CELER_ASSERT(soft_zero(dot_product(photon.polarization, photon.direction)));
0188
0189
0190 real_type u = is_neutral_ ? 1 : UniformRealDist{}(rng);
0191 photon.position = dist_.points[StepPoint::pre].pos;
0192 axpy(u, delta_pos_, &photon.position);
0193
0194
0195 photon.time
0196 = dist_.time
0197 + u * dist_.step_length
0198 / (native_value_from(dist_.points[StepPoint::pre].speed)
0199 + u * real_type(0.5) * native_value_from(delta_speed_));
0200
0201 if (component.rise_time == 0)
0202 {
0203
0204 photon.time += sample_time(rng);
0205 }
0206 else
0207 {
0208 real_type scint_time{};
0209 real_type target;
0210 do
0211 {
0212
0213
0214 scint_time = sample_time(rng);
0215 target = -std::expm1(-scint_time / component.rise_time);
0216 } while (RejectionSampler(target)(rng));
0217 photon.time += scint_time;
0218 }
0219 return photon;
0220 }
0221
0222
0223 }