Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FullBilloirVertexFitter.ipp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file FullBilloirVertexFitter.ipp
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 
15 
16 namespace {
17 
21 template <typename input_track_t>
22 struct BilloirTrack {
23  BilloirTrack(const input_track_t* params) : originalTrack(params) {}
24 
25  BilloirTrack(const BilloirTrack& arg) = default;
26 
27  const input_track_t* originalTrack;
28  double chi2 = 0;
29 
30  // We drop the summation index i from Ref. (1) for better readability
32  Acts::ActsMatrix<Acts::eBoundSize, 4> D; // Di (position Jacobian)
33  Acts::ActsMatrix<Acts::eBoundSize, 3> E; // Ei (momentum Jacobian)
34  Acts::ActsSquareMatrix<3> C; // = sum{Ei^T Wi * Ei}
35  Acts::ActsMatrix<4, 3> B; // = Di^T * Wi * Ei
36  Acts::ActsSquareMatrix<3> Cinv; // = (Ei^T * Wi * Ei)^-1
37  Acts::Vector3 U; // = Ei^T * Wi * dqi
38  Acts::ActsMatrix<4, 3> BCinv; // = Bi * Ci^-1
39  Acts::BoundVector deltaQ;
40 };
41 
45 struct BilloirVertex {
46  // A = sum{Di^T * Wi * Di}
47  Acts::SquareMatrix4 A = Acts::SquareMatrix4::Zero();
48  // T = sum{Di^T * Wi * dqi}
49  Acts::Vector4 T = Acts::Vector4::Zero();
50  // sumBCinvBt = sum{Bi * Ci^-1 * Bi^T}
51  Acts::SquareMatrix4 sumBCinvBt = Acts::SquareMatrix4::Zero();
52  // sumBCinvU = sum{B * Ci^-1 * Ui}
53  Acts::Vector4 sumBCinvU = Acts::Vector4::Zero();
54 };
55 
56 } // end anonymous namespace
57 
58 template <typename input_track_t, typename linearizer_t>
61  const std::vector<const input_track_t*>& paramVector,
62  const linearizer_t& linearizer,
63  const VertexingOptions<input_track_t>& vertexingOptions,
64  State& state) const {
65  unsigned int nTracks = paramVector.size();
66  double chi2 = std::numeric_limits<double>::max();
67 
68  if (nTracks == 0) {
69  return Vertex<input_track_t>(Vector3(0., 0., 0.));
70  }
71 
72  // Set number of degrees of freedom following Eq. 8.28 from Ref. (2):
73  //
74  // ndf = sum_i=1^nTracks rank(Wi^-1) - 3 * (nTracks + 1),
75  //
76  // where W_i denotes the weight matrix of the i-th track.
77  // Assuming rank(W_i) = #(Perigee params) = 6 for all tracks, we have
78  //
79  // ndf = 3 * nTracks - 3.
80  int ndf = 3 * nTracks - 3;
81  // TODO: this seems strange - can we even do a vertex fit if we only have one
82  // track?
83  if (nTracks < 2) {
84  ndf = 1;
85  }
86 
87  // Since we add a term to the chi2 when adding a vertex constraint (see Ref.
88  // (1)), the number of degrees of freedom increases
89  if (vertexingOptions.useConstraintInFit) {
90  ndf += 3;
91  }
92 
93  std::vector<BilloirTrack<input_track_t>> billoirTracks;
94  std::vector<Vector3> trackMomenta;
95  // Initial guess of the 4D vertex position
96  Vector4 linPoint = vertexingOptions.constraint.fullPosition();
97  Vertex<input_track_t> fittedVertex;
98 
99  for (int nIter = 0; nIter < m_cfg.maxIterations; ++nIter) {
100  billoirTracks.clear();
101  double newChi2 = 0;
102  BilloirVertex billoirVertex;
103 
104  Vector3 linPointPos = VectorHelpers::position(linPoint);
105  // Make Perigee surface at linPointPos, transverse plane of Perigee
106  // corresponds the global x-y plane
107  const std::shared_ptr<PerigeeSurface> perigeeSurface =
108  Surface::makeShared<PerigeeSurface>(linPointPos);
109 
110  // iterate over all tracks
111  for (std::size_t iTrack = 0; iTrack < nTracks; ++iTrack) {
112  const input_track_t* trackContainer = paramVector[iTrack];
113 
114  const auto& trackParams = extractParameters(*trackContainer);
115 
116  auto result = linearizer.linearizeTrack(
117  trackParams, linPoint[3], *perigeeSurface,
118  vertexingOptions.geoContext, vertexingOptions.magFieldContext,
119  state.linearizerState);
120  if (!result.ok()) {
121  return result.error();
122  }
123 
124  const auto& linTrack = *result;
125  const auto& parametersAtPCA = linTrack.parametersAtPCA;
126  double d0 = parametersAtPCA[BoundIndices::eBoundLoc0];
127  double z0 = parametersAtPCA[BoundIndices::eBoundLoc1];
128  double phi = parametersAtPCA[BoundIndices::eBoundPhi];
129  double theta = parametersAtPCA[BoundIndices::eBoundTheta];
130  double qOverP = parametersAtPCA[BoundIndices::eBoundQOverP];
131  double t0 = parametersAtPCA[BoundIndices::eBoundTime];
132 
133  // Take the track momenta at the PCA as an initial estimate of the track
134  // momenta at the vertex
135  if (nIter == 0) {
136  trackMomenta.push_back(Vector3(phi, theta, qOverP));
137  }
138 
139  // Calculate F(V_0,p_0), i.e., the track parameters estimated from the
140  // vertex position and the track momenta. fD0 = fZ0 = 0 because the track
141  // originates at the vertex in the Billoir model.
142  double fPhi = trackMomenta[iTrack][0];
143  double fTheta = trackMomenta[iTrack][1];
144  double fQOvP = trackMomenta[iTrack][2];
145  double fTime = linPoint[FreeIndices::eFreeTime];
146  BilloirTrack<input_track_t> billoirTrack(trackContainer);
147 
148  billoirTrack.deltaQ << d0, z0, phi - fPhi, theta - fTheta, qOverP - fQOvP,
149  t0 - fTime;
150 
151  // position jacobian (D matrix)
152  ActsMatrix<eBoundSize, 4> D = linTrack.positionJacobian;
153 
154  // momentum jacobian (E matrix)
155  ActsMatrix<eBoundSize, 3> E = linTrack.momentumJacobian;
156 
157  // cache some matrix multiplications
158  BoundSquareMatrix W = linTrack.weightAtPCA;
159  ActsMatrix<4, eBoundSize> DtW = D.transpose() * W;
160  ActsMatrix<3, eBoundSize> EtW = E.transpose() * W;
161 
162  // compute track quantities for Billoir fit
163  billoirTrack.D = D;
164  billoirTrack.E = E;
165  billoirTrack.W = W;
166  billoirTrack.C = EtW * E;
167  billoirTrack.B = DtW * E; // Di^T * Wi * Ei
168  billoirTrack.U = EtW * billoirTrack.deltaQ; // Ei^T * Wi * dqi
169  billoirTrack.Cinv = (billoirTrack.C).inverse(); // (Ei^T * Wi * Ei)^-1
170  billoirTrack.BCinv =
171  billoirTrack.B * billoirTrack.Cinv; // BCinv = Bi * Ci^-1
172 
173  // compute vertex quantities for Billoir fit
174  billoirVertex.T += DtW * billoirTrack.deltaQ; // sum{Di^T * Wi * dqi}
175  billoirVertex.A += DtW * D; // sum{Di^T * Wi * Di}
176  billoirVertex.sumBCinvU +=
177  billoirTrack.BCinv * billoirTrack.U; // sum{Bi * Ci^-1 * Ui}
178  billoirVertex.sumBCinvBt +=
179  billoirTrack.BCinv *
180  billoirTrack.B.transpose(); // sum{Bi * Ci^-1 * Bi^T}
181 
182  billoirTracks.push_back(billoirTrack);
183  } // end loop tracks
184 
185  // (Matrix-valued) factor contributing to the vertex estimate update (i.e.,
186  // to deltaV).
187  // deltaVFac = T-sum{Bi*Ci^-1*Ui}
188  Vector4 deltaVFac = billoirVertex.T - billoirVertex.sumBCinvU;
189 
190  // Inverse of the covariance matrix of the 4D vertex position (note that
191  // Cov(V) = Cov(deltaV)), see Ref. (1).
192  // invCovV = A-sum{Bi*Ci^-1*Bi^T}
193  SquareMatrix4 invCovV = billoirVertex.A - billoirVertex.sumBCinvBt;
194  if (vertexingOptions.useConstraintInFit) {
195  // Position of vertex constraint in Billoir frame (i.e., in coordinate
196  // system with origin at linPoint). This will be 0 for first iteration but
197  // != 0 from second on since our first guess for the vertex position is
198  // the vertex constraint position.
199  Vector4 posInBilloirFrame =
200  vertexingOptions.constraint.fullPosition() - linPoint;
201 
202  // For vertex constraint: T -> T + Cb^-1 (b - V0) where Cb is the
203  // covariance matrix of the constraint, b is the constraint position, and
204  // V0 is the vertex estimate (see Ref. (1)).
205  deltaVFac += vertexingOptions.constraint.fullCovariance().inverse() *
206  posInBilloirFrame;
207  // For vertex constraint: A -> A + Cb^-1
208  invCovV += vertexingOptions.constraint.fullCovariance().inverse();
209  }
210 
211  // Covariance matrix of the 4D vertex position
212  SquareMatrix4 covV = invCovV.inverse();
213  // Update of the vertex position
214  Vector4 deltaV = covV * deltaVFac;
215  //--------------------------------------------------------------------------------------
216  // start momentum related calculations
217 
218  std::vector<std::optional<BoundSquareMatrix>> covDeltaP(nTracks);
219 
220  // Update track momenta and calculate the covariance of the track parameters
221  // after the fit (TODO: parameters -> momenta).
222  for (std::size_t iTrack = 0; iTrack < billoirTracks.size(); ++iTrack) {
223  auto& billoirTrack = billoirTracks[iTrack];
224 
225  Vector3 deltaP = (billoirTrack.Cinv) *
226  (billoirTrack.U - billoirTrack.B.transpose() * deltaV);
227 
228  // update track momenta
229  trackMomenta[iTrack] += deltaP;
230 
231  // correct for 2PI / PI periodicity
232  const auto correctedPhiTheta = detail::normalizePhiTheta(
233  trackMomenta[iTrack][0], trackMomenta[iTrack][1]);
234  trackMomenta[iTrack][0] = correctedPhiTheta.first;
235  trackMomenta[iTrack][1] = correctedPhiTheta.second;
236 
237  // Calculate chi2 of the track...
238  Acts::BoundVector diffQ = billoirTrack.deltaQ - billoirTrack.D * deltaV -
239  billoirTrack.E * deltaP;
240  billoirTrack.chi2 = diffQ.transpose().dot(billoirTrack.W * diffQ);
241  // ... and add it to the total chi2 value
242  newChi2 += billoirTrack.chi2;
243 
244  // calculate the 6x6 cross-covariance matrix
245  // TODO: replace fittedParams with fittedMomentum since d0 and z0 are
246  // ill-defined. At the moment, only the submatrix of momentum covariances
247  // is correct.
248 
249  // coordinate transformation matrix, i.e.,
250  // d(d0,z0,phi,theta,qOverP,t)/d(x,y,z,t,phi,theta,qOverP)
251  // TODO: This is not correct since the (x, y, z, t) in the derivatives in
252  // the D matrix correspond to the global track position at the PCA rather
253  // than the 4D vertex position.
254  ActsMatrix<eBoundSize, 7> transMat;
255  transMat.setZero();
256  transMat.block<2, 2>(0, 0) = billoirTrack.D.template block<2, 2>(0, 0);
257  transMat(1, 2) = 1.;
258  transMat(2, 4) = 1.;
259  transMat(3, 5) = 1.;
260  transMat(4, 6) = 1.;
261  transMat(5, 3) = 1.;
262 
263  // cov(V,P)
264  // TODO: This is incorrect (see Ref. (2)), but it will not be needed
265  // anyway once we replace fittedParams with fittedMomentum
266  ActsMatrix<4, 3> covVP = billoirTrack.B;
267 
268  // cov(P,P), 3x3 matrix
269  ActsSquareMatrix<3> covP =
270  billoirTrack.Cinv +
271  billoirTrack.BCinv.transpose() * covV * billoirTrack.BCinv;
272 
274  cov.setZero();
275  cov.block<4, 4>(0, 0) = covV;
276  cov.block<4, 3>(0, 4) = covVP;
277  cov.block<3, 4>(4, 0) = covVP.transpose();
278  cov.block<3, 3>(4, 4) = covP;
279 
280  covDeltaP[iTrack] = transMat * cov * transMat.transpose();
281  }
282 
283  // assign new linearization point (= new vertex position in global frame)
284  linPoint += deltaV;
285 
286  // Add an additional term to chi2 if there is a vertex constraint (see Ref.
287  // (1))
288  if (vertexingOptions.useConstraintInFit) {
289  // Position of vertex constraint in Billoir frame (i.e., in coordinate
290  // system with origin at linPoint).
291  Vector4 posInBilloirFrame =
292  vertexingOptions.constraint.fullPosition() - linPoint;
293 
294  newChi2 +=
295  (posInBilloirFrame.transpose())
296  .dot(vertexingOptions.constraint.fullCovariance().inverse() *
297  posInBilloirFrame);
298  }
299 
300  if (!std::isnormal(newChi2)) {
301  return VertexingError::NumericFailure;
302  }
303 
304  if (newChi2 < chi2) {
305  chi2 = newChi2;
306 
307  fittedVertex.setFullPosition(linPoint);
308  fittedVertex.setFullCovariance(covV);
309  fittedVertex.setFitQuality(chi2, ndf);
310 
311  std::vector<TrackAtVertex<input_track_t>> tracksAtVertex;
312 
313  std::shared_ptr<PerigeeSurface> perigee =
314  Surface::makeShared<PerigeeSurface>(
315  VectorHelpers::position(linPoint));
316 
317  for (std::size_t iTrack = 0; iTrack < billoirTracks.size(); ++iTrack) {
318  const auto& billoirTrack = billoirTracks[iTrack];
319 
320  // TODO: replace refittedParams with refittedMomenta since d0 and z0
321  // after the vertex fit are ill-defined (FullBilloir only outputs the
322  // updated track momenta)
323 
324  // new refitted trackparameters
325  BoundVector paramVec = BoundVector::Zero();
326  paramVec[eBoundPhi] = trackMomenta[iTrack](0);
327  paramVec[eBoundTheta] = trackMomenta[iTrack](1);
328  paramVec[eBoundQOverP] = trackMomenta[iTrack](2);
329  paramVec[eBoundTime] = linPoint[FreeIndices::eFreeTime];
330  BoundTrackParameters refittedParams(
331  perigee, paramVec, covDeltaP[iTrack],
332  extractParameters(*billoirTrack.originalTrack)
333  .particleHypothesis());
334  TrackAtVertex<input_track_t> trackAtVertex(
335  billoirTrack.chi2, refittedParams, billoirTrack.originalTrack);
336  tracksAtVertex.push_back(std::move(trackAtVertex));
337  }
338 
339  fittedVertex.setTracksAtVertex(tracksAtVertex);
340  }
341  } // end loop iterations
342 
343  return fittedVertex;
344 }