Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
NuclearInteraction.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file NuclearInteraction.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2020-2021 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #pragma once
10 
21 
22 #include <any>
23 #include <cmath>
24 #include <iterator>
25 #include <limits>
26 #include <optional>
27 #include <random>
28 #include <utility>
29 #include <vector>
30 
31 namespace ActsFatras {
32 
45  //~ unsigned int nMatchingTrials = std::numeric_limits<unsigned int>::max();
46  unsigned int nMatchingTrials = 100;
47  unsigned int nMatchingTrialsTotal = 1000;
48 
56  template <typename generator_t>
57  std::pair<Scalar, Scalar> generatePathLimits(generator_t& generator,
58  const Particle& particle) const {
59  // Fast exit: No paramtrization provided
60  if (multiParticleParameterisation.empty()) {
61  return std::make_pair(std::numeric_limits<Scalar>::infinity(),
62  std::numeric_limits<Scalar>::infinity());
63  }
64  // Find the parametrisation that corresponds to the particle type
65  for (const auto& particleParametrisation : multiParticleParameterisation) {
66  if (particleParametrisation.first == particle.pdg()) {
67  std::uniform_real_distribution<double> uniformDistribution{0., 1.};
68 
69  // Get the parameters
71  singleParticleParametrisation = particleParametrisation.second;
72  const detail::NuclearInteractionParameters& parametrisation =
73  findParameters(uniformDistribution(generator),
74  singleParticleParametrisation,
75  particle.absoluteMomentum());
76 
77  // Set the L0 limit if not done already
78  const auto& distribution =
79  parametrisation.nuclearInteractionProbability;
80  auto limits =
81  std::make_pair(std::numeric_limits<Scalar>::infinity(),
83  uniformDistribution(generator), distribution));
84  return limits;
85  }
86  }
87  return std::make_pair(std::numeric_limits<Scalar>::infinity(),
88  std::numeric_limits<Scalar>::infinity());
89  }
90 
99  template <typename generator_t>
100  bool run(generator_t& generator, Particle& particle,
101  std::vector<Particle>& generated) const {
102  // Fast exit: No paramtrization provided
103  if (multiParticleParameterisation.empty()) {
104  return false;
105  }
106 
107  // Find the parametrisation that corresponds to the particle type
108  for (const auto& particleParametrisation : multiParticleParameterisation) {
109  if (particleParametrisation.first == particle.pdg()) {
110  std::uniform_real_distribution<double> uniformDistribution{0., 1.};
111 
112  // Get the parameters
114  singleParticleParametrisation = particleParametrisation.second;
115  const detail::NuclearInteractionParameters& parametrisation =
116  findParameters(uniformDistribution(generator),
117  singleParticleParametrisation,
118  particle.absoluteMomentum());
119 
120  std::normal_distribution<double> normalDistribution{0., 1.};
121  // Dice the interaction type
122  const bool interactSoft =
123  softInteraction(normalDistribution(generator),
124  parametrisation.softInteractionProbability);
125 
126  // Get the final state multiplicity
127  const unsigned int multiplicity = finalStateMultiplicity(
128  uniformDistribution(generator),
129  interactSoft ? parametrisation.softMultiplicity
130  : parametrisation.hardMultiplicity);
131 
132  // Get the parameters for the kinematics
135  parametrisationOfType =
136  interactSoft ? parametrisation.softKinematicParameters
137  : parametrisation.hardKinematicParameters;
139  ParametersWithFixedMultiplicity& parametrisationOfMultiplicity =
140  parametrisationOfType[multiplicity];
141  if (!parametrisationOfMultiplicity.validParametrisation) {
142  return false;
143  }
144 
145  // Get the kinematics
146  const auto kinematics = sampleKinematics(
147  generator, parametrisationOfMultiplicity, parametrisation.momentum);
148  if (!kinematics.has_value()) {
149  return run(generator, particle, generated);
150  }
151 
152  // Get the particle types
153  const std::vector<int> pdgIds =
154  samplePdgIds(generator, parametrisation.pdgMap, multiplicity,
155  particle.pdg(), interactSoft);
156 
157  // Construct the particles
159  generator, pdgIds, kinematics->first, kinematics->second, particle,
160  parametrisation.momentum, interactSoft);
161 
162  // Kill the particle in a hard process
163  if (!interactSoft) {
164  particle.setAbsoluteMomentum(0);
165  }
166 
167  generated.insert(generated.end(), particles.begin(), particles.end());
168  return !interactSoft;
169  }
170  }
171  // Fast exit if no parametrisation for the particle was provided
172  return false;
173  }
174 
175  private:
184  double rnd,
185  const detail::NuclearInteractionParametrisation& parametrisation,
186  float particleMomentum) const;
187 
194  inline bool softInteraction(double rnd, float probability) const {
195  return rnd <= probability;
196  }
197 
204  unsigned int finalStateMultiplicity(
205  double rnd,
207  distribution) const;
208 
219  template <typename generator_t>
220  std::vector<int> samplePdgIds(
221  generator_t& generator,
223  unsigned int multiplicity, int particlePdg, bool soft) const;
224 
232  template <typename generator_t>
234  generator_t& generator,
236  ParametersWithFixedMultiplicity& parametrisation) const;
237 
246  template <typename generator_t>
248  generator_t& generator,
250  ParametersWithFixedMultiplicity& parametrisation,
251  float initialMomentum) const;
252 
262  bool match(const Acts::ActsDynamicVector& momenta,
263  const Acts::ActsDynamicVector& invariantMasses,
264  float parametrizedMomentum) const;
265 
274  template <typename generator_t>
275  std::optional<std::pair<Acts::ActsDynamicVector, Acts::ActsDynamicVector>>
276  sampleKinematics(generator_t& generator,
278  ParametersWithFixedMultiplicity& parameters,
279  float momentum) const;
280 
294  std::pair<ActsFatras::Particle::Scalar, ActsFatras::Particle::Scalar>
296  ActsFatras::Particle::Scalar theta1, float phi2,
297  float theta2) const;
298 
311  template <typename generator_t>
312  std::vector<Particle> convertParametersToParticles(
313  generator_t& generator, const std::vector<int>& pdgId,
314  const Acts::ActsDynamicVector& momenta,
315  const Acts::ActsDynamicVector& invariantMasses, Particle& initialParticle,
316  float parametrizedMomentum, bool soft) const;
317 
325  unsigned int sampleDiscreteValues(
326  double rnd,
328  distribution) const;
329 
340  double rnd,
342  distribution,
343  bool interpolate = false) const;
344 };
345 
346 template <typename generator_t>
348  generator_t& generator,
350  unsigned int multiplicity, int particlePdg, bool soft) const {
351  // Fast exit in case of no final state particles
352  if (multiplicity == 0) {
353  return {};
354  }
355 
356  // The final state PDG IDs
357  std::vector<int> pdgIds;
358  pdgIds.reserve(multiplicity);
359 
360  std::uniform_real_distribution<float> uniformDistribution{0., 1.};
361 
362  // Find the producers probability distribution
363  auto citProducer = pdgMap.cbegin();
364  while (citProducer->first != particlePdg && citProducer != pdgMap.end()) {
365  citProducer++;
366  }
367 
368  const std::vector<std::pair<int, float>>& mapInitial = citProducer->second;
369  // Set the first particle depending on the interaction type
370  if (soft) {
371  // Store the initial particle if the interaction is soft
372  pdgIds.push_back(particlePdg);
373  } else {
374  // Otherwise dice the particle
375  const float rndInitial = uniformDistribution(generator);
376 
377  pdgIds.push_back(
378  std::lower_bound(mapInitial.begin(), mapInitial.end(), rndInitial,
379  [](const std::pair<int, float>& element,
380  float random) { return element.second < random; })
381  ->first);
382  }
383 
384  // Set the remaining particles
385  for (unsigned int i = 1; i < multiplicity; i++) {
386  // Find the producers probability distribution from the last produced
387  // particle
388  citProducer = pdgMap.cbegin();
389  while (citProducer->first != pdgIds[i - 1] && citProducer != pdgMap.end()) {
390  citProducer++;
391  }
392 
393  // Set the next particle
394  const std::vector<std::pair<int, float>>& map = citProducer->second;
395  const float rnd = uniformDistribution(generator);
396  pdgIds.push_back(
397  std::lower_bound(map.begin(), map.end(), rnd,
398  [](const std::pair<int, float>& element,
399  float random) { return element.second < random; })
400  ->first);
401  }
402  return pdgIds;
403 }
404 
405 template <typename generator_t>
407  generator_t& generator,
409  parametrisation) const {
410  // The resulting vector
412  const unsigned int size = parametrisation.eigenvaluesInvariantMass.size();
413  parameters.resize(size);
414 
415  // Sample in the eigenspace
416  for (unsigned int i = 0; i < size; i++) {
417  float variance = parametrisation.eigenvaluesInvariantMass[i];
418  std::normal_distribution<Acts::ActsScalar> dist{
419  parametrisation.meanInvariantMass[i], sqrtf(variance)};
420  parameters[i] = dist(generator);
421  }
422  // Transform to multivariate normal distribution
423  parameters = parametrisation.eigenvectorsInvariantMass * parameters;
424 
425  // Perform the inverse sampling from the distributions
426  for (unsigned int i = 0; i < size; i++) {
427  const double cdf = (std::erff(parameters[i]) + 1) * 0.5;
428  parameters[i] = sampleContinuousValues(
429  cdf, parametrisation.invariantMassDistributions[i]);
430  }
431  return parameters;
432 }
433 
434 template <typename generator_t>
436  generator_t& generator,
438  parametrisation,
439  float initialMomentum) const {
440  // The resulting vector
442  const unsigned int size = parametrisation.eigenvaluesMomentum.size();
443  parameters.resize(size);
444 
445  // Sample in the eigenspace
446  for (unsigned int i = 0; i < size; i++) {
447  float variance = parametrisation.eigenvaluesMomentum[i];
448  std::normal_distribution<Acts::ActsScalar> dist{
449  parametrisation.meanMomentum[i], sqrtf(variance)};
450  parameters[i] = dist(generator);
451  }
452 
453  // Transform to multivariate normal distribution
454  parameters = parametrisation.eigenvectorsMomentum * parameters;
455 
456  // Perform the inverse sampling from the distributions
457  for (unsigned int i = 0; i < size; i++) {
458  const float cdf = (std::erff(parameters[i]) + 1) * 0.5;
459  parameters[i] =
460  sampleContinuousValues(cdf, parametrisation.momentumDistributions[i]);
461  }
462 
463  // Scale the momenta
464  Acts::ActsDynamicVector momenta = parameters.head(size - 1);
465  const float sum = momenta.sum();
466  const float scale = parameters.template tail<1>()(0, 0) / sum;
467  momenta *= scale * initialMomentum;
468  return momenta;
469 }
470 
471 template <typename generator_t>
472 std::optional<std::pair<Acts::ActsDynamicVector, Acts::ActsDynamicVector>>
474  generator_t& generator,
476  parameters,
477  float momentum) const {
478  unsigned int trials = 0;
479  Acts::ActsDynamicVector invariantMasses =
480  sampleInvariantMasses(generator, parameters);
481  Acts::ActsDynamicVector momenta =
482  sampleMomenta(generator, parameters, momentum);
483  // Repeat momentum evaluation until the parameters match
484  while (!match(momenta, invariantMasses, momentum)) {
485  if (trials == nMatchingTrialsTotal) {
486  return std::nullopt;
487  }
488  // Re-sampole invariant masses if no fitting momenta were found
489  if (trials++ % nMatchingTrials == 0) {
490  invariantMasses = sampleInvariantMasses(generator, parameters);
491  } else {
492  momenta = sampleMomenta(generator, parameters, momentum);
493  }
494  }
495  return std::make_pair(momenta, invariantMasses);
496 }
497 
498 template <typename generator_t>
500  generator_t& generator, const std::vector<int>& pdgId,
501  const Acts::ActsDynamicVector& momenta,
502  const Acts::ActsDynamicVector& invariantMasses, Particle& initialParticle,
503  float parametrizedMomentum, bool soft) const {
504  std::uniform_real_distribution<double> uniformDistribution{0., 1.};
505  const auto& initialDirection = initialParticle.direction();
506  const double phi = Acts::VectorHelpers::phi(initialDirection);
507  const double theta = Acts::VectorHelpers::theta(initialDirection);
508  const unsigned int size = momenta.size();
509 
510  std::vector<Particle> result;
511  result.reserve(size);
512 
513  // Build the particles
514  for (unsigned int i = 0; i < size; i++) {
515  const float momentum = momenta[i];
516  const float invariantMass = invariantMasses[i];
517  const float p1p2 = 2. * momentum * parametrizedMomentum;
518  const float costheta = 1. - invariantMass * invariantMass / p1p2;
519 
520  const auto phiTheta =
521  globalAngle(phi, theta, uniformDistribution(generator) * 2. * M_PI,
522  std::acos(costheta));
523  const auto direction =
524  Acts::makeDirectionFromPhiTheta(phiTheta.first, phiTheta.second);
525 
526  Particle p = Particle(initialParticle.particleId().makeDescendant(i),
527  static_cast<Acts::PdgParticle>(pdgId[i]));
528  p.setProcess(ProcessType::eNuclearInteraction)
529  .setPosition4(initialParticle.fourPosition())
530  .setAbsoluteMomentum(momentum)
531  .setDirection(direction)
532  .setReferenceSurface(initialParticle.referenceSurface());
533 
534  // Store the particle
535  if (i == 0 && soft) {
536  initialParticle = p;
537  } else {
538  result.push_back(std::move(p));
539  }
540  }
541 
542  return result;
543 }
544 } // namespace ActsFatras