From a086ffd6b7b086909cdaf9d4e37e1740e586c1b4 Mon Sep 17 00:00:00 2001
From: Wouter Hulsbergen <wouter.hulsbergen@nikhef.nl>
Date: Sat, 17 Feb 2024 00:04:08 +0100
Subject: [PATCH] added function to vertex container of states

---
 .../RecEvent/include/Event/PrimaryVertices.h  | 12 +--
 Event/RecEvent/src/PrimaryVertices.cpp        |  6 +-
 .../include/Event/TrackVertexUtils.h          | 34 +++++++-
 Event/TrackEvent/src/TrackVertexUtils.cpp     | 85 +++++++++----------
 4 files changed, 84 insertions(+), 53 deletions(-)

diff --git a/Event/RecEvent/include/Event/PrimaryVertices.h b/Event/RecEvent/include/Event/PrimaryVertices.h
index e30b9ddf8c6..c278d201fed 100644
--- a/Event/RecEvent/include/Event/PrimaryVertices.h
+++ b/Event/RecEvent/include/Event/PrimaryVertices.h
@@ -169,12 +169,12 @@ namespace LHCb::Event::PV {
   private:
     using SymMatrix3x3 = LHCb::LinAlg::MatSym<double, 3>;
     using Vector3      = LHCb::LinAlg::Vec<double, 3>;
-    uint16_t         m_begin{0};      /// Index of the first track of this PV in the track list
-    uint16_t         m_end{0};        /// Last+1 index of the first track of this PV in the track list
-    SymMatrix3x3     m_halfD2Chi2DX2; /// Cached weight matrix, for unbiasing.
-    Vector3          m_halfDChi2DX;   /// Cached first derivative, for unbiasing.
-    double           m_sumOfWeights{0};  /// Cached sum of weights
-    VeloSegmentIDMap m_veloidmap;     /// veloids
+    uint16_t         m_begin{0};        /// Index of the first track of this PV in the track list
+    uint16_t         m_end{0};          /// Last+1 index of the first track of this PV in the track list
+    SymMatrix3x3     m_halfD2Chi2DX2;   /// Cached weight matrix, for unbiasing.
+    Vector3          m_halfDChi2DX;     /// Cached first derivative, for unbiasing.
+    double           m_sumOfWeights{0}; /// Cached sum of weights
+    VeloSegmentIDMap m_veloidmap;       /// veloids
   public:
     using PrimaryVertex::PrimaryVertex;
     /// Constructor with a selected key (only for testing)
diff --git a/Event/RecEvent/src/PrimaryVertices.cpp b/Event/RecEvent/src/PrimaryVertices.cpp
index 4d2ab958f0d..3a4712fa735 100644
--- a/Event/RecEvent/src/PrimaryVertices.cpp
+++ b/Event/RecEvent/src/PrimaryVertices.cpp
@@ -88,7 +88,7 @@ namespace LHCb::Event::PV {
     pv.setHalfD2Chi2DX2( accumulator.halfD2Chi2DX2 );
     pv.setHalfDChi2DX( LinAlg::initialize_with_zeros<LHCb::LinAlg::Vec<double, 3>>() );
     pv.setCovMatrix( convertToSMatrix<double>( cov ) );
-    pv.setSumOfWeights( accumulator.sumw ) ;
+    pv.setSumOfWeights( accumulator.sumw );
     return std::abs( delta( 2 ) ) < maxDeltaZConverged && abs( deltachi2 ) < maxDeltaChi2Converged;
   };
 
@@ -222,7 +222,7 @@ namespace LHCb::Event::PV {
     Chi2Accumulator accumulator = accumulate<true, true>( pv, pvtracks, chi2max );
     pv.setHalfDChi2DX( accumulator.halfDChi2DX );
     pv.setHalfD2Chi2DX2( accumulator.halfD2Chi2DX2 );
-    pv.setSumOfWeights( accumulator.sumw ) ;
+    pv.setSumOfWeights( accumulator.sumw );
   }
 
   /// Fill the track fields that we do not read from the Velo tracks or the persistency. This routine is called by the
@@ -330,7 +330,7 @@ namespace LHCb::Event::PV {
               weight * pvtrack.halfD2Chi2DX2( PVTrackTag::PosCovMatrixElement::v22 ).cast();
           accumulator.chi2 -= weight * pvtrack.ipchi2().cast();
           accumulator.entries -= 1;
-	  accumulator.sumw -= weight ;
+          accumulator.sumw -= weight;
         }
       }
     }
