Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ImpactPointEstimator.ipp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file ImpactPointEstimator.ipp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2019-2023 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 
13 
14 template <typename input_track_t, typename propagator_t,
15  typename propagator_options_t>
19  const BoundTrackParameters& trkParams,
20  const Vector3& vtxPos, State& state) const {
21  auto res = getDistanceAndMomentum<3>(gctx, trkParams, vtxPos, state);
22 
23  if (!res.ok()) {
24  return res.error();
25  }
26 
27  // Return distance
28  return res.value().first.norm();
29 }
30 
31 template <typename input_track_t, typename propagator_t,
32  typename propagator_options_t>
36  const Acts::MagneticFieldContext& mctx,
37  const BoundTrackParameters& trkParams,
38  const Vector3& vtxPos, State& state) const {
39  auto res = getDistanceAndMomentum<3>(gctx, trkParams, vtxPos, state);
40 
41  if (!res.ok()) {
42  return res.error();
43  }
44 
45  // Vector pointing from vertex to 3D PCA
46  Vector3 deltaR = res.value().first;
47 
48  // Get corresponding unit vector
49  deltaR.normalize();
50 
51  // Momentum direction at vtxPos
52  Vector3 momDir = res.value().second;
53 
54  // To understand why deltaR and momDir are not orthogonal, let us look at the
55  // x-y-plane. Since we computed the 3D PCA, the 2D distance between the vertex
56  // and the PCA is not necessarily minimal (see Fig. 4.2 in the reference). As
57  // a consequence, the momentum and the vector connecting the vertex and the
58  // PCA are not orthogonal to each other.
59  Vector3 orthogonalDeltaR = deltaR - (deltaR.dot(momDir)) * momDir;
60 
61  // Vector perpendicular to momDir and orthogonalDeltaR
62  Vector3 perpDir = momDir.cross(orthogonalDeltaR);
63 
64  // Cartesian coordinate system with:
65  // -) origin at the vertex position
66  // -) z-axis in momentum direction
67  // -) x-axis approximately in direction of the 3D PCA (slight deviations
68  // because it was modified to make if orthogonal to momDir)
69  // -) y-axis is calculated to be orthogonal to x- and z-axis
70  // The transformation is represented by a 4x4 matrix with 0 0 0 1 in the last
71  // row.
72  Transform3 coordinateSystem;
73  // First three columns correspond to coordinate system axes
74  coordinateSystem.matrix().block<3, 1>(0, 0) = orthogonalDeltaR;
75  coordinateSystem.matrix().block<3, 1>(0, 1) = perpDir;
76  coordinateSystem.matrix().block<3, 1>(0, 2) = momDir;
77  // Fourth column corresponds to origin of the coordinate system
78  coordinateSystem.matrix().block<3, 1>(0, 3) = vtxPos;
79 
80  // Surface with normal vector in direction of the z axis of coordinateSystem
81  std::shared_ptr<PlaneSurface> planeSurface =
82  Surface::makeShared<PlaneSurface>(coordinateSystem);
83 
84  auto intersection = planeSurface
85  ->intersect(gctx, trkParams.position(gctx),
86  trkParams.direction(), false)
87  .closest();
88 
89  // Create propagator options
90  propagator_options_t pOptions(gctx, mctx);
91  pOptions.direction =
92  Direction::fromScalarZeroAsPositive(intersection.pathLength());
93 
94  // Propagate to the surface; intersection corresponds to an estimate of the 3D
95  // PCA. If deltaR and momDir were orthogonal the calculation would be exact.
96  auto result = m_cfg.propagator->propagate(trkParams, *planeSurface, pOptions);
97  if (result.ok()) {
98  return *result->endParameters;
99  } else {
100  return result.error();
101  }
102 }
103 
104 template <typename input_track_t, typename propagator_t,
105  typename propagator_options_t>
106 template <unsigned int nDim>
110  const BoundTrackParameters* trkParams,
111  const ActsVector<nDim>& vertexPos) const {
112  static_assert(nDim == 3 or nDim == 4,
113  "The number of dimensions nDim must be either 3 or 4.");
114 
115  if (trkParams == nullptr) {
116  return VertexingError::EmptyInput;
117  }
118 
119  // Retrieve weight matrix of the track's local x-, y-, and time-coordinate
120  // (the latter only if nDim = 4). For this, the covariance needs to be set.
121  if (not trkParams->covariance().has_value()) {
122  return VertexingError::NoCovariance;
123  }
124  ActsSquareMatrix<nDim - 1> subCovMat;
125  if constexpr (nDim == 3) {
126  subCovMat = trkParams->spatialImpactParameterCovariance().value();
127  } else {
128  subCovMat = trkParams->impactParameterCovariance().value();
129  }
130  ActsSquareMatrix<nDim - 1> weight = subCovMat.inverse();
131 
132  // Orientation of the surface (i.e., axes of the corresponding coordinate
133  // system)
134  RotationMatrix3 surfaceAxes =
135  trkParams->referenceSurface().transform(gctx).rotation();
136  // Origin of the surface coordinate system
137  Vector3 surfaceOrigin =
138  trkParams->referenceSurface().transform(gctx).translation();
139 
140  // x- and y-axis of the surface coordinate system
141  Vector3 xAxis = surfaceAxes.col(0);
142  Vector3 yAxis = surfaceAxes.col(1);
143 
144  // Vector pointing from the surface origin to the vertex position
145  // TODO: The vertex should always be at the surfaceOrigin since the
146  // track parameters should be obtained by estimate3DImpactParameters.
147  // Therefore, originToVertex should always be 0, which is currently not the
148  // case.
149  Vector3 originToVertex = vertexPos.template head<3>() - surfaceOrigin;
150 
151  // x-, y-, and possibly time-coordinate of the vertex and the track in the
152  // surface coordinate system
153  ActsVector<nDim - 1> localVertexCoords;
154  localVertexCoords.template head<2>() =
155  Vector2(originToVertex.dot(xAxis), originToVertex.dot(yAxis));
156 
157  ActsVector<nDim - 1> localTrackCoords;
158  localTrackCoords.template head<2>() =
159  Vector2(trkParams->parameters()[eX], trkParams->parameters()[eY]);
160 
161  // Fill time coordinates if we check the 4D vertex compatibility
162  if constexpr (nDim == 4) {
163  localVertexCoords(2) = vertexPos(3);
164  localTrackCoords(2) = trkParams->parameters()[eBoundTime];
165  }
166 
167  // residual
168  ActsVector<nDim - 1> residual = localTrackCoords - localVertexCoords;
169 
170  // return chi2
171  return residual.dot(weight * residual);
172 }
173 
174 template <typename input_track_t, typename propagator_t,
175  typename propagator_options_t>
177  input_track_t, propagator_t,
178  propagator_options_t>::performNewtonOptimization(const Vector3& helixCenter,
179  const Vector3& vtxPos,
180  double phi, double theta,
181  double rho) const {
182  double sinPhi = std::sin(phi);
183  double cosPhi = std::cos(phi);
184 
185  int nIter = 0;
186  bool hasConverged = false;
187 
188  double cotTheta = 1. / std::tan(theta);
189 
190  double xO = helixCenter.x();
191  double yO = helixCenter.y();
192  double zO = helixCenter.z();
193 
194  double xVtx = vtxPos.x();
195  double yVtx = vtxPos.y();
196  double zVtx = vtxPos.z();
197 
198  // Iterate until convergence is reached or the maximum amount of iterations is
199  // exceeded
200  while (!hasConverged && nIter < m_cfg.maxIterations) {
201  double derivative = rho * ((xVtx - xO) * cosPhi + (yVtx - yO) * sinPhi +
202  (zVtx - zO + rho * phi * cotTheta) * cotTheta);
203  double secDerivative = rho * (-(xVtx - xO) * sinPhi + (yVtx - yO) * cosPhi +
204  rho * cotTheta * cotTheta);
205 
206  if (secDerivative < 0.) {
207  return VertexingError::NumericFailure;
208  }
209 
210  double deltaPhi = -derivative / secDerivative;
211 
212  phi += deltaPhi;
213  sinPhi = std::sin(phi);
214  cosPhi = std::cos(phi);
215 
216  nIter += 1;
217 
218  if (std::abs(deltaPhi) < m_cfg.precision) {
219  hasConverged = true;
220  }
221  } // end while loop
222 
223  if (!hasConverged) {
224  // max iterations reached but did not converge
225  return VertexingError::NotConverged;
226  }
227  return phi;
228 }
229 
230 template <typename input_track_t, typename propagator_t,
231  typename propagator_options_t>
232 template <unsigned int nDim>
236  const BoundTrackParameters& trkParams,
237  const ActsVector<nDim>& vtxPos, State& state) const {
238  static_assert(nDim == 3 or nDim == 4,
239  "The number of dimensions nDim must be either 3 or 4.");
240 
241  // Reference point R
242  Vector3 refPoint = trkParams.referenceSurface().center(gctx);
243 
244  // Extract charge-related particle parameters
245  double absoluteCharge = trkParams.particleHypothesis().absoluteCharge();
246  double qOvP = trkParams.parameters()[BoundIndices::eBoundQOverP];
247 
248  // Z-component of the B field at the reference position.
249  // Note that we assume a constant B field here!
250  auto fieldRes = m_cfg.bField->getField(refPoint, state.fieldCache);
251  if (!fieldRes.ok()) {
252  return fieldRes.error();
253  }
254  double bZ = (*fieldRes)[eZ];
255 
256  // The particle moves on a straight trajectory if its charge is 0 or if there
257  // is no B field. In that case, the 3D PCA can be calculated analytically, see
258  // Sec 3.2 of the reference.
259  if (absoluteCharge == 0. or bZ == 0.) {
260  // Momentum direction (constant for straight tracks)
261  Vector3 momDirStraightTrack = trkParams.direction();
262 
263  // Current position on the track
264  Vector3 positionOnTrack = trkParams.position(gctx);
265 
266  // Distance between positionOnTrack and the 3D PCA
267  ActsScalar distanceToPca =
268  (vtxPos.template head<3>() - positionOnTrack).dot(momDirStraightTrack);
269 
270  // 3D PCA
271  ActsVector<nDim> pcaStraightTrack;
272  pcaStraightTrack.template head<3>() =
273  positionOnTrack + distanceToPca * momDirStraightTrack;
274  if constexpr (nDim == 4) {
275  // Track time at positionOnTrack
276  double timeOnTrack = trkParams.parameters()[BoundIndices::eBoundTime];
277 
278  ActsScalar m0 = trkParams.particleHypothesis().mass();
279  ActsScalar p = trkParams.particleHypothesis().extractMomentum(qOvP);
280 
281  // Speed in units of c
282  ActsScalar beta = p / std::hypot(p, m0);
283 
284  pcaStraightTrack[3] = timeOnTrack + distanceToPca / beta;
285  }
286 
287  // Vector pointing from the vertex position to the 3D PCA
288  ActsVector<nDim> deltaRStraightTrack = pcaStraightTrack - vtxPos;
289 
290  return std::make_pair(deltaRStraightTrack, momDirStraightTrack);
291  }
292 
293  // Charged particles in a constant B field follow a helical trajectory. In
294  // that case, we calculate the 3D PCA using the Newton method, see Sec 4.2 in
295  // the reference.
296 
297  // Spatial Perigee parameters (i.e., spatial parameters of 2D PCA)
298  double d0 = trkParams.parameters()[BoundIndices::eBoundLoc0];
299  double z0 = trkParams.parameters()[BoundIndices::eBoundLoc1];
300  // Momentum angles at 2D PCA
301  double phiP = trkParams.parameters()[BoundIndices::eBoundPhi];
302  double theta = trkParams.parameters()[BoundIndices::eBoundTheta];
303  // Functions of the polar angle theta for later use
304  double sinTheta = std::sin(theta);
305  double cotTheta = 1. / std::tan(theta);
306 
307  // Set optimization variable phi to the angle at the 2D PCA as a first guess.
308  // Note that phi corresponds to phiV in the reference.
309  double phi = phiP;
310 
311  // Signed radius of the helix on which the particle moves
312  double rho = sinTheta * (1. / qOvP) / bZ;
313 
314  // Position of the helix center.
315  // We can set the z-position to a convenient value since it is not fixed by
316  // the Perigee parameters. Note that phi = phiP because we did not start the
317  // optimization yet.
318  Vector3 helixCenter =
319  refPoint + Vector3(-(d0 - rho) * std::sin(phi),
320  (d0 - rho) * std::cos(phi), z0 + rho * phi * cotTheta);
321 
322  // Use Newton optimization method to iteratively change phi until we arrive at
323  // the 3D PCA
324  auto res = performNewtonOptimization(helixCenter, vtxPos.template head<3>(),
325  phi, theta, rho);
326  if (!res.ok()) {
327  return res.error();
328  }
329  // Set new phi value
330  phi = *res;
331 
332  double cosPhi = std::cos(phi);
333  double sinPhi = std::sin(phi);
334 
335  // Momentum direction at the 3D PCA.
336  // Note that we have thetaV = thetaP = theta since the polar angle does not
337  // change in a constant B field.
338  Vector3 momDir =
339  Vector3(cosPhi * sinTheta, sinPhi * sinTheta, std::cos(theta));
340 
341  // 3D PCA (point P' in the reference). Note that the prefix "3D" does not
342  // refer to the dimension of the pca variable. Rather, it indicates that we
343  // minimized the 3D distance between the track and the reference point.
344  ActsVector<nDim> pca;
345  pca.template head<3>() =
346  helixCenter + rho * Vector3(-sinPhi, cosPhi, -cotTheta * phi);
347 
348  if constexpr (nDim == 4) {
349  // Time at the 2D PCA P
350  double tP = trkParams.parameters()[BoundIndices::eBoundTime];
351 
352  ActsScalar m0 = trkParams.particleHypothesis().mass();
353  ActsScalar p = trkParams.particleHypothesis().extractMomentum(qOvP);
354 
355  // Speed in units of c
356  ActsScalar beta = p / std::hypot(p, m0);
357 
358  pca[3] = tP - rho / (beta * sinTheta) * (phi - phiP);
359  }
360  // Vector pointing from the vertex position to the 3D PCA
361  ActsVector<nDim> deltaR = pca - vtxPos;
362 
363  return std::make_pair(deltaR, momDir);
364 }
365 
366 template <typename input_track_t, typename propagator_t,
367  typename propagator_options_t>
371  const Vertex<input_track_t>& vtx,
372  const GeometryContext& gctx,
373  const Acts::MagneticFieldContext& mctx,
374  bool calculateTimeIP) const {
375  const std::shared_ptr<PerigeeSurface> perigeeSurface =
376  Surface::makeShared<PerigeeSurface>(vtx.position());
377 
378  // Create propagator options
379  propagator_options_t pOptions(gctx, mctx);
380  auto intersection =
381  perigeeSurface
382  ->intersect(gctx, track.position(gctx), track.direction(), false)
383  .closest();
384  pOptions.direction =
385  Direction::fromScalarZeroAsPositive(intersection.pathLength());
386 
387  // Do the propagation to linPoint
388  auto result = m_cfg.propagator->propagate(track, *perigeeSurface, pOptions);
389 
390  if (!result.ok()) {
391  return result.error();
392  }
393 
394  const auto& propRes = *result;
395 
396  // Check if the covariance matrix of the Perigee parameters exists
397  if (not propRes.endParameters->covariance().has_value()) {
398  return VertexingError::NoCovariance;
399  }
400 
401  // Extract Perigee parameters and corresponding covariance matrix
402  auto impactParams = propRes.endParameters->impactParameters();
403  auto impactParamCovariance =
404  propRes.endParameters->impactParameterCovariance().value();
405 
406  // Vertex variances
407  // TODO: By looking at sigmaD0 and sigmaZ0 we neglect the offdiagonal terms
408  // (i.e., we approximate the vertex as a sphere rather than an ellipsoid).
409  // Using the full covariance matrix might furnish better results.
410  double vtxVarX = vtx.covariance()(eX, eX);
411  double vtxVarY = vtx.covariance()(eY, eY);
412  double vtxVarZ = vtx.covariance()(eZ, eZ);
413 
414  ImpactParametersAndSigma ipAndSigma;
415 
416  ipAndSigma.d0 = impactParams[0];
417  // Variance of the vertex extent in the x-y-plane
418  double vtxVar2DExtent = std::max(vtxVarX, vtxVarY);
419  // TODO: vtxVar2DExtent, vtxVarZ, and vtxVarT should always be >= 0. We need
420  // to throw an error here once
421  // https://github.com/acts-project/acts/issues/2231 is resolved.
422  if (vtxVar2DExtent > 0) {
423  ipAndSigma.sigmaD0 =
424  std::sqrt(vtxVar2DExtent + impactParamCovariance(0, 0));
425  } else {
426  ipAndSigma.sigmaD0 = std::sqrt(impactParamCovariance(0, 0));
427  }
428 
429  ipAndSigma.z0 = impactParams[1];
430  if (vtxVarZ > 0) {
431  ipAndSigma.sigmaZ0 = std::sqrt(vtxVarZ + impactParamCovariance(1, 1));
432  } else {
433  ipAndSigma.sigmaZ0 = std::sqrt(impactParamCovariance(1, 1));
434  }
435 
436  if (calculateTimeIP) {
437  ipAndSigma.deltaT = std::abs(vtx.time() - impactParams[2]);
438  double vtxVarT = vtx.fullCovariance()(eTime, eTime);
439  if (vtxVarT > 0) {
440  ipAndSigma.sigmaDeltaT = std::sqrt(vtxVarT + impactParamCovariance(2, 2));
441  } else {
442  ipAndSigma.sigmaDeltaT = std::sqrt(impactParamCovariance(2, 2));
443  }
444  }
445 
446  return ipAndSigma;
447 }
448 
449 template <typename input_track_t, typename propagator_t,
450  typename propagator_options_t>
454  const Vertex<input_track_t>& vtx,
455  const Acts::Vector3& direction,
456  const GeometryContext& gctx,
457  const MagneticFieldContext& mctx) const {
458  const std::shared_ptr<PerigeeSurface> perigeeSurface =
459  Surface::makeShared<PerigeeSurface>(vtx.position());
460 
461  // Create propagator options
462  propagator_options_t pOptions(gctx, mctx);
463  pOptions.direction = Direction::Backward;
464 
465  // Do the propagation to the perigeee
466  auto result = m_cfg.propagator->propagate(track, *perigeeSurface, pOptions);
467 
468  if (!result.ok()) {
469  return result.error();
470  }
471 
472  const auto& propRes = *result;
473  const auto& params = propRes.endParameters->parameters();
474  const double d0 = params[BoundIndices::eBoundLoc0];
475  const double z0 = params[BoundIndices::eBoundLoc1];
476  const double phi = params[BoundIndices::eBoundPhi];
477  const double theta = params[BoundIndices::eBoundTheta];
478 
479  double vs = std::sin(std::atan2(direction[1], direction[0]) - phi) * d0;
480  double eta = -std::log(std::tan(theta / 2.));
481  double dir_eta = VectorHelpers::eta(direction);
482 
483  double zs = (dir_eta - eta) * z0;
484 
485  std::pair<double, double> vszs;
486 
487  vszs.first = vs >= 0. ? 1. : -1.;
488  vszs.second = zs >= 0. ? 1. : -1.;
489 
490  return vszs;
491 }
492 
493 template <typename input_track_t, typename propagator_t,
494  typename propagator_options_t>
498  const Vertex<input_track_t>& vtx,
499  const Acts::Vector3& direction,
500  const GeometryContext& gctx,
501  const MagneticFieldContext& mctx) const {
502  const std::shared_ptr<PerigeeSurface> perigeeSurface =
503  Surface::makeShared<PerigeeSurface>(vtx.position());
504 
505  // Create propagator options
506  propagator_options_t pOptions(gctx, mctx);
507  pOptions.direction = Direction::Backward;
508 
509  // Do the propagation to the perigeee
510  auto result = m_cfg.propagator->propagate(track, *perigeeSurface, pOptions);
511 
512  if (!result.ok()) {
513  return result.error();
514  }
515 
516  const auto& propRes = *result;
517  const auto& params = propRes.endParameters;
518  const Vector3 trkpos = params->position(gctx);
519  const Vector3 trkmom = params->momentum();
520 
521  double sign =
522  (direction.cross(trkmom)).dot(trkmom.cross(vtx.position() - trkpos));
523 
524  return sign >= 0. ? 1. : -1.;
525 }