From 59d8fc1d8865a1dcbe6e99a249339a31030a5294 Mon Sep 17 00:00:00 2001
From: Lucy Lewitt <lucy.lewitt@cern.ch>
Date: Tue, 28 May 2024 15:13:36 +0200
Subject: [PATCH] Add a dedicated chi2 method.

Add a dedicated chi2 method.
---
 .../EventPrimitivesCovarianceHelpers.h        |  7 ++++
 .../share/test_CovarianceHelpers.ref          | 30 +++++++++++++++
 .../test/test_CovarianceHelpers.cxx           | 37 +++++++++++++++++++
 .../src/GsfMeasurementUpdator.cxx             | 18 ++++-----
 .../TrkMeasurementUpdator/KalmanUpdator.h     |  3 +-
 .../src/KalmanUpdator.cxx                     |  6 +--
 .../src/KalmanUpdatorSMatrix.cxx              |  5 ++-
 .../src/KalmanWeightUpdator.cxx               |  9 ++---
 .../src/FastVertexFitter.cxx                  |  4 +-
 9 files changed, 95 insertions(+), 24 deletions(-)

diff --git a/Event/EventPrimitives/EventPrimitives/EventPrimitivesCovarianceHelpers.h b/Event/EventPrimitives/EventPrimitives/EventPrimitivesCovarianceHelpers.h
index eb1f3cd6ff00..d02ddab1cbba 100644
--- a/Event/EventPrimitives/EventPrimitives/EventPrimitivesCovarianceHelpers.h
+++ b/Event/EventPrimitives/EventPrimitives/EventPrimitivesCovarianceHelpers.h
@@ -214,6 +214,13 @@ inline bool isPositiveDefiniteSlow(const Amg::MatrixX& mat) {
   }
   return true;
 }
+// Chi squared statistic of a linear regression as defined in Frühwirth, 
+// section 3.2.1, equation 3.19. Precision is defined as the inverse of 
+// of the covariance.
+template <typename T, typename U>
+inline double chi2(const T& precision, const U& residual, const int sign = 1) {
+  return sign * residual.transpose() * precision * residual;
+}
 
 }  // namespace Amg
 
diff --git a/Event/EventPrimitives/share/test_CovarianceHelpers.ref b/Event/EventPrimitives/share/test_CovarianceHelpers.ref
index d6535642fb42..6b485163da14 100644
--- a/Event/EventPrimitives/share/test_CovarianceHelpers.ref
+++ b/Event/EventPrimitives/share/test_CovarianceHelpers.ref
@@ -12,6 +12,12 @@ Cholesky isPositiveSemiDefinite 0
 Cholesky isPositiveDefinite 0
 All diagonal Elements are >=0 1
 All diagonal Elements are >0 1
+ 50977.5
+ -3154.7
+   191.7
+-3.12793
+ 3.7e-05
+ChiSquared is 1.33992e+14
 
 Testing dynamic Matrix A
   50977.5   -3154.7     191.7  -3.12793   3.7e-05
@@ -25,6 +31,12 @@ Cholesky isPositiveSemiDefinite 0
 Cholesky isPositiveDefinite 0
 All diagonal Elements are >=0 1
 All diagonal Elements are >0 1
+ 50977.5
+ -3154.7
+   191.7
+-3.12793
+ 3.7e-05
+ChiSquared is 1.33992e+14
 
 Testing Matrix B
           36            0      -0.0222      -0.0001            0
@@ -38,6 +50,12 @@ Cholesky isPositiveSemiDefinite 0
 Cholesky isPositiveDefinite 0
 All diagonal Elements are >=0 0
 All diagonal Elements are >0 0
+     36
+      0
+-0.0222
+-0.0001
+      0
+ChiSquared is 43584
 
 Testing Zero Matrix
 0 0 0 0 0
@@ -51,6 +69,12 @@ Cholesky isPositiveSemiDefinite 1
 Cholesky isPositiveDefinite 0
 All diagonal Elements are >=0 1
 All diagonal Elements are >0 0
