Belle II Software development
ExtendedRiemannsMethod.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8#include <tracking/trackFindingCDC/fitting/ExtendedRiemannsMethod.h>
9
10#include <tracking/trackFindingCDC/fitting/EigenObservationMatrix.h>
11#include <tracking/trackFindingCDC/fitting/CDCObservations2D.h>
12
13#include <tracking/trackingUtilities/eventdata/trajectories/CDCTrajectory2D.h>
14
15#include <tracking/trackingUtilities/geometry/UncertainPerigeeCircle.h>
16#include <tracking/trackingUtilities/geometry/PerigeeParameters.h>
17
18#include <tracking/trackingUtilities/numerics/EigenView.h>
19
20#include <framework/logging/Logger.h>
21
22#include <Math/Vector2D.h>
23
24#include <Eigen/Eigen>
25#include <Eigen/Core>
26
27using namespace Belle2;
28using namespace TrackFindingCDC;
29using namespace TrackingUtilities;
30
36
38 CDCObservations2D& observations2D) const
39{
40 size_t nObservations = observations2D.size();
41 trajectory2D.clear();
42 if (not nObservations) return;
43
44 ROOT::Math::XYVector origin = ROOT::Math::XYVector(0.0, 0.0);
45 ROOT::Math::XYVector centralPoint = observations2D.getCentralPoint();
46
47 const ROOT::Math::XYVector& ref = isOriginConstrained() ? origin : centralPoint;
48 observations2D.passiveMoveBy(ref);
49
50 UncertainPerigeeCircle perigeeCircle = fitInternal(observations2D);
51
52 double frontX = observations2D.getX(0);
53 double frontY = observations2D.getY(0);
54 ROOT::Math::XYVector frontPos(frontX, frontY);
55
56 double backX = observations2D.getX(nObservations - 1);
57 double backY = observations2D.getY(nObservations - 1);
58 ROOT::Math::XYVector backPos(backX, backY);
59
60 ROOT::Math::XYVector overPos(0, 0);
61 double totalPerps = (perigeeCircle->arcLengthBetween(frontPos, overPos) +
62 perigeeCircle->arcLengthBetween(overPos, backPos));
63
64 if (totalPerps < 0) {
65 perigeeCircle.reverse();
66 }
67
68 trajectory2D = CDCTrajectory2D(ref, perigeeCircle);
69}
70
71namespace {
72
76 enum EParabolicIndices {
77 iW = 0,
78 iX = 1,
79 iY = 2,
80 iR2 = 3,
81 iL = 4
82 };
83 }
84
85 PerigeeCircle fit(const Eigen::Matrix< double, 5, 5 >& sumMatrix,
86 bool lineConstrained = false,
87 bool originConstrained = false)
88 {
89 using namespace NParabolicParameterIndices;
90 // Solve the normal equation X * n = y
91 if (lineConstrained) {
92 if (originConstrained) {
93 Eigen::Matrix< double, 2, 2> X = sumMatrix.block<2, 2>(1, 1);
94 Eigen::Matrix< double, 2, 1> y = sumMatrix.block<2, 1>(1, iL);
95 Eigen::Matrix< double, 2, 1> n = X.ldlt().solve(y);
96 return PerigeeCircle::fromN(0.0, n(0), n(1), 0.0);
97
98 } else {
99 Eigen::Matrix< double, 3, 3> X = sumMatrix.block<3, 3>(0, 0);
100 Eigen::Matrix< double, 3, 1> y = sumMatrix.block<3, 1>(0, iL);
101 Eigen::Matrix< double, 3, 1> n = X.ldlt().solve(y);
102 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), 0.0);
103
104 }
105
106 } else {
107 if (originConstrained) {
108 Eigen::Matrix< double, 3, 3> X = sumMatrix.block<3, 3>(1, 1);
109 Eigen::Matrix< double, 3, 1> y = sumMatrix.block<3, 1>(1, iL);
110 Eigen::Matrix< double, 3, 1> n = X.ldlt().solve(y);
111 return PerigeeCircle::fromN(0.0, n(0), n(1), n(2));
112
113 } else {
114 Eigen::Matrix< double, 4, 4> X = sumMatrix.block<4, 4>(0, 0);
115 Eigen::Matrix< double, 4, 1> y = sumMatrix.block<4, 1>(0, iL);
116 Eigen::Matrix< double, 4, 1> n = X.ldlt().solve(y);
117 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), n(iR2));
118
119 }
120 }
121 }
122
123
125 PerigeeCircle fit(Eigen::Matrix< double, 4, 4 > sumMatrix,
126 bool lineConstrained = false,
127 bool originConstrained = false)
128 {
129 // Solve the normal equation min_n n^T * X * n
130 // n is the smallest eigenvector
131
132 using namespace NParabolicParameterIndices;
133 if (lineConstrained) {
134 if (originConstrained) {
135 Eigen::Matrix< double, 2, 2> X = sumMatrix.block<2, 2>(1, 1);
136 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 2, 2> > eigensolver(X);
137 Eigen::Matrix<double, 2, 1> n = eigensolver.eigenvectors().col(0);
138 if (eigensolver.info() != Eigen::Success) {
139 B2WARNING("SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
140 }
141 return PerigeeCircle::fromN(0.0, n(0), n(1), 0.0);
142
143 } else {
144 Eigen::Matrix< double, 3, 3> X = sumMatrix.block<3, 3>(0, 0);
145 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 3, 3> > eigensolver(X);
146 Eigen::Matrix<double, 3, 1> n = eigensolver.eigenvectors().col(0);
147 if (eigensolver.info() != Eigen::Success) {
148 B2WARNING("Eigen::SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
149 }
150 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), 0.0);
151
152 }
153 } else {
154 if (originConstrained) {
155 Eigen::Matrix< double, 3, 3> X = sumMatrix.block<3, 3>(1, 1);
156 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 3, 3> > eigensolver(X);
157 Eigen::Matrix<double, 3, 1> n = eigensolver.eigenvectors().col(0);
158 if (eigensolver.info() != Eigen::Success) {
159 B2WARNING("Eigen::SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
160 }
161 return PerigeeCircle::fromN(0.0, n(0), n(1), n(2));
162
163 } else {
164 Eigen::Matrix< double, 4, 4 > X = sumMatrix.block<4, 4>(0, 0);
165 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 4, 4> > eigensolver(X);
166 Eigen::Matrix<double, 4, 1> n = eigensolver.eigenvectors().col(0);
167 if (eigensolver.info() != Eigen::Success) {
168 B2WARNING("Eigen::SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
169 }
170
171 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), n(iR2));
172 }
173 }
174 }
175
177 PerigeeCircle fitSeperateOffset(Eigen::Matrix< double, 4, 1 > means,
178 Eigen::Matrix< double, 4, 4 > c,
179 bool lineConstrained = false)
180 {
181 // Solve the normal equation min_n n^T * c * n
182 // for the plane normal and move the plain by the offset
183 // n is the smallest eigenvector
184 using namespace NParabolicParameterIndices;
185 if (lineConstrained) {
186 Eigen::Matrix< double, 2, 2> X = c.block<2, 2>(1, 1);
187 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 2, 2> > eigensolver(X);
188 if (eigensolver.info() != Eigen::Success) {
189 B2WARNING("Eigen::SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
190 }
191
192 //the eigenvalues are generated in increasing order
193 //we are interested in the lowest one since we want to compute the normal vector of the plane
194 Eigen::Matrix<double, 4, 1> n;
195 n.middleRows<2>(iX) = eigensolver.eigenvectors().col(0);
196 n(iW) = -means.middleRows<2>(iX).transpose() * n.middleRows<2>(iX);
197 n(iR2) = 0.;
198 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), n(iR2));
199
200 } else {
201 Eigen::Matrix< double, 3, 3> X = c.block<3, 3>(1, 1);
202 Eigen::SelfAdjointEigenSolver< Eigen::Matrix<double, 3, 3> > eigensolver(X);
203 if (eigensolver.info() != Eigen::Success) {
204 B2WARNING("Eigen::SelfAdjointEigenSolver could not compute the eigen values of the observation matrix");
205 }
206
207 // the eigenvalues are generated in increasing order
208 // we are interested in the lowest one since we want to compute the normal vector of the plane
209 Eigen::Matrix<double, 4, 1> n;
210 n.middleRows<3>(iX) = eigensolver.eigenvectors().col(0);
211 n(iW) = -means.middleRows<3>(iX).transpose() * n.middleRows<3>(iX);
212 return PerigeeCircle::fromN(n(iW), n(iX), n(iY), n(iR2));
213
214 }
215 }
216
217
219 double calcChi2(const PerigeeCircle& parameters,
220 const Eigen::Matrix< double, 5, 5 >& s)
221 {
222 Eigen::Matrix<double, 5, 1> n;
223 n(0) = parameters.n0();
224 n(1) = parameters.n1();
225 n(2) = parameters.n2();
226 n(3) = parameters.n3();
227 n(4) = -1;
228
229 double chi2 = n.transpose() * s * n;
230 return chi2;
231 }
232
233
235 double calcChi2(const PerigeeCircle& parameters,
236 const Eigen::Matrix< double, 4, 4 >& s)
237 {
238 Eigen::Matrix<double, 4, 1> n;
239 n(0) = parameters.n0();
240 n(1) = parameters.n1();
241 n(2) = parameters.n2();
242 n(3) = parameters.n3();
243
244 double chi2 = n.transpose() * s * n;
245 return chi2;
246 }
247
248
249 PerigeePrecision calcPrecision(const PerigeeCircle& parameters,
250 const Eigen::Matrix< double, 4, 4 >& s,
251 bool lineConstrained = false,
252 bool originConstrained = false)
253 {
254 const double impact = parameters.impact();
255 const ROOT::Math::XYVector& phi0Vec = parameters.tangential();
256 const double curvature = parameters.curvature();
257
258 using namespace NPerigeeParameterIndices;
259 Eigen::Matrix<double, 4, 3> ambiguity = Eigen::Matrix<double, 4, 3> ::Zero();
260
261 ambiguity(0, c_Curv) = impact * impact / 2;
262 ambiguity(1, c_Curv) = phi0Vec.y() * impact;
263 ambiguity(2, c_Curv) = -phi0Vec.x() * impact;
264 ambiguity(3, c_Curv) = 1.0 / 2.0;
265
266 ambiguity(0, c_Phi0) = 0;
267 ambiguity(1, c_Phi0) = phi0Vec.x() * (1 + curvature * impact);
268 ambiguity(2, c_Phi0) = phi0Vec.y() * (1 + curvature * impact);
269 ambiguity(3, c_Phi0) = 0;
270
271 ambiguity(0, c_I) = 1 + curvature * impact;
272 ambiguity(1, c_I) = phi0Vec.y() * curvature;
273 ambiguity(2, c_I) = -phi0Vec.x() * curvature;
274 ambiguity(3, c_I) = 0;
275
276 Eigen::Matrix<double, 3, 3> perigeePrecision = ambiguity.transpose() * s * ambiguity;
277 // Zero out the unfitted parameters from the precision matrix
278 if (lineConstrained) {
279 perigeePrecision.row(c_Curv) = Eigen::Matrix<double, 1, 3>::Zero();
280 perigeePrecision.col(c_Curv) = Eigen::Matrix<double, 3, 1>::Zero();
281 }
282
283 if (originConstrained) {
284 perigeePrecision.row(c_I) = Eigen::Matrix<double, 1, 3>::Zero();
285 perigeePrecision.col(c_I) = Eigen::Matrix<double, 3, 1>::Zero();
286 }
287
288 PerigeePrecision result;
289 mapToEigen(result) = perigeePrecision;
290 return result;
291 }
292
293}
294
295
296
298{
299 using namespace NParabolicParameterIndices;
300
301 // Matrix of weighted sums
302 Eigen::Matrix< double, 5, 5 > s = getWXYRLSumMatrix(observations2D);
303
304 // The same as above without drift lengths
305 Eigen::Matrix<double, 4, 4> sNoL = s.block<4, 4>(0, 0);
306
307 // Determine NDF : Circle fit eats up to 3 degrees of freedom depending on the constraints
308 size_t ndf = observations2D.size() - 1;
309
310 if (not isOriginConstrained()) {
311 --ndf;
312 }
313
314 if (not isLineConstrained()) {
315 --ndf;
316 }
317
318 // Parameters to be fitted
319 UncertainPerigeeCircle resultCircle;
320 double chi2 = 0;
321
322 size_t nObservationsWithDriftRadius = observations2D.getNObservationsWithDriftRadius();
323 if (nObservationsWithDriftRadius > 0) {
324 resultCircle = UncertainPerigeeCircle(::fit(s, isLineConstrained(), isOriginConstrained()));
325 chi2 = calcChi2(resultCircle, s);
326 } else {
327 if (not isOriginConstrained()) {
328 // Alternative implementation for comparison
329
330 // Matrix of averages
331 Eigen::Matrix< double, 4, 4> aNoL = sNoL / sNoL(iW);
332
333 // Measurement means
334 Eigen::Matrix< double, 4, 1> meansNoL = aNoL.row(iW);
335
336 // Covariance matrix
337 Eigen::Matrix< double, 4, 4> cNoL = aNoL - meansNoL * meansNoL.transpose();
338
339 resultCircle = UncertainPerigeeCircle(fitSeperateOffset(meansNoL, cNoL, isLineConstrained()));
340
341 } else {
342 resultCircle = UncertainPerigeeCircle(::fit(sNoL, isLineConstrained(), isOriginConstrained()));
343 }
344
345 chi2 = calcChi2(resultCircle, sNoL);
346 }
347
348 // Covariance calculation does not need the drift lengths, which is why we do not forward them.
349 PerigeePrecision perigeePrecision =
350 calcPrecision(resultCircle, sNoL, isLineConstrained(), isOriginConstrained());
351
352 // Use in pivoting in case the matrix is not full rank as it is for the constrained cases
353 PerigeeCovariance perigeeCovariance = PerigeeUtil::covarianceFromPrecision(perigeePrecision);
354
355 resultCircle.setNDF(ndf);
356 resultCircle.setChi2(chi2);
357 resultCircle.setPerigeeCovariance(perigeeCovariance);
358 return resultCircle;
359}
Class serving as a storage of observed drift circles to present to the Riemann fitter.
double getX(int iObservation) const
Getter for the x value of the observation at the given index.
double getY(int iObservation) const
Getter for the y value of the observation at the given index.
void passiveMoveBy(const ROOT::Math::XYVector &origin)
Moves all observations passively such that the given vector becomes to origin of the new coordinate s...
std::size_t size() const
Returns the number of observations stored.
std::size_t getNObservationsWithDriftRadius() const
Returns the number of observations having a drift radius radius.
ROOT::Math::XYVector getCentralPoint() const
Extracts the observation center that is at the index in the middle.
void update(TrackingUtilities::CDCTrajectory2D &trajectory2D, CDCObservations2D &observations2D) const
Executes the fit and updates the trajectory parameters This may render the information in the observa...
bool isLineConstrained() const
Getter for the indicator that lines should be fitted by this fitter.
TrackingUtilities::UncertainPerigeeCircle fitInternal(CDCObservations2D &observations2D) const
Internal method doing the heavy work.
bool m_originConstrained
Memory for the flag indicating that curves through the origin shall be fitter.
ExtendedRiemannsMethod()
Constructor setting the default constraints.
bool m_lineConstrained
Memory for the flag indicating that lines should be fitted.
bool isOriginConstrained() const
Getter for the indicator that curves through the origin should be fitted by this fitter.
Particle trajectory as it is seen in xy projection represented as a circle.
void clear()
Clears all information from this trajectory.
Extension of the generalized circle also caching the perigee coordinates.
double arcLengthBetween(const ROOT::Math::XYVector &from, const ROOT::Math::XYVector &to) const
Calculates the arc length between two points of closest approach on the circle.
static PerigeeCircle fromN(double n0, double n1, double n2, double n3=0)
Constructor with the four parameters of the generalized circle.
Adds an uncertainty matrix to the circle in perigee parameterisation.
void reverse()
Flips the orientation of the circle in place.
void setPerigeeCovariance(const PerigeeCovariance &perigeeCovariance)
Setter for the whole covariance matrix of the perigee parameters.
void setNDF(std::size_t ndf)
Setter for the number of degrees of freediom used in the circle fit.
void setChi2(const double chi2)
Setter for the chi square value of the circle fit.
Abstract base class for different kinds of events.
Namespace to hide the contained enum constants.