From e820c3edb57e8753028d41063792db8c452a4a0b Mon Sep 17 00:00:00 2001
From: "se.gorbunov" <se.gorbunov@gsi.de>
Date: Thu, 31 Aug 2023 19:44:35 +0000
Subject: [PATCH] CA: de-SIMDIze and clean up the triplet constructor

---
 reco/L1/CMakeLists.txt                  |    2 +-
 reco/L1/L1Algo/L1Algo.cxx               |   31 -
 reco/L1/L1Algo/L1Algo.h                 |  114 +-
 reco/L1/L1Algo/L1BaseStationInfo.cxx    |    3 +-
 reco/L1/L1Algo/L1BaseStationInfo.h      |    1 +
 reco/L1/L1Algo/L1CaTrackFinderSlice.cxx | 1526 +----------------------
 reco/L1/L1Algo/L1Constants.h            |    2 -
 reco/L1/L1Algo/L1Field.cxx              |   15 +-
 reco/L1/L1Algo/L1Field.h                |   11 +
 reco/L1/L1Algo/L1Portion.h              |  166 ---
 reco/L1/L1Algo/L1Triplet.h              |    3 +-
 reco/L1/L1Algo/L1TripletConstructor.cxx |  690 ++++++++++
 reco/L1/L1Algo/L1TripletConstructor.h   |  116 ++
 13 files changed, 894 insertions(+), 1786 deletions(-)
 delete mode 100644 reco/L1/L1Algo/L1Portion.h
 create mode 100644 reco/L1/L1Algo/L1TripletConstructor.cxx
 create mode 100644 reco/L1/L1Algo/L1TripletConstructor.h