+0
+0
+0
+0
+0
+ChiSquared is 0
 
 Testing Dynamic Zero Matrix
 0 0 0 0 0
@@ -64,3 +88,9 @@ Cholesky isPositiveSemiDefinite 1
 Cholesky isPositiveDefinite 0
 All diagonal Elements are >=0 1
 All diagonal Elements are >0 0
+0
+0
+0
+0
+0
+ChiSquared is 0
diff --git a/Event/EventPrimitives/test/test_CovarianceHelpers.cxx b/Event/EventPrimitives/test/test_CovarianceHelpers.cxx
index 5edb72353111..0dfd99fd3f65 100644
--- a/Event/EventPrimitives/test/test_CovarianceHelpers.cxx
+++ b/Event/EventPrimitives/test/test_CovarianceHelpers.cxx
@@ -36,6 +36,13 @@ int main() {
               << Amg::hasPositiveOrZeroDiagElems(A) << '\n';
     std::cout << "All diagonal Elements are >0 " << Amg::hasPositiveDiagElems(A)
               << '\n';
+
+    AmgVector(5) V;
+    V << 50977.455023, -3154.699348, 191.699597, -3.127933, 0.000037;
+
+    std::cout << V << '\n';
+    std::cout << "ChiSquared is " << Amg::chi2(A, V)
+              << '\n';
   }
 
   std::cout << '\n' << "Testing dynamic Matrix A" << '\n';
@@ -60,6 +67,14 @@ int main() {
               << Amg::hasPositiveOrZeroDiagElems(A) << '\n';
     std::cout << "All diagonal Elements are >0 " << Amg::hasPositiveDiagElems(A)
               << '\n';
+
+    Amg::VectorX V;
+    V.resize(5);
+    V << 50977.455023, -3154.699348, 191.699597, -3.127933, 0.000037;
+
+    std::cout << V << '\n';
+    std::cout << "ChiSquared is " << Amg::chi2(A, V)
+              << '\n';
   }
 
   std::cout << '\n' << "Testing Matrix B" << '\n';
@@ -82,6 +97,13 @@ int main() {
               << Amg::hasPositiveOrZeroDiagElems(B) << '\n';
     std::cout << "All diagonal Elements are >0 " << Amg::hasPositiveDiagElems(B)
               << '\n';
+
+    AmgVector(5) V;
+    V << 36.0000, 0.0000, -0.0222, -0.0001, 0.0000;
+
+    std::cout << V << '\n';
+    std::cout << "ChiSquared is " << Amg::chi2(B, V)
+              << '\n';
   }
 
   std::cout << '\n' << "Testing Zero Matrix" << '\n';
@@ -101,6 +123,13 @@ int main() {
               << Amg::hasPositiveOrZeroDiagElems(zero) << '\n';
     std::cout << "All diagonal Elements are >0 "
               << Amg::hasPositiveDiagElems(zero) << '\n';
+
+    AmgVector(5) zeroV;
+    zeroV.setZero();
+
+    std::cout << zeroV << '\n';
+    std::cout << "ChiSquared is " << Amg::chi2(zero, zeroV)
+              << '\n';
   }
 
   std::cout << '\n' << "Testing Dynamic Zero Matrix" << '\n';
@@ -121,5 +150,13 @@ int main() {
               << Amg::hasPositiveOrZeroDiagElems(zero) << '\n';
     std::cout << "All diagonal Elements are >0 "
               << Amg::hasPositiveDiagElems(zero) << '\n';
+
+    Amg::VectorX zeroV;
+    zeroV.resize(5);
+    zeroV.setZero();
+
+    std::cout << zeroV << '\n';
+    std::cout << "ChiSquared is " << Amg::chi2(zero, zeroV)
+              << '\n';
   }
 }
diff --git a/Tracking/TrkFitter/TrkGaussianSumFilterUtils/src/GsfMeasurementUpdator.cxx b/Tracking/TrkFitter/TrkGaussianSumFilterUtils/src/GsfMeasurementUpdator.cxx
index 488bc9de8d2e..a02d9f7697f0 100644
--- a/Tracking/TrkFitter/TrkGaussianSumFilterUtils/src/GsfMeasurementUpdator.cxx
+++ b/Tracking/TrkFitter/TrkGaussianSumFilterUtils/src/GsfMeasurementUpdator.cxx
@@ -8,6 +8,8 @@
  * @brief  Implementation code for Gsf Measurement Update
  */
 
