File indexing completed on 2025-02-22 09:55:28
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Common.hpp"
0013 #include "Acts/Definitions/PdgParticle.hpp"
0014 #include "Acts/Material/MaterialSlab.hpp"
0015 #include "Acts/Utilities/UnitVectors.hpp"
0016 #include "Acts/Utilities/VectorHelpers.hpp"
0017 #include "ActsFatras/EventData/Barcode.hpp"
0018 #include "ActsFatras/EventData/Particle.hpp"
0019 #include "ActsFatras/EventData/ProcessType.hpp"
0020 #include "ActsFatras/Physics/NuclearInteraction/NuclearInteractionParameters.hpp"
0021
0022 #include <any>
0023 #include <cmath>
0024 #include <iterator>
0025 #include <limits>
0026 #include <optional>
0027 #include <random>
0028 #include <utility>
0029 #include <vector>
0030
0031 namespace ActsFatras {
0032
0033
0034
0035
0036
0037
0038
0039 struct NuclearInteraction {
0040 using Scalar = Particle::Scalar;
0041
0042 detail::MultiParticleNuclearInteractionParametrisation
0043 multiParticleParameterisation;
0044
0045
0046 unsigned int nMatchingTrials = 100;
0047 unsigned int nMatchingTrialsTotal = 1000;
0048
0049
0050
0051
0052
0053
0054
0055
0056 template <typename generator_t>
0057 std::pair<Scalar, Scalar> generatePathLimits(generator_t& generator,
0058 const Particle& particle) const {
0059
0060 if (multiParticleParameterisation.empty()) {
0061 return std::make_pair(std::numeric_limits<Scalar>::infinity(),
0062 std::numeric_limits<Scalar>::infinity());
0063 }
0064
0065 for (const auto& particleParametrisation : multiParticleParameterisation) {
0066 if (particleParametrisation.first == particle.pdg()) {
0067 std::uniform_real_distribution<double> uniformDistribution{0., 1.};
0068
0069
0070 const detail::NuclearInteractionParametrisation&
0071 singleParticleParametrisation = particleParametrisation.second;
0072 const detail::NuclearInteractionParameters& parametrisation =
0073 findParameters(uniformDistribution(generator),
0074 singleParticleParametrisation,
0075 particle.absoluteMomentum());
0076
0077
0078 const auto& distribution =
0079 parametrisation.nuclearInteractionProbability;
0080 auto limits =
0081 std::make_pair(std::numeric_limits<Scalar>::infinity(),
0082 sampleContinuousValues(
0083 uniformDistribution(generator), distribution));
0084 return limits;
0085 }
0086 }
0087 return std::make_pair(std::numeric_limits<Scalar>::infinity(),
0088 std::numeric_limits<Scalar>::infinity());
0089 }
0090
0091
0092
0093
0094
0095
0096
0097
0098
0099 template <typename generator_t>
0100 bool run(generator_t& generator, Particle& particle,
0101 std::vector<Particle>& generated) const {
0102
0103 if (multiParticleParameterisation.empty()) {
0104 return false;
0105 }
0106
0107
0108 for (const auto& particleParametrisation : multiParticleParameterisation) {
0109 if (particleParametrisation.first == particle.pdg()) {
0110 std::uniform_real_distribution<double> uniformDistribution{0., 1.};
0111
0112
0113 const detail::NuclearInteractionParametrisation&
0114 singleParticleParametrisation = particleParametrisation.second;
0115 const detail::NuclearInteractionParameters& parametrisation =
0116 findParameters(uniformDistribution(generator),
0117 singleParticleParametrisation,
0118 particle.absoluteMomentum());
0119
0120 std::normal_distribution<double> normalDistribution{0., 1.};
0121
0122 const bool interactSoft =
0123 softInteraction(normalDistribution(generator),
0124 parametrisation.softInteractionProbability);
0125
0126
0127 const unsigned int multiplicity = finalStateMultiplicity(
0128 uniformDistribution(generator),
0129 interactSoft ? parametrisation.softMultiplicity
0130 : parametrisation.hardMultiplicity);
0131
0132
0133 const std::vector<detail::NuclearInteractionParameters::
0134 ParametersWithFixedMultiplicity>&
0135 parametrisationOfType =
0136 interactSoft ? parametrisation.softKinematicParameters
0137 : parametrisation.hardKinematicParameters;
0138 const detail::NuclearInteractionParameters::
0139 ParametersWithFixedMultiplicity& parametrisationOfMultiplicity =
0140 parametrisationOfType[multiplicity];
0141 if (!parametrisationOfMultiplicity.validParametrisation) {
0142 return false;
0143 }
0144
0145
0146 const auto kinematics = sampleKinematics(
0147 generator, parametrisationOfMultiplicity, parametrisation.momentum);
0148 if (!kinematics.has_value()) {
0149 return run(generator, particle, generated);
0150 }
0151
0152
0153 const std::vector<int> pdgIds =
0154 samplePdgIds(generator, parametrisation.pdgMap, multiplicity,
0155 particle.pdg(), interactSoft);
0156
0157
0158 const auto particles = convertParametersToParticles(
0159 generator, pdgIds, kinematics->first, kinematics->second, particle,
0160 parametrisation.momentum, interactSoft);
0161
0162
0163 if (!interactSoft) {
0164 particle.setAbsoluteMomentum(0);
0165 }
0166
0167 generated.insert(generated.end(), particles.begin(), particles.end());
0168 return !interactSoft;
0169 }
0170 }
0171
0172 return false;
0173 }
0174
0175 private:
0176
0177
0178
0179
0180
0181
0182
0183 const detail::NuclearInteractionParameters& findParameters(
0184 double rnd,
0185 const detail::NuclearInteractionParametrisation& parametrisation,
0186 float particleMomentum) const;
0187
0188
0189
0190
0191
0192
0193
0194 inline bool softInteraction(double rnd, float probability) const {
0195 return rnd <= probability;
0196 }
0197
0198
0199
0200
0201
0202
0203
0204 unsigned int finalStateMultiplicity(
0205 double rnd,
0206 const detail::NuclearInteractionParameters::CumulativeDistribution&
0207 distribution) const;
0208
0209
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219 template <typename generator_t>
0220 std::vector<int> samplePdgIds(
0221 generator_t& generator,
0222 const detail::NuclearInteractionParameters::PdgMap& pdgMap,
0223 unsigned int multiplicity, int particlePdg, bool soft) const;
0224
0225
0226
0227
0228
0229
0230
0231
0232 template <typename generator_t>
0233 Acts::ActsDynamicVector sampleInvariantMasses(
0234 generator_t& generator,
0235 const detail::NuclearInteractionParameters::
0236 ParametersWithFixedMultiplicity& parametrisation) const;
0237
0238
0239
0240
0241
0242
0243
0244
0245
0246 template <typename generator_t>
0247 Acts::ActsDynamicVector sampleMomenta(
0248 generator_t& generator,
0249 const detail::NuclearInteractionParameters::
0250 ParametersWithFixedMultiplicity& parametrisation,
0251 float initialMomentum) const;
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262 bool match(const Acts::ActsDynamicVector& momenta,
0263 const Acts::ActsDynamicVector& invariantMasses,
0264 float parametrizedMomentum) const;
0265
0266
0267
0268
0269
0270
0271
0272
0273
0274 template <typename generator_t>
0275 std::optional<std::pair<Acts::ActsDynamicVector, Acts::ActsDynamicVector>>
0276 sampleKinematics(generator_t& generator,
0277 const detail::NuclearInteractionParameters::
0278 ParametersWithFixedMultiplicity& parameters,
0279 float momentum) const;
0280
0281
0282
0283
0284
0285
0286
0287
0288
0289
0290
0291
0292
0293
0294 std::pair<ActsFatras::Particle::Scalar, ActsFatras::Particle::Scalar>
0295 globalAngle(ActsFatras::Particle::Scalar phi1,
0296 ActsFatras::Particle::Scalar theta1, float phi2,
0297 float theta2) const;
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307
0308
0309
0310
0311 template <typename generator_t>
0312 std::vector<Particle> convertParametersToParticles(
0313 generator_t& generator, const std::vector<int>& pdgId,
0314 const Acts::ActsDynamicVector& momenta,
0315 const Acts::ActsDynamicVector& invariantMasses, Particle& initialParticle,
0316 float parametrizedMomentum, bool soft) const;
0317
0318
0319
0320
0321
0322
0323
0324
0325 unsigned int sampleDiscreteValues(
0326 double rnd,
0327 const detail::NuclearInteractionParameters::CumulativeDistribution&
0328 distribution) const;
0329
0330
0331
0332
0333
0334
0335
0336
0337
0338
0339 Scalar sampleContinuousValues(
0340 double rnd,
0341 const detail::NuclearInteractionParameters::CumulativeDistribution&
0342 distribution,
0343 bool interpolate = false) const;
0344 };
0345
0346 template <typename generator_t>
0347 std::vector<int> NuclearInteraction::samplePdgIds(
0348 generator_t& generator,
0349 const detail::NuclearInteractionParameters::PdgMap& pdgMap,
0350 unsigned int multiplicity, int particlePdg, bool soft) const {
0351
0352 if (multiplicity == 0) {
0353 return {};
0354 }
0355
0356
0357 std::vector<int> pdgIds;
0358 pdgIds.reserve(multiplicity);
0359
0360 std::uniform_real_distribution<float> uniformDistribution{0., 1.};
0361
0362
0363 auto citProducer = pdgMap.cbegin();
0364 while (citProducer->first != particlePdg && citProducer != pdgMap.end()) {
0365 citProducer++;
0366 }
0367
0368 const std::vector<std::pair<int, float>>& mapInitial = citProducer->second;
0369
0370 if (soft) {
0371
0372 pdgIds.push_back(particlePdg);
0373 } else {
0374
0375 const float rndInitial = uniformDistribution(generator);
0376
0377 pdgIds.push_back(
0378 std::lower_bound(mapInitial.begin(), mapInitial.end(), rndInitial,
0379 [](const std::pair<int, float>& element,
0380 float random) { return element.second < random; })
0381 ->first);
0382 }
0383
0384
0385 for (unsigned int i = 1; i < multiplicity; i++) {
0386
0387
0388 citProducer = pdgMap.cbegin();
0389 while (citProducer->first != pdgIds[i - 1] && citProducer != pdgMap.end()) {
0390 citProducer++;
0391 }
0392
0393
0394 const std::vector<std::pair<int, float>>& map = citProducer->second;
0395 const float rnd = uniformDistribution(generator);
0396 pdgIds.push_back(
0397 std::lower_bound(map.begin(), map.end(), rnd,
0398 [](const std::pair<int, float>& element,
0399 float random) { return element.second < random; })
0400 ->first);
0401 }
0402 return pdgIds;
0403 }
0404
0405 template <typename generator_t>
0406 Acts::ActsDynamicVector NuclearInteraction::sampleInvariantMasses(
0407 generator_t& generator,
0408 const detail::NuclearInteractionParameters::ParametersWithFixedMultiplicity&
0409 parametrisation) const {
0410
0411 Acts::ActsDynamicVector parameters;
0412 const unsigned int size = parametrisation.eigenvaluesInvariantMass.size();
0413 parameters.resize(size);
0414
0415
0416 for (unsigned int i = 0; i < size; i++) {
0417 float variance = parametrisation.eigenvaluesInvariantMass[i];
0418 std::normal_distribution<Acts::ActsScalar> dist{
0419 parametrisation.meanInvariantMass[i], sqrtf(variance)};
0420 parameters[i] = dist(generator);
0421 }
0422
0423 parameters = parametrisation.eigenvectorsInvariantMass * parameters;
0424
0425
0426 for (unsigned int i = 0; i < size; i++) {
0427 const double cdf = (std::erff(parameters[i]) + 1) * 0.5;
0428 parameters[i] = sampleContinuousValues(
0429 cdf, parametrisation.invariantMassDistributions[i]);
0430 }
0431 return parameters;
0432 }
0433
0434 template <typename generator_t>
0435 Acts::ActsDynamicVector NuclearInteraction::sampleMomenta(
0436 generator_t& generator,
0437 const detail::NuclearInteractionParameters::ParametersWithFixedMultiplicity&
0438 parametrisation,
0439 float initialMomentum) const {
0440
0441 Acts::ActsDynamicVector parameters;
0442 const unsigned int size = parametrisation.eigenvaluesMomentum.size();
0443 parameters.resize(size);
0444
0445
0446 for (unsigned int i = 0; i < size; i++) {
0447 float variance = parametrisation.eigenvaluesMomentum[i];
0448 std::normal_distribution<Acts::ActsScalar> dist{
0449 parametrisation.meanMomentum[i], sqrtf(variance)};
0450 parameters[i] = dist(generator);
0451 }
0452
0453
0454 parameters = parametrisation.eigenvectorsMomentum * parameters;
0455
0456
0457 for (unsigned int i = 0; i < size; i++) {
0458 const float cdf = (std::erff(parameters[i]) + 1) * 0.5;
0459 parameters[i] =
0460 sampleContinuousValues(cdf, parametrisation.momentumDistributions[i]);
0461 }
0462
0463
0464 Acts::ActsDynamicVector momenta = parameters.head(size - 1);
0465 const float sum = momenta.sum();
0466 const float scale = parameters.template tail<1>()(0, 0) / sum;
0467 momenta *= scale * initialMomentum;
0468 return momenta;
0469 }
0470
0471 template <typename generator_t>
0472 std::optional<std::pair<Acts::ActsDynamicVector, Acts::ActsDynamicVector>>
0473 NuclearInteraction::sampleKinematics(
0474 generator_t& generator,
0475 const detail::NuclearInteractionParameters::ParametersWithFixedMultiplicity&
0476 parameters,
0477 float momentum) const {
0478 unsigned int trials = 0;
0479 Acts::ActsDynamicVector invariantMasses =
0480 sampleInvariantMasses(generator, parameters);
0481 Acts::ActsDynamicVector momenta =
0482 sampleMomenta(generator, parameters, momentum);
0483
0484 while (!match(momenta, invariantMasses, momentum)) {
0485 if (trials == nMatchingTrialsTotal) {
0486 return std::nullopt;
0487 }
0488
0489 if (trials++ % nMatchingTrials == 0) {
0490 invariantMasses = sampleInvariantMasses(generator, parameters);
0491 } else {
0492 momenta = sampleMomenta(generator, parameters, momentum);
0493 }
0494 }
0495 return std::make_pair(momenta, invariantMasses);
0496 }
0497
0498 template <typename generator_t>
0499 std::vector<Particle> NuclearInteraction::convertParametersToParticles(
0500 generator_t& generator, const std::vector<int>& pdgId,
0501 const Acts::ActsDynamicVector& momenta,
0502 const Acts::ActsDynamicVector& invariantMasses, Particle& initialParticle,
0503 float parametrizedMomentum, bool soft) const {
0504 std::uniform_real_distribution<double> uniformDistribution{0., 1.};
0505 const auto& initialDirection = initialParticle.direction();
0506 const double phi = Acts::VectorHelpers::phi(initialDirection);
0507 const double theta = Acts::VectorHelpers::theta(initialDirection);
0508 const unsigned int size = momenta.size();
0509
0510 std::vector<Particle> result;
0511 result.reserve(size);
0512
0513
0514 for (unsigned int i = 0; i < size; i++) {
0515 const float momentum = momenta[i];
0516 const float invariantMass = invariantMasses[i];
0517 const float p1p2 = 2. * momentum * parametrizedMomentum;
0518 const float costheta = 1. - invariantMass * invariantMass / p1p2;
0519
0520 const auto phiTheta =
0521 globalAngle(phi, theta, uniformDistribution(generator) * 2. * M_PI,
0522 std::acos(costheta));
0523 const auto direction =
0524 Acts::makeDirectionFromPhiTheta(phiTheta.first, phiTheta.second);
0525
0526 Particle p = Particle(initialParticle.particleId().makeDescendant(i),
0527 static_cast<Acts::PdgParticle>(pdgId[i]));
0528 p.setProcess(ProcessType::eNuclearInteraction)
0529 .setPosition4(initialParticle.fourPosition())
0530 .setAbsoluteMomentum(momentum)
0531 .setDirection(direction)
0532 .setReferenceSurface(initialParticle.referenceSurface());
0533
0534
0535 if (i == 0 && soft) {
0536 initialParticle = p;
0537 } else {
0538 result.push_back(std::move(p));
0539 }
0540 }
0541
0542 return result;
0543 }
0544 }