File indexing completed on 2025-12-15 10:11:00
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
0021 #include "GeneratorDistributionData.hh"
0022 #include "ScintillationData.hh"
0023 #include "TrackInitializer.hh"
0024
0025 #include "detail/OpticalUtils.hh"
0026
0027 namespace celeritas
0028 {
0029 namespace optical
0030 {
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050 class ScintillationGenerator
0051 {
0052 public:
0053
0054
0055 using Energy = units::MevEnergy;
0056
0057
0058 public:
0059
0060 inline CELER_FUNCTION
0061 ScintillationGenerator(NativeCRef<ScintillationData> const& shared,
0062 GeneratorDistributionData const& dist);
0063
0064
0065 template<class Generator>
0066 inline CELER_FUNCTION TrackInitializer operator()(Generator& rng);
0067
0068 private:
0069
0070
0071 using UniformRealDist = UniformRealDistribution<real_type>;
0072 using ExponentialDist = ExponentialDistribution<real_type>;
0073
0074
0075
0076 GeneratorDistributionData const& dist_;
0077 NativeCRef<ScintillationData> const& shared_;
0078
0079 UniformRealDist sample_cost_;
0080 UniformRealDist sample_phi_;
0081 NormalDistribution<real_type> sample_lambda_;
0082
0083 bool is_neutral_{};
0084 units::LightSpeed delta_speed_{};
0085 Real3 delta_pos_{};
0086 };
0087
0088
0089
0090
0091
0092
0093
0094 CELER_FUNCTION
0095 ScintillationGenerator::ScintillationGenerator(
0096 NativeCRef<ScintillationData> const& shared,
0097 GeneratorDistributionData const& dist)
0098 : dist_(dist)
0099 , shared_(shared)
0100 , sample_cost_(-1, 1)
0101 , sample_phi_(0, real_type(2 * constants::pi))
0102 , is_neutral_{dist_.charge == zero_quantity()}
0103 {
0104 if (shared_.scintillation_by_particle())
0105 {
0106
0107 CELER_ASSERT_UNREACHABLE();
0108 }
0109
0110 CELER_EXPECT(dist_);
0111 CELER_EXPECT(shared_);
0112
0113 auto const& pre_step = dist_.points[StepPoint::pre];
0114 auto const& post_step = dist_.points[StepPoint::post];
0115 delta_pos_ = post_step.pos - pre_step.pos;
0116 delta_speed_ = post_step.speed - pre_step.speed;
0117 }
0118
0119
0120
0121
0122
0123 template<class Generator>
0124 CELER_FUNCTION TrackInitializer ScintillationGenerator::operator()(Generator& rng)
0125 {
0126
0127 ScintRecord const& component = [&] {
0128 auto const& mat = shared_.materials[dist_.material];
0129
0130 auto pdf = shared_.reals[mat.yield_pdf];
0131 auto select_idx = make_selector([&pdf](size_type i) { return pdf[i]; },
0132 mat.yield_pdf.size());
0133 size_type component_idx = select_idx(rng);
0134 CELER_ASSERT(component_idx < mat.components.size());
0135 return shared_.scint_records[mat.components[component_idx]];
0136 }();
0137
0138
0139
0140 sample_lambda_
0141 = NormalDistribution{component.lambda_mean, component.lambda_sigma};
0142 ExponentialDist sample_time(real_type{1} / component.fall_time);
0143
0144 TrackInitializer photon;
0145 photon.energy = detail::wavelength_to_energy(sample_lambda_(rng));
0146
0147
0148 real_type cost = sample_cost_(rng);
0149 real_type phi = sample_phi_(rng);
0150 photon.direction = from_spherical(cost, phi);
0151
0152
0153 photon.polarization = [&] {
0154 Real3 temp = from_spherical(
0155 (cost > 0 ? -1 : 1) * std::sqrt(1 - ipow<2>(cost)), phi);
0156 Real3 perp = {-std::sin(phi), std::cos(phi), 0};
0157 real_type sinphi, cosphi;
0158 sincospi(UniformRealDist{}(rng), &sinphi, &cosphi);
0159 for (auto j : range(3))
0160 {
0161 temp[j] = cosphi * temp[j] + sinphi * perp[j];
0162 }
0163 return make_unit_vector(temp);
0164 }();
0165
0166
0167 real_type u = is_neutral_ ? 1 : UniformRealDist{}(rng);
0168 photon.position = dist_.points[StepPoint::pre].pos;
0169 axpy(u, delta_pos_, &photon.position);
0170
0171
0172 photon.time
0173 = dist_.time
0174 + u * dist_.step_length
0175 / (native_value_from(dist_.points[StepPoint::pre].speed)
0176 + u * real_type(0.5) * native_value_from(delta_speed_));
0177
0178 if (component.rise_time == 0)
0179 {
0180
0181 photon.time += sample_time(rng);
0182 }
0183 else
0184 {
0185 real_type scint_time{};
0186 real_type target;
0187 do
0188 {
0189
0190
0191 scint_time = sample_time(rng);
0192 target = -std::expm1(-scint_time / component.rise_time);
0193 } while (RejectionSampler(target)(rng));
0194 photon.time += scint_time;
0195 }
0196 return photon;
0197 }
0198
0199
0200 }
0201 }