+#include "EventPrimitives/EventPrimitivesCovarianceHelpers.h"
+//
 #include "TrkGaussianSumFilterUtils/GsfMeasurementUpdator.h"
 #include "TrkGaussianSumFilterUtils/GsfConstants.h"
 #include "TrkGaussianSumFilterUtils/MultiComponentStateAssembler.h"
@@ -252,8 +254,7 @@ calculateFilterStep_5D(Trk::TrackParameters& TP,
   // --- compute filtered covariance matrix
   const AmgSymMatrix(5) newCov =
     trkCov.similarity(M) + sign * measCov.similarity(K);
-  double chiSquared =
-    (sign > 0) ? r.transpose() * R * r : r.transpose() * (-R) * r;
+  const double chiSquared = Amg::chi2(R, r, sign);
   // create the FQSonSurface
   fQ = Trk::FitQualityOnSurface(chiSquared, 5);
   TP.updateParameters(newPar, newCov);
@@ -299,8 +300,7 @@ calculateFilterStep_T(Trk::TrackParameters& TP,
   // C = M * trkCov * M.T() +/- K * covRio * K.T()
   const AmgSymMatrix(5) newCov =
     M * trkCov * M.transpose() + sign * K * measCov * K.transpose();
-  const double chiSquared =
-    (sign > 0) ? r.transpose() * R * r : r.transpose() * (-R) * r;
+  const double chiSquared = Amg::chi2(R, r, sign);
   // create the FQSonSurface
   fQ = Trk::FitQualityOnSurface(chiSquared, DIM);
   // In place update of parameters
@@ -429,10 +429,7 @@ makeChi2_T(Trk::FitQualityOnSurface& updatedFitQoS,
   AmgSymMatrix(DIM) R = sign * projection_T<DIM>(trkCov, paramKey);
   R += covPar;
   // calcualte the chi2 value
-  double chiSquared = 0.0;
-  if (R.determinant() != 0.0) {
-    chiSquared = r.transpose() * R.inverse() * r;
-  }
+  const double chiSquared = Amg::chi2(R.inverse(), r);
   updatedFitQoS = Trk::FitQualityOnSurface(chiSquared, DIM);
   return true;
 }
@@ -503,8 +500,7 @@ calculateWeight_T(const Trk::TrackParameters* componentTrackParameters,
     return { 0, 0 };
   }
   // Compute Chi2
-  return std::pair<double, double>(
-    det, (1. / (double)DIM) * ((r.transpose() * R.inverse() * r)(0, 0)));
+  return {det, (1. / (double)DIM) * Amg::chi2(R.inverse(), r)};
 }
 
 std::pair<double, double>
@@ -545,7 +541,7 @@ calculateWeight_2D_3(const Trk::TrackParameters* componentTrackParameters,
     return { 0, 0 };
   }
   // Compute Chi2
-  return { det, 0.5 * ((r.transpose() * R.inverse() * r)(0, 0)) };
+  return {det, 0.5 * Amg::chi2(R.inverse(), r)};
 }
 
 Trk::MultiComponentState
diff --git a/Tracking/TrkTools/TrkMeasurementUpdator/TrkMeasurementUpdator/KalmanUpdator.h b/Tracking/TrkTools/TrkMeasurementUpdator/TrkMeasurementUpdator/KalmanUpdator.h
index a2e7ca32b488..213db1a4ae11 100755
--- a/Tracking/TrkTools/TrkMeasurementUpdator/TrkMeasurementUpdator/KalmanUpdator.h
+++ b/Tracking/TrkTools/TrkMeasurementUpdator/TrkMeasurementUpdator/KalmanUpdator.h
@@ -15,6 +15,7 @@
 #define TRK_KALMANUPDATOR_H
 
 #include "AthenaBaseComps/AthAlgTool.h"