diff --git a/Event/TrackEvent/include/Event/TrackVertexUtils.h b/Event/TrackEvent/include/Event/TrackVertexUtils.h
index d6568f924b7..6153eb0d203 100644
--- a/Event/TrackEvent/include/Event/TrackVertexUtils.h
+++ b/Event/TrackEvent/include/Event/TrackVertexUtils.h
@@ -30,11 +30,20 @@ namespace LHCb::TrackVertexUtils {
   /////////////////////////////////////////////////////////////////////////
   /// Add a track to a vertex represented by a position and a weight
   /// matrix. Both the weight matrix and the covariance matrix are
-  /// computed. Returns the chi2
+  /// computed. Make sure to initialize vertexweight with zeros. Returns the chi2.
   /////////////////////////////////////////////////////////////////////////
   double addToVertex( const LHCb::State& state, Gaudi::XYZPoint& vertexpos, Gaudi::SymMatrix3x3& vertexweight,
                       Gaudi::SymMatrix3x3& vertexcov );
 
+  /////////////////////////////////////////////////////////////////////////
+  /// Add a range of tracks to a vertex represented by a position and a weight
+  /// matrix. Both the weight matrix and the covariance matrix are
+  /// computed. Make sure to initialize vertexweight with zeros. Returns the chi2.
+  /////////////////////////////////////////////////////////////////////////
+  template <typename StateContainer>
+  double addToVertex( const StateContainer& states, Gaudi::XYZPoint& vertexpos, Gaudi::SymMatrix3x3& vertexweight,
+                      Gaudi::SymMatrix3x3& vertexcov );
+
   /////////////////////////////////////////////////////////////////////////
   /// Computes a vertex from two track states.
   /////////////////////////////////////////////////////////////////////////
@@ -48,3 +57,26 @@ namespace LHCb::TrackVertexUtils {
                  Gaudi::SymMatrix3x3& vertexcov );
 
 } // namespace LHCb::TrackVertexUtils
