Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ParticleSmearing.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file ParticleSmearing.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2019 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 
10 
25 
26 #include <algorithm>
27 #include <cmath>
28 #include <ostream>
29 #include <random>
30 #include <stdexcept>
31 #include <utility>
32 
35  : IAlgorithm("ParticleSmearing", level), m_cfg(config) {
36  if (m_cfg.inputParticles.empty()) {
37  throw std::invalid_argument("Missing input truth particles collection");
38  }
39  if (m_cfg.outputTrackParameters.empty()) {
40  throw std::invalid_argument("Missing output tracks parameters collection");
41  }
42  if (m_cfg.randomNumbers == nullptr) {
43  throw std::invalid_argument("Missing random numbers tool");
44  }
45 
47  ACTS_INFO("Override truth particle hypothesis with "
49  }
50 
53 }
54 
56  const AlgorithmContext& ctx) const {
57  // setup input and output containers
58  const auto& particles = m_inputParticles(ctx);
60  parameters.reserve(particles.size());
61 
62  // setup random number generator and standard gaussian
63  auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
64  std::normal_distribution<double> stdNormal(0.0, 1.0);
65 
66  for (auto&& [vtxId, vtxParticles] : groupBySecondaryVertex(particles)) {
67  // a group contains at least one particle by construction. assume that all
68  // particles within the group originate from the same position and use it to
69  // as the reference position for the perigee frame.
70  auto perigee = Acts::Surface::makeShared<Acts::PerigeeSurface>(
71  vtxParticles.begin()->position());
72 
73  for (const auto& particle : vtxParticles) {
74  const auto time = particle.time();
75  const auto phi = Acts::VectorHelpers::phi(particle.direction());
76  const auto theta = Acts::VectorHelpers::theta(particle.direction());
77  const auto pt = particle.transverseMomentum();
78  const auto p = particle.absoluteMomentum();
79  const auto q = particle.charge();
80  const auto particleHypothesis =
81  m_cfg.particleHypothesis.value_or(particle.hypothesis());
82 
83  // compute momentum-dependent resolutions
84  const double sigmaD0 =
85  m_cfg.sigmaD0 +
86  m_cfg.sigmaD0PtA * std::exp(-1.0 * std::abs(m_cfg.sigmaD0PtB) * pt);
87  const double sigmaZ0 =
88  m_cfg.sigmaZ0 +
89  m_cfg.sigmaZ0PtA * std::exp(-1.0 * std::abs(m_cfg.sigmaZ0PtB) * pt);
90  const double sigmaP = m_cfg.sigmaPRel * p;
91  // var(q/p) = (d(1/p)/dp)² * var(p) = (-1/p²)² * var(p)
92  const double sigmaQOverP = sigmaP / (p * p);
93  // shortcuts for other resolutions
94  const double sigmaT0 = m_cfg.sigmaT0;
95  const double sigmaPhi = m_cfg.sigmaPhi;
96  const double sigmaTheta = m_cfg.sigmaTheta;
97 
98  Acts::BoundVector params = Acts::BoundVector::Zero();
99  // smear the position/time
100  params[Acts::eBoundLoc0] = sigmaD0 * stdNormal(rng);
101  params[Acts::eBoundLoc1] = sigmaZ0 * stdNormal(rng);
102  params[Acts::eBoundTime] = time + sigmaT0 * stdNormal(rng);
103  // smear direction angles phi,theta ensuring correct bounds
104  const auto [newPhi, newTheta] = Acts::detail::normalizePhiTheta(
105  phi + sigmaPhi * stdNormal(rng), theta + sigmaTheta * stdNormal(rng));
106  params[Acts::eBoundPhi] = newPhi;
107  params[Acts::eBoundTheta] = newTheta;
108  // compute smeared absolute momentum vector
109  const double newP = std::max(0.0, p + sigmaP * stdNormal(rng));
110  params[Acts::eBoundQOverP] = particleHypothesis.qOverP(newP, q);
111 
112  ACTS_VERBOSE("Smearing particle (pos, time, phi, theta, q/p):");
113  ACTS_VERBOSE(" from: " << particle.position().transpose() << ", " << time
114  << ", " << phi << ", " << theta << ", "
115  << (q != 0 ? q / p : 1 / p));
116  ACTS_VERBOSE(" to: " << perigee
117  ->localToGlobal(
118  ctx.geoContext,
120  params[Acts::eBoundLoc1]},
121  particle.direction() * p)
122  .transpose()
123  << ", " << params[Acts::eBoundTime] << ", "
124  << params[Acts::eBoundPhi] << ", "
125  << params[Acts::eBoundTheta] << ", "
126  << params[Acts::eBoundQOverP]);
127 
128  // build the track covariance matrix using the smearing sigmas
129  Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
130  if (m_cfg.initialSigmas) {
131  // use the initial sigmas if set
132  for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
133  cov(i, i) = m_cfg.initialVarInflation[i] * (*m_cfg.initialSigmas)[i] *
134  (*m_cfg.initialSigmas)[i];
135  }
136  } else {
137  // otherwise use the smearing sigmas
139  m_cfg.initialVarInflation[Acts::eBoundLoc0] * sigmaD0 * sigmaD0;
141  m_cfg.initialVarInflation[Acts::eBoundLoc1] * sigmaZ0 * sigmaZ0;
143  m_cfg.initialVarInflation[Acts::eBoundTime] * sigmaT0 * sigmaT0;
145  m_cfg.initialVarInflation[Acts::eBoundPhi] * sigmaPhi * sigmaPhi;
147  m_cfg.initialVarInflation[Acts::eBoundTheta] * sigmaTheta *
148  sigmaTheta;
150  m_cfg.initialVarInflation[Acts::eBoundQOverP] * sigmaQOverP *
151  sigmaQOverP;
152  }
153 
154  parameters.emplace_back(perigee, params, cov, particleHypothesis);
155  }
156  }
157 
158  m_outputTrackParameters(ctx, std::move(parameters));
159  return ProcessCode::SUCCESS;
160 }