+#include "EventPrimitives/EventPrimitivesCovarianceHelpers.h"
 #include "EventPrimitives/EventPrimitives.h"
 #include "GaudiKernel/MsgStream.h"
 #include "GeoPrimitives/GeoPrimitives.h"
@@ -331,7 +332,7 @@ KalmanUpdator::makeChi2Object(const Amg::VectorX& residual,
     return FitQualityOnSurface(0.0, (int)covRio.cols());
   }
   // get chi2 = r.T() * R^-1 * r
-  double chiSquared = residual.transpose() * R.inverse() * residual;
+  const double chiSquared = Amg::chi2(R.inverse(), residual);
   ATH_MSG_VERBOSE("-U- fitQuality of " << (sign > 0 ? "predicted" : "updated")
                                        << " state, chi2 :" << chiSquared
                                        << " / ndof= " << covRio.cols());
diff --git a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdator.cxx b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdator.cxx
index e6ba4d44c3b0..caa1ae0799f5 100755
--- a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdator.cxx
+++ b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdator.cxx
@@ -250,8 +250,7 @@ std::unique_ptr<Trk::TrackParameters> Trk::KalmanUpdator::combineStates (const T
     }
 
     // compute fit quality
-    double  chiSquared = r.transpose()*R_inv*r;
-    fitQoS =  new FitQualityOnSurface(chiSquared, 5);
+    fitQoS =  new FitQualityOnSurface(Amg::chi2(R_inv, r), 5);
 
     // return cloned version of Track Parameters (MeasuredPerigee, MeasuredAtA...)
     auto comb =