+
+// Below are implementations.
+namespace LHCb::TrackVertexUtils {
+
+  /// Helper function to compute chi2 derivatives for vertex fit. See .cpp file.
+  double addToDerivatives( const LHCb::State& state, const Gaudi::XYZPoint& vertexpos, Gaudi::Vector3& halfDChi2DX,
+                           Gaudi::SymMatrix3x3& halfD2Chi2DX2 );
+
+  /// Helper function to compute vertex position from derivatives. See .cpp file.
+  double solve( const Gaudi::Vector3& halfDChi2DX, const Gaudi::SymMatrix3x3& halfD2Chi2DX2, Gaudi::XYZPoint& vertexpos,
+                Gaudi::SymMatrix3x3& vertexcov );
+
+  template <typename StateContainer>
+  double addToVertex( const StateContainer& states, Gaudi::XYZPoint& vertexpos, Gaudi::SymMatrix3x3& vertexweight,
+                      Gaudi::SymMatrix3x3& vertexcov ) {
+    Gaudi::Vector3 halfDChi2DX{}; // does default initializer initialize to zero? not for libEIGEN!
+    double         chi2{0};
+    for ( const auto& state : states ) chi2 += addToDerivatives( state, vertexpos, halfDChi2DX, vertexweight );
+    // compute the vertex
+    chi2 += solve( halfDChi2DX, vertexweight, vertexpos, vertexcov );
+    return chi2;
+  }
+} // namespace LHCb::TrackVertexUtils
diff --git a/Event/TrackEvent/src/TrackVertexUtils.cpp b/Event/TrackEvent/src/TrackVertexUtils.cpp
index 1585502f293..ab391bf508a 100644
--- a/Event/TrackEvent/src/TrackVertexUtils.cpp
+++ b/Event/TrackEvent/src/TrackVertexUtils.cpp
@@ -18,51 +18,50 @@ namespace LHCb::TrackVertexUtils {
   /// to a vertex. This should probably go into LHCb math.
   /////////////////////////////////////////////////////////////////////////
   Gaudi::Vector3 transform( const Gaudi::XYZVector& vec ) { return Gaudi::Vector3( vec.X(), vec.Y(), vec.Z() ); }
-  namespace {
-    double addToDerivatives( const LHCb::State& state, const Gaudi::XYZPoint& vertexpos, Gaudi::Vector3& halfDChi2DX,
-                             Gaudi::SymMatrix3x3& halfD2Chi2DX2 ) {
-      // compute residual
-      Gaudi::Vector2 res;
-      double         dz = vertexpos.z() - state.z();
-      res( 0 )          = state.x() + dz * state.tx() - vertexpos.x();
-      res( 1 )          = state.y() + dz * state.ty() - vertexpos.y();
-      // compute the weight matrix
-      const auto&         trkcov = state.covariance();
-      Gaudi::SymMatrix2x2 invcov = trkcov.Sub<Gaudi::SymMatrix2x2>( 0, 0 );
-      // extrapolate to the vertex
-      invcov( 0, 0 ) += dz * dz * trkcov( 2, 2 ) + 2 * dz * trkcov( 2, 0 );
-      invcov( 1, 0 ) += dz * dz * trkcov( 3, 2 ) + dz * ( trkcov( 3, 0 ) + trkcov( 2, 1 ) );
-      invcov( 1, 1 ) += dz * dz * trkcov( 3, 3 ) + 2 * dz * trkcov( 3, 1 );
-      invcov.Invert();
 
-      // I tried to make this faster by writing it out, but H does
-      // not contain sufficiently manby zeroes. Better to
-      // parallelize.
-      ROOT::Math::SMatrix<double, 3, 2> H;
-      H( 0, 0 ) = H( 1, 1 ) = 1;
-      H( 2, 0 )             = -state.tx();
-      H( 2, 1 )             = -state.ty();
-      halfD2Chi2DX2 += Similarity( H, invcov );
-      halfDChi2DX += ( H * invcov ) * res;
-      // You could potentially save time by reusing HW. However, it
-      // does not help enough. Perhaps better when parallelized.
-      // ROOT::Math::SMatrix<double,3,2> HW = H*invcov ;
-      // Gaudi::SymMatrix3x3 HWH ;
-      // ROOT::Math::AssignSym::Evaluate( HWH, HW*Transpose(H) ) ;
-      // halfD2Chi2DX2 += HWH ;
-      // halfDChi2DX += HW * res ;
-      return Similarity( res, invcov );
-    }
+  double addToDerivatives( const LHCb::State& state, const Gaudi::XYZPoint& vertexpos, Gaudi::Vector3& halfDChi2DX,
+                           Gaudi::SymMatrix3x3& halfD2Chi2DX2 ) {
+    // compute residual
+    Gaudi::Vector2 res;
+    double         dz = vertexpos.z() - state.z();
+    res( 0 )          = state.x() + dz * state.tx() - vertexpos.x();
+    res( 1 )          = state.y() + dz * state.ty() - vertexpos.y();
+    // compute the weight matrix
+    const auto&         trkcov = state.covariance();
+    Gaudi::SymMatrix2x2 invcov = trkcov.Sub<Gaudi::SymMatrix2x2>( 0, 0 );
+    // extrapolate to the vertex
+    invcov( 0, 0 ) += dz * dz * trkcov( 2, 2 ) + 2 * dz * trkcov( 2, 0 );
+    invcov( 1, 0 ) += dz * dz * trkcov( 3, 2 ) + dz * ( trkcov( 3, 0 ) + trkcov( 2, 1 ) );
+    invcov( 1, 1 ) += dz * dz * trkcov( 3, 3 ) + 2 * dz * trkcov( 3, 1 );
+    invcov.Invert();
 
-    double solve( const Gaudi::Vector3& halfDChi2DX, const Gaudi::SymMatrix3x3& halfD2Chi2DX2,
-                  Gaudi::XYZPoint& vertexpos, Gaudi::SymMatrix3x3& vertexcov ) {
-      vertexcov = halfD2Chi2DX2;
-      vertexcov.InvertChol();
-      Gaudi::Vector3 delta = vertexcov * halfDChi2DX;
-      vertexpos            = {vertexpos.x() + delta( 0 ), vertexpos.y() + delta( 1 ), vertexpos.z() + delta( 2 )};
-      return -1 * Dot( delta, halfDChi2DX );
-    }
-  } // namespace
+    // I tried to make this faster by writing it out, but H does
+    // not contain sufficiently manby zeroes. Better to
+    // parallelize.
+    ROOT::Math::SMatrix<double, 3, 2> H;
+    H( 0, 0 ) = H( 1, 1 ) = 1;
+    H( 2, 0 )             = -state.tx();
+    H( 2, 1 )             = -state.ty();
+    halfD2Chi2DX2 += Similarity( H, invcov );
+    halfDChi2DX += ( H * invcov ) * res;
+    // You could potentially save time by reusing HW. However, it
+    // does not help enough. Perhaps better when parallelized.
+    // ROOT::Math::SMatrix<double,3,2> HW = H*invcov ;
+    // Gaudi::SymMatrix3x3 HWH ;
+    // ROOT::Math::AssignSym::Evaluate( HWH, HW*Transpose(H) ) ;
+    // halfD2Chi2DX2 += HWH ;
+    // halfDChi2DX += HW * res ;
+    return Similarity( res, invcov );
+  }
+
+  double solve( const Gaudi::Vector3& halfDChi2DX, const Gaudi::SymMatrix3x3& halfD2Chi2DX2, Gaudi::XYZPoint& vertexpos,
+                Gaudi::SymMatrix3x3& vertexcov ) {
+    vertexcov = halfD2Chi2DX2;
+    vertexcov.InvertChol();
+    Gaudi::Vector3 delta = vertexcov * halfDChi2DX;
+    vertexpos            = {vertexpos.x() + delta( 0 ), vertexpos.y() + delta( 1 ), vertexpos.z() + delta( 2 )};
+    return -1 * Dot( delta, halfDChi2DX );
+  }
 
   double vertex( const LHCb::State& stateA, const LHCb::State& stateB, Gaudi::XYZPoint& vertexpos,
                  Gaudi::SymMatrix3x3& vertexweight, Gaudi::SymMatrix3x3& vertexcov ) {
-- 
GitLab