File indexing completed on 2025-09-13 08:53:33
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009 #include <cmath>
0010 #include <numeric>
0011 #include <vector>
0012
0013 #include "corecel/cont/Range.hh"
0014 #include "corecel/cont/Span.hh"
0015 #include "corecel/math/Integrator.hh"
0016 #include "celeritas/Types.hh"
0017 #include "celeritas/mat/IsotopeView.hh"
0018 #include "celeritas/neutron/data/NeutronInelasticData.hh"
0019 #include "celeritas/phys/AtomicNumber.hh"
0020
0021 #include "../CascadeOptions.hh"
0022
0023 namespace celeritas
0024 {
0025 namespace detail
0026 {
0027
0028
0029
0030
0031 class NuclearZoneBuilder
0032 {
0033 public:
0034
0035
0036 using AtomicMassNumber = AtomicNumber;
0037 using MevMass = units::MevMass;
0038 using Energy = units::MevEnergy;
0039 using ComponentVec = std::vector<ZoneComponent>;
0040 using Data = HostVal<NuclearZoneData>;
0041
0042
0043 public:
0044
0045 inline NuclearZoneBuilder(CascadeOptions const& options,
0046 NeutronInelasticScalars const& scalars,
0047 Data* data);
0048
0049
0050 inline void operator()(IsotopeView const& target);
0051
0052 private:
0053
0054
0055 struct ZoneDensity
0056 {
0057 real_type radius;
0058 real_type integral;
0059 };
0060 using VecZoneDensity = std::vector<ZoneDensity>;
0061
0062
0063
0064
0065 CascadeOptions const& options_;
0066
0067 real_type skin_depth_;
0068 MevMass neutron_mass_;
0069 MevMass proton_mass_;
0070
0071 CollectionBuilder<ZoneComponent> components_;
0072 CollectionBuilder<NuclearZones, MemSpace::host, IsotopeId> zones_;
0073
0074
0075
0076
0077 inline ComponentVec calc_zone_components(IsotopeView const& target) const;
0078
0079
0080 VecZoneDensity calc_zones_light(AtomicMassNumber a) const;
0081
0082
0083 VecZoneDensity calc_zones_small(AtomicMassNumber a) const;
0084
0085
0086 VecZoneDensity calc_zones_heavy(AtomicMassNumber a) const;
0087
0088
0089 inline real_type calc_nuclear_radius(AtomicMassNumber a) const;
0090 };
0091
0092
0093
0094
0095
0096
0097
0098 NuclearZoneBuilder::NuclearZoneBuilder(CascadeOptions const& options,
0099 NeutronInelasticScalars const& scalars,
0100 Data* data)
0101 : options_(options)
0102 , skin_depth_(0.611207 * options.radius_scale)
0103 , neutron_mass_(scalars.neutron_mass)
0104 , proton_mass_(scalars.proton_mass)
0105 , components_(&data->components)
0106 , zones_(&data->zones)
0107 {
0108 CELER_EXPECT(options_);
0109 CELER_EXPECT(data);
0110 }
0111
0112
0113
0114
0115
0116 void NuclearZoneBuilder::operator()(IsotopeView const& target)
0117 {
0118 auto comp = this->calc_zone_components(target);
0119
0120 NuclearZones nucl_zones;
0121 nucl_zones.zones = components_.insert_back(comp.begin(), comp.end());
0122 zones_.push_back(nucl_zones);
0123 }
0124
0125
0126
0127
0128
0129
0130
0131
0132
0133
0134 auto NuclearZoneBuilder::calc_zone_components(IsotopeView const& target) const
0135 -> ComponentVec
0136 {
0137 using A = AtomicMassNumber;
0138
0139
0140 A const a = target.atomic_mass_number();
0141
0142
0143 VecZoneDensity zone_dens;
0144 if (a < A{5})
0145 {
0146 zone_dens = this->calc_zones_light(a);
0147 }
0148 else if (a < A{12})
0149 {
0150 zone_dens = this->calc_zones_small(a);
0151 }
0152 else
0153 {
0154 zone_dens = this->calc_zones_heavy(a);
0155 }
0156 CELER_ASSERT(!zone_dens.empty());
0157
0158
0159 constexpr real_type four_thirds_pi = 4 * constants::pi / real_type{3};
0160
0161 ComponentVec components(zone_dens.size());
0162 real_type prev_volume{0};
0163 for (auto i : range(components.size()))
0164 {
0165 real_type volume = four_thirds_pi * ipow<3>(zone_dens[i].radius);
0166
0167
0168 components[i].radius = zone_dens[i].radius;
0169 components[i].volume = volume - prev_volume;
0170 prev_volume = volume;
0171 }
0172
0173
0174 int num_protons = target.atomic_number().get();
0175 int num_nucleons[] = {num_protons, a.get() - num_protons};
0176 real_type mass[] = {proton_mass_.value(), neutron_mass_.value()};
0177 real_type dm[] = {target.proton_loss_energy().value(),
0178 target.neutron_loss_energy().value()};
0179 static_assert(std::size(mass) == ZoneComponent::NucleonArray::size());
0180 static_assert(std::size(dm) == std::size(mass));
0181
0182 real_type const total_integral
0183 = std::accumulate(zone_dens.begin(),
0184 zone_dens.end(),
0185 real_type{0},
0186 [](real_type sum, ZoneDensity const& zone) {
0187 return sum + zone.integral;
0188 });
0189
0190 for (auto i : range(components.size()))
0191 {
0192 for (auto ptype : range(ZoneComponent::NucleonArray::size()))
0193 {
0194 components[i].density[ptype]
0195 = static_cast<real_type>(num_nucleons[ptype])
0196 * zone_dens[i].integral
0197 / (total_integral * components[i].volume);
0198 components[i].fermi_mom[ptype]
0199 = options_.fermi_scale
0200 * std::cbrt(components[i].density[ptype]);
0201 components[i].potential[ptype]
0202 = ipow<2>(components[i].fermi_mom[ptype]) / (2 * mass[ptype])
0203 + dm[ptype];
0204 }
0205 }
0206 return components;
0207 }
0208
0209
0210
0211
0212
0213 auto NuclearZoneBuilder::calc_zones_light(AtomicMassNumber a) const
0214 -> VecZoneDensity
0215 {
0216 CELER_EXPECT(a <= AtomicMassNumber{4});
0217 ZoneDensity result;
0218 result.radius = options_.radius_small
0219 * (a == AtomicMassNumber{4} ? options_.radius_alpha : 1);
0220 result.integral = 1;
0221 return {result};
0222 }
0223
0224
0225
0226
0227
0228 auto NuclearZoneBuilder::calc_zones_small(AtomicMassNumber a) const
0229 -> VecZoneDensity
0230 {
0231 real_type const nuclear_radius = this->calc_nuclear_radius(a);
0232 real_type gauss_radius = std::sqrt(
0233 ipow<2>(nuclear_radius) * (1 - 1 / static_cast<real_type>(a.get()))
0234 + real_type{6.4});
0235
0236 Integrator integrate_gauss{
0237 [](real_type r) { return ipow<2>(r) * std::exp(-ipow<2>(r)); }};
0238
0239
0240 real_type const y[] = {0.597223, 1.09726, 2.14597};
0241 VecZoneDensity result(std::size(y));
0242
0243 real_type ymin = 0;
0244 for (auto i : range(result.size()))
0245 {
0246 result[i].radius = gauss_radius * y[i];
0247 result[i].integral = ipow<3>(gauss_radius)
0248 * integrate_gauss(ymin, y[i]);
0249 ymin = y[i];
0250 }
0251
0252 CELER_ENSURE(result.size() == 3);
0253 return result;
0254 }
0255
0256
0257
0258
0259
0260
0261
0262
0263
0264
0265
0266
0267
0268
0269 auto NuclearZoneBuilder::calc_zones_heavy(AtomicMassNumber a) const
0270 -> VecZoneDensity
0271 {
0272 real_type const nuclear_radius = this->calc_nuclear_radius(a);
0273 real_type const skin_ratio = nuclear_radius / skin_depth_;
0274 real_type const skin_decay = std::exp(-skin_ratio);
0275 Integrator integrate_ws{[ws_shift = 2 * skin_ratio](real_type r) {
0276 return r * (r + ws_shift) / (1 + std::exp(r));
0277 }};
0278
0279 Span<real_type const> alpha;
0280 if (a < AtomicMassNumber{100})
0281 {
0282 static real_type const alpha_i[] = {0.7, 0.3, 0.01};
0283 alpha = make_span(alpha_i);
0284 }
0285 else
0286 {
0287 static real_type const alpha_h[] = {0.9, 0.6, 0.4, 0.2, 0.1, 0.05};
0288 alpha = make_span(alpha_h);
0289 }
0290
0291 VecZoneDensity result(alpha.size());
0292 real_type ymin = -skin_ratio;
0293 for (auto i : range(result.size()))
0294 {
0295 real_type y = std::log((1 + skin_decay) / alpha[i] - 1);
0296 result[i].radius = nuclear_radius + skin_depth_ * y;
0297
0298 result[i].integral
0299 = ipow<3>(skin_depth_)
0300 * (integrate_ws(ymin, y)
0301 + ipow<2>(skin_ratio)
0302 * std::log((1 + std::exp(-ymin)) / (1 + std::exp(-y))));
0303 ymin = y;
0304 }
0305
0306 return result;
0307 }
0308
0309
0310
0311
0312
0313
0314
0315
0316
0317
0318
0319
0320 real_type NuclearZoneBuilder::calc_nuclear_radius(AtomicMassNumber a) const
0321 {
0322 CELER_EXPECT(a > AtomicMassNumber{4});
0323
0324
0325 real_type cbrt_a = std::cbrt(static_cast<real_type>(a.get()));
0326 real_type par_a = (options_.use_two_params ? 1.16 : 1.2);
0327 real_type par_b = (options_.use_two_params ? -1.3456 : 0);
0328 return options_.radius_scale * (par_a * cbrt_a + par_b / cbrt_a);
0329 }
0330
0331
0332 }
0333 }