@@ -421,8 +420,7 @@ Trk::KalmanUpdator::predictedStateFitQuality (const Trk::TrackParameters& one,
   Amg::VectorX r = two.parameters() - one.parameters();
   AmgSymMatrix(5) R = (covTrkOne + covTrkTwo).inverse();
   // chi2 calculation
-  double  chiSquared = r.transpose()*R*r;
-  return {chiSquared, 5};
+  return {Amg::chi2(R, r), 5};
 }
 
 std::vector<double> Trk::KalmanUpdator::initialErrors() const {
diff --git a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdatorSMatrix.cxx b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdatorSMatrix.cxx
index dc1c3a691a82..f61861724784 100755
--- a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdatorSMatrix.cxx
+++ b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanUpdatorSMatrix.cxx
@@ -14,7 +14,10 @@
 // Wolfgang Liebig <http://consult.cern.ch/xwho/people/54608>
 ///////////////////////////////////////////////////////////////////
 
+#include "EventPrimitives/EventPrimitivesCovarianceHelpers.h"
+
 #include "TrkMeasurementUpdator/KalmanUpdatorSMatrix.h"
+
 #include "TrkEventPrimitives/ParamDefs.h"
 #include "TrkEventPrimitives/LocalParameters.h"
 #include "TrkEventPrimitives/FitQualityOnSurface.h"
@@ -1293,7 +1296,7 @@ Trk::FitQualityOnSurface Trk::KalmanUpdatorSMatrix::makeChi2Object(const Amg::Ve
 		chiSquared = 0.f;
     } else {
       // get chi2 = r.T() * R^-1 * r
-      chiSquared = residual.transpose() * R.inverse() * residual;
+      chiSquared = Amg::chi2(R.inverse(), residual);
       ATH_MSG_VERBOSE( "-U- fitQuality of "<< (sign>0?"predicted":"updated")
               <<" state, chi2 :" << chiSquared << " / ndof= " << covRio.cols() );
     }
diff --git a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanWeightUpdator.cxx b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanWeightUpdator.cxx
index d44ff414c32d..3ff9873ba4bd 100755
--- a/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanWeightUpdator.cxx
+++ b/Tracking/TrkTools/TrkMeasurementUpdator/src/KalmanWeightUpdator.cxx
@@ -18,6 +18,7 @@
 #include "TrkEventPrimitives/FitQualityOnSurface.h"
 #include "TrkParameters/TrackParameters.h"
 
+#include "EventPrimitives/EventPrimitivesCovarianceHelpers.h"
 
 // constructor
 Trk::KalmanWeightUpdator::KalmanWeightUpdator(const std::string& t,const std::string& n,const IInterface* p) :
@@ -431,8 +432,7 @@ Trk::KalmanWeightUpdator::predictedStateFitQuality (    const Trk::TrackParamete
 
     AmgSymMatrix(5) R = (covTrkOne + covTrkTwo).inverse();
 
-    double  chiSquared = r.transpose()*R*r;
-    return {chiSquared, 5};
+    return {Amg::chi2(R, r), 5};
 }
 
 std::vector<double> Trk::KalmanWeightUpdator::initialErrors() const {
@@ -675,8 +675,7 @@ Trk::FitQualityOnSurface  Trk::KalmanWeightUpdator::makeChi2Object( Amg::VectorX
     // attention: similarity(vector) and similarity(matrix) are defined differently in CLHEP:
     // similarity(const Amg::VectorX &v): Returns v.T()*s*v
     // similarity(const HepSymMatrix &m1): Returns m1*s*m1.T()
-    double  chiSquared = residual1.transpose()*weight1*residual1;
-    chiSquared += residual2.transpose()*weight2*residual2;
+    const double chiSquared = Amg::chi2(weight1, residual1) + Amg::chi2(weight2, residual2);
     // number of degree of freedom added
     int numberDoF  = weight1.cols();
 
@@ -694,7 +693,7 @@ Trk::FitQualityOnSurface  Trk::KalmanWeightUpdator::makeChi2Object( Amg::VectorX
 {   // sign: -1 = updated, +1 = predicted parameters.
     Amg::MatrixX R = covRio + sign* covTrk.similarity(H);
     // get chi2 = r.T() * R^-1 * r
-    double  chiSquared = residual.transpose()*R.inverse()*residual;
+    const double  chiSquared = Amg::chi2(R.inverse(), residual);
     ATH_MSG_VERBOSE( "-U- fitQuality of "<< (sign>0?"predicted":"updated")
                      <<" state, chi2 :" << chiSquared << " / ndof= " << covRio.cols()  );
 
diff --git a/Tracking/TrkVertexFitter/TrkVertexBilloirTools/src/FastVertexFitter.cxx b/Tracking/TrkVertexFitter/TrkVertexBilloirTools/src/FastVertexFitter.cxx
index 9a1d93b16fc8..b7d67c52b939 100755
--- a/Tracking/TrkVertexFitter/TrkVertexBilloirTools/src/FastVertexFitter.cxx
+++ b/Tracking/TrkVertexFitter/TrkVertexBilloirTools/src/FastVertexFitter.cxx
@@ -15,6 +15,7 @@
 #include "TrkTrack/LinkToTrack.h"
 #include "TrkParticleBase/LinkToTrackParticleBase.h"
 #include "TrkParticleBase/TrackParticleBase.h"
+#include "EventPrimitives/EventPrimitivesCovarianceHelpers.h"
 #include "EventPrimitives/EventPrimitives.h"
 #include "TrkLinks/LinkToXAODTrackParticle.h" 
 //xAOD includes 
@@ -260,8 +261,7 @@ namespace Trk
 				deltaTrk[0] = delta_V[0]                 - ( firstStartingPoint.position() [0] - linPoint [0] );
 				deltaTrk[1] = delta_V[1]                 - ( firstStartingPoint.position() [1] - linPoint [1] );
 				deltaTrk[2] = delta_V[2]                 - ( firstStartingPoint.position() [2] - linPoint [2] );
-				double chi2FromConstraint ( ( deltaTrk.transpose() * firstStartingPoint.covariancePosition().inverse().eval() * deltaTrk ) [0] );
-				chi2New  += chi2FromConstraint;
+				chi2New += Amg::chi2(firstStartingPoint.covariancePosition().inverse().eval(), deltaTrk);
 			}
 
 			/* assign new linearization point (= new vertex position in global frame) */
-- 
GitLab