diff --git a/reco/L1/CMakeLists.txt b/reco/L1/CMakeLists.txt
index a43c86b078..b35b111739 100644
--- a/reco/L1/CMakeLists.txt
+++ b/reco/L1/CMakeLists.txt
@@ -34,6 +34,7 @@ set(SRCS
   L1Algo/utils/CaAlgoRandom.cxx
   L1Algo/L1Algo.cxx
   L1Algo/L1TrackPar.cxx
+  L1Algo/L1TripletConstructor.cxx
   L1Algo/L1CaTrackFinder.cxx
   L1Algo/L1CaTrackFinderSlice.cxx
   L1Algo/L1BranchExtender.cxx
@@ -205,7 +206,6 @@ install(FILES CbmL1Counters.h
   L1Algo/L1Branch.h
   L1Algo/L1HitPoint.h
   L1Algo/L1Hit.h
-  L1Algo/L1Portion.h
   L1Algo/L1Triplet.h
   L1Algo/L1Event.h
   L1Algo/L1EventMatch.h
diff --git a/reco/L1/L1Algo/L1Algo.cxx b/reco/L1/L1Algo/L1Algo.cxx
index fba7db9add..b0a6dcff61 100644
--- a/reco/L1/L1Algo/L1Algo.cxx
+++ b/reco/L1/L1Algo/L1Algo.cxx
@@ -34,31 +34,6 @@ void L1Algo::SetNThreads(unsigned int n)
 
     fTrackCandidates[i].SetName(std::stringstream() << "L1Algo::fTrackCandidates[" << i << "]");
 
-
-    fTripletPar[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-
-    fTripletHitsL[i].SetName(std::stringstream() << "L1Algo::fhitsl_3[" << i << "]");
-    fTripletHitsM[i].SetName(std::stringstream() << "L1Algo::fhitsm_3[" << i << "]");
-    fTripletHitsR[i].SetName(std::stringstream() << "L1Algo::fhitsr_3[" << i << "]");
-
-    fTripletHitsL[i].reserve(L1Constants::size::kMaxPortionTriplets);
-    fTripletHitsM[i].reserve(L1Constants::size::kMaxPortionTriplets);
-    fTripletHitsR[i].reserve(L1Constants::size::kMaxPortionTriplets);
-
-    fTripletHitR_Ufront[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_Uback[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_Z[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_Time[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_TimeErr[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_dUfront[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_dUback[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-
-    fTripletHitR_X[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_Y[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_dX[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_dXY[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-    fTripletHitR_dY[i].reserve(L1Constants::size::kMaxPortionTripletsP);
-
     for (unsigned int j = 0; j < L1Constants::size::kMaxNstations; j++) {
       fTriplets[j][i].SetName(std::stringstream() << "L1Algo::fTriplets[" << i << "][" << j << "]");
     }
@@ -128,12 +103,6 @@ void L1Algo::ResetSliceData()
   fSliceRecoHits.clear();
   fSliceRecoHits.reserve(2 * nHits);
 
-  for (int iS = 0; iS < L1Constants::size::kMaxNstations; iS++) {
-    int nHitsStation = fSliceHitIds[iS].size();
-    fSingletPortionSize[iS].clear();
-    fSingletPortionSize[iS].reserve(2 * nHitsStation);
-  }
-
   for (int iT = 0; iT < fNThreads; iT++) {
     fSliceRecoTracks_thread[iT].clear();
     fSliceRecoTracks_thread[iT].reserve(nHits / 10);
diff --git a/reco/L1/L1Algo/L1Algo.h b/reco/L1/L1Algo/L1Algo.h
index a44de8a34c..a641957c1f 100644
--- a/reco/L1/L1Algo/L1Algo.h
+++ b/reco/L1/L1Algo/L1Algo.h
@@ -37,7 +37,6 @@ class L1AlgoDraw;
 #include "L1HitPoint.h"
 #include "L1InputData.h"
 #include "L1Parameters.h"
-#include "L1Portion.h"
 #include "L1Station.h"
 #include "L1Track.h"
 #include "L1TrackPar.h"
@@ -88,8 +87,7 @@ public:
   // *************************
 
   friend class CbmL1;  /// TODO: Remove this dependency
-  friend class ParalleledDup;
-  friend class ParalleledTrip;
+  friend class L1TripletConstructor;
 #ifdef DRAW
   friend class L1AlgoDraw;
 #endif
@@ -261,82 +259,6 @@ public:
     flag = iStation * 4 + (flag % 4);
   }
 
-  /// Prepare the portion of left hits data
-  void findSingletsStep0(  // input
-    Tindex start_lh, Tindex n1_l, L1HitPoint* Hits_l,
-    // output
-    L1HitIndex_t* hitsl, fvec* x_l, fvec* y_l, fvec* z_l, fvec* t_l, fvec* dx_l, fvec* dxy_l, fvec* dy_l, fvec* dt2_l);
-
-  /// Get the field approximation. Add the target to parameters estimation. Propagate to middle station.
-  void findSingletsStep1(  // input
-    int istal, int istam, Tindex n1_V, fvec* x_l, fvec* y_l, fvec* zPos_l, fvec* t_l, fvec* dx_l, fvec* dxy_l,
-    fvec* dy_l, fvec* dt2_l,
-    // output
-    L1TrackPar* T_1, L1FieldRegion* fld_1);
-
-  /// Find the doublets. Reformat data in the portion of doublets.
-  void findDoubletsStep0(  // input
-    Tindex n1, const L1Station& stal, L1HitPoint* vHits_l, const L1Station& stam, L1HitPoint* vHits_m, L1TrackPar* T_1,
-    L1HitIndex_t* hitsl_1,
-    // output
-    Tindex& n2, L1Vector<L1HitIndex_t>& i1_2,
-#ifdef DOUB_PERFORMANCE
-    L1Vector<L1HitIndex_t>& hitsl_2,
-#endif  // DOUB_PERFORMANCE
-    L1Vector<L1HitIndex_t>& hitsm_2);
-
-  /// Add the middle hits to parameters estimation. Propagate to right station.
-  /// Find the triplets (right hit). Reformat data in the portion of triplets.
-  void findTripletsStep0(  // input
-    L1HitPoint* vHits_r, int istal, int istam, int istar, L1HitPoint* vHits_m, L1TrackPar* T_1, L1FieldRegion* fld_1,
-    L1HitIndex_t* hitsl_1,
-
-    Tindex n2, L1Vector<L1HitIndex_t>& hitsm_2, L1Vector<L1HitIndex_t>& i1_2,
-
-    // output
-    Tindex& n3, L1Vector<L1TrackPar>& T_3, L1Vector<L1HitIndex_t>& hitsl_3, L1Vector<L1HitIndex_t>& hitsm_3,
-    L1Vector<L1HitIndex_t>& hitsr_3,
-
-    L1Vector<fvec>& x_3, L1Vector<fvec>& y_3, L1Vector<fvec>& z_3, L1Vector<fvec>& t_3, L1Vector<fvec>& dx_3,
-    L1Vector<fvec>& dxy_3, L1Vector<fvec>& dy_3, L1Vector<fvec>& dt2_3);
-
-  /// Add the right hits to parameters estimation.
-  void findTripletsStep1(  // input
-    Tindex n3_V, const L1Station& star, L1Vector<fvec>& x_3, L1Vector<fvec>& y_3, L1Vector<fvec>& z_3,
-    L1Vector<fvec>& t_3, L1Vector<fvec>& dx_3, L1Vector<fvec>& dxy_3, L1Vector<fvec>& dy_3, L1Vector<fvec>& dt2_3,
-    // output
-    L1Vector<L1TrackPar>& T_3);
-
-  /// Refit Triplets.
-  void findTripletsStep2(  // input
-    Tindex n3, int istal, int istam, int istar, L1Vector<L1TrackPar>& T_3, L1Vector<L1HitIndex_t>& hitsl_3,
-    L1Vector<L1HitIndex_t>& hitsm_3, L1Vector<L1HitIndex_t>& hitsr_3, int nIterations = 0);
-
-  /// Select triplets. Save them into vTriplets.
-  void findTripletsStep3(  // input
-    Tindex n3, int istal, int istam, int istar, L1Vector<L1TrackPar>& T_3, L1Vector<L1HitIndex_t>& hitsl_3,
-    L1Vector<L1HitIndex_t>& hitsm_3, L1Vector<L1HitIndex_t>& hitsr_3);
-
-
-  /// Find neighbours of triplets. Calculate level of triplets.
-  void f5(  // input
-    // output
-    int* nlevel);
-
-
-  /// Find doublets on station
-  void CreatePortionOfDoublets(  // input
-    int istal, int istam, Tindex iSingletPortion, Tindex singletPortionSize,
-    // output
-    L1TrackPar* T_1, L1FieldRegion* fld_1, L1HitIndex_t* hitsl_1,
-    //
-    Tindex& n_2, L1Vector<L1HitIndex_t>& i1_2, L1Vector<L1HitIndex_t>& hitsm_2);
-
-  /// Find triplets on station
-  void CreatePortionOfTriplets(  // input
-    int istal, int istam, int istar, L1TrackPar* T_1, L1FieldRegion* fld_1, L1HitIndex_t* hitsl_1, Tindex& n_2,
-    L1Vector<L1HitIndex_t>& i1_2, L1Vector<L1HitIndex_t>& hitsm_2);
-
 #ifdef DRAW
   L1AlgoDraw* draw {nullptr};
   void DrawRecoTracksTime(const L1Vector<CbmL1Track>& tracks);
@@ -432,10 +354,6 @@ public:
   /// The candidates may share any amount of hits.
   L1Vector<L1Branch> fTrackCandidates[L1Constants::size::kMaxNthreads] {"L1Algo::fTrackCandidates"};
 
-  L1Vector<Tindex> fSingletPortionSize[L1Constants::size::kMaxNstations] {
-    "L1Algo::fSingletPortionSize"};  ///< Number of doublets in a portion
-
-
   ///< indices of the sub-slice hits
   L1Vector<L1HitIndex_t> fSliceHitIds[L1Constants::size::kMaxNstations] {"L1Algo::fSliceHitIds"};
 
@@ -476,36 +394,6 @@ public:
   L1Vector<int> fHitNtriplets {"L1Algo::fHitNtriplets"};        /// link hit ->n triplets { hit, *, *}
 
 
-  //  fvec u_front[Portion/fvecLen], u_back[Portion/fvecLen];
-  //  fvec zPos[Portion/fvecLen];
-  //  fvec fHitTime[Portion/fvecLen];
-
-  L1Vector<L1TrackPar> fTripletPar[L1Constants::size::kMaxNthreads] {"fTripletPar"};
-
-  L1Vector<L1HitIndex_t> fTripletHitsL[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitsL"};
-  L1Vector<L1HitIndex_t> fTripletHitsM[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitsM"};
-  L1Vector<L1HitIndex_t> fTripletHitsR[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitsR"};
-
-  L1Vector<fvec> fTripletHitR_Ufront[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_Ufront"};
-  L1Vector<fvec> fTripletHitR_Uback[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_Uback"};
-  L1Vector<fvec> fTripletHitR_Z[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_Z"};
-  L1Vector<fvec> fTripletHitR_Time[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_Time"};
-  L1Vector<fvec> fTripletHitR_TimeErr[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_TimeErr"};
-  L1Vector<fvec> fTripletHitR_dUfront[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_dUfront"};
-  L1Vector<fvec> fTripletHitR_dUback[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_dUback"};
-
-  L1Vector<fvec> fTripletHitR_X[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_X"};
-  L1Vector<fvec> fTripletHitR_Y[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_Y"};
-  L1Vector<fvec> fTripletHitR_dX[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_dX"};
-  L1Vector<fvec> fTripletHitR_dXY[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_dXY"};
-  L1Vector<fvec> fTripletHitR_dY[L1Constants::size::kMaxNthreads] {"L1Algo::fTripletHitR_dY"};
-
-
-  //   Tindex NHits_l[L1Constants::size::kMaxNstations];
-  //   Tindex NHits_l_P[L1Constants::size::kMaxNstations];
-  /// ----- Output data -----
-
-
 private:
   L1CloneMerger fCloneMerger {*this};  ///< Object of L1Algo clones merger algorithm
 
diff --git a/reco/L1/L1Algo/L1BaseStationInfo.cxx b/reco/L1/L1Algo/L1BaseStationInfo.cxx
index 044ea49e2c..c5b214ecb8 100644
--- a/reco/L1/L1Algo/L1BaseStationInfo.cxx
+++ b/reco/L1/L1Algo/L1BaseStationInfo.cxx
@@ -205,6 +205,7 @@ void L1BaseStationInfo::SetFieldFunction(
     fL1Station.fieldSlice.cy[j] = A[j][N + 1] / A[j][j];
     fL1Station.fieldSlice.cz[j] = A[j][N + 2] / A[j][j];
   }
+  fL1Station.fieldSlice.z = fZref;
 
   fInitController.SetFlag(EInitKey::kFieldSlice);
 }
@@ -285,7 +286,7 @@ void L1BaseStationInfo::SetXmax(double aSize)
 //
 void L1BaseStationInfo::SetYmax(double aSize)
 {
-  fYmax = aSize;
+  fYmax           = aSize;
   fL1Station.Ymax = aSize;
   fInitController.SetFlag(EInitKey::kYmax);
 }
diff --git a/reco/L1/L1Algo/L1BaseStationInfo.h b/reco/L1/L1Algo/L1BaseStationInfo.h
index 60df5ababc..a23cbb5e7a 100644
--- a/reco/L1/L1Algo/L1BaseStationInfo.h
+++ b/reco/L1/L1Algo/L1BaseStationInfo.h
@@ -99,6 +99,7 @@ public:
   /// Gets array of the coefficients for the field z-component approximation
   const fvec* GetFieldSliceCz() const { return fL1Station.fieldSlice.cz; }
 
+
   /// Gets field status: 0 - station is outside the field, 1 - station is inside the field
   int GetFieldStatus() const { return fL1Station.fieldStatus; }
 
diff --git a/reco/L1/L1Algo/L1CaTrackFinderSlice.cxx b/reco/L1/L1Algo/L1CaTrackFinderSlice.cxx
index e521b76627..d2e9a0d04f 100644
--- a/reco/L1/L1Algo/L1CaTrackFinderSlice.cxx
+++ b/reco/L1/L1Algo/L1CaTrackFinderSlice.cxx
@@ -29,9 +29,9 @@
 #include "L1Grid.h"
 #include "L1HitArea.h"
 #include "L1HitPoint.h"
-#include "L1Portion.h"
 #include "L1Track.h"
 #include "L1TrackPar.h"
+#include "L1TripletConstructor.h"
 
 #ifdef _OPENMP
 #include "omp.h"
@@ -122,1355 +122,6 @@ bool L1Algo::checkTripletMatch(const L1Triplet& l, const L1Triplet& r, fscal& dc
   return true;
 }
 
-inline void L1Algo::findSingletsStep0(  // input
-  Tindex start_lh, Tindex n1_l, L1HitPoint* Hits_l,
-  // output
-  L1HitIndex_t* hitsl, fvec* x_l, fvec* y_l, fvec* z_l, fvec* t_l, fvec* dx_l, fvec* dxy_l, fvec* dy_l, fvec* dt2_l)
-{
-
-  /// Prepare the portion of data of left hits of a triplet:
-  /// all hits except the last and the second last station will be procesesed in the algorithm,
-  /// the data is orginesed in order to be used by SIMD
-
-  const Tindex end_lh = start_lh + n1_l;
-  const int lastV     = (n1_l - 1) / fvec::size();
-  if (lastV >= 0) {
-    // set some positive errors to unfilled part of vectors in order to avoid nans
-    L1HitPoint& hitl = Hits_l[0];
-    x_l[lastV]       = hitl.X();
-    y_l[lastV]       = hitl.Y();
-    z_l[lastV]       = hitl.Z();
-    t_l[lastV]       = hitl.T();
-    dx_l[lastV]      = hitl.dX();
-    dxy_l[lastV]     = hitl.dXY();
-    dy_l[lastV]      = hitl.dY();
-    dt2_l[lastV]     = hitl.dT2();
-  }
-
-  for (Tindex ilh = start_lh, i1 = 0; ilh < end_lh; ++ilh, ++i1) {
-    Tindex i1_V       = i1 / fvec::size();
-    Tindex i1_4       = i1 % fvec::size();
-    L1HitPoint& hitl  = Hits_l[ilh];
-    hitsl[i1]         = ilh;
-    x_l[i1_V][i1_4]   = hitl.X();
-    y_l[i1_V][i1_4]   = hitl.Y();
-    z_l[i1_V][i1_4]   = hitl.Z();
-    t_l[i1_V][i1_4]   = hitl.T();
-    dx_l[i1_V][i1_4]  = hitl.dX();
-    dxy_l[i1_V][i1_4] = hitl.dXY();
-    dy_l[i1_V][i1_4]  = hitl.dY();
-    dt2_l[i1_V][i1_4] = hitl.dT2();
-  }
-}
-
-
-inline void L1Algo::findSingletsStep1(  /// input 1st stage of singlet search
-  int istal,
-  int istam,    /// indexes of left and middle stations of a triplet
-  Tindex n1_V,  ///
-  fvec* x_l, fvec* y_l, fvec* z_l, fvec* t_l, fvec* dx_l, fvec* dxy_l, fvec* dy_l, fvec* dt2_l,
-  // output
-  //                 L1TrackPar *T_1,
-  L1TrackPar* T_1, L1FieldRegion* fld_1)
-{
-
-  /// Get the field approximation. Add the target to parameters estimation.
-  /// Propagaete to the middle station of a triplet.
-
-  const L1Station& stal = fParameters.GetStation(istal);
-  const L1Station& stam = fParameters.GetStation(istam);
-
-  // stations for approximating the field between the target and the left hit
-  int fld0Ista1 = istal;
-  if (fld0Ista1 >= fNfieldStations) { fld0Ista1 = fNfieldStations - 1; }
-  if (fld0Ista1 < 1) { fld0Ista1 = 1; }
-  int fld0Ista0 = fld0Ista1 / 2;  // station between istal and the target
-
-  assert(0 <= fld0Ista0 && fld0Ista0 < fld0Ista1 && fld0Ista1 < fParameters.GetNstationsActive());
-
-  // stations for approximating the field between the left and the right hit
-  int fld1Ista0 = istal;
-  int fld1Ista1 = istam;
-  int fld1Ista2 = istam + 1;
-  if (fld1Ista2 >= fParameters.GetNstationsActive()) {
-    fld1Ista2 = istam;
-    fld1Ista1 = istam - 1;
-    if (fld1Ista0 >= fld1Ista1) { fld1Ista0 = fld1Ista1 - 1; }
-  }
-
-  assert(0 <= fld1Ista0 && fld1Ista0 < fld1Ista1 && fld1Ista1 < fld1Ista2
-         && fld1Ista2 < fParameters.GetNstationsActive());
-
-  const L1Station& fld0Sta0 = fParameters.GetStation(fld0Ista0);
-  const L1Station& fld0Sta1 = fParameters.GetStation(fld0Ista1);
-  const L1Station& fld1Sta0 = fParameters.GetStation(fld1Ista0);
-  const L1Station& fld1Sta1 = fParameters.GetStation(fld1Ista1);
-  const L1Station& fld1Sta2 = fParameters.GetStation(fld1Ista2);
-
-  L1Fit fit;
-  fit.SetParticleMass(GetDefaultParticleMass());
-  fit.SetMask(fmask::One());
-  fit.SetQp0(fMaxInvMom);
-
-  if (fpCurrentIteration->GetElectronFlag()) { fit.SetParticleMass(L1Constants::phys::kElectronMass); }
-  else {
-    fit.SetParticleMass(fDefaultMass);
-  }
-
-  for (int i1_V = 0; i1_V < n1_V; i1_V++) {
-
-    // field made by  the left hit, the target and the station istac in-between.
-    // is used for extrapolation to the target and to the middle hit
-    L1FieldRegion fld0;
-
-    // field, made by the left hit, the middle station and the right station
-    // Will be used for extrapolation to the right hit
-    L1FieldRegion& fld1 = fld_1[i1_V];
-
-    L1FieldValue lB, mB, cB, rB _fvecalignment;
-    L1FieldValue l_B_global, centB_global _fvecalignment;
-
-    // get the magnetic field along the track.
-    // (suppose track is straight line with origin in the target)
-
-    fvec& xl        = x_l[i1_V];
-    fvec& yl        = y_l[i1_V];
-    fvec& zl        = z_l[i1_V];
-    fvec& time      = t_l[i1_V];
-    fvec& timeEr2   = dt2_l[i1_V];
-    const fvec dzli = 1.f / (zl - fTargZ);
-
-    const fvec tx = (xl - fTargX) * dzli;
-    const fvec ty = (yl - fTargY) * dzli;
-
-    L1FieldValue b00, b01, b10, b11, b12;
-    fld0Sta0.fieldSlice.GetFieldValue(fTargX + tx * (fld0Sta0.fZ - fTargZ), fTargY + ty * (fld0Sta0.fZ - fTargZ), b00);
-    fld0Sta1.fieldSlice.GetFieldValue(fTargX + tx * (fld0Sta1.fZ - fTargZ), fTargY + ty * (fld0Sta1.fZ - fTargZ), b01);
-    fld1Sta0.fieldSlice.GetFieldValue(fTargX + tx * (fld1Sta0.fZ - fTargZ), fTargY + ty * (fld1Sta0.fZ - fTargZ), b10);
-    fld1Sta1.fieldSlice.GetFieldValue(fTargX + tx * (fld1Sta1.fZ - fTargZ), fTargY + ty * (fld1Sta1.fZ - fTargZ), b11);
-    fld1Sta2.fieldSlice.GetFieldValue(fTargX + tx * (fld1Sta2.fZ - fTargZ), fTargY + ty * (fld1Sta2.fZ - fTargZ), b12);
-
-    fld0.Set(fTargB, fTargZ, b00, fld0Sta0.fZ, b01, fld0Sta1.fZ);
-    fld1.Set(b10, fld1Sta0.fZ, b11, fld1Sta1.fZ, b12, fld1Sta2.fZ);
-
-    L1TrackPar& T = fit.Tr();
-
-    T.x  = xl;
-    T.y  = yl;
-    T.z  = zl;
-    T.tx = tx;
-    T.ty = ty;
-    T.qp = fvec(0.);
-    T.t  = time;
-    T.vi = fvec(0.);
-
-    fvec txErr2 = fMaxSlopePV * fMaxSlopePV / fvec(9.);
-    fvec qpErr2 = fMaxInvMom * fMaxInvMom / fvec(9.);
-
-    T.ResetErrors(1., 1., txErr2, txErr2, qpErr2, (stal.timeInfo ? timeEr2 : 1.e6), 1.e2);
-    T.C00 = dx_l[i1_V] * dx_l[i1_V];
-    T.C10 = dxy_l[i1_V];
-    T.C11 = dy_l[i1_V] * dy_l[i1_V];
-
-    T.chi2 = 0.;
-    T.NDF  = (fpCurrentIteration->GetPrimaryFlag()) ? fvec(2.) : fvec(0.);
-
-    // TODO: iteration parameter: "Starting NDF of track parameters"
-    // NDF = number of track parameters (6: x, y, tx, ty, qp, time) - number of measured parameters (3: x, y, time) on station or (2: x, y) on target
-
-    // add the target constraint
-
-    fit.AddTargetToLine(fTargX, fTargY, fTargZ, TargetXYInfo, fld0);
-
-    fit.AddMsInMaterial(fParameters.GetMaterialThickness(istal, T.x, T.y));
-
-    // extrapolate to the middle hit
-
-    fit.ExtrapolateLine(stam.fZ, fld1);
-
-    T_1[i1_V] = T;
-  }  // i1_V
-}
-
-
-inline void L1Algo::findDoubletsStep0(
-  /// input
-  Tindex n1, const L1Station& stal, L1HitPoint* vHits_l, const L1Station& stam, L1HitPoint* vHits_m, L1TrackPar* T_1,
-  L1HitIndex_t* hitsl_1,
-  /// output
-  Tindex& n2, L1Vector<L1HitIndex_t>& i1_2,
-#ifdef DOUB_PERFORMANCE
-  L1Vector<L1HitIndex_t>& hitsl_2,
-#endif  // DOUB_PERFORMANCE
-  L1Vector<L1HitIndex_t>& hitsm_2)
-{
-  /// Find the doublets. Reformat data into portions of doublets.
-
-  assert(i1_2.size() == 0);
-
-  int iStaL = &stal - fParameters.GetStations().begin();
-  int iStaM = &stam - fParameters.GetStations().begin();
-
-  n2 = 0;  // number of doublets
-
-  L1Fit fit;
-  fit.SetParticleMass(GetDefaultParticleMass());
-
-  for (Tindex i1 = 0; i1 < n1; ++i1)  // for each singlet
-  {
-    unsigned int Ndoublets = 0;
-    const Tindex i1_V      = i1 / fvec::size();
-    const Tindex i1_4      = i1 % fvec::size();
-
-    fit.SetTrack(T_1[i1_V]);
-    L1TrackPar& T1 = fit.Tr();
-
-    // assert(T1.IsEntryConsistent(true, i1_4));
-    // if (!T1.IsEntryConsistent(false, i1_4)) continue;
-
-    const fvec Pick_m22 = (fDoubletChi2Cut - T1.chi2);
-    // if make it bigger the found hits will be rejected later because of the chi2 cut.
-    // Pick_m22 is not used, search for mean squared, 2nd version
-
-    unsigned int indFirstDoublet = hitsm_2.size();
-
-    // -- collect possible doublets --
-    const fscal iz         = 1.f / (T1.z[i1_4] - fParameters.GetTargetPositionZ()[0]);
-    const fscal timeError2 = T1.C55[i1_4];
-    const fscal time       = T1.t[i1_4];
-
-    L1HitAreaTime areaTime(vGridTime[iStaM], T1.x[i1_4] * iz, T1.y[i1_4] * iz,
-                           (sqrt(Pick_m22 * T1.C00) + fMaxDx[iStaM] + fMaxDZ * abs(T1.tx))[i1_4] * iz,
-                           (sqrt(Pick_m22 * T1.C11) + fMaxDy[iStaM] + fMaxDZ * abs(T1.ty))[i1_4] * iz, time,
-                           sqrt(timeError2) * 5);
-
-    for (L1HitIndex_t imh = -1; true;) {  // loop over the hits in the area
-      if (fParameters.DevIsIgnoreHitSearchAreas()) {
-        imh++;
-        if ((L1HitIndex_t) imh >= (fGridHitStopIndex[iStaM] - fGridHitStartIndex[iStaM])) { break; }
-      }
-      else {
-        if (!areaTime.GetNext(imh)) { break; }
-      }
-
-      L1HitPoint& hitm = vHits_m[imh];
-      if (hitm.IsSuppressed()) continue;
-
-      const L1HitPoint& hitl = vHits_l[hitsl_1[i1]];
-
-      if (fParameters.DevIsMatchDoubletsViaMc()) {  // trd2d
-        int indL = fGridHitStartIndex[iStaL] + hitsl_1[i1];
-        int indM = fGridHitStartIndex[iStaM] + imh;
-        int iMC  = GetMcTrackIdForGridHit(indL);
-        if (iMC < 0 || iMC != GetMcTrackIdForGridHit(indM)) { continue; }
-      }
-
-      // check y-boundaries
-      //TODO: move hardcoded cuts to parameters
-      if ((stam.timeInfo) && (stal.timeInfo)) {
-        if (fabs(time - hitm.T()) > sqrt(timeError2 + hitm.dT2()) * 5) continue;
-        if (fabs(time - hitm.T()) > 30) continue;
-      }
-
-      // - check whether hit belong to the window ( track position +\- errors ) -
-      const fscal zm = hitm.Z();
-      //       L1TrackPar T1_new = T1;
-      //       fvec dz           = fvec(zm) - T1.z;
-      // L1ExtrapolateTime(T1, dz);
-
-
-      //       if (fabs(T1_new.t[i1_4]-hitm.time)>sqrt(T1_new.C55[i1_4]+hitm.timeEr*hitm.timeEr)*4) continue;
-      //       if (fabs(T1_new.t[i1_4]-hitm.time)>sqrt(2.9*2.9)*5) continue;
-      //       const fscal dt_est2 = Pick_m22[i1_4] * fabs(T1_new.C55[i1_4] + hitm.timeEr*hitm.timeEr);
-      //       const fscal dt = hitm.time - T1_new.t[i1_4];
-      //       if ( dt*dt > dt_est2 && dt < 0  ) continue;
-
-      fvec y, C11;
-      fit.ExtrapolateYC11Line(zm, y, C11);
-
-      fscal dy_est2 = Pick_m22[i1_4] * fabs(C11[i1_4] + hitm.dY2());
-
-      fscal xm = hitm.X();
-      fscal ym = hitm.Y();
-
-      const fscal dY = ym - y[i1_4];
-
-      if (dY * dY > dy_est2) continue;
-
-      // check x-boundaries
-      fvec x, C00;
-      fit.ExtrapolateXC00Line(zm, x, C00);
-
-      fscal dx_est2 = Pick_m22[i1_4] * fabs(C00[i1_4] + hitm.dX2());
-
-      const fscal dX = xm - x[i1_4];
-
-      if (dX * dX > dx_est2) continue;
-
-      // check chi2
-
-      fvec C10;
-      fit.ExtrapolateC10Line(zm, C10);
-
-      auto [chi2x, chi2u] =
-        L1Fit::GetChi2XChi2U(hitm.X(), hitm.Y(), hitm.dX2(), hitm.dXY(), hitm.dY2(), x, y, C00, C10, C11);
-
-      // TODO: adjust the cut, cut on chi2x & chi2u separately
-      if (!fpCurrentIteration->GetTrackFromTripletsFlag()) {
-        if (chi2x[i1_4] > fDoubletChi2Cut) continue;
-        if (chi2x[i1_4] + chi2u[i1_4] > fDoubletChi2Cut) continue;
-      }
-
-      // FilterTime(T1, hitm.T(), hitm.dT2());
-
-      // check if there is a second hit on the same station
-      {
-        bool isOtherHit = 0;
-        fscal dz        = hitm.Z() - hitl.Z();
-        fscal tx        = (hitm.X() - hitl.X()) / dz;
-        fscal ty        = (hitm.Y() - hitl.Y()) / dz;
-        fscal tt        = (hitm.T() - hitl.T()) / dz;
-
-        for (unsigned int imh1 = indFirstDoublet; imh1 < hitsm_2.size(); imh1++) {
-          const L1HitPoint& hitm1 = vHits_m[hitsm_2[imh1]];
-
-          if ((stam.timeInfo) && (stal.timeInfo)) {
-            fscal dt = hitm.T() + tt * (hitm1.Z() - hitm.Z()) - hitm1.T();
-            if (dt * dt > 30. * (hitm.dT2() + hitm1.dT2())) { continue; }
-          }
-
-          fscal dx = hitm.X() + tx * (hitm1.Z() - hitm.Z()) - hitm1.X();
-          if (dx * dx > 20. * (hitm.dX2() + hitm1.dX2())) { continue; }
-
-          fscal dy = hitm.Y() + ty * (hitm1.Z() - hitm.Z()) - hitm1.Y();
-          if (dy * dy > 30. * (hitm.dY2() + hitm1.dY2())) { continue; }
-
-          if (fParameters.DevIsSuppressOverlapHitsViaMc()) {
-            int indL  = fGridHitStartIndex[iStaL] + hitsl_1[i1];
-            int indM  = fGridHitStartIndex[iStaM] + imh;
-            int indM1 = fGridHitStartIndex[iStaM] + hitsm_2[imh1];
-            int iMC   = GetMcTrackIdForGridHit(indL);
-            if ((iMC != GetMcTrackIdForGridHit(indM)) || (iMC != GetMcTrackIdForGridHit(indM1))) { continue; }
-          }
-
-          isOtherHit = true;
-          break;
-        }
-
-        if (isOtherHit) {
-          hitm.SetIsSuppresed(1);
-          continue;
-        }
-      }
-
-      i1_2.push_back(i1);
-#ifdef DOUB_PERFORMANCE
-      hitsl_2.push_back(hitsl_1[i1]);
-#endif  // DOUB_PERFORMANCE
-      hitsm_2.push_back(imh);
-
-      Ndoublets++;
-      n2++;
-
-#ifndef FAST_CODE
-//TODO: optimise triplet portion limit and put a warning
-// assert(Ndoublets < fParameters.GetMaxDoubletsPerSinglet());
-#endif  // FAST_CODE
-
-      if (Ndoublets >= fParameters.GetMaxDoubletsPerSinglet()) {
-        n2 = n2 - Ndoublets;
-        i1_2.reduce(i1_2.size() - Ndoublets);
-        hitsm_2.reduce(hitsm_2.size() - Ndoublets);
-        break;
-      }
-    }  // loop over the hits in the area
-
-    T_1[i1_V] = fit.Tr();
-
-  }  // for i1
-}
-
-
-/// Add the middle hits to parameters estimation. Propagate to right station.
-/// Find the triplets(right hit). Reformat data in the portion of triplets.
-inline void L1Algo::findTripletsStep0(  // input
-  L1HitPoint* vHits_r, int iStaL, int iStaM, int iStaR, L1HitPoint* vHits_m, L1TrackPar* T_1, L1FieldRegion* fld_1,
-  L1HitIndex_t* hitsl_1, Tindex n2, L1Vector<L1HitIndex_t>& hitsm_2, L1Vector<L1HitIndex_t>& i1_2,
-  // output
-  Tindex& n3, L1Vector<L1TrackPar>& T_3, L1Vector<L1HitIndex_t>& hitsl_3, L1Vector<L1HitIndex_t>& hitsm_3,
-  L1Vector<L1HitIndex_t>& hitsr_3, L1Vector<fvec>& x_3, L1Vector<fvec>& y_3, L1Vector<fvec>& z_3, L1Vector<fvec>& t_3,
-  L1Vector<fvec>& dx_3, L1Vector<fvec>& dxy_3, L1Vector<fvec>& dy_3, L1Vector<fvec>& dt2_3)
-{
-  const L1Station& stal = fParameters.GetStation(iStaL);
-  const L1Station& stam = fParameters.GetStation(iStaM);
-  const L1Station& star = fParameters.GetStation(iStaR);
-
-  std::array<L1HitIndex_t, fvec::size()> hitsl_2     = {};
-  std::array<L1HitIndex_t, fvec::size()> hitsm_2_tmp = {};
-
-  std::fill(hitsl_2.begin(), hitsl_2.end(), undef::kU32);
-  std::fill(hitsm_2_tmp.begin(), hitsm_2_tmp.end(), undef::kU32);
-
-  L1TrackPar L1TrackPar_0;
-  // SG!! to avoid nans in unfilled part
-  //TODO: SG: investigate, it changes the results !!
-  /*
-  L1TrackPar_0.C00 = 1.f;
-  L1TrackPar_0.C11 = 1.f;
-  L1TrackPar_0.C22 = 1.f;
-  L1TrackPar_0.C33 = 1.f;
-  L1TrackPar_0.C44 = 1.f;
-  L1TrackPar_0.C55 = 1.f;
-  */
-
-  bool isMomentumFitted = (fIsTargetField || (stal.fieldStatus != 0) || (stam.fieldStatus != 0));
-  //bool isTimeFitted     = ((stal.timeInfo != 0) || (stam.timeInfo != 0));
-
-  L1Fit fit;
-  fit.SetParticleMass(fDefaultMass);
-  fit.SetMask(fmask::One());
-  fit.SetQp0(fvec(0.));
-
-  n3          = 0;
-  Tindex n3_V = 0, n3_4 = 0;
-
-  T_3.reset(1, L1TrackPar_0);
-
-  x_3.reset(1, fvec::Zero());
-  y_3.reset(1, fvec::Zero());
-  z_3.reset(1, fvec::Zero());
-  t_3.reset(1, fvec::Zero());
-
-  dx_3.reset(1, fvec::One());
-  dxy_3.reset(1, fvec::Zero());
-  dy_3.reset(1, fvec::One());
-  dt2_3.reset(1, fvec::One());
-
-  assert(iStaR < fParameters.GetNstationsActive());  //TODO SG!!! check if it is needed
-
-  if (iStaR >= fParameters.GetNstationsActive()) return;
-
-  // ---- Add the middle hits to parameters estimation. Propagate to right station. ----
-
-  for (Tindex i2 = 0; i2 < n2;) {
-    fit.SetTrack(L1TrackPar_0);
-    fit.SetQp0(fvec(0.));
-    L1TrackPar& T2 = fit.Tr();
-
-    L1FieldRegion f2;
-    // pack the data
-    fvec x_2 = 0.f;
-    fvec y_2 = 0.f;
-    fvec z_2 = 0.f;
-    fvec t_2 = 0.f;
-    L1XYMeasurementInfo cov_2;
-    cov_2.C00  = 1.f;
-    cov_2.C10  = 0.f;
-    cov_2.C11  = 1.f;
-    fvec dt2_2 = 1.f;
-
-    size_t n2_4 = 0;
-    for (; n2_4 < fvec::size() && i2 < n2; i2++, n2_4++) {
-
-      const Tindex& i1  = i1_2[i2];
-      const Tindex i1_V = i1 / fvec::size();
-      const Tindex i1_4 = i1 % fvec::size();
-
-      const L1TrackPar& T1 = T_1[i1_V];
-
-      //assert(T1.IsEntryConsistent(true, i1_4));
-      //if (!T1.IsEntryConsistent(false, i1_4)) continue;
-
-      const L1FieldRegion& f1 = fld_1[i1_V];
-      T2.SetOneEntry(n2_4, T1, i1_4);
-      f2.SetOneEntry(n2_4, f1, i1_4);
-
-      const Tindex& imh      = hitsm_2[i2];
-      const L1HitPoint& hitm = vHits_m[imh];
-
-      x_2[n2_4]       = hitm.X();
-      y_2[n2_4]       = hitm.Y();
-      z_2[n2_4]       = hitm.Z();
-      t_2[n2_4]       = hitm.T();
-      cov_2.C00[n2_4] = hitm.dX2();
-      cov_2.C10[n2_4] = hitm.dXY();
-      cov_2.C11[n2_4] = hitm.dY2();
-      dt2_2[n2_4]     = hitm.dT2();
-
-      hitsl_2[n2_4]     = hitsl_1[i1];
-      hitsm_2_tmp[n2_4] = hitsm_2[i2];
-    }  // n2_4
-
-    // add the middle hit
-
-    fit.ExtrapolateLineNoField(z_2);
-
-    fit.FilterXY(cov_2, x_2, y_2);
-
-    fit.FilterTime(t_2, dt2_2, stam.timeInfo);
-
-    fit.SetQp0(isMomentumFitted ? fit.Tr().qp : fMaxInvMom);
-
-    fit.AddMsInMaterial(fParameters.GetMaterialThickness(iStaM, T2.x, T2.y));
-
-    fit.SetQp0(fit.Tr().qp);
-
-    // extrapolate to the right hit station
-
-    fit.Extrapolate(star.fZ, f2);
-
-    // assert(T2.IsConsistent(true, n2_4));
-
-    // ---- Find the triplets(right hit). Reformat data in the portion of triplets. ----
-    for (size_t i2_4 = 0; i2_4 < n2_4; ++i2_4) {
-
-      //if (!T2.IsEntryConsistent(false, i2_4)) { continue; }
-      // FIXME: SZh 22.08.2023: Remove the hack!! (Why is it used only for kSts?)
-      if (kSts == fTrackingMode && (T2.C44[i2_4] < 0)) { continue; }
-      if (T2.C00[i2_4] < 0 || T2.C11[i2_4] < 0 || T2.C22[i2_4] < 0 || T2.C33[i2_4] < 0 || T2.C55[i2_4] < 0) continue;
-
-      if (fabs(T2.tx[i2_4]) > fMaxSlope) continue;
-      if (fabs(T2.ty[i2_4]) > fMaxSlope) continue;
-
-      const fvec Pick_r22    = fTripletChi2Cut - T2.chi2 + (!fpCurrentIteration->GetTrackFromTripletsFlag() ? 0 : 1);
-      const fscal timeError2 = T2.C55[i2_4];
-      const fscal time       = T2.t[i2_4];
-      // find first possible hit
-
-
-      const fscal iz = 1.f / (T2.z[i2_4] - fParameters.GetTargetPositionZ()[0]);
-      L1HitAreaTime area(vGridTime[&star - fParameters.GetStations().begin()], T2.x[i2_4] * iz, T2.y[i2_4] * iz,
-                         (sqrt(Pick_r22 * T2.C00) + fMaxDx[iStaM] + fMaxDZ * abs(T2.tx))[i2_4] * iz,
-                         (sqrt(Pick_r22 * T2.C11) + fMaxDy[iStaM] + fMaxDZ * abs(T2.ty))[i2_4] * iz, time,
-                         sqrt(timeError2) * 5);
-
-      L1HitIndex_t irh              = 0;
-      L1HitIndex_t doubletNtriplets = 0;
-      int irh1                      = -1;
-      while (true) {
-        if (fParameters.DevIsIgnoreHitSearchAreas()) {
-          irh1++;
-          if ((L1HitIndex_t) irh1 >= (fGridHitStopIndex[iStaR] - fGridHitStartIndex[iStaR])) break;
-          irh = irh1;
-        }
-        else {
-          if (!area.GetNext(irh)) break;
-        }
-
-        // while (area.GetNext(irh)) {
-        //for (int irh = 0; irh < ( fGridHitStopIndex[iStaR] - fGridHitStartIndex[iStaR] ); irh++){
-        const L1HitPoint& hitr = vHits_r[irh];
-        if (hitr.IsSuppressed()) continue;
-
-        if (fParameters.DevIsMatchTripletsViaMc()) {
-          int indL = fGridHitStartIndex[iStaL] + hitsl_2[i2_4];
-          int indM = fGridHitStartIndex[iStaM] + hitsm_2_tmp[i2_4];
-          int indR = fGridHitStartIndex[iStaR] + irh;
-          int mcL  = GetMcTrackIdForGridHit(indL);
-          if (mcL < 0 || mcL != GetMcTrackIdForGridHit(indM) || mcL != GetMcTrackIdForGridHit(indR)) { continue; }
-        }
-
-        const fscal zr = hitr.Z();
-
-        fscal xr = hitr.X();
-        fscal yr = hitr.Y();
-
-        //auto [xr, yr] = star.ConvUVtoXY<fscal>(hitr.U(), hitr.V());
-
-        L1Fit fit3;
-        fit3.SetParticleMass(fDefaultMass);
-        fit3.SetMask(fmask::One());
-        fit3.SetTrack(T2);
-        L1TrackPar& T_cur = fit3.Tr();
-
-        fit3.ExtrapolateLineNoField(zr);
-
-        if ((star.timeInfo) && (stam.timeInfo)) {
-          auto dt = T_cur.t[i2_4] - hitr.T();
-          if (dt > (T_cur.C55[i2_4] + hitr.dT2()) * 5) continue;
-        }
-
-        // TODO: SG: hardcoded cut of 30 ns
-        if ((star.timeInfo) && (stam.timeInfo)) {
-          if (fabs(T_cur.t[i2_4] - hitr.T()) > 30) continue;
-        }
-
-        // - check whether hit belong to the window ( track position +\- errors ) -
-        // check lower boundary
-        fvec y, C11;
-        fit3.ExtrapolateYC11Line(zr, y, C11);
-
-        fscal dy_est2 = (Pick_r22[i2_4] * (fabs(C11[i2_4] + hitr.dY2())));
-
-        const fscal dY  = yr - y[i2_4];
-        const fscal dY2 = dY * dY;
-
-        if (dY2 > dy_est2) continue;  // if (yr > y_plus_new [i2_4] ) continue;
-
-        // check x-boundaries
-        fvec x, C00;
-        fit3.ExtrapolateXC00Line(zr, x, C00);
-
-        fscal dx_est2 = (Pick_r22[i2_4] * (fabs(C00[i2_4] + hitr.dX2())));
-
-        const fscal dX = xr - x[i2_4];
-        if (dX * dX > dx_est2) continue;
-        // check chi2  // not effective
-        fvec C10;
-        fit3.ExtrapolateC10Line(zr, C10);
-
-        auto [chi2x, chi2u] =
-          L1Fit::GetChi2XChi2U(hitr.X(), hitr.Y(), hitr.dX2(), hitr.dXY(), hitr.dY2(), x, y, C00, C10, C11);
-
-        //fit3.FilterTime(hitr.T(), hitr.dT2(), star.timeInfo);
-
-        if (!fpCurrentIteration->GetTrackFromTripletsFlag()) {
-          if (chi2x[i2_4] + chi2u[i2_4] > fTripletChi2Cut || T_cur.C55[i2_4] < 0) {
-            continue;  // chi2_triplet < CHI2_CUT
-          }
-        }
-
-#ifndef FAST_CODE
-//TODO: optimise triplet portion limit and put a warning
-// assert(doubletNtriplets < fParameters.GetMaxTripletPerDoublets());
-#endif  // FAST_CODE
-
-        if (doubletNtriplets >= fParameters.GetMaxTripletPerDoublets()) {
-          LOG(debug) << "L1: GetMaxTripletPerDoublets==" << fParameters.GetMaxTripletPerDoublets()
-                     << " reached in findTripletsStep0()";
-          // reject already created triplets for this doublet
-          n3   = n3 - doubletNtriplets;
-          n3_V = n3 / fvec::size();
-          n3_4 = n3 % fvec::size();
-          hitsl_3.reduce(n3);
-          hitsm_3.reduce(n3);
-          hitsr_3.reduce(n3);
-          break;
-        }
-
-        // pack triplet
-
-        doubletNtriplets++;
-
-        L1TrackPar& T3 = T_3[n3_V];
-
-        hitsl_3.push_back(hitsl_2[i2_4]);
-        hitsm_3.push_back(hitsm_2_tmp[i2_4]);
-        hitsr_3.push_back(irh);
-
-
-        T3.SetOneEntry(n3_4, T2, i2_4);
-
-        x_3[n3_V][n3_4]   = hitr.X();
-        y_3[n3_V][n3_4]   = hitr.Y();
-        z_3[n3_V][n3_4]   = zr;
-        t_3[n3_V][n3_4]   = hitr.T();
-        dx_3[n3_V][n3_4]  = hitr.dX();
-        dxy_3[n3_V][n3_4] = hitr.dXY();
-        dy_3[n3_V][n3_4]  = hitr.dY();
-        dt2_3[n3_V][n3_4] = hitr.dT2();
-
-        n3++;
-        n3_V = n3 / fvec::size();
-        n3_4 = n3 % fvec::size();
-
-        assert(n3 == (int) hitsl_3.size());
-        assert(n3 == (int) hitsm_3.size());
-        assert(n3 == (int) hitsr_3.size());
-
-        if (0 == n3_4) {
-          T_3.push_back(L1TrackPar_0);
-          x_3.push_back(fvec::Zero());
-          y_3.push_back(fvec::Zero());
-          z_3.push_back(fvec::Zero());
-          t_3.push_back(fvec::Zero());
-          dx_3.push_back(fvec::One());
-          dxy_3.push_back(fvec::Zero());
-          dy_3.push_back(fvec::One());
-          dt2_3.push_back(fvec::One());
-        }
-      }  // search area
-
-    }  // i2_4
-  }    // i2_V
-}
-
-/// Add the right hits to parameters estimation.
-inline void L1Algo::findTripletsStep1(  // input
-  Tindex n3_V, const L1Station& star, L1Vector<fvec>& x_3, L1Vector<fvec>& y_3, L1Vector<fvec>& z_3,
-  L1Vector<fvec>& t_3, L1Vector<fvec>& dx_3, L1Vector<fvec>& dxy_3, L1Vector<fvec>& dy_3, L1Vector<fvec>& dt2_3,
-  // output
-  L1Vector<L1TrackPar>& T_3)
-{
-  L1Fit fit;
-  fit.SetParticleMass(fDefaultMass);
-  fit.SetMask(fmask::One());
-  for (Tindex i3_V = 0; i3_V < n3_V; ++i3_V) {
-    fit.SetTrack(T_3[i3_V]);
-    fit.ExtrapolateLineNoField(z_3[i3_V]);
-    L1XYMeasurementInfo info;
-    info.C00 = dx_3[i3_V] * dx_3[i3_V];
-    info.C10 = dxy_3[i3_V];
-    info.C11 = dy_3[i3_V] * dy_3[i3_V];
-    fit.FilterXY(info, x_3[i3_V], y_3[i3_V]);
-    if (kMcbm != fTrackingMode) { fit.FilterTime(t_3[i3_V], dt2_3[i3_V], star.timeInfo); }
-    T_3[i3_V] = fit.Tr();
-  }
-}
-
-
-inline void L1Algo::findTripletsStep2(Tindex n3, int istal, int istam, int istar, L1Vector<L1TrackPar>& T_3,
-                                      L1Vector<L1HitIndex_t>& hitsl_3, L1Vector<L1HitIndex_t>& hitsm_3,
-                                      L1Vector<L1HitIndex_t>& hitsr_3, int nIterations)
-{
-
-  nIterations = 2;
-
-  /// Refit Triplets
-
-  ca::tools::Debugger::Instance().AddNtuple("triplets", "ev:mc:sta:p:vx:vy:vz:chi2:ndf");
-
-  L1Fit fit;
-  fit.SetMask(fmask::One());
-
-  if (fpCurrentIteration->GetElectronFlag()) { fit.SetParticleMass(L1Constants::phys::kElectronMass); }
-  else {
-    fit.SetParticleMass(GetDefaultParticleMass());
-  }
-  const int NHits = 3;  // triplets
-
-  // prepare data
-  int ista[NHits] = {istal, istam, istar};
-
-  L1Station sta[3];
-  for (int is = 0; is < NHits; ++is) {
-    sta[is] = fParameters.GetStation(ista[is]);
-  };
-
-  const L1Station& stal = fParameters.GetStation(istal);
-  const L1Station& stam = fParameters.GetStation(istam);
-  const L1Station& star = fParameters.GetStation(istar);
-
-  bool isMomentumFitted = ((stal.fieldStatus != 0) || (stam.fieldStatus != 0) || (star.fieldStatus != 0));
-  bool isTimeFitted     = ((stal.timeInfo != 0) || (stam.timeInfo != 0) || (star.timeInfo != 0));
-  fvec ndf              = -4;  // straight line
-  ndf += isMomentumFitted ? -1 : 0;
-  ndf += isTimeFitted ? -1 : 0;
-
-  for (int i3 = 0; i3 < n3; ++i3) {
-    int i3_V = i3 / fvec::size();
-    int i3_4 = i3 % fvec::size();
-
-    L1TrackPar& T3 = T_3[i3_V];
-
-    //if (!T3.IsEntryConsistent(false, i3_4)) continue;
-
-    // prepare data
-    L1HitIndex_t ihit[NHits] = {fGridHitIds[hitsl_3[i3] + fGridHitStartIndex[ista[0]]],
-                                fGridHitIds[hitsm_3[i3] + fGridHitStartIndex[ista[1]]],
-                                fGridHitIds[hitsr_3[i3] + fGridHitStartIndex[ista[2]]]};
-
-    if (fParameters.DevIsMatchTripletsViaMc()) {
-      int mc1 = GetMcTrackIdForCaHit(ihit[0]);
-      int mc2 = GetMcTrackIdForCaHit(ihit[1]);
-      int mc3 = GetMcTrackIdForCaHit(ihit[2]);
-      if ((mc1 != mc2) || (mc1 != mc3)) { continue; }
-    }
-
-    fscal x[NHits], y[NHits], z[NHits], t[NHits];
-    fscal dt2[NHits];
-    L1XYMeasurementInfo cov[NHits];
-
-    for (int ih = 0; ih < NHits; ++ih) {
-      const L1Hit& hit = fInputData.GetHit(ihit[ih]);
-      x[ih]            = hit.x;
-      y[ih]            = hit.y;
-      z[ih]            = hit.z;
-      t[ih]            = hit.t;
-      cov[ih].C00      = hit.dx * hit.dx;
-      cov[ih].C10      = hit.dxy;
-      cov[ih].C11      = hit.dy * hit.dy;
-      dt2[ih]          = hit.dt2;
-    };
-
-    // find the field along the track
-
-    L1FieldValue B[3] _fvecalignment;
-    L1FieldRegion fld _fvecalignment;
-    L1FieldRegion fldTarget _fvecalignment;
-
-    fvec tx[3] = {(x[1] - x[0]) / (z[1] - z[0]), (x[2] - x[0]) / (z[2] - z[0]), (x[2] - x[1]) / (z[2] - z[1])};
-    fvec ty[3] = {(y[1] - y[0]) / (z[1] - z[0]), (y[2] - y[0]) / (z[2] - z[0]), (y[2] - y[1]) / (z[2] - z[1])};
-    for (int ih = 0; ih < NHits; ++ih) {
-      fvec dz = (sta[ih].fZ - z[ih]);
-      sta[ih].fieldSlice.GetFieldValue(x[ih] + tx[ih] * dz, y[ih] + ty[ih] * dz, B[ih]);
-    };
-
-    fld.Set(B[0], sta[0].fZ, B[1], sta[1].fZ, B[2], sta[2].fZ);
-    fldTarget.Set(fTargB, fTargZ, B[0], sta[0].fZ, B[1], sta[1].fZ);
-
-    L1TrackPar& T = fit.Tr();
-
-    // initial parameters
-    {
-      fvec dz01 = 1. / (z[1] - z[0]);
-      T.tx      = (x[1] - x[0]) * dz01;
-      T.ty      = (y[1] - y[0]) * dz01;
-      T.qp      = 0.;  //(fscal) T3.qp[i3_4];
-      T.vi      = 0.;
-    }
-
-    // repeat several times in order to improve the precision
-    for (int iiter = 0; iiter < nIterations; ++iiter) {
-      // fit forward
-      {
-        fit.SetQp0(T.qp);
-        fit.Qp0()(fit.Qp0() > GetMaxInvMom())  = GetMaxInvMom();
-        fit.Qp0()(fit.Qp0() < -GetMaxInvMom()) = -GetMaxInvMom();
-
-        T.qp    = 0.;
-        T.vi    = 0.;
-        int ih0 = 0;
-        T.x     = x[ih0];
-        T.y     = y[ih0];
-        T.z     = z[ih0];
-        T.t     = t[ih0];
-
-        T.ResetErrors(1., 1., 1., 1., 100., (sta[ih0].timeInfo ? dt2[ih0] : 1.e6), 1.e2);
-        T.C00 = cov[ih0].C00;
-        T.C10 = cov[ih0].C10;
-        T.C11 = cov[ih0].C11;
-
-        T.NDF = ndf;
-
-        //  add the target constraint
-        fit.AddTargetToLine(fTargX, fTargY, fTargZ, TargetXYInfo, fldTarget);
-
-        for (int ih = 1; ih < NHits; ++ih) {
-          fit.Extrapolate(z[ih], fld);
-          auto radThick = fParameters.GetMaterialThickness(ista[ih], T.x, T.y);
-          fit.AddMsInMaterial(radThick);
-          fit.EnergyLossCorrection(radThick, fvec(-1.f));
-          fit.FilterXY(cov[ih], x[ih], y[ih]);
-          fit.FilterTime(t[ih], dt2[ih], sta[ih].timeInfo);
-        }
-      }
-
-      if (iiter == nIterations - 1) break;
-
-      // fit backward
-      {
-        fit.SetQp0(T.qp);
-        fit.Qp0()(fit.Qp0() > GetMaxInvMom())  = GetMaxInvMom();
-        fit.Qp0()(fit.Qp0() < -GetMaxInvMom()) = -GetMaxInvMom();
-
-        T.NDF   = ndf;
-        T.qp    = 0.;
-        T.vi    = 0.;
-        int ih0 = NHits - 1;
-        T.x     = x[ih0];
-        T.y     = y[ih0];
-        T.z     = z[ih0];
-        T.t     = t[ih0];
-
-        T.ResetErrors(1., 1., 1., 1., 100., (sta[ih0].timeInfo ? dt2[ih0] : 1.e6), 1.e2);
-        T.C00 = cov[ih0].C00;
-        T.C10 = cov[ih0].C10;
-        T.C11 = cov[ih0].C11;
-
-        T.NDF = ndf;
-
-        for (int ih = NHits - 2; ih >= 0; --ih) {
-          fit.Extrapolate(z[ih], fld);
-          auto radThick = fParameters.GetMaterialThickness(ista[ih], T.x, T.y);
-          fit.AddMsInMaterial(radThick);
-          fit.EnergyLossCorrection(radThick, fvec(1.f));
-          fit.FilterXY(cov[ih], x[ih], y[ih]);
-          fit.FilterTime(t[ih], dt2[ih], sta[ih].timeInfo);
-        }
-      }
-    }  // for iiter
-
-    //cout << " chi2 " << T3.chi2[i3_4] << " " << T.chi2[0] << endl;
-    T.chi2 = (fscal) T3.chi2[i3_4];  //SG!!!
-    T3.SetOneEntry(i3_4, T, i3_4);
-
-    {
-      int mc1 = GetMcTrackIdForCaHit(ihit[0]);
-      int mc2 = GetMcTrackIdForCaHit(ihit[1]);
-      int mc3 = GetMcTrackIdForCaHit(ihit[2]);
-      if ((mc1 >= 0) && (mc1 == mc2) && (mc1 == mc3)) {
-        const CbmL1MCTrack& mctr = CbmL1::Instance()->GetMcTracks()[mc1];
-        float ev                 = 0;
-        float chi2               = T.chi2[i3_4];
-        float nd                 = T.NDF[i3_4];
-        ca::tools::Debugger::Instance().FillNtuple("triplets", ev, mc1, istal, mctr.p, mctr.x, mctr.y, mctr.z, chi2,
-                                                   nd);
-      }
-    }
-  }  //i3
-
-}  // findTripletsStep2
-
-
-inline void L1Algo::findTripletsStep3(  // input
-  Tindex n3, int istal, int istam, int istar, L1Vector<L1TrackPar>& T_3, L1Vector<L1HitIndex_t>& hitsl_3,
-  L1Vector<L1HitIndex_t>& hitsm_3, L1Vector<L1HitIndex_t>& hitsr_3)
-{
-  /// Selects good triplets and saves them into fTriplets.
-  /// Finds neighbouring triplets at the next station.
-  ///
-  /// \param n3  Number of triplets to process
-  /// \param istal  index of the left station
-  /// \param istam  index of the middle station
-  /// \param istar  index of the right station
-  /// \param T_3  fitted trajectories of triplets
-  /// \param hitsl_3  index of the left triplet hit in Grid hits on the left station
-  /// \param hitsm_3  index of the middle triplet hit in Grid hits on the middle station
-  /// \param hitsr_3  index of the right triplet hit in Grid hits on the right station
-
-#ifdef _OPENMP
-  unsigned int Thread = omp_get_thread_num();
-#else
-  unsigned int Thread = 0;
-#endif
-
-  L1HitIndex_t ihitl_prev = 0;
-
-  const L1Station& stal = fParameters.GetStation(istal);
-  const L1Station& stam = fParameters.GetStation(istam);
-  const L1Station& star = fParameters.GetStation(istar);
-
-  bool isMomentumFitted = ((stal.fieldStatus != 0) || (stam.fieldStatus != 0) || (star.fieldStatus != 0));
-
-  for (Tindex i3 = 0; i3 < n3; ++i3) {
-    const Tindex i3_V = i3 / fvec::size();
-    const Tindex i3_4 = i3 % fvec::size();
-
-    L1TrackPar& T3 = T_3[i3_V];
-
-    //if (!T3.IsEntryConsistent(false, i3_4)) continue;
-
-    // select
-    fscal chi2 = T3.chi2[i3_4];  // / T3.NDF[i3_4];
-
-    const L1HitIndex_t ihitl = hitsl_3[i3] + fGridHitStartIndex[istal];
-    const L1HitIndex_t ihitm = hitsm_3[i3] + fGridHitStartIndex[istam];
-    const L1HitIndex_t ihitr = hitsr_3[i3] + fGridHitStartIndex[istar];
-    L1_ASSERT(ihitl < fGridHitStopIndex[istal], ihitl << " < " << fGridHitStopIndex[istal]);
-    L1_ASSERT(ihitm < fGridHitStopIndex[istam], ihitm << " < " << fGridHitStopIndex[istam]);
-    L1_ASSERT(ihitr < fGridHitStopIndex[istar], ihitr << " < " << fGridHitStopIndex[istar]);
-
-    unsigned int Location = PackTripletId(istal, Thread, fTriplets[istal][Thread].size());
-
-    if (ihitl_prev != 1 + hitsl_3[i3]) {
-      // TODO: SG: A bug! The code doesn't work for iterations with missing hits.
-      // TODO: Triplets with the same left hit are not placed continiously in fTriplets vector.
-      // TODO: Urgent!!!
-      // assert(fHitNtriplets[ihitl] == 0);
-      fHitFirstTriplet[ihitl] = Location;
-      fHitNtriplets[ihitl]    = 0;
-    }
-
-    ihitl_prev = 1 + hitsl_3[i3];
-
-    if (!fpCurrentIteration->GetTrackFromTripletsFlag()) {
-      if (chi2 > fTripletFinalChi2Cut) { continue; }
-    }
-
-    // assert(std::isfinite(chi2) && chi2 > 0);
-
-    if (!std::isfinite(chi2) || chi2 < 0) { continue; }
-    //if (!T3.IsEntryConsistent(false, i3_4)) { continue; }
-
-    fscal qp = T3.qp[i3_4];
-
-    //TODO: why sqrt's? Wouldn't it be faster to skip sqrt() here and
-    //TODO: compare the squared differences dqr*dqp later?
-
-    fscal Cqp = T3.C44[i3_4];
-
-    // TODO: SG: a magic correction that comes from the legacy code
-    // removing it leads to a higher ghost ratio
-    Cqp += 0.001;
-
-    fTriplets[istal][Thread].emplace_back(ihitl, ihitm, ihitr, istal, istam, istar, 0, 0, 0, chi2, qp, Cqp, T3.tx[i3_4],
-                                          T3.C22[i3_4], T3.ty[i3_4], T3.C33[i3_4]);
-
-    L1Triplet& tr1 = fTriplets[istal][Thread].back();
-    tr1.SetIsMomentumFitted(isMomentumFitted);
-    tr1.SetLevel(0);
-    tr1.SetFNeighbour(0);
-    tr1.SetNNeighbours(0);
-
-    fHitNtriplets[ihitl]++;
-
-    if (istal > (fParameters.GetNstationsActive() - 4)) continue;
-
-    unsigned int nNeighbours = fHitNtriplets[ihitm];
-
-    unsigned int neighLocation = fHitFirstTriplet[ihitm];
-    unsigned int neighStation  = TripletId2Station(neighLocation);
-    unsigned int neighThread   = TripletId2Thread(neighLocation);
-    unsigned int neighTriplet  = TripletId2Triplet(neighLocation);
-
-    if (nNeighbours > 0) { assert((int) neighStation == istal + 1 || (int) neighStation == istal + 2); }
-
-    unsigned char level = 0;
-
-    for (unsigned int iN = 0; iN < nNeighbours; ++iN, ++neighTriplet, ++neighLocation) {
-
-      L1Triplet& curNeighbour = fTriplets[neighStation][neighThread][neighTriplet];
-
-      fscal dchi2 = 0.;
-      if (!checkTripletMatch(tr1, curNeighbour, dchi2)) continue;
-
-      if (tr1.GetFNeighbour() == 0) tr1.SetFNeighbour(neighLocation);
-
-      tr1.SetNNeighbours(neighLocation - tr1.GetFNeighbour() + 1);
-
-      const unsigned char jlevel = curNeighbour.GetLevel();
-
-      if (level <= jlevel) level = jlevel + 1;
-    }
-
-    tr1.SetLevel(level);
-  }
-}
-
-/// Find neighbours of triplets. Calculate level of triplets.
-inline void L1Algo::f5(  // input
-  // output
-
-  int* nlevel)
-{
-  if (!fpCurrentIteration->GetTrackFromTripletsFlag()) {
-    for (int istal = fParameters.GetNstationsActive() - 4; istal >= fFirstCAstation; istal--) {
-      for (int tripType = 0; tripType < 3; tripType++)  // tT = 0 - 123triplet, tT = 1 - 124triplet, tT = 2 - 134triplet
-      {
-        // All iters - jump
-        if ((!fpCurrentIteration->GetJumpedFlag() && tripType != 0)
-            || (fpCurrentIteration->GetJumpedFlag() && tripType != 0
-                && (fParameters.GetNstationsActive() - 4 == istal))) {
-          continue;
-        }
-
-        int istam = istal + 1;
-        int istar = istal + 2;
-        switch (tripType) {
-          case 1: istar++; break;
-          case 2:
-            istam++;
-            istar++;
-            break;
-        }
-
-        for (Tindex iThread = 0; iThread < fNThreads; ++iThread) {
-          for (Tindex itrip = 0; itrip < (Tindex) fTriplets[istal][iThread].size(); ++itrip) {
-            L1Triplet& trip = fTriplets[istal][iThread][itrip];
-            if (istam != trip.GetMSta()) continue;
-            if (istar != trip.GetRSta()) continue;
-
-            unsigned char level = 0;
-            //           fscal  chi2 = trip->GetChi2();
-
-            L1HitIndex_t ihitl = trip.GetLHit();
-            L1HitIndex_t ihitm = trip.GetMHit();
-            L1HitIndex_t ihitr = trip.GetRHit();
-            L1_ASSERT(ihitl < fGridHitStopIndex[istal], ihitl << " < " << fGridHitStopIndex[istal]);
-            L1_ASSERT(ihitm < fGridHitStopIndex[istam], ihitm << " < " << fGridHitStopIndex[istam]);
-            L1_ASSERT(ihitr < fGridHitStopIndex[istar], ihitr << " < " << fGridHitStopIndex[istar]);
-
-            L1Vector<unsigned int> neighCands("L1CATrackFinder::neighCands");  // save neighbour candidates
-            neighCands.reserve(8);                                             // ~average is 1-2 for central, up to 5
-
-            unsigned int nNeighbours = fHitNtriplets[ihitm];
-
-            unsigned int neighLocation = fHitFirstTriplet[ihitm];
-            unsigned int neighStation  = TripletId2Station(neighLocation);
-            unsigned int neighThread   = TripletId2Thread(neighLocation);
-            unsigned int neighTriplet  = TripletId2Triplet(neighLocation);
-
-            for (unsigned int iN = 0; iN < nNeighbours; ++iN, ++neighTriplet, ++neighLocation) {
-              //    for (iN = first_triplet; iN <= last_triplet; ++iN){
-              L1Triplet& neigh = fTriplets[neighStation][neighThread][neighTriplet];
-              //      if (neigh.GetMSta() != istar) continue; // neighbours should have 2 common hits
-              //      if (neigh.GetMHit() != ihitr) continue; //!!!
-
-              fscal dchi2 = 0.;
-              if (!checkTripletMatch(trip, neigh, dchi2)) continue;
-
-              // calculate level
-              unsigned char jlevel = neigh.GetLevel();
-              if (level <= jlevel) level = jlevel + 1;
-              if (level == jlevel + 1) neighCands.push_back(neighLocation);
-            }
-
-            // trip->neighbours.reset(0);
-            // for (unsigned int in = 0; in < neighCands.size(); in++) {
-            //   int ID           = neighCands[in];
-            //   unsigned int Station     = TripletId2Station(ID);
-            //   unsigned int Thread      = TripletId2Thread(ID);
-            //   unsigned int Triplet     = TripletId2Triplet(ID);
-            //   const int nLevel = fTriplets[Station][Thread][Triplet].GetLevel();
-            //   if (level == nLevel + 1) trip->neighbours.push_back(Location);
-            // }
-            nlevel[level]++;
-          }  // vTriplets
-        }
-      }  // tripType
-    }    // istal
-  }      // if !fpCurrentIteration->GetTrackFromTripletsFlag()
-}
-
-/// ------------------- doublets on station ----------------------
-
-inline void L1Algo::CreatePortionOfDoublets(
-  /// input:
-  const int istal, const int istam, const Tindex iSingletPortion, const Tindex singletPortionSize,
-  /// output:
-  L1TrackPar* T_1, L1FieldRegion* fld_1, L1HitIndex_t* hitsl_1, Tindex& n_2, L1Vector<L1HitIndex_t>& i1_2,
-  L1Vector<L1HitIndex_t>& hitsm_2
-  ///
-)
-{
-  /// creates doublets
-  /// input:
-  ///   @istal - start station number
-  ///   @istam - last station number
-  ///   @iSingletPortion - index of portion of left hits
-  ///   @singletPortionSize - number of left hits in the portion
-  /// output:
-  ///   @*T_1 - singlet parameters
-  ///   @*fld_1 - field aproximation for singlets
-  ///   @*hitsl_1- left hits of future triplets
-  ///   @&n_2 - number of doublets
-  ///   @&i1_2 - index of 1st hit in portion indexed by doublet index
-  ///   @&hitsm_2 - index of middle hit in hits array indexed by doublet index
-
-  if (istam < fParameters.GetNstationsActive()) {
-    const L1Station& stal = fParameters.GetStation(istal);
-    const L1Station& stam = fParameters.GetStation(istam);
-
-    // prepare data
-    L1HitPoint* vHits_l = &(fGridPoints[0]) + fGridHitStartIndex[istal];
-    L1HitPoint* vHits_m = &(fGridPoints[0]) + fGridHitStartIndex[istam];
-
-    fvec x[L1Constants::size::kSingletPortionSizeVec];
-    fvec y[L1Constants::size::kSingletPortionSizeVec];
-    fvec z[L1Constants::size::kSingletPortionSizeVec];
-    fvec t[L1Constants::size::kSingletPortionSizeVec];
-    fvec dx[L1Constants::size::kSingletPortionSizeVec];
-    fvec dxy[L1Constants::size::kSingletPortionSizeVec];
-    fvec dy[L1Constants::size::kSingletPortionSizeVec];
-    fvec dt2[L1Constants::size::kSingletPortionSizeVec];
-
-    /// prepare the portion of left hits data
-
-    findSingletsStep0(  // input
-      iSingletPortion * L1Constants::size::kSingletPortionSize, singletPortionSize, vHits_l,
-      // output
-      hitsl_1, x, y, z, t, dx, dxy, dy, dt2);
-
-    for (Tindex i = 0; i < singletPortionSize; ++i)
-      L1_ASSERT(hitsl_1[i] < fGridHitStopIndex[istal] - fGridHitStartIndex[istal],
-                hitsl_1[i] << " < " << fGridHitStopIndex[istal] - fGridHitStartIndex[istal]);
-
-    Tindex portionSize_V = (singletPortionSize + fvec::size() - 1) / fvec::size();
-
-    /// Get the field approximation. Add the target to parameters estimation. Propagaete to middle station.
-
-    findSingletsStep1(istal, istam, portionSize_V, x, y, z, t, dx, dxy, dy, dt2,
-                      // output
-                      T_1, fld_1);
-
-    /// Find the doublets. Reformat data in the portion of doublets.
-
-    // TODO: repalce with constexpr if (C++17) (S.Zharko)
-#ifdef DOUB_PERFORMANCE
-    L1Vector<L1HitIndex_t> hitsl_2("L1CATrackFinder::hitsl_2");
-#endif  // DOUB_PERFORMANCE
-
-    findDoubletsStep0(  // input
-      singletPortionSize, stal, vHits_l, stam, vHits_m, T_1, hitsl_1,
-      // output
-      n_2, i1_2,
-#ifdef DOUB_PERFORMANCE
-      hitsl_2,
-#endif  // DOUB_PERFORMANCE
-      hitsm_2);
-
-    for (Tindex i = 0; i < static_cast<Tindex>(hitsm_2.size()); ++i)
-      L1_ASSERT(hitsm_2[i] < fGridHitStopIndex[istam] - fGridHitStartIndex[istam],
-                hitsm_2[i] << " " << fGridHitStopIndex[istam] - fGridHitStartIndex[istam]);
-
-#ifdef DOUB_PERFORMANCE
-    L1HitIndex_t* RealIHitL = &(fGridHitIds[fGridHitStartIndex[istal]]);
-    L1HitIndex_t* RealIHitM = &(fGridHitIds[fGridHitStartIndex[istam]]);
-    for (Tindex i = 0; i < n2; ++i) {
-      // int i_4 = i%4;
-      // int i_V = i/4;
-      L1HitIndex_t iHits[2] = {RealIHitL[hitsl_2[i]], RealIHitM[hitsm_2[i]]};
-      fL1Eff_doublets->AddOne(iHits);
-    }
-#endif  // DOUB_PERFORMANCE
-  }
-}
-
-
-/// ------------------- Triplets on station ----------------------
-
-inline void L1Algo::CreatePortionOfTriplets(
-  /// input
-  int istal, int istam, int istar,
-
-  /// input / output
-  L1TrackPar* T_1, L1FieldRegion* fld_1, L1HitIndex_t* hitsl_1,
-
-  Tindex& n_2, L1Vector<L1HitIndex_t>& i1_2, L1Vector<L1HitIndex_t>& hitsm_2)
-{
-
-  /// creates a portion of triplets:
-  /// input:
-  ///  @istal - left station number,
-  ///  @istam - middle station number,
-  ///  @istar - right station number,
-
-  /// input / output:
-
-  /// @*T_1 - track parameters for singlets,
-  /// @*fld_1 - field approximation for singlets,
-  /// @&n_2 - number of doublets in portion
-  /// @&n_2 - number of doublets,
-  /// @&i1_2 - index of 1st hit in portion indexed by doublet index,
-  /// @&hitsm_2 - index of middle hit in hits array indexed by doublet index
-  /// output:
-  // @*vTriplets_part - array of triplets,
-  // @*TripStartIndexH,
-  // @*TripStopIndexH - start/stop index of a triplet in the array
-
-  if (istar < fParameters.GetNstationsActive()) {
-    // prepare data
-    const L1Station& star = fParameters.GetStation(istar);
-
-    L1HitPoint* vHits_m = &(fGridPoints[0]) + fGridHitStartIndex[istam];
-
-    L1HitPoint* vHits_r = 0;
-    vHits_r             = &(fGridPoints[0]) + fGridHitStartIndex[istar];
-
-    Tindex n3 = 0;
-
-    /// Add the middle hits to parameters estimation. Propagate to right station.
-
-
-#ifdef _OPENMP
-    int Thread = omp_get_thread_num();
-#else
-    int Thread = 0;
-#endif
-
-    L1Vector<L1TrackPar>& T_3       = fTripletPar[Thread];
-    L1Vector<L1HitIndex_t>& hitsl_3 = fTripletHitsL[Thread];
-    L1Vector<L1HitIndex_t>& hitsm_3 = fTripletHitsM[Thread];
-    L1Vector<L1HitIndex_t>& hitsr_3 = fTripletHitsR[Thread];
-
-    L1Vector<fvec>& x_3 = fTripletHitR_X[Thread];
-    L1Vector<fvec>& y_3 = fTripletHitR_Y[Thread];
-    L1Vector<fvec>& z_3 = fTripletHitR_Z[Thread];
-    L1Vector<fvec>& t_3 = fTripletHitR_Time[Thread];
-
-    L1Vector<fvec>& dx_3  = fTripletHitR_dX[Thread];
-    L1Vector<fvec>& dxy_3 = fTripletHitR_dXY[Thread];
-    L1Vector<fvec>& dy_3  = fTripletHitR_dY[Thread];
-    L1Vector<fvec>& dt2_3 = fTripletHitR_TimeErr[Thread];
-
-    T_3.clear();
-    hitsl_3.clear();
-    hitsm_3.clear();
-    hitsr_3.clear();
-
-    x_3.clear();
-    y_3.clear();
-    z_3.clear();
-    t_3.clear();
-
-    dx_3.clear();
-    dxy_3.clear();
-    dy_3.clear();
-    dt2_3.clear();
-
-    /// Find the triplets(right hit). Reformat data in the portion of triplets.
-
-    findTripletsStep0(  // input
-      vHits_r,
-
-      istal, istam, istar, vHits_m, T_1, fld_1, hitsl_1,
-
-      n_2, hitsm_2, i1_2,
-
-      // output
-      n3, T_3, hitsl_3, hitsm_3, hitsr_3, x_3, y_3, z_3, t_3, dx_3, dxy_3, dy_3, dt2_3);
-
-
-    for (Tindex i = 0; i < static_cast<Tindex>(hitsl_3.size()); ++i)
-      L1_assert(hitsl_3[i] < fGridHitStopIndex[istal] - fGridHitStartIndex[istal]);
-    for (Tindex i = 0; i < static_cast<Tindex>(hitsm_3.size()); ++i)
-      L1_assert(hitsm_3[i] < fGridHitStopIndex[istam] - fGridHitStartIndex[istam]);
-    for (Tindex i = 0; i < static_cast<Tindex>(hitsr_3.size()); ++i)
-      L1_assert(hitsr_3[i] < fGridHitStopIndex[istar] - fGridHitStartIndex[istar]);
-
-    /// Add the right hits to parameters estimation.
-
-
-    Tindex n3_V = (n3 + fvec::size() - 1) / fvec::size();
-    findTripletsStep1(  // input
-      n3_V, star, x_3, y_3, z_3, t_3, dx_3, dxy_3, dy_3, dt2_3,
-      // output
-      T_3);
-
-
-    /// refit
-    findTripletsStep2(n3, istal, istam, istar, T_3, hitsl_3, hitsm_3, hitsr_3, 1);
-
-#ifdef TRIP_PERFORMANCE
-    L1HitIndex_t* RealIHitL = &(fGridHitIds[fGridHitStartIndex[istal]]);
-    L1HitIndex_t* RealIHitM = &(fGridHitIds[fGridHitStartIndex[istam]]);
-    L1HitIndex_t* RealIHitR = &(fGridHitIds[fGridHitStartIndex[istar]]);
-    for (Tindex i = 0; i < n3; ++i) {
-      Tindex i_4            = i % 4;
-      Tindex i_V            = i / 4;
-      L1HitIndex_t iHits[3] = {RealIHitL[hitsl_3[i]], RealIHitM[hitsm_3[i]], RealIHitR[hitsr_3[i]]};
-#ifdef PULLS
-      if (fL1Eff_triplets->AddOne(iHits)) fL1Pulls->AddOne(T_3[i_V], i_4, iHits[2]);
-#else
-      fL1Eff_triplets->AddOne(iHits);
-#endif
-      if (T_3[i_V].chi2[i_4] < fTripletFinalChi2Cut) { fL1Eff_triplets2->AddOne(iHits); }
-    }
-#endif  // TRIP_PERFORMANCE
-
-
-    /// Fill Triplets.
-    findTripletsStep3(  // input
-      n3, istal, istam, istar, T_3, hitsl_3, hitsm_3, hitsr_3);
-  }
-}
-
-
-// hitCheck::hitCheck()
-// {
-//          omp_init_lock(&Occupied);
-//          trackCandidateIndex = -1;
-//          UsedByTrack=0;
-//          Chi2Track = 100000000;
-//          Length = 0;
-//          ista = 1000;
-//
-// }
-// hitCheck::~hitCheck()
-// {
-// omp_destroy_lock(&Occupied);
-// }
 
 
 // **************************************************************************************************
@@ -1761,142 +412,81 @@ void L1Algo::CaTrackFinderSlice()
     }
 
 
-    {
-      /// possible left hits of triplets are splited in portions of 16 (4 SIMDs) to use memory faster
-
-      for (int istal = fParameters.GetNstationsActive() - 2; istal >= fFirstCAstation;
-           istal--) {  //start downstream chambers
-        fSingletPortionSize[istal].clear();
-        int NHits_l   = fGridHitStopIndex[istal] - fGridHitStartIndex[istal];
-        int nPortions = NHits_l / L1Constants::size::kSingletPortionSize;
-        int rest      = NHits_l - nPortions * L1Constants::size::kSingletPortionSize;
-        for (int ipp = 0; ipp < nPortions; ipp++) {
-          fSingletPortionSize[istal].push_back(L1Constants::size::kSingletPortionSize);
-        }  // start_lh - portion of left hits
-        if (rest > 0) fSingletPortionSize[istal].push_back(rest);
-      }  // lstations
-
-
-#ifdef COUNTERS
-      stat_nSinglets[isec] += nSinglets;
-#endif
-    }
-
-    /*    {
-         /// possible left hits of triplets are splited in portions of 16 (4 SIMDs) to use memory faster
-         fDoubletPortionStopIndex[fParameters.GetNstationsActive() - 1] = 0;
-         unsigned int ip = 0;  //index of curent portion
-
-         for (int istal = fParameters.GetNstationsActive() - 2; istal >= fFirstCAstation; istal--)  //start downstream chambers
-         {
-         int nHits = fGridHitStopIndex[istal] - fGridHitStartIndex[istal];
-
-         int NHits_P = nHits/L1Constants::size::kSingletPortionSize;
-
-         for( int ipp = 0; ipp < NHits_P; ipp++ )
-         {
-         n_g1[ip] = L1Constants::size::kSingletPortionSize;
-         ip++;
-         } // start_lh - portion of left hits
+    ///   stage for triplets creation
 
-         n_g1[ip] = nHits - NHits_P * L1Constants::size::kSingletPortionSize;
+    L1TripletConstructor constructor1(this);
+    L1TripletConstructor constructor2(this);
+    L1TripletConstructor constructor3(this);
 
-         ip++;
-         fDoubletPortionStopIndex[istal] = ip;
-         }// lstations
-         //       nPortions = ip;
-         } */
+    for (int istal = fParameters.GetNstationsActive() - 2; istal >= fFirstCAstation;
+         istal--) {  // start with downstream chambers
+      Tindex NHits_l = fGridHitStopIndex[istal] - fGridHitStartIndex[istal];
+      for (Tindex ih = 0; ih < NHits_l; ++ih) {
+        constructor1.CreateTripletsForHit(istal, istal + 1, istal + 2, ih);
+        if (fpCurrentIteration->GetJumpedFlag()) {
+          constructor2.CreateTripletsForHit(istal, istal + 1, istal + 3, ih);
+          constructor3.CreateTripletsForHit(istal, istal + 2, istal + 3, ih);
+        }
 
-    ///   stage for triplets creation
+        auto oldSize = fTriplets[istal][0].size();
 
-#ifdef XXX
-    TStopwatch c_timer;
-    c_timer.Start();
-#endif
+        fTriplets[istal][0].insert(fTriplets[istal][0].end(), constructor1.GetTriplets().begin(),
+                                   constructor1.GetTriplets().end());
+        fTriplets[istal][0].insert(fTriplets[istal][0].end(), constructor2.GetTriplets().begin(),
+                                   constructor2.GetTriplets().end());
+        fTriplets[istal][0].insert(fTriplets[istal][0].end(), constructor3.GetTriplets().begin(),
+                                   constructor3.GetTriplets().end());
 
+        const L1HitIndex_t ihitl = ih + fGridHitStartIndex[istal];
+        fHitFirstTriplet[ihitl]  = PackTripletId(istal, 0, oldSize);
+        fHitNtriplets[ihitl]     = fTriplets[istal][0].size() - oldSize;
+      }
+    }  // istal
 
-    L1TrackPar T_1[L1Constants::size::kSingletPortionSizeVec];
-    L1FieldRegion fld_1[L1Constants::size::kSingletPortionSizeVec];
-    L1HitIndex_t hitsl_1[L1Constants::size::kSingletPortionSize];
-    L1TrackPar TG_1[L1Constants::size::kSingletPortionSizeVec];
-    L1FieldRegion fldG_1[L1Constants::size::kSingletPortionSizeVec];
-    L1HitIndex_t hitslG_1[L1Constants::size::kSingletPortionSize];
 
-    /// middle hits indexed by number of doublets in portion(i2)
-    L1Vector<L1HitIndex_t> hitsm_2("L1CATrackFinder::hitsm_2");
+    // search for neighbouring triplets
 
-    /// index in portion of singlets(i1) indexed by index in portion of doublets(i2)
-    L1Vector<L1HitIndex_t> i1_2("L1CATrackFinder::i1_2");
+    for (int istal = fParameters.GetNstationsActive() - 2; istal >= fFirstCAstation;
+         istal--) {  // start with downstream chambers
 
-    /// middle hits indexed by number of doublets in portion(i2)
-    L1Vector<L1HitIndex_t> hitsmG_2("L1CATrackFinder::hitsmG_2");
+      for (unsigned int it = 0; it < fTriplets[istal][0].size(); ++it) {
 
-    /// index in portion of singlets(i1) indexed by index in portion of doublets(i2)
-    L1Vector<L1HitIndex_t> i1G_2("L1CATrackFinder::i1G_2");
+        L1Triplet& tr = fTriplets[istal][0][it];
+        tr.SetLevel(0);
+        tr.SetFNeighbour(0);
+        tr.SetNNeighbours(0);
 
-    hitsm_2.reserve(9000);  // TODO: make reasonable cuts on n combinations, put them to the header
-    i1_2.reserve(9000);  // TODO: why that large numbers are needed even for mbias??? something goes wrong sometimes..
-    hitsmG_2.reserve(9000);
-    i1G_2.reserve(9000);
+        if (istal > (fParameters.GetNstationsActive() - 4)) break;
 
-    for (int istal = fParameters.GetNstationsActive() - 2; istal >= fFirstCAstation;
-         istal--)  // start downstream chambers
-    {
+        unsigned int nNeighbours = fHitNtriplets[tr.GetMHit()];
 
-#ifdef _OPENMP
-#pragma omp parallel for firstprivate(T_1, fld_1, hitsl_1, hitsm_2, i1_2, TG_1, fldG_1, hitslG_1, hitsmG_2,            \
-                                      i1G_2)  //schedule(dynamic, 2)
-#endif
-      for (Tindex ip = 0; ip < (Tindex) fSingletPortionSize[istal].size(); ++ip) {
-        Tindex n_2 = 0;  /// number of doublets in portion
+        unsigned int neighLocation = fHitFirstTriplet[tr.GetMHit()];
+        unsigned int neighStation  = TripletId2Station(neighLocation);
+        unsigned int neighThread   = TripletId2Thread(neighLocation);
+        unsigned int neighTriplet  = TripletId2Triplet(neighLocation);
 
-        hitsm_2.clear();
-        i1_2.clear();
+        if (nNeighbours > 0) { assert((int) neighStation == istal + 1 || (int) neighStation == istal + 2); }
 
-        CreatePortionOfDoublets(istal, istal + 1, ip, fSingletPortionSize[istal][ip],
-                                // output
-                                T_1, fld_1, hitsl_1, n_2, i1_2, hitsm_2);
+        unsigned char level = 0;
 
+        for (unsigned int iN = 0; iN < nNeighbours; ++iN, ++neighTriplet, ++neighLocation) {
 
-        CreatePortionOfTriplets(  // input
-          istal, istal + 1, istal + 2, T_1, fld_1, hitsl_1, n_2, i1_2, hitsm_2);
+          L1Triplet& neighbour = fTriplets[neighStation][neighThread][neighTriplet];
 
-        if (fpCurrentIteration->GetJumpedFlag()) {
-          // All iterations are "jump"!
-          Tindex nG_2;
-          hitsmG_2.clear();
-          i1G_2.clear();
+          fscal dchi2 = 0.;
+          if (!checkTripletMatch(tr, neighbour, dchi2)) continue;
 
-          CreatePortionOfDoublets(  // input
-            istal, istal + 2, ip, fSingletPortionSize[istal][ip],
-            // output
-            TG_1, fldG_1, hitslG_1, nG_2, i1G_2, hitsmG_2);
+          if (tr.GetFNeighbour() == 0) { tr.SetFNeighbour(neighLocation); }
 
-          CreatePortionOfTriplets(  // input
-            istal, istal + 1, istal + 3, T_1, fld_1, hitsl_1,
+          tr.SetNNeighbours(neighLocation - tr.GetFNeighbour() + 1);
 
-            n_2, i1_2, hitsm_2);
+          const unsigned char neighbourLevel = neighbour.GetLevel();
 
-          CreatePortionOfTriplets(  // input
-            istal, istal + 2, istal + 3, TG_1, fldG_1, hitslG_1, nG_2, i1G_2, hitsmG_2);
+          if (level <= neighbourLevel) { level = neighbourLevel + 1; }
         }
-      }  //
-    }
-
-    //     int nlevels[L1Constants::size::kMaxNstations];  // number of triplets with some number of neighbours.
-    //     for (int il = 0; il < fParameters.GetNstationsActive(); ++il) nlevels[il] = 0;
-    //
-    //      f5(   // input
-    //           // output
-    //         0,
-    //         nlevels
-    //       );
-
-#ifdef XXX
-    c_timer.Stop();
-    ti[isec]["tripl1"] = c_timer;
-    c_timer.Start();
-#endif
+        tr.SetLevel(level);
+      }  // neighbour search
+    }    // istal
 
     ///====================================================================
     ///=                                                                  =
@@ -2408,8 +998,8 @@ void L1Algo::CaTrackFinderSlice()
 #ifdef TRIP_PERFORMANCE
   fL1Eff_triplets->CalculateEff();
   fL1Eff_triplets->Print("Triplet performance", 1);
-  //   fL1Eff_triplets2->CalculateEff();
-  //   fL1Eff_triplets2->Print("Triplet performance. After chi2 cut");
+//   fL1Eff_triplets2->CalculateEff();
+//   fL1Eff_triplets2->Print("Triplet performance. After chi2 cut");
 #endif
 }
 
@@ -2423,9 +1013,9 @@ void L1Algo::CaTrackFinderSlice()
      *                                                              *
      ****************************************************************/
 
-inline void L1Algo::CAFindTrack(int ista, L1Branch& best_tr, unsigned char& best_L, fscal& best_chi2,
-                                const L1Triplet* curr_trip, L1Branch& curr_tr, unsigned char& curr_L, fscal& curr_chi2,
-                                unsigned char min_best_l, L1Branch* new_tr)
+void L1Algo::CAFindTrack(int ista, L1Branch& best_tr, unsigned char& best_L, fscal& best_chi2,
+                         const L1Triplet* curr_trip, L1Branch& curr_tr, unsigned char& curr_L, fscal& curr_chi2,
+                         unsigned char min_best_l, L1Branch* new_tr)
 /// recursive search for tracks
 /// input: @ista - station index, @&best_tr - best track for the privious call, @&best_L -
 /// output: @&NCalls - number of function calls
diff --git a/reco/L1/L1Algo/L1Constants.h b/reco/L1/L1Algo/L1Constants.h
index eed01d2eec..e228cbea1e 100644
--- a/reco/L1/L1Algo/L1Constants.h
+++ b/reco/L1/L1Algo/L1Constants.h
@@ -49,8 +49,6 @@ namespace L1Constants
 
     // TODO: Clarify the meaning of these coefficients
     constexpr int kCoeff                 = 64 / 4;                        ///< TODO:
-    constexpr int kSingletPortionSize    = 1024 / kCoeff;                 ///< portion of left hits
-    constexpr int kSingletPortionSizeVec = 1024 / kCoeff / fvec::size();  ///< portion of left hits per one vector word
     constexpr int kMaxPortionDoublets    = 10000 / 5 * 64 / 2 / kCoeff;   ///< Max size of the doublets portion
     constexpr int kMaxPortionTriplets    = 10000 * 5 * 64 / 2 / kCoeff;   ///< Max size of the triplets portion
     constexpr int kMaxPortionTripletsP   = kMaxPortionTriplets / fvec::size();  ///< Max size of the triplets portion
diff --git a/reco/L1/L1Algo/L1Field.cxx b/reco/L1/L1Algo/L1Field.cxx
index 57172997aa..c50562a226 100644
--- a/reco/L1/L1Algo/L1Field.cxx
+++ b/reco/L1/L1Algo/L1Field.cxx
@@ -11,6 +11,7 @@
 #include <iostream>
 #include <sstream>
 
+#include "L1TrackPar.h"
 #include "L1Utils.h"
 
 //
@@ -60,10 +61,11 @@ std::ostream& operator<<(std::ostream& out, const L1FieldValue& B)
 L1FieldSlice::L1FieldSlice()
 {
   for (int i = 0; i < L1Constants::size::kMaxNFieldApproxCoefficients; ++i) {
-    cx[i] = L1Utils::kNaN;
-    cy[i] = L1Utils::kNaN;
-    cz[i] = L1Utils::kNaN;
+    cx[i] = undef::kFvc;
+    cy[i] = undef::kFvc;
+    cz[i] = undef::kFvc;
   }
+  z = undef::kFvc;
 }
 
 //----------------------------------------------------------------------------------------------------------------------
@@ -76,6 +78,7 @@ void L1FieldSlice::CheckConsistency() const
     L1Utils::CheckSimdVectorEquality(cy[i], "L1FieldSlice: cy");
     L1Utils::CheckSimdVectorEquality(cz[i], "L1FieldSlice: cz");
   }
+  L1Utils::CheckSimdVectorEquality(z, "L1FieldSlice: z");
 }
 
 //----------------------------------------------------------------------------------------------------------------------
@@ -117,6 +120,12 @@ void L1FieldSlice::GetFieldValue(const fvec& x, const fvec& y, L1FieldValue& B)
         + cz[16] * x4y + cz[17] * x3y2 + cz[18] * x2y3 + cz[19] * xy4 + cz[20] * y5;
 }
 
+void L1FieldSlice::GetFieldValueForLine(const L1TrackPar& t, L1FieldValue& B) const
+{
+  fvec dz = z - t.z;
+  GetFieldValue(t.x + t.tx * dz, t.y + t.ty * dz, B);
+}
+
 //----------------------------------------------------------------------------------------------------------------------
 //
 std::string L1FieldSlice::ToString(int indentLevel) const
diff --git a/reco/L1/L1Algo/L1Field.h b/reco/L1/L1Algo/L1Field.h
index 086dca9c1c..e8069ce082 100644
--- a/reco/L1/L1Algo/L1Field.h
+++ b/reco/L1/L1Algo/L1Field.h
@@ -10,6 +10,9 @@
 #include "L1Constants.h"
 #include "L1Def.h"
 #include "L1SimdSerializer.h"
+#include "L1Undef.h"
+
+class L1TrackPar;
 
 class L1FieldValue {
 public:
@@ -76,6 +79,11 @@ public:
   /// \param B  the L1FieldValue output
   void GetFieldValue(const fvec& x, const fvec& y, L1FieldValue& B) const;
 
+  /// Gets field value for the intersection with a straight track
+  /// \param t  straight track
+  /// \param B  the L1FieldValue output
+  void GetFieldValueForLine(const L1TrackPar& t, L1FieldValue& B) const;
+
   /// String representation of class contents
   /// \param indentLevel      number of indent characters in the output
   std::string ToString(int indentLevel = 0) const;
@@ -91,6 +99,8 @@ public:
   fvec cz
     [L1Constants::size::kMaxNFieldApproxCoefficients];  ///< Polynomial coefficients for z-component of the field value
 
+  fvec z {undef::kFvc};  ///< z coordinate of the slice
+
   /// Serialization function
   friend class boost::serialization::access;
   template<class Archive>
@@ -99,6 +109,7 @@ public:
     ar& cx;
     ar& cy;
     ar& cz;
+    ar& z;
   }
 } _fvecalignment;
 
diff --git a/reco/L1/L1Algo/L1Portion.h b/reco/L1/L1Algo/L1Portion.h
deleted file mode 100644
index 56c04ce973..0000000000
--- a/reco/L1/L1Algo/L1Portion.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/* Copyright (C) 2010-2021 Frankfurt Institute for Advanced Studies, Goethe-Universität Frankfurt, Frankfurt
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Igor Kulakov [committer] */
-
-#ifndef L1Portion_H
-#define L1Portion_H
-
-#include <vector>
-
-#include "L1TrackPar.h"
-
-using std::vector;
-// class for portions organization
-
-template<typename T>
-class L1Portion;
-
-template<>
-class L1Portion<L1TrackPar> {
-public:
-  typedef L1TrackPar T;
-  typedef L1Vector<T> vType;
-
-  L1Portion() {};
-  L1Portion(int size) { reserve(size); };
-  L1Portion(int size, int size2) : dataSize(size2)
-  {
-    reserve(size);
-    //     reserve2(size2);
-  };
-  vType& operator[](int i) { return a[i]; }
-  void resize(int size) { a.resize(size); };
-  void reserve(int size) { a.reserve(size); };
-  void reserve2(int size)
-  {
-    for (unsigned int i = 0; i < a.size(); i++)
-      a[i].reserve(size);
-  };
-  void push_back(vType& v) { a.push_back(v); };
-  void add_void()
-  {
-    vType v("L1Portion<L1TrackPar>::add_void");
-    //     v.resize(dataSize);
-    a.push_back(v);
-    a[a.size() - 1].reserve(dataSize);
-  };
-  void add_void(int i)
-  {
-    T t;
-    a[i].push_back(t);
-  };
-
-  int CalcSize()
-  {
-    int size = 0;
-    for (unsigned int i = 0; i < a.size(); i++)
-      size += a[i].size();
-    return size * sizeof(T);
-  };
-
-private:
-  vector<vType> a {"L1Portion<L1TrackPar>::a"};
-  //   int mainSize; // size of a
-  int dataSize {0};  // size of vType
-};
-
-template<>
-class L1Portion<L1FieldRegion> {
-public:
-  typedef L1FieldRegion T;
-  typedef L1Vector<T> vType;
-
-  L1Portion() {};
-  L1Portion(int size) { reserve(size); };
-  L1Portion(int size, int size2) : dataSize(size2)
-  {
-    reserve(size);
-    //     reserve2(size2);
-  };
-
-  vType& operator[](int i) { return a[i]; }
-  void resize(int size) { a.resize(size); };
-  void reserve(int size) { a.reserve(size); };
-  void reserve2(int size)
-  {
-    for (unsigned int i = 0; i < a.size(); i++)
-      a[i].reserve(size);
-  };
-  void push_back(vType& v) { a.push_back(v); };
-  void add_void()
-  {
-    vType v("L1Portion<L1FieldRegion>::add_void");
-    //     v.resize(dataSize);
-    a.push_back(v);
-    a[a.size() - 1].reserve(dataSize);
-  };
-  void add_void(int i)
-  {
-    T t;
-    a[i].push_back(t);
-  };
-
-  int CalcSize()
-  {
-    int size = 0;
-    for (unsigned int i = 0; i < a.size(); i++)
-      size += a[i].size();
-    return size * sizeof(T);
-  };
-
-private:
-  vector<vType> a {"L1Portion<L1FieldRegion>::a"};
-  //   int mainSize; // size of a
-  int dataSize {0};  // size of vType
-};
-
-
-template<typename T>
-class L1Portion {
-public:
-  typedef vector<T> vType;
-
-  L1Portion() {};
-  L1Portion(int size) { reserve(size); };
-  L1Portion(int size, int size2) : dataSize(size2)
-  {
-    reserve(size);
-    //     reserve2(size2);
-  };
-  vType& operator[](int i) { return a[i]; }
-  void resize(int size) { a.resize(size); };
-  void reserve(int size) { a.reserve(size); };
-  void reserve2(int size)
-  {
-    for (int i = 0; i < a.size(); i++)
-      a[i].reserve(size);
-  };
-  void push_back(vType& v) { a.push_back(v); };
-  void add_void()
-  {
-    vType v("L1Portion<T>::add_void");
-    //     v.resize(dataSize);
-    a.push_back(v);
-    a[a.size() - 1].reserve(dataSize);
-  };
-  void add_void(int i)
-  {
-    T t;
-    a[i].push_back(t);
-  };
-
-  int CalcSize()
-  {
-    int size = 0;
-    for (int i = 0; i < a.size(); i++)
-      size += a[i].size();
-    return size * sizeof(T);
-  };
-
-private:
-  vector<vType> a {"L1Portion<T>::a"};
-  //   int mainSize; // size of a
-  int dataSize {0};  // size of vType
-};
-
-#endif  // L1Portion_H
diff --git a/reco/L1/L1Algo/L1Triplet.h b/reco/L1/L1Algo/L1Triplet.h
index 0e0f4adb1f..4731592963 100644
--- a/reco/L1/L1Algo/L1Triplet.h
+++ b/reco/L1/L1Algo/L1Triplet.h
@@ -22,7 +22,7 @@ public:
   /// constructor
   L1Triplet(unsigned int iHitL, unsigned int iHitM, unsigned int iHitR, unsigned int iStaL, unsigned int iStaM,
             unsigned int iStaR, unsigned char Level, unsigned int firstNeighbour, char nNeighbours, fscal Chi2,
-            fscal Qp, fscal Cqp, fscal tx, fscal Ctx, fscal ty, fscal Cty)
+            fscal Qp, fscal Cqp, fscal tx, fscal Ctx, fscal ty, fscal Cty, bool isMomentumFitted)
     : fChi2(Chi2)
     , fQp(Qp)
     , fCqp(Cqp)
@@ -37,6 +37,7 @@ public:
     , fNneighbours(nNeighbours)
     , fLevel(Level)
     , fSta((iStaL << 4) + ((iStaM - iStaL - 1) << 2) + (iStaR - iStaL - 2))
+    , fIsMomentumFitted(isMomentumFitted)
   {
   }
 
diff --git a/reco/L1/L1Algo/L1TripletConstructor.cxx b/reco/L1/L1Algo/L1TripletConstructor.cxx
new file mode 100644
index 0000000000..96f4b28f62
--- /dev/null
+++ b/reco/L1/L1Algo/L1TripletConstructor.cxx
@@ -0,0 +1,690 @@
+/* Copyright (C) 2010-2021 Frankfurt Institute for Advanced Studies, Goethe-Universität Frankfurt, Frankfurt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Igor Kulakov [committer], Maksym Zyzak, Valentina Akishina */
+
+#include "L1TripletConstructor.h"
+
+#include "CbmL1.h"
+#include "CbmL1MCTrack.h"
+
+#include <algorithm>
+#include <iostream>
+
+#include "CaToolsDebugger.h"
+#include "L1Algo.h"
+#include "L1Assert.h"
+#include "L1Fit.h"
+#include "L1Grid.h"
+#include "L1HitArea.h"
+#include "L1HitPoint.h"
+#include "L1TrackPar.h"
+
+L1TripletConstructor::L1TripletConstructor(L1Algo* algo) { fAlgo = algo; }
+
+void L1TripletConstructor::InitStations(int istal, int istam, int istar)
+{
+  fIsInitialized = false;
+
+  fIstaL = istal;
+  fIstaM = istam;
+  fIstaR = istar;
+
+  if (fIstaM >= fAlgo->fParameters.GetNstationsActive()) { return; }
+
+  fStaL = &fAlgo->fParameters.GetStation(fIstaL);
+  fStaM = &fAlgo->fParameters.GetStation(fIstaM);
+  fStaR = &fAlgo->fParameters.GetStation(fIstaR);
+
+  fHitsL = &(fAlgo->fGridPoints[0]) + fAlgo->fGridHitStartIndex[fIstaL];
+  fHitsM = &(fAlgo->fGridPoints[0]) + fAlgo->fGridHitStartIndex[fIstaM];
+  fHitsR = &(fAlgo->fGridPoints[0]) + fAlgo->fGridHitStartIndex[fIstaR];
+
+  {  // two stations for approximating the field between the target and the left hit
+    int sta1 = fIstaL;
+    if (sta1 >= fAlgo->fNfieldStations) { sta1 = fAlgo->fNfieldStations - 1; }
+    if (sta1 < 1) { sta1 = 1; }
+    int sta0 = sta1 / 2;  // station between fIstaL and the target
+
+    assert(0 <= sta0 && sta0 < sta1 && sta1 < fAlgo->fParameters.GetNstationsActive());
+
+    fFld0Sta[0] = &fAlgo->fParameters.GetStation(sta0);
+    fFld0Sta[1] = &fAlgo->fParameters.GetStation(sta1);
+  }
+
+  {  // three stations for approximating the field between the left and the right hit
+
+    int sta0 = fIstaL;
+    int sta1 = fIstaM;
+    int sta2 = fIstaM + 1;
+    if (sta2 >= fAlgo->fParameters.GetNstationsActive()) {
+      sta2 = fIstaM;
+      sta1 = fIstaM - 1;
+      if (sta0 >= sta1) { sta0 = sta1 - 1; }
+    }
+
+    assert(0 <= sta0 && sta0 < sta1 && sta1 < sta2 && sta2 < fAlgo->fParameters.GetNstationsActive());
+
+    fFld1Sta[0] = &fAlgo->fParameters.GetStation(sta0);
+    fFld1Sta[1] = &fAlgo->fParameters.GetStation(sta1);
+    fFld1Sta[2] = &fAlgo->fParameters.GetStation(sta2);
+  }
+
+  fFit.SetParticleMass(fAlgo->GetDefaultParticleMass());
+  if (fAlgo->fpCurrentIteration->GetElectronFlag()) { fFit.SetParticleMass(L1Constants::phys::kElectronMass); }
+  fFit.SetMask(fmask::One());
+
+  fIsInitialized = true;
+}
+
+
+void L1TripletConstructor::CreateTripletsForHit(int istal, int istam, int istar, L1HitIndex_t ihl)
+{
+
+  InitStations(istal, istam, istar);
+
+  if (!fIsInitialized) return;
+
+  fIhitL = ihl;
+
+  L1HitPoint& hitl = fHitsL[fIhitL];
+
+  // fit a straight line through the target and the left hit.
+  {
+
+    /// Get the field approximation. Add the target to parameters estimation.
+    /// Propagaete to the middle station of a triplet.
+
+    fFit.SetQp0(fAlgo->fMaxInvMom);
+
+    L1FieldValue lB, mB, cB, rB _fvecalignment;
+    L1FieldValue l_B_global, centB_global _fvecalignment;
+
+    // get the magnetic field along the track.
+    // (suppose track is straight line with origin in the target)
+
+    fvec xl         = hitl.X();
+    fvec yl         = hitl.Y();
+    fvec zl         = hitl.Z();
+    fvec time       = hitl.T();
+    fvec timeEr2    = hitl.dT2();
+    const fvec dzli = 1.f / (zl - fAlgo->fTargZ);
+
+    const fvec tx = (xl - fAlgo->fTargX) * dzli;
+    const fvec ty = (yl - fAlgo->fTargY) * dzli;
+
+    L1TrackPar& T = fFit.Tr();
+
+    T.x  = xl;
+    T.y  = yl;
+    T.z  = zl;
+    T.tx = tx;
+    T.ty = ty;
+    T.qp = fvec(0.);
+    T.t  = time;
+    T.vi = fvec(0.);
+
+    fvec txErr2 = fAlgo->fMaxSlopePV * fAlgo->fMaxSlopePV / fvec(9.);
+    fvec qpErr2 = fAlgo->fMaxInvMom * fAlgo->fMaxInvMom / fvec(9.);
+
+    T.ResetErrors(1., 1., txErr2, txErr2, qpErr2, (staL().timeInfo ? timeEr2 : 1.e6), 1.e2);
+    T.C00 = hitl.dX2();
+    T.C10 = hitl.dXY();
+    T.C11 = hitl.dY2();
+
+    T.chi2              = 0.;
+    T.NDF               = (fAlgo->fpCurrentIteration->GetPrimaryFlag()) ? fvec(2.) : fvec(0.);
+    T.nTimeMeasurements = (staL().timeInfo ? 1 : 0);
+
+    // TODO: iteration parameter: "Starting NDF of track parameters"
+    // NDF = number of track parameters (6: x, y, tx, ty, qp, time) - number of measured parameters (3: x, y, time) on station or (2: x, y) on target
+
+
+    // field made by  the left hit, the target and the station istac in-between.
+    // is used for extrapolation to the target and to the middle hit
+    L1FieldRegion fld0;
+    {
+      L1FieldValue B0, B1;
+      fFld0Sta[0]->fieldSlice.GetFieldValueForLine(T, B0);
+      fFld0Sta[1]->fieldSlice.GetFieldValueForLine(T, B1);
+      fld0.Set(fAlgo->fTargB, fAlgo->fTargZ, B0, fFld0Sta[0]->fZ, B1, fFld0Sta[1]->fZ);
+    }
+
+    {  // field, made by the left hit, the middle station and the right station
+      // Will be used for extrapolation to the right hit
+      L1FieldValue B0, B1, B2;
+      fFld1Sta[0]->fieldSlice.GetFieldValueForLine(T, B0);
+      fFld1Sta[1]->fieldSlice.GetFieldValueForLine(T, B1);
+      fFld1Sta[2]->fieldSlice.GetFieldValueForLine(T, B2);
+      fFldL.Set(B0, fFld1Sta[0]->fZ, B1, fFld1Sta[1]->fZ, B2, fFld1Sta[2]->fZ);
+    }
+
+    // add the target constraint
+
+    fFit.AddTargetToLine(fAlgo->fTargX, fAlgo->fTargY, fAlgo->fTargZ, fAlgo->TargetXYInfo, fld0);
+
+    fFit.AddMsInMaterial(fAlgo->fParameters.GetMaterialThickness(fIstaL, T.x, T.y));
+
+    // extrapolate to the middle hit
+
+    fFit.ExtrapolateLine(staM().fZ, fFldL);
+
+    fTrL = T;
+  }
+
+  FindDoublets();
+  FitDoublets();
+  FindTriplets();
+}
+
+
+void L1TripletConstructor::FindDoublets()
+{
+  /// Find the doublets. Reformat data into portions of doublets.
+
+  int iMC = -1;
+  if (fAlgo->fParameters.DevIsMatchDoubletsViaMc()) {
+    int indL = fAlgo->fGridHitStartIndex[fIstaL] + fIhitL;
+    iMC      = fAlgo->GetMcTrackIdForGridHit(indL);
+    if (iMC < 0) { return; }
+  }
+
+  CollectHits(fTrL, fIstaM, fAlgo->fDoubletChi2Cut, iMC, fHitsM_2, fAlgo->fParameters.GetMaxDoubletsPerSinglet());
+}
+
+
+void L1TripletConstructor::FitDoublets()
+{
+
+  // ---- Add the middle hits to parameters estimation ----
+
+  L1Vector<L1HitIndex_t> hitsMtmp("L1TripletConstructor::hitsMtmp", fHitsM_2);
+
+  fHitsM_2.clear();
+  fTracks_2.clear();
+  fTracks_2.reserve(hitsMtmp.size());
+
+  bool isMomentumFitted = (fAlgo->fIsTargetField || (staL().fieldStatus != 0) || (staM().fieldStatus != 0));
+
+  for (unsigned int i2 = 0; i2 < hitsMtmp.size(); i2++) {
+
+    const L1HitIndex_t imh = hitsMtmp[i2];
+    const L1HitPoint& hitm = fHitsM[imh];
+
+    if (hitm.IsSuppressed()) continue;
+
+    L1TrackPar& T2 = fFit.Tr();
+    T2             = fTrL;
+    fFit.SetQp0(fvec(0.f));
+
+    fvec x_2 = hitm.X();
+    fvec y_2 = hitm.Y();
+    fvec z_2 = hitm.Z();
+    fvec t_2 = hitm.T();
+    L1XYMeasurementInfo cov_2;
+    cov_2.C00  = hitm.dX2();
+    cov_2.C10  = hitm.dXY();
+    cov_2.C11  = hitm.dY2();
+    fvec dt2_2 = hitm.dT2();
+
+    // add the middle hit
+
+    fFit.ExtrapolateLineNoField(z_2);
+
+    fFit.FilterXY(cov_2, x_2, y_2);
+
+    fFit.FilterTime(t_2, dt2_2, staM().timeInfo);
+
+    fFit.SetQp0(isMomentumFitted ? fFit.Tr().qp : fAlgo->fMaxInvMom);
+
+    fFit.AddMsInMaterial(fAlgo->fParameters.GetMaterialThickness(fIstaM, T2.x, T2.y));
+
+    fFit.SetQp0(fFit.Tr().qp);
+
+    // check if there are other hits close to the doublet on the same station
+    {
+      fscal tx = T2.tx[0];
+      fscal ty = T2.ty[0];
+      fscal tt = T2.vi[0] * sqrt(1. + tx * tx + ty * ty);  // dt/dl * dl/dz
+
+      for (unsigned int j2 = i2 + 1; j2 < hitsMtmp.size(); j2++) {
+
+        L1HitPoint& hitm1 = fHitsM[hitsMtmp[j2]];
+
+        fscal dz = hitm1.Z() - T2.z[0];
+
+        if ((staM().timeInfo) && (T2.nTimeMeasurements[0] > 0)) {
+          fscal dt = T2.t[0] + tt * dz - hitm.T();
+          if (dt * dt > 30. * (T2.C55[0] + hitm1.dT2())) { continue; }
+        }
+
+        fscal dx = T2.x[0] + tx * dz - hitm1.X();
+        if (dx * dx > 20. * (T2.C00[0] + hitm1.dX2())) { continue; }
+
+        fscal dy = T2.y[0] + ty * dz - hitm1.Y();
+        if (dy * dy > 30. * (T2.C11[0] + hitm1.dY2())) { continue; }
+
+        if (fAlgo->fParameters.DevIsSuppressOverlapHitsViaMc()) {
+          int indL  = fAlgo->fGridHitStartIndex[fIstaL] + fIhitL;
+          int indM  = fAlgo->fGridHitStartIndex[fIstaM] + imh;
+          int indM1 = fAlgo->fGridHitStartIndex[fIstaM] + hitsMtmp[j2];
+          int iMC   = fAlgo->GetMcTrackIdForGridHit(indL);
+          if ((iMC != fAlgo->GetMcTrackIdForGridHit(indM)) || (iMC != fAlgo->GetMcTrackIdForGridHit(indM1))) {
+            continue;
+          }
+        }
+        hitm1.SetIsSuppresed(1);
+      }
+    }
+
+    fHitsM_2.push_back(imh);
+    fTracks_2.push_back(T2);
+  }  // i2
+}
+
+
+void L1TripletConstructor::FindTriplets()
+{
+  if (fIstaR >= fAlgo->fParameters.GetNstationsActive()) { return; }
+  findRightHit();
+  fitTriplets();
+  storeTriplets();
+}
+
+
+void L1TripletConstructor::findRightHit()
+{
+
+  /// Add the middle hits to parameters estimation. Propagate to right station.
+  /// Find the triplets(right hit). Reformat data in the portion of triplets.
+
+  fFit.SetQp0(fvec(0.));
+
+  if (fIstaR >= fAlgo->fParameters.GetNstationsActive()) return;
+
+  {
+    int maxTriplets = fHitsM_2.size() * fAlgo->fParameters.GetMaxTripletPerDoublets();
+    fHitsM_3.clear();
+    fHitsR_3.clear();
+    fHitsM_3.reserve(maxTriplets);
+    fHitsR_3.reserve(maxTriplets);
+  }
+  // ---- Add the middle hits to parameters estimation. Propagate to right station. ----
+
+  for (unsigned int i2 = 0; i2 < fHitsM_2.size(); i2++) {
+
+    fFit.SetTrack(fTracks_2[i2]);
+    L1TrackPar& T2 = fFit.Tr();
+
+    // extrapolate to the right hit station
+
+    fFit.Extrapolate(staR().fZ, fFldL);
+
+    // ---- Find the triplets(right hit). Reformat data in the portion of triplets. ----
+
+    if (fAlgo->kSts == fAlgo->fTrackingMode && (T2.C44[0] < 0)) { continue; }
+    if (T2.C00[0] < 0 || T2.C11[0] < 0 || T2.C22[0] < 0 || T2.C33[0] < 0 || T2.C55[0] < 0) continue;
+
+    if (fabs(T2.tx[0]) > fAlgo->fMaxSlope) continue;
+    if (fabs(T2.ty[0]) > fAlgo->fMaxSlope) continue;
+
+    int iMC = -1;
+    if (fAlgo->fParameters.DevIsMatchTripletsViaMc()) {
+      int indL = fAlgo->fGridHitStartIndex[fIstaL] + fIhitL;
+      int indM = fAlgo->fGridHitStartIndex[fIstaM] + fHitsM_2[i2];
+      iMC      = fAlgo->GetMcTrackIdForGridHit(indL);
+      if (iMC < 0 || iMC != fAlgo->GetMcTrackIdForGridHit(indM)) { continue; }
+    }
+
+    L1Vector<L1HitIndex_t> collectedHits;
+    CollectHits(T2, fIstaR, fAlgo->fTripletChi2Cut, iMC, collectedHits, fAlgo->fParameters.GetMaxTripletPerDoublets());
+
+    if (collectedHits.size() >= fAlgo->fParameters.GetMaxTripletPerDoublets()) {
+      LOG(debug) << "L1: GetMaxTripletPerDoublets==" << fAlgo->fParameters.GetMaxTripletPerDoublets()
+                 << " reached in findTripletsStep0()";
+      // reject already created triplets for this doublet
+      collectedHits.clear();
+    }
+
+    for (unsigned int ih = 0; ih < collectedHits.size(); ih++) {
+      L1HitIndex_t irh       = collectedHits[ih];
+      const L1HitPoint& hitr = fHitsR[irh];
+      if (hitr.IsSuppressed()) continue;
+
+      // pack the triplet
+
+      fHitsM_3.push_back(fHitsM_2[i2]);
+      fHitsR_3.push_back(irh);
+
+    }  // search area
+  }    // i2
+}
+
+
+void L1TripletConstructor::fitTriplets()
+{
+  constexpr bool dumpTriplets = 0;
+
+  constexpr int nIterations = 2;
+
+  Tindex n3 = fHitsM_3.size();
+  assert(fHitsM_3.size() == fHitsR_3.size());
+
+  fTracks_3.clear();
+  fTracks_3.reset(n3, L1TrackPar());
+
+  /// Refit Triplets
+  if (dumpTriplets) { ca::tools::Debugger::Instance().AddNtuple("triplets", "ev:mc:sta:p:vx:vy:vz:chi2:ndf"); }
+
+  L1Fit fit;
+  fit.SetMask(fmask::One());
+
+  if (fAlgo->fpCurrentIteration->GetElectronFlag()) { fit.SetParticleMass(L1Constants::phys::kElectronMass); }
+  else {
+    fit.SetParticleMass(fAlgo->GetDefaultParticleMass());
+  }
+  const int NHits = 3;  // triplets
+
+  // prepare data
+  int ista[NHits] = {fIstaL, fIstaM, fIstaR};
+
+  L1Station sta[3];
+  for (int is = 0; is < NHits; ++is) {
+    sta[is] = fAlgo->fParameters.GetStation(ista[is]);
+  };
+
+  bool isMomentumFitted = ((staL().fieldStatus != 0) || (staM().fieldStatus != 0) || (staR().fieldStatus != 0));
+  bool isTimeFitted     = ((staL().timeInfo != 0) || (staM().timeInfo != 0) || (staR().timeInfo != 0));
+  fvec ndf              = -4;  // straight line
+  ndf += isMomentumFitted ? -1 : 0;
+  ndf += isTimeFitted ? -1 : 0;
+
+  for (int i3 = 0; i3 < n3; ++i3) {
+
+    // prepare data
+    L1HitIndex_t ihit[NHits] = {fAlgo->fGridHitIds[fIhitL + fAlgo->fGridHitStartIndex[ista[0]]],
+                                fAlgo->fGridHitIds[fHitsM_3[i3] + fAlgo->fGridHitStartIndex[ista[1]]],
+                                fAlgo->fGridHitIds[fHitsR_3[i3] + fAlgo->fGridHitStartIndex[ista[2]]]};
+
+    if (fAlgo->fParameters.DevIsMatchTripletsViaMc()) {
+      int mc1 = fAlgo->GetMcTrackIdForCaHit(ihit[0]);
+      int mc2 = fAlgo->GetMcTrackIdForCaHit(ihit[1]);
+      int mc3 = fAlgo->GetMcTrackIdForCaHit(ihit[2]);
+      if ((mc1 != mc2) || (mc1 != mc3)) { continue; }
+    }
+
+    fscal x[NHits], y[NHits], z[NHits], t[NHits];
+    fscal dt2[NHits];
+    L1XYMeasurementInfo cov[NHits];
+
+    for (int ih = 0; ih < NHits; ++ih) {
+      const L1Hit& hit = fAlgo->fInputData.GetHit(ihit[ih]);
+      x[ih]            = hit.x;
+      y[ih]            = hit.y;
+      z[ih]            = hit.z;
+      t[ih]            = hit.t;
+      cov[ih].C00      = hit.dx * hit.dx;
+      cov[ih].C10      = hit.dxy;
+      cov[ih].C11      = hit.dy * hit.dy;
+      dt2[ih]          = hit.dt2;
+    };
+
+    // find the field along the track
+
+    L1FieldValue B[3] _fvecalignment;
+    L1FieldRegion fld _fvecalignment;
+    L1FieldRegion fldTarget _fvecalignment;
+
+    fvec tx[3] = {(x[1] - x[0]) / (z[1] - z[0]), (x[2] - x[0]) / (z[2] - z[0]), (x[2] - x[1]) / (z[2] - z[1])};
+    fvec ty[3] = {(y[1] - y[0]) / (z[1] - z[0]), (y[2] - y[0]) / (z[2] - z[0]), (y[2] - y[1]) / (z[2] - z[1])};
+    for (int ih = 0; ih < NHits; ++ih) {
+      fvec dz = (sta[ih].fZ - z[ih]);
+      sta[ih].fieldSlice.GetFieldValue(x[ih] + tx[ih] * dz, y[ih] + ty[ih] * dz, B[ih]);
+    };
+
+    fld.Set(B[0], sta[0].fZ, B[1], sta[1].fZ, B[2], sta[2].fZ);
+    fldTarget.Set(fAlgo->fTargB, fAlgo->fTargZ, B[0], sta[0].fZ, B[1], sta[1].fZ);
+
+    L1TrackPar& T = fit.Tr();
+
+    // initial parameters
+    {
+      fvec dz01 = 1. / (z[1] - z[0]);
+      T.tx      = (x[1] - x[0]) * dz01;
+      T.ty      = (y[1] - y[0]) * dz01;
+      T.qp      = 0.;
+      T.vi      = 0.;
+    }
+
+    // repeat several times in order to improve the precision
+    for (int iiter = 0; iiter < nIterations; ++iiter) {
+      // fit forward
+      {
+        fit.SetQp0(T.qp);
+        fit.Qp0()(fit.Qp0() > fAlgo->GetMaxInvMom())  = fAlgo->GetMaxInvMom();
+        fit.Qp0()(fit.Qp0() < -fAlgo->GetMaxInvMom()) = -fAlgo->GetMaxInvMom();
+
+        T.qp    = 0.;
+        T.vi    = 0.;
+        int ih0 = 0;
+        T.x     = x[ih0];
+        T.y     = y[ih0];
+        T.z     = z[ih0];
+        T.t     = t[ih0];
+
+        T.ResetErrors(1., 1., 1., 1., 100., (sta[ih0].timeInfo ? dt2[ih0] : 1.e6), 1.e2);
+        T.C00 = cov[ih0].C00;
+        T.C10 = cov[ih0].C10;
+        T.C11 = cov[ih0].C11;
+
+        T.NDF = ndf;
+
+        //  add the target constraint
+        fit.AddTargetToLine(fAlgo->fTargX, fAlgo->fTargY, fAlgo->fTargZ, fAlgo->TargetXYInfo, fldTarget);
+
+        for (int ih = 1; ih < NHits; ++ih) {
+          fit.Extrapolate(z[ih], fld);
+          auto radThick = fAlgo->fParameters.GetMaterialThickness(ista[ih], T.x, T.y);
+          fit.AddMsInMaterial(radThick);
+          fit.EnergyLossCorrection(radThick, fvec(-1.f));
+          fit.FilterXY(cov[ih], x[ih], y[ih]);
+          fit.FilterTime(t[ih], dt2[ih], sta[ih].timeInfo);
+        }
+      }
+
+      if (iiter == nIterations - 1) break;
+
+      // fit backward
+      {
+        fit.SetQp0(T.qp);
+        fit.Qp0()(fit.Qp0() > fAlgo->GetMaxInvMom())  = fAlgo->GetMaxInvMom();
+        fit.Qp0()(fit.Qp0() < -fAlgo->GetMaxInvMom()) = -fAlgo->GetMaxInvMom();
+
+        T.NDF   = ndf;
+        T.qp    = 0.;
+        T.vi    = 0.;
+        int ih0 = NHits - 1;
+        T.x     = x[ih0];
+        T.y     = y[ih0];
+        T.z     = z[ih0];
+        T.t     = t[ih0];
+
+        T.ResetErrors(1., 1., 1., 1., 100., (sta[ih0].timeInfo ? dt2[ih0] : 1.e6), 1.e2);
+        T.C00 = cov[ih0].C00;
+        T.C10 = cov[ih0].C10;
+        T.C11 = cov[ih0].C11;
+
+        T.NDF = ndf;
+
+        for (int ih = NHits - 2; ih >= 0; --ih) {
+          fit.Extrapolate(z[ih], fld);
+          auto radThick = fAlgo->fParameters.GetMaterialThickness(ista[ih], T.x, T.y);
+          fit.AddMsInMaterial(radThick);
+          fit.EnergyLossCorrection(radThick, fvec(1.f));
+          fit.FilterXY(cov[ih], x[ih], y[ih]);
+          fit.FilterTime(t[ih], dt2[ih], sta[ih].timeInfo);
+        }
+      }
+    }  // for iiter
+
+    fTracks_3[i3] = T;
+
+    if (dumpTriplets) {
+      int mc1 = fAlgo->GetMcTrackIdForCaHit(ihit[0]);
+      int mc2 = fAlgo->GetMcTrackIdForCaHit(ihit[1]);
+      int mc3 = fAlgo->GetMcTrackIdForCaHit(ihit[2]);
+      if ((mc1 >= 0) && (mc1 == mc2) && (mc1 == mc3)) {
+        const CbmL1MCTrack& mctr = CbmL1::Instance()->GetMcTracks()[mc1];
+        float ev                 = 0;
+        float chi2               = T.chi2[0];
+        float nd                 = T.NDF[0];
+        ca::tools::Debugger::Instance().FillNtuple("triplets", ev, mc1, fIstaL, mctr.p, mctr.x, mctr.y, mctr.z, chi2,
+                                                   nd);
+      }
+    }
+  }  //i3
+
+}  // findTripletsStep2
+
+
+void L1TripletConstructor::storeTriplets()
+{
+  /// Selects good triplets and saves them into fTriplets.
+  /// Finds neighbouring triplets at the next station.
+
+  Tindex n3 = fHitsM_3.size();
+
+  bool isMomentumFitted = ((staL().fieldStatus != 0) || (staM().fieldStatus != 0) || (staR().fieldStatus != 0));
+
+  fTriplets.clear();
+  fTriplets.reserve(n3);
+
+  for (Tindex i3 = 0; i3 < n3; ++i3) {
+
+    L1TrackPar& T3 = fTracks_3[i3];
+
+    fscal chi2 = T3.chi2[0];  // / T3.NDF[0];
+
+    const L1HitIndex_t ihitl = fIhitL + fAlgo->fGridHitStartIndex[fIstaL];
+    const L1HitIndex_t ihitm = fHitsM_3[i3] + fAlgo->fGridHitStartIndex[fIstaM];
+    const L1HitIndex_t ihitr = fHitsR_3[i3] + fAlgo->fGridHitStartIndex[fIstaR];
+
+    L1_ASSERT(ihitl < fAlgo->fGridHitStopIndex[fIstaL], ihitl << " < " << fAlgo->fGridHitStopIndex[fIstaL]);
+    L1_ASSERT(ihitm < fAlgo->fGridHitStopIndex[fIstaM], ihitm << " < " << fAlgo->fGridHitStopIndex[fIstaM]);
+    L1_ASSERT(ihitr < fAlgo->fGridHitStopIndex[fIstaR], ihitr << " < " << fAlgo->fGridHitStopIndex[fIstaR]);
+
+    if (!fAlgo->fpCurrentIteration->GetTrackFromTripletsFlag()) {
+      if (chi2 > fAlgo->fTripletFinalChi2Cut) { continue; }
+    }
+
+    // assert(std::isfinite(chi2) && chi2 > 0);
+
+    if (!std::isfinite(chi2) || chi2 < 0) { continue; }
+    //if (!T3.IsEntryConsistent(false, 0)) { continue; }
+
+    fscal qp = T3.qp[0];
+
+    //TODO: why sqrt's? Wouldn't it be faster to skip sqrt() here and
+    //TODO: compare the squared differences dqr*dqp later?
+
+    fscal Cqp = T3.C44[0];
+
+    // TODO: SG: a magic correction that comes from the legacy code
+    // removing it leads to a higher ghost ratio
+    Cqp += 0.001;
+
+    fTriplets.emplace_back(ihitl, ihitm, ihitr, fIstaL, fIstaM, fIstaR, 0, 0, 0, chi2, qp, Cqp, T3.tx[0], T3.C22[0],
+                           T3.ty[0], T3.C33[0], isMomentumFitted);
+  }
+}
+
+
+void L1TripletConstructor::CollectHits(const L1TrackPar& Tr, const int iSta, const double chi2Cut, const int iMC,
+                                       L1Vector<L1HitIndex_t>& collectedHits, int maxNhits)
+{
+  /// Collect hits on a station
+
+  collectedHits.clear();
+  collectedHits.reserve(maxNhits);
+
+  const L1Station& sta     = fAlgo->fParameters.GetStation(iSta);
+  const L1HitPoint* hits   = &(fAlgo->fGridPoints[0]) + fAlgo->fGridHitStartIndex[iSta];
+  const L1HitIndex_t nHits = fAlgo->fGridHitStopIndex[iSta] - fAlgo->fGridHitStartIndex[iSta];
+
+  fFit.SetTrack(Tr);
+  L1TrackPar& T = fFit.Tr();
+
+  // if make it bigger the found hits will be rejected later because of the chi2 cut.
+  const fvec Pick_m22 = (fvec(chi2Cut) - T.chi2);
+
+  const fscal iz         = 1.f / (T.z[0] - fAlgo->fParameters.GetTargetPositionZ()[0]);
+  const fscal timeError2 = T.C55[0];
+  const fscal time       = T.t[0];
+
+  L1HitAreaTime areaTime(fAlgo->vGridTime[iSta], T.x[0] * iz, T.y[0] * iz,
+                         (sqrt(Pick_m22 * T.C00) + fAlgo->fMaxDx[iSta] + fAlgo->fMaxDZ * abs(T.tx))[0] * iz,
+                         (sqrt(Pick_m22 * T.C11) + fAlgo->fMaxDy[iSta] + fAlgo->fMaxDZ * abs(T.ty))[0] * iz, time,
+                         sqrt(timeError2) * 5);
+
+  for (L1HitIndex_t ih = 0; (ih < nHits) && ((int) collectedHits.size() < maxNhits);
+       ih++) {  // loop over all station hits
+
+    if (!fAlgo->fParameters.DevIsIgnoreHitSearchAreas()) {  // only loop over the hits in the area
+      if (!areaTime.GetNext(ih)) { break; }
+    }
+
+    const L1HitPoint& hit = hits[ih];
+    if (hit.IsSuppressed()) continue;
+
+    if (iMC >= 0) {  // match via MC
+      if (iMC != fAlgo->GetMcTrackIdForGridHit(fAlgo->fGridHitStartIndex[iSta] + ih)) { continue; }
+    }
+
+    // check y-boundaries
+    //TODO: move hardcoded cuts to parameters
+    if ((sta.timeInfo) && (T.nTimeMeasurements[0] > 0)) {
+      if (fabs(time - hit.T()) > sqrt(timeError2 + hit.dT2()) * 5) continue;
+      if (fabs(time - hit.T()) > 30) continue;
+    }
+
+    // - check whether hit belong to the window ( track position +\- errors ) -
+    const fscal z = hit.Z();
+
+    fvec y, C11;
+    fFit.ExtrapolateYC11Line(z, y, C11);
+
+    fscal dy_est2 = Pick_m22[0] * fabs(C11[0] + hit.dY2());
+
+    fscal xm = hit.X();
+    fscal ym = hit.Y();
+
+    const fscal dY = ym - y[0];
+
+    if (dY * dY > dy_est2) continue;
+
+    // check x-boundaries
+    fvec x, C00;
+    fFit.ExtrapolateXC00Line(z, x, C00);
+
+    fscal dx_est2 = Pick_m22[0] * fabs(C00[0] + hit.dX2());
+
+    const fscal dX = xm - x[0];
+
+    if (dX * dX > dx_est2) continue;
+
+    // check chi2
+
+    fvec C10;
+    fFit.ExtrapolateC10Line(z, C10);
+
+    auto [chi2x, chi2u] = L1Fit::GetChi2XChi2U(hit.X(), hit.Y(), hit.dX2(), hit.dXY(), hit.dY2(), x, y, C00, C10, C11);
+
+    // TODO: adjust the cut, cut on chi2x & chi2u separately
+    if (!fAlgo->fpCurrentIteration->GetTrackFromTripletsFlag()) {
+      if (chi2x[0] > chi2Cut) continue;
+      if (chi2x[0] + chi2u[0] > chi2Cut) continue;
+    }
+
+    collectedHits.push_back(ih);
+
+  }  // loop over the hits in the area
+}
diff --git a/reco/L1/L1Algo/L1TripletConstructor.h b/reco/L1/L1Algo/L1TripletConstructor.h
new file mode 100644
index 0000000000..6cf5a6b31d
--- /dev/null
+++ b/reco/L1/L1Algo/L1TripletConstructor.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergey Gorbunov [committer] */
+
+#ifndef L1TripletConstructor_h
+#define L1TripletConstructor_h
+
+#include "L1Algo.h"
+#include "L1Field.h"
+#include "L1Fit.h"
+#include "L1HitPoint.h"
+#include "L1Station.h"
+#include "L1TrackPar.h"
+#include "L1Triplet.h"
+#include "L1Vector.h"
+
+
+/// Construction of triplets for the CA tracker
+///
+class L1TripletConstructor {
+public:
+  ///                             ------  Constructors and destructor ------
+
+  /// Constructor
+  /// \param nThreads  Number of threads for multi-threaded mode
+  L1TripletConstructor(L1Algo* algo);
+
+  /// Copy constructor
+  L1TripletConstructor(const L1TripletConstructor&) = delete;
+
+  /// Move constructor
+  L1TripletConstructor(L1TripletConstructor&&) = delete;
+
+  /// Copy assignment operator
+  L1TripletConstructor& operator=(const L1TripletConstructor&) = delete;
+
+  /// Move assignment operator
+  L1TripletConstructor& operator=(L1TripletConstructor&&) = delete;
+
+  /// Destructor
+  ~L1TripletConstructor() = default;
+
+
+  ///                             ------  FUNCTIONAL PART ------
+
+  void InitStations(int istal, int istam, int istar);
+
+  void CreateTripletsForHit(int istal, int istam, int istar, L1HitIndex_t ihl);
+
+  const L1Vector<L1Triplet>& GetTriplets() const { return fTriplets; }
+
+  /// Find the doublets. Reformat data in the portion of doublets.
+  void FindDoublets();
+  void FitDoublets();
+
+  /// Find triplets on station
+  void FindTriplets();
+
+  /// Add the middle hits to parameters estimation. Propagate to right station.
+  /// Find the triplets (right hit). Reformat data in the portion of triplets.
+  void findRightHit();
+
+  /// Fit Triplets
+  void fitTriplets();
+
+  /// Select triplets. Save them into vTriplets.
+  void storeTriplets();
+
+  void CollectHits(const L1TrackPar& Tr, const int iSta, const double chi2Cut, const int iMC,
+                   L1Vector<L1HitIndex_t>& collectedHits, int maxNhits);
+
+private:
+  /// left station
+  const L1Station& staL() { return *fStaL; }
+  /// middle station
+  const L1Station& staM() { return *fStaM; }
+  /// right station
+  const L1Station& staR() { return *fStaR; }
+
+private:
+  bool fIsInitialized {false};  ///< is initialized;
+  L1Algo* fAlgo {nullptr};      ///< pointer to L1Algo object
+
+  int fIstaL {-1};  ///< left station index
+  int fIstaM {-1};  ///< middle station index
+  int fIstaR {-1};  ///< right station index
+
+  const L1Station* fStaL {nullptr};  ///< left station
+  const L1Station* fStaM {nullptr};  ///< mid station
+  const L1Station* fStaR {nullptr};  ///< right station
+
+  L1HitPoint* fHitsL {nullptr};  ///< hits on the left station
+  L1HitPoint* fHitsM {nullptr};  ///< hits on the middle station
+  L1HitPoint* fHitsR {nullptr};  ///< hits on the right station
+
+  const L1Station* fFld0Sta[2];  // two stations for approximating the field between the target and the left hit
+  const L1Station* fFld1Sta[3];  // three stations for approximating the field between the left and the right hit
+
+  L1HitIndex_t fIhitL;
+  L1TrackPar fTrL;
+  L1FieldRegion fFldL;
+
+  L1Vector<L1HitIndex_t> fHitsM_2 {"L1TripletConstructor::fHitsM_2"};
+  L1Vector<L1TrackPar> fTracks_2 {"L1TripletConstructor::fTracks_2"};
+
+  L1Vector<L1TrackPar> fTracks_3 {"L1TripletConstructor::fTracks_3"};
+  L1Vector<L1HitIndex_t> fHitsM_3 {"L1TripletConstructor::fHitsM_3"};
+  L1Vector<L1HitIndex_t> fHitsR_3 {"L1TripletConstructor::fHitsR_3"};
+
+  L1Vector<L1Triplet> fTriplets {"L1TripletConstructor::fTriplets"};
+
+  L1Fit fFit;
+
+} _fvecalignment;
+
+#endif
-- 
GitLab