From d7b5cf78446ccb64585f509c107d7d9eacfa7d58 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Tue, 3 Dec 2024 10:16:42 +0800
Subject: [PATCH 01/12] add use costheta

---
 .vscode/settings.json       | 21 +++++++++++++++++++++
 Generator/src/GtGunTool.cpp | 18 ++++++++++++++++--
 Generator/src/GtGunTool.h   |  2 ++
 3 files changed, 39 insertions(+), 2 deletions(-)
 create mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..6ad48fa1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,21 @@
+{
+    "files.associations": {
+        "*.sycl": "cpp",
+        "*.hpp": "cpp",
+        "*.h": "cpp",
+        "*.c": "cpp",
+        "*.cpp": "cpp",
+        "array": "cpp",
+        "bitset": "cpp",
+        "initializer_list": "cpp",
+        "list": "cpp",
+        "random": "cpp",
+        "type_traits": "cpp",
+        "vector": "cpp",
+        "xhash": "cpp",
+        "xstring": "cpp",
+        "xtree": "cpp",
+        "xutility": "cpp",
+        "cmath": "cpp"
+    }
+}
\ No newline at end of file
diff --git a/Generator/src/GtGunTool.cpp b/Generator/src/GtGunTool.cpp
index ad747984..71206ebf 100644
--- a/Generator/src/GtGunTool.cpp
+++ b/Generator/src/GtGunTool.cpp
@@ -95,6 +95,19 @@ GtGunTool::initialize() {
         return StatusCode::FAILURE;
     }
 
+    if (m_usecostheta.value()) {
+        for (int i=0; i<m_thetamins.value().size(); ++i) {
+            if ((m_thetamins.value()[i] < -1) || (m_thetamins.value()[i] > 1)) {
+                error() << "UseCostheta: ThetaMins has values outside the range [-1, 1]." << endmsg;
+                return StatusCode::FAILURE;
+            }
+            if ((m_thetamaxs.value()[i] < -1) || (m_thetamaxs.value()[i] > 1)) {
+                error() << "UseCostheta: ThetaMaxs has values outside the range [-1, 1]." << endmsg;
+                return StatusCode::FAILURE;
+            }
+        }
+    }
+
     // Time
     if (m_times.value().size()==0){
       for(int i=0; i<m_particles.value().size(); i++) m_times.value().push_back(0);
@@ -246,13 +259,14 @@ GtGunTool::mutate(Gen::GenEvent& event) {
 
         double theta = m_thetamins.value()[i]==m_thetamaxs.value()[i] ? m_thetamins.value()[i] : CLHEP::RandFlat::shoot(m_thetamins.value()[i], m_thetamaxs.value()[i]);
         double phi =   m_phimins  .value()[i]==m_phimaxs  .value()[i] ? m_phimins  .value()[i] : CLHEP::RandFlat::shoot(m_phimins  .value()[i], m_phimaxs  .value()[i]);
-        double costheta = cos(theta*acos(-1)/180);
+
+        double costheta = (m_usecostheta.value()) ? theta : cos(theta*acos(-1)/180);
         double phi_  = phi*acos(-1)/180;
         double sintheta = sqrt(1.-costheta*costheta);
         double px = p*sintheta*cos(phi_);
         double py = p*sintheta*sin(phi_);
         double pz = p*costheta;
-        std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<<theta<<",phi="<<phi<<std::endl;
+        std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<< (m_usecostheta.value()) ? (acos(theta)/acos(-1)*180) : theta<<",phi="<<phi<<std::endl;
         mcp.setMomentum(edm4hep::Vector3f(px,py,pz));
         // mcp.setMomentumAtEndpoint();
         // mcp.setSpin();
diff --git a/Generator/src/GtGunTool.h b/Generator/src/GtGunTool.h
index bc1fcd58..ec193bdf 100644
--- a/Generator/src/GtGunTool.h
+++ b/Generator/src/GtGunTool.h
@@ -57,6 +57,8 @@ private:
     Gaudi::Property<std::vector<double>> m_phimins{this, "PhiMins"};
     Gaudi::Property<std::vector<double>> m_phimaxs{this, "PhiMaxs"};
 
+    Gaudi::Property<bool> m_usecostheta{this, "UseCostheta", false};
+    
     // For time
     Gaudi::Property<std::vector<double>> m_times{this, "Times"};
 
-- 
GitLab


From 98e0fcaaf4c546f9266f41839806c93d7394f395 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Tue, 3 Dec 2024 10:58:55 +0800
Subject: [PATCH 02/12] add use costheta

---
 Generator/src/GtGunTool.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Generator/src/GtGunTool.cpp b/Generator/src/GtGunTool.cpp
index 71206ebf..198f1a5c 100644
--- a/Generator/src/GtGunTool.cpp
+++ b/Generator/src/GtGunTool.cpp
@@ -266,7 +266,7 @@ GtGunTool::mutate(Gen::GenEvent& event) {
         double px = p*sintheta*cos(phi_);
         double py = p*sintheta*sin(phi_);
         double pz = p*costheta;
-        std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<< (m_usecostheta.value()) ? (acos(theta)/acos(-1)*180) : theta<<",phi="<<phi<<std::endl;
+        std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<<theta<<",phi="<<phi<<std::endl;
         mcp.setMomentum(edm4hep::Vector3f(px,py,pz));
         // mcp.setMomentumAtEndpoint();
         // mcp.setSpin();
-- 
GitLab


From 4defa6d87a7792341b0b3bdaddf7318ae41234d1 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Tue, 3 Dec 2024 11:57:20 +0800
Subject: [PATCH 03/12] add use costheta

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 4dd952a9..9f405dfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,6 @@ spack*
 ./Generator/output/
 ./Generator/options/
 
+.vscode/
 InstallArea/
 venv
-- 
GitLab


From e57d5445b37f11293d2b2d5d593e3e6544416459 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Tue, 3 Dec 2024 11:58:04 +0800
Subject: [PATCH 04/12] add use costheta

---
 .gitignore            |  1 -
 .vscode/settings.json | 21 ---------------------
 2 files changed, 22 deletions(-)
 delete mode 100644 .vscode/settings.json

diff --git a/.gitignore b/.gitignore
index 9f405dfe..4dd952a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,5 @@ spack*
 ./Generator/output/
 ./Generator/options/
 
-.vscode/
 InstallArea/
 venv
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 6ad48fa1..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-    "files.associations": {
-        "*.sycl": "cpp",
-        "*.hpp": "cpp",
-        "*.h": "cpp",
-        "*.c": "cpp",
-        "*.cpp": "cpp",
-        "array": "cpp",
-        "bitset": "cpp",
-        "initializer_list": "cpp",
-        "list": "cpp",
-        "random": "cpp",
-        "type_traits": "cpp",
-        "vector": "cpp",
-        "xhash": "cpp",
-        "xstring": "cpp",
-        "xtree": "cpp",
-        "xutility": "cpp",
-        "cmath": "cpp"
-    }
-}
\ No newline at end of file
-- 
GitLab


From 0832639dfce3b11618aed65833e57aef19deceed Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Sun, 8 Dec 2024 12:46:15 +0800
Subject: [PATCH 05/12] add variables

---
 .vscode/settings.json       | 12 ++++++++++++
 Generator/src/GtGunTool.cpp | 39 ++++++++++++++++++++++++-------------
 Generator/src/GtGunTool.h   |  3 ++-
 3 files changed, 39 insertions(+), 15 deletions(-)
 create mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..3ade7b39
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+    "files.associations": {
+        "*.sycl": "cpp",
+        "*.hpp": "cpp",
+        "*.h": "cpp",
+        "*.c": "cpp",
+        "*.cpp": "cpp",
+        "chrono": "cpp",
+        "optional": "cpp",
+        "system_error": "cpp"
+    }
+}
\ No newline at end of file
diff --git a/Generator/src/GtGunTool.cpp b/Generator/src/GtGunTool.cpp
index 198f1a5c..3c953f1b 100644
--- a/Generator/src/GtGunTool.cpp
+++ b/Generator/src/GtGunTool.cpp
@@ -73,17 +73,6 @@ GtGunTool::initialize() {
     }
     
     // others should be empty or specify
-    if (m_thetamins.value().size()
-        && m_thetamins.value().size() != m_particles.value().size()) {
-        error() << "Mismatched thetamins and particles." << endmsg;
-        return StatusCode::FAILURE;
-    }
-    if (m_thetamaxs.value().size()
-        && m_thetamaxs.value().size() != m_particles.value().size()) {
-        error() << "Mismatched thetamaxs and particles." << endmsg;
-        return StatusCode::FAILURE;
-    }
-
     if (m_phimins.value().size()
         && m_phimins.value().size() != m_particles.value().size()) {
         error() << "Mismatched phimins and particles." << endmsg;
@@ -96,6 +85,14 @@ GtGunTool::initialize() {
     }
 
     if (m_usecostheta.value()) {
+        if (m_costhetamins.value().size() != m_particles.value().size()) {
+            error() << "Mismatched CosthetaMins and particles." << endmsg;
+            return StatusCode::FAILURE;
+        }
+        if (m_costhetamaxs.value().size() != m_particles.value().size()) {
+            error() << "Mismatched CosthetaMaxs and particles." << endmsg;
+            return StatusCode::FAILURE;
+        }
         for (int i=0; i<m_thetamins.value().size(); ++i) {
             if ((m_thetamins.value()[i] < -1) || (m_thetamins.value()[i] > 1)) {
                 error() << "UseCostheta: ThetaMins has values outside the range [-1, 1]." << endmsg;
@@ -106,6 +103,17 @@ GtGunTool::initialize() {
                 return StatusCode::FAILURE;
             }
         }
+    } else {
+        if (m_thetamins.value().size()
+            && m_thetamins.value().size() != m_particles.value().size()) {
+            error() << "Mismatched thetamins and particles." << endmsg;
+            return StatusCode::FAILURE;
+        }
+        if (m_thetamaxs.value().size()
+            && m_thetamaxs.value().size() != m_particles.value().size()) {
+            error() << "Mismatched thetamaxs and particles." << endmsg;
+            return StatusCode::FAILURE;
+        }
     }
 
     // Time
@@ -256,11 +264,14 @@ GtGunTool::mutate(Gen::GenEvent& event) {
             return false;
         }
 
-
-        double theta = m_thetamins.value()[i]==m_thetamaxs.value()[i] ? m_thetamins.value()[i] : CLHEP::RandFlat::shoot(m_thetamins.value()[i], m_thetamaxs.value()[i]);
+        if (m_usecostheta.value()) {
+            double costheta = m_costhetamins.value()[i]==m_costhetamaxs.value()[i] ? m_costhetamins.value()[i] : CLHEP::RandFlat::shoot(m_costhetamins.value()[i], m_costhetamaxs.value()[i]);
+        } else {
+            double theta = m_thetamins.value()[i]==m_thetamaxs.value()[i] ? m_thetamins.value()[i] : CLHEP::RandFlat::shoot(m_thetamins.value()[i], m_thetamaxs.value()[i]);
+            double costheta = cos(theta*acos(-1)/180);
+        }
         double phi =   m_phimins  .value()[i]==m_phimaxs  .value()[i] ? m_phimins  .value()[i] : CLHEP::RandFlat::shoot(m_phimins  .value()[i], m_phimaxs  .value()[i]);
 
-        double costheta = (m_usecostheta.value()) ? theta : cos(theta*acos(-1)/180);
         double phi_  = phi*acos(-1)/180;
         double sintheta = sqrt(1.-costheta*costheta);
         double px = p*sintheta*cos(phi_);
diff --git a/Generator/src/GtGunTool.h b/Generator/src/GtGunTool.h
index ec193bdf..fb43164e 100644
--- a/Generator/src/GtGunTool.h
+++ b/Generator/src/GtGunTool.h
@@ -58,7 +58,8 @@ private:
     Gaudi::Property<std::vector<double>> m_phimaxs{this, "PhiMaxs"};
 
     Gaudi::Property<bool> m_usecostheta{this, "UseCostheta", false};
-    
+    Gaudi::Property<std::vector<double>> m_costhetamins{this, "CosthetaMins"};
+    Gaudi::Property<std::vector<double>> m_costhetamaxs{this, "CosthetaMaxs"};
     // For time
     Gaudi::Property<std::vector<double>> m_times{this, "Times"};
 
-- 
GitLab


From 94ca44c470766a5ab6ec1b2ac610a456584bfc93 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Sun, 8 Dec 2024 12:49:48 +0800
Subject: [PATCH 06/12] add variables

---
 .vscode/settings.json | 12 ------------
 1 file changed, 12 deletions(-)
 delete mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 3ade7b39..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "files.associations": {
-        "*.sycl": "cpp",
-        "*.hpp": "cpp",
-        "*.h": "cpp",
-        "*.c": "cpp",
-        "*.cpp": "cpp",
-        "chrono": "cpp",
-        "optional": "cpp",
-        "system_error": "cpp"
-    }
-}
\ No newline at end of file
-- 
GitLab


From da5637f74e6f5ab8780897784e2a3b996f765c5a Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Sun, 8 Dec 2024 12:56:29 +0800
Subject: [PATCH 07/12] add variables

---
 .vscode/settings.json       | 13 +++++++++++++
 Generator/src/GtGunTool.cpp | 16 +++++++++++-----
 2 files changed, 24 insertions(+), 5 deletions(-)
 create mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..257d1d17
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+    "files.associations": {
+        "*.sycl": "cpp",
+        "*.hpp": "cpp",
+        "*.h": "cpp",
+        "*.c": "cpp",
+        "*.cpp": "cpp",
+        "cmath": "cpp",
+        "chrono": "cpp",
+        "optional": "cpp",
+        "system_error": "cpp"
+    }
+}
\ No newline at end of file
diff --git a/Generator/src/GtGunTool.cpp b/Generator/src/GtGunTool.cpp
index 3c953f1b..8ab4ce2e 100644
--- a/Generator/src/GtGunTool.cpp
+++ b/Generator/src/GtGunTool.cpp
@@ -264,20 +264,26 @@ GtGunTool::mutate(Gen::GenEvent& event) {
             return false;
         }
 
+        double costheta = 0;
+        double theta = 0;
         if (m_usecostheta.value()) {
-            double costheta = m_costhetamins.value()[i]==m_costhetamaxs.value()[i] ? m_costhetamins.value()[i] : CLHEP::RandFlat::shoot(m_costhetamins.value()[i], m_costhetamaxs.value()[i]);
+            costheta = m_costhetamins.value()[i]==m_costhetamaxs.value()[i] ? m_costhetamins.value()[i] : CLHEP::RandFlat::shoot(m_costhetamins.value()[i], m_costhetamaxs.value()[i]);
         } else {
-            double theta = m_thetamins.value()[i]==m_thetamaxs.value()[i] ? m_thetamins.value()[i] : CLHEP::RandFlat::shoot(m_thetamins.value()[i], m_thetamaxs.value()[i]);
-            double costheta = cos(theta*acos(-1)/180);
+            theta = m_thetamins.value()[i]==m_thetamaxs.value()[i] ? m_thetamins.value()[i] : CLHEP::RandFlat::shoot(m_thetamins.value()[i], m_thetamaxs.value()[i]);
+            costheta = cos(theta*acos(-1)/180);
         }
-        double phi =   m_phimins  .value()[i]==m_phimaxs  .value()[i] ? m_phimins  .value()[i] : CLHEP::RandFlat::shoot(m_phimins  .value()[i], m_phimaxs  .value()[i]);
+        double phi =   m_phimins.value()[i]==m_phimaxs.value()[i] ? m_phimins.value()[i] : CLHEP::RandFlat::shoot(m_phimins.value()[i], m_phimaxs.value()[i]);
 
         double phi_  = phi*acos(-1)/180;
         double sintheta = sqrt(1.-costheta*costheta);
         double px = p*sintheta*cos(phi_);
         double py = p*sintheta*sin(phi_);
         double pz = p*costheta;
-        std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<<theta<<",phi="<<phi<<std::endl;
+        if(m_usecostheta.value()) {
+            std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",costheta="<<costheta<<",phi="<<phi<<std::endl;
+        } else {
+            std::cout<<"GenGt p="<<p<<", px="<<px<<",py="<<py<<",pz="<<pz<<",theta="<<theta<<",phi="<<phi<<std::endl;
+        }
         mcp.setMomentum(edm4hep::Vector3f(px,py,pz));
         // mcp.setMomentumAtEndpoint();
         // mcp.setSpin();
-- 
GitLab


From 3f36769cd29dd6df3ebebb5756d29bf5ba9d6496 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Sun, 8 Dec 2024 12:57:12 +0800
Subject: [PATCH 08/12] add variables

---
 .vscode/settings.json | 13 -------------
 1 file changed, 13 deletions(-)
 delete mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 257d1d17..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "files.associations": {
-        "*.sycl": "cpp",
-        "*.hpp": "cpp",
-        "*.h": "cpp",
-        "*.c": "cpp",
-        "*.cpp": "cpp",
-        "cmath": "cpp",
-        "chrono": "cpp",
-        "optional": "cpp",
-        "system_error": "cpp"
-    }
-}
\ No newline at end of file
-- 
GitLab


From 916bee6c374b5f1e8dca3db32d9dbffa9c4a4082 Mon Sep 17 00:00:00 2001
From: stch-zhangyzh <zhangyzh@shanghaitech.edu.cn>
Date: Tue, 21 Jan 2025 13:43:23 +0800
Subject: [PATCH 09/12] fix FTD bug

---
 .../RecActsTracking/src/RecActsTracking.cpp   |  4 ++--
 .../RecActsTracking/src/RecActsTracking.h     | 21 ++++++++++---------
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/Reconstruction/RecActsTracking/src/RecActsTracking.cpp b/Reconstruction/RecActsTracking/src/RecActsTracking.cpp
index 40562fee..ba125e91 100644
--- a/Reconstruction/RecActsTracking/src/RecActsTracking.cpp
+++ b/Reconstruction/RecActsTracking/src/RecActsTracking.cpp
@@ -341,10 +341,10 @@ StatusCode RecActsTracking::execute()
         Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
         for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
             double sigma = initialSigmas[i];
-            sigma += initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP];
+            sigma += abs(initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP]);
             double var = sigma * sigma;
             if (i == Acts::eBoundTime && !bottomSP->t().has_value()) { var *= noTimeVarInflation; }
-            var *= initialVarInflation[i];
+            var *= initialVarInflation.value()[i];
             
             cov(i, i) = var;
         }
diff --git a/Reconstruction/RecActsTracking/src/RecActsTracking.h b/Reconstruction/RecActsTracking/src/RecActsTracking.h
index afc66cd7..f7d44417 100644
--- a/Reconstruction/RecActsTracking/src/RecActsTracking.h
+++ b/Reconstruction/RecActsTracking/src/RecActsTracking.h
@@ -195,6 +195,7 @@ class RecActsTracking : public GaudiAlgorithm
         Gaudi::Property<double> SeedImpactMax{this, "SeedImpactMax", 3}; // mm
         Gaudi::Property<double> SeedRMinMiddle{this, "SeedRMinMiddle", 14}; // mm
         Gaudi::Property<double> SeedRMaxMiddle{this, "SeedRMaxMiddle", 24}; // mm
+        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 20, 20, 20, 20}};
 
         // CKF config
         Gaudi::Property<double> CKFchi2Cut{this, "CKFchi2Cut", std::numeric_limits<double>::max()};
@@ -259,20 +260,20 @@ class RecActsTracking : public GaudiAlgorithm
         // param estimate configuration
         double noTimeVarInflation = 100.;
         std::array<double, 6> initialSigmas = {
-            25 * Acts::UnitConstants::um,
-            100 * Acts::UnitConstants::um,
-            0.02 * Acts::UnitConstants::degree,
-            0.02 * Acts::UnitConstants::degree,
-            0.1 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
-            1400 * Acts::UnitConstants::s};
+            5 * Acts::UnitConstants::um,
+            5 * Acts::UnitConstants::um,
+            2e-2 * Acts::UnitConstants::degree,
+            2e-2 * Acts::UnitConstants::degree,
+            1e-1 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
+            1 * Acts::UnitConstants::s};
         std::array<double, 6> initialSimgaQoverPCoefficients = {
             0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
             0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0.1,
+            7.1e-2 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            2.1e-2 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            6.4e-2,
             0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)};
-        std::array<double, 6> initialVarInflation = {10., 10., 10., 10., 10., 10.};
+        // std::array<double, 6> initialVarInflation = {10., 10., 10., 10., 10., 10.};
         Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
         std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
         // Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
-- 
GitLab


From 2260ed89fc0bb99a066e6e739bd1e71ec5b67952 Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Thu, 5 Jun 2025 10:11:02 +0800
Subject: [PATCH 10/12] update ACTS for tdr25.5

---
 .../RecActsTracking/ActsHelper/CMakeLists.txt |   55 +
 .../TrackFitting/RefittingCalibrator.hpp      |   35 +
 .../TrackFitting/TrackFitterFunction.hpp      |  112 ++
 .../ActsHelper/Detectors/TGeoDetector.hpp     |  179 ++
 .../ActsHelper/EventData/AverageSimHits.hpp   |   84 +
 .../include/ActsHelper/EventData/Cluster.hpp  |   20 +
 .../ActsHelper/EventData/DriftCircle.hpp      |   87 +
 .../EventData/ExtractedSimulationProcess.hpp  |   19 +
 .../EventData/GeometryContainers.hpp          |  224 +++
 .../include/ActsHelper/EventData/Index.hpp    |   53 +
 .../ActsHelper/EventData/IndexSourceLink.hpp  |   86 +
 .../ActsHelper/EventData/Measurement.hpp      |   19 +
 .../EventData/MeasurementCalibration.hpp      |   69 +
 .../ActsHelper/EventData/MuonSimHit.hpp       |   62 +
 .../ActsHelper/EventData/ProtoTrack.hpp       |   16 +
 .../ActsHelper/EventData/ProtoVertex.hpp      |   14 +
 .../EventData/ScalingCalibrator.hpp           |   57 +
 .../include/ActsHelper/EventData/SimHit.hpp   |   18 +
 .../ActsHelper/EventData/SimParticle.hpp      |   67 +
 .../include/ActsHelper/EventData/SimSeed.hpp  |   14 +
 .../ActsHelper/EventData/SimSpacePoint.hpp    |  118 ++
 .../ActsHelper/EventData/SimVertex.hpp        |  130 ++
 .../include/ActsHelper/EventData/Track.hpp    |   30 +
 .../ActsHelper/EventData/Trajectories.hpp     |  104 ++
 .../ActsHelper/EventData/TruthMatching.hpp    |   47 +
 .../include/ActsHelper/EventData/Vertex.hpp   |   12 +
 .../MagneticField/FieldMapRootIo.hpp          |  103 ++
 .../MagneticField/FieldMapTextIo.hpp          |  105 ++
 .../MagneticField/MagneticField.hpp           |   29 +
 .../MagneticField/ScalableBField.hpp          |  104 ++
 .../Utilities/EventDataTransforms.hpp         |   16 +
 .../include/ActsHelper/Utilities/GroupBy.hpp  |  133 ++
 .../include/ActsHelper/Utilities/Helpers.hpp  |  138 ++
 .../include/ActsHelper/Utilities}/Options.hpp |    6 +-
 .../ActsHelper/Utilities/OptionsFwd.hpp       |   11 +
 .../include/ActsHelper/Utilities/Paths.hpp    |   39 +
 .../include/ActsHelper/Utilities/Range.hpp    |   48 +
 .../include/ActsHelper/Utilities/tbbWrap.hpp  |  166 ++
 .../Validation/DuplicationPlotTool.hpp        |   96 +
 .../ActsHelper/Validation/EffPlotTool.hpp     |   81 +
 .../Validation/FakeRatePlotTool.hpp           |  102 ++
 .../ActsHelper/Validation/ResPlotTool.hpp     |  122 ++
 .../Validation/TrackClassification.hpp        |   57 +
 .../Validation/TrackSummaryPlotTool.hpp       |   88 +
 .../GlobalChiSquareFitterFunction.cpp         |  157 ++
 .../TrackFitting/GsfFitterFunction.cpp        |  220 +++
 .../TrackFitting/KalmanFitterFunction.cpp     |  182 ++
 .../TrackFitting/RefittingCalibrator.cpp      |   35 +
 .../src/Detectors/TGeoDetector.cpp}           |  168 +-
 .../src/EventData/MeasurementCalibration.cpp  |   43 +
 .../src/EventData/ScalingCalibrator.cpp       |  174 ++
 .../src/MagneticField/FieldMapRootIo.cpp      |  117 ++
 .../src/MagneticField/FieldMapTextIo.cpp      |   99 ++
 .../src/Utilities/EventDataTransforms.cpp     |   74 +
 .../ActsHelper/src/Utilities/Helpers.cpp      |  101 ++
 .../src/Utilities}/Options.cpp                |   30 +-
 .../ActsHelper/src/Utilities/Paths.cpp        |  108 ++
 .../src/Validation/DuplicationPlotTool.cpp    |  105 ++
 .../ActsHelper/src/Validation/EffPlotTool.cpp |   66 +
 .../src/Validation/FakeRatePlotTool.cpp       |  120 ++
 .../ActsHelper/src/Validation/ResPlotTool.cpp |  249 +++
 .../src/Validation/TrackClassification.cpp    |  106 ++
 .../src/Validation/TrackSummaryPlotTool.cpp   |  110 ++
 Reconstruction/RecActsTracking/CMakeLists.txt |   40 +-
 .../RecActsTracking/RecActsSvc/CMakeLists.txt |   34 +
 .../include/RecActsSvc/IRecActsSvc.h          |   98 ++
 .../RecActsSvc/src/RecActsSvc.cpp             |  251 +++
 .../RecActsSvc/src/RecActsSvc.h               |   83 +
 .../RecActsSvc/src/csv2/mio.hpp               | 1562 +++++++++++++++++
 .../RecActsSvc/src/csv2/parameters.hpp        |   50 +
 .../RecActsSvc/src/csv2/reader.hpp            |  306 ++++
 .../RecActsSvc/src/csv2/writer.hpp            |   38 +
 .../RecActsTracking/CMakeLists.txt            |   46 +
 .../options/RecActsTracking.py                |  143 ++
 .../RecActsTracking/src/RecActsReadInput.cpp  |  447 +++++
 .../RecActsTracking/src/RecActsReadInput.h    |  103 ++
 .../RecActsTracking/src/RecActsSeeding.cpp    |  169 ++
 .../RecActsTracking/src/RecActsSeeding.h      |   84 +
 .../src/RecActsTrackFinding.cpp               |  451 +++++
 .../RecActsTracking/src/RecActsTrackFinding.h |  378 ++++
 .../src/RecActsTrackFitting.cpp               |  144 ++
 .../RecActsTracking/src/RecActsTrackFitting.h |  194 ++
 .../src/RecActsTrackParamsEstimation.cpp      |  134 ++
 .../src/RecActsTrackParamsEstimation.h        |   80 +
 .../src/RecActsTrackReFitting.cpp             |  401 +++++
 .../src/RecActsTrackReFitting.h               |  153 ++
 .../RecActsTracking/src/RecActsTruthInput.cpp |  473 +++++
 .../RecActsTracking/src/RecActsTruthInput.h   |  100 ++
 .../src/RecActsTruthTracking.cpp              |  796 +++++++++
 .../src/RecActsTruthTracking.h                |  183 ++
 .../options/RecActsTracking.py                |   92 -
 .../RecActsTracking/src/RecActsTracking.cpp   | 1208 -------------
 .../RecActsTracking/src/RecActsTracking.h     |  320 ----
 .../RecActsTracking/src/utils/CKFhelper.hpp   |  388 ----
 .../src/utils/GeometryContainers.hpp          |  355 ----
 .../src/utils/MagneticField.hpp               |  121 --
 .../src/utils/SimSpacePoint.hpp               |  243 ---
 97 files changed, 12157 insertions(+), 2950 deletions(-)
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/CMakeLists.txt
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Detectors/TGeoDetector.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/AverageSimHits.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Cluster.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/DriftCircle.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ExtractedSimulationProcess.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/GeometryContainers.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Index.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/IndexSourceLink.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Measurement.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MeasurementCalibration.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MuonSimHit.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoTrack.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoVertex.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ScalingCalibrator.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimHit.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimParticle.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSeed.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSpacePoint.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimVertex.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Track.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Trajectories.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/TruthMatching.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Vertex.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapRootIo.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapTextIo.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/MagneticField.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/ScalableBField.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/EventDataTransforms.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/GroupBy.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Helpers.hpp
 rename Reconstruction/RecActsTracking/{src/utils => ActsHelper/include/ActsHelper/Utilities}/Options.hpp (98%)
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/OptionsFwd.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Paths.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Range.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/tbbWrap.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/DuplicationPlotTool.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/EffPlotTool.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/FakeRatePlotTool.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/ResPlotTool.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackClassification.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackSummaryPlotTool.hpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GlobalChiSquareFitterFunction.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GsfFitterFunction.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/KalmanFitterFunction.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/RefittingCalibrator.cpp
 rename Reconstruction/RecActsTracking/{src/utils/TGeoDetector.hpp => ActsHelper/src/Detectors/TGeoDetector.cpp} (77%)
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/EventData/MeasurementCalibration.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/EventData/ScalingCalibrator.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapRootIo.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapTextIo.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Utilities/EventDataTransforms.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Helpers.cpp
 rename Reconstruction/RecActsTracking/{src/utils => ActsHelper/src/Utilities}/Options.cpp (82%)
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Paths.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/DuplicationPlotTool.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/EffPlotTool.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/FakeRatePlotTool.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/ResPlotTool.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackClassification.cpp
 create mode 100644 Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackSummaryPlotTool.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/CMakeLists.txt
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/include/RecActsSvc/IRecActsSvc.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/csv2/mio.hpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/csv2/parameters.hpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/csv2/reader.hpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsSvc/src/csv2/writer.hpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/options/RecActsTracking.py
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
 create mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h
 delete mode 100644 Reconstruction/RecActsTracking/options/RecActsTracking.py
 delete mode 100644 Reconstruction/RecActsTracking/src/RecActsTracking.cpp
 delete mode 100644 Reconstruction/RecActsTracking/src/RecActsTracking.h
 delete mode 100644 Reconstruction/RecActsTracking/src/utils/CKFhelper.hpp
 delete mode 100644 Reconstruction/RecActsTracking/src/utils/GeometryContainers.hpp
 delete mode 100644 Reconstruction/RecActsTracking/src/utils/MagneticField.hpp
 delete mode 100644 Reconstruction/RecActsTracking/src/utils/SimSpacePoint.hpp

diff --git a/Reconstruction/RecActsTracking/ActsHelper/CMakeLists.txt b/Reconstruction/RecActsTracking/ActsHelper/CMakeLists.txt
new file mode 100644
index 00000000..c6ff43b8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/CMakeLists.txt
@@ -0,0 +1,55 @@
+find_package(Acts COMPONENTS
+    Core PluginFpeMonitoring PluginGeant4 PluginJson
+    PluginTGeo PluginDD4hep PluginEDM4hep Fatras)
+
+if(NOT Acts_FOUND)
+    message("Acts package not found. RecActsSvc module requires Acts.")
+    return()
+endif()
+
+gaudi_add_library(ActsHelperLib
+                  SOURCES
+                    src/Detectors/TGeoDetector.cpp
+
+                    src/EventData/MeasurementCalibration.cpp
+                    src/EventData/ScalingCalibrator.cpp
+
+                    src/MagneticField/FieldMapRootIo.cpp
+                    src/MagneticField/FieldMapTextIo.cpp
+
+                    src/Utilities/EventDataTransforms.cpp
+                    src/Utilities/Paths.cpp
+                    src/Utilities/Options.cpp
+                    src/Utilities/Helpers.cpp
+
+                    src/Validation/DuplicationPlotTool.cpp
+                    src/Validation/EffPlotTool.cpp
+                    src/Validation/FakeRatePlotTool.cpp
+                    src/Validation/ResPlotTool.cpp
+                    src/Validation/TrackClassification.cpp
+                    src/Validation/TrackSummaryPlotTool.cpp
+
+                    src/Algorithms/TrackFitting/KalmanFitterFunction.cpp
+                    src/Algorithms/TrackFitting/GsfFitterFunction.cpp
+                    src/Algorithms/TrackFitting/GlobalChiSquareFitterFunction.cpp
+                    src/Algorithms/TrackFitting/RefittingCalibrator.cpp
+
+                  LINK
+                    EDM4HEP::edm4hep EDM4HEP::edm4hepDict
+                    ActsCore ActsPluginFpeMonitoring ActsPluginGeant4
+                    ActsPluginJson ActsPluginTGeo  ActsPluginDD4hep
+                    ActsPluginEDM4hep ActsFatras
+)
+
+target_include_directories(ActsHelperLib PUBLIC $ENV{ACTS}/include)
+
+target_include_directories(ActsHelperLib PUBLIC
+  ${LCIO_INCLUDE_DIRS}
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>/include
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
+  
+install(TARGETS ActsHelperLib
+  EXPORT CEPCSWTargets
+  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib
+  COMPONENT dev)
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp
new file mode 100644
index 00000000..952e252d
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/CalibrationContext.hpp"
+
+namespace Acts {
+class ConstVectorMultiTrajectory;
+class VectorMultiTrajectory;
+}  // namespace Acts
+
+namespace ActsHelper {
+
+struct RefittingCalibrator {
+  using Proxy = Acts::VectorMultiTrajectory::TrackStateProxy;
+  using ConstProxy = Acts::ConstVectorMultiTrajectory::ConstTrackStateProxy;
+
+  struct RefittingSourceLink {
+    ConstProxy state;
+
+    Acts::GeometryIdentifier geometryId() const {
+      return state.referenceSurface().geometryId();
+    }
+  };
+
+  void calibrate(const Acts::GeometryContext& gctx,
+                 const Acts::CalibrationContext& cctx,
+                 const Acts::SourceLink& sourceLink, Proxy trackState) const;
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp
new file mode 100644
index 00000000..ce1eb08c
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/MagneticField/MagneticFieldContext.hpp"
+#include "Acts/MagneticField/MagneticFieldProvider.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/BetheHeitlerApprox.hpp"
+#include "Acts/TrackFitting/GsfOptions.hpp"
+#include "Acts/Utilities/CalibrationContext.hpp"
+#include "ActsHelper/EventData/Measurement.hpp"
+#include "ActsHelper/EventData/MeasurementCalibration.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+
+namespace ActsHelper {
+
+/// Fit function that takes the above parameters and runs a fit
+/// @note This is separated into a virtual interface to keep compilation units
+/// small.
+class TrackFitterFunction {
+ public:
+  using TrackFitterResult = Acts::Result<TrackContainer::TrackProxy>;
+
+  struct GeneralFitterOptions {
+    std::reference_wrapper<const Acts::GeometryContext> geoContext;
+    std::reference_wrapper<const Acts::MagneticFieldContext> magFieldContext;
+    std::reference_wrapper<const Acts::CalibrationContext> calibrationContext;
+    const Acts::Surface* referenceSurface = nullptr;
+    Acts::PropagatorPlainOptions propOptions;
+  };
+
+  virtual ~TrackFitterFunction() = default;
+
+  virtual TrackFitterResult operator()(const std::vector<Acts::SourceLink>&,
+                                       const TrackParameters&,
+                                       const GeneralFitterOptions&,
+                                       const MeasurementCalibratorAdapter&,
+                                       TrackContainer&) const = 0;
+
+  virtual TrackFitterResult operator()(const std::vector<Acts::SourceLink>&,
+                                       const TrackParameters&,
+                                       const GeneralFitterOptions&,
+                                       const RefittingCalibrator&,
+                                       const std::vector<const Acts::Surface*>&,
+                                       TrackContainer&) const = 0;
+};
+
+/// Makes a fitter function object for the Kalman Filter
+///
+std::shared_ptr<TrackFitterFunction> makeKalmanFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    bool multipleScattering = true, bool energyLoss = true,
+    double reverseFilteringMomThreshold = 0.0,
+    Acts::FreeToBoundCorrection freeToBoundCorrection =
+        Acts::FreeToBoundCorrection(),
+    const Acts::Logger& logger = *Acts::getDefaultLogger("Kalman",
+                                                         Acts::Logging::INFO));
+
+/// This type is used in the Examples framework for the Bethe-Heitler
+/// approximation
+using BetheHeitlerApprox = Acts::AtlasBetheHeitlerApprox<6, 5>;
+
+/// Available algorithms for the mixture reduction
+enum class MixtureReductionAlgorithm { weightCut, KLDistance };
+
+/// Makes a fitter function object for the GSF
+///
+/// @param trackingGeometry the trackingGeometry for the propagator
+/// @param magneticField the magnetic field for the propagator
+/// @param betheHeitlerApprox The object that encapsulates the approximation.
+/// @param maxComponents number of maximum components in the track state
+/// @param weightCutoff when to drop components
+/// @param componentMergeMethod How to merge a mixture to a single set of
+/// parameters and covariance
+/// @param mixtureReductionAlgorithm How to reduce the number of components
+/// in a mixture
+/// @param logger a logger instance
+std::shared_ptr<TrackFitterFunction> makeGsfFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    BetheHeitlerApprox betheHeitlerApprox, std::size_t maxComponents,
+    double weightCutoff, Acts::ComponentMergeMethod componentMergeMethod,
+    MixtureReductionAlgorithm mixtureReductionAlgorithm,
+    const Acts::Logger& logger);
+
+/// Makes a fitter function object for the Global Chi Square Fitter (GX2F)
+///
+/// @param trackingGeometry the trackingGeometry for the propagator
+/// @param magneticField the magnetic field for the propagator
+/// @param multipleScattering bool
+/// @param energyLoss bool
+/// @param freeToBoundCorrection bool
+/// @param nUpdateMax max number of iterations during the fit
+/// @param relChi2changeCutOff Check for convergence (abort condition). Set to 0 to skip.
+/// @param logger a logger instance
+std::shared_ptr<TrackFitterFunction> makeGlobalChiSquareFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    bool multipleScattering = true, bool energyLoss = true,
+    Acts::FreeToBoundCorrection freeToBoundCorrection =
+        Acts::FreeToBoundCorrection(),
+    std::size_t nUpdateMax = 5, double relChi2changeCutOff = 1e-7,
+    const Acts::Logger& logger = *Acts::getDefaultLogger("Gx2f",
+                                                         Acts::Logging::INFO));
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Detectors/TGeoDetector.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Detectors/TGeoDetector.hpp
new file mode 100644
index 00000000..98c9c5d8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Detectors/TGeoDetector.hpp
@@ -0,0 +1,179 @@
+#ifndef ACTSHELPER_TGEODETECTOR_HPP
+#define ACTSHELPER_TGEODETECTOR_HPP
+
+#include "Acts/Geometry/CylinderVolumeBuilder.hpp"
+#include "Acts/Geometry/CylinderVolumeHelper.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/ITrackingVolumeBuilder.hpp"
+#include "Acts/Geometry/LayerArrayCreator.hpp"
+#include "Acts/Geometry/LayerCreator.hpp"
+#include "Acts/Geometry/PassiveLayerBuilder.hpp"
+#include "Acts/Geometry/ProtoLayerHelper.hpp"
+#include "Acts/Geometry/SurfaceArrayCreator.hpp"
+#include "Acts/Geometry/SurfaceBinningMatcher.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Geometry/TrackingGeometryBuilder.hpp"
+#include "Acts/Geometry/TrackingVolumeArrayCreator.hpp"
+
+#include "Acts/Plugins/TGeo/TGeoCylinderDiscSplitter.hpp"
+#include "Acts/Plugins/TGeo/TGeoLayerBuilder.hpp"
+#include "Acts/Plugins/Json/JsonMaterialDecorator.hpp"
+#include "Acts/Plugins/Json/ActsJson.hpp"
+
+#include "Acts/Utilities/BinningType.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+#include <boost/program_options.hpp>
+#include <nlohmann/json.hpp>
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+#include <list>
+
+namespace Acts
+{
+    class TGeoDetectorElement;
+    class TrackingGeometry;
+    class IMaterialDecorator;
+}
+
+/// ----------------------------
+///        pre definitions 
+/// ----------------------------
+
+/// Half open [lower,upper) interval type for a single user option.
+///
+/// A missing limit represents an unbounded upper or lower limit. With just
+/// one defined limit the interval is just a lower/upper bound; with both
+/// limits undefined, the interval is unbounded everywhere and thus contains
+/// all possible values.
+struct Interval
+{
+    std::optional<double> lower;
+    std::optional<double> upper;
+};
+
+/// Extract an interval from an input of the form 'lower:upper'.
+///
+/// An input of the form `lower:` or `:upper` sets just one of the limits. Any
+/// other input leads to an unbounded interval.
+///
+/// @note The more common range notation uses `lower-upper` but the `-`
+///   separator complicates the parsing of negative values.
+std::istream& operator>>(std::istream& is, Interval& interval);
+
+/// Print an interval as `lower:upper`.
+std::ostream& operator<<(std::ostream& os, const Interval& interval);
+
+struct TGeoConfig {
+    Acts::Logging::Level surfaceLogLevel = Acts::Logging::WARNING;
+    Acts::Logging::Level layerLogLevel   = Acts::Logging::WARNING;
+    Acts::Logging::Level volumeLogLevel  = Acts::Logging::WARNING;
+
+    std::string fileName;
+    bool buildBeamPipe = false;
+    double beamPipeRadius{0};
+    double beamPipeHalflengthZ{0};
+    double beamPipeLayerThickness{0};
+    double beamPipeEnvelopeR{1.0};
+    double layerEnvelopeR{0.2};
+
+    double unitScalor = 1.0;
+
+    Acts::TGeoLayerBuilder::ElementFactory elementFactory =
+        Acts::TGeoLayerBuilder::defaultElementFactory;
+
+    /// Optional geometry identifier hook to be used during closure
+    std::shared_ptr<const Acts::GeometryIdentifierHook> geometryIdentifierHook =
+    std::make_shared<Acts::GeometryIdentifierHook>();
+
+    enum SubVolume : std::size_t { Negative = 0, Central, Positive };
+
+    template <typename T>
+    struct LayerTriplet
+    {
+        LayerTriplet() = default;
+
+        LayerTriplet(T value)
+            : negative{value}, central{value}, positive{value} {}
+
+        LayerTriplet(T _negative, T _central, T _positive)
+            : negative{_negative}, central{_central}, positive{_positive} {}
+
+        T negative;
+        T central;
+        T positive;
+
+        T& at(SubVolume i)
+        {
+            switch (i)
+            {
+                case Negative: return negative;
+                case Central:  return central;
+                case Positive: return positive;
+                default: throw std::invalid_argument{"Unknown index"};
+            }
+        }
+
+        const T& at(SubVolume i) const
+        {
+            switch (i)
+            {
+                case Negative: return negative;
+                case Central: return central;
+                case Positive: return positive;
+                default: throw std::invalid_argument{"Unknown index"};
+            }
+        }
+    };
+
+    struct Volume {
+        std::string name;
+        LayerTriplet<bool> layers{false};
+        LayerTriplet<std::string> subVolumeName;
+        LayerTriplet<std::vector<std::string>> sensitiveNames;
+        LayerTriplet<std::string> sensitiveAxes;
+        LayerTriplet<Interval> rRange;
+        LayerTriplet<Interval> zRange;
+        LayerTriplet<double> splitTolR{0};
+        LayerTriplet<double> splitTolZ{0};
+        LayerTriplet<std::vector<std::pair<int, Acts::BinningType>>> binning0;
+        LayerTriplet<std::vector<std::pair<int, Acts::BinningType>>> binning1;
+
+        Interval binToleranceR;
+        Interval binTolerancePhi;
+        Interval binToleranceZ;
+
+        bool cylinderDiscSplit = false;
+        unsigned int cylinderNZSegments = 0;
+        unsigned int cylinderNPhiSegments = 0;
+        unsigned int discNRSegments = 0;
+        unsigned int discNPhiSegments = 0;
+
+        bool itkModuleSplit = false;
+        std::map<std::string, unsigned int> barrelMap;
+        std::map<std::string, std::vector<std::pair<double, double>>> discMap;
+        /// pairs of regular expressions to match sensor names and category keys
+        /// for either the barrelMap or the discMap
+        /// @TODO in principle vector<pair< > > would be good enough
+        std::map<std::string, std::string> splitPatterns;
+    };
+
+    std::vector<Volume> volumes;
+};
+
+std::shared_ptr<const Acts::TrackingGeometry> buildTGeoDetector(
+    const Acts::GeometryContext& context,
+    std::vector<std::shared_ptr<const Acts::TGeoDetectorElement>>& detElementStore,
+    const std::string& TGeo_ROOTFilePath,
+    const std::string& TGeoConfig_jFilePath,
+    const std::string& MaterialMap_jFilePath,
+    const Acts::Logger& logger);
+    
+#endif // ACTSHELPER_TGEODETECTOR_HPP
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/AverageSimHits.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/AverageSimHits.hpp
new file mode 100644
index 00000000..3511cf15
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/AverageSimHits.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/Definitions/Units.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/SimHit.hpp"
+#include "ActsHelper/Utilities/Range.hpp"
+
+#include <tuple>
+
+namespace ActsHelper {
+
+/// A range within a hit-simhits map.
+using HitSimHitsRange = Range<IndexMultimap<Index>::const_iterator>;
+
+/// Create (average) truth representation for selected simulated hits.
+///
+/// @param gCtx The geometry context for this
+/// @param surface The reference surface of the measurement
+/// @param simHits The simulated hits container
+/// @param hitSimHitsRange Selection of simulated hits from the container
+/// @return a local position, a 4D global position, a direction
+///
+/// If more than one simulated hit is selected, the average truth information is
+/// returned.
+inline std::tuple<Acts::Vector2, Acts::Vector4, Acts::Vector3> averageSimHits(
+    const Acts::GeometryContext& gCtx, const Acts::Surface& surface,
+    const SimHitContainer& simHits, const HitSimHitsRange& hitSimHitsRange,
+    const Acts::Logger& logger) {
+  using namespace Acts::UnitLiterals;
+
+  Acts::Vector2 avgLocal = Acts::Vector2::Zero();
+  Acts::Vector4 avgPos4 = Acts::Vector4::Zero();
+  Acts::Vector3 avgDir = Acts::Vector3::Zero();
+
+  std::size_t n = 0u;
+  for (auto [_, simHitIdx] : hitSimHitsRange) {
+    n += 1u;
+
+    // we assume that the indices are within valid ranges so we do not need to
+    // check their validity again.
+    const auto& simHit = *simHits.nth(simHitIdx);
+
+    // We use the thickness of the detector element as tolerance, because Geant4
+    // treats the Surfaces as volumes and thus it is not ensured, that each hit
+    // lies exactly on the Acts::Surface
+    const auto tolerance =
+        surface.associatedDetectorElement() != nullptr
+            ? surface.associatedDetectorElement()->thickness()
+            : Acts::s_onSurfaceTolerance;
+
+    // transforming first to local positions and average that ensures that the
+    // averaged position is still on the surface. the averaged global position
+    // might not be on the surface anymore.
+    auto result = surface.globalToLocal(gCtx, simHit.position(),
+                                        simHit.direction(), tolerance);
+    if (result.ok()) {
+      avgLocal += result.value();
+    } else {
+      ACTS_WARNING("While averaging simhit, hit "
+                   << simHitIdx << " is not on the corresponding surface "
+                   << surface.geometryId() << "; use [0,0] as local position");
+    }
+    // global position should already be at the intersection. no need to perform
+    // an additional intersection call.
+    avgPos4 += simHit.fourPosition();
+    avgDir += simHit.direction();
+  }
+
+  // only need to average if there are at least two inputs
+  if (2u <= n) {
+    double scale = 1.0 / n;
+    avgLocal *= scale;
+    avgPos4 *= scale;
+    avgDir.normalize();
+  }
+
+  return {avgLocal, avgPos4, avgDir};
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Cluster.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Cluster.hpp
new file mode 100644
index 00000000..9bc1bb7d
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Cluster.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "ActsFatras/Digitization/Segmentizer.hpp"
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// Simple struct holding cluster information.
+struct Cluster {
+  using Cell = ActsFatras::Segmentizer::ChannelSegment;
+  std::size_t sizeLoc0 = 0;
+  std::size_t sizeLoc1 = 0;
+  std::vector<Cell> channels;
+};
+
+/// Clusters have a one-to-one relation with measurements
+using ClusterContainer = std::vector<Cluster>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/DriftCircle.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/DriftCircle.hpp
new file mode 100644
index 00000000..13491170
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/DriftCircle.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/Common.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+
+#include <cmath>
+#include <vector>
+
+#include <boost/container/static_vector.hpp>
+
+namespace ActsHelper {
+
+/// representation of a drift circle measurement used for track finding
+class DriftCircle {
+  using Scalar = Acts::ActsScalar;
+
+ public:
+  /// Construct the drift circle from the drift radius and tube location
+  ///
+  /// @param tubePos position of the tube in the station frame
+  /// @param driftRadius measured drift radius
+  /// @param driftRadiusError error on drift radius
+  /// @param stationName station name index
+  /// @param stationEta station eta index
+  /// @param stationPhi station phi index
+  /// @param multilayer multilayer index
+  /// @param tubeLayer tube layer index
+  /// @param tube tube index
+  DriftCircle(const Acts::Vector3&& tubePos, float driftRadius,
+              float driftRadiusError, int stationName, int stationEta,
+              int stationPhi, int multilayer, int tubeLayer, int tube)
+      : m_x(tubePos[Acts::ePos0]),
+        m_y(tubePos[Acts::ePos1]),
+        m_z(tubePos[Acts::ePos2]),
+        m_rho(driftRadius),
+        m_sigmaRho(driftRadiusError),
+        m_stationName(stationName),
+        m_stationEta(stationEta),
+        m_stationPhi(stationPhi),
+        m_multilayer(multilayer),
+        m_tubeLayer(tubeLayer),
+        m_tube(tube) {}
+
+  constexpr Scalar x() const { return m_x; }
+  constexpr Scalar y() const { return m_y; }
+  constexpr Scalar z() const { return m_z; }
+  constexpr Scalar rDrift() const { return m_rho; }
+  constexpr Scalar rDriftError() const { return m_sigmaRho; }
+  constexpr int stationName() const { return m_stationName; }
+  constexpr int stationEta() const { return m_stationEta; }
+  constexpr int stationPhi() const { return m_stationPhi; }
+  constexpr int multilayer() const { return m_multilayer; }
+  constexpr int tubeLayer() const { return m_tubeLayer; }
+  constexpr int tube() const { return m_tube; }
+
+ private:
+  // Global position
+  Scalar m_x = 0.0f;
+  Scalar m_y = 0.0f;
+  Scalar m_z = 0.0f;
+  Scalar m_rho = 0.0f;
+  Scalar m_sigmaRho = 0.0f;
+  int m_stationName = 0;
+  int m_stationEta = 0;
+  int m_stationPhi = 0;
+  int m_multilayer = 0;
+  int m_tubeLayer = 0;
+  int m_tube = 0;
+};
+
+inline bool operator==(const DriftCircle& lhs, const DriftCircle& rhs) {
+  return (lhs.stationName() == rhs.stationName() &&
+          lhs.stationEta() == rhs.stationEta() &&
+          lhs.stationPhi() == rhs.stationPhi() &&
+          lhs.multilayer() == rhs.multilayer() &&
+          lhs.tubeLayer() == rhs.tubeLayer() && lhs.tube() == rhs.tube() &&
+          std::abs(rhs.rDrift() - lhs.rDrift()) < 1.e-8 &&
+          std::abs(rhs.rDriftError() - lhs.rDriftError()) < 1.e-8);
+}
+
+/// Container of space points.
+using DriftCircleContainer = std::vector<DriftCircle>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ExtractedSimulationProcess.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ExtractedSimulationProcess.hpp
new file mode 100644
index 00000000..23a84167
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ExtractedSimulationProcess.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "ActsHelper/EventData/SimParticle.hpp"
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// Stores the initial properties of a particle, the properties before the
+/// interaction and the particle properties after the interaction
+struct ExtractedSimulationProcess {
+  SimParticle initial;
+  SimParticle before;
+  std::vector<SimParticle> after;
+};
+
+using ExtractedSimulationProcessContainer =
+    std::vector<ExtractedSimulationProcess>;
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/GeometryContainers.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/GeometryContainers.hpp
new file mode 100644
index 00000000..a7cdfc02
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/GeometryContainers.hpp
@@ -0,0 +1,224 @@
+#pragma once
+
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "ActsHelper/Utilities/GroupBy.hpp"
+#include "ActsHelper/Utilities/Range.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iostream>
+#include <utility>
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+
+namespace ActsHelper {
+namespace detail {
+
+// extract the geometry identifier from a variety of types
+struct GeometryIdGetter {
+  // explicit geometry identifier are just forwarded
+  constexpr Acts::GeometryIdentifier operator()(
+      Acts::GeometryIdentifier geometryId) const {
+    return geometryId;
+  }
+  // encoded geometry ids are converted back to geometry identifiers.
+  constexpr Acts::GeometryIdentifier operator()(
+      Acts::GeometryIdentifier::Value encoded) const {
+    return Acts::GeometryIdentifier(encoded);
+  }
+  // support elements in map-like structures.
+  template <typename T>
+  constexpr Acts::GeometryIdentifier operator()(
+      const std::pair<Acts::GeometryIdentifier, T>& mapItem) const {
+    return mapItem.first;
+  }
+  // support elements that implement `.geometryId()`.
+  template <typename T>
+  inline auto operator()(const T& thing) const
+      -> decltype(thing.geometryId(), Acts::GeometryIdentifier()) {
+    return thing.geometryId();
+  }
+  // support reference_wrappers around such types as well
+  template <typename T>
+  inline auto operator()(std::reference_wrapper<T> thing) const
+      -> decltype(thing.get().geometryId(), Acts::GeometryIdentifier()) {
+    return thing.get().geometryId();
+  }
+};
+
+struct CompareGeometryId {
+  // indicate that comparisons between keys and full objects are allowed.
+  using is_transparent = void;
+  // compare two elements using the automatic key extraction.
+  template <typename Left, typename Right>
+  constexpr bool operator()(Left&& lhs, Right&& rhs) const {
+    return GeometryIdGetter()(lhs) < GeometryIdGetter()(rhs);
+  }
+};
+
+}  // namespace detail
+
+/// Store elements that know their detector geometry id, e.g. simulation hits.
+///
+/// @tparam T type to be stored, must be compatible with `CompareGeometryId`
+///
+/// The container stores an arbitrary number of elements for any geometry
+/// id. Elements can be retrieved via the geometry id; elements can be selected
+/// for a specific geometry id or for a larger range, e.g. a volume or a layer
+/// within the geometry hierarchy using the helper functions below. Elements can
+/// also be accessed by index that uniquely identifies each element regardless
+/// of geometry id.
+template <typename T>
+using GeometryIdMultiset =
+    boost::container::flat_multiset<T, detail::CompareGeometryId>;
+
+/// Store elements indexed by an geometry id.
+///
+/// @tparam T type to be stored
+///
+/// The behaviour is the same as for the `GeometryIdMultiset` except that the
+/// stored elements do not know their geometry id themself. When iterating
+/// the iterator elements behave as for the `std::map`, i.e.
+///
+///     for (const auto& entry: elements) {
+///         auto id = entry.first; // geometry id
+///         const auto& el = entry.second; // stored element
+///     }
+///
+template <typename T>
+using GeometryIdMultimap =
+    GeometryIdMultiset<std::pair<Acts::GeometryIdentifier, T>>;
+
+/// Select all elements within the given volume.
+template <typename T>
+inline Range<typename GeometryIdMultiset<T>::const_iterator> selectVolume(
+    const GeometryIdMultiset<T>& container,
+    Acts::GeometryIdentifier::Value volume) {
+  auto cmp = Acts::GeometryIdentifier().setVolume(volume);
+  auto beg = std::lower_bound(container.begin(), container.end(), cmp,
+                              detail::CompareGeometryId{});
+  // WARNING overflows to volume==0 if the input volume is the last one
+  cmp = Acts::GeometryIdentifier().setVolume(volume + 1u);
+  // optimize search by using the lower bound as start point. also handles
+  // volume overflows since the geo id would be located before the start of
+  // the upper edge search window.
+  auto end =
+      std::lower_bound(beg, container.end(), cmp, detail::CompareGeometryId{});
+  return makeRange(beg, end);
+}
+
+/// Select all elements within the given volume.
+template <typename T>
+inline auto selectVolume(const GeometryIdMultiset<T>& container,
+                         Acts::GeometryIdentifier id) {
+  return selectVolume(container, id.volume());
+}
+
+/// Select all elements within the given layer.
+template <typename T>
+inline Range<typename GeometryIdMultiset<T>::const_iterator> selectLayer(
+    const GeometryIdMultiset<T>& container,
+    Acts::GeometryIdentifier::Value volume,
+    Acts::GeometryIdentifier::Value layer) {
+  auto cmp = Acts::GeometryIdentifier().setVolume(volume).setLayer(layer);
+  auto beg = std::lower_bound(container.begin(), container.end(), cmp,
+                              detail::CompareGeometryId{});
+  // WARNING resets to layer==0 if the input layer is the last one
+  cmp = Acts::GeometryIdentifier().setVolume(volume).setLayer(layer + 1u);
+  // optimize search by using the lower bound as start point. also handles
+  // volume overflows since the geo id would be located before the start of
+  // the upper edge search window.
+  auto end =
+      std::lower_bound(beg, container.end(), cmp, detail::CompareGeometryId{});
+  return makeRange(beg, end);
+}
+
+// Select all elements within the given layer.
+template <typename T>
+inline auto selectLayer(const GeometryIdMultiset<T>& container,
+                        Acts::GeometryIdentifier id) {
+  return selectLayer(container, id.volume(), id.layer());
+}
+
+/// Select all elements for the given module / sensitive surface.
+template <typename T>
+inline Range<typename GeometryIdMultiset<T>::const_iterator> selectModule(
+    const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier geoId) {
+  // module is the lowest level and defines a single geometry id value
+  return makeRange(container.equal_range(geoId));
+}
+
+/// Select all elements for the given module / sensitive surface.
+template <typename T>
+inline auto selectModule(const GeometryIdMultiset<T>& container,
+                         Acts::GeometryIdentifier::Value volume,
+                         Acts::GeometryIdentifier::Value layer,
+                         Acts::GeometryIdentifier::Value module) {
+  return selectModule(
+      container,
+      Acts::GeometryIdentifier().setVolume(volume).setLayer(layer).setSensitive(
+          module));
+}
+
+/// Select all elements for the lowest non-zero identifier component.
+///
+/// Zero values of lower components are interpreted as wildcard search patterns
+/// that select all element at the given geometry hierarchy and below. This only
+/// applies to the lower components and not to intermediate zeros.
+///
+/// Examples:
+/// - volume=2,layer=0,module=3 -> select all elements in the module
+/// - volume=1,layer=2,module=0 -> select all elements in the layer
+/// - volume=3,layer=0,module=0 -> select all elements in the volume
+///
+/// @note An identifier with all components set to zero selects the whole input
+///   container.
+/// @note Boundary and approach surfaces do not really fit into the geometry
+///   hierarchy and must be set to zero for the selection. If they are set on an
+///   input identifier, the behaviour of this search method is undefined.
+template <typename T>
+inline Range<typename GeometryIdMultiset<T>::const_iterator>
+selectLowestNonZeroGeometryObject(const GeometryIdMultiset<T>& container,
+                                  Acts::GeometryIdentifier geoId) {
+  assert((geoId.boundary() == 0u) && "Boundary component must be zero");
+  assert((geoId.approach() == 0u) && "Approach component must be zero");
+
+  if (geoId.sensitive() != 0u) {
+    return selectModule(container, geoId);
+  } else if (geoId.layer() != 0u) {
+    return selectLayer(container, geoId);
+  } else if (geoId.volume() != 0u) {
+    return selectVolume(container, geoId);
+  } else {
+    return makeRange(container.begin(), container.end());
+  }
+}
+
+/// Iterate over groups of elements belonging to each module/ sensitive surface.
+template <typename T>
+inline GroupBy<typename GeometryIdMultiset<T>::const_iterator,
+               detail::GeometryIdGetter>
+groupByModule(const GeometryIdMultiset<T>& container) {
+  return makeGroupBy(container, detail::GeometryIdGetter());
+}
+
+/// The accessor for the GeometryIdMultiset container
+///
+/// It wraps up a few lookup methods to be used in the Combinatorial Kalman
+/// Filter
+template <typename T>
+struct GeometryIdMultisetAccessor {
+  using Container = GeometryIdMultiset<T>;
+  using Key = Acts::GeometryIdentifier;
+  using Value = typename GeometryIdMultiset<T>::value_type;
+  using Iterator = typename GeometryIdMultiset<T>::const_iterator;
+
+  // pointer to the container
+  const Container* container = nullptr;
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Index.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Index.hpp
new file mode 100644
index 00000000..598f470b
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Index.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cstdint>
+
+#include <boost/container/flat_map.hpp>
+
+namespace ActsHelper {
+
+/// Index type to reference elements in a container.
+///
+/// We do not expect to have more than 2^32 elements in any given container so a
+/// fixed sized integer type is sufficient.
+using Index = std::uint32_t;
+
+/// Store elements that are identified by an index, e.g. in another container.
+///
+/// Each index can have zero or more associated elements. A typical case could
+/// be to store all generating particles for a hit where the hit is identified
+/// by its index in the hit container.
+template <typename value_t>
+using IndexMultimap = boost::container::flat_multimap<Index, value_t>;
+
+/// Invert the multimap, i.e. from a -> {b...} to b -> {a...}.
+///
+/// @note This assumes that the value in the initial multimap is itself a
+///   sortable index-like object, as would be the case when mapping e.g.
+///   hit ids to particle ids/ barcodes.
+template <typename value_t>
+inline boost::container::flat_multimap<value_t, Index> invertIndexMultimap(
+    const IndexMultimap<value_t>& multimap) {
+  using InverseMultimap = boost::container::flat_multimap<value_t, Index>;
+
+  // switch key-value without enforcing the new ordering (linear copy)
+  typename InverseMultimap::sequence_type unordered;
+  unordered.reserve(multimap.size());
+  for (auto&& [index, value] : multimap) {
+    // value is now the key and the index is now the value
+    unordered.emplace_back(value, index);
+  }
+
+  // adopting the unordered sequence will reestablish the correct order
+  InverseMultimap inverse;
+#if BOOST_VERSION < 107800
+  for (const auto& i : unordered) {
+    inverse.insert(i);
+  }
+#else
+  inverse.insert(unordered.begin(), unordered.end());
+#endif
+  return inverse;
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/IndexSourceLink.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/IndexSourceLink.hpp
new file mode 100644
index 00000000..41202c50
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/IndexSourceLink.hpp
@@ -0,0 +1,86 @@
+#pragma once
+
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "ActsHelper/EventData/GeometryContainers.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+
+#include <cassert>
+
+namespace ActsHelper {
+
+/// A source link that stores just an index.
+///
+/// This is intentionally kept as barebones as possible. The source link
+/// is just a reference and will be copied, moved around, etc. often.
+/// Keeping it small and separate from the actual, potentially large,
+/// measurement data should result in better overall performance.
+/// Using an index instead of e.g. a pointer, means source link and
+/// measurement are decoupled and the measurement representation can be
+/// easily changed without having to also change the source link.
+class IndexSourceLink final {
+ public:
+  /// Construct from geometry identifier and index.
+  constexpr IndexSourceLink(Acts::GeometryIdentifier gid, Index idx)
+      : m_geometryId(gid), m_index(idx) {}
+
+  // Construct an invalid source link. Must be default constructible to
+  /// satisfy SourceLinkConcept.
+  IndexSourceLink() = default;
+  IndexSourceLink(const IndexSourceLink&) = default;
+  IndexSourceLink(IndexSourceLink&&) = default;
+  IndexSourceLink& operator=(const IndexSourceLink&) = default;
+  IndexSourceLink& operator=(IndexSourceLink&&) = default;
+
+  /// Access the index.
+  constexpr Index index() const { return m_index; }
+
+  Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
+
+  struct SurfaceAccessor {
+    const Acts::TrackingGeometry& trackingGeometry;
+
+    const Acts::Surface* operator()(const Acts::SourceLink& sourceLink) const {
+      const auto& indexSourceLink = sourceLink.get<IndexSourceLink>();
+      return trackingGeometry.findSurface(indexSourceLink.geometryId());
+    }
+  };
+
+ private:
+  Acts::GeometryIdentifier m_geometryId;
+  Index m_index = 0;
+
+  friend bool operator==(const IndexSourceLink& lhs,
+                         const IndexSourceLink& rhs) {
+    return (lhs.geometryId() == rhs.geometryId()) &&
+           (lhs.m_index == rhs.m_index);
+  }
+  friend bool operator!=(const IndexSourceLink& lhs,
+                         const IndexSourceLink& rhs) {
+    return !(lhs == rhs);
+  }
+};
+
+/// Container of index source links.
+///
+/// Since the source links provide a `.geometryId()` accessor, they can be
+/// stored in an ordered geometry container.
+using IndexSourceLinkContainer = GeometryIdMultiset<IndexSourceLink>;
+/// Accessor for the above source link container
+///
+/// It wraps up a few lookup methods to be used in the Combinatorial Kalman
+/// Filter
+struct IndexSourceLinkAccessor : GeometryIdMultisetAccessor<IndexSourceLink> {
+  using BaseIterator = GeometryIdMultisetAccessor<IndexSourceLink>::Iterator;
+
+  using Iterator = Acts::SourceLinkAdapterIterator<BaseIterator>;
+
+  // get the range of elements with requested geoId
+  std::pair<Iterator, Iterator> range(const Acts::Surface& surface) const {
+    assert(container != nullptr);
+    auto [begin, end] = container->equal_range(surface.geometryId());
+    return {Iterator{begin}, Iterator{end}};
+  }
+};
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Measurement.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Measurement.hpp
new file mode 100644
index 00000000..777d0325
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Measurement.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <Acts/EventData/Measurement.hpp>
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// Variable measurement type that can contain all possible combinations.
+using Measurement = ::Acts::BoundVariantMeasurement;
+
+/// Container of measurements.
+///
+/// In contrast to the source links, the measurements themself must not be
+/// orderable. The source links stored in the measurements are treated
+/// as opaque here and no ordering is enforced on the stored measurements.
+using MeasurementContainer = std::vector<Measurement>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MeasurementCalibration.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MeasurementCalibration.hpp
new file mode 100644
index 00000000..f9d68320
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MeasurementCalibration.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Utilities/CalibrationContext.hpp"
+#include "ActsHelper/EventData/Cluster.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include <ActsHelper/EventData/Measurement.hpp>
+
+#include <cassert>
+
+namespace Acts {
+class VectorMultiTrajectory;
+}  // namespace Acts
+
+namespace ActsHelper {
+
+/// Abstract base class for measurement-based calibration
+class MeasurementCalibrator {
+ public:
+  virtual void calibrate(
+      const MeasurementContainer& measurements,
+      const ClusterContainer* clusters, const Acts::GeometryContext& gctx,
+      const Acts::CalibrationContext& cctx, const Acts::SourceLink& sourceLink,
+      Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const = 0;
+
+  virtual ~MeasurementCalibrator() = default;
+  virtual bool needsClusters() const { return false; }
+};
+
+// Calibrator to convert an index source link to a measurement as-is
+class PassThroughCalibrator : public MeasurementCalibrator {
+ public:
+  /// Find the measurement corresponding to the source link.
+  ///
+  /// @tparam parameters_t Track parameters type
+  /// @param gctx The geometry context (unused)
+  /// @param trackState The track state to calibrate
+  void calibrate(
+      const MeasurementContainer& measurements,
+      const ClusterContainer* clusters, const Acts::GeometryContext& gctx,
+      const Acts::CalibrationContext& cctx, const Acts::SourceLink& sourceLink,
+      Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const override;
+};
+
+// Adapter class that wraps a MeasurementCalibrator to conform to the
+// core ACTS calibration interface
+class MeasurementCalibratorAdapter {
+ public:
+  MeasurementCalibratorAdapter(const MeasurementCalibrator& calibrator,
+                               const MeasurementContainer& measurements,
+                               const ClusterContainer* clusters = nullptr);
+
+  MeasurementCalibratorAdapter() = delete;
+
+  void calibrate(const Acts::GeometryContext& gctx,
+                 const Acts::CalibrationContext& cctx,
+                 const Acts::SourceLink& sourceLink,
+                 Acts::VectorMultiTrajectory::TrackStateProxy trackState) const;
+
+ private:
+  const MeasurementCalibrator& m_calibrator;
+  const MeasurementContainer& m_measurements;
+  const ClusterContainer* m_clusters;
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MuonSimHit.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MuonSimHit.hpp
new file mode 100644
index 00000000..36d60c7f
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/MuonSimHit.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/Common.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+
+#include <cmath>
+#include <vector>
+
+#include <boost/container/static_vector.hpp>
+
+namespace ActsHelper {
+using MuonSimHit = SimHit;
+/// Container of space points.
+using MuonSimHitContainer = std::vector<MuonSimHit>;
+constexpr int g_fieldShift = 8;
+// field translators
+enum class MuonIdentifierFieldMaps {
+  stationName = 40,
+  stationEta = 32,
+  stationPhi = 24,
+  multilayer = 16,
+  tubeLayer = 8,
+  tube = 0,
+};
+struct muonMdtIdentifierFields {
+  int8_t stationName = 0;
+  int8_t stationEta = 0;
+  int8_t stationPhi = 0;
+  int8_t multilayer = 0;
+  int8_t tubeLayer = 0;
+  int8_t tube = 0;
+};
+muonMdtIdentifierFields splitId(Acts::GeometryIdentifier::Value theID) {
+  muonMdtIdentifierFields f;
+  f.tube = theID & 0xFF;
+  theID = theID >> g_fieldShift;
+  f.tubeLayer = theID & 0xFF;
+  theID = theID >> g_fieldShift;
+  f.multilayer = theID & 0xFF;
+  theID = theID >> g_fieldShift;
+  f.stationPhi = theID & 0xFF;
+  theID = theID >> g_fieldShift;
+  f.stationEta = theID & 0xFF;
+  theID = theID >> g_fieldShift;
+  f.stationName = theID & 0xFF;
+  return f;
+}
+Acts::GeometryIdentifier::Value compressId(muonMdtIdentifierFields f) {
+  Acts::GeometryIdentifier::Value out{0};
+  out = out << g_fieldShift | f.stationName;
+  out = out << g_fieldShift | f.stationEta;
+  out = out << g_fieldShift | f.stationPhi;
+  out = out << g_fieldShift | f.multilayer;
+  out = out << g_fieldShift | f.tubeLayer;
+  out = out << g_fieldShift | f.tube;
+  return out;
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoTrack.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoTrack.hpp
new file mode 100644
index 00000000..f5bc2381
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoTrack.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "ActsHelper/EventData/Index.hpp"
+
+#include <vector>
+
+#include <boost/container/small_vector.hpp>
+
+namespace ActsHelper {
+
+/// A proto track is a collection of hits identified by their indices.
+using ProtoTrack = boost::container::small_vector<Index, 3>;
+/// Container of proto tracks. Each proto track is identified by its index.
+using ProtoTrackContainer = std::vector<ProtoTrack>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoVertex.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoVertex.hpp
new file mode 100644
index 00000000..def338da
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ProtoVertex.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "ActsHelper/EventData/Index.hpp"
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// A proto vertex is a collection of tracks identified by their indices.
+using ProtoVertex = std::vector<Index>;
+/// Container of proto vertices. Each proto vertex is identified by its index.
+using ProtoVertexContainer = std::vector<ProtoVertex>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ScalingCalibrator.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ScalingCalibrator.hpp
new file mode 100644
index 00000000..15c2901e
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/ScalingCalibrator.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <Acts/Geometry/GeometryIdentifier.hpp>
+#include <ActsHelper/EventData/MeasurementCalibration.hpp>
+
+#include <filesystem>
+
+#include <TFile.h>
+#include <TH2D.h>
+
+namespace ActsHelper {
+
+class ScalingCalibrator : public MeasurementCalibrator {
+ public:
+  struct ConstantTuple {
+    double x_offset{0};
+    double x_scale{1};
+    double y_offset{0};
+    double y_scale{1};
+  };
+
+  struct MapTuple {
+    TH2D x_offset;
+    TH2D x_scale;
+    TH2D y_offset;
+    TH2D y_scale;
+
+    ConstantTuple at(std::size_t sizeLoc0, std::size_t sizeLoc1) const {
+      ConstantTuple ct;
+      ct.x_offset =
+          x_offset.GetBinContent(x_offset.FindFixBin(sizeLoc0, sizeLoc1));
+      ct.x_scale =
+          x_scale.GetBinContent(x_scale.FindFixBin(sizeLoc0, sizeLoc1));
+      ct.y_offset =
+          y_offset.GetBinContent(y_offset.FindFixBin(sizeLoc0, sizeLoc1));
+      ct.y_scale =
+          y_scale.GetBinContent(y_scale.FindFixBin(sizeLoc0, sizeLoc1));
+      return ct;
+    }
+  };
+
+  ScalingCalibrator(const std::filesystem::path& path);
+
+  void calibrate(
+      const MeasurementContainer& measurements,
+      const ClusterContainer* clusters, const Acts::GeometryContext& gctx,
+      const Acts::CalibrationContext& cctx, const Acts::SourceLink& sourceLink,
+      Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const override;
+
+  bool needsClusters() const override { return true; }
+
+ private:
+  std::map<Acts::GeometryIdentifier, MapTuple> m_calib_maps;
+  std::bitset<3> m_mask;
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimHit.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimHit.hpp
new file mode 100644
index 00000000..1096acd7
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimHit.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "ActsHelper/EventData/GeometryContainers.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/SimParticle.hpp"
+#include "ActsFatras/EventData/Hit.hpp"
+
+namespace ActsHelper {
+
+using SimHit = ::ActsFatras::Hit;
+/// Store hits ordered by geometry identifier.
+using SimHitContainer = GeometryIdMultiset<SimHit>;
+
+using HitParticlesMap = IndexMultimap<SimBarcode>;
+
+using HitSimHitsMap = IndexMultimap<Index>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimParticle.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimParticle.hpp
new file mode 100644
index 00000000..57589faf
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimParticle.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "ActsHelper/Utilities/GroupBy.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <boost/container/flat_set.hpp>
+
+namespace ActsHelper {
+namespace detail {
+struct CompareParticleId {
+  using is_transparent = void;
+  constexpr bool operator()(const ActsFatras::Particle& lhs,
+                            const ActsFatras::Particle& rhs) const {
+    return lhs.particleId() < rhs.particleId();
+  }
+  constexpr bool operator()(ActsFatras::Barcode lhs,
+                            const ActsFatras::Particle& rhs) const {
+    return lhs < rhs.particleId();
+  }
+  constexpr bool operator()(const ActsFatras::Particle& lhs,
+                            ActsFatras::Barcode rhs) const {
+    return lhs.particleId() < rhs;
+  }
+};
+struct PrimaryVertexIdGetter {
+  constexpr ActsFatras::Barcode operator()(
+      const ActsFatras::Particle& particle) const {
+    return ActsFatras::Barcode(0u).setVertexPrimary(
+        particle.particleId().vertexPrimary());
+  }
+};
+struct SecondaryVertexIdGetter {
+  constexpr ActsFatras::Barcode operator()(
+      const ActsFatras::Particle& particle) const {
+    return ActsFatras::Barcode(0u)
+        .setVertexPrimary(particle.particleId().vertexPrimary())
+        .setVertexSecondary(particle.particleId().vertexSecondary());
+  }
+};
+}  // namespace detail
+
+using SimBarcode = ::ActsFatras::Barcode;
+using SimParticle = ::ActsFatras::Particle;
+/// Store particles ordered by particle identifier.
+using SimBarcodeContainer = ::boost::container::flat_set<SimBarcode>;
+using SimParticleContainer =
+    ::boost::container::flat_set<SimParticle, detail::CompareParticleId>;
+
+/// Iterate over groups of particles belonging to the same primary vertex.
+inline GroupBy<SimParticleContainer::const_iterator,
+               detail::PrimaryVertexIdGetter>
+groupByPrimaryVertex(const SimParticleContainer& container) {
+  return makeGroupBy(container, detail::PrimaryVertexIdGetter());
+}
+
+/// Iterate over groups of particles belonging to the same secondary vertex.
+///
+/// For each primary vertex, this yields one group of particles belonging
+/// directly to the primary vertex (secondary vertex id 0) and a group for
+/// each secondary vertex.
+inline GroupBy<SimParticleContainer::const_iterator,
+               detail::SecondaryVertexIdGetter>
+groupBySecondaryVertex(const SimParticleContainer& container) {
+  return makeGroupBy(container, detail::SecondaryVertexIdGetter());
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSeed.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSeed.hpp
new file mode 100644
index 00000000..3d883a22
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSeed.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "Acts/Seeding/Seed.hpp"
+#include "ActsHelper/EventData/SimSpacePoint.hpp"
+
+#include <map>
+#include <vector>
+
+namespace ActsHelper {
+using SimSeed = Acts::Seed<SimSpacePoint>;
+/// Container of sim seed
+using SimSeedContainer = std::vector<SimSeed>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSpacePoint.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSpacePoint.hpp
new file mode 100644
index 00000000..5f3267b8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimSpacePoint.hpp
@@ -0,0 +1,118 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/Common.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+
+// edm4hep
+#include "edm4hep/TrackerHit.h"
+#include "edm4hep/SimTrackerHit.h"
+
+#include <cmath>
+#include <vector>
+
+#include <boost/container/static_vector.hpp>
+
+namespace ActsHelper {
+/// Space point representation of a measurement suitable for track seeding.
+class SimSpacePoint {
+    using Scalar = Acts::ActsScalar;
+
+public:
+    SimSpacePoint(float x, float y, float z, float t,
+                  float varianceRho,
+                  float varianceZ,
+                  float varianceT,
+                  boost::container::static_vector<Acts::SourceLink, 2> sourceLinks)
+                  : m_x(x), m_y(y), m_z(z), m_t(t),
+                    m_rho(std::hypot(x, y)),
+                    m_varianceRho(varianceRho),
+                    m_varianceZ(varianceZ),
+                    m_varianceT(varianceT),
+                    m_sourceLinks(sourceLinks){}
+
+    constexpr Scalar x() const { return m_x; }
+    constexpr Scalar y() const { return m_y; }
+    constexpr Scalar z() const { return m_z; }
+
+    constexpr std::optional<Scalar> t() const { return m_t; }
+    constexpr Scalar r() const { return m_rho; }
+    constexpr Scalar varianceR() const { return m_varianceRho; }
+    constexpr Scalar varianceZ() const { return m_varianceZ; }
+    constexpr std::optional<Scalar> varianceT() const { return m_varianceT; }
+
+    // constexpr std::uint64_t cellid() const { return m_cellid; }
+    // constexpr Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
+
+    const boost::container::static_vector<Acts::SourceLink, 2>&
+        sourceLinks() const { return m_sourceLinks; }
+
+    constexpr float topHalfStripLength() const { return m_topHalfStripLength; }
+    constexpr float bottomHalfStripLength() const { return m_bottomHalfStripLength; }
+
+    Acts::Vector3 topStripDirection() const { return m_topStripDirection; }
+    Acts::Vector3 bottomStripDirection() const { return m_bottomStripDirection; }
+    Acts::Vector3 stripCenterDistance() const { return m_stripCenterDistance; }
+    Acts::Vector3 topStripCenterPosition() const { return m_topStripCenterPosition; }
+
+    constexpr bool validDoubleMeasurementDetails() const {
+        return m_validDoubleMeasurementDetails;
+    }
+
+private:
+
+    // Global position
+    Scalar m_x;
+    Scalar m_y;
+    Scalar m_z;
+    
+    std::optional<Scalar> m_t;
+    Scalar m_rho;
+    // Variance in rho/z of the global coordinates
+    Scalar m_varianceRho;
+    Scalar m_varianceZ;
+    std::optional<Scalar> m_varianceT;
+    // SourceLinks of the corresponding measurements. A Pixel (strip) SP has one
+    // (two) sourceLink(s).
+    boost::container::static_vector<Acts::SourceLink, 2> m_sourceLinks;
+
+    // half of the length of the top strip
+    float m_topHalfStripLength = 0;
+    // half of the length of the bottom strip
+    float m_bottomHalfStripLength = 0;
+    // direction of the top strip
+    Acts::Vector3 m_topStripDirection = {0, 0, 0};
+    // direction of the bottom strip
+    Acts::Vector3 m_bottomStripDirection = {0, 0, 0};
+    // distance between the center of the two strips
+    Acts::Vector3 m_stripCenterDistance = {0, 0, 0};
+    // position of the center of the bottom strip
+    Acts::Vector3 m_topStripCenterPosition = {0, 0, 0};
+    bool m_validDoubleMeasurementDetails = false;
+
+}; // SimSpacePoint
+
+inline bool operator==(const SimSpacePoint& lhs, const SimSpacePoint& rhs) {
+  // TODO would it be sufficient to check just the index under the assumption
+  //   that the same measurement index always produces the same space point?
+  // no need to check r since it is fully defined by x/y
+
+  return (std::equal(lhs.sourceLinks().begin(), lhs.sourceLinks().end(),
+                     rhs.sourceLinks().begin(),
+                     [](const auto& lsl, const auto& rsl) {
+                       return lsl.template get<IndexSourceLink>() ==
+                              rsl.template get<IndexSourceLink>();
+                     }) &&
+          (lhs.x() == rhs.x()) && (lhs.y() == rhs.y()) &&
+          (lhs.z() == rhs.z()) && (lhs.t() == rhs.t()) &&
+          (lhs.varianceR() == rhs.varianceR()) &&
+          (lhs.varianceZ() == rhs.varianceZ()) &&
+          (lhs.varianceT() == rhs.varianceT()));
+}
+
+/// Container of space points.
+using SimSpacePointContainer = std::vector<SimSpacePoint>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimVertex.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimVertex.hpp
new file mode 100644
index 00000000..8e604f97
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/SimVertex.hpp
@@ -0,0 +1,130 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "ActsHelper/EventData/SimParticle.hpp"
+#include "ActsFatras/EventData/ProcessType.hpp"
+
+#include <boost/container/flat_set.hpp>
+
+namespace ActsHelper {
+
+class SimVertexBarcode {
+ public:
+  using Value = SimBarcode::Value;
+
+  constexpr SimVertexBarcode() = default;
+  constexpr SimVertexBarcode(Value encoded) : m_id(SimBarcode(encoded)) {}
+  constexpr SimVertexBarcode(SimBarcode vertexId)
+      : m_id(vertexId.setParticle(0).setSubParticle(0)) {
+    if (vertexId != vertexId.vertexId()) {
+      throw std::invalid_argument("SimVertexBarcode: invalid vertexId");
+    }
+  }
+
+  /// Get the encoded value of all index levels.
+  constexpr Value value() const { return m_id.value(); }
+
+  /// Return the barcode.
+  constexpr SimBarcode barcode() const { return m_id; }
+
+  /// Return the primary vertex identifier.
+  constexpr Value vertexPrimary() const { return m_id.vertexPrimary(); }
+  /// Return the secondary vertex identifier.
+  constexpr Value vertexSecondary() const { return m_id.vertexSecondary(); }
+  /// Return the generation identifier.
+  constexpr Value generation() const { return m_id.generation(); }
+
+  /// Set the primary vertex identifier.
+  constexpr SimVertexBarcode& setVertexPrimary(Value id) {
+    return m_id.setVertexPrimary(id), *this;
+  }
+  /// Set the secondary vertex identifier.
+  constexpr SimVertexBarcode& setVertexSecondary(Value id) {
+    return m_id.setVertexSecondary(id), *this;
+  }
+  /// Set the particle identifier.
+  constexpr SimVertexBarcode& setGeneration(Value id) {
+    return m_id.setGeneration(id), *this;
+  }
+
+ private:
+  /// The vertex ID
+  /// Note that only primary, secondary and generation should be set
+  SimBarcode m_id = 0;
+
+  friend constexpr bool operator<(SimVertexBarcode lhs, SimVertexBarcode rhs) {
+    return lhs.m_id < rhs.m_id;
+  }
+  friend constexpr bool operator==(SimVertexBarcode lhs, SimVertexBarcode rhs) {
+    return lhs.m_id == rhs.m_id;
+  }
+  friend constexpr bool operator!=(SimVertexBarcode lhs, SimVertexBarcode rhs) {
+    return lhs.m_id != rhs.m_id;
+  }
+  friend inline std::ostream& operator<<(std::ostream& os,
+                                         SimVertexBarcode idx) {
+    return os << idx.m_id;
+  }
+};
+
+/// A simultated vertex e.g. from a physics process.
+struct SimVertex {
+  using Scalar = Acts::ActsScalar;
+  using Vector4 = Acts::ActsVector<4>;
+
+  /// The vertex ID
+  SimVertexBarcode id;
+  /// The vertex four-position
+  Vector4 position4 = Vector4::Zero();
+  /// The vertex process type
+  ActsFatras::ProcessType process = ActsFatras::ProcessType::eUndefined;
+  /// The incoming particles into the vertex
+  SimBarcodeContainer incoming;
+  /// The outgoing particles from the vertex
+  SimBarcodeContainer outgoing;
+
+  /// Construct the vertex from a position and optional process type.
+  ///
+  /// @param position4_ the vertex four-position
+  /// @param process_ the process type that generated this vertex
+  ///
+  /// Associated particles are left empty by default and must be filled by the
+  /// user after construction.
+  SimVertex(
+      SimVertexBarcode id_, const Vector4& position4_,
+      ActsFatras::ProcessType process_ = ActsFatras::ProcessType::eUndefined)
+      : id(id_), position4(position4_), process(process_) {}
+  // explicitly default rule-of-five.
+  SimVertex() = default;
+  SimVertex(const SimVertex&) = default;
+  SimVertex(SimVertex&&) = default;
+  SimVertex& operator=(const SimVertex&) = default;
+  SimVertex& operator=(SimVertex&&) = default;
+
+  constexpr SimVertexBarcode vertexId() const { return id; }
+  /// The vertex three-position.
+  auto position() const { return position4.head<3>(); }
+  /// The vertex time.
+  Scalar time() const { return position4[3]; }
+};
+
+namespace detail {
+struct CompareVertexId {
+  using is_transparent = void;
+  constexpr bool operator()(const SimVertex& lhs, const SimVertex& rhs) const {
+    return lhs.vertexId() < rhs.vertexId();
+  }
+  constexpr bool operator()(SimVertexBarcode lhs, const SimVertex& rhs) const {
+    return lhs < rhs.vertexId();
+  }
+  constexpr bool operator()(const SimVertex& lhs, SimVertexBarcode rhs) const {
+    return lhs.vertexId() < rhs;
+  }
+};
+}  // namespace detail
+
+/// Store vertices ordered by vertex identifier.
+using SimVertexContainer =
+    ::boost::container::flat_set<SimVertex, detail::CompareVertexId>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Track.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Track.hpp
new file mode 100644
index 00000000..3fd7abaa
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Track.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// (Reconstructed) track parameters e.g. close to the vertex.
+using TrackParameters = ::Acts::BoundTrackParameters;
+/// Container of reconstructed track states for multiple tracks.
+using TrackParametersContainer = std::vector<TrackParameters>;
+
+using TrackContainer =
+    Acts::TrackContainer<Acts::VectorTrackContainer,
+                         Acts::VectorMultiTrajectory, std::shared_ptr>;
+
+using ConstTrackContainer =
+    Acts::TrackContainer<Acts::ConstVectorTrackContainer,
+                         Acts::ConstVectorMultiTrajectory, std::shared_ptr>;
+
+using TrackIndexType = TrackContainer::IndexType;
+
+using TrackProxy = TrackContainer::TrackProxy;
+using ConstTrackProxy = ConstTrackContainer::ConstTrackProxy;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Trajectories.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Trajectories.hpp
new file mode 100644
index 00000000..9a87aad8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Trajectories.hpp
@@ -0,0 +1,104 @@
+#pragma once
+
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/Utilities/ThrowAssert.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+
+#include <algorithm>
+#include <unordered_map>
+#include <vector>
+
+namespace ActsHelper {
+
+/// Store reconstructed trajectories from track finding/fitting.
+///
+/// It contains a MultiTrajectory with a vector of entry indices for
+/// individual trajectories, and a map of fitted parameters indexed by the
+/// entry index. In case of track fitting, there is at most one trajectory
+/// in the MultiTrajectory; In case of track finding, there could be
+/// multiple trajectories in the MultiTrajectory.
+struct Trajectories final {
+ public:
+  /// (Reconstructed) trajectory with multiple states.
+  using MultiTrajectory = Acts::ConstVectorMultiTrajectory;
+  /// Fitted parameters identified by indices in the multi trajectory.
+  using IndexedParameters =
+      std::unordered_map<Acts::MultiTrajectoryTraits::IndexType,
+                         TrackParameters>;
+
+  /// Default construct an empty object. Required for container compatibility
+  /// and to signal an error.
+  Trajectories() = default;
+  /// Construct from fitted multi trajectory and parameters.
+  ///
+  /// @param multiTraj The multi trajectory
+  /// /// @param tTips Tip indices that identify valid trajectories
+  /// @param parameters Fitted track parameters indexed by trajectory index
+  Trajectories(const MultiTrajectory& multiTraj,
+               const std::vector<Acts::MultiTrajectoryTraits::IndexType>& tTips,
+               const IndexedParameters& parameters)
+      : m_multiTrajectory(&multiTraj),
+        m_trackTips(tTips),
+        m_trackParameters(parameters) {}
+
+  /// Return true if there exists no valid trajectory.
+  bool empty() const { return m_trackTips.empty(); }
+
+  /// Access the underlying multi trajectory.
+  const MultiTrajectory& multiTrajectory() const {
+    throw_assert(m_multiTrajectory != nullptr, "MultiTrajectory is null");
+    return *m_multiTrajectory;
+  }
+
+  /// Access the tip indices that identify valid trajectories.
+  const std::vector<Acts::MultiTrajectoryTraits::IndexType>& tips() const {
+    return m_trackTips;
+  }
+
+  /// Check if a trajectory exists for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return Whether there is trajectory with provided entry index
+  bool hasTrajectory(Acts::MultiTrajectoryTraits::IndexType entryIndex) const {
+    return (0 < std::count(m_trackTips.begin(), m_trackTips.end(), entryIndex));
+  }
+
+  /// Check if fitted track parameters exists for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return Whether having fitted track parameters or not
+  bool hasTrackParameters(
+      Acts::MultiTrajectoryTraits::IndexType entryIndex) const {
+    return (0 < m_trackParameters.count(entryIndex));
+  }
+
+  /// Access the fitted track parameters for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return The fitted track parameters of the trajectory
+  const TrackParameters& trackParameters(
+      Acts::MultiTrajectoryTraits::IndexType entryIndex) const {
+    auto it = m_trackParameters.find(entryIndex);
+    if (it == m_trackParameters.end()) {
+      throw std::runtime_error(
+          "No fitted track parameters for trajectory with entry index = " +
+          std::to_string(entryIndex));
+    }
+    return it->second;
+  }
+
+ private:
+  // The track container
+  const MultiTrajectory* m_multiTrajectory{nullptr};
+  // The entry indices of trajectories stored in multiTrajectory
+  std::vector<Acts::MultiTrajectoryTraits::IndexType> m_trackTips = {};
+  // The fitted parameters at the provided surface for individual trajectories
+  IndexedParameters m_trackParameters = {};
+};
+
+/// Container for multiple trajectories.
+using TrajectoriesContainer = std::vector<Trajectories>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/TruthMatching.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/TruthMatching.hpp
new file mode 100644
index 00000000..9ae6d25e
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/TruthMatching.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "ActsHelper/EventData/ProtoTrack.hpp"
+#include "ActsHelper/EventData/SimParticle.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/Validation/TrackClassification.hpp"
+
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <vector>
+
+namespace ActsHelper {
+
+enum class TrackMatchClassification {
+  Unknown = 0,
+  /// The track is associated to a truth particle
+  Matched,
+  /// The track is associated to a truth particle, but the track is not unique
+  Duplicate,
+  /// The track cannot be uniquely associated to a truth particle
+  Fake,
+};
+
+struct TrackMatchEntry {
+  TrackMatchClassification classification{TrackMatchClassification::Unknown};
+
+  std::optional<SimBarcode> particle;
+
+  /// Number of hits on the track that are associated to a particle
+  /// Sorted by decreasing number of hits
+  std::vector<ParticleHitCount> contributingParticles;
+};
+
+struct ParticleMatchEntry {
+  std::optional<TrackIndexType> track;
+  std::uint32_t duplicates{};
+  std::uint32_t fakes{};
+};
+
+using ProtoTrackParticleMatching = std::map<TrackIndexType, TrackMatchEntry>;
+using ParticleProtoTrackMatching = std::map<SimBarcode, ParticleMatchEntry>;
+
+using TrackParticleMatching = std::map<TrackIndexType, TrackMatchEntry>;
+using ParticleTrackMatching = std::map<SimBarcode, ParticleMatchEntry>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Vertex.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Vertex.hpp
new file mode 100644
index 00000000..adc1d442
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/EventData/Vertex.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Acts/Vertexing/Vertex.hpp"
+
+#include <vector>
+
+namespace ActsHelper {
+
+/// Container of vertices.
+using VertexContainer = std::vector<Acts::Vertex>;
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapRootIo.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapRootIo.hpp
new file mode 100644
index 00000000..4fa9a285
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapRootIo.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "ActsHelper/MagneticField/MagneticField.hpp"
+
+#include <array>
+#include <cstddef>
+#include <functional>
+#include <string>
+
+namespace ActsHelper {
+
+/// Method to setup the FieldMap
+/// @param localToGlobalBin Function mapping the local bins of r,z to the
+/// global
+/// bin of the map magnetic field value e.g.: we have small grid with the
+/// values: r={2,3}, z ={4,5}, the corresponding indices are i(r) and j(z),
+/// the
+/// globalIndex is M and the field map is:
+///|| r | i || z | j || |B(r,z)| ||  M ||
+///  -----------------------------------
+///|| 2 | 0 || 4 | 0 ||  2.323   ||  0 ||
+///|| 2 | 0 || 5 | 1 ||  2.334   ||  1 ||
+///|| 3 | 1 || 4 | 0 ||  2.325   ||  2 ||
+///|| 3 | 1 || 5 | 1 ||  2.331   ||  3 ||
+///
+/// @code
+/// In this case the function would look like:
+/// [](std::array<std::size_t, 2> binsRZ, std::array<std::size_t, 2> nBinsRZ) {
+///    return (binsRZ.at(0) * nBinsRZ.at(1) + binsRZ.at(1));
+/// }
+/// @endcode
+/// @param[in] fieldMapFile Path to file containing field map in txt format
+/// @param[in] treeName The name of the root tree
+/// @param[in] lengthUnit The unit of the grid points
+/// @param[in] BFieldUnit The unit of the magnetic field
+/// @param[in] firstQuadrant Flag if set to true indicating that only the
+/// first
+/// quadrant of the grid points and the BField values has been given and
+/// that
+/// the BFieldMap should be created symmetrically for all quadrants.
+/// e.g. we have the grid values r={0,1} with BFieldValues={2,3} on the r
+/// axis.
+/// If the flag is set to true the r-axis grid values will be set to
+/// {-1,0,1}
+/// and the BFieldValues will be set to {3,2,3}.
+detail::InterpolatedMagneticField2 makeMagneticFieldMapRzFromRoot(
+    const std::function<std::size_t(std::array<std::size_t, 2> binsRZ,
+                                    std::array<std::size_t, 2> nBinsRZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, const std::string& treeName,
+    Acts::ActsScalar lengthUnit, Acts::ActsScalar BFieldUnit,
+    bool firstQuadrant = false);
+
+/// Method to setup the FieldMap
+/// @param localToGlobalBin Function mapping the local bins of x,y,z to the
+/// global bin of the map magnetic field value e.g.: we have small grid with
+/// the
+/// values: x={2,3}, y={3,4}, z ={4,5}, the corresponding indices are i(x),
+/// j(y)
+/// and z(k), the globalIndex is M and the field map is:
+///|| x | i || y | j || z | k || |B(x,y,z)| ||  M ||
+///  --------------------------------------------
+///|| 2 | 0 || 3 | 0 || 4 | 0 ||  2.323   ||  0 ||
+///|| 2 | 0 || 3 | 0 || 5 | 1 ||  2.334   ||  1 ||
+///|| 2 | 0 || 4 | 1 || 4 | 0 ||  2.325   ||  2 ||
+///|| 2 | 0 || 4 | 1 || 5 | 1 ||  2.331   ||  3 ||
+///|| 3 | 1 || 3 | 0 || 4 | 0 ||  2.323   ||  4 ||
+///|| 3 | 1 || 3 | 0 || 5 | 1 ||  2.334   ||  5 ||
+///|| 3 | 1 || 4 | 1 || 4 | 0 ||  2.325   ||  6 ||
+///|| 3 | 1 || 4 | 1 || 5 | 1 ||  2.331   ||  7 ||
+///
+/// @code
+/// In this case the function would look like:
+/// [](std::array<std::size_t, 3> binsXYZ, std::array<std::size_t, 3> nBinsXYZ)
+/// {
+///   return (binsXYZ.at(0) * (nBinsXYZ.at(1) * nBinsXYZ.at(2))
+///        + binsXYZ.at(1) * nBinsXYZ.at(2)
+///        + binsXYZ.at(2));
+/// }
+/// @endcode
+/// @param[in] fieldMapFile Path to file containing field map in txt format
+/// @param[in] treeName The name of the root tree
+/// @param[in] lengthUnit The unit of the grid points
+/// @param[in] BFieldUnit The unit of the magnetic field
+/// @param[in] firstOctant Flag if set to true indicating that only the
+/// first
+/// octant of the grid points and the BField values has been given and that
+/// the BFieldMap should be created symmetrically for all quadrants.
+/// e.g. we have the grid values z={0,1} with BFieldValues={2,3} on the r
+/// axis.
+/// If the flag is set to true the z-axis grid values will be set to
+/// {-1,0,1}
+/// and the BFieldValues will be set to {3,2,3}.
+detail::InterpolatedMagneticField3 makeMagneticFieldMapXyzFromRoot(
+    const std::function<std::size_t(std::array<std::size_t, 3> binsXYZ,
+                                    std::array<std::size_t, 3> nBinsXYZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, const std::string& treeName,
+    Acts::ActsScalar lengthUnit, Acts::ActsScalar BFieldUnit,
+    bool firstOctant = false);
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapTextIo.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapTextIo.hpp
new file mode 100644
index 00000000..362f8601
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/FieldMapTextIo.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "ActsHelper/MagneticField/MagneticField.hpp"
+
+#include <array>
+#include <cstddef>
+#include <functional>
+#include <string>
+
+namespace ActsHelper {
+
+/// Method to setup the FieldMapper
+/// @param localToGlobalBin Function mapping the local bins of r,z to the
+/// global
+/// bin of the map magnetic field value e.g.: we have small grid with the
+/// values: r={2,3}, z ={4,5}, the corresponding indices are i(r) and j(z),
+/// the
+/// globalIndex is M and the field map is:
+///|| r | i || z | j || |B(r,z)| ||  M ||
+///  -----------------------------------
+///|| 2 | 0 || 4 | 0 ||  2.323   ||  0 ||
+///|| 2 | 0 || 5 | 1 ||  2.334   ||  1 ||
+///|| 3 | 1 || 4 | 0 ||  2.325   ||  2 ||
+///|| 3 | 1 || 5 | 1 ||  2.331   ||  3 ||
+///
+/// @code
+/// In this case the function would look like:
+/// [](std::array<std::size_t, 2> binsRZ, std::array<std::size_t, 2> nBinsRZ) {
+///    return (binsRZ.at(0) * nBinsRZ.at(1) + binsRZ.at(1));
+/// }
+/// @endcode
+/// @param[in] fieldMapFile Path to file containing field map in txt format
+/// @param[in] lengthUnit The unit of the grid points
+/// @param[in] BFieldUnit The unit of the magnetic field
+/// @note This information is only used as a hint for the required size of
+///       the internal vectors. A correct value is not needed, but will help
+///       to speed up the field map initialization process.
+/// @param[in] firstQuadrant Flag if set to true indicating that only the
+/// first
+/// quadrant of the grid points and the BField values has been given and
+/// that
+/// the BFieldMap should be created symmetrically for all quadrants.
+/// e.g. we have the grid values r={0,1} with BFieldValues={2,3} on the r
+/// axis.
+/// If the flag is set to true the r-axis grid values will be set to
+/// {-1,0,1}
+/// and the BFieldValues will be set to {3,2,3}.
+detail::InterpolatedMagneticField2 makeMagneticFieldMapRzFromText(
+    const std::function<std::size_t(std::array<std::size_t, 2> binsRZ,
+                                    std::array<std::size_t, 2> nBinsRZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, Acts::ActsScalar lengthUnit,
+    Acts::ActsScalar BFieldUnit, bool firstQuadrant = false);
+
+/// Method to setup the FieldMapper
+/// @param localToGlobalBin Function mapping the local bins of x,y,z to the
+/// global bin of the map magnetic field value e.g.: we have small grid with
+/// the
+/// values: x={2,3}, y={3,4}, z ={4,5}, the corresponding indices are i(x),
+/// j(y)
+/// and z(k), the globalIndex is M and the field map is:
+///|| x | i || y | j || z | k || |B(x,y,z)| ||  M ||
+///  --------------------------------------------
+///|| 2 | 0 || 3 | 0 || 4 | 0 ||  2.323   ||  0 ||
+///|| 2 | 0 || 3 | 0 || 5 | 1 ||  2.334   ||  1 ||
+///|| 2 | 0 || 4 | 1 || 4 | 0 ||  2.325   ||  2 ||
+///|| 2 | 0 || 4 | 1 || 5 | 1 ||  2.331   ||  3 ||
+///|| 3 | 1 || 3 | 0 || 4 | 0 ||  2.323   ||  4 ||
+///|| 3 | 1 || 3 | 0 || 5 | 1 ||  2.334   ||  5 ||
+///|| 3 | 1 || 4 | 1 || 4 | 0 ||  2.325   ||  6 ||
+///|| 3 | 1 || 4 | 1 || 5 | 1 ||  2.331   ||  7 ||
+///
+/// @code
+/// In this case the function would look like:
+/// [](std::array<std::size_t, 3> binsXYZ, std::array<std::size_t, 3> nBinsXYZ)
+/// {
+///   return (binsXYZ.at(0) * (nBinsXYZ.at(1) * nBinsXYZ.at(2))
+///        + binsXYZ.at(1) * nBinsXYZ.at(2)
+///        + binsXYZ.at(2));
+/// }
+/// @endcode
+/// @param[in] fieldMapFile Path to file containing field map in txt format
+/// @param[in] lengthUnit The unit of the grid points
+/// @param[in] BFieldUnit The unit of the magnetic field
+/// @note This information is only used as a hint for the required size of
+///       the internal vectors. A correct value is not needed, but will help
+///       to speed up the field map initialization process.
+/// @param[in] firstOctant Flag if set to true indicating that only the
+/// first
+/// octant of the grid points and the BField values has been given and that
+/// the BFieldMap should be created symmetrically for all quadrants.
+/// e.g. we have the grid values z={0,1} with BFieldValues={2,3} on the r
+/// axis.
+/// If the flag is set to true the z-axis grid values will be set to
+/// {-1,0,1}
+/// and the BFieldValues will be set to {3,2,3}.
+detail::InterpolatedMagneticField3 makeMagneticFieldMapXyzFromText(
+    const std::function<std::size_t(std::array<std::size_t, 3> binsXYZ,
+                                    std::array<std::size_t, 3> nBinsXYZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, Acts::ActsScalar lengthUnit,
+    Acts::ActsScalar BFieldUnit, bool firstOctant = false);
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/MagneticField.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/MagneticField.hpp
new file mode 100644
index 00000000..0a3b3c2c
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/MagneticField.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/MagneticField/ConstantBField.hpp"
+#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
+#include "Acts/MagneticField/MagneticFieldProvider.hpp"
+#include "Acts/MagneticField/NullBField.hpp"
+#include "Acts/Utilities/Grid.hpp"
+#include "Acts/Utilities/Result.hpp"
+#include "Acts/Utilities/detail/Axis.hpp"
+#include "Acts/Utilities/detail/AxisFwd.hpp"
+#include "Acts/Utilities/detail/grid_helper.hpp"
+#include "ActsHelper/MagneticField/ScalableBField.hpp"
+
+#include <memory>
+#include <variant>
+#include <vector>
+
+namespace ActsHelper::detail {
+
+using InterpolatedMagneticField2 = Acts::InterpolatedBFieldMap<
+    Acts::Grid<Acts::Vector2, Acts::detail::EquidistantAxis,
+               Acts::detail::EquidistantAxis>>;
+
+using InterpolatedMagneticField3 = Acts::InterpolatedBFieldMap<
+    Acts::Grid<Acts::Vector3, Acts::detail::EquidistantAxis,
+               Acts::detail::EquidistantAxis, Acts::detail::EquidistantAxis>>;
+
+}  // namespace ActsHelper::detail
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/ScalableBField.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/ScalableBField.hpp
new file mode 100644
index 00000000..33d9649b
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/MagneticField/ScalableBField.hpp
@@ -0,0 +1,104 @@
+#pragma once
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/MagneticField/MagneticFieldContext.hpp"
+#include "Acts/MagneticField/MagneticFieldProvider.hpp"
+
+namespace ActsHelper {
+
+/// The ScalableBField-specific magnetic field context.
+struct ScalableBFieldContext {
+  Acts::ActsScalar scalor = 1.;
+};
+
+/// A constant magnetic field that is scaled depending on the event context.
+class ScalableBField final : public Acts::MagneticFieldProvider {
+ public:
+  struct Cache {
+    Acts::ActsScalar scalor = 1.;
+
+    /// @brief constructor with context
+    Cache(const Acts::MagneticFieldContext& mctx) {
+      scalor = mctx.get<const ScalableBFieldContext>().scalor;
+    }
+  };
+
+  /// @brief construct constant magnetic field from field vector
+  ///
+  /// @param [in] B magnetic field vector in global coordinate system
+  explicit ScalableBField(Acts::Vector3 B) : m_BField(std::move(B)) {}
+
+  /// @brief construct constant magnetic field from components
+  ///
+  /// @param [in] Bx magnetic field component in global x-direction
+  /// @param [in] By magnetic field component in global y-direction
+  /// @param [in] Bz magnetic field component in global z-direction
+  ScalableBField(Acts::ActsScalar Bx = 0, Acts::ActsScalar By = 0,
+                 Acts::ActsScalar Bz = 0)
+      : m_BField(Bx, By, Bz) {}
+
+  /// @brief retrieve magnetic field value
+  ///
+  /// @param [in] position global position
+  /// @param [in] cache Cache object (is ignored)
+  /// @return magnetic field vector
+  ///
+  /// @note The @p position is ignored and only kept as argument to provide
+  ///       a consistent interface with other magnetic field services.
+  Acts::Result<Acts::Vector3> getField(
+      const Acts::Vector3& /*position*/,
+      MagneticFieldProvider::Cache& gCache) const override {
+    Cache& cache = gCache.as<Cache>();
+    return Acts::Result<Acts::Vector3>::success(m_BField * cache.scalor);
+  }
+
+  /// @brief retrieve magnetic field value & its gradient
+  ///
+  /// @param [in]  position   global position
+  /// @param [out] derivative gradient of magnetic field vector as (3x3)
+  /// matrix
+  /// @param [in] cache Cache object (is ignored)
+  /// @return magnetic field vector
+  ///
+  /// @note The @p position is ignored and only kept as argument to provide
+  ///       a consistent interface with other magnetic field services.
+  /// @note currently the derivative is not calculated
+  /// @todo return derivative
+  Acts::Result<Acts::Vector3> getFieldGradient(
+      const Acts::Vector3& /*position*/, Acts::ActsMatrix<3, 3>& /*derivative*/,
+      MagneticFieldProvider::Cache& gCache) const override {
+    Cache& cache = gCache.as<Cache>();
+    return Acts::Result<Acts::Vector3>::success(m_BField * cache.scalor);
+  }
+
+  Acts::MagneticFieldProvider::Cache makeCache(
+      const Acts::MagneticFieldContext& mctx) const override {
+    return Acts::MagneticFieldProvider::Cache(std::in_place_type<Cache>, mctx);
+  }
+
+  /// @brief check whether given 3D position is inside look-up domain
+  ///
+  /// @param [in] position global 3D position
+  /// @return @c true if position is inside the defined look-up grid,
+  ///         otherwise @c false
+  /// @note The method will always return true for the constant B-Field
+  bool isInside(const Acts::Vector3& /*position*/) const { return true; }
+
+  /// @brief update magnetic field vector from components
+  ///
+  /// @param [in] Bx magnetic field component in global x-direction
+  /// @param [in] By magnetic field component in global y-direction
+  /// @param [in] Bz magnetic field component in global z-direction
+  void setField(double Bx, double By, double Bz) { m_BField << Bx, By, Bz; }
+
+  /// @brief update magnetic field vector
+  ///
+  /// @param [in] B magnetic field vector in global coordinate system
+  void setField(const Acts::Vector3& B) { m_BField = B; }
+
+ private:
+  /// magnetic field vector
+  Acts::Vector3 m_BField;
+};  // namespace BField
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/EventDataTransforms.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/EventDataTransforms.hpp
new file mode 100644
index 00000000..8992d6ba
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/EventDataTransforms.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "ActsHelper/EventData/ProtoTrack.hpp"
+#include "ActsHelper/EventData/SimSeed.hpp"
+
+namespace ActsHelper {
+
+ProtoTrack seedToPrototrack(const SimSeed &seed);
+
+const SimSpacePoint *findSpacePointForIndex(
+    Index index, const SimSpacePointContainer &spacepoints);
+
+SimSeed prototrackToSeed(const ProtoTrack &track,
+                         const SimSpacePointContainer &spacepoints);
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/GroupBy.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/GroupBy.hpp
new file mode 100644
index 00000000..69adff0e
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/GroupBy.hpp
@@ -0,0 +1,133 @@
+#pragma once
+
+#include "ActsHelper/Utilities/Range.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <utility>
+
+namespace ActsHelper {
+
+/// Proxy for iterating over groups of elements within a container.
+///
+/// @note Each group will contain at least one element.
+///
+/// Consecutive elements with the same key (as defined by the KeyGetter) are
+/// placed in one group. The proxy should always be used as part of a
+/// range-based for loop. In combination with structured bindings to reduce the
+/// boilerplate, the group iteration can be written as
+///
+///     for (auto&& [key, elements] : GroupBy<...>(...)) {
+///         // do something with just the key
+///         ...
+///
+///         // iterate over the group elements
+///         for (const auto& element : elements) {
+///             ...
+///         }
+///     }
+///
+template <typename Iterator, typename KeyGetter>
+class GroupBy {
+ public:
+  /// The key type that identifies elements within a group.
+  using Key = std::decay_t<decltype(KeyGetter()(*Iterator()))>;
+  /// A Group is an iterator range with the associated key.
+  using Group = std::pair<Key, Range<Iterator>>;
+  /// Iterator type representing the end of the groups.
+  ///
+  /// The end iterator will not be dereferenced in C++17 range-based loops. It
+  /// can thus be a simpler type without the overhead of the full group iterator
+  /// below.
+  using GroupEndIterator = Iterator;
+  /// Iterator type representing a group of elements.
+  class GroupIterator {
+   public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = Group;
+    using difference_type = std::ptrdiff_t;
+    using pointer = Group*;
+    using reference = Group&;
+
+    constexpr GroupIterator(const GroupBy& groupBy, Iterator groupBegin)
+        : m_groupBy(groupBy),
+          m_groupBegin(groupBegin),
+          m_groupEnd(groupBy.findEndOfGroup(groupBegin)) {}
+    /// Pre-increment operator to advance to the next group.
+    constexpr GroupIterator& operator++() {
+      // make the current end the new group beginning
+      std::swap(m_groupBegin, m_groupEnd);
+      // find the end of the next group starting from the new beginning
+      m_groupEnd = m_groupBy.findEndOfGroup(m_groupBegin);
+      return *this;
+    }
+    /// Post-increment operator to advance to the next group.
+    constexpr GroupIterator operator++(int) {
+      GroupIterator retval = *this;
+      ++(*this);
+      return retval;
+    }
+    /// Dereference operator that returns the pointed-to group of elements.
+    constexpr Group operator*() const {
+      const Key key = (m_groupBegin != m_groupEnd)
+                          ? m_groupBy.m_keyGetter(*m_groupBegin)
+                          : Key();
+      return {key, makeRange(m_groupBegin, m_groupEnd)};
+    }
+
+   private:
+    const GroupBy& m_groupBy;
+    Iterator m_groupBegin;
+    Iterator m_groupEnd;
+
+    friend constexpr bool operator==(const GroupIterator& lhs,
+                                     const GroupEndIterator& rhs) {
+      return lhs.m_groupBegin == rhs;
+    }
+    friend constexpr bool operator!=(const GroupIterator& lhs,
+                                     const GroupEndIterator& rhs) {
+      return !(lhs == rhs);
+    }
+  };
+
+  /// Construct the group-by proxy for an iterator range.
+  constexpr GroupBy(Iterator begin, Iterator end,
+                    KeyGetter keyGetter = KeyGetter())
+      : m_begin(begin), m_end(end), m_keyGetter(std::move(keyGetter)) {}
+  constexpr GroupIterator begin() const {
+    return GroupIterator(*this, m_begin);
+  }
+  constexpr GroupEndIterator end() const { return m_end; }
+  constexpr bool empty() const { return m_begin == m_end; }
+
+ private:
+  Iterator m_begin;
+  Iterator m_end;
+  KeyGetter m_keyGetter;
+
+  /// Find the end of the group that starts at the given position.
+  ///
+  /// This uses a linear search from the start position and thus has linear
+  /// complexity in the group size. It does not assume any ordering of the
+  /// underlying container and is a cache-friendly access pattern.
+  constexpr Iterator findEndOfGroup(Iterator start) const {
+    // check for end so we can safely dereference the start iterator.
+    if (start == m_end) {
+      return start;
+    }
+    // search the first element that does not share a key with the start.
+    return std::find_if_not(std::next(start), m_end,
+                            [this, start](const auto& x) {
+                              return m_keyGetter(x) == m_keyGetter(*start);
+                            });
+  }
+};
+
+/// Construct the group-by proxy for a container.
+template <typename Container, typename KeyGetter>
+auto makeGroupBy(const Container& container, KeyGetter keyGetter)
+    -> GroupBy<decltype(std::begin(container)), KeyGetter> {
+  return {std::begin(container), std::end(container), std::move(keyGetter)};
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Helpers.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Helpers.hpp
new file mode 100644
index 00000000..1f663483
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Helpers.hpp
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <string>
+#include <utility>
+
+#include "TEfficiency.h"
+#include "TFitResult.h"
+#include "TFitResultPtr.h"
+#include "TH1F.h"
+#include "TH2F.h"
+#include "TProfile.h"
+#include "TROOT.h"
+
+class TEfficiency;
+class TH1D;
+class TH1F;
+class TH2F;
+class TProfile;
+
+namespace ActsHelper::PlotHelpers {
+/// @brief Nested binning struct for booking plots
+class Binning {
+ public:
+  Binning() : m_bins({0.0}) {}
+
+  Binning(std::string title, int bins, double bMin, double bMax)
+      : m_title(std::move(title)) {
+    const auto step = (bMax - bMin) / bins;
+    m_bins.resize(bins + 1);
+    std::generate(m_bins.begin(), m_bins.end(), [&, v = bMin]() mutable {
+      auto r = v;
+      v += step;
+      return r;
+    });
+  }
+
+  Binning(std::string title, std::vector<double> bins)
+      : m_title(std::move(title)), m_bins(std::move(bins)) {}
+
+  const auto& title() const { return m_title; }
+  auto nBins() const { return m_bins.size() - 1; }
+  const double* data() const { return m_bins.data(); }
+  auto low() const { return m_bins.front(); }
+  auto high() const { return m_bins.back(); }
+
+ private:
+  std::string m_title;
+  std::vector<double> m_bins;
+};
+
+/// @brief book a 1D histogram
+/// @param histName the name of histogram
+/// @param histTitle the title of histogram
+/// @param varBinning the binning info of variable
+/// @return histogram pointer
+TH1F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varBinning);
+
+/// @brief book a 2D histogram
+/// @param histName the name of histogram
+/// @param histTitle the title of histogram
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return histogram pointer
+TH2F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a 1D histogram
+/// @param hist histogram to fill
+/// @param value value to fill
+/// @param weight weight to fill
+void fillHisto(TH1F* hist, float value, float weight = 1.0);
+
+/// @brief fill a 2D histogram
+/// @param hist histogram to fill
+/// @param xValue x value to fill
+/// @param yValue y value to fill
+/// @param weight weight to fill
+void fillHisto(TH2F* hist, float xValue, float yValue, float weight = 1.0);
+
+/// @brief extract details, i.e. mean and width of a 1D histogram and fill
+/// them into histograms
+/// @param inputHist histogram to investigate
+/// @param j  which bin number of meanHist and widthHist to fill
+/// @param meanHist histogram to fill the mean value of inputHist
+/// @param widthHist  histogram to fill the width value of inputHist
+///
+/// @todo  write specialized helper class to extract details of hists
+void anaHisto(TH1D* inputHist, int j, TH1F* meanHist, TH1F* widthHist);
+
+/// @brief book a 1D efficiency plot
+/// @param effName the name of plot
+/// @param effTitle the title of plot
+/// @param varBinning the binning info of variable
+/// @return TEfficiency pointer
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varBinning);
+
+/// @brief book a 2D efficiency plot
+/// @param effName the name of plot
+/// @param effTitle the title of plot
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return TEfficiency pointer
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a 1D efficiency plot
+/// @param efficiency plot to fill
+/// @param value value to fill
+/// @param status bool to denote passed or not
+void fillEff(TEfficiency* efficiency, float value, bool status);
+
+/// @brief fill a 2D efficiency plot
+/// @param efficiency plot to fill
+/// @param xValue x value to fill
+/// @param yValue y value to fill
+/// @param status bool to denote passed or not
+void fillEff(TEfficiency* efficiency, float xValue, float yValue, bool status);
+
+/// @brief book a TProfile plot
+/// @param profName the name of plot
+/// @param profTitle the title of plot
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return TProfile pointer
+TProfile* bookProf(const char* profName, const char* profTitle,
+                   const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a TProfile plot
+/// @param profile plot to fill
+/// @param xValue  xvalue to fill
+/// @param yValue  yvalue to fill
+/// @param weight weight to fill
+void fillProf(TProfile* profile, float xValue, float yValue,
+              float weight = 1.0);
+
+}  // namespace ActsHelper::PlotHelpers
diff --git a/Reconstruction/RecActsTracking/src/utils/Options.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Options.hpp
similarity index 98%
rename from Reconstruction/RecActsTracking/src/utils/Options.hpp
rename to Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Options.hpp
index 897233cb..4278d8d9 100644
--- a/Reconstruction/RecActsTracking/src/utils/Options.hpp
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Options.hpp
@@ -1,10 +1,12 @@
+#pragma once
+
 #include <array>
 #include <cstddef>
 #include <iosfwd>
 #include <optional>
 #include <vector>
 
-namespace Options {
+namespace ActsHelper::Options {
 
 /// @defgroup option-types Additional types for program options
 ///
@@ -156,4 +158,4 @@ inline std::ostream& operator<<(std::ostream& os,
   return os;
 }
 
-}  // namespace Options
+}  // namespace ActsHelper::Options
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/OptionsFwd.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/OptionsFwd.hpp
new file mode 100644
index 00000000..6ae5c0d7
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/OptionsFwd.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace boost::program_options {
+class options_description;
+class variables_map;
+}  // namespace boost::program_options
+
+namespace ActsHelper::Options {
+using Description = ::boost::program_options::options_description;
+using Variables = ::boost::program_options::variables_map;
+}  // namespace ActsHelper::Options
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Paths.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Paths.hpp
new file mode 100644
index 00000000..bff1713d
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Paths.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace ActsHelper {
+
+/// Ensure that the given directory exists and is writable.
+///
+/// @return Canonical path to the directory.
+///
+/// Will create missing directories and throw on any error.
+std::string ensureWritableDirectory(const std::string& dir);
+
+/// Join dir and name into one path with correct handling of empty dirs.
+std::string joinPaths(const std::string& dir, const std::string& name);
+
+/// Construct a file path of the form `[<dir>/]event<XXXXXXXXX>-<name>`.
+///
+/// @params dir output directory, current directory if empty
+/// @params name basic filename
+/// @params event event number
+std::string perEventFilepath(const std::string& dir, const std::string& name,
+                             std::size_t event);
+
+/// Determine the range of available events in a directory of per-event files.
+///
+/// @params dir input directory, current directory if empty
+/// @params name base filename
+/// @return first and last+1 event number
+/// @returns {0, 0} when no matching files could be found
+///
+/// Event files must be named `[<dir>/]event<XXXXXXXXX>-<name>` to be considered
+std::pair<std::size_t, std::size_t> determineEventFilesRange(
+    const std::string& dir, const std::string& name);
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Range.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Range.hpp
new file mode 100644
index 00000000..72ad96f9
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/Range.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <iterator>
+#include <utility>
+
+namespace ActsHelper {
+
+/// A wrapper around a pair of iterators to simplify range-based loops.
+///
+/// Some standard library algorithms return pairs of iterators to identify
+/// a sub-range. This wrapper simplifies the iteration and should be used as
+/// follows:
+///
+///     for (auto x : makeRange(std::equal_range(...)) {
+///         ...
+///     }
+///
+template <typename Iterator>
+class Range {
+ public:
+  Range(Iterator b, Iterator e) : m_begin(b), m_end(e) {}
+  Range(Range&&) = default;
+  Range(const Range&) = default;
+  ~Range() = default;
+  Range& operator=(Range&&) = default;
+  Range& operator=(const Range&) = default;
+
+  Iterator begin() const { return m_begin; }
+  Iterator end() const { return m_end; }
+  bool empty() const { return m_begin == m_end; }
+  std::size_t size() const { return std::distance(m_begin, m_end); }
+
+ private:
+  Iterator m_begin;
+  Iterator m_end;
+};
+
+template <typename Iterator>
+Range<Iterator> makeRange(Iterator begin, Iterator end) {
+  return Range<Iterator>(begin, end);
+}
+
+template <typename Iterator>
+Range<Iterator> makeRange(std::pair<Iterator, Iterator> range) {
+  return Range<Iterator>(range.first, range.second);
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/tbbWrap.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/tbbWrap.hpp
new file mode 100644
index 00000000..57e98585
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Utilities/tbbWrap.hpp
@@ -0,0 +1,166 @@
+#pragma once
+
+// uncomment to remove all use of tbb library.
+// #define ACTS_EXAMPLES_NO_TBB
+
+#ifdef ACTS_EXAMPLES_NO_TBB
+#define ACTS_EXAMPLES_WITH_TBB(a)
+#include <stdexcept>
+#else
+#define ACTS_EXAMPLES_WITH_TBB(a) a
+#include <optional>
+
+#include <tbb/parallel_for.h>
+#include <tbb/queuing_mutex.h>
+#include <tbb/task_arena.h>
+#endif
+
+/// Wrapper for most of the tbb functions that we use in Sequencer.
+///
+/// It disables the use of tbb if nthreads=1.
+/// Note that only a small subset of tbb functions are implemented, and
+/// tbb::blocked_range (which doesn't require any thread setup) is still taken
+/// from the tbb library.
+///
+/// However, if ACTS_EXAMPLES_NO_TBB is defined, then don't use tbb library at
+/// all (requires nthreads=1 or -1). This allows the ACTS Examples to be built
+/// without the tbb library (and reduces the dependency on ROOT).
+/// In this case, we provide our own minimal implementation of
+/// tbb::blocked_range.
+///
+/// Based on an idea from
+///   https://stackoverflow.com/questions/59736661/how-to-completely-switch-off-threading-in-tbb-code
+
+#ifdef ACTS_EXAMPLES_NO_TBB
+namespace ActsHelper::tbb {
+namespace task_arena {
+constexpr int automatic = -1;
+}  // namespace task_arena
+
+template <typename Value>
+struct blocked_range {
+  blocked_range(Value begin_, Value end_) : my_end(end_), my_begin(begin_) {}
+  Value begin() const { return my_begin; }
+  Value end() const { return my_end; }
+
+ private:
+  Value my_end;
+  Value my_begin;
+};
+}  // namespace ActsHelper::tbb
+#endif
+
+namespace ActsHelper::tbbWrap {
+/// enableTBB keeps a record of whether we are multi-threaded (nthreads!=1) or
+/// not. This is set once in task_arena and stored globally.
+/// This means that enableTBB(nthreads) itself is not thread-safe. That should
+/// be fine because the task_arena is initialised before spawning any threads.
+/// If multi-threading is ever enabled, then it is not disabled.
+static bool enableTBB(int nthreads = -99) {
+  static bool setting = false;
+  if (nthreads != -99) {
+#ifdef ACTS_EXAMPLES_NO_TBB
+    if (nthreads > 1) {
+      throw std::runtime_error(
+          "tbb is not available, so can't do multi-threading.");
+    }
+#else
+    bool newSetting = (nthreads != 1);
+    if (!setting && newSetting) {
+      setting = newSetting;
+    }
+#endif
+  }
+  return setting;
+}
+
+/// Small wrapper for tbb::task_arena.
+/// Note that the tbbWrap::task_arena constructor is not thread-safe.
+/// That should be fine because the task_arena is initialised before spawning
+/// any threads.
+class task_arena {
+#ifndef ACTS_EXAMPLES_NO_TBB
+  std::optional<tbb::task_arena> tbb;
+#endif
+
+ public:
+  task_arena(int nthreads = tbb::task_arena::automatic,
+             unsigned ACTS_EXAMPLES_WITH_TBB(res) = 1) {
+    if (enableTBB(nthreads)) {
+#ifndef ACTS_EXAMPLES_NO_TBB
+      tbb.emplace(nthreads, res);
+#endif
+    }
+  }
+
+  template <typename F>
+  void execute(const F& f) {
+#ifndef ACTS_EXAMPLES_NO_TBB
+    if (tbb) {
+      tbb->execute(f);
+    } else
+#endif
+    {
+      f();
+    }
+  }
+};
+
+/// Small wrapper for tbb::parallel_for.
+class parallel_for {
+ public:
+  template <typename R, typename F>
+  parallel_for(const R& r, const F& f) {
+#ifndef ACTS_EXAMPLES_NO_TBB
+    if (enableTBB()) {
+      tbb::parallel_for(r, f);
+    } else
+#endif
+    {
+      for (auto i = r.begin(); i != r.end(); ++i) {  // use default grainsize=1
+        f(R(i, i + 1));
+      }
+    }
+  }
+};
+
+/// Small wrapper for tbb::queuing_mutex and tbb::queuing_mutex::scoped_lock.
+class queuing_mutex {
+#ifndef ACTS_EXAMPLES_NO_TBB
+  std::optional<tbb::queuing_mutex> tbb;
+#endif
+
+ public:
+  queuing_mutex() {
+#ifndef ACTS_EXAMPLES_NO_TBB
+    if (enableTBB()) {
+      tbb.emplace();
+    }
+#endif
+  }
+
+  class scoped_lock {
+#ifndef ACTS_EXAMPLES_NO_TBB
+    std::optional<tbb::queuing_mutex::scoped_lock> tbb;
+#endif
+
+   public:
+    scoped_lock() {
+#ifndef ACTS_EXAMPLES_NO_TBB
+      if (enableTBB()) {
+        tbb.emplace();
+      }
+#endif
+    }
+
+    scoped_lock(queuing_mutex& ACTS_EXAMPLES_WITH_TBB(m)) {
+#ifndef ACTS_EXAMPLES_NO_TBB
+      if (enableTBB()) {
+        tbb.emplace(*m.tbb);
+      }
+#endif
+    }
+  };
+};
+
+}  // namespace ActsHelper::tbbWrap
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/DuplicationPlotTool.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/DuplicationPlotTool.hpp
new file mode 100644
index 00000000..d5655b74
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/DuplicationPlotTool.hpp
@@ -0,0 +1,96 @@
+#pragma once
+
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+
+class TEfficiency;
+class TProfile;
+namespace ActsFatras {
+class Particle;
+}  // namespace ActsFatras
+
+namespace ActsHelper {
+
+// Tools to make duplication rate and duplication number plots to show tracking
+// duplication.
+//
+// The duplication is investigated for those truth-matched reco tracks. If there
+// are a few reco tracks matched to the same truth particle, the reco track with
+// the highest matching probability is tagges as 'real' and the others are
+// 'duplicated'.
+class DuplicationPlotTool {
+ public:
+  /// @brief The nested configuration struct
+  struct Config {
+    std::map<std::string, PlotHelpers::Binning> varBinning = {
+        {"Eta", PlotHelpers::Binning("#eta", 40, -4, 4)},
+        {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+        {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 100)},
+        {"Num", PlotHelpers::Binning("N", 30, -0.5, 29.5)}};
+  };
+
+  /// @brief Nested Cache struct
+  struct DuplicationPlotCache {
+    TProfile* nDuplicated_vs_pT;         ///< Number of duplicated tracks vs pT
+    TProfile* nDuplicated_vs_eta;        ///< Number of duplicated tracks vs eta
+    TProfile* nDuplicated_vs_phi;        ///< Number of duplicated tracks vs phi
+    TEfficiency* duplicationRate_vs_pT;  ///< Tracking duplication rate vs pT
+    TEfficiency* duplicationRate_vs_eta;  ///< Tracking duplication rate vs eta
+    TEfficiency* duplicationRate_vs_phi;  ///< Tracking duplication rate vs phi
+  };
+
+  /// Constructor
+  ///
+  /// @param cfg Configuration struct
+  /// @param lvl Message level declaration
+  DuplicationPlotTool(const Config& cfg, Acts::Logging::Level lvl);
+
+  /// @brief book the duplication plots
+  ///
+  /// @param duplicationPlotCache the cache for duplication plots
+  void book(DuplicationPlotCache& duplicationPlotCache) const;
+
+  /// @brief fill duplication rate w.r.t. fitted track parameters
+  ///
+  /// @param duplicationPlotCache cache object for duplication plots
+  /// @param fittedParameters fitted track parameters of this track
+  /// @param status the (truth-matched) reconstructed track is duplicated or not
+  void fill(DuplicationPlotCache& duplicationPlotCache,
+            const Acts::BoundTrackParameters& fittedParameters,
+            bool status) const;
+
+  /// @brief fill number of duplicated tracks for a truth particle seed
+  ///
+  /// @param duplicationPlotCache cache object for duplication plots
+  /// @param truthParticle the truth Particle
+  /// @param nDuplicatedTracks the number of duplicated tracks
+  void fill(DuplicationPlotCache& duplicationPlotCache,
+            const ActsFatras::Particle& truthParticle,
+            std::size_t nDuplicatedTracks) const;
+
+  /// @brief write the duplication plots to file
+  ///
+  /// @param duplicationPlotCache cache object for duplication plots
+  void write(const DuplicationPlotCache& duplicationPlotCache) const;
+
+  /// @brief delete the duplication plots
+  ///
+  /// @param duplicationPlotCache cache object for duplication plots
+  void clear(DuplicationPlotCache& duplicationPlotCache) const;
+
+ private:
+  Config m_cfg;                                  ///< The Config class
+  std::unique_ptr<const Acts::Logger> m_logger;  ///< The logging instance
+
+  /// The logger
+  const Acts::Logger& logger() const { return *m_logger; }
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/EffPlotTool.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/EffPlotTool.hpp
new file mode 100644
index 00000000..47185540
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/EffPlotTool.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <map>
+#include <memory>
+#include <string>
+
+class TEfficiency;
+namespace ActsFatras {
+class Particle;
+}  // namespace ActsFatras
+
+namespace ActsHelper {
+
+// Tools to make efficiency plots to show tracking efficiency.
+// For the moment, the efficiency is taken as the fraction of successfully
+// smoothed track over all tracks
+class EffPlotTool {
+ public:
+  /// @brief The nested configuration struct
+  struct Config {
+    std::map<std::string, PlotHelpers::Binning> varBinning = {
+        {"Eta", PlotHelpers::Binning("#eta", 40, -4, 4)},
+        {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+        {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 100)},
+        {"DeltaR", PlotHelpers::Binning("#Delta R", 100, 0, 0.3)}};
+  };
+
+  /// @brief Nested Cache struct
+  struct EffPlotCache {
+    TEfficiency* trackEff_vs_pT{nullptr};   ///< Tracking efficiency vs pT
+    TEfficiency* trackEff_vs_eta{nullptr};  ///< Tracking efficiency vs eta
+    TEfficiency* trackEff_vs_phi{nullptr};  ///< Tracking efficiency vs phi
+    TEfficiency* trackEff_vs_DeltaR{
+        nullptr};  ///< Tracking efficiency vs distance to the closest truth
+                   ///< particle
+  };
+
+  /// Constructor
+  ///
+  /// @param cfg Configuration struct
+  /// @param lvl Message level declaration
+  EffPlotTool(const Config& cfg, Acts::Logging::Level lvl);
+
+  /// @brief book the efficiency plots
+  ///
+  /// @param effPlotCache the cache for efficiency plots
+  void book(EffPlotCache& effPlotCache) const;
+
+  /// @brief fill efficiency plots
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  /// @param truthParticle the truth Particle
+  /// @param deltaR the distance to the closest truth particle
+  /// @param status the reconstruction status
+  void fill(EffPlotCache& effPlotCache,
+            const ActsFatras::Particle& truthParticle, double deltaR,
+            bool status) const;
+
+  /// @brief write the efficiency plots to file
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  void write(const EffPlotCache& effPlotCache) const;
+
+  /// @brief delete the efficiency plots
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  void clear(EffPlotCache& effPlotCache) const;
+
+ private:
+  Config m_cfg;                                  ///< The Config class
+  std::unique_ptr<const Acts::Logger> m_logger;  ///< The logging instance
+
+  /// The logger
+  const Acts::Logger& logger() const { return *m_logger; }
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/FakeRatePlotTool.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/FakeRatePlotTool.hpp
new file mode 100644
index 00000000..b5a27aba
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/FakeRatePlotTool.hpp
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+
+class TEfficiency;
+class TH2F;
+namespace ActsFatras {
+class Particle;
+}  // namespace ActsFatras
+
+namespace ActsHelper {
+
+// Tools to make fake rate plots to show tracking fake rate.
+//
+// The fake rate is investigated for all reco tracks. A track is 'fake' if it's
+// not matched with truth.
+class FakeRatePlotTool {
+ public:
+  /// @brief The nested configuration struct
+  struct Config {
+    std::map<std::string, PlotHelpers::Binning> varBinning = {
+        {"Eta", PlotHelpers::Binning("#eta", 40, -4, 4)},
+        {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+        {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 100)},
+        {"Num", PlotHelpers::Binning("N", 30, -0.5, 29.5)}};
+  };
+
+  /// @brief Nested Cache struct
+  struct FakeRatePlotCache {
+    TH2F* nReco_vs_pT;          ///< Number of reco tracks vs pT scatter plot
+    TH2F* nTruthMatched_vs_pT;  ///< Number of truth-matched reco tracks vs pT
+                                ///< scatter plot
+    TH2F* nFake_vs_pT;   ///< Number of fake (truth-unmatched) tracks vs pT
+                         ///< scatter plot
+    TH2F* nReco_vs_eta;  ///< Number of reco tracks vs eta scatter plot
+    TH2F* nTruthMatched_vs_eta;  ///< Number of truth-matched reco tracks vs eta
+                                 ///< scatter plot
+    TH2F* nFake_vs_eta;  ///< Number of fake (truth-unmatched) tracks vs eta
+                         ///< scatter plot
+    TEfficiency* fakeRate_vs_pT;   ///< Tracking fake rate vs pT
+    TEfficiency* fakeRate_vs_eta;  ///< Tracking fake rate vs eta
+    TEfficiency* fakeRate_vs_phi;  ///< Tracking fake rate vs phi
+  };
+
+  /// Constructor
+  ///
+  /// @param cfg Configuration struct
+  /// @param lvl Message level declaration
+  FakeRatePlotTool(const Config& cfg, Acts::Logging::Level lvl);
+
+  /// @brief book the fake rate plots
+  ///
+  /// @param fakeRatePlotCache the cache for fake rate plots
+  void book(FakeRatePlotCache& fakeRatePlotCache) const;
+
+  /// @brief fill fake rate w.r.t. fitted track parameters
+  ///
+  /// @param fakeRatePlotCache cache object for fake rate plots
+  /// @param fittedParameters fitted track parameters of this track
+  /// @param status the reconstructed track is fake or not
+  void fill(FakeRatePlotCache& fakeRatePlotCache,
+            const Acts::BoundTrackParameters& fittedParameters,
+            bool status) const;
+
+  /// @brief fill number of reco/truth-matched/fake tracks for a truth particle
+  /// seed
+  ///
+  /// @param fakeRatePlotCache cache object for fake rate plots
+  /// @param truthParticle the truth Particle
+  /// @param nTruthMatchedTracks the number of truth-Matched tracks
+  /// @param nFakeTracks the number of fake tracks
+  void fill(FakeRatePlotCache& fakeRatePlotCache,
+            const ActsFatras::Particle& truthParticle,
+            std::size_t nTruthMatchedTracks, std::size_t nFakeTracks) const;
+
+  /// @brief write the fake rate plots to file
+  ///
+  /// @param fakeRatePlotCache cache object for fake rate plots
+  void write(const FakeRatePlotCache& fakeRatePlotCache) const;
+
+  /// @brief delete the fake rate plots
+  ///
+  /// @param fakeRatePlotCache cache object for fake rate plots
+  void clear(FakeRatePlotCache& fakeRatePlotCache) const;
+
+ private:
+  Config m_cfg;                                  ///< The Config class
+  std::unique_ptr<const Acts::Logger> m_logger;  ///< The logging instance
+
+  /// The logger
+  const Acts::Logger& logger() const { return *m_logger; }
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/ResPlotTool.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/ResPlotTool.hpp
new file mode 100644
index 00000000..8ef6e85a
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/ResPlotTool.hpp
@@ -0,0 +1,122 @@
+#pragma once
+
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+class TH1F;
+class TH2F;
+namespace ActsFatras {
+class Particle;
+}  // namespace ActsFatras
+
+namespace ActsHelper {
+
+// Tools to make hists to show residual, i.e. smoothed_parameter -
+// truth_parameter, and pull, i.e. (smoothed_parameter -
+// truth_parameter)/smoothed_paramter_error, of track parameters at perigee
+// surface
+class ResPlotTool {
+ public:
+  /// @brief Nested configuration struct
+  struct Config {
+    /// parameter sets to do plots
+    std::vector<std::string> paramNames = {"d0",    "z0",  "phi",
+                                           "theta", "qop", "t"};
+
+    /// Binning info for variables
+    std::map<std::string, PlotHelpers::Binning> varBinning = {
+        {"Eta", PlotHelpers::Binning("#eta", 40, -4, 4)},
+        {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 100)},
+        {"Pull", PlotHelpers::Binning("pull", 100, -5, 5)},
+        {"Residual_d0", PlotHelpers::Binning("r_{d0} [mm]", 100, -0.5, 0.5)},
+        {"Residual_z0", PlotHelpers::Binning("r_{z0} [mm]", 100, -0.5, 0.5)},
+        {"Residual_phi",
+         PlotHelpers::Binning("r_{#phi} [rad]", 100, -0.01, 0.01)},
+        {"Residual_theta",
+         PlotHelpers::Binning("r_{#theta} [rad]", 100, -0.01, 0.01)},
+        {"Residual_qop",
+         PlotHelpers::Binning("r_{q/p} [c/GeV]", 100, -0.1, 0.1)},
+        {"Residual_t", PlotHelpers::Binning("r_{t} [s]", 100, -1000, 1000)}};
+  };
+
+  /// @brief Nested Cache struct
+  struct ResPlotCache {
+    std::map<std::string, TH1F*> res;         ///< Residual distribution
+    std::map<std::string, TH2F*> res_vs_eta;  ///< Residual vs eta scatter plot
+    std::map<std::string, TH1F*>
+        resMean_vs_eta;  ///< Residual mean vs eta distribution
+    std::map<std::string, TH1F*>
+        resWidth_vs_eta;  ///< Residual width vs eta distribution
+    std::map<std::string, TH2F*> res_vs_pT;  ///< Residual vs pT scatter plot
+    std::map<std::string, TH1F*>
+        resMean_vs_pT;  ///< Residual mean vs pT distribution
+    std::map<std::string, TH1F*>
+        resWidth_vs_pT;  ///< Residual width vs pT distribution
+
+    std::map<std::string, TH1F*> pull;         ///< Pull distribution
+    std::map<std::string, TH2F*> pull_vs_eta;  ///< Pull vs eta scatter plot
+    std::map<std::string, TH1F*>
+        pullMean_vs_eta;  ///< Pull mean vs eta distribution
+    std::map<std::string, TH1F*>
+        pullWidth_vs_eta;  ///< Pull width vs eta distribution
+    std::map<std::string, TH2F*> pull_vs_pT;  ///< Pull vs pT scatter plot
+    std::map<std::string, TH1F*>
+        pullMean_vs_pT;  ///< Pull mean vs pT distribution
+    std::map<std::string, TH1F*>
+        pullWidth_vs_pT;  ///< Pull width vs pT distribution
+  };
+
+  /// Constructor
+  ///
+  /// @param cfg Configuration struct
+  /// @param level Message level declaration
+  ResPlotTool(const Config& cfg, Acts::Logging::Level lvl);
+
+  /// @brief book the histograms
+  ///
+  /// @param resPlotCache the cache for residual/pull histograms
+  void book(ResPlotCache& resPlotCache) const;
+
+  /// @brief fill the histograms
+  ///
+  /// @param resPlotCache the cache for residual/pull histograms
+  /// @param gctx the geometry context
+  /// @param truthParticle the truth particle
+  /// @param fittedParamters the fitted parameters at perigee surface
+  void fill(ResPlotCache& resPlotCache, const Acts::GeometryContext& gctx,
+            const ActsFatras::Particle& truthParticle,
+            const Acts::BoundTrackParameters& fittedParamters) const;
+
+  /// @brief extract the details of the residual/pull plots and fill details
+  ///
+  /// into separate histograms
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void refinement(ResPlotCache& resPlotCache) const;
+
+  /// @brief write the histograms to output file
+  ///
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void write(const ResPlotCache& resPlotCache) const;
+
+  /// @brief delete the histograms
+  ///
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void clear(ResPlotCache& resPlotCache) const;
+
+ private:
+  Config m_cfg;                                  ///< The config class
+  std::unique_ptr<const Acts::Logger> m_logger;  ///< The logging instance
+
+  /// The logger
+  const Acts::Logger& logger() const { return *m_logger; }
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackClassification.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackClassification.hpp
new file mode 100644
index 00000000..a1ad8daf
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackClassification.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/ProtoTrack.hpp"
+#include "ActsHelper/EventData/Trajectories.hpp"
+#include "ActsFatras/EventData/Barcode.hpp"
+
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+namespace ActsHelper {
+struct Trajectories;
+
+/// Associate a particle to its hit count within a proto track.
+struct ParticleHitCount {
+  ActsFatras::Barcode particleId;
+  std::size_t hitCount;
+};
+
+/// Identify all particles that contribute to the proto track.
+///
+/// @param[in] hitParticlesMap Map hit indices to contributing particles
+/// @param[in] protoTrack The proto track to classify
+/// @param[out] particleHitCounts List of contributing particles
+///
+/// The list of contributing particles is ordered according to their hit count,
+/// i.e. the first element is the majority particle that contributes the most
+/// hits to the track. There can be both hits without a generating particle
+/// (noise hits) and hits that have more than one generating particle. The sum
+/// of the particle hit count must not be identical to the size of the proto
+/// track.
+void identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const ProtoTrack& protoTrack,
+    std::vector<ParticleHitCount>& particleHitCounts);
+
+/// Identify all particles that contribute to a trajectory.
+///
+/// @param[in] hitParticlesMap Map hit indices to contributing particles
+/// @param[in] trajectories The input trajectories to classify
+/// @param[in] trajectoryTip Which trajectory in the trajectories to use
+/// @param[out] particleHitCounts List of contributing particles
+///
+/// See `identifyContributingParticles` for proto tracks for further
+/// information.
+void identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const Trajectories& trajectories, std::size_t trajectoryTip,
+    std::vector<ParticleHitCount>& particleHitCounts);
+
+void identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const ConstTrackContainer::ConstTrackProxy& track,
+    std::vector<ParticleHitCount>& particleHitCounts);
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackSummaryPlotTool.hpp b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackSummaryPlotTool.hpp
new file mode 100644
index 00000000..e76a7d56
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/include/ActsHelper/Validation/TrackSummaryPlotTool.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+
+class TProfile;
+
+namespace ActsHelper {
+
+// Tools to make track info plots to show tracking track info.
+class TrackSummaryPlotTool {
+ public:
+  /// @brief The nested configuration struct
+  struct Config {
+    std::map<std::string, PlotHelpers::Binning> varBinning = {
+        {"Eta", PlotHelpers::Binning("#eta", 40, -4, 4)},
+        {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+        {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 100)},
+        {"Num", PlotHelpers::Binning("N", 30, -0.5, 29.5)}};
+  };
+
+  /// @brief Nested Cache struct
+  struct TrackSummaryPlotCache {
+    TProfile* nStates_vs_eta;  ///< Number of total states vs eta
+    TProfile*
+        nMeasurements_vs_eta;    ///< Number of non-outlier measurements vs eta
+    TProfile* nHoles_vs_eta;     ///< Number of holes vs eta
+    TProfile* nOutliers_vs_eta;  ///< Number of outliers vs eta
+    TProfile* nSharedHits_vs_eta;  ///< Number of Shared Hits vs eta
+    TProfile* nStates_vs_pt;       ///< Number of total states vs pt
+    TProfile*
+        nMeasurements_vs_pt;      ///< Number of non-outlier measurements vs pt
+    TProfile* nHoles_vs_pt;       ///< Number of holes vs pt
+    TProfile* nOutliers_vs_pt;    ///< Number of outliers vs pt
+    TProfile* nSharedHits_vs_pt;  ///< Number of Shared Hits vs pt
+  };
+
+  /// Constructor
+  ///
+  /// @param cfg Configuration struct
+  /// @param lvl Message level declaration
+  TrackSummaryPlotTool(const Config& cfg, Acts::Logging::Level lvl);
+
+  /// @brief book the track info plots
+  ///
+  /// @param trackSummaryPlotCache the cache for track info plots
+  void book(TrackSummaryPlotCache& trackSummaryPlotCache) const;
+
+  /// @brief fill reco track info w.r.t. fitted track parameters
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  /// @param fittedParameters fitted track parameters of this track
+  /// @param nStates number of track states
+  /// @param nMeasurements number of measurements
+  /// @param nOutliers number of outliers
+  /// @param nHoles number of holes
+  void fill(TrackSummaryPlotCache& trackSummaryPlotCache,
+            const Acts::BoundTrackParameters& fittedParameters,
+            std::size_t nStates, std::size_t nMeasurements,
+            std::size_t Outliers, std::size_t nHoles,
+            std::size_t nSharedHits) const;
+
+  /// @brief write the track info plots to file
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  void write(const TrackSummaryPlotCache& trackSummaryPlotCache) const;
+
+  /// @brief delete the track info plots
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  void clear(TrackSummaryPlotCache& trackSummaryPlotCache) const;
+
+ private:
+  Config m_cfg;                                  ///< The Config class
+  std::unique_ptr<const Acts::Logger> m_logger;  ///< The logging instance
+
+  /// The logger
+  const Acts::Logger& logger() const { return *m_logger; }
+};
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GlobalChiSquareFitterFunction.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GlobalChiSquareFitterFunction.cpp
new file mode 100644
index 00000000..64c3bfbc
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GlobalChiSquareFitterFunction.cpp
@@ -0,0 +1,157 @@
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/MeasurementCalibration.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace Acts {
+class MagneticFieldProvider;
+class SourceLink;
+class Surface;
+class TrackingGeometry;
+}  // namespace Acts
+
+namespace {
+
+using Stepper = Acts::EigenStepper<>;
+using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
+using Fitter =
+    Acts::Experimental::Gx2Fitter<Propagator, Acts::VectorMultiTrajectory>;
+using DirectPropagator = Acts::Propagator<Stepper, Acts::DirectNavigator>;
+using DirectFitter =
+    Acts::KalmanFitter<DirectPropagator, Acts::VectorMultiTrajectory>;
+
+using TrackContainer =
+    Acts::TrackContainer<Acts::VectorTrackContainer,
+                         Acts::VectorMultiTrajectory, std::shared_ptr>;
+
+using namespace ActsHelper;
+
+struct GlobalChiSquareFitterFunctionImpl final : public TrackFitterFunction {
+  Fitter fitter;
+  DirectFitter directFitter;
+
+  bool multipleScattering = false;
+  bool energyLoss = false;
+  Acts::FreeToBoundCorrection freeToBoundCorrection;
+  std::size_t nUpdateMax = 5;
+  double relChi2changeCutOff = 1e-7;
+
+  IndexSourceLink::SurfaceAccessor m_slSurfaceAccessor;
+
+  GlobalChiSquareFitterFunctionImpl(Fitter&& f, DirectFitter&& df,
+                                    const Acts::TrackingGeometry& trkGeo)
+      : fitter(std::move(f)),
+        directFitter(std::move(df)),
+        m_slSurfaceAccessor{trkGeo} {}
+
+  template <typename calibrator_t>
+  auto makeGx2fOptions(const GeneralFitterOptions& options,
+                       const calibrator_t& calibrator) const {
+    Acts::Experimental::Gx2FitterExtensions<Acts::VectorMultiTrajectory>
+        extensions;
+    extensions.calibrator.connect<&calibrator_t::calibrate>(&calibrator);
+
+    extensions.surfaceAccessor
+        .connect<&IndexSourceLink::SurfaceAccessor::operator()>(
+            &m_slSurfaceAccessor);
+
+    const Acts::Experimental::Gx2FitterOptions gx2fOptions(
+        options.geoContext, options.magFieldContext, options.calibrationContext,
+        extensions, options.propOptions, &(*options.referenceSurface),
+        multipleScattering, energyLoss, freeToBoundCorrection, nUpdateMax,
+        relChi2changeCutOff);
+
+    return gx2fOptions;
+  }
+
+  TrackFitterResult operator()(const std::vector<Acts::SourceLink>& sourceLinks,
+                               const TrackParameters& initialParameters,
+                               const GeneralFitterOptions& options,
+                               const MeasurementCalibratorAdapter& calibrator,
+                               TrackContainer& tracks) const override {
+    const auto gx2fOptions = makeGx2fOptions(options, calibrator);
+    return fitter.fit(sourceLinks.begin(), sourceLinks.end(), initialParameters,
+                      gx2fOptions, tracks);
+  }
+
+  // We need a placeholder for the directNavigator overload. Otherwise, we would
+  // have an unimplemented pure virtual method in a final class.
+  TrackFitterResult operator()(
+      const std::vector<Acts::SourceLink>& /*sourceLinks*/,
+      const TrackParameters& /*initialParameters*/,
+      const GeneralFitterOptions& /*options*/,
+      const RefittingCalibrator& /*calibrator*/,
+      const std::vector<const Acts::Surface*>& /*surfaceSequence*/,
+      TrackContainer& /*tracks*/) const override {
+    throw std::runtime_error(
+        "direct navigation with GX2 fitter is not implemented");
+  }
+};
+
+}  // namespace
+
+std::shared_ptr<ActsHelper::TrackFitterFunction>
+ActsHelper::makeGlobalChiSquareFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    bool multipleScattering, bool energyLoss,
+    Acts::FreeToBoundCorrection freeToBoundCorrection, std::size_t nUpdateMax,
+    double relChi2changeCutOff, const Acts::Logger& logger) {
+  // Stepper should be copied into the fitters
+  const Stepper stepper(std::move(magneticField));
+
+  // Standard fitter
+  const auto& geo = *trackingGeometry;
+  Acts::Navigator::Config cfg{std::move(trackingGeometry)};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg, logger.cloneWithSuffix("Navigator"));
+  Propagator propagator(stepper, std::move(navigator),
+                        logger.cloneWithSuffix("Propagator"));
+  Fitter trackFitter(std::move(propagator), logger.cloneWithSuffix("Fitter"));
+
+  // Direct fitter
+  Acts::DirectNavigator directNavigator{
+      logger.cloneWithSuffix("DirectNavigator")};
+  DirectPropagator directPropagator(stepper, std::move(directNavigator),
+                                    logger.cloneWithSuffix("DirectPropagator"));
+  DirectFitter directTrackFitter(std::move(directPropagator),
+                                 logger.cloneWithSuffix("DirectFitter"));
+
+  // build the fitter function. owns the fitter object.
+  auto fitterFunction = std::make_shared<GlobalChiSquareFitterFunctionImpl>(
+      std::move(trackFitter), std::move(directTrackFitter), geo);
+  fitterFunction->multipleScattering = multipleScattering;
+  fitterFunction->energyLoss = energyLoss;
+  fitterFunction->freeToBoundCorrection = freeToBoundCorrection;
+  fitterFunction->nUpdateMax = nUpdateMax;
+  fitterFunction->relChi2changeCutOff = relChi2changeCutOff;
+
+  return fitterFunction;
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GsfFitterFunction.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GsfFitterFunction.cpp
new file mode 100644
index 00000000..e6a13d44
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/GsfFitterFunction.cpp
@@ -0,0 +1,220 @@
+#include "Acts/Definitions/Common.hpp"
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/MultiEigenStepperLoop.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+#include "Acts/TrackFitting/GaussianSumFitter.hpp"
+#include "Acts/TrackFitting/GsfMixtureReduction.hpp"
+#include "Acts/TrackFitting/GsfOptions.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/HashedString.hpp"
+#include "Acts/Utilities/Intersection.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "Acts/Utilities/Zip.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/MeasurementCalibration.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace Acts {
+class MagneticFieldProvider;
+class SourceLink;
+class Surface;
+class TrackingGeometry;
+}  // namespace Acts
+
+using namespace ActsHelper;
+
+namespace {
+
+using MultiStepper = Acts::MultiEigenStepperLoop<>;
+using Propagator = Acts::Propagator<MultiStepper, Acts::Navigator>;
+using DirectPropagator = Acts::Propagator<MultiStepper, Acts::DirectNavigator>;
+
+using Fitter = Acts::GaussianSumFitter<Propagator, BetheHeitlerApprox,
+                                       Acts::VectorMultiTrajectory>;
+using DirectFitter =
+    Acts::GaussianSumFitter<DirectPropagator, BetheHeitlerApprox,
+                            Acts::VectorMultiTrajectory>;
+using TrackContainer =
+    Acts::TrackContainer<Acts::VectorTrackContainer,
+                         Acts::VectorMultiTrajectory, std::shared_ptr>;
+
+struct GsfFitterFunctionImpl final : public ActsHelper::TrackFitterFunction {
+  Fitter fitter;
+  DirectFitter directFitter;
+
+  Acts::GainMatrixUpdater updater;
+
+  std::size_t maxComponents = 0;
+  double weightCutoff = 0;
+  const double momentumCutoff = 0;  // 500_MeV;
+  bool abortOnError = false;
+  // default is false, but we set it to true for debugging
+  bool disableAllMaterialHandling = true;
+  MixtureReductionAlgorithm reductionAlg =
+      MixtureReductionAlgorithm::KLDistance;
+  Acts::ComponentMergeMethod mergeMethod =
+      Acts::ComponentMergeMethod::eMaxWeight;
+
+  IndexSourceLink::SurfaceAccessor m_slSurfaceAccessor;
+
+  GsfFitterFunctionImpl(Fitter&& f, DirectFitter&& df,
+                        const Acts::TrackingGeometry& trkGeo)
+      : fitter(std::move(f)),
+        directFitter(std::move(df)),
+        m_slSurfaceAccessor{trkGeo} {}
+
+  template <typename calibrator_t>
+  auto makeGsfOptions(const GeneralFitterOptions& options,
+                      const calibrator_t& calibrator) const {
+    Acts::GsfExtensions<Acts::VectorMultiTrajectory> extensions;
+    extensions.updater.connect<
+        &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
+        &updater);
+
+    Acts::GsfOptions<Acts::VectorMultiTrajectory> gsfOptions{
+        options.geoContext,
+        options.magFieldContext,
+        options.calibrationContext,
+        extensions,
+        options.propOptions,
+        &(*options.referenceSurface),
+        maxComponents,
+        weightCutoff,
+        abortOnError,
+        disableAllMaterialHandling};
+    gsfOptions.componentMergeMethod = mergeMethod;
+
+    gsfOptions.extensions.calibrator.connect<&calibrator_t::calibrate>(
+        &calibrator);
+    gsfOptions.extensions.surfaceAccessor
+        .connect<&IndexSourceLink::SurfaceAccessor::operator()>(
+            &m_slSurfaceAccessor);
+    switch (reductionAlg) {
+      case MixtureReductionAlgorithm::weightCut: {
+        gsfOptions.extensions.mixtureReducer
+            .connect<&Acts::reduceMixtureLargestWeights>();
+      } break;
+      case MixtureReductionAlgorithm::KLDistance: {
+        gsfOptions.extensions.mixtureReducer
+            .connect<&Acts::reduceMixtureWithKLDistance>();
+      } break;
+    }
+
+    return gsfOptions;
+  }
+
+  TrackFitterResult operator()(const std::vector<Acts::SourceLink>& sourceLinks,
+                               const TrackParameters& initialParameters,
+                               const GeneralFitterOptions& options,
+                               const MeasurementCalibratorAdapter& calibrator,
+                               TrackContainer& tracks) const override {
+    const auto gsfOptions = makeGsfOptions(options, calibrator);
+
+    using namespace Acts::GsfConstants;
+    if (!tracks.hasColumn(Acts::hashString(kFinalMultiComponentStateColumn))) {
+      std::string key(kFinalMultiComponentStateColumn);
+      tracks.template addColumn<FinalMultiComponentState>(key);
+    }
+
+    if (!tracks.hasColumn(Acts::hashString(kFwdMaxMaterialXOverX0))) {
+      tracks.template addColumn<double>(std::string(kFwdMaxMaterialXOverX0));
+    }
+
+    if (!tracks.hasColumn(Acts::hashString(kFwdSumMaterialXOverX0))) {
+      tracks.template addColumn<double>(std::string(kFwdSumMaterialXOverX0));
+    }
+
+    return fitter.fit(sourceLinks.begin(), sourceLinks.end(), initialParameters,
+                      gsfOptions, tracks);
+  }
+
+  TrackFitterResult operator()(
+      const std::vector<Acts::SourceLink>& sourceLinks,
+      const TrackParameters& initialParameters,
+      const GeneralFitterOptions& options,
+      const RefittingCalibrator& calibrator,
+      const std::vector<const Acts::Surface*>& surfaceSequence,
+      TrackContainer& tracks) const override {
+    const auto gsfOptions = makeGsfOptions(options, calibrator);
+
+    using namespace Acts::GsfConstants;
+    if (!tracks.hasColumn(Acts::hashString(kFinalMultiComponentStateColumn))) {
+      std::string key(kFinalMultiComponentStateColumn);
+      tracks.template addColumn<FinalMultiComponentState>(key);
+    }
+
+    return directFitter.fit(sourceLinks.begin(), sourceLinks.end(),
+                            initialParameters, gsfOptions, surfaceSequence,
+                            tracks);
+  }
+};
+
+}  // namespace
+
+std::shared_ptr<TrackFitterFunction> ActsHelper::makeGsfFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    BetheHeitlerApprox betheHeitlerApprox, std::size_t maxComponents,
+    double weightCutoff, Acts::ComponentMergeMethod componentMergeMethod,
+    MixtureReductionAlgorithm mixtureReductionAlgorithm,
+    const Acts::Logger& logger) {
+  // Standard fitter
+  MultiStepper stepper(magneticField, logger.cloneWithSuffix("Step"));
+  const auto& geo = *trackingGeometry;
+  Acts::Navigator::Config cfg{std::move(trackingGeometry)};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg, logger.cloneWithSuffix("Navigator"));
+  Propagator propagator(std::move(stepper), std::move(navigator),
+                        logger.cloneWithSuffix("Propagator"));
+  Fitter trackFitter(std::move(propagator),
+                     BetheHeitlerApprox(betheHeitlerApprox),
+                     logger.cloneWithSuffix("GSF"));
+
+  // Direct fitter
+  MultiStepper directStepper(std::move(magneticField),
+                             logger.cloneWithSuffix("Step"));
+  Acts::DirectNavigator directNavigator{
+      logger.cloneWithSuffix("DirectNavigator")};
+  DirectPropagator directPropagator(std::move(directStepper),
+                                    std::move(directNavigator),
+                                    logger.cloneWithSuffix("DirectPropagator"));
+  DirectFitter directTrackFitter(std::move(directPropagator),
+                                 BetheHeitlerApprox(betheHeitlerApprox),
+                                 logger.cloneWithSuffix("DirectGSF"));
+
+  // build the fitter functions. owns the fitter object.
+  auto fitterFunction = std::make_shared<GsfFitterFunctionImpl>(
+      std::move(trackFitter), std::move(directTrackFitter), geo);
+  fitterFunction->maxComponents = maxComponents;
+  fitterFunction->weightCutoff = weightCutoff;
+  fitterFunction->mergeMethod = componentMergeMethod;
+  fitterFunction->reductionAlg = mixtureReductionAlgorithm;
+
+  return fitterFunction;
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/KalmanFitterFunction.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/KalmanFitterFunction.cpp
new file mode 100644
index 00000000..55841eb9
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/KalmanFitterFunction.cpp
@@ -0,0 +1,182 @@
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/MeasurementCalibration.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace Acts {
+class MagneticFieldProvider;
+class SourceLink;
+class Surface;
+class TrackingGeometry;
+}  // namespace Acts
+
+namespace {
+
+using Stepper = Acts::EigenStepper<>;
+using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
+using Fitter = Acts::KalmanFitter<Propagator, Acts::VectorMultiTrajectory>;
+using DirectPropagator = Acts::Propagator<Stepper, Acts::DirectNavigator>;
+using DirectFitter =
+    Acts::KalmanFitter<DirectPropagator, Acts::VectorMultiTrajectory>;
+
+using TrackContainer =
+    Acts::TrackContainer<Acts::VectorTrackContainer,
+                         Acts::VectorMultiTrajectory, std::shared_ptr>;
+
+struct SimpleReverseFilteringLogic {
+  double momentumThreshold = 0;
+
+  bool doBackwardFiltering(
+      Acts::VectorMultiTrajectory::ConstTrackStateProxy trackState) const {
+    auto momentum = fabs(1 / trackState.filtered()[Acts::eBoundQOverP]);
+    return (momentum <= momentumThreshold);
+  }
+};
+
+using namespace ActsHelper;
+
+struct KalmanFitterFunctionImpl final : public TrackFitterFunction {
+  Fitter fitter;
+  DirectFitter directFitter;
+
+  Acts::GainMatrixUpdater kfUpdater;
+  Acts::GainMatrixSmoother kfSmoother;
+  SimpleReverseFilteringLogic reverseFilteringLogic;
+
+  bool multipleScattering = false;
+  bool energyLoss = false;
+  Acts::FreeToBoundCorrection freeToBoundCorrection;
+
+  IndexSourceLink::SurfaceAccessor slSurfaceAccessor;
+
+  KalmanFitterFunctionImpl(Fitter&& f, DirectFitter&& df,
+                           const Acts::TrackingGeometry& trkGeo)
+      : fitter(std::move(f)),
+        directFitter(std::move(df)),
+        slSurfaceAccessor{trkGeo} {}
+
+  template <typename calibrator_t>
+  auto makeKfOptions(const GeneralFitterOptions& options,
+                     const calibrator_t& calibrator) const {
+    Acts::KalmanFitterExtensions<Acts::VectorMultiTrajectory> extensions;
+    extensions.updater.connect<
+        &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
+        &kfUpdater);
+    extensions.smoother.connect<
+        &Acts::GainMatrixSmoother::operator()<Acts::VectorMultiTrajectory>>(
+        &kfSmoother);
+    extensions.reverseFilteringLogic
+        .connect<&SimpleReverseFilteringLogic::doBackwardFiltering>(
+            &reverseFilteringLogic);
+
+    Acts::KalmanFitterOptions<Acts::VectorMultiTrajectory> kfOptions(
+        options.geoContext, options.magFieldContext, options.calibrationContext,
+        extensions, options.propOptions, &(*options.referenceSurface));
+
+    kfOptions.referenceSurfaceStrategy =
+        Acts::KalmanFitterTargetSurfaceStrategy::first;
+    kfOptions.multipleScattering = multipleScattering;
+    kfOptions.energyLoss = energyLoss;
+    kfOptions.freeToBoundCorrection = freeToBoundCorrection;
+    kfOptions.extensions.calibrator.connect<&calibrator_t::calibrate>(
+        &calibrator);
+    kfOptions.extensions.surfaceAccessor
+        .connect<&IndexSourceLink::SurfaceAccessor::operator()>(
+            &slSurfaceAccessor);
+
+    return kfOptions;
+  }
+
+  TrackFitterResult operator()(const std::vector<Acts::SourceLink>& sourceLinks,
+                               const TrackParameters& initialParameters,
+                               const GeneralFitterOptions& options,
+                               const MeasurementCalibratorAdapter& calibrator,
+                               TrackContainer& tracks) const override {
+    const auto kfOptions = makeKfOptions(options, calibrator);
+    return fitter.fit(sourceLinks.begin(), sourceLinks.end(), initialParameters,
+                      kfOptions, tracks);
+  }
+
+  TrackFitterResult operator()(
+      const std::vector<Acts::SourceLink>& sourceLinks,
+      const TrackParameters& initialParameters,
+      const GeneralFitterOptions& options,
+      const RefittingCalibrator& calibrator,
+      const std::vector<const Acts::Surface*>& surfaceSequence,
+      TrackContainer& tracks) const override {
+    const auto kfOptions = makeKfOptions(options, calibrator);
+    return directFitter.fit(sourceLinks.begin(), sourceLinks.end(),
+                            initialParameters, kfOptions, surfaceSequence,
+                            tracks);
+  }
+};
+
+}  // namespace
+
+std::shared_ptr<ActsHelper::TrackFitterFunction>
+ActsHelper::makeKalmanFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
+    bool multipleScattering, bool energyLoss,
+    double reverseFilteringMomThreshold,
+    Acts::FreeToBoundCorrection freeToBoundCorrection,
+    const Acts::Logger& logger) {
+  // Stepper should be copied into the fitters
+  const Stepper stepper(std::move(magneticField));
+
+  // Standard fitter
+  const auto& geo = *trackingGeometry;
+  Acts::Navigator::Config cfg{std::move(trackingGeometry)};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg, logger.cloneWithSuffix("Navigator"));
+  Propagator propagator(stepper, std::move(navigator),
+                        logger.cloneWithSuffix("Propagator"));
+  Fitter trackFitter(std::move(propagator), logger.cloneWithSuffix("Fitter"));
+
+  // Direct fitter
+  Acts::DirectNavigator directNavigator{
+      logger.cloneWithSuffix("DirectNavigator")};
+  DirectPropagator directPropagator(stepper, std::move(directNavigator),
+                                    logger.cloneWithSuffix("DirectPropagator"));
+  DirectFitter directTrackFitter(std::move(directPropagator),
+                                 logger.cloneWithSuffix("DirectFitter"));
+
+  // build the fitter function. owns the fitter object.
+  auto fitterFunction = std::make_shared<KalmanFitterFunctionImpl>(
+      std::move(trackFitter), std::move(directTrackFitter), geo);
+  fitterFunction->multipleScattering = multipleScattering;
+  fitterFunction->energyLoss = energyLoss;
+  fitterFunction->reverseFilteringLogic.momentumThreshold =
+      reverseFilteringMomThreshold;
+  fitterFunction->freeToBoundCorrection = freeToBoundCorrection;
+
+  return fitterFunction;
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/RefittingCalibrator.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/RefittingCalibrator.cpp
new file mode 100644
index 00000000..0ef3e8a2
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Algorithms/TrackFitting/RefittingCalibrator.cpp
@@ -0,0 +1,35 @@
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/EventData/MeasurementHelpers.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/Utilities/CalibrationContext.hpp"
+
+namespace ActsHelper {
+
+void RefittingCalibrator::calibrate(const Acts::GeometryContext& /*gctx*/,
+                                    const Acts::CalibrationContext& /*cctx*/,
+                                    const Acts::SourceLink& sourceLink,
+                                    Proxy trackState) const {
+  const auto sl = sourceLink.get<RefittingSourceLink>();
+
+  // Reset the original uncalibrated sourcelink on this track state
+  trackState.setUncalibratedSourceLink(sl.state.getUncalibratedSourceLink());
+
+  // Here we construct a measurement by extracting the information available
+  // in the state
+  Acts::visit_measurement(sl.state.calibratedSize(), [&](auto N) {
+    using namespace Acts;
+    constexpr int Size = decltype(N)::value;
+
+    trackState.allocateCalibrated(Size);
+    trackState.template calibrated<Size>() =
+        sl.state.template calibrated<Size>();
+    trackState.template calibratedCovariance<Size>() =
+        sl.state.template calibratedCovariance<Size>();
+  });
+
+  trackState.setProjectorBitset(sl.state.projectorBitset());
+}
+
+}  // namespace ActsHelper
diff --git a/Reconstruction/RecActsTracking/src/utils/TGeoDetector.hpp b/Reconstruction/RecActsTracking/ActsHelper/src/Detectors/TGeoDetector.cpp
similarity index 77%
rename from Reconstruction/RecActsTracking/src/utils/TGeoDetector.hpp
rename to Reconstruction/RecActsTracking/ActsHelper/src/Detectors/TGeoDetector.cpp
index 4163d23b..bc695120 100644
--- a/Reconstruction/RecActsTracking/src/utils/TGeoDetector.hpp
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Detectors/TGeoDetector.cpp
@@ -1,168 +1,4 @@
-#include "Acts/Geometry/CylinderVolumeBuilder.hpp"
-#include "Acts/Geometry/CylinderVolumeHelper.hpp"
-#include "Acts/Geometry/GeometryContext.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Geometry/ITrackingVolumeBuilder.hpp"
-#include "Acts/Geometry/LayerArrayCreator.hpp"
-#include "Acts/Geometry/LayerCreator.hpp"
-#include "Acts/Geometry/PassiveLayerBuilder.hpp"
-#include "Acts/Geometry/ProtoLayerHelper.hpp"
-#include "Acts/Geometry/SurfaceArrayCreator.hpp"
-#include "Acts/Geometry/SurfaceBinningMatcher.hpp"
-#include "Acts/Geometry/TrackingGeometry.hpp"
-#include "Acts/Geometry/TrackingGeometryBuilder.hpp"
-#include "Acts/Geometry/TrackingVolumeArrayCreator.hpp"
-
-#include "Acts/Plugins/TGeo/TGeoCylinderDiscSplitter.hpp"
-#include "Acts/Plugins/TGeo/TGeoLayerBuilder.hpp"
-#include "Acts/Plugins/Json/JsonMaterialDecorator.hpp"
-#include "Acts/Plugins/Json/ActsJson.hpp"
-
-#include "Acts/Utilities/BinningType.hpp"
-#include "Acts/Utilities/Logger.hpp"
-
-#include <boost/program_options.hpp>
-#include <nlohmann/json.hpp>
-
-#include <cstddef>
-#include <map>
-#include <memory>
-#include <stdexcept>
-#include <string>
-#include <utility>
-#include <vector>
-
-namespace Acts
-{
-    class TGeoDetectorElement;
-    class TrackingGeometry;
-    class IMaterialDecorator;
-}
-
-/// ----------------------------
-///        pre definitions 
-/// ----------------------------
-
-/// Half open [lower,upper) interval type for a single user option.
-///
-/// A missing limit represents an unbounded upper or lower limit. With just
-/// one defined limit the interval is just a lower/upper bound; with both
-/// limits undefined, the interval is unbounded everywhere and thus contains
-/// all possible values.
-struct Interval
-{
-    std::optional<double> lower;
-    std::optional<double> upper;
-};
-
-/// Extract an interval from an input of the form 'lower:upper'.
-///
-/// An input of the form `lower:` or `:upper` sets just one of the limits. Any
-/// other input leads to an unbounded interval.
-///
-/// @note The more common range notation uses `lower-upper` but the `-`
-///   separator complicates the parsing of negative values.
-std::istream& operator>>(std::istream& is, Interval& interval);
-
-/// Print an interval as `lower:upper`.
-std::ostream& operator<<(std::ostream& os, const Interval& interval);
-
-struct TGeoConfig {
-    Acts::Logging::Level surfaceLogLevel = Acts::Logging::WARNING;
-    Acts::Logging::Level layerLogLevel   = Acts::Logging::WARNING;
-    Acts::Logging::Level volumeLogLevel  = Acts::Logging::WARNING;
-
-    std::string fileName;
-    bool buildBeamPipe = false;
-    double beamPipeRadius{0};
-    double beamPipeHalflengthZ{0};
-    double beamPipeLayerThickness{0};
-    double beamPipeEnvelopeR{1.0};
-    double layerEnvelopeR{1.0};
-
-    double unitScalor = 1.0;
-
-    Acts::TGeoLayerBuilder::ElementFactory elementFactory =
-        Acts::TGeoLayerBuilder::defaultElementFactory;
-
-    /// Optional geometry identifier hook to be used during closure
-    std::shared_ptr<const Acts::GeometryIdentifierHook> geometryIdentifierHook =
-    std::make_shared<Acts::GeometryIdentifierHook>();
-
-    enum SubVolume : std::size_t { Negative = 0, Central, Positive };
-
-    template <typename T>
-    struct LayerTriplet
-    {
-        LayerTriplet() = default;
-
-        LayerTriplet(T value)
-            : negative{value}, central{value}, positive{value} {}
-
-        LayerTriplet(T _negative, T _central, T _positive)
-            : negative{_negative}, central{_central}, positive{_positive} {}
-
-        T negative;
-        T central;
-        T positive;
-
-        T& at(SubVolume i)
-        {
-            switch (i)
-            {
-                case Negative: return negative;
-                case Central:  return central;
-                case Positive: return positive;
-                default: throw std::invalid_argument{"Unknown index"};
-            }
-        }
-
-        const T& at(SubVolume i) const
-        {
-            switch (i)
-            {
-                case Negative: return negative;
-                case Central: return central;
-                case Positive: return positive;
-                default: throw std::invalid_argument{"Unknown index"};
-            }
-        }
-    };
-
-    struct Volume {
-        std::string name;
-        LayerTriplet<bool> layers{false};
-        LayerTriplet<std::string> subVolumeName;
-        LayerTriplet<std::vector<std::string>> sensitiveNames;
-        LayerTriplet<std::string> sensitiveAxes;
-        LayerTriplet<Interval> rRange;
-        LayerTriplet<Interval> zRange;
-        LayerTriplet<double> splitTolR{0};
-        LayerTriplet<double> splitTolZ{0};
-        LayerTriplet<std::vector<std::pair<int, Acts::BinningType>>> binning0;
-        LayerTriplet<std::vector<std::pair<int, Acts::BinningType>>> binning1;
-
-        Interval binToleranceR;
-        Interval binTolerancePhi;
-        Interval binToleranceZ;
-
-        bool cylinderDiscSplit = false;
-        unsigned int cylinderNZSegments = 0;
-        unsigned int cylinderNPhiSegments = 0;
-        unsigned int discNRSegments = 0;
-        unsigned int discNPhiSegments = 0;
-
-        bool itkModuleSplit = false;
-        std::map<std::string, unsigned int> barrelMap;
-        std::map<std::string, std::vector<std::pair<double, double>>> discMap;
-        /// pairs of regular expressions to match sensor names and category keys
-        /// for either the barrelMap or the discMap
-        /// @TODO in principle vector<pair< > > would be good enough
-        std::map<std::string, std::string> splitPatterns;
-    };
-
-    std::vector<Volume> volumes;
-};
+#include "ActsHelper/Detectors/TGeoDetector.hpp"
 
 /// ------------------------------------------------------------------------------------------------
 ///     @note nlohmann::json must define functions *from_json* && *to_json* for json conversion
@@ -541,6 +377,8 @@ std::shared_ptr<const Acts::TrackingGeometry> buildTGeoDetector(
         volumeConfig.volumeName = lbc.configurationName;
         volumeConfig.buildToRadiusZero = volumeBuilders.empty();
         volumeConfig.layerEnvelopeR = {config.layerEnvelopeR, config.layerEnvelopeR};
+        volumeConfig.layerEnvelopeZ = 0.2 * Acts::UnitConstants::mm;
+        
         auto ringLayoutConfiguration =
             [&](const std::vector<Acts::TGeoLayerBuilder::LayerConfig>& lConfigs) -> void
             {
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/EventData/MeasurementCalibration.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/EventData/MeasurementCalibration.cpp
new file mode 100644
index 00000000..93de070b
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/EventData/MeasurementCalibration.cpp
@@ -0,0 +1,43 @@
+#include "Acts/EventData/SourceLink.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/Measurement.hpp"
+#include <ActsHelper/EventData/MeasurementCalibration.hpp>
+
+#include <cassert>
+#include <variant>
+
+namespace Acts {
+class VectorMultiTrajectory;
+}  // namespace Acts
+
+void ActsHelper::PassThroughCalibrator::calibrate(
+    const MeasurementContainer& measurements,
+    const ClusterContainer* /*clusters*/, const Acts::GeometryContext& /*gctx*/,
+    const Acts::CalibrationContext& /*cctx*/,
+    const Acts::SourceLink& sourceLink,
+    Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const {
+  trackState.setUncalibratedSourceLink(sourceLink);
+  const IndexSourceLink& idxSourceLink = sourceLink.get<IndexSourceLink>();
+
+  assert((idxSourceLink.index() < measurements.size()) &&
+         "Source link index is outside the container bounds");
+
+  std::visit(
+      [&trackState](const auto& meas) { trackState.setCalibrated(meas); },
+      measurements[idxSourceLink.index()]);
+}
+
+ActsHelper::MeasurementCalibratorAdapter::MeasurementCalibratorAdapter(
+    const MeasurementCalibrator& calibrator,
+    const MeasurementContainer& measurements, const ClusterContainer* clusters)
+    : m_calibrator{calibrator},
+      m_measurements{measurements},
+      m_clusters{clusters} {}
+
+void ActsHelper::MeasurementCalibratorAdapter::calibrate(
+    const Acts::GeometryContext& gctx, const Acts::CalibrationContext& cctx,
+    const Acts::SourceLink& sourceLink,
+    Acts::VectorMultiTrajectory::TrackStateProxy trackState) const {
+  return m_calibrator.calibrate(m_measurements, m_clusters, gctx, cctx,
+                                sourceLink, trackState);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/EventData/ScalingCalibrator.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/EventData/ScalingCalibrator.cpp
new file mode 100644
index 00000000..34c48a18
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/EventData/ScalingCalibrator.cpp
@@ -0,0 +1,174 @@
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Utilities/CalibrationContext.hpp"
+#include "ActsHelper/EventData/Cluster.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/Measurement.hpp"
+#include <Acts/Definitions/TrackParametrization.hpp>
+#include <ActsHelper/EventData/ScalingCalibrator.hpp>
+
+#include <algorithm>
+#include <array>
+#include <bitset>
+#include <cassert>
+#include <cstring>
+#include <filesystem>
+#include <map>
+#include <regex>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include <TCollection.h>
+#include <TFile.h>
+#include <TH2.h>
+#include <TKey.h>
+#include <TList.h>
+#include <TString.h>
+
+namespace Acts {
+class VectorMultiTrajectory;
+}  // namespace Acts
+
+namespace detail {
+
+std::pair<Acts::GeometryIdentifier, std::string> parseMapKey(
+    const std::string& mapkey) {
+  std::regex reg("^map_([0-9]+)-([0-9]+)-([0-9]+)_([xy]_.*)$");
+  std::smatch matches;
+
+  if (std::regex_search(mapkey, matches, reg) && matches.size() == 5) {
+    std::size_t vol = std::stoull(matches[1].str());
+    std::size_t lyr = std::stoull(matches[2].str());
+    std::size_t mod = std::stoull(matches[3].str());
+
+    Acts::GeometryIdentifier geoId;
+    geoId.setVolume(vol);
+    geoId.setLayer(lyr);
+    geoId.setSensitive(mod);
+
+    std::string var(matches[4].str());
+
+    return std::make_pair(geoId, var);
+  } else {
+    throw std::runtime_error("Invalid map key: " + mapkey);
+  }
+}
+
+std::map<Acts::GeometryIdentifier, ActsHelper::ScalingCalibrator::MapTuple>
+readMaps(const std::filesystem::path& path) {
+  std::map<Acts::GeometryIdentifier, ActsHelper::ScalingCalibrator::MapTuple>
+      maps;
+
+  TFile ifile(path.c_str(), "READ");
+  if (ifile.IsZombie()) {
+    throw std::runtime_error("Unable to open TFile: " + path.string());
+  }
+
+  TList* lst = ifile.GetListOfKeys();
+  assert(lst != nullptr);
+
+  for (auto it = lst->begin(); it != lst->end(); ++it) {
+    TKey* key = static_cast<TKey*>(*it);
+    if (std::strcmp(key->GetClassName(), "TH2D") == 0) {
+      auto [geoId, var] = parseMapKey(key->GetName());
+
+      TH2D hist;
+      key->Read(&hist);
+
+      if (var == "x_offset") {
+        maps[geoId].x_offset = hist;
+      } else if (var == "x_scale") {
+        maps[geoId].x_scale = hist;
+      } else if (var == "y_offset") {
+        maps[geoId].y_offset = hist;
+      } else if (var == "y_scale") {
+        maps[geoId].y_scale = hist;
+      } else {
+        throw std::runtime_error("Unrecognized var: " + var);
+      }
+    }
+  }
+  return maps;
+}
+
+std::bitset<3> readMask(const std::filesystem::path& path) {
+  TFile ifile(path.c_str(), "READ");
+  if (ifile.IsZombie()) {
+    throw std::runtime_error("Unable to open TFile: " + path.string());
+  }
+
+  TString* tstr = ifile.Get<TString>("v_mask");
+  if (tstr == nullptr) {
+    throw std::runtime_error("Unable to read mask");
+  }
+
+  return std::bitset<3>(std::string{*tstr});
+}
+
+}  // namespace detail
+
+ActsHelper::ScalingCalibrator::ScalingCalibrator(
+    const std::filesystem::path& path)
+    : m_calib_maps{::detail::readMaps(path)},
+      m_mask{::detail::readMask(path)} {}
+
+void ActsHelper::ScalingCalibrator::calibrate(
+    const MeasurementContainer& measurements, const ClusterContainer* clusters,
+    const Acts::GeometryContext& /*gctx*/,
+    const Acts::CalibrationContext& /*cctx*/,
+    const Acts::SourceLink& sourceLink,
+    Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const {
+  trackState.setUncalibratedSourceLink(sourceLink);
+  const IndexSourceLink& idxSourceLink = sourceLink.get<IndexSourceLink>();
+
+  assert((idxSourceLink.index() < measurements.size()) &&
+         "Source link index is outside the container bounds");
+
+  auto geoId = trackState.referenceSurface().geometryId();
+  Acts::GeometryIdentifier mgid;
+  mgid.setVolume(geoId.volume() *
+                 static_cast<Acts::GeometryIdentifier::Value>(m_mask[2]));
+  mgid.setLayer(geoId.layer() *
+                static_cast<Acts::GeometryIdentifier::Value>(m_mask[1]));
+  mgid.setSensitive(geoId.sensitive() *
+                    static_cast<Acts::GeometryIdentifier::Value>(m_mask[0]));
+  const Cluster& cl = clusters->at(idxSourceLink.index());
+  ConstantTuple ct = m_calib_maps.at(mgid).at(cl.sizeLoc0, cl.sizeLoc1);
+
+  std::visit(
+      [&](const auto& meas) {
+        auto E = meas.expander();
+        auto P = meas.projector();
+
+        Acts::ActsVector<Acts::eBoundSize> fpar = E * meas.parameters();
+
+        Acts::ActsSquareMatrix<Acts::eBoundSize> fcov =
+            E * meas.covariance() * E.transpose();
+
+        fpar[Acts::eBoundLoc0] += ct.x_offset;
+        fpar[Acts::eBoundLoc1] += ct.y_offset;
+        fcov(Acts::eBoundLoc0, Acts::eBoundLoc0) *= ct.x_scale;
+        fcov(Acts::eBoundLoc1, Acts::eBoundLoc1) *= ct.y_scale;
+
+        constexpr std::size_t kSize =
+            std::remove_reference_t<decltype(meas)>::size();
+        std::array<Acts::BoundIndices, kSize> indices = meas.indices();
+        Acts::ActsVector<kSize> cpar = P * fpar;
+        Acts::ActsSquareMatrix<kSize> ccov = P * fcov * P.transpose();
+
+        Acts::Measurement<Acts::BoundIndices, kSize> cmeas(
+            Acts::SourceLink{idxSourceLink}, indices, cpar, ccov);
+
+        trackState.allocateCalibrated(cmeas.size());
+        trackState.setCalibrated(cmeas);
+      },
+      (measurements)[idxSourceLink.index()]);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapRootIo.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapRootIo.cpp
new file mode 100644
index 00000000..8fbe91d1
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapRootIo.cpp
@@ -0,0 +1,117 @@
+#include "ActsHelper/MagneticField/FieldMapRootIo.hpp"
+
+#include "Acts/MagneticField/BFieldMapUtils.hpp"
+
+#include <stdexcept>
+#include <vector>
+
+#include <RtypesCore.h>
+#include <TFile.h>
+#include <TTree.h>
+
+ActsHelper::detail::InterpolatedMagneticField2
+ActsHelper::makeMagneticFieldMapRzFromRoot(
+    const std::function<std::size_t(std::array<std::size_t, 2> binsRZ,
+                                    std::array<std::size_t, 2> nBinsRZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, const std::string& treeName,
+    Acts::ActsScalar lengthUnit, Acts::ActsScalar BFieldUnit,
+    bool firstQuadrant) {
+  /// [1] Read in field map file
+  // Grid position points in r and z
+  std::vector<double> rPos;
+  std::vector<double> zPos;
+  // components of magnetic field on grid points
+  std::vector<Acts::Vector2> bField;
+  // [1] Read in file and fill values
+  TFile* inputFile = TFile::Open(fieldMapFile.c_str());
+  if (inputFile == nullptr) {
+    throw std::runtime_error("file does not exist");
+  }
+  TTree* tree = inputFile->Get<TTree>(treeName.c_str());
+  if (tree == nullptr) {
+    throw std::runtime_error("object not found in file");
+  }
+  Int_t entries = tree->GetEntries();
+
+  double r = 0, z = 0;
+  double Br = 0, Bz = 0;
+
+  tree->SetBranchAddress("r", &r);
+  tree->SetBranchAddress("z", &z);
+
+  tree->SetBranchAddress("Br", &Br);
+  tree->SetBranchAddress("Bz", &Bz);
+
+  // reserve size
+  rPos.reserve(entries);
+  zPos.reserve(entries);
+  bField.reserve(entries);
+
+  for (int i = 0; i < entries; i++) {
+    tree->GetEvent(i);
+    rPos.push_back(r);
+    zPos.push_back(z);
+    bField.push_back(Acts::Vector2(Br, Bz));
+  }
+  delete inputFile;
+  /// [2] use helper function in core
+  return Acts::fieldMapRZ(localToGlobalBin, rPos, zPos, bField, lengthUnit,
+                          BFieldUnit, firstQuadrant);
+}
+
+ActsHelper::detail::InterpolatedMagneticField3
+ActsHelper::makeMagneticFieldMapXyzFromRoot(
+    const std::function<std::size_t(std::array<std::size_t, 3> binsXYZ,
+                                    std::array<std::size_t, 3> nBinsXYZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, const std::string& treeName,
+    Acts::ActsScalar lengthUnit, Acts::ActsScalar BFieldUnit,
+    bool firstOctant) {
+  /// [1] Read in field map file
+  // Grid position points in x, y and z
+  std::vector<double> xPos;
+  std::vector<double> yPos;
+  std::vector<double> zPos;
+  // components of magnetic field on grid points
+  std::vector<Acts::Vector3> bField;
+  // [1] Read in file and fill values
+  TFile* inputFile = TFile::Open(fieldMapFile.c_str());
+  if (inputFile == nullptr) {
+    throw std::runtime_error("file does not exist");
+  }
+  TTree* tree = inputFile->Get<TTree>(treeName.c_str());
+  if (tree == nullptr) {
+    throw std::runtime_error("object not found in file");
+  }
+  Int_t entries = tree->GetEntries();
+
+  double x = 0, y = 0, z = 0;
+  double Bx = 0, By = 0, Bz = 0;
+
+  tree->SetBranchAddress("x", &x);
+  tree->SetBranchAddress("y", &y);
+  tree->SetBranchAddress("z", &z);
+
+  tree->SetBranchAddress("Bx", &Bx);
+  tree->SetBranchAddress("By", &By);
+  tree->SetBranchAddress("Bz", &Bz);
+
+  // reserve size
+  xPos.reserve(entries);
+  yPos.reserve(entries);
+  zPos.reserve(entries);
+  bField.reserve(entries);
+
+  for (int i = 0; i < entries; i++) {
+    tree->GetEvent(i);
+    xPos.push_back(x);
+    yPos.push_back(y);
+    zPos.push_back(z);
+    bField.push_back(Acts::Vector3(Bx, By, Bz));
+  }
+  delete inputFile;
+
+  return Acts::fieldMapXYZ(localToGlobalBin, xPos, yPos, zPos, bField,
+                           lengthUnit, BFieldUnit, firstOctant);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapTextIo.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapTextIo.cpp
new file mode 100644
index 00000000..ffeebf29
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/MagneticField/FieldMapTextIo.cpp
@@ -0,0 +1,99 @@
+#include "ActsHelper/MagneticField/FieldMapTextIo.hpp"
+
+#include "Acts/MagneticField/BFieldMapUtils.hpp"
+
+#include <fstream>
+#include <vector>
+
+namespace {
+constexpr std::size_t kDefaultSize = 1 << 15;
+}
+
+ActsHelper::detail::InterpolatedMagneticField2
+ActsHelper::makeMagneticFieldMapRzFromText(
+    const std::function<std::size_t(std::array<std::size_t, 2> binsRZ,
+                                    std::array<std::size_t, 2> nBinsRZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, Acts::ActsScalar lengthUnit,
+    Acts::ActsScalar BFieldUnit, bool firstQuadrant) {
+  /// [1] Read in field map file
+  // Grid position points in r and z
+  std::vector<double> rPos;
+  std::vector<double> zPos;
+  // components of magnetic field on grid points
+  std::vector<Acts::Vector2> bField;
+  // reserve estimated size
+  rPos.reserve(kDefaultSize);
+  zPos.reserve(kDefaultSize);
+  bField.reserve(kDefaultSize);
+  // [1] Read in file and fill values
+  std::ifstream map_file(fieldMapFile.c_str(), std::ios::in);
+  std::string line;
+  double r = 0., z = 0.;
+  double br = 0., bz = 0.;
+  while (std::getline(map_file, line)) {
+    if (line.empty() || line[0] == '%' || line[0] == '#' ||
+        line.find_first_not_of(' ') == std::string::npos) {
+      continue;
+    }
+
+    std::istringstream tmp(line);
+    tmp >> r >> z >> br >> bz;
+    rPos.push_back(r);
+    zPos.push_back(z);
+    bField.push_back(Acts::Vector2(br, bz));
+  }
+  map_file.close();
+  rPos.shrink_to_fit();
+  zPos.shrink_to_fit();
+  bField.shrink_to_fit();
+  /// [2] use helper function in core
+  return Acts::fieldMapRZ(localToGlobalBin, rPos, zPos, bField, lengthUnit,
+                          BFieldUnit, firstQuadrant);
+}
+
+ActsHelper::detail::InterpolatedMagneticField3
+ActsHelper::makeMagneticFieldMapXyzFromText(
+    const std::function<std::size_t(std::array<std::size_t, 3> binsXYZ,
+                                    std::array<std::size_t, 3> nBinsXYZ)>&
+        localToGlobalBin,
+    const std::string& fieldMapFile, Acts::ActsScalar lengthUnit,
+    Acts::ActsScalar BFieldUnit, bool firstOctant) {
+  /// [1] Read in field map file
+  // Grid position points in x, y and z
+  std::vector<double> xPos;
+  std::vector<double> yPos;
+  std::vector<double> zPos;
+  // components of magnetic field on grid points
+  std::vector<Acts::Vector3> bField;
+  // reserve estimated size
+  xPos.reserve(kDefaultSize);
+  yPos.reserve(kDefaultSize);
+  zPos.reserve(kDefaultSize);
+  bField.reserve(kDefaultSize);
+  // [1] Read in file and fill values
+  std::ifstream map_file(fieldMapFile.c_str(), std::ios::in);
+  std::string line;
+  double x = 0., y = 0., z = 0.;
+  double bx = 0., by = 0., bz = 0.;
+  while (std::getline(map_file, line)) {
+    if (line.empty() || line[0] == '%' || line[0] == '#' ||
+        line.find_first_not_of(' ') == std::string::npos) {
+      continue;
+    }
+
+    std::istringstream tmp(line);
+    tmp >> x >> y >> z >> bx >> by >> bz;
+    xPos.push_back(x);
+    yPos.push_back(y);
+    zPos.push_back(z);
+    bField.push_back(Acts::Vector3(bx, by, bz));
+  }
+  map_file.close();
+  xPos.shrink_to_fit();
+  yPos.shrink_to_fit();
+  zPos.shrink_to_fit();
+  bField.shrink_to_fit();
+  return Acts::fieldMapXYZ(localToGlobalBin, xPos, yPos, zPos, bField,
+                           lengthUnit, BFieldUnit, firstOctant);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/EventDataTransforms.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/EventDataTransforms.cpp
new file mode 100644
index 00000000..35b36927
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/EventDataTransforms.cpp
@@ -0,0 +1,74 @@
+#include "ActsHelper/Utilities/EventDataTransforms.hpp"
+
+#include "Acts/EventData/SourceLink.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/SimSpacePoint.hpp"
+
+#include <vector>
+
+ActsHelper::ProtoTrack ActsHelper::seedToPrototrack(
+    const ActsHelper::SimSeed& seed) {
+  ProtoTrack track;
+  track.reserve(seed.sp().size());
+  for (auto spacePointPtr : seed.sp()) {
+    for (const auto& slink : spacePointPtr->sourceLinks()) {
+      const auto& islink = slink.get<IndexSourceLink>();
+      track.emplace_back(islink.index());
+    }
+  }
+  return track;
+}
+
+const ActsHelper::SimSpacePoint* ActsHelper::findSpacePointForIndex(
+    ActsHelper::Index index, const SimSpacePointContainer& spacepoints) {
+  auto match = [&](const SimSpacePoint& sp) {
+    const auto& sls = sp.sourceLinks();
+    return std::any_of(sls.begin(), sls.end(), [&](const auto& sl) {
+      return sl.template get<IndexSourceLink>().index() == index;
+    });
+  };
+
+  auto found = std::find_if(spacepoints.begin(), spacepoints.end(), match);
+
+  if (found == spacepoints.end()) {
+    return nullptr;
+  }
+
+  return &(*found);
+}
+
+ActsHelper::SimSeed ActsHelper::prototrackToSeed(
+    const ActsHelper::ProtoTrack& track,
+    const ActsHelper::SimSpacePointContainer& spacepoints) {
+  auto findSpacePoint = [&](ActsHelper::Index index) {
+    auto found = findSpacePointForIndex(index, spacepoints);
+    if (found == nullptr) {
+      throw std::runtime_error("No spacepoint found for source-link index " +
+                               std::to_string(index));
+    }
+    return found;
+  };
+
+  const auto s = track.size();
+  if (s < 3) {
+    throw std::runtime_error(
+        "Cannot convert track with less then 3 spacepoints to seed");
+  }
+
+  std::vector<const SimSpacePoint*> ps;
+  ps.reserve(track.size());
+
+  std::transform(track.begin(), track.end(), std::back_inserter(ps),
+                 findSpacePoint);
+  std::sort(ps.begin(), ps.end(),
+            [](const auto& a, const auto& b) { return a->r() < b->r(); });
+
+  // Simply use r = m*z + t and solve for r=0 to find z vertex position...
+  // Probably not the textbook way to do
+  const auto m =
+      (ps.back()->r() - ps.front()->r()) / (ps.back()->z() - ps.front()->z());
+  const auto t = ps.front()->r() - m * ps.front()->z();
+  const auto z_vertex = -t / m;
+
+  return SimSeed(*ps[0], *ps[s / 2], *ps[s - 1], z_vertex);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Helpers.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Helpers.cpp
new file mode 100644
index 00000000..024c828f
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Helpers.cpp
@@ -0,0 +1,101 @@
+#include "ActsHelper/Utilities/Helpers.hpp"
+
+#include <cassert>
+
+#include <TAxis.h>
+#include <TEfficiency.h>
+#include <TFitResult.h>
+#include <TFitResultPtr.h>
+#include <TH1.h>
+#include <TH2.h>
+#include <TProfile.h>
+
+namespace ActsHelper::PlotHelpers {
+TH1F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varBinning) {
+  TH1F* hist =
+      new TH1F(histName, histTitle, varBinning.nBins(), varBinning.data());
+  hist->GetXaxis()->SetTitle(varBinning.title().c_str());
+  hist->GetYaxis()->SetTitle("Entries");
+  hist->Sumw2();
+  return hist;
+}
+
+TH2F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varXBinning, const Binning& varYBinning) {
+  TH2F* hist =
+      new TH2F(histName, histTitle, varXBinning.nBins(), varXBinning.data(),
+               varYBinning.nBins(), varYBinning.data());
+  hist->GetXaxis()->SetTitle(varXBinning.title().c_str());
+  hist->GetYaxis()->SetTitle(varYBinning.title().c_str());
+  hist->Sumw2();
+  return hist;
+}
+
+void fillHisto(TH1F* hist, float value, float weight) {
+  assert(hist != nullptr);
+  hist->Fill(value, weight);
+}
+
+void fillHisto(TH2F* hist, float xValue, float yValue, float weight) {
+  assert(hist != nullptr);
+  hist->Fill(xValue, yValue, weight);
+}
+
+void anaHisto(TH1D* inputHist, int j, TH1F* meanHist, TH1F* widthHist) {
+  // evaluate mean and width via the Gauss fit
+  assert(inputHist != nullptr);
+  if (inputHist->GetEntries() > 0) {
+    TFitResultPtr r = inputHist->Fit("gaus", "QS0");
+    if ((r.Get() != nullptr) && ((r->Status() % 1000) == 0)) {
+      // fill the mean and width into 'j'th bin of the meanHist and widthHist,
+      // respectively
+      meanHist->SetBinContent(j, r->Parameter(1));
+      meanHist->SetBinError(j, r->ParError(1));
+      widthHist->SetBinContent(j, r->Parameter(2));
+      widthHist->SetBinError(j, r->ParError(2));
+    }
+  }
+}
+
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varBinning) {
+  TEfficiency* efficiency =
+      new TEfficiency(effName, effTitle, varBinning.nBins(), varBinning.data());
+  return efficiency;
+}
+
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varXBinning, const Binning& varYBinning) {
+  TEfficiency* efficiency = new TEfficiency(
+      effName, effTitle, varXBinning.nBins(), varXBinning.data(),
+      varYBinning.nBins(), varYBinning.data());
+  return efficiency;
+}
+
+void fillEff(TEfficiency* efficiency, float value, bool status) {
+  assert(efficiency != nullptr);
+  efficiency->Fill(status, value);
+}
+
+void fillEff(TEfficiency* efficiency, float xValue, float yValue, bool status) {
+  assert(efficiency != nullptr);
+  efficiency->Fill(status, xValue, yValue);
+}
+
+TProfile* bookProf(const char* profName, const char* profTitle,
+                   const Binning& varXBinning, const Binning& varYBinning) {
+  TProfile* prof =
+      new TProfile(profName, profTitle, varXBinning.nBins(), varXBinning.data(),
+                   varYBinning.low(), varYBinning.high());
+  prof->GetXaxis()->SetTitle(varXBinning.title().c_str());
+  prof->GetYaxis()->SetTitle(varYBinning.title().c_str());
+  return prof;
+}
+
+void fillProf(TProfile* profile, float xValue, float yValue, float weight) {
+  assert(profile != nullptr);
+  profile->Fill(xValue, yValue, weight);
+}
+
+}  // namespace ActsHelper::PlotHelpers
diff --git a/Reconstruction/RecActsTracking/src/utils/Options.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Options.cpp
similarity index 82%
rename from Reconstruction/RecActsTracking/src/utils/Options.cpp
rename to Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Options.cpp
index 0ce24a87..515853c4 100644
--- a/Reconstruction/RecActsTracking/src/utils/Options.cpp
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Options.cpp
@@ -1,12 +1,4 @@
-// This file is part of the Acts project.
-//
-// Copyright (C) 2020 CERN for the benefit of the Acts project
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include "Options.hpp"
+#include "ActsHelper/Utilities/Options.hpp"
 
 #include <algorithm>
 #include <iostream>
@@ -21,8 +13,8 @@ constexpr char s_separator = ':';
 
 // interval
 
-std::istream& Options::operator>>(
-    std::istream& is, Options::Interval& interval) {
+std::istream& ActsHelper::Options::operator>>(
+    std::istream& is, ActsHelper::Options::Interval& interval) {
   std::string buf;
   is >> buf;
 
@@ -51,8 +43,8 @@ std::istream& Options::operator>>(
   return is;
 }
 
-std::ostream& Options::operator<<(
-    std::ostream& os, const Options::Interval& interval) {
+std::ostream& ActsHelper::Options::operator<<(
+    std::ostream& os, const ActsHelper::Options::Interval& interval) {
   if (!interval.lower.has_value() && !interval.upper.has_value()) {
     os << "unbounded";
   } else {
@@ -127,19 +119,19 @@ void print(std::ostream& os, std::size_t size, const value_t* values) {
 
 // fixed and variable number of generic values
 
-void Options::detail::parseDoublesFixed(std::istream& is,
+void ActsHelper::Options::detail::parseDoublesFixed(std::istream& is,
                                                       std::size_t size,
                                                       double* values) {
   parseFixed(is, size, values,
              [](const std::string& s) { return std::stod(s); });
 }
 
-void Options::detail::parseDoublesVariable(
+void ActsHelper::Options::detail::parseDoublesVariable(
     std::istream& is, std::vector<double>& values) {
   parseVariable(is, values, [](const std::string& s) { return std::stod(s); });
 }
 
-void Options::detail::printDoubles(std::ostream& os,
+void ActsHelper::Options::detail::printDoubles(std::ostream& os,
                                                  std::size_t size,
                                                  const double* values) {
   print(os, size, values);
@@ -147,19 +139,19 @@ void Options::detail::printDoubles(std::ostream& os,
 
 // fixed and variable number of integers
 
-void Options::detail::parseIntegersFixed(std::istream& is,
+void ActsHelper::Options::detail::parseIntegersFixed(std::istream& is,
                                                        std::size_t size,
                                                        int* values) {
   parseFixed(is, size, values,
              [](const std::string& s) { return std::stoi(s); });
 }
 
-void Options::detail::parseIntegersVariable(
+void ActsHelper::Options::detail::parseIntegersVariable(
     std::istream& is, std::vector<int>& values) {
   parseVariable(is, values, [](const std::string& s) { return std::stoi(s); });
 }
 
-void Options::detail::printIntegers(std::ostream& os,
+void ActsHelper::Options::detail::printIntegers(std::ostream& os,
                                                   std::size_t size,
                                                   const int* values) {
   print(os, size, values);
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Paths.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Paths.cpp
new file mode 100644
index 00000000..b41f4a15
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Utilities/Paths.cpp
@@ -0,0 +1,108 @@
+#include "ActsHelper/Utilities/Paths.hpp"
+
+#include "Acts/Utilities/Logger.hpp"
+
+#include <algorithm>
+#include <charconv>
+#include <cstdint>
+#include <cstdio>
+#include <filesystem>
+#include <map>
+#include <regex>
+#include <sstream>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+std::string ActsHelper::ensureWritableDirectory(const std::string& dir) {
+  using std::filesystem::current_path;
+  using std::filesystem::path;
+
+  auto dir_path = dir.empty() ? current_path() : path(dir);
+  if (exists(dir_path) && !is_directory(dir_path)) {
+    throw std::runtime_error("'" + dir +
+                             "' already exists but is not a directory");
+  }
+  create_directories(dir_path);
+  return canonical(dir_path).native();
+}
+
+std::string ActsHelper::joinPaths(const std::string& dir,
+                                    const std::string& name) {
+  if (dir.empty()) {
+    return name;
+  } else {
+    return dir + '/' + name;
+  }
+}
+
+std::string ActsHelper::perEventFilepath(const std::string& dir,
+                                           const std::string& name,
+                                           std::size_t event) {
+  char prefix[64];
+
+  snprintf(prefix, sizeof(prefix), "event%09zu-", event);
+
+  if (dir.empty()) {
+    return prefix + name;
+  } else {
+    return dir + '/' + prefix + name;
+  }
+}
+
+std::pair<std::size_t, std::size_t> ActsHelper::determineEventFilesRange(
+    const std::string& dir, const std::string& name) {
+  using std::filesystem::current_path;
+  using std::filesystem::directory_iterator;
+  using std::filesystem::path;
+
+  ACTS_LOCAL_LOGGER(
+      Acts::getDefaultLogger("EventFilesRange", Acts::Logging::INFO));
+
+  // ensure directory path is valid
+  auto dir_path = dir.empty() ? current_path() : path(dir);
+  if (!exists(dir_path)) {
+    throw std::runtime_error("'" + dir_path.native() + "' does not exists");
+  }
+  if (!is_directory(dir_path)) {
+    throw std::runtime_error("'" + dir_path.native() + "' is not a directory");
+  }
+
+  // invalid default range that allows simple restriction later on
+  std::size_t eventMin = SIZE_MAX;
+  std::size_t eventMax = 0;
+
+  // filter matching event files from the directory listing
+  std::string filename;
+  std::regex re("^event([0-9]+)-" + name + "$");
+  std::cmatch match;
+
+  for (const auto& f : directory_iterator(dir_path)) {
+    if ((!exists(f.status())) || (!is_regular_file(f.status()))) {
+      continue;
+    }
+    // keep a copy so the match can refer to the underlying const char*
+    filename = f.path().filename().native();
+    if (std::regex_match(filename.c_str(), match, re)) {
+      ACTS_VERBOSE("Matching file " << filename);
+
+      // first sub_match is the whole string, second should be the event number
+      std::size_t event = 0;
+      auto ret = std::from_chars(match[1].first, match[1].second, event);
+      if (ret.ptr == match[1].first) {
+        throw std::runtime_error(
+            "Could not extract event number from filename");
+      }
+      // enlarge available event range
+      eventMin = std::min(eventMin, event);
+      eventMax = std::max(eventMax, event);
+    }
+  }
+  ACTS_VERBOSE("Detected event range [" << eventMin << "," << eventMax << "]");
+
+  // should only occur if no files matched and the initial values persisted.
+  if (eventMax < eventMin) {
+    return std::make_pair(0u, 0u);
+  }
+  return std::make_pair(eventMin, eventMax + 1);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/DuplicationPlotTool.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/DuplicationPlotTool.cpp
new file mode 100644
index 00000000..d90241d8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/DuplicationPlotTool.cpp
@@ -0,0 +1,105 @@
+#include "ActsHelper/Validation/DuplicationPlotTool.hpp"
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Utilities/VectorHelpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <TEfficiency.h>
+#include <TProfile.h>
+
+using Acts::VectorHelpers::eta;
+using Acts::VectorHelpers::perp;
+using Acts::VectorHelpers::phi;
+using Acts::VectorHelpers::theta;
+
+ActsHelper::DuplicationPlotTool::DuplicationPlotTool(
+    const ActsHelper::DuplicationPlotTool::Config& cfg,
+    Acts::Logging::Level lvl)
+    : m_cfg(cfg),
+      m_logger(Acts::getDefaultLogger("DuplicationPlotTool", lvl)) {}
+
+void ActsHelper::DuplicationPlotTool::book(
+    DuplicationPlotTool::DuplicationPlotCache& duplicationPlotCache) const {
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPhi = m_cfg.varBinning.at("Phi");
+  PlotHelpers::Binning bNum = m_cfg.varBinning.at("Num");
+  ACTS_DEBUG("Initialize the histograms for duplication rate plots");
+
+  // duplication rate vs pT
+  duplicationPlotCache.duplicationRate_vs_pT =
+      PlotHelpers::bookEff("duplicationRate_vs_pT",
+                           "Duplication rate;pT [GeV/c];Duplication rate", bPt);
+  // duplication rate vs eta
+  duplicationPlotCache.duplicationRate_vs_eta = PlotHelpers::bookEff(
+      "duplicationRate_vs_eta", "Duplication rate;#eta;Duplication rate", bEta);
+  // duplication rate vs phi
+  duplicationPlotCache.duplicationRate_vs_phi = PlotHelpers::bookEff(
+      "duplicationRate_vs_phi", "Duplication rate;#phi;Duplication rate", bPhi);
+
+  // duplication number vs pT
+  duplicationPlotCache.nDuplicated_vs_pT = PlotHelpers::bookProf(
+      "nDuplicated_vs_pT", "Number of duplicated track candidates", bPt, bNum);
+  // duplication number vs eta
+  duplicationPlotCache.nDuplicated_vs_eta = PlotHelpers::bookProf(
+      "nDuplicated_vs_eta", "Number of duplicated track candidates", bEta,
+      bNum);
+  // duplication number vs phi
+  duplicationPlotCache.nDuplicated_vs_phi = PlotHelpers::bookProf(
+      "nDuplicated_vs_phi", "Number of duplicated track candidates", bPhi,
+      bNum);
+}
+
+void ActsHelper::DuplicationPlotTool::clear(
+    DuplicationPlotCache& duplicationPlotCache) const {
+  delete duplicationPlotCache.duplicationRate_vs_pT;
+  delete duplicationPlotCache.duplicationRate_vs_eta;
+  delete duplicationPlotCache.duplicationRate_vs_phi;
+  delete duplicationPlotCache.nDuplicated_vs_pT;
+  delete duplicationPlotCache.nDuplicated_vs_eta;
+  delete duplicationPlotCache.nDuplicated_vs_phi;
+}
+
+void ActsHelper::DuplicationPlotTool::write(
+    const DuplicationPlotTool::DuplicationPlotCache& duplicationPlotCache)
+    const {
+  ACTS_DEBUG("Write the plots to output file.");
+  duplicationPlotCache.duplicationRate_vs_pT->Write();
+  duplicationPlotCache.duplicationRate_vs_eta->Write();
+  duplicationPlotCache.duplicationRate_vs_phi->Write();
+  duplicationPlotCache.nDuplicated_vs_pT->Write();
+  duplicationPlotCache.nDuplicated_vs_eta->Write();
+  duplicationPlotCache.nDuplicated_vs_phi->Write();
+}
+
+void ActsHelper::DuplicationPlotTool::fill(
+    DuplicationPlotTool::DuplicationPlotCache& duplicationPlotCache,
+    const Acts::BoundTrackParameters& fittedParameters, bool status) const {
+  const auto momentum = fittedParameters.momentum();
+  const double fit_phi = phi(momentum);
+  const double fit_eta = eta(momentum);
+  const double fit_pT = perp(momentum);
+
+  PlotHelpers::fillEff(duplicationPlotCache.duplicationRate_vs_pT, fit_pT,
+                       status);
+  PlotHelpers::fillEff(duplicationPlotCache.duplicationRate_vs_eta, fit_eta,
+                       status);
+  PlotHelpers::fillEff(duplicationPlotCache.duplicationRate_vs_phi, fit_phi,
+                       status);
+}
+
+void ActsHelper::DuplicationPlotTool::fill(
+    DuplicationPlotTool::DuplicationPlotCache& duplicationPlotCache,
+    const ActsFatras::Particle& truthParticle,
+    std::size_t nDuplicatedTracks) const {
+  const auto t_phi = phi(truthParticle.direction());
+  const auto t_eta = eta(truthParticle.direction());
+  const auto t_pT = truthParticle.transverseMomentum();
+
+  PlotHelpers::fillProf(duplicationPlotCache.nDuplicated_vs_pT, t_pT,
+                        nDuplicatedTracks);
+  PlotHelpers::fillProf(duplicationPlotCache.nDuplicated_vs_eta, t_eta,
+                        nDuplicatedTracks);
+  PlotHelpers::fillProf(duplicationPlotCache.nDuplicated_vs_phi, t_phi,
+                        nDuplicatedTracks);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/EffPlotTool.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/EffPlotTool.cpp
new file mode 100644
index 00000000..fd1314ad
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/EffPlotTool.cpp
@@ -0,0 +1,66 @@
+#include "ActsHelper/Validation/EffPlotTool.hpp"
+
+#include "Acts/Utilities/VectorHelpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <TEfficiency.h>
+
+using Acts::VectorHelpers::eta;
+using Acts::VectorHelpers::perp;
+using Acts::VectorHelpers::phi;
+
+ActsHelper::EffPlotTool::EffPlotTool(
+    const ActsHelper::EffPlotTool::Config& cfg, Acts::Logging::Level lvl)
+    : m_cfg(cfg), m_logger(Acts::getDefaultLogger("EffPlotTool", lvl)) {}
+
+void ActsHelper::EffPlotTool::book(
+    EffPlotTool::EffPlotCache& effPlotCache) const {
+  PlotHelpers::Binning bPhi = m_cfg.varBinning.at("Phi");
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  PlotHelpers::Binning bDeltaR = m_cfg.varBinning.at("DeltaR");
+  ACTS_DEBUG("Initialize the histograms for efficiency plots");
+  // efficiency vs pT
+  effPlotCache.trackEff_vs_pT = PlotHelpers::bookEff(
+      "trackeff_vs_pT", "Tracking efficiency;Truth pT [GeV/c];Efficiency", bPt);
+  // efficiency vs eta
+  effPlotCache.trackEff_vs_eta = PlotHelpers::bookEff(
+      "trackeff_vs_eta", "Tracking efficiency;Truth #eta;Efficiency", bEta);
+  // efficiency vs phi
+  effPlotCache.trackEff_vs_phi = PlotHelpers::bookEff(
+      "trackeff_vs_phi", "Tracking efficiency;Truth #phi;Efficiency", bPhi);
+  // efficiancy vs distance to the closest truth particle
+  effPlotCache.trackEff_vs_DeltaR = PlotHelpers::bookEff(
+      "trackeff_vs_DeltaR",
+      "Tracking efficiency;Closest track #Delta R;Efficiency", bDeltaR);
+}
+
+void ActsHelper::EffPlotTool::clear(EffPlotCache& effPlotCache) const {
+  delete effPlotCache.trackEff_vs_pT;
+  delete effPlotCache.trackEff_vs_eta;
+  delete effPlotCache.trackEff_vs_phi;
+  delete effPlotCache.trackEff_vs_DeltaR;
+}
+
+void ActsHelper::EffPlotTool::write(
+    const EffPlotTool::EffPlotCache& effPlotCache) const {
+  ACTS_DEBUG("Write the plots to output file.");
+  effPlotCache.trackEff_vs_pT->Write();
+  effPlotCache.trackEff_vs_eta->Write();
+  effPlotCache.trackEff_vs_phi->Write();
+  effPlotCache.trackEff_vs_DeltaR->Write();
+}
+
+void ActsHelper::EffPlotTool::fill(EffPlotTool::EffPlotCache& effPlotCache,
+                                     const ActsFatras::Particle& truthParticle,
+                                     double deltaR, bool status) const {
+  const auto t_phi = phi(truthParticle.direction());
+  const auto t_eta = eta(truthParticle.direction());
+  const auto t_pT = truthParticle.transverseMomentum();
+  const auto t_deltaR = deltaR;
+
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_pT, t_pT, status);
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_eta, t_eta, status);
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_phi, t_phi, status);
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_DeltaR, t_deltaR, status);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/FakeRatePlotTool.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/FakeRatePlotTool.cpp
new file mode 100644
index 00000000..2a0c0f0d
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/FakeRatePlotTool.cpp
@@ -0,0 +1,120 @@
+#include "ActsHelper/Validation/FakeRatePlotTool.hpp"
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Utilities/VectorHelpers.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <TEfficiency.h>
+#include <TH2.h>
+
+using Acts::VectorHelpers::eta;
+using Acts::VectorHelpers::perp;
+using Acts::VectorHelpers::phi;
+using Acts::VectorHelpers::theta;
+
+ActsHelper::FakeRatePlotTool::FakeRatePlotTool(
+    const ActsHelper::FakeRatePlotTool::Config& cfg, Acts::Logging::Level lvl)
+    : m_cfg(cfg), m_logger(Acts::getDefaultLogger("FakeRatePlotTool", lvl)) {}
+
+void ActsHelper::FakeRatePlotTool::book(
+    FakeRatePlotTool::FakeRatePlotCache& fakeRatePlotCache) const {
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPhi = m_cfg.varBinning.at("Phi");
+  PlotHelpers::Binning bNum = m_cfg.varBinning.at("Num");
+  ACTS_DEBUG("Initialize the histograms for fake rate plots");
+
+  // number of reco tracks vs pT scatter plots
+  fakeRatePlotCache.nReco_vs_pT = PlotHelpers::bookHisto(
+      "nRecoTracks_vs_pT", "Number of reconstructed track candidates", bPt,
+      bNum);
+  // number of truth-matched tracks vs pT scatter plots
+  fakeRatePlotCache.nTruthMatched_vs_pT = PlotHelpers::bookHisto(
+      "nTruthMatchedTracks_vs_pT", "Number of truth-matched track candidates",
+      bPt, bNum);
+  // number of fake tracks vs pT scatter plots
+  fakeRatePlotCache.nFake_vs_pT = PlotHelpers::bookHisto(
+      "nFakeTracks_vs_pT", "Number of fake track candidates", bPt, bNum);
+
+  // number of reco tracks vs eta scatter plots
+  fakeRatePlotCache.nReco_vs_eta = PlotHelpers::bookHisto(
+      "nRecoTracks_vs_eta", "Number of reconstructed track candidates", bEta,
+      bNum);
+  // number of truth-matched tracks vs eta scatter plots
+  fakeRatePlotCache.nTruthMatched_vs_eta = PlotHelpers::bookHisto(
+      "nTruthMatchedTracks_vs_eta", "Number of truth-matched track candidates",
+      bEta, bNum);
+  // number of fake tracks vs eta scatter plots
+  fakeRatePlotCache.nFake_vs_eta = PlotHelpers::bookHisto(
+      "nFakeTracks_vs_eta", "Number of fake track candidates", bEta, bNum);
+
+  // fake rate vs pT
+  fakeRatePlotCache.fakeRate_vs_pT = PlotHelpers::bookEff(
+      "fakerate_vs_pT", "Tracking fake rate;pT [GeV/c];Fake rate", bPt);
+  // fake rate vs eta
+  fakeRatePlotCache.fakeRate_vs_eta = PlotHelpers::bookEff(
+      "fakerate_vs_eta", "Tracking fake rate;#eta;Fake rate", bEta);
+  // fake rate vs phi
+  fakeRatePlotCache.fakeRate_vs_phi = PlotHelpers::bookEff(
+      "fakerate_vs_phi", "Tracking fake rate;#phi;Fake rate", bPhi);
+}
+
+void ActsHelper::FakeRatePlotTool::clear(
+    FakeRatePlotCache& fakeRatePlotCache) const {
+  delete fakeRatePlotCache.nReco_vs_pT;
+  delete fakeRatePlotCache.nTruthMatched_vs_pT;
+  delete fakeRatePlotCache.nFake_vs_pT;
+  delete fakeRatePlotCache.nReco_vs_eta;
+  delete fakeRatePlotCache.nTruthMatched_vs_eta;
+  delete fakeRatePlotCache.nFake_vs_eta;
+  delete fakeRatePlotCache.fakeRate_vs_pT;
+  delete fakeRatePlotCache.fakeRate_vs_eta;
+  delete fakeRatePlotCache.fakeRate_vs_phi;
+}
+
+void ActsHelper::FakeRatePlotTool::write(
+    const FakeRatePlotTool::FakeRatePlotCache& fakeRatePlotCache) const {
+  ACTS_DEBUG("Write the plots to output file.");
+  fakeRatePlotCache.nReco_vs_pT->Write();
+  fakeRatePlotCache.nTruthMatched_vs_pT->Write();
+  fakeRatePlotCache.nFake_vs_pT->Write();
+  fakeRatePlotCache.nReco_vs_eta->Write();
+  fakeRatePlotCache.nTruthMatched_vs_eta->Write();
+  fakeRatePlotCache.nFake_vs_eta->Write();
+  fakeRatePlotCache.fakeRate_vs_pT->Write();
+  fakeRatePlotCache.fakeRate_vs_eta->Write();
+  fakeRatePlotCache.fakeRate_vs_phi->Write();
+}
+
+void ActsHelper::FakeRatePlotTool::fill(
+    FakeRatePlotTool::FakeRatePlotCache& fakeRatePlotCache,
+    const Acts::BoundTrackParameters& fittedParameters, bool status) const {
+  const auto momentum = fittedParameters.momentum();
+  const double fit_phi = phi(momentum);
+  const double fit_eta = eta(momentum);
+  const double fit_pT = perp(momentum);
+
+  PlotHelpers::fillEff(fakeRatePlotCache.fakeRate_vs_pT, fit_pT, status);
+  PlotHelpers::fillEff(fakeRatePlotCache.fakeRate_vs_eta, fit_eta, status);
+  PlotHelpers::fillEff(fakeRatePlotCache.fakeRate_vs_phi, fit_phi, status);
+}
+
+void ActsHelper::FakeRatePlotTool::fill(
+    FakeRatePlotTool::FakeRatePlotCache& fakeRatePlotCache,
+    const ActsFatras::Particle& truthParticle, std::size_t nTruthMatchedTracks,
+    std::size_t nFakeTracks) const {
+  const auto t_eta = eta(truthParticle.direction());
+  const auto t_pT = truthParticle.transverseMomentum();
+
+  PlotHelpers::fillHisto(fakeRatePlotCache.nReco_vs_pT, t_pT,
+                         nTruthMatchedTracks + nFakeTracks);
+  PlotHelpers::fillHisto(fakeRatePlotCache.nTruthMatched_vs_pT, t_pT,
+                         nTruthMatchedTracks);
+  PlotHelpers::fillHisto(fakeRatePlotCache.nFake_vs_pT, t_pT, nFakeTracks);
+
+  PlotHelpers::fillHisto(fakeRatePlotCache.nReco_vs_eta, t_eta,
+                         nTruthMatchedTracks + nFakeTracks);
+  PlotHelpers::fillHisto(fakeRatePlotCache.nTruthMatched_vs_eta, t_eta,
+                         nTruthMatchedTracks);
+  PlotHelpers::fillHisto(fakeRatePlotCache.nFake_vs_eta, t_eta, nFakeTracks);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/ResPlotTool.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/ResPlotTool.cpp
new file mode 100644
index 00000000..b2bb31e0
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/ResPlotTool.cpp
@@ -0,0 +1,249 @@
+#include "ActsHelper/Validation/ResPlotTool.hpp"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Helpers.hpp"
+#include "Acts/Utilities/Result.hpp"
+#include "ActsFatras/EventData/Particle.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <optional>
+#include <ostream>
+
+#include <TH1.h>
+#include <TH2.h>
+#include <TString.h>
+
+ActsHelper::ResPlotTool::ResPlotTool(
+    const ActsHelper::ResPlotTool::Config& cfg, Acts::Logging::Level lvl)
+    : m_cfg(cfg), m_logger(Acts::getDefaultLogger("ResPlotTool", lvl)) {}
+
+void ActsHelper::ResPlotTool::book(
+    ResPlotTool::ResPlotCache& resPlotCache) const {
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  PlotHelpers::Binning bPull = m_cfg.varBinning.at("Pull");
+
+  ACTS_DEBUG("Initialize the histograms for residual and pull plots");
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_cfg.paramNames.at(parID);
+
+    std::string parResidual = "Residual_" + parName;
+    // Binning for residual is parameter dependent
+    PlotHelpers::Binning bResidual = m_cfg.varBinning.at(parResidual);
+
+    // residual distributions
+    resPlotCache.res[parName] = PlotHelpers::bookHisto(
+        Form("res_%s", parName.c_str()),
+        Form("Residual of %s", parName.c_str()), bResidual);
+    // residual vs eta scatter plots
+    resPlotCache.res_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("res_%s_vs_eta", parName.c_str()),
+        Form("Residual of %s vs eta", parName.c_str()), bEta, bResidual);
+    // residual mean in each eta bin
+    resPlotCache.resMean_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("resmean_%s_vs_eta", parName.c_str()),
+        Form("Residual mean of %s", parName.c_str()), bEta);
+    // residual width in each eta bin
+    resPlotCache.resWidth_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("reswidth_%s_vs_eta", parName.c_str()),
+        Form("Residual width of %s", parName.c_str()), bEta);
+    // residual vs pT scatter plots
+    resPlotCache.res_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("res_%s_vs_pT", parName.c_str()),
+        Form("Residual of %s vs pT", parName.c_str()), bPt, bResidual);
+    // residual mean in each pT bin
+    resPlotCache.resMean_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("resmean_%s_vs_pT", parName.c_str()),
+        Form("Residual mean of %s", parName.c_str()), bPt);
+    // residual width in each pT bin
+    resPlotCache.resWidth_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("reswidth_%s_vs_pT", parName.c_str()),
+        Form("Residual width of %s", parName.c_str()), bPt);
+
+    // pull distritutions
+    resPlotCache.pull[parName] =
+        PlotHelpers::bookHisto(Form("pull_%s", parName.c_str()),
+                               Form("Pull of %s", parName.c_str()), bPull);
+    // pull vs eta scatter plots
+    resPlotCache.pull_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("pull_%s_vs_eta", parName.c_str()),
+        Form("Pull of %s vs eta", parName.c_str()), bEta, bPull);
+    // pull mean in each eta bin
+    resPlotCache.pullMean_vs_eta[parName] =
+        PlotHelpers::bookHisto(Form("pullmean_%s_vs_eta", parName.c_str()),
+                               Form("Pull mean of %s", parName.c_str()), bEta);
+    // pull width in each eta bin
+    resPlotCache.pullWidth_vs_eta[parName] =
+        PlotHelpers::bookHisto(Form("pullwidth_%s_vs_eta", parName.c_str()),
+                               Form("Pull width of %s", parName.c_str()), bEta);
+    // pull vs pT scatter plots
+    resPlotCache.pull_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("pull_%s_vs_pT", parName.c_str()),
+        Form("Pull of %s vs pT", parName.c_str()), bPt, bPull);
+    // pull mean in each pT bin
+    resPlotCache.pullMean_vs_pT[parName] =
+        PlotHelpers::bookHisto(Form("pullmean_%s_vs_pT", parName.c_str()),
+                               Form("Pull mean of %s", parName.c_str()), bPt);
+    // pull width in each pT bin
+    resPlotCache.pullWidth_vs_pT[parName] =
+        PlotHelpers::bookHisto(Form("pullwidth_%s_vs_pT", parName.c_str()),
+                               Form("Pull width of %s", parName.c_str()), bPt);
+  }
+}
+
+void ActsHelper::ResPlotTool::clear(ResPlotCache& resPlotCache) const {
+  ACTS_DEBUG("Delete the hists.");
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_cfg.paramNames.at(parID);
+    delete resPlotCache.res.at(parName);
+    delete resPlotCache.res_vs_eta.at(parName);
+    delete resPlotCache.resMean_vs_eta.at(parName);
+    delete resPlotCache.resWidth_vs_eta.at(parName);
+    delete resPlotCache.res_vs_pT.at(parName);
+    delete resPlotCache.resMean_vs_pT.at(parName);
+    delete resPlotCache.resWidth_vs_pT.at(parName);
+    delete resPlotCache.pull.at(parName);
+    delete resPlotCache.pull_vs_eta.at(parName);
+    delete resPlotCache.pullMean_vs_eta.at(parName);
+    delete resPlotCache.pullWidth_vs_eta.at(parName);
+    delete resPlotCache.pull_vs_pT.at(parName);
+    delete resPlotCache.pullMean_vs_pT.at(parName);
+    delete resPlotCache.pullWidth_vs_pT.at(parName);
+  }
+}
+
+void ActsHelper::ResPlotTool::write(
+    const ResPlotTool::ResPlotCache& resPlotCache) const {
+  ACTS_DEBUG("Write the hists to output file.");
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_cfg.paramNames.at(parID);
+    resPlotCache.res.at(parName)->Write();
+    resPlotCache.res_vs_eta.at(parName)->Write();
+    resPlotCache.resMean_vs_eta.at(parName)->Write();
+    resPlotCache.resWidth_vs_eta.at(parName)->Write();
+    resPlotCache.res_vs_pT.at(parName)->Write();
+    resPlotCache.resMean_vs_pT.at(parName)->Write();
+    resPlotCache.resWidth_vs_pT.at(parName)->Write();
+    resPlotCache.pull.at(parName)->Write();
+    resPlotCache.pull_vs_eta.at(parName)->Write();
+    resPlotCache.pullMean_vs_eta.at(parName)->Write();
+    resPlotCache.pullWidth_vs_eta.at(parName)->Write();
+    resPlotCache.pull_vs_pT.at(parName)->Write();
+    resPlotCache.pullMean_vs_pT.at(parName)->Write();
+    resPlotCache.pullWidth_vs_pT.at(parName)->Write();
+  }
+}
+
+void ActsHelper::ResPlotTool::fill(
+    ResPlotTool::ResPlotCache& resPlotCache, const Acts::GeometryContext& gctx,
+    const ActsFatras::Particle& truthParticle,
+    const Acts::BoundTrackParameters& fittedParamters) const {
+  using ParametersVector = Acts::BoundTrackParameters::ParametersVector;
+  using Acts::VectorHelpers::eta;
+  using Acts::VectorHelpers::perp;
+  using Acts::VectorHelpers::phi;
+  using Acts::VectorHelpers::theta;
+
+  // get the fitted parameter (at perigee surface) and its error
+  auto trackParameter = fittedParamters.parameters();
+
+  // get the perigee surface
+  auto pSurface = &fittedParamters.referenceSurface();
+
+  // get the truth position and momentum
+  ParametersVector truthParameter = ParametersVector::Zero();
+
+  // get the truth perigee parameter
+  auto lpResult = pSurface->globalToLocal(gctx, truthParticle.position(),
+                                          truthParticle.direction());
+  if (lpResult.ok()) {
+    truthParameter[Acts::BoundIndices::eBoundLoc0] =
+        lpResult.value()[Acts::BoundIndices::eBoundLoc0];
+    truthParameter[Acts::BoundIndices::eBoundLoc1] =
+        lpResult.value()[Acts::BoundIndices::eBoundLoc1];
+  } else {
+    ACTS_ERROR("Global to local transformation did not succeed.");
+  }
+  truthParameter[Acts::BoundIndices::eBoundPhi] =
+      phi(truthParticle.direction());
+  truthParameter[Acts::BoundIndices::eBoundTheta] =
+      theta(truthParticle.direction());
+  truthParameter[Acts::BoundIndices::eBoundQOverP] =
+      truthParticle.charge() / truthParticle.absoluteMomentum();
+  truthParameter[Acts::BoundIndices::eBoundTime] = truthParticle.time();
+
+  // get the truth eta and pT
+  const auto truthEta = eta(truthParticle.direction());
+  const auto truthPt = truthParticle.transverseMomentum();
+
+  // fill the histograms for residual and pull
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_cfg.paramNames.at(parID);
+    float residual = trackParameter[parID] - truthParameter[parID];
+    PlotHelpers::fillHisto(resPlotCache.res.at(parName), residual);
+    PlotHelpers::fillHisto(resPlotCache.res_vs_eta.at(parName), truthEta,
+                           residual);
+    PlotHelpers::fillHisto(resPlotCache.res_vs_pT.at(parName), truthPt,
+                           residual);
+
+    if (fittedParamters.covariance().has_value()) {
+      auto covariance = *fittedParamters.covariance();
+      if (covariance(parID, parID) > 0) {
+        float pull = residual / sqrt(covariance(parID, parID));
+        PlotHelpers::fillHisto(resPlotCache.pull[parName], pull);
+        PlotHelpers::fillHisto(resPlotCache.pull_vs_eta.at(parName), truthEta,
+                               pull);
+        PlotHelpers::fillHisto(resPlotCache.pull_vs_pT.at(parName), truthPt,
+                               pull);
+      } else {
+        ACTS_WARNING("Fitted track parameter :" << parName
+                                                << " has negative covariance = "
+                                                << covariance(parID, parID));
+      }
+    } else {
+      ACTS_WARNING("Fitted track parameter :" << parName
+                                              << " has no covariance");
+    }
+  }
+}
+
+// get the mean and width of residual/pull in each eta/pT bin and fill them into
+// histograms
+void ActsHelper::ResPlotTool::refinement(
+    ResPlotTool::ResPlotCache& resPlotCache) const {
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_cfg.paramNames.at(parID);
+    // refine the plots vs eta
+    for (int j = 1; j <= static_cast<int>(bEta.nBins()); j++) {
+      TH1D* temp_res = resPlotCache.res_vs_eta.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Residual_vs_eta_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_res, j,
+                            resPlotCache.resMean_vs_eta.at(parName),
+                            resPlotCache.resWidth_vs_eta.at(parName));
+
+      TH1D* temp_pull = resPlotCache.pull_vs_eta.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Pull_vs_eta_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_pull, j,
+                            resPlotCache.pullMean_vs_eta.at(parName),
+                            resPlotCache.pullWidth_vs_eta.at(parName));
+    }
+
+    // refine the plots vs pT
+    for (int j = 1; j <= static_cast<int>(bPt.nBins()); j++) {
+      TH1D* temp_res = resPlotCache.res_vs_pT.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Residual_vs_pT_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_res, j, resPlotCache.resMean_vs_pT.at(parName),
+                            resPlotCache.resWidth_vs_pT.at(parName));
+
+      TH1D* temp_pull = resPlotCache.pull_vs_pT.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Pull_vs_pT_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_pull, j,
+                            resPlotCache.pullMean_vs_pT.at(parName),
+                            resPlotCache.pullWidth_vs_pT.at(parName));
+    }
+  }
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackClassification.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackClassification.cpp
new file mode 100644
index 00000000..4f2efedf
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackClassification.cpp
@@ -0,0 +1,106 @@
+#include "ActsHelper/Validation/TrackClassification.hpp"
+
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/Utilities/MultiIndex.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/EventData/Trajectories.hpp"
+#include "ActsHelper/Utilities/Range.hpp"
+
+#include <algorithm>
+#include <utility>
+
+namespace {
+
+/// Increase the hit count for the given particle id by one.
+inline void increaseHitCount(
+    std::vector<ActsHelper::ParticleHitCount>& particleHitCounts,
+    ActsFatras::Barcode particleId) {
+  // linear search since there is no ordering
+  auto it = std::find_if(particleHitCounts.begin(), particleHitCounts.end(),
+                         [=](const ActsHelper::ParticleHitCount& phc) {
+                           return (phc.particleId == particleId);
+                         });
+  // either increase count if we saw the particle before or add it
+  if (it != particleHitCounts.end()) {
+    it->hitCount += 1u;
+  } else {
+    particleHitCounts.push_back({particleId, 1u});
+  }
+}
+
+/// Sort hit counts by decreasing values, i.e. majority particle comes first.
+inline void sortHitCount(
+    std::vector<ActsHelper::ParticleHitCount>& particleHitCounts) {
+  std::sort(particleHitCounts.begin(), particleHitCounts.end(),
+            [](const ActsHelper::ParticleHitCount& lhs,
+               const ActsHelper::ParticleHitCount& rhs) {
+              return (lhs.hitCount > rhs.hitCount);
+            });
+}
+
+}  // namespace
+
+void ActsHelper::identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const ProtoTrack& protoTrack,
+    std::vector<ActsHelper::ParticleHitCount>& particleHitCounts) {
+  particleHitCounts.clear();
+
+  for (auto hitIndex : protoTrack) {
+    // register all particles that generated this hit
+    for (auto hitParticle : makeRange(hitParticlesMap.equal_range(hitIndex))) {
+      increaseHitCount(particleHitCounts, hitParticle.second);
+    }
+  }
+  sortHitCount(particleHitCounts);
+}
+
+void ActsHelper::identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const Trajectories& trajectories, std::size_t tip,
+    std::vector<ParticleHitCount>& particleHitCounts) {
+  particleHitCounts.clear();
+
+  if (!trajectories.hasTrajectory(tip)) {
+    return;
+  }
+
+  trajectories.multiTrajectory().visitBackwards(tip, [&](const auto& state) {
+    // no truth info with non-measurement state
+    if (!state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) {
+      return true;
+    }
+    // register all particles that generated this hit
+    IndexSourceLink sl =
+        state.getUncalibratedSourceLink().template get<IndexSourceLink>();
+    auto hitIndex = sl.index();
+    for (auto hitParticle : makeRange(hitParticlesMap.equal_range(hitIndex))) {
+      increaseHitCount(particleHitCounts, hitParticle.second);
+    }
+    return true;
+  });
+  sortHitCount(particleHitCounts);
+}
+
+void ActsHelper::identifyContributingParticles(
+    const IndexMultimap<ActsFatras::Barcode>& hitParticlesMap,
+    const ConstTrackContainer::ConstTrackProxy& track,
+    std::vector<ParticleHitCount>& particleHitCounts) {
+  particleHitCounts.clear();
+
+  for (const auto& state : track.trackStatesReversed()) {
+    // no truth info with non-measurement state
+    if (!state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) {
+      continue;
+    }
+    // register all particles that generated this hit
+    IndexSourceLink sl =
+        state.getUncalibratedSourceLink().template get<IndexSourceLink>();
+    auto hitIndex = sl.index();
+    for (auto hitParticle : makeRange(hitParticlesMap.equal_range(hitIndex))) {
+      increaseHitCount(particleHitCounts, hitParticle.second);
+    }
+  }
+  sortHitCount(particleHitCounts);
+}
diff --git a/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackSummaryPlotTool.cpp b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackSummaryPlotTool.cpp
new file mode 100644
index 00000000..d8d685f6
--- /dev/null
+++ b/Reconstruction/RecActsTracking/ActsHelper/src/Validation/TrackSummaryPlotTool.cpp
@@ -0,0 +1,110 @@
+#include "ActsHelper/Validation/TrackSummaryPlotTool.hpp"
+
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Utilities/VectorHelpers.hpp"
+
+#include <TProfile.h>
+
+ActsHelper::TrackSummaryPlotTool::TrackSummaryPlotTool(
+    const ActsHelper::TrackSummaryPlotTool::Config& cfg,
+    Acts::Logging::Level lvl)
+    : m_cfg(cfg),
+      m_logger(Acts::getDefaultLogger("TrackSummaryPlotTool", lvl)) {}
+
+void ActsHelper::TrackSummaryPlotTool::book(
+    TrackSummaryPlotTool::TrackSummaryPlotCache& trackSummaryPlotCache) const {
+  PlotHelpers::Binning bEta = m_cfg.varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_cfg.varBinning.at("Pt");
+  PlotHelpers::Binning bNum = m_cfg.varBinning.at("Num");
+  ACTS_DEBUG("Initialize the histograms for track info plots");
+  // number of track states versus eta
+  trackSummaryPlotCache.nStates_vs_eta = PlotHelpers::bookProf(
+      "nStates_vs_eta", "Number of total states vs. #eta", bEta, bNum);
+  // number of measurements versus eta
+  trackSummaryPlotCache.nMeasurements_vs_eta = PlotHelpers::bookProf(
+      "nMeasurements_vs_eta", "Number of measurements vs. #eta", bEta, bNum);
+  // number of holes versus eta
+  trackSummaryPlotCache.nHoles_vs_eta = PlotHelpers::bookProf(
+      "nHoles_vs_eta", "Number of holes vs. #eta", bEta, bNum);
+  // number of outliers versus eta
+  trackSummaryPlotCache.nOutliers_vs_eta = PlotHelpers::bookProf(
+      "nOutliers_vs_eta", "Number of outliers vs. #eta", bEta, bNum);
+  // number of Shared Hits versus eta
+  trackSummaryPlotCache.nSharedHits_vs_eta = PlotHelpers::bookProf(
+      "nSharedHits_vs_eta", "Number of Shared Hits vs. #eta", bEta, bNum);
+  // number of track states versus pt
+  trackSummaryPlotCache.nStates_vs_pt = PlotHelpers::bookProf(
+      "nStates_vs_pT", "Number of total states vs. pT", bPt, bNum);
+  // number of measurements versus pt
+  trackSummaryPlotCache.nMeasurements_vs_pt = PlotHelpers::bookProf(
+      "nMeasurements_vs_pT", "Number of measurements vs. pT", bPt, bNum);
+  // number of holes versus pt
+  trackSummaryPlotCache.nHoles_vs_pt = PlotHelpers::bookProf(
+      "nHoles_vs_pT", "Number of holes vs. pT", bPt, bNum);
+  // number of outliers versus pt
+  trackSummaryPlotCache.nOutliers_vs_pt = PlotHelpers::bookProf(
+      "nOutliers_vs_pT", "Number of outliers vs. pT", bPt, bNum);
+  // number of Shared Hits versus pt
+  trackSummaryPlotCache.nSharedHits_vs_pt = PlotHelpers::bookProf(
+      "nSharedHits_vs_pT", "Number of Shared Hits vs. pT", bPt, bNum);
+}
+
+void ActsHelper::TrackSummaryPlotTool::clear(
+    TrackSummaryPlotCache& trackSummaryPlotCache) const {
+  delete trackSummaryPlotCache.nStates_vs_eta;
+  delete trackSummaryPlotCache.nMeasurements_vs_eta;
+  delete trackSummaryPlotCache.nOutliers_vs_eta;
+  delete trackSummaryPlotCache.nHoles_vs_eta;
+  delete trackSummaryPlotCache.nSharedHits_vs_eta;
+  delete trackSummaryPlotCache.nStates_vs_pt;
+  delete trackSummaryPlotCache.nMeasurements_vs_pt;
+  delete trackSummaryPlotCache.nOutliers_vs_pt;
+  delete trackSummaryPlotCache.nHoles_vs_pt;
+  delete trackSummaryPlotCache.nSharedHits_vs_pt;
+}
+
+void ActsHelper::TrackSummaryPlotTool::write(
+    const TrackSummaryPlotTool::TrackSummaryPlotCache& trackSummaryPlotCache)
+    const {
+  ACTS_DEBUG("Write the plots to output file.");
+  trackSummaryPlotCache.nStates_vs_eta->Write();
+  trackSummaryPlotCache.nMeasurements_vs_eta->Write();
+  trackSummaryPlotCache.nOutliers_vs_eta->Write();
+  trackSummaryPlotCache.nHoles_vs_eta->Write();
+  trackSummaryPlotCache.nSharedHits_vs_eta->Write();
+  trackSummaryPlotCache.nStates_vs_pt->Write();
+  trackSummaryPlotCache.nMeasurements_vs_pt->Write();
+  trackSummaryPlotCache.nOutliers_vs_pt->Write();
+  trackSummaryPlotCache.nHoles_vs_pt->Write();
+  trackSummaryPlotCache.nSharedHits_vs_pt->Write();
+}
+
+void ActsHelper::TrackSummaryPlotTool::fill(
+    TrackSummaryPlotTool::TrackSummaryPlotCache& trackSummaryPlotCache,
+    const Acts::BoundTrackParameters& fittedParameters, std::size_t nStates,
+    std::size_t nMeasurements, std::size_t nOutliers, std::size_t nHoles,
+    std::size_t nSharedHits) const {
+  using Acts::VectorHelpers::eta;
+  using Acts::VectorHelpers::perp;
+  const auto momentum = fittedParameters.momentum();
+  const double fit_eta = eta(momentum);
+  const double fit_pT = perp(momentum);
+
+  PlotHelpers::fillProf(trackSummaryPlotCache.nStates_vs_eta, fit_eta, nStates);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nMeasurements_vs_eta, fit_eta,
+                        nMeasurements);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nOutliers_vs_eta, fit_eta,
+                        nOutliers);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nHoles_vs_eta, fit_eta, nHoles);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nSharedHits_vs_eta, fit_eta,
+                        nSharedHits);
+
+  PlotHelpers::fillProf(trackSummaryPlotCache.nStates_vs_pt, fit_pT, nStates);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nMeasurements_vs_pt, fit_pT,
+                        nMeasurements);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nOutliers_vs_pt, fit_pT,
+                        nOutliers);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nHoles_vs_pt, fit_pT, nHoles);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nSharedHits_vs_pt, fit_pT,
+                        nSharedHits);
+}
diff --git a/Reconstruction/RecActsTracking/CMakeLists.txt b/Reconstruction/RecActsTracking/CMakeLists.txt
index d0c8c9d9..ca889490 100644
--- a/Reconstruction/RecActsTracking/CMakeLists.txt
+++ b/Reconstruction/RecActsTracking/CMakeLists.txt
@@ -1,37 +1,3 @@
-find_package(Acts COMPONENTS
-    Core PluginFpeMonitoring PluginGeant4 PluginJson
-    PluginTGeo PluginDD4hep PluginEDM4hep Fatras) 
-
-if(NOT Acts_FOUND)
-    message("Acts package not found. RecActsTracking module requires Acts.")
-    return()
-endif()
-
-gaudi_add_module(RecActsTracking
-                 SOURCES
-                        src/RecActsTracking.cpp
-                 LINK DetInterface
-                      k4FWCore::k4FWCore
-                      Gaudi::GaudiAlgLib Gaudi::GaudiKernel
-                      ${LCIO_LIBRARIES} 
-                      ${DD4hep_COMPONENT_LIBRARIES}
-                      EDM4HEP::edm4hep EDM4HEP::edm4hepDict
-                      ${podio_LIBRARIES} podio::podioRootIO
-                      GearSvc ${GEAR_LIBRARIES}
-                      ActsCore ActsPluginFpeMonitoring ActsPluginGeant4
-                      ActsPluginJson ActsPluginTGeo  ActsPluginDD4hep
-                      ActsPluginEDM4hep ActsFatras
-)
-
-target_include_directories(RecActsTracking PUBLIC $ENV{ACTS}/include)
-
-target_include_directories(RecActsTracking PUBLIC
-  ${LCIO_INCLUDE_DIRS}
-  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>/include
-  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
-
-install(TARGETS RecActsTracking
-  EXPORT CEPCSWTargets
-  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin
-  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib
-  COMPONENT dev)
+add_subdirectory(ActsHelper)
+add_subdirectory(RecActsSvc)
+add_subdirectory(RecActsTracking)
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/CMakeLists.txt b/Reconstruction/RecActsTracking/RecActsSvc/CMakeLists.txt
new file mode 100644
index 00000000..6ae7ffc3
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/CMakeLists.txt
@@ -0,0 +1,34 @@
+find_package(Acts COMPONENTS
+    Core PluginFpeMonitoring PluginGeant4 PluginJson
+    PluginTGeo PluginDD4hep PluginEDM4hep Fatras)
+
+if(NOT Acts_FOUND)
+    message("Acts package not found. RecActsSvc module requires Acts.")
+    return()
+endif()
+
+gaudi_add_header_only_library(RecActsSvc LINK ActsHelperLib)
+
+gaudi_add_module(RecActsSvcPlugins
+                 SOURCES 
+                    src/RecActsSvc.cpp
+                 LINK RecActsSvc
+                    ActsHelperLib
+                    Gaudi::GaudiKernel
+                    ${DD4hep_COMPONENT_LIBRARIES}
+                    EDM4HEP::edm4hep EDM4HEP::edm4hepDict
+                    ActsCore ActsPluginFpeMonitoring ActsPluginGeant4
+                    ActsPluginJson ActsPluginTGeo  ActsPluginDD4hep
+                    ActsPluginEDM4hep ActsFatras
+)
+
+target_include_directories(RecActsSvcPlugins PUBLIC $ENV{ACTS}/include)
+
+install(TARGETS RecActsSvc RecActsSvcPlugins
+  EXPORT CEPCSWTargets
+  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib
+  COMPONENT dev)
+
+
+
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/include/RecActsSvc/IRecActsSvc.h b/Reconstruction/RecActsTracking/RecActsSvc/include/RecActsSvc/IRecActsSvc.h
new file mode 100644
index 00000000..86501cfa
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/include/RecActsSvc/IRecActsSvc.h
@@ -0,0 +1,98 @@
+#ifndef IRECACTSSVC_H
+#define IRECACTSSVC_H
+
+#include "GaudiKernel/IService.h"
+#include "GaudiKernel/MsgStream.h"
+
+#include "ActsHelper/Detectors/TGeoDetector.hpp"
+
+#include "ActsHelper/EventData/AverageSimHits.hpp"
+#include "ActsHelper/EventData/Cluster.hpp"
+#include "ActsHelper/EventData/DriftCircle.hpp"
+#include "ActsHelper/EventData/ExtractedSimulationProcess.hpp"
+#include "ActsHelper/EventData/GeometryContainers.hpp"
+#include "ActsHelper/EventData/Index.hpp"
+#include "ActsHelper/EventData/IndexSourceLink.hpp"
+#include "ActsHelper/EventData/Measurement.hpp"
+#include "ActsHelper/EventData/MeasurementCalibration.hpp"
+#include "ActsHelper/EventData/ProtoTrack.hpp"
+#include "ActsHelper/EventData/ProtoVertex.hpp"
+#include "ActsHelper/EventData/ScalingCalibrator.hpp"
+#include "ActsHelper/EventData/SimHit.hpp"
+#include "ActsHelper/EventData/SimParticle.hpp"
+#include "ActsHelper/EventData/SimSeed.hpp"
+#include "ActsHelper/EventData/SimSpacePoint.hpp"
+#include "ActsHelper/EventData/SimVertex.hpp"
+#include "ActsHelper/EventData/Track.hpp"
+#include "ActsHelper/EventData/Trajectories.hpp"
+#include "ActsHelper/EventData/TruthMatching.hpp"
+#include "ActsHelper/EventData/Vertex.hpp"
+
+#include "ActsHelper/MagneticField/FieldMapRootIo.hpp"
+#include "ActsHelper/MagneticField/FieldMapTextIo.hpp"
+#include "ActsHelper/MagneticField/MagneticField.hpp"
+#include "ActsHelper/MagneticField/ScalableBField.hpp"
+
+#include "ActsHelper/Utilities/EventDataTransforms.hpp"
+#include "ActsHelper/Utilities/GroupBy.hpp"
+#include "ActsHelper/Utilities/Helpers.hpp"
+#include "ActsHelper/Utilities/Options.hpp"
+#include "ActsHelper/Utilities/OptionsFwd.hpp"
+#include "ActsHelper/Utilities/Paths.hpp"
+#include "ActsHelper/Utilities/Range.hpp"
+#include "ActsHelper/Utilities/tbbWrap.hpp"
+
+#include "ActsHelper/Validation/DuplicationPlotTool.hpp"
+#include "ActsHelper/Validation/EffPlotTool.hpp"
+#include "ActsHelper/Validation/FakeRatePlotTool.hpp"
+#include "ActsHelper/Validation/ResPlotTool.hpp"
+#include "ActsHelper/Validation/TrackClassification.hpp"
+#include "ActsHelper/Validation/TrackSummaryPlotTool.hpp"
+
+#include "ActsHelper/Algorithms/TrackFitting/RefittingCalibrator.hpp"
+#include "ActsHelper/Algorithms/TrackFitting/TrackFitterFunction.hpp"
+
+// ACTS context / environment
+
+class IRecActsSvc : virtual public IInterface {
+public:
+    DeclareInterfaceID(IRecActsSvc, 0, 1); // major/minor version
+    virtual ~IRecActsSvc() = default;
+
+    // ---------------------------------
+    // ---- main tracking functions ----
+    // ---------------------------------
+
+    // build geometry
+    virtual StatusCode BuildGeometry() = 0;
+
+    // read input 
+    virtual StatusCode ReadInput(const edm4hep::TrackerHit hit,
+                                 uint64_t acts_volume, uint64_t acts_layer, uint64_t acts_sensitive,
+                                 bool buildSpacePoint = false,
+                                 int moduleType = 0, /* 0: PLANAR, 1: CYLINDER, 2: TPC*/
+                                 double onSurfaceTolerance = 1e-3) = 0;
+    // finalize
+    virtual StatusCode Clean() = 0;
+    virtual StatusCode WriteOutput() = 0;
+
+    // ------------------
+    // ---- Get Data ----
+    // ------------------
+
+    virtual std::string GetTGeoFilePath() = 0;
+    virtual std::string GetTGeoConfigFilePath() = 0;
+    virtual std::string GetMaterialMapFilePath() = 0;
+    virtual std::shared_ptr<const Acts::TrackingGeometry> Geometry() = 0;
+    
+    virtual std::vector<const ActsHelper::SimSpacePoint*>* SpacePointPtrs() = 0;
+    virtual ActsHelper::IndexSourceLinkContainer* SourceLinks() = 0;
+    virtual std::vector<::Acts::BoundVariantMeasurement>* Measurements() = 0;
+    virtual ActsHelper::SimSeedContainer* Seeds() = 0;
+    virtual std::vector<ActsHelper::SimSeedContainer>* SelectedSeeds() = 0;
+    virtual std::vector<std::vector<::Acts::BoundTrackParameters>>* TrackParameters() = 0;
+    virtual std::vector<std::vector<::Acts::SourceLink>>* TrackSourceLinks() = 0;
+};
+
+
+#endif // IRECACTSSVC_H 
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.cpp b/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.cpp
new file mode 100644
index 00000000..8b995fe0
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.cpp
@@ -0,0 +1,251 @@
+#include "RecActsSvc.h"
+
+DECLARE_COMPONENT(RecActsSvc)
+
+RecActsSvc::RecActsSvc(const std::string& name, ISvcLocator* svc)
+    : base_class(name, svc)
+{
+}
+
+RecActsSvc::~RecActsSvc()
+{
+}
+
+StatusCode RecActsSvc::initialize()
+{
+    m_isGeometryBuilt = false;
+    if (m_TGeoFilePath.value().empty()){
+        error() << "TGeoFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    if (m_TGeoConfigFilePath.value().empty()){
+        error() << "TGeoConfigFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    if (m_MaterialMapFilePath.value().empty()){
+        error() << "MaterialMapFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    BuildGeometry();
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSvc::finalize()
+{
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSvc::BuildGeometry()
+{
+    if (m_isGeometryBuilt) {
+        info() << "ACTS geometry already built!" << endmsg;
+        return StatusCode::SUCCESS;
+    }
+
+    if (m_TGeoFilePath.value().empty()){
+        error() << "TGeoFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    if (m_TGeoConfigFilePath.value().empty()){
+        error() << "TGeoConfigFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    if (m_MaterialMapFilePath.value().empty()){
+        error() << "MaterialMapFilePath is not set!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    info() << "Building ACTS geometry ..." << endmsg;
+    info() << "TGeoFilePath: " << m_TGeoFilePath << endmsg;
+    info() << "TGeoConfigFilePath: " << m_TGeoConfigFilePath << endmsg;
+    info() << "MaterialMapFilePath: " << m_MaterialMapFilePath << endmsg;
+
+    auto logger = Acts::getDefaultLogger("TGeoDetector", Acts::Logging::INFO);
+    trackingGeometry = buildTGeoDetector(geoContext, detElementStore, m_TGeoFilePath, m_TGeoConfigFilePath, m_MaterialMapFilePath, *logger);
+
+    m_isGeometryBuilt = true;
+    info() << "ACTS geometry built successfully!" << endmsg;
+
+    // (REMOVE IT) DEBUG: write to csv
+    // hit_stream = new std::ofstream("/home/zhangyz/CEPCSW/hits.csv");
+    // hit_writer = new csv2::Writer<csv2::delimiter<','>>(*hit_stream);
+    // std::vector<std::string> hit_header = {"sim_x", "sim_y", "sim_z", "hit_x", "hit_y", "hit_z"};
+    // hit_writer->write_row(hit_header);
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSvc::ReadInput(const edm4hep::TrackerHit hit,
+                                 uint64_t acts_volume, uint64_t acts_layer, uint64_t acts_sensitive,
+                                 bool buildSpacePoint,
+                                 int moduleType,
+                                 double onSurfaceTolerance)
+{
+    if (!m_isGeometryBuilt){
+        error() << "Read input failed, ACTS geometry is not built!" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    double acts_x = hit.getPosition()[0];
+    double acts_y = hit.getPosition()[1];
+    double acts_z = hit.getPosition()[2];
+    std::array<float, 6> m_covMatrix = hit.getCovMatrix();
+
+    Acts::GeometryIdentifier moduleGeoId;
+    moduleGeoId.setVolume(acts_volume);
+    moduleGeoId.setBoundary(0);
+    moduleGeoId.setLayer(acts_layer);
+    moduleGeoId.setApproach(0);
+    moduleGeoId.setSensitive(acts_sensitive);
+
+    uint32_t measurementIdx = m_measurements.size();
+    ActsHelper::IndexSourceLink sourceLink{moduleGeoId, measurementIdx};
+    Acts::SourceLink sl{sourceLink};
+    boost::container::static_vector<Acts::SourceLink, 2> slinks;
+    slinks.emplace_back(sl);
+
+    // get the surface of the hit
+    ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
+    const Acts::Surface* acts_surface = surfaceAccessor(sl);
+
+    // get the local position of the hit
+    const Acts::Vector3 globalPos{acts_x, acts_y, acts_z};
+    auto acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalFakeMom, onSurfaceTolerance);
+    if (!acts_local_postion.ok()){
+        info() << "Error: failed to get local position at: (x,y,z) " << acts_x << ", " << acts_y << ", " << acts_z << endmsg;
+        return StatusCode::FAILURE;
+    }
+    const std::array<Acts::BoundIndices, 2> indices{Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundLoc1};
+    const Acts::Vector2 par{acts_local_postion.value()[0], acts_local_postion.value()[1]};
+    
+    auto acts_global_postion = acts_surface->localToGlobal(geoContext, par, globalFakeMom);
+    // debug() << "measurements global position(x,y,z): " << acts_x << ", "
+    //         << acts_y << ", " << acts_z << endmsg;
+    // debug() << "converted position(x,y,z): " << acts_global_postion[0] << ", "
+    //         << acts_global_postion[1] << ", " << acts_global_postion[2] << endmsg;
+
+    // (REMOVE IT) DEBUG: write to csv
+    // std::vector<std::string> hit_data = {std::to_string(simhit.getPosition()[0]), std::to_string(simhit.getPosition()[1]), std::to_string(simhit.getPosition()[2]), std::to_string(hit.getPosition()[0]), std::to_string(hit.getPosition()[1]), std::to_string(hit.getPosition()[2])};
+    // hit_writer->write_row(hit_data);
+
+    // create and store the measurement
+    // 0: PLANAR, 1: CYLINDER, 2: TPC
+    // PLANAR {u_direction[0], u_direction[1], resU, v_direction[0], v_direction[1], resV}
+    // CYLINDER {resU*resU/2, 0, resU*resU/2, 0, 0, resV*resV}
+    // TPC {sin(unsmearedPhi)*sin(unsmearedPhi)*tpcRPhiRes*tpcRPhiRes,
+    //      -cos(unsmearedPhi)*sin(unsmearedPhi)*tpcRPhiRes*tpcRPhiRes,
+    //      cos(unsmearedPhi)*cos(unsmearedPhi)*tpcRPhiRes*tpcRPhiRes,
+    //      0, 0, tpcZRes*tpcZRes}
+    Acts::ActsSquareMatrix<2> cov = Acts::ActsSquareMatrix<2>::Identity();
+    if (moduleType == 0){
+        cov(0, 0) = std::max<double>(double(m_covMatrix[2]*m_covMatrix[2]), eps);
+        cov(1, 1) = std::max<double>(double(m_covMatrix[5]*m_covMatrix[5]), eps);
+    } else if (moduleType == 1){
+        cov(0, 0) = std::max<double>(double(fabs(m_covMatrix[0] + m_covMatrix[2])), eps);
+        cov(1, 1) = std::max<double>(double(fabs(m_covMatrix[5])), eps);
+    } else if (moduleType == 2){
+        cov(0, 0) = std::max<double>(double(fabs(m_covMatrix[0] + m_covMatrix[2])), eps);
+        cov(1, 1) = std::max<double>(double(fabs(m_covMatrix[5])), eps);
+    } else {
+        error() << "Invalid module type: " << moduleType << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    // debug() << "covariance(u,v): " << cov(0, 0) << ", " << cov(1, 1) << endmsg;
+
+    m_measurements.emplace_back(Acts::Measurement<Acts::BoundIndices, 2>(sl, indices, par, cov));
+    m_sourceLinks.insert(m_sourceLinks.end(), sourceLink);
+    
+    if (buildSpacePoint){
+        ActsHelper::SimSpacePoint *hitExt = new ActsHelper::SimSpacePoint(
+            acts_global_postion[0], acts_global_postion[1], acts_global_postion[2], 0.,
+            eps, cov(1, 1), eps,
+            slinks);
+
+        m_SpacePointPtrs.push_back(hitExt);
+    }
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSvc::Clean()
+{
+    m_SpacePointPtrs.clear();
+    m_sourceLinks.clear();
+    m_measurements.clear();
+    m_Seeds.clear();
+    m_Selected_Seeds.clear();
+    m_trackParameters.clear();
+    m_trackSourceLinks.clear();
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSvc::WriteOutput()
+{
+    return StatusCode::SUCCESS;
+}
+
+std::string RecActsSvc::GetTGeoFilePath()
+{
+    debug() << "Get TGeoFilePath: " << m_TGeoFilePath.value() << endmsg;
+    return m_TGeoFilePath.value();
+}
+
+std::string RecActsSvc::GetTGeoConfigFilePath()
+{
+    debug() << "Get TGeoConfigFilePath: " << m_TGeoConfigFilePath.value() << endmsg;
+    return m_TGeoConfigFilePath.value();
+}
+
+std::string RecActsSvc::GetMaterialMapFilePath()
+{
+    debug() << "Get MaterialMapFilePath: " << m_MaterialMapFilePath.value() << endmsg;
+    return m_MaterialMapFilePath.value();
+}
+
+std::shared_ptr<const Acts::TrackingGeometry> RecActsSvc::Geometry()
+{
+    if (!m_isGeometryBuilt) {
+        error() << "Failed to get ACTS geometry, Geometry is not built!" << endmsg;
+        return nullptr;
+    }
+
+    return trackingGeometry;
+}
+
+std::vector<const ActsHelper::SimSpacePoint*> *RecActsSvc::SpacePointPtrs()
+{
+    return &m_SpacePointPtrs;
+}
+
+ActsHelper::IndexSourceLinkContainer *RecActsSvc::SourceLinks()
+{
+    return &m_sourceLinks;
+}
+
+std::vector<::Acts::BoundVariantMeasurement> *RecActsSvc::Measurements()
+{
+    return &m_measurements;
+}
+
+ActsHelper::SimSeedContainer *RecActsSvc::Seeds()
+{
+    return &m_Seeds;
+}
+
+std::vector<ActsHelper::SimSeedContainer> *RecActsSvc::SelectedSeeds()
+{
+    return &m_Selected_Seeds;
+}
+
+std::vector<std::vector<::Acts::BoundTrackParameters>> *RecActsSvc::TrackParameters()
+{
+    return &m_trackParameters;
+}
+
+std::vector<std::vector<::Acts::SourceLink>> *RecActsSvc::TrackSourceLinks()
+{
+    return &m_trackSourceLinks;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.h b/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.h
new file mode 100644
index 00000000..110fa5d4
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/RecActsSvc.h
@@ -0,0 +1,83 @@
+#ifndef RECACTSSVC_H
+#define RECACTSSVC_H
+
+#include "GaudiKernel/Service.h"
+#include "RecActsSvc/IRecActsSvc.h"
+
+
+#include <iostream>
+#include <fstream>
+#include <cstdlib>
+#include <sstream>
+#include <filesystem>
+
+// #include "csv2/writer.hpp"
+
+class RecActsSvc : public extends<Service, IRecActsSvc> {
+public:
+    RecActsSvc(const std::string& name, ISvcLocator* svc);
+    virtual ~RecActsSvc();
+
+    StatusCode initialize() override;
+    StatusCode finalize() override;
+
+    // ---------------------------------
+    // ---- main tracking functions ----
+    // ---------------------------------
+
+    StatusCode BuildGeometry() override;
+
+    StatusCode ReadInput(const edm4hep::TrackerHit hit,
+                         uint64_t acts_volume, uint64_t acts_layer, uint64_t acts_sensitive,
+                         bool buildSpacePoint = false,
+                         int moduleType = 0, /* 0: PLANAR, 1: CYLINDER, 2: TPC*/
+                         double onSurfaceTolerance = 1e-3) override;
+
+    StatusCode Clean() override;
+    StatusCode WriteOutput() override;
+
+    // ------------------
+    // ---- Get Data ----
+    // ------------------
+
+    std::string GetTGeoFilePath() override;
+    std::string GetTGeoConfigFilePath() override;
+    std::string GetMaterialMapFilePath() override;
+    std::shared_ptr<const Acts::TrackingGeometry> Geometry() override;
+
+    std::vector<const ActsHelper::SimSpacePoint*>* SpacePointPtrs() override;
+    ActsHelper::IndexSourceLinkContainer* SourceLinks() override;
+    std::vector<::Acts::BoundVariantMeasurement>* Measurements() override;
+    ActsHelper::SimSeedContainer* Seeds() override;
+    std::vector<ActsHelper::SimSeedContainer>* SelectedSeeds() override;
+    std::vector<std::vector<::Acts::BoundTrackParameters>>* TrackParameters() override;
+    std::vector<std::vector<::Acts::SourceLink>>* TrackSourceLinks() override;
+
+private:
+    Gaudi::Property<std::string> m_TGeoFilePath{this, "TGeoFile"};
+    Gaudi::Property<std::string> m_TGeoConfigFilePath{this, "TGeoConfigFile"};
+    Gaudi::Property<std::string> m_MaterialMapFilePath{this, "MaterialMapFile"};
+
+    Acts::GeometryContext geoContext;
+    Acts::MagneticFieldContext magFieldContext;
+    Acts::CalibrationContext calibContext;
+
+    bool m_isGeometryBuilt;
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry;
+    std::vector<std::shared_ptr<const Acts::TGeoDetectorElement>> detElementStore;
+    
+    // Store the space points & measurements & sourcelinks **per event**
+    std::vector<const ActsHelper::SimSpacePoint*> m_SpacePointPtrs;
+    ActsHelper::IndexSourceLinkContainer m_sourceLinks;
+    std::vector<::Acts::BoundVariantMeasurement> m_measurements;
+    ActsHelper::SimSeedContainer m_Seeds;
+    std::vector<ActsHelper::SimSeedContainer> m_Selected_Seeds;
+
+    std::vector<std::vector<::Acts::BoundTrackParameters>> m_trackParameters;
+    std::vector<std::vector<::Acts::SourceLink>> m_trackSourceLinks;
+
+    double eps = 1e-5;
+    const Acts::Vector3 globalFakeMom{1.e6, 1.e6, 1.e6};
+};
+
+#endif // RECACTSSVC_H 
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/mio.hpp b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/mio.hpp
new file mode 100644
index 00000000..48a86684
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/mio.hpp
@@ -0,0 +1,1562 @@
+
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_MMAP_HEADER
+#define MIO_MMAP_HEADER
+
+// #include "mio/page.hpp"
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_PAGE_HEADER
+#define MIO_PAGE_HEADER
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+namespace mio {
+
+/**
+ * This is used by `basic_mmap` to determine whether to create a read-only or
+ * a read-write memory mapping.
+ */
+enum class access_mode { read, write };
+
+/**
+ * Determines the operating system's page allocation granularity.
+ *
+ * On the first call to this function, it invokes the operating system specific syscall
+ * to determine the page size, caches the value, and returns it. Any subsequent call to
+ * this function serves the cached value, so no further syscalls are made.
+ */
+inline size_t page_size() {
+  static const size_t page_size = [] {
+#ifdef _WIN32
+    SYSTEM_INFO SystemInfo;
+    GetSystemInfo(&SystemInfo);
+    return SystemInfo.dwAllocationGranularity;
+#else
+    return sysconf(_SC_PAGE_SIZE);
+#endif
+  }();
+  return page_size;
+}
+
+/**
+ * Alligns `offset` to the operating's system page size such that it subtracts the
+ * difference until the nearest page boundary before `offset`, or does nothing if
+ * `offset` is already page aligned.
+ */
+inline size_t make_offset_page_aligned(size_t offset) noexcept {
+  const size_t page_size_ = page_size();
+  // Use integer division to round down to the nearest page alignment.
+  return offset / page_size_ * page_size_;
+}
+
+} // namespace mio
+
+#endif // MIO_PAGE_HEADER
+
+#include <cstdint>
+#include <iterator>
+#include <string>
+#include <system_error>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif // WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#else // ifdef _WIN32
+#define INVALID_HANDLE_VALUE -1
+#endif // ifdef _WIN32
+
+namespace mio {
+
+// This value may be provided as the `length` parameter to the constructor or
+// `map`, in which case a memory mapping of the entire file is created.
+enum { map_entire_file = 0 };
+
+#ifdef _WIN32
+using file_handle_type = HANDLE;
+#else
+using file_handle_type = int;
+#endif
+
+// This value represents an invalid file handle type. This can be used to
+// determine whether `basic_mmap::file_handle` is valid, for example.
+const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;
+
+template <access_mode AccessMode, typename ByteT> struct basic_mmap {
+  using value_type = ByteT;
+  using size_type = size_t;
+  using reference = value_type &;
+  using const_reference = const value_type &;
+  using pointer = value_type *;
+  using const_pointer = const value_type *;
+  using difference_type = std::ptrdiff_t;
+  using iterator = pointer;
+  using const_iterator = const_pointer;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  using iterator_category = std::random_access_iterator_tag;
+  using handle_type = file_handle_type;
+
+  static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char.");
+
+private:
+  // Points to the first requested byte, and not to the actual start of the mapping.
+  pointer data_ = nullptr;
+
+  // Length--in bytes--requested by user (which may not be the length of the
+  // full mapping) and the length of the full mapping.
+  size_type length_ = 0;
+  size_type mapped_length_ = 0;
+
+  // Letting user map a file using both an existing file handle and a path
+  // introcudes some complexity (see `is_handle_internal_`).
+  // On POSIX, we only need a file handle to create a mapping, while on
+  // Windows systems the file handle is necessary to retrieve a file mapping
+  // handle, but any subsequent operations on the mapped region must be done
+  // through the latter.
+  handle_type file_handle_ = INVALID_HANDLE_VALUE;
+#ifdef _WIN32
+  handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;
+#endif
+
+  // Letting user map a file using both an existing file handle and a path
+  // introcudes some complexity in that we must not close the file handle if
+  // user provided it, but we must close it if we obtained it using the
+  // provided path. For this reason, this flag is used to determine when to
+  // close `file_handle_`.
+  bool is_handle_internal_;
+
+public:
+  /**
+   * The default constructed mmap object is in a non-mapped state, that is,
+   * any operation that attempts to access nonexistent underlying data will
+   * result in undefined behaviour/segmentation faults.
+   */
+  basic_mmap() = default;
+
+#ifdef __cpp_exceptions
+  /**
+   * The same as invoking the `map` function, except any error that may occur
+   * while establishing the mapping is wrapped in a `std::system_error` and is
+   * thrown.
+   */
+  template <typename String>
+  basic_mmap(const String &path, const size_type offset = 0,
+             const size_type length = map_entire_file) {
+    std::error_code error;
+    map(path, offset, length, error);
+    if (error) {
+      throw std::system_error(error);
+    }
+  }
+
+  /**
+   * The same as invoking the `map` function, except any error that may occur
+   * while establishing the mapping is wrapped in a `std::system_error` and is
+   * thrown.
+   */
+  basic_mmap(const handle_type handle, const size_type offset = 0,
+             const size_type length = map_entire_file) {
+    std::error_code error;
+    map(handle, offset, length, error);
+    if (error) {
+      throw std::system_error(error);
+    }
+  }
+#endif // __cpp_exceptions
+
+  /**
+   * `basic_mmap` has single-ownership semantics, so transferring ownership
+   * may only be accomplished by moving the object.
+   */
+  basic_mmap(const basic_mmap &) = delete;
+  basic_mmap(basic_mmap &&);
+  basic_mmap &operator=(const basic_mmap &) = delete;
+  basic_mmap &operator=(basic_mmap &&);
+
+  /**
+   * If this is a read-write mapping, the destructor invokes sync. Regardless
+   * of the access mode, unmap is invoked as a final step.
+   */
+  ~basic_mmap();
+
+  /**
+   * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
+   * however, a mapped region of a file gets its own handle, which is returned by
+   * 'mapping_handle'.
+   */
+  handle_type file_handle() const noexcept { return file_handle_; }
+  handle_type mapping_handle() const noexcept;
+
+  /** Returns whether a valid memory mapping has been created. */
+  bool is_open() const noexcept { return file_handle_ != invalid_handle; }
+
+  /**
+   * Returns true if no mapping was established, that is, conceptually the
+   * same as though the length that was mapped was 0. This function is
+   * provided so that this class has Container semantics.
+   */
+  bool empty() const noexcept { return length() == 0; }
+
+  /** Returns true if a mapping was established. */
+  bool is_mapped() const noexcept;
+
+  /**
+   * `size` and `length` both return the logical length, i.e. the number of bytes
+   * user requested to be mapped, while `mapped_length` returns the actual number of
+   * bytes that were mapped which is a multiple of the underlying operating system's
+   * page allocation granularity.
+   */
+  size_type size() const noexcept { return length(); }
+  size_type length() const noexcept { return length_; }
+  size_type mapped_length() const noexcept { return mapped_length_; }
+
+  /** Returns the offset relative to the start of the mapping. */
+  size_type mapping_offset() const noexcept { return mapped_length_ - length_; }
+
+  /**
+   * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
+   * exists.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  pointer data() noexcept {
+    return data_;
+  }
+  const_pointer data() const noexcept { return data_; }
+
+  /**
+   * Returns an iterator to the first requested byte, if a valid memory mapping
+   * exists, otherwise this function call is undefined behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  iterator begin() noexcept {
+    return data();
+  }
+  const_iterator begin() const noexcept { return data(); }
+  const_iterator cbegin() const noexcept { return data(); }
+
+  /**
+   * Returns an iterator one past the last requested byte, if a valid memory mapping
+   * exists, otherwise this function call is undefined behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  iterator end() noexcept {
+    return data() + length();
+  }
+  const_iterator end() const noexcept { return data() + length(); }
+  const_iterator cend() const noexcept { return data() + length(); }
+
+  /**
+   * Returns a reverse iterator to the last memory mapped byte, if a valid
+   * memory mapping exists, otherwise this function call is undefined
+   * behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  reverse_iterator rbegin() noexcept {
+    return reverse_iterator(end());
+  }
+  const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
+  const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); }
+
+  /**
+   * Returns a reverse iterator past the first mapped byte, if a valid memory
+   * mapping exists, otherwise this function call is undefined behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  reverse_iterator rend() noexcept {
+    return reverse_iterator(begin());
+  }
+  const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
+  const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
+
+  /**
+   * Returns a reference to the `i`th byte from the first requested byte (as returned
+   * by `data`). If this is invoked when no valid memory mapping has been created
+   * prior to this call, undefined behaviour ensues.
+   */
+  reference operator[](const size_type i) noexcept { return data_[i]; }
+  const_reference operator[](const size_type i) const noexcept { return data_[i]; }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `path`, which must be a path to an existing file, is used to retrieve a file
+   * handle (which is closed when the object destructs or `unmap` is called), which is
+   * then used to memory map the requested region. Upon failure, `error` is set to
+   * indicate the reason and the object remains in an unmapped state.
+   *
+   * `offset` is the number of bytes, relative to the start of the file, where the
+   * mapping should begin. When specifying it, there is no need to worry about
+   * providing a value that is aligned with the operating system's page allocation
+   * granularity. This is adjusted by the implementation such that the first requested
+   * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
+   * `offset` from the start of the file.
+   *
+   * `length` is the number of bytes to map. It may be `map_entire_file`, in which
+   * case a mapping of the entire file is created.
+   */
+  template <typename String>
+  void map(const String &path, const size_type offset, const size_type length,
+           std::error_code &error);
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `path`, which must be a path to an existing file, is used to retrieve a file
+   * handle (which is closed when the object destructs or `unmap` is called), which is
+   * then used to memory map the requested region. Upon failure, `error` is set to
+   * indicate the reason and the object remains in an unmapped state.
+   *
+   * The entire file is mapped.
+   */
+  template <typename String> void map(const String &path, std::error_code &error) {
+    map(path, 0, map_entire_file, error);
+  }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is
+   * unsuccesful, the reason is reported via `error` and the object remains in
+   * a state as if this function hadn't been called.
+   *
+   * `handle`, which must be a valid file handle, which is used to memory map the
+   * requested region. Upon failure, `error` is set to indicate the reason and the
+   * object remains in an unmapped state.
+   *
+   * `offset` is the number of bytes, relative to the start of the file, where the
+   * mapping should begin. When specifying it, there is no need to worry about
+   * providing a value that is aligned with the operating system's page allocation
+   * granularity. This is adjusted by the implementation such that the first requested
+   * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
+   * `offset` from the start of the file.
+   *
+   * `length` is the number of bytes to map. It may be `map_entire_file`, in which
+   * case a mapping of the entire file is created.
+   */
+  void map(const handle_type handle, const size_type offset, const size_type length,
+           std::error_code &error);
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is
+   * unsuccesful, the reason is reported via `error` and the object remains in
+   * a state as if this function hadn't been called.
+   *
+   * `handle`, which must be a valid file handle, which is used to memory map the
+   * requested region. Upon failure, `error` is set to indicate the reason and the
+   * object remains in an unmapped state.
+   *
+   * The entire file is mapped.
+   */
+  void map(const handle_type handle, std::error_code &error) {
+    map(handle, 0, map_entire_file, error);
+  }
+
+  /**
+   * If a valid memory mapping has been created prior to this call, this call
+   * instructs the kernel to unmap the memory region and disassociate this object
+   * from the file.
+   *
+   * The file handle associated with the file that is mapped is only closed if the
+   * mapping was created using a file path. If, on the other hand, an existing
+   * file handle was used to create the mapping, the file handle is not closed.
+   */
+  void unmap();
+
+  void swap(basic_mmap &other);
+
+  /** Flushes the memory mapped page to disk. Errors are reported via `error`. */
+  template <access_mode A = AccessMode>
+  typename std::enable_if<A == access_mode::write, void>::type sync(std::error_code &error);
+
+  /**
+   * All operators compare the address of the first byte and size of the two mapped
+   * regions.
+   */
+
+private:
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  pointer get_mapping_start() noexcept {
+    return !data() ? nullptr : data() - mapping_offset();
+  }
+
+  const_pointer get_mapping_start() const noexcept {
+    return !data() ? nullptr : data() - mapping_offset();
+  }
+
+  /**
+   * The destructor syncs changes to disk if `AccessMode` is `write`, but not
+   * if it's `read`, but since the destructor cannot be templated, we need to
+   * do SFINAE in a dedicated function, where one syncs and the other is a noop.
+   */
+  template <access_mode A = AccessMode>
+  typename std::enable_if<A == access_mode::write, void>::type conditional_sync();
+  template <access_mode A = AccessMode>
+  typename std::enable_if<A == access_mode::read, void>::type conditional_sync();
+};
+
+template <access_mode AccessMode, typename ByteT>
+bool operator==(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+template <access_mode AccessMode, typename ByteT>
+bool operator!=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+template <access_mode AccessMode, typename ByteT>
+bool operator<(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+template <access_mode AccessMode, typename ByteT>
+bool operator<=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+template <access_mode AccessMode, typename ByteT>
+bool operator>(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+template <access_mode AccessMode, typename ByteT>
+bool operator>=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b);
+
+/**
+ * This is the basis for all read-only mmap objects and should be preferred over
+ * directly using `basic_mmap`.
+ */
+template <typename ByteT> using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;
+
+/**
+ * This is the basis for all read-write mmap objects and should be preferred over
+ * directly using `basic_mmap`.
+ */
+template <typename ByteT> using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;
+
+/**
+ * These aliases cover the most common use cases, both representing a raw byte stream
+ * (either with a char or an unsigned char/uint8_t).
+ */
+using mmap_source = basic_mmap_source<char>;
+using ummap_source = basic_mmap_source<unsigned char>;
+
+using mmap_sink = basic_mmap_sink<char>;
+using ummap_sink = basic_mmap_sink<unsigned char>;
+
+/**
+ * Convenience factory method that constructs a mapping for any `basic_mmap` or
+ * `basic_mmap` type.
+ */
+template <typename MMap, typename MappingToken>
+MMap make_mmap(const MappingToken &token, int64_t offset, int64_t length, std::error_code &error) {
+  MMap mmap;
+  mmap.map(token, offset, length, error);
+  return mmap;
+}
+
+/**
+ * Convenience factory method.
+ *
+ * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
+ * `std::filesystem::path`, `std::vector<char>`, or similar), or a
+ * `mmap_source::handle_type`.
+ */
+template <typename MappingToken>
+mmap_source make_mmap_source(const MappingToken &token, mmap_source::size_type offset,
+                             mmap_source::size_type length, std::error_code &error) {
+  return make_mmap<mmap_source>(token, offset, length, error);
+}
+
+template <typename MappingToken>
+mmap_source make_mmap_source(const MappingToken &token, std::error_code &error) {
+  return make_mmap_source(token, 0, map_entire_file, error);
+}
+
+/**
+ * Convenience factory method.
+ *
+ * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
+ * `std::filesystem::path`, `std::vector<char>`, or similar), or a
+ * `mmap_sink::handle_type`.
+ */
+template <typename MappingToken>
+mmap_sink make_mmap_sink(const MappingToken &token, mmap_sink::size_type offset,
+                         mmap_sink::size_type length, std::error_code &error) {
+  return make_mmap<mmap_sink>(token, offset, length, error);
+}
+
+template <typename MappingToken>
+mmap_sink make_mmap_sink(const MappingToken &token, std::error_code &error) {
+  return make_mmap_sink(token, 0, map_entire_file, error);
+}
+
+} // namespace mio
+
+// #include "detail/mmap.ipp"
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_BASIC_MMAP_IMPL
+#define MIO_BASIC_MMAP_IMPL
+
+// #include "mio/mmap.hpp"
+
+// #include "mio/page.hpp"
+
+// #include "mio/detail/string_util.hpp"
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_STRING_UTIL_HEADER
+#define MIO_STRING_UTIL_HEADER
+
+#include <type_traits>
+
+namespace mio {
+namespace detail {
+
+template <typename S, typename C = typename std::decay<S>::type,
+          typename = decltype(std::declval<C>().data()),
+          typename = typename std::enable_if<std::is_same<typename C::value_type, char>::value
+#ifdef _WIN32
+                                             || std::is_same<typename C::value_type, wchar_t>::value
+#endif
+                                             >::type>
+struct char_type_helper {
+  using type = typename C::value_type;
+};
+
+template <class T> struct char_type { using type = typename char_type_helper<T>::type; };
+
+// TODO: can we avoid this brute force approach?
+template <> struct char_type<char *> { using type = char; };
+
+template <> struct char_type<const char *> { using type = char; };
+
+template <size_t N> struct char_type<char[N]> { using type = char; };
+
+template <size_t N> struct char_type<const char[N]> { using type = char; };
+
+#ifdef _WIN32
+template <> struct char_type<wchar_t *> { using type = wchar_t; };
+
+template <> struct char_type<const wchar_t *> { using type = wchar_t; };
+
+template <size_t N> struct char_type<wchar_t[N]> { using type = wchar_t; };
+
+template <size_t N> struct char_type<const wchar_t[N]> { using type = wchar_t; };
+#endif // _WIN32
+
+template <typename CharT, typename S> struct is_c_str_helper {
+  static constexpr bool value =
+      std::is_same<CharT *,
+                   // TODO: I'm so sorry for this... Can this be made cleaner?
+                   typename std::add_pointer<typename std::remove_cv<typename std::remove_pointer<
+                       typename std::decay<S>::type>::type>::type>::type>::value;
+};
+
+template <typename S> struct is_c_str {
+  static constexpr bool value = is_c_str_helper<char, S>::value;
+};
+
+#ifdef _WIN32
+template <typename S> struct is_c_wstr {
+  static constexpr bool value = is_c_str_helper<wchar_t, S>::value;
+};
+#endif // _WIN32
+
+template <typename S> struct is_c_str_or_c_wstr {
+  static constexpr bool value = is_c_str<S>::value
+#ifdef _WIN32
+                                || is_c_wstr<S>::value
+#endif
+      ;
+};
+
+template <typename String, typename = decltype(std::declval<String>().data()),
+          typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type>
+const typename char_type<String>::type *c_str(const String &path) {
+  return path.data();
+}
+
+template <typename String, typename = decltype(std::declval<String>().empty()),
+          typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type>
+bool empty(const String &path) {
+  return path.empty();
+}
+
+template <typename String,
+          typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type>
+const typename char_type<String>::type *c_str(String path) {
+  return path;
+}
+
+template <typename String,
+          typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type>
+bool empty(String path) {
+  return !path || (*path == 0);
+}
+
+} // namespace detail
+} // namespace mio
+
+#endif // MIO_STRING_UTIL_HEADER
+
+#include <algorithm>
+
+#ifndef _WIN32
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+namespace mio {
+namespace detail {
+
+#ifdef _WIN32
+namespace win {
+
+/** Returns the 4 upper bytes of an 8-byte integer. */
+inline DWORD int64_high(int64_t n) noexcept { return n >> 32; }
+
+/** Returns the 4 lower bytes of an 8-byte integer. */
+inline DWORD int64_low(int64_t n) noexcept { return n & 0xffffffff; }
+
+template <typename String, typename = typename std::enable_if<
+                               std::is_same<typename char_type<String>::type, char>::value>::type>
+file_handle_type open_file_helper(const String &path, const access_mode mode) {
+  return ::CreateFileA(
+      c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
+      FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+}
+
+template <typename String>
+typename std::enable_if<std::is_same<typename char_type<String>::type, wchar_t>::value,
+                        file_handle_type>::type
+open_file_helper(const String &path, const access_mode mode) {
+  return ::CreateFileW(
+      c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
+      FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+}
+
+} // namespace win
+#endif // _WIN32
+
+/**
+ * Returns the last platform specific system error (errno on POSIX and
+ * GetLastError on Win) as a `std::error_code`.
+ */
+inline std::error_code last_error() noexcept {
+  std::error_code error;
+#ifdef _WIN32
+  error.assign(GetLastError(), std::system_category());
+#else
+  error.assign(errno, std::system_category());
+#endif
+  return error;
+}
+
+template <typename String>
+file_handle_type open_file(const String &path, const access_mode mode, std::error_code &error) {
+  error.clear();
+  if (detail::empty(path)) {
+    error = std::make_error_code(std::errc::invalid_argument);
+    return invalid_handle;
+  }
+#ifdef _WIN32
+  const auto handle = win::open_file_helper(path, mode);
+#else // POSIX
+  const auto handle = ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR);
+#endif
+  if (handle == invalid_handle) {
+    error = detail::last_error();
+  }
+  return handle;
+}
+
+inline size_t query_file_size(file_handle_type handle, std::error_code &error) {
+  error.clear();
+#ifdef _WIN32
+  LARGE_INTEGER file_size;
+  if (::GetFileSizeEx(handle, &file_size) == 0) {
+    error = detail::last_error();
+    return 0;
+  }
+  return static_cast<int64_t>(file_size.QuadPart);
+#else // POSIX
+  struct stat sbuf;
+  if (::fstat(handle, &sbuf) == -1) {
+    error = detail::last_error();
+    return 0;
+  }
+  return sbuf.st_size;
+#endif
+}
+
+struct mmap_context {
+  char *data;
+  int64_t length;
+  int64_t mapped_length;
+#ifdef _WIN32
+  file_handle_type file_mapping_handle;
+#endif
+};
+
+inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset,
+                               const int64_t length, const access_mode mode,
+                               std::error_code &error) {
+  const int64_t aligned_offset = make_offset_page_aligned(offset);
+  const int64_t length_to_map = offset - aligned_offset + length;
+#ifdef _WIN32
+  const int64_t max_file_size = offset + length;
+  const auto file_mapping_handle = ::CreateFileMapping(
+      file_handle, 0, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
+      win::int64_high(max_file_size), win::int64_low(max_file_size), 0);
+  if (file_mapping_handle == invalid_handle) {
+    error = detail::last_error();
+    return {};
+  }
+  char *mapping_start = static_cast<char *>(::MapViewOfFile(
+      file_mapping_handle, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
+      win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map));
+  if (mapping_start == nullptr) {
+    // Close file handle if mapping it failed.
+    ::CloseHandle(file_mapping_handle);
+    error = detail::last_error();
+    return {};
+  }
+#else // POSIX
+  char *mapping_start =
+      static_cast<char *>(::mmap(0, // Don't give hint as to where to map.
+                                 length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE,
+                                 MAP_SHARED, file_handle, aligned_offset));
+  if (mapping_start == MAP_FAILED) {
+    error = detail::last_error();
+    return {};
+  }
+#endif
+  mmap_context ctx;
+  ctx.data = mapping_start + offset - aligned_offset;
+  ctx.length = length;
+  ctx.mapped_length = length_to_map;
+#ifdef _WIN32
+  ctx.file_mapping_handle = file_mapping_handle;
+#endif
+  return ctx;
+}
+
+} // namespace detail
+
+// -- basic_mmap --
+
+template <access_mode AccessMode, typename ByteT> basic_mmap<AccessMode, ByteT>::~basic_mmap() {
+  conditional_sync();
+  unmap();
+}
+
+template <access_mode AccessMode, typename ByteT>
+basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap &&other)
+    : data_(std::move(other.data_)), length_(std::move(other.length_)),
+      mapped_length_(std::move(other.mapped_length_)), file_handle_(std::move(other.file_handle_))
+#ifdef _WIN32
+      ,
+      file_mapping_handle_(std::move(other.file_mapping_handle_))
+#endif
+      ,
+      is_handle_internal_(std::move(other.is_handle_internal_)) {
+  other.data_ = nullptr;
+  other.length_ = other.mapped_length_ = 0;
+  other.file_handle_ = invalid_handle;
+#ifdef _WIN32
+  other.file_mapping_handle_ = invalid_handle;
+#endif
+}
+
+template <access_mode AccessMode, typename ByteT>
+basic_mmap<AccessMode, ByteT> &basic_mmap<AccessMode, ByteT>::operator=(basic_mmap &&other) {
+  if (this != &other) {
+    // First the existing mapping needs to be removed.
+    unmap();
+    data_ = std::move(other.data_);
+    length_ = std::move(other.length_);
+    mapped_length_ = std::move(other.mapped_length_);
+    file_handle_ = std::move(other.file_handle_);
+#ifdef _WIN32
+    file_mapping_handle_ = std::move(other.file_mapping_handle_);
+#endif
+    is_handle_internal_ = std::move(other.is_handle_internal_);
+
+    // The moved from basic_mmap's fields need to be reset, because
+    // otherwise other's destructor will unmap the same mapping that was
+    // just moved into this.
+    other.data_ = nullptr;
+    other.length_ = other.mapped_length_ = 0;
+    other.file_handle_ = invalid_handle;
+#ifdef _WIN32
+    other.file_mapping_handle_ = invalid_handle;
+#endif
+    other.is_handle_internal_ = false;
+  }
+  return *this;
+}
+
+template <access_mode AccessMode, typename ByteT>
+typename basic_mmap<AccessMode, ByteT>::handle_type
+basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept {
+#ifdef _WIN32
+  return file_mapping_handle_;
+#else
+  return file_handle_;
+#endif
+}
+
+template <access_mode AccessMode, typename ByteT>
+template <typename String>
+void basic_mmap<AccessMode, ByteT>::map(const String &path, const size_type offset,
+                                        const size_type length, std::error_code &error) {
+  error.clear();
+  if (detail::empty(path)) {
+    error = std::make_error_code(std::errc::invalid_argument);
+    return;
+  }
+  const auto handle = detail::open_file(path, AccessMode, error);
+  if (error) {
+    return;
+  }
+
+  map(handle, offset, length, error);
+  // This MUST be after the call to map, as that sets this to true.
+  if (!error) {
+    is_handle_internal_ = true;
+  }
+}
+
+template <access_mode AccessMode, typename ByteT>
+void basic_mmap<AccessMode, ByteT>::map(const handle_type handle, const size_type offset,
+                                        const size_type length, std::error_code &error) {
+  error.clear();
+  if (handle == invalid_handle) {
+    error = std::make_error_code(std::errc::bad_file_descriptor);
+    return;
+  }
+
+  const auto file_size = detail::query_file_size(handle, error);
+  if (error) {
+    return;
+  }
+
+  if (offset + length > file_size) {
+    error = std::make_error_code(std::errc::invalid_argument);
+    return;
+  }
+
+  const auto ctx = detail::memory_map(
+      handle, offset, length == map_entire_file ? (file_size - offset) : length, AccessMode, error);
+  if (!error) {
+    // We must unmap the previous mapping that may have existed prior to this call.
+    // Note that this must only be invoked after a new mapping has been created in
+    // order to provide the strong guarantee that, should the new mapping fail, the
+    // `map` function leaves this instance in a state as though the function had
+    // never been invoked.
+    unmap();
+    file_handle_ = handle;
+    is_handle_internal_ = false;
+    data_ = reinterpret_cast<pointer>(ctx.data);
+    length_ = ctx.length;
+    mapped_length_ = ctx.mapped_length;
+#ifdef _WIN32
+    file_mapping_handle_ = ctx.file_mapping_handle;
+#endif
+  }
+}
+
+template <access_mode AccessMode, typename ByteT>
+template <access_mode A>
+typename std::enable_if<A == access_mode::write, void>::type
+basic_mmap<AccessMode, ByteT>::sync(std::error_code &error) {
+  error.clear();
+  if (!is_open()) {
+    error = std::make_error_code(std::errc::bad_file_descriptor);
+    return;
+  }
+
+  if (data()) {
+#ifdef _WIN32
+    if (::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 ||
+        ::FlushFileBuffers(file_handle_) == 0)
+#else // POSIX
+    if (::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
+#endif
+    {
+      error = detail::last_error();
+      return;
+    }
+  }
+#ifdef _WIN32
+  if (::FlushFileBuffers(file_handle_) == 0) {
+    error = detail::last_error();
+  }
+#endif
+}
+
+template <access_mode AccessMode, typename ByteT> void basic_mmap<AccessMode, ByteT>::unmap() {
+  if (!is_open()) {
+    return;
+  }
+  // TODO do we care about errors here?
+#ifdef _WIN32
+  if (is_mapped()) {
+    ::UnmapViewOfFile(get_mapping_start());
+    ::CloseHandle(file_mapping_handle_);
+  }
+#else // POSIX
+  if (data_) {
+    ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_);
+  }
+#endif
+
+  // If `file_handle_` was obtained by our opening it (when map is called with
+  // a path, rather than an existing file handle), we need to close it,
+  // otherwise it must not be closed as it may still be used outside this
+  // instance.
+  if (is_handle_internal_) {
+#ifdef _WIN32
+    ::CloseHandle(file_handle_);
+#else // POSIX
+    ::close(file_handle_);
+#endif
+  }
+
+  // Reset fields to their default values.
+  data_ = nullptr;
+  length_ = mapped_length_ = 0;
+  file_handle_ = invalid_handle;
+#ifdef _WIN32
+  file_mapping_handle_ = invalid_handle;
+#endif
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept {
+#ifdef _WIN32
+  return file_mapping_handle_ != invalid_handle;
+#else // POSIX
+  return is_open();
+#endif
+}
+
+template <access_mode AccessMode, typename ByteT>
+void basic_mmap<AccessMode, ByteT>::swap(basic_mmap &other) {
+  if (this != &other) {
+    using std::swap;
+    swap(data_, other.data_);
+    swap(file_handle_, other.file_handle_);
+#ifdef _WIN32
+    swap(file_mapping_handle_, other.file_mapping_handle_);
+#endif
+    swap(length_, other.length_);
+    swap(mapped_length_, other.mapped_length_);
+    swap(is_handle_internal_, other.is_handle_internal_);
+  }
+}
+
+template <access_mode AccessMode, typename ByteT>
+template <access_mode A>
+typename std::enable_if<A == access_mode::write, void>::type
+basic_mmap<AccessMode, ByteT>::conditional_sync() {
+  // This is invoked from the destructor, so not much we can do about
+  // failures here.
+  std::error_code ec;
+  sync(ec);
+}
+
+template <access_mode AccessMode, typename ByteT>
+template <access_mode A>
+typename std::enable_if<A == access_mode::read, void>::type
+basic_mmap<AccessMode, ByteT>::conditional_sync() {
+  // noop
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator==(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  return a.data() == b.data() && a.size() == b.size();
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator!=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  return !(a == b);
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator<(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  if (a.data() == b.data()) {
+    return a.size() < b.size();
+  }
+  return a.data() < b.data();
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator<=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  return !(a > b);
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator>(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  if (a.data() == b.data()) {
+    return a.size() > b.size();
+  }
+  return a.data() > b.data();
+}
+
+template <access_mode AccessMode, typename ByteT>
+bool operator>=(const basic_mmap<AccessMode, ByteT> &a, const basic_mmap<AccessMode, ByteT> &b) {
+  return !(a < b);
+}
+
+} // namespace mio
+
+#endif // MIO_BASIC_MMAP_IMPL
+
+#endif // MIO_MMAP_HEADER
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_PAGE_HEADER
+#define MIO_PAGE_HEADER
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+namespace mio {
+
+/**
+ * This is used by `basic_mmap` to determine whether to create a read-only or
+ * a read-write memory mapping.
+ */
+enum class access_mode { read, write };
+
+/**
+ * Determines the operating system's page allocation granularity.
+ *
+ * On the first call to this function, it invokes the operating system specific syscall
+ * to determine the page size, caches the value, and returns it. Any subsequent call to
+ * this function serves the cached value, so no further syscalls are made.
+ */
+inline size_t page_size() {
+  static const size_t page_size = [] {
+#ifdef _WIN32
+    SYSTEM_INFO SystemInfo;
+    GetSystemInfo(&SystemInfo);
+    return SystemInfo.dwAllocationGranularity;
+#else
+    return sysconf(_SC_PAGE_SIZE);
+#endif
+  }();
+  return page_size;
+}
+
+/**
+ * Alligns `offset` to the operating's system page size such that it subtracts the
+ * difference until the nearest page boundary before `offset`, or does nothing if
+ * `offset` is already page aligned.
+ */
+inline size_t make_offset_page_aligned(size_t offset) noexcept {
+  const size_t page_size_ = page_size();
+  // Use integer division to round down to the nearest page alignment.
+  return offset / page_size_ * page_size_;
+}
+
+} // namespace mio
+
+#endif // MIO_PAGE_HEADER
+/* Copyright 2017 https://github.com/mandreyel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MIO_SHARED_MMAP_HEADER
+#define MIO_SHARED_MMAP_HEADER
+
+// #include "mio/mmap.hpp"
+
+#include <memory>       // std::shared_ptr
+#include <system_error> // std::error_code
+
+namespace mio {
+
+/**
+ * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with
+ * `std::shared_ptr` semantics.
+ *
+ * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if
+ * shared semantics are not required.
+ */
+template <access_mode AccessMode, typename ByteT> class basic_shared_mmap {
+  using impl_type = basic_mmap<AccessMode, ByteT>;
+  std::shared_ptr<impl_type> pimpl_;
+
+public:
+  using value_type = typename impl_type::value_type;
+  using size_type = typename impl_type::size_type;
+  using reference = typename impl_type::reference;
+  using const_reference = typename impl_type::const_reference;
+  using pointer = typename impl_type::pointer;
+  using const_pointer = typename impl_type::const_pointer;
+  using difference_type = typename impl_type::difference_type;
+  using iterator = typename impl_type::iterator;
+  using const_iterator = typename impl_type::const_iterator;
+  using reverse_iterator = typename impl_type::reverse_iterator;
+  using const_reverse_iterator = typename impl_type::const_reverse_iterator;
+  using iterator_category = typename impl_type::iterator_category;
+  using handle_type = typename impl_type::handle_type;
+  using mmap_type = impl_type;
+
+  basic_shared_mmap() = default;
+  basic_shared_mmap(const basic_shared_mmap &) = default;
+  basic_shared_mmap &operator=(const basic_shared_mmap &) = default;
+  basic_shared_mmap(basic_shared_mmap &&) = default;
+  basic_shared_mmap &operator=(basic_shared_mmap &&) = default;
+
+  /** Takes ownership of an existing mmap object. */
+  basic_shared_mmap(mmap_type &&mmap) : pimpl_(std::make_shared<mmap_type>(std::move(mmap))) {}
+
+  /** Takes ownership of an existing mmap object. */
+  basic_shared_mmap &operator=(mmap_type &&mmap) {
+    pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
+    return *this;
+  }
+
+  /** Initializes this object with an already established shared mmap. */
+  basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {}
+
+  /** Initializes this object with an already established shared mmap. */
+  basic_shared_mmap &operator=(std::shared_ptr<mmap_type> mmap) {
+    pimpl_ = std::move(mmap);
+    return *this;
+  }
+
+#ifdef __cpp_exceptions
+  /**
+   * The same as invoking the `map` function, except any error that may occur
+   * while establishing the mapping is wrapped in a `std::system_error` and is
+   * thrown.
+   */
+  template <typename String>
+  basic_shared_mmap(const String &path, const size_type offset = 0,
+                    const size_type length = map_entire_file) {
+    std::error_code error;
+    map(path, offset, length, error);
+    if (error) {
+      throw std::system_error(error);
+    }
+  }
+
+  /**
+   * The same as invoking the `map` function, except any error that may occur
+   * while establishing the mapping is wrapped in a `std::system_error` and is
+   * thrown.
+   */
+  basic_shared_mmap(const handle_type handle, const size_type offset = 0,
+                    const size_type length = map_entire_file) {
+    std::error_code error;
+    map(handle, offset, length, error);
+    if (error) {
+      throw std::system_error(error);
+    }
+  }
+#endif // __cpp_exceptions
+
+  /**
+   * If this is a read-write mapping and the last reference to the mapping,
+   * the destructor invokes sync. Regardless of the access mode, unmap is
+   * invoked as a final step.
+   */
+  ~basic_shared_mmap() = default;
+
+  /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */
+  std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; }
+
+  /**
+   * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
+   * however, a mapped region of a file gets its own handle, which is returned by
+   * 'mapping_handle'.
+   */
+  handle_type file_handle() const noexcept {
+    return pimpl_ ? pimpl_->file_handle() : invalid_handle;
+  }
+
+  handle_type mapping_handle() const noexcept {
+    return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;
+  }
+
+  /** Returns whether a valid memory mapping has been created. */
+  bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); }
+
+  /**
+   * Returns true if no mapping was established, that is, conceptually the
+   * same as though the length that was mapped was 0. This function is
+   * provided so that this class has Container semantics.
+   */
+  bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); }
+
+  /**
+   * `size` and `length` both return the logical length, i.e. the number of bytes
+   * user requested to be mapped, while `mapped_length` returns the actual number of
+   * bytes that were mapped which is a multiple of the underlying operating system's
+   * page allocation granularity.
+   */
+  size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
+  size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
+  size_type mapped_length() const noexcept { return pimpl_ ? pimpl_->mapped_length() : 0; }
+
+  /**
+   * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
+   * exists.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  pointer data() noexcept {
+    return pimpl_->data();
+  }
+  const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; }
+
+  /**
+   * Returns an iterator to the first requested byte, if a valid memory mapping
+   * exists, otherwise this function call is undefined behaviour.
+   */
+  iterator begin() noexcept { return pimpl_->begin(); }
+  const_iterator begin() const noexcept { return pimpl_->begin(); }
+  const_iterator cbegin() const noexcept { return pimpl_->cbegin(); }
+
+  /**
+   * Returns an iterator one past the last requested byte, if a valid memory mapping
+   * exists, otherwise this function call is undefined behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  iterator end() noexcept {
+    return pimpl_->end();
+  }
+  const_iterator end() const noexcept { return pimpl_->end(); }
+  const_iterator cend() const noexcept { return pimpl_->cend(); }
+
+  /**
+   * Returns a reverse iterator to the last memory mapped byte, if a valid
+   * memory mapping exists, otherwise this function call is undefined
+   * behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  reverse_iterator rbegin() noexcept {
+    return pimpl_->rbegin();
+  }
+  const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); }
+  const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); }
+
+  /**
+   * Returns a reverse iterator past the first mapped byte, if a valid memory
+   * mapping exists, otherwise this function call is undefined behaviour.
+   */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  reverse_iterator rend() noexcept {
+    return pimpl_->rend();
+  }
+  const_reverse_iterator rend() const noexcept { return pimpl_->rend(); }
+  const_reverse_iterator crend() const noexcept { return pimpl_->crend(); }
+
+  /**
+   * Returns a reference to the `i`th byte from the first requested byte (as returned
+   * by `data`). If this is invoked when no valid memory mapping has been created
+   * prior to this call, undefined behaviour ensues.
+   */
+  reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; }
+  const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `path`, which must be a path to an existing file, is used to retrieve a file
+   * handle (which is closed when the object destructs or `unmap` is called), which is
+   * then used to memory map the requested region. Upon failure, `error` is set to
+   * indicate the reason and the object remains in an unmapped state.
+   *
+   * `offset` is the number of bytes, relative to the start of the file, where the
+   * mapping should begin. When specifying it, there is no need to worry about
+   * providing a value that is aligned with the operating system's page allocation
+   * granularity. This is adjusted by the implementation such that the first requested
+   * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
+   * `offset` from the start of the file.
+   *
+   * `length` is the number of bytes to map. It may be `map_entire_file`, in which
+   * case a mapping of the entire file is created.
+   */
+  template <typename String>
+  void map(const String &path, const size_type offset, const size_type length,
+           std::error_code &error) {
+    map_impl(path, offset, length, error);
+  }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `path`, which must be a path to an existing file, is used to retrieve a file
+   * handle (which is closed when the object destructs or `unmap` is called), which is
+   * then used to memory map the requested region. Upon failure, `error` is set to
+   * indicate the reason and the object remains in an unmapped state.
+   *
+   * The entire file is mapped.
+   */
+  template <typename String> void map(const String &path, std::error_code &error) {
+    map_impl(path, 0, map_entire_file, error);
+  }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `handle`, which must be a valid file handle, which is used to memory map the
+   * requested region. Upon failure, `error` is set to indicate the reason and the
+   * object remains in an unmapped state.
+   *
+   * `offset` is the number of bytes, relative to the start of the file, where the
+   * mapping should begin. When specifying it, there is no need to worry about
+   * providing a value that is aligned with the operating system's page allocation
+   * granularity. This is adjusted by the implementation such that the first requested
+   * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
+   * `offset` from the start of the file.
+   *
+   * `length` is the number of bytes to map. It may be `map_entire_file`, in which
+   * case a mapping of the entire file is created.
+   */
+  void map(const handle_type handle, const size_type offset, const size_type length,
+           std::error_code &error) {
+    map_impl(handle, offset, length, error);
+  }
+
+  /**
+   * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
+   * reason is reported via `error` and the object remains in a state as if this
+   * function hadn't been called.
+   *
+   * `handle`, which must be a valid file handle, which is used to memory map the
+   * requested region. Upon failure, `error` is set to indicate the reason and the
+   * object remains in an unmapped state.
+   *
+   * The entire file is mapped.
+   */
+  void map(const handle_type handle, std::error_code &error) {
+    map_impl(handle, 0, map_entire_file, error);
+  }
+
+  /**
+   * If a valid memory mapping has been created prior to this call, this call
+   * instructs the kernel to unmap the memory region and disassociate this object
+   * from the file.
+   *
+   * The file handle associated with the file that is mapped is only closed if the
+   * mapping was created using a file path. If, on the other hand, an existing
+   * file handle was used to create the mapping, the file handle is not closed.
+   */
+  void unmap() {
+    if (pimpl_)
+      pimpl_->unmap();
+  }
+
+  void swap(basic_shared_mmap &other) { pimpl_.swap(other.pimpl_); }
+
+  /** Flushes the memory mapped page to disk. Errors are reported via `error`. */
+  template <access_mode A = AccessMode,
+            typename = typename std::enable_if<A == access_mode::write>::type>
+  void sync(std::error_code &error) {
+    if (pimpl_)
+      pimpl_->sync(error);
+  }
+
+  /** All operators compare the underlying `basic_mmap`'s addresses. */
+
+  friend bool operator==(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return a.pimpl_ == b.pimpl_;
+  }
+
+  friend bool operator!=(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return !(a == b);
+  }
+
+  friend bool operator<(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return a.pimpl_ < b.pimpl_;
+  }
+
+  friend bool operator<=(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return a.pimpl_ <= b.pimpl_;
+  }
+
+  friend bool operator>(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return a.pimpl_ > b.pimpl_;
+  }
+
+  friend bool operator>=(const basic_shared_mmap &a, const basic_shared_mmap &b) {
+    return a.pimpl_ >= b.pimpl_;
+  }
+
+private:
+  template <typename MappingToken>
+  void map_impl(const MappingToken &token, const size_type offset, const size_type length,
+                std::error_code &error) {
+    if (!pimpl_) {
+      mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);
+      if (error) {
+        return;
+      }
+      pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
+    } else {
+      pimpl_->map(token, offset, length, error);
+    }
+  }
+};
+
+/**
+ * This is the basis for all read-only mmap objects and should be preferred over
+ * directly using basic_shared_mmap.
+ */
+template <typename ByteT>
+using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;
+
+/**
+ * This is the basis for all read-write mmap objects and should be preferred over
+ * directly using basic_shared_mmap.
+ */
+template <typename ByteT>
+using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;
+
+/**
+ * These aliases cover the most common use cases, both representing a raw byte stream
+ * (either with a char or an unsigned char/uint8_t).
+ */
+using shared_mmap_source = basic_shared_mmap_source<char>;
+using shared_ummap_source = basic_shared_mmap_source<unsigned char>;
+
+using shared_mmap_sink = basic_shared_mmap_sink<char>;
+using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;
+
+} // namespace mio
+
+#endif // MIO_SHARED_MMAP_HEADER
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/parameters.hpp b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/parameters.hpp
new file mode 100644
index 00000000..f3a858c1
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/parameters.hpp
@@ -0,0 +1,50 @@
+
+#pragma once
+#include <utility>
+
+namespace csv2 {
+
+namespace trim_policy {
+struct no_trimming {
+public:
+  static std::pair<size_t, size_t> trim(const char *buffer, size_t start, size_t end) {
+    (void)(buffer); // to silence unused parameter warning
+    return {start, end};
+  }
+};
+
+template <char... character_list> struct trim_characters {
+private:
+  constexpr static bool is_trim_char(char) { return false; }
+
+  template <class... Tail> constexpr static bool is_trim_char(char c, char head, Tail... tail) {
+    return c == head || is_trim_char(c, tail...);
+  }
+
+public:
+  static std::pair<size_t, size_t> trim(const char *buffer, size_t start, size_t end) {
+    size_t new_start = start, new_end = end;
+    while (new_start != new_end && is_trim_char(buffer[new_start], character_list...))
+      ++new_start;
+    while (new_start != new_end && is_trim_char(buffer[new_end - 1], character_list...))
+      --new_end;
+    return {new_start, new_end};
+  }
+};
+
+using trim_whitespace = trim_characters<' ', '\t'>;
+} // namespace trim_policy
+
+template <char character> struct delimiter {
+  constexpr static char value = character;
+};
+
+template <char character> struct quote_character {
+  constexpr static char value = character;
+};
+
+template <bool flag> struct first_row_is_header {
+  constexpr static bool value = flag;
+};
+
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/reader.hpp b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/reader.hpp
new file mode 100644
index 00000000..4fb93bbd
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/reader.hpp
@@ -0,0 +1,306 @@
+
+#pragma once
+#include <cstring>
+#if __has_include("sys/mman.h") || __has_include(<sys/mman.h>) || __has_include("windows.h") || __has_include(<windows.h>)
+#define __CSV2_HAS_MMAN_H__ 1
+#include "mio.hpp"
+#endif
+#include "parameters.hpp"
+#include <istream>
+#include <string>
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
+	#include <string_view>
+#endif
+
+namespace csv2 {
+
+template <class delimiter = delimiter<','>, class quote_character = quote_character<'"'>,
+          class first_row_is_header = first_row_is_header<true>,
+          class trim_policy = trim_policy::trim_whitespace>
+class Reader {
+  #if __CSV2_HAS_MMAN_H__
+  mio::mmap_source mmap_;          // mmap source
+  #endif
+  const char *buffer_{nullptr};    // pointer to memory-mapped data
+  size_t buffer_size_{0};          // mapped length of buffer
+  size_t header_start_{0};         // start index of header (cache)
+  size_t header_end_{0};           // end index of header (cache)
+
+public:
+  #if __CSV2_HAS_MMAN_H__
+  // Use this if you'd like to mmap the CSV file
+  template <typename StringType> bool mmap(StringType &&filename) {
+    mmap_ = mio::mmap_source(filename);
+    if (!mmap_.is_open() || !mmap_.is_mapped())
+      return false;
+    buffer_ = mmap_.data();
+    buffer_size_ = mmap_.mapped_length();
+    return true;
+  }
+  #endif
+
+  // Use this if you have the CSV contents
+  // in an std::string already
+  template <typename StringType> bool parse(StringType &&contents) {
+    buffer_ = std::forward<StringType>(contents).c_str();
+    buffer_size_ = contents.size();
+    return buffer_size_ > 0;
+  }
+
+
+  // Use this if you already have the CSV contents
+  // in a std::string_view 
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
+  bool parse_view(std::string_view sv) {
+    buffer_ = sv.data();
+    buffer_size_ = sv.size();
+    return buffer_size_ > 0;
+  }
+#endif
+
+
+  class RowIterator;
+  class Row;
+  class CellIterator;
+
+  class Cell {
+    const char *buffer_{nullptr}; // Pointer to memory-mapped buffer
+    size_t start_{0};             // Start index of cell content
+    size_t end_{0};               // End index of cell content
+    bool escaped_{false};         // Does the cell have escaped content?
+    friend class Row;
+    friend class CellIterator;
+
+  public:
+  
+	// returns a view on the cell's contents if C++17 available
+	#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
+      std::string_view read_view() const {
+      const auto new_start_end = trim_policy::trim(buffer_, start_, end_);
+      return std::string_view(buffer_ + new_start_end.first, new_start_end.second- new_start_end.first);
+      }
+	#endif
+    // Returns the raw_value of the cell without handling escaped
+    // content, e.g., cell containing """foo""" will be returned
+    // as is
+    template <typename Container> void read_raw_value(Container &result) const {
+      if (start_ >= end_)
+        return;
+      result.reserve(end_ - start_);
+      for (size_t i = start_; i < end_; ++i)
+        result.push_back(buffer_[i]);
+    }
+
+    // If cell is escaped, convert and return correct cell contents,
+    // e.g., """foo""" => ""foo""
+    template <typename Container> void read_value(Container &result) const {
+      if (start_ >= end_)
+        return;
+      result.reserve(end_ - start_);
+      const auto new_start_end = trim_policy::trim(buffer_, start_, end_);
+      for (size_t i = new_start_end.first; i < new_start_end.second; ++i)
+        result.push_back(buffer_[i]);
+      for (size_t i = 1; i < result.size(); ++i) {
+        if (result[i] == quote_character::value && result[i - 1] == quote_character::value) {
+          result.erase(i - 1, 1);
+        }
+      }
+    }
+  };
+
+  class Row {
+    const char *buffer_{nullptr}; // Pointer to memory-mapped buffer
+    size_t start_{0};             // Start index of row content
+    size_t end_{0};               // End index of row content
+    friend class RowIterator;
+    friend class Reader;
+
+  public:
+    // address of row
+    const char *address() const { return buffer_; }
+	// returns the char length of the row
+	size_t length() const { return end_ - start_; }
+
+    // Returns the raw_value of the row
+    template <typename Container> void read_raw_value(Container &result) const {
+      if (start_ >= end_)
+        return;
+      result.reserve(end_ - start_);
+      for (size_t i = start_; i < end_; ++i)
+        result.push_back(buffer_[i]);
+    }
+
+    class CellIterator {
+      friend class Row;
+      const char *buffer_;
+      size_t buffer_size_;
+      size_t start_;
+      size_t current_;
+      size_t end_;
+
+    public:
+      CellIterator(const char *buffer, size_t buffer_size, size_t start, size_t end)
+          : buffer_(buffer), buffer_size_(buffer_size), start_(start), current_(start_), end_(end) {
+      }
+
+      CellIterator &operator++() {
+        current_ += 1;
+        return *this;
+      }
+
+      Cell operator*() {
+        bool escaped{false};
+        class Cell cell;
+        cell.buffer_ = buffer_;
+        cell.start_ = current_;
+        cell.end_ = end_;
+
+        size_t last_quote_location = 0;
+        bool quote_opened = false;
+        for (auto i = current_; i < end_; i++) {
+          current_ = i;
+          if (buffer_[i] == delimiter::value && !quote_opened) {
+            // actual delimiter
+            // end of cell
+            cell.end_ = current_;
+            cell.escaped_ = escaped;
+            return cell;
+          } else {
+            if (buffer_[i] == quote_character::value) {
+              if (!quote_opened) {
+                // first quote for this cell
+                quote_opened = true;
+                last_quote_location = i;
+              } else {
+                escaped = (last_quote_location == i - 1);
+                last_quote_location += (i - last_quote_location) * size_t(!escaped);
+                quote_opened = escaped || (buffer_[i + 1] != delimiter::value);
+              }
+            }
+          }
+        }
+        cell.end_ = current_ + 1;
+        return cell;
+      }
+
+      bool operator!=(const CellIterator &rhs) { return current_ != rhs.current_; }
+    };
+
+    CellIterator begin() const { return CellIterator(buffer_, end_ - start_, start_, end_); }
+    CellIterator end() const { return CellIterator(buffer_, end_ - start_, end_, end_); }
+  };
+
+  class RowIterator {
+    friend class Reader;
+    const char *buffer_;
+    size_t buffer_size_;
+    size_t start_;
+    size_t end_;
+
+  public:
+    RowIterator(const char *buffer, size_t buffer_size, size_t start)
+        : buffer_(buffer), buffer_size_(buffer_size), start_(start), end_(start_) {}
+
+    RowIterator &operator++() {
+      start_ = end_ + 1;
+      end_ = start_;
+      return *this;
+    }
+
+    Row operator*() {
+      Row result;
+      result.buffer_ = buffer_;
+      result.start_ = start_;
+      result.end_ = end_;
+
+      if (const char *ptr =
+              static_cast<const char *>(memchr(&buffer_[start_], '\n', (buffer_size_ - start_)))) {
+        end_ = start_ + (ptr - &buffer_[start_]);
+        result.end_ = end_;
+        start_ = end_ + 1;
+      } else {
+        // last row
+        end_ = buffer_size_;
+        result.end_ = end_;
+      }
+      return result;
+    }
+
+    bool operator!=(const RowIterator &rhs) { return start_ != rhs.start_; }
+  };
+
+  RowIterator begin() const {
+    if (buffer_size_ == 0)
+      return end();
+    if (first_row_is_header::value) {
+      const auto header_indices = header_indices_();
+      return RowIterator(buffer_, buffer_size_, header_indices.second  > 0 ? header_indices.second + 1 : 0);
+    } else {
+      return RowIterator(buffer_, buffer_size_, 0);
+    }
+  }
+
+  RowIterator end() const { return RowIterator(buffer_, buffer_size_, buffer_size_ + 1); }
+
+private:
+  std::pair<size_t, size_t> header_indices_() const {
+    size_t start = 0, end = 0;
+
+    if (const char *ptr =
+            static_cast<const char *>(memchr(&buffer_[start], '\n', (buffer_size_ - start)))) {
+      end = start + (ptr - &buffer_[start]);
+    }
+    return {start, end};
+  }
+
+public:
+
+  Row header() const {
+    size_t start = 0, end = 0;
+    Row result;
+    result.buffer_ = buffer_;
+    result.start_ = start;
+    result.end_ = end;
+
+    if (const char *ptr =
+            static_cast<const char *>(memchr(&buffer_[start], '\n', (buffer_size_ - start)))) {
+      end = start + (ptr - &buffer_[start]);
+      result.end_ = end;
+    }
+    return result;
+  }
+
+  /**
+   * @returns The number of rows (excluding the header)
+  */
+  size_t rows(bool ignore_empty_lines = false) const {
+    size_t result{0};
+    if (!buffer_ || buffer_size_ == 0)
+      return result;
+    
+    // Count the first row if not header
+    if (not first_row_is_header::value
+        and (not ignore_empty_lines
+        or *(static_cast<const char*>(buffer_)) != '\r'))
+      ++result;
+
+    for (const char *p = buffer_
+        ; (p = static_cast<const char *>(memchr(p, '\n', (buffer_ + buffer_size_) - p)))
+        ; ++p) {
+      if (ignore_empty_lines
+          and (p >= buffer_ + buffer_size_ - 1
+          or *(p + 1) == '\r'))
+        continue;
+      ++result;
+    }
+    return result;
+  }
+
+  size_t cols() const {
+    size_t result{0};
+    for (const auto cell : header())
+      result += 1;
+    return result;
+  }
+};
+} // namespace csv2
diff --git a/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/writer.hpp b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/writer.hpp
new file mode 100644
index 00000000..1d813ab8
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsSvc/src/csv2/writer.hpp
@@ -0,0 +1,38 @@
+
+#pragma once
+#include <cstring>
+#include "parameters.hpp"
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <utility>
+
+namespace csv2 {
+
+template <class delimiter = delimiter<','>, typename Stream = std::ofstream> class Writer {
+  Stream &stream_; // output stream for the writer
+public:
+  Writer(Stream &stream) : stream_(stream) {}
+
+  ~Writer() {
+    stream_.close();
+  }
+
+  template <typename Container> void write_row(Container &&row) {
+    const auto &strings = std::forward<Container>(row);
+    const auto delimiter_string = std::string(1, delimiter::value);
+    std::copy(strings.begin(), strings.end() - 1,
+              std::ostream_iterator<std::string>(stream_, delimiter_string.c_str()));
+    stream_ << strings.back() << "\n";
+  }
+
+  template <typename Container> void write_rows(Container &&rows) {
+    const auto &container_of_rows = std::forward<Container>(rows);
+    for (const auto &row : container_of_rows) {
+      write_row(row);
+    }
+  }
+};
+
+} // namespace csv2
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt b/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
new file mode 100644
index 00000000..a0aad036
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
@@ -0,0 +1,46 @@
+find_package(Acts COMPONENTS
+    Core PluginFpeMonitoring PluginGeant4 PluginJson
+    PluginTGeo PluginDD4hep PluginEDM4hep Fatras) 
+
+if(NOT Acts_FOUND)
+    message("Acts package not found. RecActsTracking module requires Acts.")
+    return()
+endif()
+
+gaudi_add_module(RecActsTracking
+                 SOURCES
+                        src/RecActsReadInput.cpp
+                        src/RecActsTruthInput.cpp
+                        src/RecActsSeeding.cpp
+                        src/RecActsTrackParamsEstimation.cpp
+                        src/RecActsTrackFinding.cpp
+                        src/RecActsTrackFitting.cpp
+                        # src/RecActsTrackReFitting.cpp
+                        # src/RecActsTruthTracking.cpp
+                 LINK DetInterface
+                      k4FWCore::k4FWCore
+                      Gaudi::GaudiAlgLib Gaudi::GaudiKernel
+                      DataHelperLib
+                      ${LCIO_LIBRARIES} 
+                      ${DD4hep_COMPONENT_LIBRARIES}
+                      EDM4HEP::edm4hep EDM4HEP::edm4hepDict
+                      ${podio_LIBRARIES} podio::podioRootIO
+                      GearSvc ${GEAR_LIBRARIES}
+                      RecActsSvc
+                      ActsCore ActsPluginFpeMonitoring ActsPluginGeant4
+                      ActsPluginJson ActsPluginTGeo  ActsPluginDD4hep
+                      ActsPluginEDM4hep ActsFatras
+)
+
+target_include_directories(RecActsTracking PUBLIC $ENV{ACTS}/include)
+
+target_include_directories(RecActsTracking PUBLIC
+  ${LCIO_INCLUDE_DIRS}
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>/include
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
+
+install(TARGETS RecActsTracking
+  EXPORT CEPCSWTargets
+  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib
+  COMPONENT dev)
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/options/RecActsTracking.py b/Reconstruction/RecActsTracking/RecActsTracking/options/RecActsTracking.py
new file mode 100644
index 00000000..0a99619e
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/options/RecActsTracking.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+import os
+from Gaudi.Configuration import *
+
+from Configurables import k4DataSvc
+dsvc = k4DataSvc("EventDataSvc", input="rec_v01.root")
+
+from Configurables import PodioInput
+podioinput = PodioInput("PodioReader", collections=[
+#    "EventHeader",
+    "MCParticle",
+    "VXDCollection",
+    "VXDTrackerHits",
+    "ITKBarrelCollection",
+    "ITKBarrelTrackerHits",
+    "ITKEndcapCollection",
+    "ITKEndcapTrackerHits",
+    "TPCCollection",
+    "TPCTrackerHits",
+    "OTKBarrelCollection",
+    "OTKBarrelTrackerHits",
+    "OTKEndcapCollection",
+    "OTKEndcapTrackerHits",
+    ])
+
+##############################################################################
+# Geometry Svc
+##############################################################################
+
+geometry_option = "TDR_o1_v01/TDR_o1_v01.xml"
+
+if not os.getenv("DETCRDROOT"):
+    print("Can't find the geometry. Please setup envvar DETCRDROOT." )
+    sys.exit(-1)
+
+geometry_path = os.path.join(os.getenv("DETCRDROOT"), "compact", geometry_option)
+if not os.path.exists(geometry_path):
+    print("Can't find the compact geometry file: %s"%geometry_path)
+    sys.exit(-1)
+
+from Configurables import DetGeomSvc
+geosvc = DetGeomSvc("GeomSvc")
+geosvc.compact = geometry_path
+
+from Configurables import MarlinEvtSeeder
+evtseeder = MarlinEvtSeeder("EventSeeder")
+
+from Configurables import GearSvc
+gearsvc = GearSvc("GearSvc")
+
+from Configurables import TrackSystemSvc
+tracksystemsvc = TrackSystemSvc("TrackSystemSvc")
+
+##############################################################################
+# RecActsSvc
+##############################################################################
+
+cepcswdatatop ="/cvmfs/cepcsw.ihep.ac.cn/prototype/releases/data/latest"
+
+from Configurables import RecActsSvc
+recactssvc = RecActsSvc("RecActsSvc")
+recactssvc.TGeoFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.5.0/CEPC-tgeo.root")
+recactssvc.TGeoConfigFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.5.0/CEPC-tgeo-config.json")
+recactssvc.MaterialMapFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.5.0/CEPC-material-maps")
+
+##############################################################################
+# RecActsTracking
+##############################################################################
+
+from Configurables import RecActsReadInput
+from Configurables import RecActsSeeding
+from Configurables import RecActsTrackParamsEstimation
+from Configurables import RecActsTrackFinding
+from Configurables import RecActsTrackFitting
+
+##############################################################################
+# Silicon Track
+##############################################################################
+# include read VTX+SIT, seeding, param estimation, track finding
+# only use VTX + SIT
+ReadSi = RecActsReadInput("ActsReadSi")
+ReadSi.useVTX = True
+ReadSi.useITKBarrel = True
+ReadSi.useITKEndcap = True
+ReadSi.useTPC = False
+ReadSi.useOTKBarrel = False
+ReadSi.useOTKEndcap = False
+
+Seeding = RecActsSeeding("ActsSeeding")
+Seeding.SeedDeltaRMin = 0.05
+
+TrackParamsEstimation = RecActsTrackParamsEstimation("ActsTrackParamsEstimation")
+TrackParamsEstimation.AssumeParticle = "muon"
+# TrackParamsEstimation.AssumeParticle = "electron"
+TrackParamsEstimation.initialVarInflation = [1, 1, 10, 10, 10, 10]
+
+SiTrackFinding = RecActsTrackFinding("ActsSiTrackFinding")
+SiTrackFinding.ACTSFindOutCol = "ACTSSiTracks"
+SiTrackFinding.numMeasurementsCutOff = 2
+
+##############################################################################
+# Full Track
+##############################################################################
+# include read TPC+OTK, track finding, track fitting
+# use VTX + SIT + TPC + OTK
+
+ReadOuter = RecActsReadInput("ACTSReadOuter")
+ReadOuter.useVTX = False
+ReadOuter.useITKBarrel = False
+ReadOuter.useITKEndcap = False
+ReadOuter.useTPC = True
+ReadOuter.useOTKBarrel = True
+ReadOuter.useOTKEndcap = True
+ReadOuter.CleanBeforeRead = False
+
+FullTrackFinding = RecActsTrackFinding("ActsFullTrackFinding")
+FullTrackFinding.ACTSFindOutCol = "ACTSFullTracks"
+FullTrackFinding.numMeasurementsCutOff = 2
+
+TrackFitting = RecActsTrackFitting("ActsTrackFitting")
+TrackFitting.ACTSFitOutCol = "ACTSCompleteTracks"
+TrackFitting.fitType = "kalman"
+# TrackFitting.fitType = "gsf"
+# TrackFitting.maxComponents = 12
+
+##############################################################################
+# output
+##############################################################################
+from Configurables import PodioOutput
+out = PodioOutput("out")
+out.filename = "rec_acts.root"
+out.outputCommands = ["keep *"]
+
+# ApplicationMgr
+from Configurables import ApplicationMgr
+mgr = ApplicationMgr(
+    # TopAlg = [podioinput, ActsReadInput, ActsSeeding, out],
+    TopAlg = [podioinput, ReadSi, Seeding, TrackParamsEstimation, SiTrackFinding, ReadOuter, FullTrackFinding, TrackFitting, out],
+    EvtSel = 'NONE',
+    EvtMax = -1,
+    ExtSvc = [dsvc, geosvc, evtseeder, recactssvc],
+    # OutputLevel=DEBUG
+)
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.cpp
new file mode 100644
index 00000000..c02b76aa
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.cpp
@@ -0,0 +1,447 @@
+// dependence
+#include "RecActsReadInput.h"
+
+DECLARE_COMPONENT(RecActsReadInput)
+
+RecActsReadInput::RecActsReadInput(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsReadInput::initialize()
+{
+    // --------------------------------
+    // ---- initialize properties -----
+    // --------------------------------
+
+    m_geosvc = service<IGeomSvc>("GeomSvc");
+    if (!m_geosvc) {
+        error() << "Failed to get GeomSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    
+    vtx_decoder = m_geosvc->getDecoder("VXDCollection");
+    if(!vtx_decoder){
+        info() << "Failed to create vtx_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKBarrel_decoder = m_geosvc->getDecoder("ITKBarrelCollection");
+    if(!ITKBarrel_decoder){
+        info() << "Failed to create ITKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKEndcap_decoder = m_geosvc->getDecoder("ITKEndcapCollection");
+    if(!ITKEndcap_decoder){
+        info() << "Failed to create ITKEndcap_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    tpc_decoder = m_geosvc->getDecoder("TPCCollection");
+    if(!tpc_decoder){
+        info() << "Failed to create TPC_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    OTKBarrel_decoder = m_geosvc->getDecoder("OTKBarrelCollection");
+    if(!OTKBarrel_decoder){
+        info() << "Failed to create OTKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    OTKEndcap_decoder = m_geosvc->getDecoder("OTKEndcapCollection");
+    if(!OTKEndcap_decoder){
+        info() << "Failed to create OTKEndcap_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    
+    // --------------------------------
+    // ---- initialize recActsSvc ----
+    // --------------------------------
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsReadInput::execute()
+{
+    _nEvt++;
+    isGoodEvent = true;
+
+    if (CleanBeforeRead.value()) {
+        recActsSvc->Clean();
+        VTXhits_num = 0;
+        ITKhits_num = 0;
+        
+        if (collect_bad_events.value()) {
+            const edm4hep::MCParticleCollection* mcCols = nullptr;
+            try { mcCols = _inMCColHdl.get(); }
+            catch ( GaudiException &e ) {
+                debug() << "Collection " << _inMCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+                return StatusCode::FAILURE;
+            }
+            
+            for(const auto& mc : *mcCols){
+                if (mc.getGeneratorStatus() != 1){
+                    info() << "MCParticle " << mc.getPDG() << " is not primary at event " << _nEvt << endmsg;
+                    isGoodEvent = false;
+                }
+            }
+        }
+    }
+
+    chronoStatSvc->chronoStart("Read Input");
+    
+    if (useVTX.value()) {
+        if (!ReadInputVTX()) {
+            info() << "VTX input failed at event " << _nEvt << endmsg;
+        }
+    }
+    
+    if (useITKBarrel.value()) {
+        if (!ReadInputITKBarrel()) {
+            info() << "ITKBarrel input failed at event " << _nEvt << endmsg;
+        }
+    }
+    
+    if (useITKEndcap.value()) {
+        if (!ReadInputITKEndcap()) {
+            info() << "ITKEndcap input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (collect_bad_events.value() && CleanBeforeRead.value()){
+        if (VTXhits_num < 1 || ITKhits_num < 2) {
+            info() << "Event " << _nEvt << " has no VTX hit or less than 2 ITK hits" << endmsg;
+            isGoodEvent = false;
+        }
+    }
+
+    if (useTPC.value()) {
+        if (!ReadInputTPC()) {
+            info() << "TPC input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (useOTKBarrel.value()) {
+        if (!ReadInputOTKBarrel()) {
+            info() << "OTKBarrel input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (useOTKEndcap.value()) {
+        if (!ReadInputOTKEndcap()) {
+            info() << "OTKEndcap input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (!isGoodEvent) {
+        debug() << "Event " << _nEvt << " is not good" << endmsg;
+        m_badEvents++;
+    }
+
+    chronoStatSvc->chronoStop("Read Input");
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsReadInput::finalize()
+{
+    info() << "finalize ReadInput" << endmsg;
+    info() << "Total number of events processed: " << _nEvt + 1 << endmsg;
+    info() << "Number of bad events: " << m_badEvents << endmsg;
+    return StatusCode::SUCCESS;
+}
+
+bool RecActsReadInput::ReadInputVTX()
+{
+    const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
+
+    try { hitVTXCol = _inVTXTrackHdl.get(); }
+    catch (GaudiException& e)
+    {
+        fatal() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitVTXCol)
+    {
+        int nelem = hitVTXCol->size();
+        debug() << "VTX hits number: " << nelem << endmsg;
+        VTXhits_num += nelem;
+
+        for(int ielem = 0; ielem < nelem; ++ielem){
+            auto hit = hitVTXCol->at(ielem);
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
+            uint64_t m_module  = vtx_decoder->get(cellid, "module");
+            uint64_t m_sensor  = vtx_decoder->get(cellid, "sensor");
+
+            if(m_layer <= 3){
+                uint64_t acts_volume = VXD_volume_ids[m_layer];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = 1;
+                bool buildSpacePoint = true;
+                int moduleType = 1;
+                double onSurfaceTolerance = 1e-4;
+
+                if (!recActsSvc->ReadInput(hit,
+                        acts_volume, acts_layer, acts_sensitive,
+                        buildSpacePoint, moduleType, onSurfaceTolerance))
+                    { return false; }
+            } else {
+                uint64_t acts_volume = VXD_volume_ids[4];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;
+                bool buildSpacePoint = true;
+                int moduleType = 0;
+                double onSurfaceTolerance = 1e-4;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive,
+                    buildSpacePoint, moduleType, onSurfaceTolerance))
+                    { return false; }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsReadInput::ReadInputITKBarrel()
+{
+    const edm4hep::TrackerHitCollection* hitITKBarrelCol = nullptr;
+
+    try {
+        hitITKBarrelCol = _inITKBarrelTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inITKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitITKBarrelCol)
+    {
+        int nelem = hitITKBarrelCol->size();
+        debug() << "ITKBarrel hits number: " << nelem << endmsg;
+        ITKhits_num += nelem;
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitITKBarrelCol->at(ielem);
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
+            uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
+
+            uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = m_stave + 1;
+            bool buildSpacePoint = true;
+            int moduleType = 0;
+            double onSurfaceTolerance = 1e-4;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsReadInput::ReadInputITKEndcap()
+{
+    const edm4hep::TrackerHitCollection* hitITKEndcapCol = nullptr;
+
+    try {
+        hitITKEndcapCol = _inITKEndcapTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inITKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if (hitITKEndcapCol)
+    {
+        int nelem = hitITKEndcapCol->size();
+        debug() << "ITKEndcap hits number: " << nelem << endmsg;
+        ITKhits_num += nelem;
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitITKEndcapCol->at(ielem);
+
+            auto cellid = hit.getCellID();
+            int m_side = ITKEndcap_decoder->get(cellid, "side");
+            uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
+            uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
+            uint64_t m_ring = ITKEndcap_decoder->get(cellid, "ring");
+
+            uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = 0;
+
+            if (m_ring > 0){
+                acts_sensitive += ITKEndcap_modules_per_ring[m_layer][0] * 2;
+            }
+
+            if (m_ring > 1) {
+                acts_sensitive += ITKEndcap_modules_per_ring[m_layer][1] * 2;
+            }
+
+            if (m_module % 2 == 0) {
+                acts_sensitive += (m_module / 2) + ITKEndcap_modules_per_ring[m_layer][m_ring] + 1;
+            } else {
+                acts_sensitive += (m_module / 2) + 1;
+            }
+
+            bool buildSpacePoint = true;
+            int moduleType = 0;
+            double onSurfaceTolerance = 1e-4;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+    
+    return true;
+}
+
+
+bool RecActsReadInput::ReadInputTPC()
+{
+    const edm4hep::TrackerHitCollection* hitTPCCol = nullptr;
+
+    try {
+        hitTPCCol = _inTPCTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inTPCTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitTPCCol)
+    {
+        int nelem = hitTPCCol->size();
+        debug() << "TPC hits number: " << nelem << endmsg;
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitTPCCol->at(ielem);
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
+
+            uint64_t acts_volume = TPC_volume_id;
+            uint64_t acts_layer = (m_layer + 1) * 2;
+            uint64_t acts_sensitive = 1;
+            bool buildSpacePoint = false;
+            int moduleType = 2;
+            double onSurfaceTolerance = 1e-4;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsReadInput::ReadInputOTKBarrel()
+{
+    const edm4hep::TrackerHitCollection* hitOTKBarrelCol = nullptr;
+
+    try {   
+        hitOTKBarrelCol = _inOTKBarrelTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inOTKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitOTKBarrelCol)
+    {
+        int nelem = hitOTKBarrelCol->size();
+        debug() << "OTKBarrel hits number: " << nelem << endmsg;
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitOTKBarrelCol->at(ielem);
+
+            // system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6
+            auto cellid = hit.getCellID();
+            uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
+
+            uint64_t acts_volume = OTKBarrel_volume_id;
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = m_module + 1;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive))
+                { return false; }
+        }
+    }
+    return true;
+}
+
+bool RecActsReadInput::ReadInputOTKEndcap()
+{
+    const edm4hep::TrackerHitCollection* hitOTKEndcapCol = nullptr;
+
+    try {   
+        hitOTKEndcapCol = _inOTKEndcapTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inOTKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitOTKEndcapCol)
+    {
+        int nelem = hitOTKEndcapCol->size();
+        debug() << "OTKEndcap hits number: " << nelem << endmsg;
+        
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitOTKEndcapCol->at(ielem);
+
+            auto cellid = hit.getCellID();
+            int m_side = OTKEndcap_decoder->get(cellid, "side");
+            double x = hit.getPosition()[0];
+            double y = hit.getPosition()[1];
+
+            // Calculate the angle of the hit in the x-y plane
+            // The angle is in the range [-pi/16, 31pi/16)
+            // Returned angle index is in the range [1, 16]
+            double theta = atan2(y, x);
+            if (theta < 0) { theta += 2 * M_PI; }
+            theta += M_PI/16;
+            if (theta >= 2 * M_PI) { theta -= 2 * M_PI; }
+            int index = floor(theta / (M_PI/8));
+
+            uint64_t acts_volume = (m_side == 1) ? OTKEndcap_positive_volume_id : OTKEndcap_negative_volume_id;
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = index % 16 + 1;
+            bool buildSpacePoint = false;
+            int moduleType = 0;
+            double onSurfaceTolerance = 1e-2;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+    return true;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.h
new file mode 100644
index 00000000..4482c0aa
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsReadInput.h
@@ -0,0 +1,103 @@
+#ifndef RecActsReadInput_H
+#define RecActsReadInput_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// DD4hep
+#include "DD4hep/Detector.h"
+#include "DDRec/ISurface.h"
+#include "DDRec/SurfaceManager.h"
+
+#include "UTIL/ILDConf.h"
+#include "DataHelper/Navigation.h"
+
+// edm4hep
+#include "edm4hep/MCParticle.h"
+#include "edm4hep/MCParticleCollection.h"
+#include "edm4hep/TrackerHitCollection.h"
+#include "edm4hep/SimTrackerHitCollection.h"
+#include "edm4hep/EventHeaderCollection.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+
+
+class RecActsReadInput : public GaudiAlgorithm
+{
+    public:
+        RecActsReadInput(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // Input collections
+        DataHandle<edm4hep::MCParticleCollection> _inMCColHdl{"MCParticle", Gaudi::DataHandle::Reader, this};
+
+        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKBarrelTrackHdl{"ITKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKEndcapTrackHdl{"ITKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inOTKBarrelTrackHdl{"OTKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inOTKEndcapTrackHdl{"OTKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inTPCTrackHdl{"TPCTrackerHits", Gaudi::DataHandle::Reader, this};        
+
+        // properties
+        Gaudi::Property<bool> useVTX{this, "useVTX", true};
+        Gaudi::Property<bool> useITKBarrel{this, "useITKBarrel", true};
+        Gaudi::Property<bool> useITKEndcap{this, "useITKEndcap", true};
+        Gaudi::Property<bool> useTPC{this, "useTPC", true};
+        Gaudi::Property<bool> useOTKBarrel{this, "useOTKBarrel", true};
+        Gaudi::Property<bool> useOTKEndcap{this, "useOTKEndcap", true};
+
+        Gaudi::Property<bool> CleanBeforeRead{this, "CleanBeforeRead", true};
+        Gaudi::Property<bool> collect_bad_events{this, "collect_bad_events", true};
+
+        // read input
+        bool ReadInputVTX();
+        bool ReadInputITKBarrel();
+        bool ReadInputITKEndcap();
+        bool ReadInputTPC();
+        bool ReadInputOTKBarrel();
+        bool ReadInputOTKEndcap();
+
+        // services
+        SmartIF<IGeomSvc> m_geosvc;
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *OTKEndcap_decoder;
+
+        // constants
+        int _nEvt;
+        bool isGoodEvent = true;
+        uint64_t TPC_volume_id = 45;
+        uint64_t OTKBarrel_volume_id = 47;
+        uint64_t OTKEndcap_positive_volume_id = 48;
+        uint64_t OTKEndcap_negative_volume_id = 2;
+        std::vector<uint64_t> VXD_volume_ids{28, 29, 30, 31, 32};
+        std::vector<uint64_t> ITKBarrel_volume_ids{35, 38, 41};
+        std::vector<uint64_t> ITKEndcap_positive_volume_ids{36, 39, 42, 43};
+        std::vector<uint64_t> ITKEndcap_negative_volume_ids{34, 17, 12, 10};
+        std::vector<std::vector<uint64_t>> ITKEndcap_modules_per_ring{{13, 20, 0}, {16, 24, 28}, {24, 36, 44}, {24, 36, 44}};
+        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
+
+        uint64_t VTXhits_num = 0;
+        uint64_t ITKhits_num = 0;
+        mutable std::atomic<std::size_t> m_badEvents{0};
+};
+
+#endif // RecActsReadInput_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.cpp
new file mode 100644
index 00000000..75df9cfe
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.cpp
@@ -0,0 +1,169 @@
+// dependence
+#include "RecActsSeeding.h"
+
+DECLARE_COMPONENT(RecActsSeeding)
+
+RecActsSeeding::RecActsSeeding(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsSeeding::initialize()
+{
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    // configure seed finder
+    m_cfg.seedFinderOptions.bFieldInZ = m_field.value();
+    m_cfg.seedFinderConfig.deltaRMin = SeedDeltaRMin.value();
+    m_cfg.seedFinderConfig.deltaRMax = SeedDeltaRMax.value();
+    m_cfg.seedFinderConfig.rMax = SeedRMax.value();
+    m_cfg.seedFinderConfig.rMin = SeedRMin.value();
+    m_cfg.seedFinderConfig.impactMax = SeedImpactMax.value();
+    m_cfg.seedFinderConfig.useVariableMiddleSPRange = false;
+    m_cfg.seedFinderConfig.rMinMiddle = SeedRMinMiddle.value();
+    m_cfg.seedFinderConfig.rMaxMiddle = SeedRMaxMiddle.value();
+
+    // initialize the seeding tools
+    m_cfg.seedFilterConfig = m_cfg.seedFilterConfig.toInternalUnits();
+    m_cfg.seedFinderConfig.seedFilter =
+        std::make_unique<Acts::SeedFilter<ActsHelper::SimSpacePoint>>(m_cfg.seedFilterConfig);
+    m_cfg.seedFinderConfig =
+        m_cfg.seedFinderConfig.toInternalUnits().calculateDerivedQuantities();
+    m_cfg.seedFinderOptions =
+        m_cfg.seedFinderOptions.toInternalUnits().calculateDerivedQuantities(m_cfg.seedFinderConfig);
+    m_cfg.gridConfig = m_cfg.gridConfig.toInternalUnits();
+    m_cfg.gridOptions = m_cfg.gridOptions.toInternalUnits();
+
+    if (std::isnan(m_cfg.seedFinderConfig.deltaRMaxTopSP)) {
+        m_cfg.seedFinderConfig.deltaRMaxTopSP = m_cfg.seedFinderConfig.deltaRMax;}
+    if (std::isnan(m_cfg.seedFinderConfig.deltaRMinTopSP)) {
+        m_cfg.seedFinderConfig.deltaRMinTopSP = m_cfg.seedFinderConfig.deltaRMin;}
+    if (std::isnan(m_cfg.seedFinderConfig.deltaRMaxBottomSP)) {
+        m_cfg.seedFinderConfig.deltaRMaxBottomSP = m_cfg.seedFinderConfig.deltaRMax;}
+    if (std::isnan(m_cfg.seedFinderConfig.deltaRMinBottomSP)) {
+        m_cfg.seedFinderConfig.deltaRMinBottomSP = m_cfg.seedFinderConfig.deltaRMin;}
+        
+    m_bottomBinFinder = std::make_unique<const Acts::GridBinFinder<2ul>>(
+        m_cfg.numPhiNeighbors, m_cfg.zBinNeighborsBottom);
+    m_topBinFinder = std::make_unique<const Acts::GridBinFinder<2ul>>(
+        m_cfg.numPhiNeighbors, m_cfg.zBinNeighborsTop);
+
+    m_cfg.seedFinderConfig.seedFilter =
+        std::make_unique<Acts::SeedFilter<ActsHelper::SimSpacePoint>>(m_cfg.seedFilterConfig);
+    m_seedFinder =
+        Acts::SeedFinder<ActsHelper::SimSpacePoint, Acts::CylindricalSpacePointGrid<ActsHelper::SimSpacePoint>>(m_cfg.seedFinderConfig);
+    
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSeeding::execute()
+{
+    _nEvt++;
+    
+    auto SpacePointPtrs = recActsSvc->SpacePointPtrs();
+
+    chronoStatSvc->chronoStart("Seeding");
+
+    auto extractGlobalQuantities = [=](const ActsHelper::SimSpacePoint& sp, float, float, float)
+    {
+        Acts::Vector3 position{sp.x(), sp.y(), sp.z()};
+        Acts::Vector2 covariance{sp.varianceR(), sp.varianceZ()};
+        return std::make_tuple(position, covariance, sp.t());
+    };
+
+    Acts::Extent rRangeSPExtent;
+
+    Acts::CylindricalSpacePointGrid<ActsHelper::SimSpacePoint> grid =
+        Acts::CylindricalSpacePointGridCreator::createGrid<ActsHelper::SimSpacePoint>(m_cfg.gridConfig, m_cfg.gridOptions);
+    Acts::CylindricalSpacePointGridCreator::fillGrid(
+        m_cfg.seedFinderConfig, m_cfg.seedFinderOptions, grid,
+        SpacePointPtrs->begin(), SpacePointPtrs->end(), extractGlobalQuantities, rRangeSPExtent);
+
+    std::array<std::vector<std::size_t>, 2ul> navigation;
+    navigation[1ul] = m_cfg.seedFinderConfig.zBinsCustomLooping;
+
+    auto spacePointsGrouping = Acts::CylindricalBinnedGroup<ActsHelper::SimSpacePoint>(
+        std::move(grid), *m_bottomBinFinder, *m_topBinFinder, std::move(navigation));
+
+    // safely clamp double to float
+    float up = Acts::clampValue<float>(std::floor(rRangeSPExtent.max(Acts::binR) / 2) * 2);
+
+    /// variable middle SP radial region of interest
+    const Acts::Range1D<float> rMiddleSPRange(
+        std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 +
+        m_cfg.seedFinderConfig.deltaRMiddleMinSPRange,
+        up - m_cfg.seedFinderConfig.deltaRMiddleMaxSPRange);
+
+    // run the seeding
+    static thread_local ActsHelper::SimSeedContainer seeds;
+    seeds.clear();
+    static thread_local decltype(m_seedFinder)::SeedingState state;
+    state.spacePointData.resize(
+        SpacePointPtrs->size(),
+        m_cfg.seedFinderConfig.useDetailedDoubleMeasurementInfo);
+
+    // use double stripe measurement
+    if (m_cfg.seedFinderConfig.useDetailedDoubleMeasurementInfo)
+    {
+        for (std::size_t grid_glob_bin(0); grid_glob_bin < spacePointsGrouping.grid().size(); ++grid_glob_bin)
+        {
+            const auto& collection = spacePointsGrouping.grid().at(grid_glob_bin);
+            for (const auto& sp : collection)
+            {
+                std::size_t index = sp->index();
+                const float topHalfStripLength =
+                    m_cfg.seedFinderConfig.getTopHalfStripLength(sp->sp());
+                const float bottomHalfStripLength =
+                    m_cfg.seedFinderConfig.getBottomHalfStripLength(sp->sp());
+                const Acts::Vector3 topStripDirection =
+                    m_cfg.seedFinderConfig.getTopStripDirection(sp->sp());
+                const Acts::Vector3 bottomStripDirection =
+                    m_cfg.seedFinderConfig.getBottomStripDirection(sp->sp());
+
+                state.spacePointData.setTopStripVector(
+                    index, topHalfStripLength * topStripDirection);
+                state.spacePointData.setBottomStripVector(
+                    index, bottomHalfStripLength * bottomStripDirection);
+                state.spacePointData.setStripCenterDistance(
+                    index, m_cfg.seedFinderConfig.getStripCenterDistance(sp->sp()));
+                state.spacePointData.setTopStripCenterPosition(
+                    index, m_cfg.seedFinderConfig.getTopStripCenterPosition(sp->sp()));
+            }
+        }
+    }
+
+    for (const auto [bottom, middle, top] : spacePointsGrouping)
+    {
+        m_seedFinder.createSeedsForGroup(
+            m_cfg.seedFinderOptions, state, spacePointsGrouping.grid(),
+            std::back_inserter(seeds), bottom, middle, top, rMiddleSPRange);
+    }
+
+    debug() << "Found " << seeds.size() << " seeds for event " << _nEvt << "!" << endmsg;
+
+    // Sort seeds by the radius of their bottom space point
+    std::sort(seeds.begin(), seeds.end(), 
+        [](const ActsHelper::SimSeed& a, const ActsHelper::SimSeed& b) {
+            return a.sp()[0]->r() < b.sp()[0]->r();
+        });
+
+    // clear the seeds in RecActsSvc and insert the new seeds
+    recActsSvc->Seeds()->clear();
+    recActsSvc->Seeds()->insert(recActsSvc->Seeds()->end(), seeds.begin(), seeds.end());
+
+    chronoStatSvc->chronoStop("Seeding");
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsSeeding::finalize()
+{
+    return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.h
new file mode 100644
index 00000000..ee97506e
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsSeeding.h
@@ -0,0 +1,84 @@
+#ifndef RecActsSeeding_H
+#define RecActsSeeding_H
+
+// gaudi framework
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// Acts
+#include "Acts/Seeding/SeedFilterConfig.hpp"
+#include "Acts/Seeding/SeedFinder.hpp"
+#include "Acts/Seeding/SeedFinderConfig.hpp"
+#include "Acts/Seeding/SpacePointGrid.hpp"
+#include "Acts/Utilities/GridBinFinder.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+namespace Acts {
+    template <std::size_t> class GridBinFinder;
+}
+
+class RecActsSeeding : public GaudiAlgorithm
+{
+    public:
+        RecActsSeeding(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // properties
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+
+        Gaudi::Property<double> SeedDeltaRMin{this, "SeedDeltaRMin", 0.5}; // mm
+        Gaudi::Property<double> SeedDeltaRMax{this, "SeedDeltaRMax", 600}; // mm
+        Gaudi::Property<double> SeedRMax{this, "SeedRMax", 600}; // mm
+        Gaudi::Property<double> SeedRMin{this, "SeedRMin", 10}; // mm
+        Gaudi::Property<double> SeedImpactMax{this, "SeedImpactMax", 3}; // mm
+        Gaudi::Property<double> SeedRMinMiddle{this, "SeedRMinMiddle", 10}; // mm
+        Gaudi::Property<double> SeedRMaxMiddle{this, "SeedRMaxMiddle", 600}; // mm
+
+        // services
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        struct Config {
+            /// Input space point collections.
+            ///
+            /// We allow multiple space point collections to allow different parts of
+            /// the detector to use different algorithms for space point construction,
+            /// e.g. single-hit space points for pixel-like detectors or double-hit
+            /// space points for strip-like detectors.
+            std::vector<std::string> inputSpacePoints;
+            /// Output track seed collection.
+            std::string outputSeeds;
+
+            Acts::SeedFilterConfig seedFilterConfig;
+            Acts::SeedFinderConfig<ActsHelper::SimSpacePoint> seedFinderConfig;
+            Acts::CylindricalSpacePointGridConfig gridConfig;
+            Acts::CylindricalSpacePointGridOptions gridOptions;
+            Acts::SeedFinderOptions seedFinderOptions;
+
+            // allow for different values of rMax in gridConfig and seedFinderConfig
+            bool allowSeparateRMax = false;
+
+            // vector containing the map of z bins in the top and bottom layers
+            std::vector<std::pair<int, int>> zBinNeighborsTop;
+            std::vector<std::pair<int, int>> zBinNeighborsBottom;
+            // number of phiBin neighbors at each side of the current bin that will be
+            // used to search for SPs
+            int numPhiNeighbors = 1;
+        };
+        
+        Config m_cfg;
+        std::unique_ptr<const Acts::GridBinFinder<2ul>> m_bottomBinFinder;
+        std::unique_ptr<const Acts::GridBinFinder<2ul>> m_topBinFinder;
+        Acts::SeedFinder<ActsHelper::SimSpacePoint, Acts::CylindricalSpacePointGrid<ActsHelper::SimSpacePoint>> m_seedFinder;
+
+        int _nEvt;
+};
+
+#endif // RecActsSeeding_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.cpp
new file mode 100644
index 00000000..cf526186
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.cpp
@@ -0,0 +1,451 @@
+// dependence
+#include "RecActsTrackFinding.h"
+#include "TError.h"
+
+DECLARE_COMPONENT(RecActsTrackFinding)
+
+RecActsTrackFinding::RecActsTrackFinding(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+    declareProperty("ACTSFindOutCol", _outColHdl, "ACTS Track Finding Output track collection");
+}
+
+StatusCode RecActsTrackFinding::initialize()
+{
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    magneticField = std::make_shared<Acts::ConstantBField>(
+        Acts::Vector3(0., 0., m_field.value()*_FCT));
+    findTracks = makeTrackFinderFunction(recActsSvc->Geometry(), magneticField);
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackFinding::execute()
+{
+    _nEvt++;
+
+    auto trkCol = _outColHdl.createAndPut();
+
+    auto measurements = recActsSvc->Measurements();
+    auto sourceLinks = recActsSvc->SourceLinks();
+    auto Selected_Seeds = recActsSvc->SelectedSeeds();
+    auto trackParameters = recActsSvc->TrackParameters();
+    auto trackSourceLinks = recActsSvc->TrackSourceLinks();
+    // trackSourceLinks only store the track source links of latest trackParameters
+    trackSourceLinks->clear();
+
+    if ((trackParameters->size() == 0) || (Selected_Seeds->size() == 0)) {
+        debug() << "No initial parameters or selected seeds found for event " << _nEvt << endmsg;
+        return StatusCode::SUCCESS;
+    }
+
+    auto initialParameters = trackParameters->back();
+    auto selectedSeeds = Selected_Seeds->back();
+    ActsHelper::SimSeedContainer currentSeeds;
+
+    chronoStatSvc->chronoStart("CKF Track Finding");
+    
+    ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*recActsSvc->Geometry()};
+    // Construct a perigee surface as the target surface
+    auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
+    ActsHelper::PassThroughCalibrator pcalibrator;
+    ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
+    Acts::GainMatrixUpdater kfUpdater;
+    Acts::MeasurementSelector::Config measurementSelectorCfg =
+    {
+        {Acts::GeometryIdentifier(), {{}, {CKFchi2Cut.value()}, {numMeasurementsCutOff.value()}}},
+    };
+
+    MeasurementSelector measSel{ Acts::MeasurementSelector(measurementSelectorCfg) };
+    BranchStopper branchStopper(trackSelectorCfg);
+
+    // Construct the CKF
+    Extensions extensions;
+    extensions.calibrator.connect<&ActsHelper::MeasurementCalibratorAdapter::calibrate>(&calibrator);
+    extensions.updater.connect<&Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(&kfUpdater);
+    extensions.measurementSelector.connect<&MeasurementSelector::select>(&measSel);
+    extensions.branchStopper.connect<&BranchStopper::operator()>(&branchStopper);
+
+    ActsHelper::IndexSourceLinkAccessor slAccessor;
+    slAccessor.container = sourceLinks;
+    Acts::SourceLinkAccessorDelegate<ActsHelper::IndexSourceLinkAccessor::Iterator> slAccessorDelegate;
+    slAccessorDelegate.connect<&ActsHelper::IndexSourceLinkAccessor::range>(&slAccessor);
+
+    Acts::PropagatorPlainOptions firstPropOptions;
+    firstPropOptions.maxSteps = maxSteps;
+    firstPropOptions.direction = Acts::Direction::Forward;
+
+    Acts::PropagatorPlainOptions secondPropOptions;
+    secondPropOptions.maxSteps = maxSteps;
+    secondPropOptions.direction = firstPropOptions.direction.invert();
+
+    // Set the CombinatorialKalmanFilter options
+    TrackFinderOptions firstOptions(
+        geoContext, magFieldContext, calibContext,
+        slAccessorDelegate, extensions, firstPropOptions);
+    
+    TrackFinderOptions secondOptions(
+        geoContext, magFieldContext, calibContext,
+        slAccessorDelegate, extensions, secondPropOptions);
+    secondOptions.targetSurface = pSurface.get();
+
+    Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator> extrapolator(
+        Acts::EigenStepper<>(magneticField), Acts::Navigator({recActsSvc->Geometry()}));
+
+    Acts::PropagatorOptions<Acts::ActionList<Acts::MaterialInteractor>, Acts::AbortList<Acts::EndOfWorldReached>>
+        extrapolationOptions(geoContext, magFieldContext);
+
+    auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
+    auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
+    auto trackContainerTemp = std::make_shared<Acts::VectorTrackContainer>();
+    auto trackStateContainerTemp = std::make_shared<Acts::VectorMultiTrajectory>();
+    TrackContainer tracks(trackContainer, trackStateContainer);
+    TrackContainer tracksTemp(trackContainerTemp, trackStateContainerTemp);
+
+    tracks.addColumn<unsigned int>("trackGroup");
+    tracksTemp.addColumn<unsigned int>("trackGroup");
+    Acts::ProxyAccessor<unsigned int> seedNumber("trackGroup");
+
+    unsigned int nSeed = 0;
+    // A map indicating whether a seed has been discovered already
+    std::unordered_map<SeedIdentifier, bool> discoveredSeeds;
+    auto addTrack = [&](const TrackProxy& track, const ActsHelper::SimSeed& seed)
+    {
+        ++m_nFoundTracks;
+        // flag seeds which are covered by the track
+        visitSeedIdentifiers(track, [&](const SeedIdentifier& seedIdentifier)
+        {
+            if (auto it = discoveredSeeds.find(seedIdentifier); it != discoveredSeeds.end())
+            {
+                it->second = true;
+            }
+        });
+
+        if (m_trackSelector.has_value() && !m_trackSelector->isValidTrack(track)) { return; }
+
+        ++m_nSelectedTracks;
+        currentSeeds.push_back(seed);
+        auto destProxy = tracks.makeTrack();
+        // make sure we copy track states!
+        destProxy.copyFrom(track, true);
+    };
+
+    for (std::size_t iSeed = 0; iSeed < selectedSeeds.size(); ++iSeed) {
+        SeedIdentifier seedIdentifier = makeSeedIdentifier(selectedSeeds.at(iSeed));
+        discoveredSeeds.emplace(seedIdentifier, false);
+    }
+
+    for (std::size_t iSeed = 0; iSeed < initialParameters.size(); ++iSeed)
+    {           
+        m_nTotalSeeds++;
+        const auto& seed = selectedSeeds.at(iSeed);
+
+        SeedIdentifier seedIdentifier = makeSeedIdentifier(seed);
+        // check if the seed has been discovered already
+        if (auto it = discoveredSeeds.find(seedIdentifier); it != discoveredSeeds.end() && it->second)
+        {
+            m_nDeduplicatedSeeds++;
+            continue;
+        }
+
+        /// Whether to stick on the seed measurements during track finding.
+        // measSel.setSeed(seed);
+
+        // Clear trackContainerTemp and trackStateContainerTemp
+        tracksTemp.clear();
+
+        const Acts::BoundTrackParameters& firstInitialParameters = initialParameters.at(iSeed);
+        auto firstResult = (*findTracks)(firstInitialParameters, firstOptions, tracksTemp);
+
+        nSeed++;
+        if (!firstResult.ok())
+        {
+            m_nFailedSeeds++;
+            continue;
+        }
+
+        auto& firstTracksForSeed = firstResult.value();
+        for (auto& firstTrack : firstTracksForSeed)
+        {
+            auto trackCandidate = tracksTemp.makeTrack();
+            trackCandidate.copyFrom(firstTrack, true);
+
+            auto firstSmoothingResult = Acts::smoothTrack(geoContext, trackCandidate);
+
+            if (!firstSmoothingResult.ok())
+            {
+                m_nFailedSmoothing++;
+                debug() << "First smoothing for seed " 
+                       << iSeed << " and track " << firstTrack.index()
+                       << " failed with error " << firstSmoothingResult.error() << endmsg;
+                continue;
+            }
+
+            seedNumber(trackCandidate) = nSeed - 1;
+      
+            // second way track finding
+            std::size_t nSecond = 0;
+            if (CKFtwoWay)
+            {
+                std::optional<Acts::VectorMultiTrajectory::TrackStateProxy> firstMeasurement;
+                for (auto trackState : trackCandidate.trackStatesReversed())
+                {
+                    bool isMeasurement = trackState.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag);
+                    bool isOutlier = trackState.typeFlags().test(Acts::TrackStateFlag::OutlierFlag);
+                    if (isMeasurement && !isOutlier) { firstMeasurement = trackState; }
+                }
+                
+                if (firstMeasurement.has_value())
+                {
+                    Acts::BoundTrackParameters secondInitialParameters = trackCandidate.createParametersFromState(*firstMeasurement);
+                    auto secondResult = (*findTracks)(secondInitialParameters, secondOptions, tracksTemp);
+                    if (!secondResult.ok())
+                    {
+                        debug() << "Second track finding failed for seed "
+                                << iSeed << " with error" << secondResult.error() << endmsg;
+                    } else {
+                        auto firstState = *std::next(trackCandidate.trackStatesReversed().begin(), trackCandidate.nTrackStates() - 1);
+                        auto& secondTracksForSeed = secondResult.value();
+                        for (auto& secondTrack : secondTracksForSeed)
+                        { 
+                            if (secondTrack.nTrackStates() < 2) { continue; }
+                            auto secondTrackCopy = tracksTemp.makeTrack();
+                            secondTrackCopy.copyFrom(secondTrack, true);
+                            secondTrackCopy.reverseTrackStates(true);
+
+                            firstState.previous() = (*std::next(secondTrackCopy.trackStatesReversed().begin())).index();
+                            Acts::calculateTrackQuantities(trackCandidate);
+                            auto secondExtrapolationResult = Acts::extrapolateTrackToReferenceSurface(
+                                trackCandidate, *pSurface, extrapolator,
+                                extrapolationOptions, extrapolationStrategy);
+                            if (!secondExtrapolationResult.ok())
+                            {
+                                m_nFailedExtrapolation++;
+                                debug() << "Second extrapolation for seed " 
+                                        << iSeed << " and track " << secondTrack.index()
+                                        << " failed with error "
+                                        << secondExtrapolationResult.error() << endmsg;
+                                continue;
+                            }
+
+                            addTrack(trackCandidate, seed);
+
+                            ++nSecond;
+                        }
+
+                        firstState.previous() = Acts::kTrackIndexInvalid;
+                        Acts::calculateTrackQuantities(trackCandidate);
+                    }
+                }
+            }
+
+            // if no second track was found, we will use only the first track
+            if (nSecond == 0) {
+                auto firstExtrapolationResult = Acts::extrapolateTrackToReferenceSurface(
+                    trackCandidate, *pSurface, extrapolator, extrapolationOptions, extrapolationStrategy);
+
+                if (!firstExtrapolationResult.ok())
+                {
+                    m_nFailedExtrapolation++;
+                    continue;
+                }
+
+                addTrack(trackCandidate, seed);
+            }
+        }
+    }
+
+    chronoStatSvc->chronoStop("CKF Track Finding");
+
+    chronoStatSvc->chronoStart("Track Selection & WriteOut");
+    // debug() << "CKF found " << tracks.size() << " tracks for event " << _nEvt << "!" << endmsg;
+    m_memoryStatistics.local().hist += tracks.trackStateContainer().statistics().hist;
+    auto constTrackStateContainer = std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer));
+    auto constTrackContainer = std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer));
+    ConstTrackContainer constTracks{constTrackContainer, constTrackStateContainer};
+    std::vector<Acts::BoundTrackParameters> cur_track_params;
+
+    debug() << "CKF found " << constTracks.size() << " tracks at event " << _nEvt << endmsg;
+
+    // bool isSingleTrack = constTracks.size() == 1;
+    // if (!isSingleTrack) {
+    //     info() << "CKF found " << constTracks.size() << " tracks at event " << _nEvt << endmsg;
+    //     info() << "all measurements size: " << measurements->size() << endmsg;
+    // }
+
+    // std::vector<int> picked_track_idx;
+    // std::vector<double> picked_track_Chi2;
+    // std::vector<int> picked_track_nMeasurements;
+    // for (int i = 0; i < constTracks.size(); i++) {
+    //     auto cur_track = constTracks.getTrack(i);
+    //     auto cur_chi2 = cur_track.chi2();
+    //     auto cur_nMeasurements = cur_track.nMeasurements();
+
+    //     if (cur_nMeasurements < TrackNdfCut.value()) {
+    //         continue;
+    //     }
+
+    //     picked_track_idx.push_back(i);
+    //     picked_track_Chi2.push_back(cur_chi2);
+    //     picked_track_nMeasurements.push_back(cur_nMeasurements);
+    // }
+
+    // std::sort(picked_track_idx.begin(), picked_track_idx.end(),
+    //             [&picked_track_Chi2, &picked_track_nMeasurements](int i1, int i2) { 
+    //                 if (picked_track_nMeasurements[i1] != picked_track_nMeasurements[i2]) {
+    //                     return picked_track_nMeasurements[i1] > picked_track_nMeasurements[i2];
+    //                 }
+    //                 return picked_track_Chi2[i1] < picked_track_Chi2[i2]; 
+    //             });
+
+    std::vector<int> picked_track_idx;
+    std::vector<double> picked_track_Chi2;
+    ActsHelper::SimSeedContainer writeout_seeds;
+    // std::vector<int> picked_track_nMeasurements;
+    for (int i = 0; i < constTracks.size(); i++) {
+        auto cur_track = constTracks.getTrack(i);
+        auto cur_chi2 = cur_track.chi2();
+        auto cur_nMeasurements = cur_track.nMeasurements();
+
+        picked_track_idx.push_back(i);
+        picked_track_Chi2.push_back(cur_chi2/cur_nMeasurements);
+    }
+
+    std::sort(picked_track_idx.begin(), picked_track_idx.end(),
+                [&picked_track_Chi2](int i1, int i2) { 
+                    return picked_track_Chi2[i1] < picked_track_Chi2[i2]; 
+                });
+
+    int picked_track_num = std::min(pickTrackMaxNum.value(), picked_track_idx.size());
+    for (int i = 0; i < picked_track_num; i++) {
+        auto writeout_track = trkCol->create();
+        auto cur_track = constTracks.getTrack(picked_track_idx[i]);
+        writeout_seeds.push_back(currentSeeds[picked_track_idx[i]]);
+
+        writeout_track.setChi2(cur_track.chi2());
+        writeout_track.setNdf(cur_track.nDoF());
+        writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
+
+        // if (!isSingleTrack){
+        //     info() << "picked Track Chi2: " << cur_track.chi2() << " nMeasurements: " << cur_track.nMeasurements() << endmsg;
+        // }
+
+        debug() << "A track with p=" << cur_track.absoluteMomentum() << " GeV is found for event " << _nEvt << "!" << endmsg;
+        debug() << "Track nMeasurements: " << cur_track.nMeasurements() << endmsg;
+        debug() << "MC nMeasurements: " << measurements->size() << endmsg;
+
+        // TODO: covmatrix need to be converted
+        std::array<float, 21> writeout_covMatrix;
+        auto cur_track_covariance = cur_track.covariance();
+        for (int i = 0; i < 6; i++) {
+            for (int j = 0; j < 6-i; j++) {
+                writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
+            }
+        }
+        // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
+        // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
+        edm4hep::TrackState writeout_trackState{
+            1, // location: AtOther
+            cur_track.loc0() / Acts::UnitConstants::mm, // d0
+            cur_track.phi(), // phi
+            cur_track.qOverP() * _FCT * m_field.value() / sin(cur_track.theta()), // omega = qop * sin(theta) * _FCT * bf
+            cur_track.loc1() / Acts::UnitConstants::mm, // z0
+            1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
+            cur_track.time(), // time
+            ::edm4hep::Vector3f(0, 0, 0), // referencePoint
+            writeout_covMatrix
+        };
+
+        writeout_track.addToTrackStates(writeout_trackState);
+
+        // for track fitting
+        std::vector<Acts::SourceLink> cur_trackSourceLinks;
+        for (auto trackState : cur_track.trackStates())
+        {
+            if (trackState.hasUncalibratedSourceLink())
+            {
+                auto cur_measurement_sl = trackState.getUncalibratedSourceLink();
+                cur_trackSourceLinks.push_back(cur_measurement_sl);
+            }
+        }
+
+        cur_track_params.push_back(cur_track.createParametersAtReference());
+        trackSourceLinks->push_back(cur_trackSourceLinks);
+
+        m_nFinalTracks++;
+    }
+
+    trackParameters->push_back(cur_track_params);
+    Selected_Seeds->push_back(writeout_seeds);
+    chronoStatSvc->chronoStop("Track Selection & WriteOut");
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackFinding::finalize()
+{
+    info() << "finalize Finding" << endmsg;
+    info() << "Total number of events processed: " << _nEvt + 1 << endmsg;
+    info() << "Total number of **TotalSeeds** processed: " << m_nTotalSeeds << endmsg;
+    info() << "Total number of **FoundTracks** processed: " << m_nFoundTracks << endmsg;
+    info() << "Total number of **SelectedTracks** processed: " << m_nSelectedTracks << endmsg;
+    info() << "Total number of **FinalTracks** processed: " << m_nFinalTracks << endmsg;
+    return StatusCode::SUCCESS;
+}
+
+template <typename source_link_accessor_container_t>
+void RecActsTrackFinding::computeSharedHits(const source_link_accessor_container_t& sourceLinks, TrackContainer& tracks) const
+{
+    // Compute shared hits from all the reconstructed tracks
+    // Compute nSharedhits and Update ckf results
+    // hit index -> list of multi traj indexes [traj, meas]
+
+    std::vector<std::size_t> firstTrackOnTheHit(sourceLinks.size(), std::numeric_limits<std::size_t>::max());
+    std::vector<std::size_t> firstStateOnTheHit(sourceLinks.size(), std::numeric_limits<std::size_t>::max());
+
+    for (auto track : tracks)
+    {
+        for (auto state : track.trackStatesReversed())
+        {
+            if (!state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) { continue; }
+
+            std::size_t hitIndex = state.getUncalibratedSourceLink()
+                                        .template get<ActsHelper::IndexSourceLink>()
+                                        .index();
+
+            // Check if hit not already used
+            if (firstTrackOnTheHit.at(hitIndex) == std::numeric_limits<std::size_t>::max())
+            {
+                firstTrackOnTheHit.at(hitIndex) = track.index();
+                firstStateOnTheHit.at(hitIndex) = state.index();
+                continue;
+            }
+
+            // if already used, control if first track state has been marked
+            // as shared
+            int indexFirstTrack = firstTrackOnTheHit.at(hitIndex);
+            int indexFirstState = firstStateOnTheHit.at(hitIndex);
+
+            auto firstState = tracks.getTrack(indexFirstTrack)
+                                    .container()
+                                    .trackStateContainer()
+                                    .getTrackState(indexFirstState);
+
+            if (!firstState.typeFlags().test(Acts::TrackStateFlag::SharedHitFlag))
+            {
+                firstState.typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+            }
+            
+            // Decorate this track state
+            state.typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.h
new file mode 100644
index 00000000..20e152d9
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFinding.h
@@ -0,0 +1,378 @@
+#ifndef RecActsTrackFinding_H
+#define RecActsTrackFinding_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+
+// Acts
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackProxy.hpp"
+#include "Acts/EventData/ProxyAccessor.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Propagator/AbortList.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/MaterialInteractor.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/Propagator/StandardAborters.hpp"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
+#include "Acts/TrackFinding/MeasurementSelector.hpp"
+#include "Acts/TrackFinding/TrackSelector.hpp"
+#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Enumerate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+#include "Acts/Utilities/TrackHelpers.hpp"
+#include "Acts/Utilities/Result.hpp"
+
+#include <cmath>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <stdexcept>
+#include <system_error>
+#include <unordered_map>
+#include <utility>
+#include <atomic>
+#include <cstddef>
+#include <limits>
+#include <string>
+#include <variant>
+
+#include <boost/functional/hash.hpp>
+#include <tbb/combinable.h>
+
+namespace Acts
+{
+    class MagneticFieldProvider;
+    class TrackingGeometry;
+}
+
+using TrackParameters = ::Acts::BoundTrackParameters;
+using TrackParametersContainer = std::vector<TrackParameters>;
+using TrackContainer = Acts::TrackContainer<Acts::VectorTrackContainer, Acts::VectorMultiTrajectory, std::shared_ptr>;
+using ConstTrackContainer = Acts::TrackContainer<Acts::ConstVectorTrackContainer, Acts::ConstVectorMultiTrajectory, std::shared_ptr>;
+using TrackIndexType = TrackContainer::IndexType;
+using TrackProxy = TrackContainer::TrackProxy;
+using ConstTrackProxy = ConstTrackContainer::ConstTrackProxy;
+
+using TrackFinderOptions = Acts::CombinatorialKalmanFilterOptions<ActsHelper::IndexSourceLinkAccessor::Iterator, Acts::VectorMultiTrajectory>;
+using TrackFinderResult  = Acts::Result<std::vector<TrackProxy>>;
+using Extensions = Acts::CombinatorialKalmanFilterExtensions<Acts::VectorMultiTrajectory>;
+using Updater = Acts::GainMatrixUpdater;
+using Smoother = Acts::GainMatrixSmoother;
+using Stepper = Acts::EigenStepper<>;
+using Navigator = Acts::Navigator;
+using Propagator = Acts::Propagator<Stepper, Navigator>;
+
+using CKF = Acts::CombinatorialKalmanFilter<Propagator, Acts::VectorMultiTrajectory>;
+
+class RecActsTrackFinding : public GaudiAlgorithm
+{
+    public:
+        RecActsTrackFinding(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+        class TrackFinderFunction
+        {
+            public:
+            virtual ~TrackFinderFunction() = default;
+            virtual TrackFinderResult operator()(const TrackParameters&,
+                                                const TrackFinderOptions&,
+                                                TrackContainer&) const = 0;
+        };
+
+        static std::shared_ptr<TrackFinderFunction> makeTrackFinderFunction(
+            std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+            std::shared_ptr<const Acts::MagneticFieldProvider> magneticField
+            );
+
+    private:
+        // Output collections
+        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSTracks", Gaudi::DataHandle::Writer, this};
+
+        // CKF config
+        Gaudi::Property<double> CKFchi2Cut{this, "CKFchi2Cut", std::numeric_limits<double>::max()};
+        Gaudi::Property<std::size_t> numMeasurementsCutOff{this, "numMeasurementsCutOff", 1u};
+        Gaudi::Property<std::size_t> pickTrackMaxNum{this, "pickTrackMaxNum", 1u};
+        Gaudi::Property<bool> CKFtwoWay{this, "CKFtwoWay", true};
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+        
+        template <typename source_link_accessor_container_t>
+        void computeSharedHits(const source_link_accessor_container_t& sourcelinks, TrackContainer& tracks) const;
+
+        // track finder
+        std::shared_ptr<TrackFinderFunction> findTracks;
+        std::optional<Acts::TrackSelector> m_trackSelector;
+        std::optional<std::variant<Acts::TrackSelector::Config, Acts::TrackSelector::EtaBinnedConfig>> trackSelectorCfg = std::nullopt;
+        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
+
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+        
+        int _nEvt;
+        const double _FCT = 2.99792458E-4;
+        const Acts::Vector3 globalFakeMom{1.e6, 1.e6, 1.e6};
+
+        const std::array<Acts::BoundIndices, 6> writeout_indices{
+            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
+            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
+            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
+            
+        Acts::GeometryContext geoContext;
+        Acts::MagneticFieldContext magFieldContext;
+        Acts::CalibrationContext calibContext;
+
+        mutable std::atomic<std::size_t> m_nTotalSeeds{0};
+        mutable std::atomic<std::size_t> m_nDeduplicatedSeeds{0};
+        mutable std::atomic<std::size_t> m_nFailedSeeds{0};
+        mutable std::atomic<std::size_t> m_nFailedSmoothing{0};
+        mutable std::atomic<std::size_t> m_nFailedExtrapolation{0};
+        mutable std::atomic<std::size_t> m_nFoundTracks{0};
+        mutable std::atomic<std::size_t> m_nSelectedTracks{0};
+        mutable std::atomic<std::size_t> m_nStoppedBranches{0};
+        mutable std::atomic<std::size_t> m_nFinalTracks{0};
+
+        // layer hits, VXD (0-5) & ITKBarrel (6-8)
+        std::array<std::atomic<size_t>, 9> m_nLayerHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
+        std::array<std::atomic<size_t>, 6> m_nRec_VTX{0, 0, 0, 0, 0, 0};
+        std::array<std::atomic<size_t>, 3> m_nRec_ITKBarrel{0, 0, 0};
+        std::array<std::atomic<size_t>, 3> m_nRec_ITKEndcap{0, 0, 0};
+        std::array<std::atomic<size_t>, 9> m_n0EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
+        std::array<std::atomic<size_t>, 9> m_n1EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
+        std::array<std::atomic<size_t>, 9> m_n2EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
+        std::array<std::atomic<size_t>, 9> m_n3EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
+        
+        mutable tbb::combinable<Acts::VectorMultiTrajectory::Statistics> m_memoryStatistics{[]() {
+            auto mtj = std::make_shared<Acts::VectorMultiTrajectory>();
+            return mtj->statistics();
+        }};
+        Acts::TrackExtrapolationStrategy extrapolationStrategy = Acts::TrackExtrapolationStrategy::firstOrLast; /// Extrapolation strategy
+        unsigned int maxSteps = 100000;
+};
+
+struct TrackFinderFunctionImpl: public RecActsTrackFinding::TrackFinderFunction
+{
+    CKF trackFinder;
+    TrackFinderFunctionImpl(CKF&& f) : trackFinder(std::move(f)) {}
+    
+    TrackFinderResult operator()(
+        const TrackParameters& initialParameters,
+        const TrackFinderOptions& options,
+        TrackContainer& tracks) const override
+    {
+        return trackFinder.findTracks(initialParameters, options, tracks);
+    };
+};
+
+std::shared_ptr<RecActsTrackFinding::TrackFinderFunction> RecActsTrackFinding::makeTrackFinderFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField)
+{
+    Stepper stepper(magneticField);
+    Navigator::Config cfg{trackingGeometry};
+    cfg.resolvePassive = false;
+    cfg.resolveMaterial = true;
+    cfg.resolveSensitive = true;
+    Navigator navigator(cfg);
+    Propagator propagator(std::move(stepper), std::move(navigator));
+    CKF trackFinder(std::move(propagator));
+
+    // build the track finder functions. owns the track finder object.
+    return std::make_shared<TrackFinderFunctionImpl>(std::move(trackFinder));
+}
+
+// Specialize std::hash for SeedIdentifier
+// This is required to use SeedIdentifier as a key in an `std::unordered_map`.
+template <class T, std::size_t N>
+struct std::hash<std::array<T, N>>
+{
+    std::size_t operator()(const std::array<T, N>& array) const
+    {
+        std::hash<T> hasher;
+        std::size_t result = 0;
+        for (auto&& element : array) { boost::hash_combine(result, hasher(element)); }
+        return result;
+    }
+};
+
+class MeasurementSelector
+{
+public:
+    using Traj = Acts::VectorMultiTrajectory;
+    explicit MeasurementSelector(Acts::MeasurementSelector selector): m_selector(std::move(selector)) {}
+
+    void setSeed(const std::optional<ActsHelper::SimSeed>& seed) { m_seed = seed; }
+
+    Acts::Result<std::pair<std::vector<Traj::TrackStateProxy>::iterator, std::vector<Traj::TrackStateProxy>::iterator>> select(
+        std::vector<Traj::TrackStateProxy>& candidates, bool& isOutlier, const Acts::Logger& logger) const
+    {
+        if (m_seed.has_value())
+        {
+            std::vector<Traj::TrackStateProxy> newCandidates;
+            for (const auto& candidate : candidates)
+            {
+                if (isSeedCandidate(candidate)) { newCandidates.push_back(candidate); }
+            }
+            if (!newCandidates.empty()) { candidates = std::move(newCandidates); }
+        }
+        
+        return m_selector.select<Acts::VectorMultiTrajectory>(candidates, isOutlier, logger);
+    }
+
+private:
+    Acts::MeasurementSelector m_selector;
+    std::optional<ActsHelper::SimSeed> m_seed;
+
+    bool isSeedCandidate(const Traj::TrackStateProxy& candidate) const
+    {
+        assert(candidate.hasUncalibratedSourceLink());
+        const Acts::SourceLink& sourceLink = candidate.getUncalibratedSourceLink();
+        for (const auto& sp : m_seed->sp())
+        {
+            for (const auto& sl : sp->sourceLinks())
+            {
+                if (sourceLink.get<ActsHelper::IndexSourceLink>() == sl.get<ActsHelper::IndexSourceLink>()) { return true; }
+            }
+        }
+        return false;
+    }
+};
+
+/// Source link indices of the bottom, middle, top measurements.
+/// In case of strip seeds only the first source link of the pair is used.
+using SeedIdentifier = std::array<ActsHelper::Index, 3>;
+
+/// Build a seed identifier from a seed.
+///
+/// @param seed The seed to build the identifier from.
+/// @return The seed identifier.
+SeedIdentifier makeSeedIdentifier(const ActsHelper::SimSeed& seed)
+{
+    SeedIdentifier result;
+    for (const auto& [i, sp] : Acts::enumerate(seed.sp()))
+    {
+        const Acts::SourceLink& firstSourceLink = sp->sourceLinks().front();
+        result.at(i) = firstSourceLink.get<ActsHelper::IndexSourceLink>().index();
+    }
+    
+    return result;
+}
+
+/// Visit all possible seed identifiers of a track.
+///
+/// @param track The track to visit the seed identifiers of.
+/// @param visitor The visitor to call for each seed identifier.
+template <typename Visitor>
+void visitSeedIdentifiers(const TrackProxy& track, Visitor visitor)
+{
+    // first we collect the source link indices of the track states
+    std::vector<ActsHelper::Index> sourceLinkIndices;
+    sourceLinkIndices.reserve(track.nMeasurements());
+
+    for (const auto& trackState : track.trackStatesReversed())
+    {
+        if (!trackState.hasUncalibratedSourceLink()) { continue; }
+        const Acts::SourceLink& sourceLink = trackState.getUncalibratedSourceLink();
+        sourceLinkIndices.push_back(sourceLink.get<ActsHelper::IndexSourceLink>().index());
+    }
+
+    // then we iterate over all possible triplets and form seed identifiers
+    for (std::size_t i = 0; i < sourceLinkIndices.size(); ++i)
+    {
+        for (std::size_t j = i + 1; j < sourceLinkIndices.size(); ++j)
+        {
+            for (std::size_t k = j + 1; k < sourceLinkIndices.size(); ++k)
+            {
+                // Putting them into reverse order (k, j, i) to compensate for the
+                // `trackStatesReversed` above.
+                visitor({sourceLinkIndices.at(k), sourceLinkIndices.at(j),
+                sourceLinkIndices.at(i)});
+            }
+        }
+    }
+}
+
+class BranchStopper
+{
+public:
+    using Config = std::optional<std::variant<Acts::TrackSelector::Config, Acts::TrackSelector::EtaBinnedConfig>>;
+    using BranchStopperResult = Acts::CombinatorialKalmanFilterBranchStopperResult;
+
+    mutable std::atomic<std::size_t> m_nStoppedBranches{0};
+
+    explicit BranchStopper(const Config& config) : m_config(config) {}
+
+    BranchStopperResult operator()(
+        const Acts::CombinatorialKalmanFilterTipState& tipState,
+        Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const
+    {
+    if (!m_config.has_value()) { return BranchStopperResult::Continue; }
+
+    const Acts::TrackSelector::Config* singleConfig = std::visit(
+        [&](const auto& config) -> const Acts::TrackSelector::Config*
+    {
+        using T = std::decay_t<decltype(config)>;
+        if constexpr (std::is_same_v<T, Acts::TrackSelector::Config>)
+        {
+            return &config;
+        } else if constexpr (std::is_same_v<T, Acts::TrackSelector::EtaBinnedConfig>)
+        {
+            double theta = trackState.parameters()[Acts::eBoundTheta];
+            double eta = -std::log(std::tan(0.5 * theta));
+            return config.hasCuts(eta) ? &config.getCuts(eta) : nullptr;
+        }
+    }, *m_config);
+
+    if (singleConfig == nullptr)
+    {
+        ++m_nStoppedBranches;
+        return BranchStopperResult::StopAndDrop;
+    }
+
+    bool enoughMeasurements = tipState.nMeasurements >= singleConfig->minMeasurements;
+    bool tooManyHoles = tipState.nHoles > singleConfig->maxHoles;
+    bool tooManyOutliers = tipState.nOutliers > singleConfig->maxOutliers;
+
+    if (tooManyHoles || tooManyOutliers)
+    {
+        ++m_nStoppedBranches;
+        return enoughMeasurements ? BranchStopperResult::StopAndKeep
+        : BranchStopperResult::StopAndDrop;
+    }
+
+    return BranchStopperResult::Continue;
+}
+
+private:
+    Config m_config;
+};
+
+#endif // RecActsTrackFinding_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.cpp
new file mode 100644
index 00000000..e78742e0
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.cpp
@@ -0,0 +1,144 @@
+// dependence
+#include "RecActsTrackFitting.h"
+
+DECLARE_COMPONENT(RecActsTrackFitting)
+
+RecActsTrackFitting::RecActsTrackFitting(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+    declareProperty("ACTSFitOutCol", _outColHdl, "ACTS Track Fitting Output track collection");
+}
+
+StatusCode RecActsTrackFitting::initialize()
+{
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    magneticField = std::make_shared<Acts::ConstantBField>(Acts::Vector3(0., 0., m_field.value()*_FCT));
+
+    if (m_fitType == "gsf") {
+        fit = ActsHelper::makeGsfFitterFunction(recActsSvc->Geometry(), magneticField,
+                                                betheHeitlerApprox, m_maxComponents.value(), weightCutoff, mergeMethod, reductionAlg,
+                                                *Acts::getDefaultLogger("Gsf", Acts::Logging::INFO));
+    } else if (m_fitType == "kalman") {
+        fit = ActsHelper::makeKalmanFitterFunction(recActsSvc->Geometry(), magneticField);
+    } else {
+        error() << "Invalid fit type: " << m_fitType << endmsg;
+        return StatusCode::FAILURE;
+    }
+    
+    // fit = ActsHelper::makeGlobalChiSquareFitterFunction(recActsSvc->Geometry(), magneticField); (ok)
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackFitting::execute()
+{
+    _nEvt++;
+
+    auto trkCol = _outColHdl.createAndPut();
+
+    auto measurements = recActsSvc->Measurements();
+    auto trackSourceLinks = recActsSvc->TrackSourceLinks();
+    auto trackParameters = recActsSvc->TrackParameters();
+
+    chronoStatSvc->chronoStart("Track Fitting & WriteOut");
+
+    if (trackParameters->size() == 0) {
+        debug() << "No track parameters found for event " << _nEvt << endmsg;
+        return StatusCode::SUCCESS;
+    }
+
+    auto initialParameters = trackParameters->back();
+
+    if (initialParameters.size() == 0) {
+        debug() << "No initial parameters found for event " << _nEvt << endmsg;
+        return StatusCode::SUCCESS;
+    }
+
+    if (initialParameters.size() != trackSourceLinks->size()) {
+        debug() << "Number of tracks and source links do not match for event " << _nEvt << endmsg;
+        return StatusCode::SUCCESS;
+    }
+
+    auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
+    ActsHelper::PassThroughCalibrator pcalibrator;
+    ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
+    ActsHelper::TrackFitterFunction::GeneralFitterOptions options{
+        geoContext, magFieldContext, calibContext, pSurface.get(), Acts::PropagatorPlainOptions()};
+
+    for (int i = 0; i < initialParameters.size(); i++)
+    {
+        auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
+        auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
+        ActsHelper::TrackContainer tracks(trackContainer, trackStateContainer);
+
+        auto cur_trackSourceLinks = trackSourceLinks->at(i);
+        auto cur_trackParameters  = initialParameters.at(i);
+        auto result = (*fit)(cur_trackSourceLinks, cur_trackParameters, options, calibrator, tracks);
+
+        if (!result.ok()) {
+            info() << "Track fit failed for event " << _nEvt << endmsg;
+            continue;
+        }
+
+        std::stringstream ss;
+        trackStateContainer->statistics().toStream(ss);
+        debug() << ss.str() << endmsg;
+
+        ActsHelper::ConstTrackContainer constTracks
+        {
+            std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer)),
+            std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer))
+        };
+
+        for (const auto& cur_track : constTracks)
+        {
+            auto writeout_track = trkCol->create();
+
+            writeout_track.setChi2(cur_track.chi2());
+            writeout_track.setNdf(cur_track.nDoF());
+            writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
+
+            // TODO: covmatrix need to be converted
+            std::array<float, 21> writeout_covMatrix;
+            auto cur_track_covariance = cur_track.covariance();
+            for (int i = 0; i < 6; i++) {
+                for (int j = 0; j < 6-i; j++) {
+                    writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
+                }
+            }
+            // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
+            // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
+            edm4hep::TrackState writeout_trackState{
+                1, // location: AtOther
+                cur_track.loc0() / Acts::UnitConstants::mm, // d0
+                cur_track.phi(), // phi
+                cur_track.qOverP() * _FCT * m_field.value() / sin(cur_track.theta()) , // omega = qop * sin(theta) * _FCT * bf
+                cur_track.loc1() / Acts::UnitConstants::mm, // z0
+                1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
+                cur_track.time(), // time
+                ::edm4hep::Vector3f(0, 0, 0), // referencePoint
+                writeout_covMatrix
+            };
+
+            debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " GeV!" << endmsg;
+            writeout_track.addToTrackStates(writeout_trackState);
+        }
+    }
+
+    chronoStatSvc->chronoStop("Track Fitting & WriteOut");
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackFitting::finalize()
+{
+    return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.h
new file mode 100644
index 00000000..e3a1291a
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackFitting.h
@@ -0,0 +1,194 @@
+#ifndef RecActsTrackFitting_H
+#define RecActsTrackFitting_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+#include "UTIL/ILDConf.h"
+#include "DataHelper/Navigation.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// DD4hep
+#include "DD4hep/Detector.h"
+#include "DDRec/ISurface.h"
+#include "DDRec/SurfaceManager.h"
+
+// edm4hep
+#include "edm4hep/MCParticle.h"
+#include "edm4hep/MCParticleCollection.h"
+#include "edm4hep/TrackerHitCollection.h"
+#include "edm4hep/SimTrackerHitCollection.h"
+#include "edm4hep/EventHeaderCollection.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+// Acts
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/EventData/GenericBoundTrackParameters.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/TrackProxy.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Result.hpp"
+
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+namespace Acts
+{
+    class MagneticFieldProvider;
+    class TrackingGeometry;
+}
+
+class RecActsTrackFitting : public GaudiAlgorithm
+{
+    public:
+        RecActsTrackFitting(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // Output collections
+        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSFitTracks", Gaudi::DataHandle::Writer, this};
+
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+        Gaudi::Property<std::string> m_fitType{this, "fitType", "kalman"}; // gsf or kalman
+        Gaudi::Property<std::size_t> m_maxComponents{this, "maxComponents", 12};
+
+        // services
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        std::shared_ptr<ActsHelper::TrackFitterFunction> fit;
+        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
+        
+        // context
+        Acts::GeometryContext geoContext;
+        Acts::MagneticFieldContext magFieldContext;
+        Acts::CalibrationContext calibContext;
+
+        Acts::AtlasBetheHeitlerApprox<6, 5> makeBetheHeitlerApprox() {
+            // reference: https://gitlab.cern.ch/atlas/athena/
+            // Tracking/TrkFitter/TrkGaussianSumFilter/Data/GeantSim_LT01_cdf_nC6_O5.par
+            constexpr static Acts::AtlasBetheHeitlerApprox<6, 5>::Data LT01_cdf_cmps6_order5_data = {{
+                // Component #1
+                {
+                    {{3.2101094136612709e+06,  -9.4705630748389300e+05,  1.0829875911775141e+05, -6.1863554011577025e+03,  2.0135361106894280e+02, -8.0761514988818153e+00}},
+                    {{4.5079440999297943e+05,  -1.0647181055807657e+05,  9.0139114799507752e+03, -3.6081366141823679e+02,  7.0376737639960325e+00, -2.3610285047746506e+00}},
+                    {{7.4586241329435969e+05,  -1.6634188647165423e+05,  1.2838436550628834e+04, -4.5120828291620853e+02,  8.1842526809672229e+00, -5.9197536687071333e+00}}
+                },
+                // Component #2
+                {
+                    {{3.5884379653355042e+06,  -1.0531756617027025e+06,  1.1934641124479045e+05, -6.7061190805056403e+03,  2.1095020483594095e+02, -6.5568419073184483e+00}},
+                    {{0.0000000000000000e+00,   0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -1.5062019624783798e+00, -3.1569307956279197e-01}},
+                    {{0.0000000000000000e+00,   0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -1.3027693288928379e+00, -3.5400481738796068e+00}}
+                },
+                // Component #3
+                {
+                    {{2.3155452602605596e+06,  -7.1891501255134703e+05,  8.6585158124840658e+04, -5.1799762005076909e+03,  1.7441315420719826e+02, -5.3334936315347807e+00}},
+                    {{0.0000000000000000e+00,   0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -3.6114283847420849e+00,  1.5982998291730415e+00}},
+                    {{0.0000000000000000e+00,   0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -9.8697714460669317e-01, -4.0296821938338319e+00}}
+                },
+                // Component #4
+                {
+                    {{ 2.5492741738774744e+06, -7.8599345529792004e+05,  9.3737591740107193e+04, -5.5326423872095538e+03,  1.8118619438298339e+02, -5.2390153677605218e+00}},
+                    {{-3.1098153044707078e+05,  1.1007248411865601e+05, -1.4280144141196661e+04,  8.4109025518696251e+02, -2.5970904293141370e+01,  3.1892310883718391e+00}},
+                    {{-5.5577338590808783e+05,  1.3154358085719805e+05, -1.1694747055412488e+04,  5.6105436016810438e+02, -1.5530677686084243e+01, -6.1618612270588793e+00}}
+                },
+                // Component #5
+                {
+                    {{ 2.6932154305977230e+06, -8.2250052647013229e+05,  9.6899959691172277e+04, -5.6297294779617941e+03,  1.7951168208697126e+02, -4.6983951237217196e+00}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -5.4812317220100892e+00,  4.7513315671141720e+00}},
+                    {{-5.2836565144663176e+05,  1.2409531281512717e+05, -1.0931090612321719e+04,  5.2509257144231174e+02, -1.3301367935777353e+01, -9.1621620095231666e+00}}
+                },
+                // Component #6
+                {
+                    {{-2.7011359673979054e+06,  8.2180193078306376e+05, -9.6658466651702445e+04,  5.6226808449705832e+03, -1.8772212765456376e+02,  3.8617748667187937e+00}},
+                    {{-5.2076889316070534e+05,  1.5949629384340727e+05, -1.8999044900140336e+04,  1.1022106651944848e+03, -3.4436466136411170e+01,  7.4376238285956164e+00}},
+                    {{ 6.4766725322662554e+04, -2.6994996461319020e+04,  2.0876601827129484e+03,  7.6609049415560705e+01,  2.6995934389534368e+00, -1.6338670795256700e+01}}
+                }
+                }};
+
+            // Tracking/TrkFitter/TrkGaussianSumFilterUtils/Data/GeantSim_GT01_cdf_nC6_O5.par
+            constexpr static Acts::AtlasBetheHeitlerApprox<6, 5>::Data GT01_cdf_cmps6_order5_data = {{
+                // Component #1
+                {
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  3.4177030972717031e+00, -4.5023370509744884e+00}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -3.9457052964315942e+00, -2.0763454756086683e+00}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -7.1457121881276997e+00, -5.3628979048490102e+00}}
+                },
+                // Component #2
+                {
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  1.1451687460393387e+00, -2.8495425297831072e+00}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -5.2948114471475387e+00, -9.8180069037555190e-02}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -6.2682749645404909e+00, -3.1860384549381653e+00}}
+                },
+                // Component #3
+                {
+                    {{-1.6121439489178472e+04,  1.5384843175364240e+04, -5.6689338934841298e+03,  9.8861606625107947e+02, -7.4716003818930389e+01, -3.8665489218520077e-04}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -5.3892009434433490e+00,  1.6646106984668607e+00}},
+                    {{ 8.5418865316008014e+04, -8.4729845210357686e+04,  3.2509874328900842e+04, -6.0360232287805875e+03,  5.4703207243206543e+02, -2.3269764393538701e+01}}
+                },
+                // Component #4
+                {
+                    {{-2.1753363459026186e+03,  2.5480537135521458e+03, -1.0800153221381784e+03,  1.9801727578735878e+02, -1.1004696684536585e+01, -1.9122411799639756e+00}},
+                    {{-7.2398222368353381e+04,  7.1692026459293120e+04, -2.7540169230500542e+04,  5.1273037919535100e+03, -4.6741803632344750e+02,  1.9108986234485663e+01}},
+                    {{ 1.1990918855988217e+05, -1.1822072172982995e+05,  4.5191687950925800e+04, -8.3693591975687395e+03,  7.5772428412511772e+02, -3.2747029952012895e+01}}
+                },
+                // Component #5
+                {
+                    {{ 3.9301134316298808e+03, -3.7541418325082136e+03,  1.4425795489191560e+03, -2.9377291122879348e+02,  3.3552924307793113e+01, -3.1130586170893051e+00}},
+                    {{ 0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00,  0.0000000000000000e+00, -6.1138164489828783e+00,  4.6083958462708150e+00}},
+                    {{ 1.5753424210854530e+05, -1.5644030230854396e+05,  6.0286848768357333e+04, -1.1264081605869356e+04,  1.0303907886480429e+03, -4.5437223190174670e+01}}
+                },
+                // Component #6
+                {
+                    {{ 3.2397164095336298e+04, -3.2114134513896468e+04,  1.2294103984600111e+04, -2.2470209645997870e+03,  1.8686345236342740e+02, -5.6709849549599838e+00}},
+                    {{-5.3617340636071443e+04,  5.4302846581616992e+04, -2.1435558635721187e+04,  4.1270402937878653e+03, -3.9750537586689597e+02,  2.1789322216626250e+01}},
+                    {{ 1.7215536713492026e+05, -1.7277470681531032e+05,  6.7174146724203878e+04, -1.2602759089486393e+04,  1.1522785437972470e+03, -5.3827210284314098e+01}}
+                }
+                }};
+
+            return Acts::AtlasBetheHeitlerApprox<6, 5>(
+                LT01_cdf_cmps6_order5_data, GT01_cdf_cmps6_order5_data, true, true);
+        };
+
+        //gsf parameters
+        // Acts::AtlasBetheHeitlerApprox<6, 5> betheHeitlerApprox = makeBetheHeitlerApprox();
+        Acts::AtlasBetheHeitlerApprox<6, 5> betheHeitlerApprox = Acts::makeDefaultBetheHeitlerApprox();
+        double weightCutoff = 1.0e-4;
+        Acts::ComponentMergeMethod mergeMethod = Acts::ComponentMergeMethod::eMaxWeight;
+        ActsHelper::MixtureReductionAlgorithm reductionAlg = ActsHelper::MixtureReductionAlgorithm::KLDistance;
+
+        int _nEvt;
+        const double _FCT = 2.99792458E-4;
+        const std::array<Acts::BoundIndices, 6> writeout_indices{
+            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
+            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
+            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
+};
+
+#endif // RecActsTrackFitting_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.cpp
new file mode 100644
index 00000000..5f0f2b91
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.cpp
@@ -0,0 +1,134 @@
+// dependence
+#include "RecActsTrackParamsEstimation.h"
+
+DECLARE_COMPONENT(RecActsTrackParamsEstimation)
+
+RecActsTrackParamsEstimation::RecActsTrackParamsEstimation(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsTrackParamsEstimation::initialize()
+{
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    info() << "Assume Particle: " << m_particle.value() << endmsg;
+    if(m_particle.value() == "muon"){
+        particleHypothesis = Acts::ParticleHypothesis::muon();
+    }
+    else if(m_particle.value() == "pion"){
+        particleHypothesis = Acts::ParticleHypothesis::pion();
+    }
+    else if(m_particle.value() == "electron"){
+        particleHypothesis = Acts::ParticleHypothesis::electron();
+    }
+    else if(m_particle.value() == "kaon"){
+        particleHypothesis = Acts::ParticleHypothesis::kaon();
+    }
+    else if(m_particle.value() == "proton"){
+        particleHypothesis = Acts::ParticleHypothesis::proton();
+    }
+    else if(m_particle.value() == "photon"){
+        particleHypothesis = Acts::ParticleHypothesis::photon();
+    }
+    else if(m_particle.value() == "geantino"){
+        particleHypothesis = Acts::ParticleHypothesis::geantino();
+    }
+    else if(m_particle.value() == "chargedgeantino"){
+        particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
+    }
+    else{
+        info() << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
+                                            << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
+                                            << particleNames[6] << ", " << particleNames[7] << endmsg;
+        error() << "Unsupported particle name " << m_particle.value() << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    acts_field_value = Acts::Vector3(0., 0., m_field.value() * _FCT);
+    bFieldMin = 0.1 * m_field.value() * _FCT * Acts::UnitConstants::T;
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackParamsEstimation::execute()
+{
+    _nEvt++;
+    // DEBUG
+    // info() << "DEBUG: print first seed" << endmsg;
+    // auto seed = recActsSvc->GetSeed(0);
+    // for (const auto& sp : seed.sp()) {
+    //     info() << "seed: x=" << sp->x() << " y=" << sp->y() << " z=" << sp->z() << endmsg;
+    // }
+    auto trackParameters = recActsSvc->TrackParameters();
+    auto seeds = recActsSvc->Seeds();
+    auto selectedSeeds = recActsSvc->SelectedSeeds();
+
+    ActsHelper::SimSeedContainer currentSeed;
+    std::vector<Acts::BoundTrackParameters> initialParameters;
+
+    auto trackingGeometry = recActsSvc->Geometry();
+    if (!trackingGeometry) {
+        error() << "Tracking geometry is not set" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc->chronoStart("Track Params Estimation");
+
+    ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
+
+    for (std::size_t iseed = 0; iseed < seeds->size(); ++iseed)
+    {
+        const auto& seed = seeds->at(iseed);
+        const auto bottomSP = seed.sp().front();
+        const auto& sourceLink = bottomSP->sourceLinks()[0];
+        const Acts::Surface* surface = surfaceAccessor(sourceLink);
+        if (surface == nullptr) {
+            debug() << "Surface from source link is not found in the tracking geometry: iseed " << iseed << endmsg;
+            continue;
+        }
+
+        auto optParams = Acts::estimateTrackParamsFromSeed(
+            geoContext, seed.sp().begin(), seed.sp().end(), *surface, acts_field_value, bFieldMin);
+        
+        if (!optParams.has_value()) {
+            debug() << "Estimation of track parameters for seed " << iseed << " failed." << endmsg;
+            continue;
+        }
+
+        const auto& params = optParams.value();
+        Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
+        for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
+            double sigma = initialSigmas[i];
+            sigma += abs(initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP]);
+            double var = sigma * sigma;
+            // var *= noTimeVarInflation.value();
+            if (i == Acts::eBoundTime && !bottomSP->t().has_value()) { var *= noTimeVarInflation.value(); }
+            var *= initialVarInflation.value()[i];
+            
+            cov(i, i) = var;
+        }
+        initialParameters.emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
+        currentSeed.push_back(seed);
+
+        debug() << "A track with p=" << -1 / params[Acts::eBoundQOverP] << " GeV is estimated for event " << _nEvt << "!" << endmsg;
+    }
+
+    trackParameters->push_back(initialParameters);
+    selectedSeeds->push_back(currentSeed);
+    chronoStatSvc->chronoStop("Track Params Estimation");
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackParamsEstimation::finalize()
+{
+    return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.h
new file mode 100644
index 00000000..70616854
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackParamsEstimation.h
@@ -0,0 +1,80 @@
+#ifndef RecActsTrackParamsEstimation_H
+#define RecActsTrackParamsEstimation_H
+
+// gaudi framework
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// Acts
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/Definitions/Units.hpp"
+#include "Acts/EventData/ParticleHypothesis.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/MagneticField/ConstantBField.hpp"
+#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
+#include "Acts/MagneticField/MagneticFieldProvider.hpp"
+#include "Acts/Seeding/EstimateTrackParamsFromSeed.hpp"
+#include "Acts/Seeding/Seed.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Result.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+class RecActsTrackParamsEstimation : public GaudiAlgorithm
+{
+    public:
+        RecActsTrackParamsEstimation(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // properties
+        Gaudi::Property<std::string> m_particle{this, "AssumeParticle", "muon"};
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 1, 1, 1, 1}};
+        Gaudi::Property<double> noTimeVarInflation{this, "noTimeVarInflation", 1.};
+
+        // services
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        // param estimate configuration
+        // Params: loc0, loc1, phi, theta, qop, time
+        // double noTimeVarInflation = 100.;
+        std::array<double, 6> initialSigmas = {
+            5 * Acts::UnitConstants::um,
+            5 * Acts::UnitConstants::um,
+            2e-2 * Acts::UnitConstants::degree,
+            2e-2 * Acts::UnitConstants::degree,
+            0 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
+            1 * Acts::UnitConstants::s
+        };
+        std::array<double, 6> initialSimgaQoverPCoefficients = {
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0.1,
+            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)
+        };
+        
+        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
+
+        // configs to build acts geometry
+        int _nEvt;
+        const double _FCT = 2.99792458E-4;
+        Acts::Vector3 acts_field_value;
+        double bFieldMin;
+        Acts::GeometryContext geoContext;
+        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
+
+};
+
+#endif // RecActsTrackParamsEstimation_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
new file mode 100644
index 00000000..87ce36e5
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
@@ -0,0 +1,401 @@
+// dependence
+#include "RecActsTrackReFitting.h"
+
+DECLARE_COMPONENT(RecActsTrackReFitting)
+
+RecActsTrackReFitting::RecActsTrackReFitting(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsTrackReFitting::initialize()
+{
+    // --------------------------------
+    // ---- initialize properties -----
+    // --------------------------------
+
+    info() << "Assume Particle: " << m_particle.value() << endmsg;
+    if(m_particle.value() == "muon"){
+        particleHypothesis = Acts::ParticleHypothesis::muon();
+    }
+    else if(m_particle.value() == "pion"){
+        particleHypothesis = Acts::ParticleHypothesis::pion();
+    }
+    else if(m_particle.value() == "electron"){
+        particleHypothesis = Acts::ParticleHypothesis::electron();
+    }
+    else if(m_particle.value() == "kaon"){
+        particleHypothesis = Acts::ParticleHypothesis::kaon();
+    }
+    else if(m_particle.value() == "proton"){
+        particleHypothesis = Acts::ParticleHypothesis::proton();
+    }
+    else if(m_particle.value() == "photon"){
+        particleHypothesis = Acts::ParticleHypothesis::photon();
+    }
+    else if(m_particle.value() == "geantino"){
+        particleHypothesis = Acts::ParticleHypothesis::geantino();
+    }
+    else if(m_particle.value() == "chargedgeantino"){
+        particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
+    }
+    else{
+        info()  << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
+                                            << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
+                                            << particleNames[6] << ", " << particleNames[7] << endmsg;
+        error() << "Unsupported particle name " << m_particle.value() << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
+    if(!vtx_decoder){
+        info() << "Failed to create vtx_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
+    if(!ITKBarrel_decoder){
+        info() << "Failed to create ITKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
+    if(!ITKEndcap_decoder){
+        info() << "Failed to create ITKEndcap_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
+    if(!tpc_decoder){
+        info() << "Failed to create TPC_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
+    if(!OTKBarrel_decoder){
+        info() << "Failed to create OTKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    } 
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    // --------------------------------
+    // ---- initialize recActsSvc ----
+    // --------------------------------
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    magneticField = std::make_shared<Acts::ConstantBField>(Acts::Vector3(0., 0., m_field.value()*_FCT));
+    fit = ActsHelper::makeKalmanFitterFunction(recActsSvc->Geometry(), magneticField);
+    // fit = ActsHelper::makeGsfFitterFunction(recActsSvc->Geometry(), magneticField); (not working)
+    // fit = ActsHelper::makeGlobalChiSquareFitterFunction(recActsSvc->Geometry(), magneticField); (ok)
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackReFitting::execute()
+{
+    _nEvt++;
+
+    auto trkCol = _outColHdl.createAndPut();
+
+    const edm4hep::TrackCollection* trackCols = nullptr;
+    try { trackCols = _inTrackColHdl.get(); }
+    catch ( GaudiException &e )
+    { debug() << "Collection " << _inTrackColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg; }
+
+    const edm4hep::MCRecoTrackerAssociationCollection* vtxHitAssCols = nullptr;
+    try { vtxHitAssCols = _inVTXAssColHdl.get(); }
+    catch ( GaudiException &e )
+    { debug() << "Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg; }
+
+    std::map<int, int> hitNumbers; 
+
+    hitNumbers[UTIL::ILDDetID::VXD] = 0;
+    hitNumbers[UTIL::ILDDetID::SIT] = 0;
+    hitNumbers[UTIL::ILDDetID::FTD] = 0;
+    hitNumbers[UTIL::ILDDetID::TPC] = 0;
+    hitNumbers[UTIL::ILDDetID::SET] = 0;
+    hitNumbers[UTIL::ILDDetID::ETD] = 0;
+
+    if(trackCols){
+        int Track_count = 0;
+        for(auto track : *trackCols){
+            recActsSvc->Clean();
+            Track_count++;
+            unsigned int nHits = track.trackerHits_size();
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nHits; ++ielem){
+                const edm4hep::TrackerHit& hit = track.getTrackerHits(ielem);
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            auto first_hit = track.getTrackerHits(hit_indices[0]);
+
+            std::vector<edm4hep::SimTrackerHit> first_simHits;
+            for(auto ass : *vtxHitAssCols){
+                if(ass.getRec().getObjectID().collectionID != first_hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==first_hit) first_simHits.push_back(ass.getSim());
+            }
+
+            if(first_simHits.size() == 0){
+                info() << "Skip: Not found VTX simHit in AssociationCollection for track " << Track_count << " at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto first_simhit = first_simHits[0];
+            double px = first_simhit.getMomentum()[0];
+            double py = first_simhit.getMomentum()[1];
+            double pz = first_simhit.getMomentum()[2];
+
+            for(int i_hit : hit_indices){
+                const edm4hep::TrackerHit& hit = track.getTrackerHits(i_hit);
+                auto cellid = hit.getCellID();
+                UTIL::BitField64 encoder(UTIL::ILDCellID0::encoder_string);
+                encoder.setValue(cellid);
+                int subdet = encoder[UTIL::ILDCellID0::subdet];
+                ++hitNumbers[subdet];
+
+                if(subdet==UTIL::ILDDetID::VXD)
+                {
+                    uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
+                    uint64_t m_module  = vtx_decoder->get(cellid, "module");
+
+                    if(m_layer <= 3){
+                        uint64_t acts_volume = VXD_volume_ids[m_layer];
+                        uint64_t acts_layer = 2;
+                        uint64_t acts_sensitive = 1;
+                        bool buildSpacePoint = false;
+                        int moduleType = 1;
+                        double onSurfaceTolerance = 1e-4;
+
+                        if (!recActsSvc->ReadInput(hit,
+                                acts_volume, acts_layer, acts_sensitive,
+                                buildSpacePoint, moduleType, onSurfaceTolerance))
+                            {
+                                info() << "Failed to read VXD hit: layer " << m_layer << ", module " << m_module << endmsg;
+                            }
+                    } else {
+                        uint64_t acts_volume = VXD_volume_ids[4];
+                        uint64_t acts_layer = 2;
+                        uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;;
+
+                        if (!recActsSvc->ReadInput(hit,
+                            acts_volume, acts_layer, acts_sensitive))
+                            {
+                                info() << "Failed to read VXD hit: layer " << m_layer << ", module " << m_module << endmsg;
+                            }
+                    }
+                }
+                else if(subdet==UTIL::ILDDetID::SIT)
+                {
+                    uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
+                    uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
+                    
+                    uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
+                    uint64_t acts_layer = 2;
+                    uint64_t acts_sensitive = m_stave + 1;
+                    if (!recActsSvc->ReadInput(hit,
+                            acts_volume, acts_layer, acts_sensitive))
+                        {
+                            info() << "Failed to read ITKBarrel hit: layer " << m_layer << ", stave " << m_stave << endmsg;
+                        }
+                }
+                else if(subdet==UTIL::ILDDetID::FTD)
+                {
+                    int m_side = ITKEndcap_decoder->get(cellid, "side");
+                    uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
+                    uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
+                    
+                    uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
+                    uint64_t acts_layer = 2;
+                    uint64_t acts_sensitive = m_module + 1;
+                    if (!recActsSvc->ReadInput(hit,
+                            acts_volume, acts_layer, acts_sensitive))
+                        {
+                            info() << "Failed to read ITKEndcap hit: side " << m_side << ", layer " << m_layer << ", module " << m_module << endmsg;
+                        }
+                }
+                else if(subdet==UTIL::ILDDetID::TPC)
+                {
+                    uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
+                    
+                    uint64_t acts_volume = TPC_volume_id;
+                    uint64_t acts_layer = (m_layer + 1) * 2;
+                    uint64_t acts_sensitive = 1;
+                    bool buildSpacePoint = false;
+                    int moduleType = 2;
+                    double onSurfaceTolerance = 1e-4;
+
+                    if (!recActsSvc->ReadInput(hit,
+                            acts_volume, acts_layer, acts_sensitive,
+                            buildSpacePoint, moduleType, onSurfaceTolerance))
+                        {
+                            info() << "Failed to read TPC hit: layer " << m_layer << endmsg;
+                        }
+                }
+                else if(subdet==UTIL::ILDDetID::SET)
+                {
+                    uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
+                    
+                    uint64_t acts_volume = OTK_volume_id;
+                    uint64_t acts_layer = 2;
+                    uint64_t acts_sensitive = m_module + 1;
+                    if (!recActsSvc->ReadInput(hit,
+                            acts_volume, acts_layer, acts_sensitive))
+                        {
+                            info() << "Failed to read OTKBarrel hit: module " << m_module << endmsg;
+                        }
+                }
+                else
+                {
+                    debug() << "not supported subdetector: " << subdet << endmsg;
+                }
+            }
+
+            double p  = sqrt(px*px + py*py + pz*pz);
+            double phi = atan2(py, px);
+            double theta = atan2(sqrt(px*px + py*py), pz);
+            double qop = -1 / p;
+
+            Acts::BoundVector params = Acts::BoundVector::Zero();
+            Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
+            ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*recActsSvc->Geometry()};
+
+            // (TODO) assume the first hit is the first measurement
+            auto first_hit_meas =  recActsSvc->Measurements()->at(0);
+            auto cur_meas_param = std::get<1>(first_hit_meas).parameters();
+            auto cur_meas_sl = std::get<1>(first_hit_meas).sourceLink();
+            const Acts::Surface* surface = surfaceAccessor(cur_meas_sl);
+
+            params[Acts::eBoundLoc0]   = cur_meas_param[Acts::eBoundLoc0];
+            params[Acts::eBoundLoc1]   = cur_meas_param[Acts::eBoundLoc1];
+            params[Acts::eBoundPhi]    = phi;
+            params[Acts::eBoundTheta]  = theta;
+            params[Acts::eBoundQOverP] = qop;
+            params[Acts::eBoundTime]   = 0;
+
+            for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
+                double sigma = initialSigmas[i];
+                sigma += abs(initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP]);
+                double var = sigma * sigma;
+                // var *= noTimeVarInflation.value();
+                var *= initialVarInflation.value()[i];
+                
+                cov(i, i) = var;
+            }
+
+            recActsSvc->InitialParameters()->emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
+            
+            auto measurements = recActsSvc->Measurements();
+            auto trackSourceLinks = recActsSvc->SourceLinks();
+            auto initialParams = recActsSvc->InitialParameters();
+
+            // info() << "debug: event " << _nEvt << ", track " << Track_count << endmsg;
+            // info() << "measurements size: " << measurements->size() << endmsg;
+            // info() << "trackSourceLinks size: " << trackSourceLinks->size() << endmsg;
+            // info() << "initialParams size: " << initialParams->size() << endmsg;
+
+            if (initialParams->size() == 0) {
+                info() << "No initial parameters found for track " << Track_count << " at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
+            ActsHelper::PassThroughCalibrator pcalibrator;
+            ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
+            ActsHelper::TrackFitterFunction::GeneralFitterOptions options{
+                geoContext, magFieldContext, calibContext, pSurface.get(), Acts::PropagatorPlainOptions()};
+            
+            auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
+            auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
+            ActsHelper::TrackContainer tracks(trackContainer, trackStateContainer);
+
+            std::vector<Acts::SourceLink> cur_trackSourceLinks;
+            for (const auto& sourceLink : *trackSourceLinks) {
+                cur_trackSourceLinks.push_back(Acts::SourceLink{sourceLink});
+            }
+
+            auto result = (*fit)(cur_trackSourceLinks, initialParams->at(0), options, calibrator, tracks);
+            if (!result.ok()) {
+                info() << "Track fit failed for track " << Track_count << " at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            // std::stringstream ss;
+            // trackStateContainer->statistics().toStream(ss);
+            // debug() << ss.str() << endmsg;
+
+            ActsHelper::ConstTrackContainer constTracks
+            {
+                std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer)),
+                std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer))
+            };
+
+            if (constTracks.size() == 0) { 
+                info() << "No tracks fitted for track " << Track_count << " at event " << _nEvt << endmsg;
+                continue; 
+            } else if (constTracks.size() > 1) {
+                info() << "Number of tracks fitted: " << constTracks.size() << " for track " << Track_count << " at event " << _nEvt << endmsg;
+            }
+
+            for (const auto& cur_track : constTracks)
+            {
+                auto writeout_track = trkCol->create();
+
+                writeout_track.setChi2(cur_track.chi2());
+                writeout_track.setNdf(cur_track.nDoF());
+                writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
+                writeout_track.setDEdxError(0);
+
+                // TODO: covmatrix need to be converted
+                std::array<float, 21> writeout_covMatrix;
+                auto cur_track_covariance = cur_track.covariance();
+                for (int i = 0; i < 6; i++) {
+                    for (int j = 0; j < 6-i; j++) {
+                        writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
+                    }
+                }
+                // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
+                // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
+                edm4hep::TrackState writeout_trackState{
+                    1, // location: AtOther
+                    cur_track.loc0() / Acts::UnitConstants::mm, // d0
+                    cur_track.phi(), // phi
+                    cur_track.qOverP() * _FCT * m_field / sin(cur_track.theta()) , // omega = qop * sin(theta) * _FCT * bf
+                    cur_track.loc1() / Acts::UnitConstants::mm, // z0
+                    1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
+                    cur_track.time(), // time
+                    ::edm4hep::Vector3f(0, 0, 0), // referencePoint
+                    writeout_covMatrix
+                };
+
+                debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " GeV!" << endmsg;
+                writeout_track.addToTrackStates(writeout_trackState);
+            }
+        }
+    }
+
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTrackReFitting::finalize()
+{
+    return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h
new file mode 100644
index 00000000..97f091c5
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h
@@ -0,0 +1,153 @@
+#ifndef RecActsTrackReFitting_H
+#define RecActsTrackReFitting_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+#include "UTIL/ILDConf.h"
+#include "DataHelper/Navigation.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// DD4hep
+#include "DD4hep/Detector.h"
+#include "DDRec/ISurface.h"
+#include "DDRec/SurfaceManager.h"
+
+// edm4hep
+#include "edm4hep/MCParticle.h"
+#include "edm4hep/MCParticleCollection.h"
+#include "edm4hep/TrackerHitCollection.h"
+#include "edm4hep/SimTrackerHitCollection.h"
+#include "edm4hep/EventHeaderCollection.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+// Acts
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/EventData/GenericBoundTrackParameters.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/TrackProxy.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Result.hpp"
+
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+namespace Acts
+{
+    class MagneticFieldProvider;
+    class TrackingGeometry;
+}
+
+class RecActsTrackReFitting : public GaudiAlgorithm
+{
+    public:
+        RecActsTrackReFitting(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // Input collections
+        DataHandle<edm4hep::TrackCollection> _inTrackColHdl{"CompleteTracks", Gaudi::DataHandle::Reader, this};
+
+        // associations
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+
+        // Output collections
+        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSRefitTracks", Gaudi::DataHandle::Writer, this};
+
+        Gaudi::Property<std::string> m_particle{this, "AssumeParticle", "muon"};
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 1, 1, 1, 1}};
+        Gaudi::Property<double> noTimeVarInflation{this, "noTimeVarInflation", 1.};
+        
+        // services
+        // SmartIF<IGeomSvc> m_geosvc;
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+        
+        // Decoders
+        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
+
+        std::shared_ptr<ActsHelper::TrackFitterFunction> fit;
+        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
+        // std::shared_ptr<ActsHelper::MeasurementCalibrator> m_calibrator;
+
+        // globalChiSquareFitter config
+        bool multipleScattering = false;
+        bool energyLoss = false;
+        Acts::FreeToBoundCorrection freeToBoundCorrection;
+        std::size_t nUpdateMax = 5;
+        double relChi2changeCutOff = 1e-7;
+
+        // context
+        Acts::GeometryContext geoContext;
+        Acts::MagneticFieldContext magFieldContext;
+        Acts::CalibrationContext calibContext;
+
+        // double noTimeVarInflation = 10.;
+        std::array<double, 6> initialSigmas = {
+            5 * Acts::UnitConstants::um,
+            5 * Acts::UnitConstants::um,
+            2e-2 * Acts::UnitConstants::degree,
+            2e-2 * Acts::UnitConstants::degree,
+            1e-1 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
+            1 * Acts::UnitConstants::s
+        };
+        std::array<double, 6> initialSimgaQoverPCoefficients = {
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            1e-1,
+            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)
+        };
+        
+        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
+
+        // constants
+        const double _FCT = 2.99792458E-4;
+        uint64_t TPC_volume_id = 43;
+        uint64_t OTK_volume_id = 45;
+        std::vector<uint64_t> VXD_volume_ids{26, 27, 28, 29, 30};
+        std::vector<uint64_t> ITKBarrel_volume_ids{33, 36, 39};
+        std::vector<uint64_t> ITKEndcap_positive_volume_ids{34, 37, 40, 41};
+        std::vector<uint64_t> ITKEndcap_negative_volume_ids{32, 15, 10, 8};
+        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
+        const std::array<Acts::BoundIndices, 6> writeout_indices{
+            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
+            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
+            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
+        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
+        int _nEvt;
+};
+
+#endif // RecActsTrackReFitting_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
new file mode 100644
index 00000000..1418bc48
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
@@ -0,0 +1,473 @@
+// dependence
+#include "RecActsTruthInput.h"
+
+DECLARE_COMPONENT(RecActsTruthInput)
+
+RecActsTruthInput::RecActsTruthInput(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsTruthInput::initialize()
+{
+    // --------------------------------
+    // ---- initialize properties -----
+    // --------------------------------
+
+    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
+    if(!vtx_decoder){
+        info() << "Failed to create vtx_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
+    if(!ITKBarrel_decoder){
+        info() << "Failed to create ITKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
+    if(!ITKEndcap_decoder){
+        info() << "Failed to create ITKEndcap_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
+    if(!tpc_decoder){
+        info() << "Failed to create TPC_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
+    if(!OTKBarrel_decoder){
+        info() << "Failed to create OTKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    } 
+    
+    // --------------------------------
+    // ---- initialize recActsSvc ----
+    // --------------------------------
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+    
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTruthInput::execute()
+{
+    _nEvt++;
+
+    recActsSvc->Clean();
+
+    if (useVTX.value()) {
+        if (!ReadInputVTX()) {
+            debug() << "VTX input failed at event " << _nEvt << endmsg;
+        }
+    }
+    
+    if (useITKBarrel.value()) {
+        if (!ReadInputITKBarrel()) {
+            debug() << "ITKBarrel input failed at event " << _nEvt << endmsg;
+        }
+    }
+    
+    if (useITKEndcap.value()) {
+        if (!ReadInputITKEndcap()) {
+            debug() << "ITKEndcap input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (useTPC.value()) {
+        if (!ReadInputTPC()) {
+            debug() << "TPC input failed at event " << _nEvt << endmsg;
+        }
+    }
+
+    if (useOTKBarrel.value()) {
+        if (!ReadInputOTKBarrel()) {
+            debug() << "OTKBarrel input failed at event " << _nEvt << endmsg;
+        }
+    }
+    
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTruthInput::finalize()
+{
+    return StatusCode::SUCCESS;
+}
+
+bool RecActsTruthInput::ReadInputVTX()
+{
+    const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
+    const edm4hep::SimTrackerHitCollection* SimhitVTXCol = nullptr;
+    const edm4hep::MCRecoTrackerAssociationCollection* vtxAssCol = nullptr;
+
+    try {
+        hitVTXCol = _inVTXTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        SimhitVTXCol = _inVTXColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Sim Collection " << _inVTXColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        vtxAssCol = _inVTXAssColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Association Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitVTXCol && SimhitVTXCol)
+    {
+        int nelem = hitVTXCol->size();
+
+        for(int ielem = 0; ielem < nelem; ++ielem){
+            auto hit = hitVTXCol->at(ielem);
+
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *vtxAssCol){
+                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+            }
+
+            if(simHits.size() == 0){
+                info() << "Skip: Not found VTX simHit in AssociationCollection at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto simhit = simHits[0];
+
+            if (simhit.isProducedBySecondary()) {
+                continue;
+            }
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
+            uint64_t m_module  = vtx_decoder->get(cellid, "module");
+            uint64_t m_sensor  = vtx_decoder->get(cellid, "sensor");
+
+            if(m_layer <= 3){
+                uint64_t acts_volume = VXD_volume_ids[m_layer];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = 1;
+                bool buildSpacePoint = true;
+                int moduleType = 1;
+                double onSurfaceTolerance = 1e-4;
+
+                if (!recActsSvc->ReadInput(hit,
+                        acts_volume, acts_layer, acts_sensitive,
+                        buildSpacePoint, moduleType, onSurfaceTolerance))
+                    { return false; }
+            } else {
+                uint64_t acts_volume = VXD_volume_ids[4];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;
+                bool buildSpacePoint = true;
+                int moduleType = 0;
+                double onSurfaceTolerance = 1e-4;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive,
+                    buildSpacePoint, moduleType, onSurfaceTolerance))
+                    { return false; }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsTruthInput::ReadInputITKBarrel()
+{
+    const edm4hep::TrackerHitCollection* hitITKBarrelCol = nullptr;
+    const edm4hep::SimTrackerHitCollection* SimhitITKBarrelCol = nullptr;
+    const edm4hep::MCRecoTrackerAssociationCollection* itkBarrelAssCol = nullptr;
+
+    try {
+        hitITKBarrelCol = _inITKBarrelTrackHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Collection " << _inITKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        SimhitITKBarrelCol = _inITKBarrelColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Sim Collection " << _inITKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        itkBarrelAssCol = _inITKBarrelAssColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Association Collection " << _inITKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitITKBarrelCol)
+    {
+        int nelem = hitITKBarrelCol->size();
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitITKBarrelCol->at(ielem);
+
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *itkBarrelAssCol){
+                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+            }
+            
+            if(simHits.size() == 0){
+                info() << "Skip: Not found ITKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto simhit = simHits[0];
+
+            if (simhit.isProducedBySecondary()) {
+                continue;
+            }
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
+            uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
+
+            uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = m_stave + 1;
+            bool buildSpacePoint = true;
+            int moduleType = 0;
+            double onSurfaceTolerance = 1e-4;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsTruthInput::ReadInputITKEndcap()
+{
+    const edm4hep::TrackerHitCollection* hitITKEndcapCol = nullptr;
+    const edm4hep::SimTrackerHitCollection* SimhitITKEndcapCol = nullptr;
+    const edm4hep::MCRecoTrackerAssociationCollection* itkEndcapAssCol = nullptr;
+
+    try {
+        hitITKEndcapCol = _inITKEndcapTrackHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Collection " << _inITKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        SimhitITKEndcapCol = _inITKEndcapColHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Sim Collection " << _inITKEndcapColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        itkEndcapAssCol = _inITKEndcapAssColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Association Collection " << _inITKEndcapAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if (hitITKEndcapCol)
+    {
+        int nelem = hitITKEndcapCol->size();
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitITKEndcapCol->at(ielem);
+
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *itkEndcapAssCol){
+                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+            }
+
+            if(simHits.size() == 0){
+                info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto simhit = simHits[0];
+
+            if (simhit.isProducedBySecondary()) {
+                continue;
+            }
+
+            auto cellid = hit.getCellID();
+            int m_side = ITKEndcap_decoder->get(cellid, "side");
+            uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
+            uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
+
+            uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = m_module + 1;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive))
+                { return false; }
+        }
+    }
+    
+    return true;
+}
+
+
+bool RecActsTruthInput::ReadInputTPC()
+{
+    const edm4hep::TrackerHitCollection* hitTPCCol = nullptr;
+    const edm4hep::SimTrackerHitCollection* SimhitTPCCol = nullptr;
+    const edm4hep::MCRecoTrackerAssociationCollection* tpcAssCol = nullptr;
+
+    try {
+        hitTPCCol = _inTPCTrackHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Collection " << _inTPCTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        SimhitTPCCol = _inTPCColHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Sim Collection " << _inTPCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        tpcAssCol = _inTPCAssColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Association Collection " << _inTPCAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitTPCCol)
+    {
+        int nelem = hitTPCCol->size();
+
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitTPCCol->at(ielem);
+
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *tpcAssCol){
+                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+            }
+
+            if(simHits.size() == 0){
+                info() << "Skip: Not found TPC simHit in AssociationCollection at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto simhit = simHits[0];
+
+            if (simhit.isProducedBySecondary()) {
+                continue;
+            }
+
+            auto cellid = hit.getCellID();
+            uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
+
+            uint64_t acts_volume = TPC_volume_id;
+            uint64_t acts_layer = (m_layer + 1) * 2;
+            uint64_t acts_sensitive = 1;
+            bool buildSpacePoint = false;
+            int moduleType = 2;
+            double onSurfaceTolerance = 1e-4;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive,
+                buildSpacePoint, moduleType, onSurfaceTolerance))
+                { return false; }
+        }
+    }
+
+    return true;
+}
+
+bool RecActsTruthInput::ReadInputOTKBarrel()
+{
+    const edm4hep::TrackerHitCollection* hitOTKBarrelCol = nullptr;
+    const edm4hep::SimTrackerHitCollection* SimhitOTKBarrelCol = nullptr;
+    const edm4hep::MCRecoTrackerAssociationCollection* otkBarrelAssCol = nullptr;
+
+    try {   
+        hitOTKBarrelCol = _inOTKBarrelTrackHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Collection " << _inOTKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        SimhitOTKBarrelCol = _inOTKBarrelColHdl.get();
+    } catch (GaudiException& e) {
+        debug() << "Sim Collection " << _inOTKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    try {
+        otkBarrelAssCol = _inOTKBarrelAssColHdl.get();
+    } catch (GaudiException& e) {
+        fatal() << "Association Collection " << _inOTKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+        return false;
+    }
+
+    if(hitOTKBarrelCol)
+    {
+        int nelem = hitOTKBarrelCol->size();
+        
+        for (int ielem = 0; ielem < nelem; ++ielem)
+        {
+            auto hit = hitOTKBarrelCol->at(ielem);
+            
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *otkBarrelAssCol){
+                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+            }
+
+            if(simHits.size() == 0){
+                info() << "Skip: Not found OTKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto simhit = simHits[0];
+
+            if (simhit.isProducedBySecondary()) {
+                continue;
+            }
+
+            auto cellid = hit.getCellID();
+            uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
+
+            uint64_t acts_volume = OTK_volume_id;
+            uint64_t acts_layer = 2;
+            uint64_t acts_sensitive = m_module + 1;
+
+            if (!recActsSvc->ReadInput(hit,
+                acts_volume, acts_layer, acts_sensitive))
+                { return false; }
+        }
+    }
+    return true;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
new file mode 100644
index 00000000..bd390199
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
@@ -0,0 +1,100 @@
+#ifndef RecActsTruthInput_H
+#define RecActsTruthInput_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+// #include "DetInterface/IGeomSvc.h"
+
+// DD4hep
+#include "DD4hep/Detector.h"
+#include "DDRec/ISurface.h"
+#include "DDRec/SurfaceManager.h"
+
+#include "UTIL/ILDConf.h"
+#include "DataHelper/Navigation.h"
+
+// edm4hep
+#include "edm4hep/MCParticle.h"
+#include "edm4hep/MCParticleCollection.h"
+#include "edm4hep/TrackerHitCollection.h"
+#include "edm4hep/SimTrackerHitCollection.h"
+#include "edm4hep/EventHeaderCollection.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+
+
+class RecActsTruthInput : public GaudiAlgorithm
+{
+    public:
+        RecActsTruthInput(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // Input collections
+        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKBarrelTrackHdl{"ITKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKEndcapTrackHdl{"ITKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inOTKBarrelTrackHdl{"OTKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inTPCTrackHdl{"TPCTrackerHits", Gaudi::DataHandle::Reader, this};        
+
+        DataHandle<edm4hep::SimTrackerHitCollection> _inVTXColHdl{"VXDCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inITKBarrelColHdl{"ITKBarrelCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inITKEndcapColHdl{"ITKEndcapCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inOTKBarrelColHdl{"OTKBarrelCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inTPCColHdl{"TPCCollection", Gaudi::DataHandle::Reader, this};
+
+        // associations
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKBarrelAssColHdl{"ITKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKEndcapAssColHdl{"ITKEndcapTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inOTKBarrelAssColHdl{"OTKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inTPCAssColHdl{"TPCTrackerHitAss", Gaudi::DataHandle::Reader, this};
+        
+        // properties
+        Gaudi::Property<bool> useVTX{this, "useVTX", true};
+        Gaudi::Property<bool> useITKBarrel{this, "useITKBarrel", true};
+        Gaudi::Property<bool> useITKEndcap{this, "useITKEndcap", true};
+        Gaudi::Property<bool> useTPC{this, "useTPC", true};
+        Gaudi::Property<bool> useOTKBarrel{this, "useOTKBarrel", true};
+
+        // read input
+        bool ReadInputVTX();
+        bool ReadInputITKBarrel();
+        bool ReadInputITKEndcap();
+        bool ReadInputTPC();
+        bool ReadInputOTKBarrel();
+
+        // services
+        // SmartIF<IGeomSvc> m_geosvc;
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
+
+        // constants
+        int _nEvt;
+        uint64_t TPC_volume_id = 43;
+        uint64_t OTK_volume_id = 45;
+        std::vector<uint64_t> VXD_volume_ids{26, 27, 28, 29, 30};
+        std::vector<uint64_t> ITKBarrel_volume_ids{33, 36, 39};
+        std::vector<uint64_t> ITKEndcap_positive_volume_ids{34, 37, 40, 41};
+        std::vector<uint64_t> ITKEndcap_negative_volume_ids{32, 15, 10, 8};
+        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
+        
+};
+
+#endif // RecActsTruthInput_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
new file mode 100644
index 00000000..8a58b67c
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
@@ -0,0 +1,796 @@
+// dependence
+#include "RecActsTruthTracking.h"
+
+DECLARE_COMPONENT(RecActsTruthTracking)
+
+RecActsTruthTracking::RecActsTruthTracking(const std::string& name, ISvcLocator* svcLoc)
+    : GaudiAlgorithm(name, svcLoc)
+{
+}
+
+StatusCode RecActsTruthTracking::initialize()
+{
+    // --------------------------------
+    // ---- initialize properties -----
+    // --------------------------------
+
+    info() << "Assume Particle: " << m_particle.value() << endmsg;
+    if(m_particle.value() == "muon"){
+        particleHypothesis = Acts::ParticleHypothesis::muon();
+    }
+    else if(m_particle.value() == "pion"){
+        particleHypothesis = Acts::ParticleHypothesis::pion();
+    }
+    else if(m_particle.value() == "electron"){
+        particleHypothesis = Acts::ParticleHypothesis::electron();
+    }
+    else if(m_particle.value() == "kaon"){
+        particleHypothesis = Acts::ParticleHypothesis::kaon();
+    }
+    else if(m_particle.value() == "proton"){
+        particleHypothesis = Acts::ParticleHypothesis::proton();
+    }
+    else if(m_particle.value() == "photon"){
+        particleHypothesis = Acts::ParticleHypothesis::photon();
+    }
+    else if(m_particle.value() == "geantino"){
+        particleHypothesis = Acts::ParticleHypothesis::geantino();
+    }
+    else if(m_particle.value() == "chargedgeantino"){
+        particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
+    }
+    else{
+        info()  << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
+                                            << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
+                                            << particleNames[6] << ", " << particleNames[7] << endmsg;
+        error() << "Unsupported particle name " << m_particle.value() << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    // m_geosvc = service<IGeomSvc>("GeomSvc");
+    // if (!m_geosvc) {
+    //     error() << "Failed to get GeomSvc" << endmsg;
+    //     return StatusCode::FAILURE;
+    // }
+
+    // m_vtx_surfaces = m_geosvc->getSurfaceMap("VXD");
+    // debug() << "VXD Surface map size: " << m_vtx_surfaces->size() << endmsg;
+
+    // m_ITKBarrel_surfaces = m_geosvc->getSurfaceMap("ITKBarrel");
+    // debug() << "ITKBarrel Surface map size: " << m_ITKBarrel_surfaces->size() << endmsg;
+
+    // m_ITKEndcap_surfaces = m_geosvc->getSurfaceMap("ITKEndcap");
+    // debug() << "ITKEndcap Surface map size: " << m_ITKEndcap_surfaces->size() << endmsg;
+
+    // m_tpc_surfaces = m_geosvc->getSurfaceMap("TPC");
+    // debug() << "TPC Surface map size: " << m_tpc_surfaces->size() << endmsg;
+
+    // m_OTKBarrel_surfaces = m_geosvc->getSurfaceMap("OTKBarrel");
+    // debug() << "OTK Barrel Surface map size: " << m_OTKBarrel_surfaces->size() << endmsg;
+
+    // vtx_decoder = m_geosvc->getDecoder("VXDCollection");
+    // if(!vtx_decoder){
+    //     return StatusCode::FAILURE;
+    // }
+
+    // ITKBarrel_decoder = m_geosvc->getDecoder("ITKBarrelCollection");
+    // if(!ITKBarrel_decoder){
+    //     return StatusCode::FAILURE;
+    // }
+
+    // tpc_decoder = m_geosvc->getDecoder("TPCCollection");
+    // if(!tpc_decoder){
+    //     return StatusCode::FAILURE;
+    // }
+
+    // ITKEndcap_decoder = m_geosvc->getDecoder("ITKEndcapCollection");
+    // if(!ITKEndcap_decoder){
+    //     return StatusCode::FAILURE;
+    // }
+
+    // OTKBarrel_decoder = m_geosvc->getDecoder("OTKBarrelCollection");
+    // if(!OTKBarrel_decoder){
+    //     return StatusCode::FAILURE;
+    // }
+
+    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
+    if(!vtx_decoder){
+        info() << "Failed to create vtx_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
+    if(!ITKBarrel_decoder){
+        info() << "Failed to create ITKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
+    if(!ITKEndcap_decoder){
+        info() << "Failed to create ITKEndcap_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
+    if(!tpc_decoder){
+        info() << "Failed to create TPC_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
+    if(!OTKBarrel_decoder){
+        info() << "Failed to create OTKBarrel_decoder" << endmsg;
+        return StatusCode::FAILURE;
+    } 
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    // --------------------------------
+    // ---- initialize recActsSvc ----
+    // --------------------------------
+
+    recActsSvc = service<IRecActsSvc>("RecActsSvc");
+    if (!recActsSvc) {
+        error() << "Failed to get RecActsSvc" << endmsg;
+        return StatusCode::FAILURE;
+    }
+
+    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
+
+    magneticField = std::make_shared<Acts::ConstantBField>(Acts::Vector3(0., 0., m_field.value()*_FCT));
+    fit = ActsHelper::makeKalmanFitterFunction(recActsSvc->Geometry(), magneticField);
+    // fit = ActsHelper::makeGsfFitterFunction(recActsSvc->Geometry(), magneticField); (not working)
+    // fit = ActsHelper::makeGlobalChiSquareFitterFunction(recActsSvc->Geometry(), magneticField); (ok)
+
+    _nEvt = -1;
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTruthTracking::execute()
+{
+    _nEvt++;
+
+    chronoStatSvc->chronoStart("Truth Tracking");
+
+    auto trkCol = _outColHdl.createAndPut();
+
+    const edm4hep::MCParticleCollection* mcCols = nullptr;
+    try { mcCols = _inMCColHdl.get(); }
+    catch ( GaudiException &e ) {
+        debug() << "Collection " << _inMCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+    }
+
+    if(mcCols){
+        for(const auto& mc : *mcCols){
+            if (mc.getGeneratorStatus() != 1){
+                info() << "MCParticle " << mc.getPDG() << " is not primary at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            recActsSvc->Clean();
+            debug() << "Processing MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
+
+            if(!ReadHits(mc)){
+                info() << "Fit MCParticle " << mc.getPDG() << " failed at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto measurements = recActsSvc->Measurements();
+            auto trackSourceLinks = recActsSvc->SourceLinks();
+            auto initialParams = recActsSvc->InitialParameters();
+
+            if (initialParams->size() == 0) {
+                info() << "No initial parameters found for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
+            ActsHelper::PassThroughCalibrator pcalibrator;
+            ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
+            ActsHelper::TrackFitterFunction::GeneralFitterOptions options{
+                geoContext, magFieldContext, calibContext, pSurface.get(), Acts::PropagatorPlainOptions()};
+            
+            auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
+            auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
+            ActsHelper::TrackContainer tracks(trackContainer, trackStateContainer);
+
+            std::vector<Acts::SourceLink> cur_trackSourceLinks;
+            for (const auto& sourceLink : *trackSourceLinks) {
+                cur_trackSourceLinks.push_back(Acts::SourceLink{sourceLink});
+            }
+            auto result = (*fit)(cur_trackSourceLinks, initialParams->at(0), options, calibrator, tracks);
+            if (!result.ok()) {
+                info() << "Track fit failed for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
+                continue;
+            }
+
+            std::stringstream ss;
+            trackStateContainer->statistics().toStream(ss);
+            debug() << ss.str() << endmsg;
+
+            ActsHelper::ConstTrackContainer constTracks
+            {
+                std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer)),
+                std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer))
+            };
+
+            if (constTracks.size() == 0) { 
+                info() << "No tracks fitted for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
+                continue; 
+            } else if (constTracks.size() > 1) {
+                info() << "Number of tracks fitted: " << constTracks.size() << " for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
+            }
+
+            for (const auto& cur_track : constTracks)
+            {
+                auto writeout_track = trkCol->create();
+
+                writeout_track.setChi2(cur_track.chi2());
+                writeout_track.setNdf(cur_track.nDoF());
+                writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
+                writeout_track.setDEdxError(mc.getPDG());
+
+                // TODO: covmatrix need to be converted
+                std::array<float, 21> writeout_covMatrix;
+                auto cur_track_covariance = cur_track.covariance();
+                for (int i = 0; i < 6; i++) {
+                    for (int j = 0; j < 6-i; j++) {
+                        writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
+                    }
+                }
+                // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
+                // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
+                edm4hep::TrackState writeout_trackState{
+                    1, // location: AtOther
+                    cur_track.loc0() / Acts::UnitConstants::mm, // d0
+                    cur_track.phi(), // phi
+                    cur_track.qOverP() * _FCT * m_field.value() / sin(cur_track.theta()) , // omega = qop * sin(theta) * _FCT * bf
+                    cur_track.loc1() / Acts::UnitConstants::mm, // z0
+                    1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
+                    cur_track.time(), // time
+                    ::edm4hep::Vector3f(0, 0, 0), // referencePoint
+                    writeout_covMatrix
+                };
+
+                debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " GeV!" << endmsg;
+                writeout_track.addToTrackStates(writeout_trackState);
+            }
+
+            if (!useMCTruth.value()) { break; }
+        }
+    }
+
+    chronoStatSvc->chronoStop("Truth Tracking");
+    
+    return StatusCode::SUCCESS;
+}
+
+StatusCode RecActsTruthTracking::finalize()
+{
+    return StatusCode::SUCCESS;
+}
+
+bool RecActsTruthTracking::ReadHits(const edm4hep::MCParticle& mc)
+{
+    double px = 0;
+    double py = 0;
+    double pz = 0;
+
+    if (useVTX.value())
+    {
+        const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
+        const edm4hep::SimTrackerHitCollection* SimhitVTXCol = nullptr;
+        const edm4hep::MCRecoTrackerAssociationCollection* vtxAssCol = nullptr;
+
+        try {
+            hitVTXCol = _inVTXTrackHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            SimhitVTXCol = _inVTXColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Sim Collection " << _inVTXColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            vtxAssCol = _inVTXAssColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Association Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        if(hitVTXCol && SimhitVTXCol)
+        {
+            int nelem = hitVTXCol->size();
+            debug() << "Number of VTX hits = " << nelem << endmsg;
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nelem; ++ielem){
+                auto hit = hitVTXCol->at(ielem);
+
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *vtxAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found VTX simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            for(int i_hit : hit_indices){
+                auto hit = hitVTXCol->at(i_hit);
+
+                auto cellid = hit.getCellID();
+                uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
+                uint64_t m_module  = vtx_decoder->get(cellid, "module");
+
+                if(m_layer <= 3){
+                    uint64_t acts_volume = VXD_volume_ids[m_layer];
+                    uint64_t acts_layer = 2;
+                    uint64_t acts_sensitive = 1;
+                    bool buildSpacePoint = true;
+                    int moduleType = 1;
+                    double onSurfaceTolerance = 1e-4;
+
+                    if (!recActsSvc->ReadInput(hit,
+                        acts_volume, acts_layer, acts_sensitive,
+                        buildSpacePoint, moduleType, onSurfaceTolerance))
+                        { return false; }
+                } else {
+                    uint64_t acts_volume = VXD_volume_ids[4];
+                    uint64_t acts_layer = 2;
+                    uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;;
+
+                    if (!recActsSvc->ReadInput(hit,
+                        acts_volume, acts_layer, acts_sensitive))
+                        { return false; }
+                }
+            }
+
+            if (hit_indices.size() == 0) {
+                info() << "Skip: Not found VTX simHit at event " << _nEvt << endmsg;
+                return false;
+            }
+
+            auto first_hit = hitVTXCol->at(hit_indices[0]);
+            std::vector<edm4hep::SimTrackerHit> simHits;
+            for(auto ass : *vtxAssCol){
+                if(ass.getRec().getObjectID().collectionID != first_hit.getObjectID().collectionID) break;
+                else if(ass.getRec()==first_hit) simHits.push_back(ass.getSim());
+            }
+            auto simhit = simHits[0];
+            px = simhit.getMomentum()[0];
+            py = simhit.getMomentum()[1];
+            pz = simhit.getMomentum()[2];
+        }
+    }
+
+    if (useITKBarrel.value())
+    { 
+        const edm4hep::TrackerHitCollection* hitITKBarrelCol = nullptr;
+        const edm4hep::SimTrackerHitCollection* SimhitITKBarrelCol = nullptr;
+        const edm4hep::MCRecoTrackerAssociationCollection* itkBarrelAssCol = nullptr;
+
+        try {
+            hitITKBarrelCol = _inITKBarrelTrackHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Collection " << _inITKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            SimhitITKBarrelCol = _inITKBarrelColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Sim Collection " << _inITKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            itkBarrelAssCol = _inITKBarrelAssColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Association Collection " << _inITKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+        
+        if(hitITKBarrelCol && SimhitITKBarrelCol)
+        {
+            int nelem = hitITKBarrelCol->size();
+            debug() << "Number of ITKBarrel hits = " << nelem << endmsg;
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nelem; ++ielem){
+                auto hit = hitITKBarrelCol->at(ielem);
+                
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *itkBarrelAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+                
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found ITKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            for (int i_hit : hit_indices)
+            {
+                auto hit = hitITKBarrelCol->at(i_hit);
+
+                auto cellid = hit.getCellID();
+                uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
+                uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
+
+                uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = m_stave + 1;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive))
+                    { return false; }
+            }
+        }
+    }
+
+    if (useITKEndcap.value())
+    {            
+        const edm4hep::TrackerHitCollection* hitITKEndcapCol = nullptr;
+        const edm4hep::SimTrackerHitCollection* SimhitITKEndcapCol = nullptr;
+        const edm4hep::MCRecoTrackerAssociationCollection* itkEndcapAssCol = nullptr;
+
+        try {
+            hitITKEndcapCol = _inITKEndcapTrackHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Collection " << _inITKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            SimhitITKEndcapCol = _inITKEndcapColHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Sim Collection " << _inITKEndcapColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            itkEndcapAssCol = _inITKEndcapAssColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Association Collection " << _inITKEndcapAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        if (hitITKEndcapCol && SimhitITKEndcapCol)
+        {
+            int nelem = hitITKEndcapCol->size();
+            debug() << "Number of ITKEndcap hits = " << nelem << endmsg;
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nelem; ++ielem){
+                auto hit = hitITKEndcapCol->at(ielem);
+
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *itkEndcapAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            for (int i_hit : hit_indices)
+            {
+                auto hit = hitITKEndcapCol->at(i_hit);
+                
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *itkEndcapAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                auto cellid = hit.getCellID();
+                int m_side = ITKEndcap_decoder->get(cellid, "side");
+                uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
+                uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
+
+                uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = m_module + 1;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive))
+                    { return false; }
+            }
+        }
+    }
+
+    if (useTPC.value())
+    {
+
+        const edm4hep::TrackerHitCollection* hitTPCCol = nullptr;
+        const edm4hep::SimTrackerHitCollection* SimhitTPCCol = nullptr;
+        const edm4hep::MCRecoTrackerAssociationCollection* tpcAssCol = nullptr;
+
+        try {
+            hitTPCCol = _inTPCTrackHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Collection " << _inTPCTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            SimhitTPCCol = _inTPCColHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Sim Collection " << _inTPCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            tpcAssCol = _inTPCAssColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Association Collection " << _inTPCAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        if(hitTPCCol && SimhitTPCCol)
+        {
+            int nelem = hitTPCCol->size();
+            debug() << "Number of TPC hits = " << nelem << endmsg;
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nelem; ++ielem){
+                auto hit = hitTPCCol->at(ielem);
+
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *tpcAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found TPC simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            for (int i_hit : hit_indices)
+            {
+                auto hit = hitTPCCol->at(i_hit);
+                
+                auto cellid = hit.getCellID();
+                uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
+
+                uint64_t acts_volume = TPC_volume_id;
+                uint64_t acts_layer = (m_layer + 1) * 2;
+                uint64_t acts_sensitive = 1;
+                bool buildSpacePoint = false;
+                int moduleType = 2;
+                double onSurfaceTolerance = 1e-4;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive,
+                    buildSpacePoint, moduleType, onSurfaceTolerance))
+                    { return false; }
+            }
+        }
+    }
+
+    if (useOTKBarrel.value())
+    {
+        const edm4hep::TrackerHitCollection* hitOTKBarrelCol = nullptr;
+        const edm4hep::SimTrackerHitCollection* SimhitOTKBarrelCol = nullptr;
+        const edm4hep::MCRecoTrackerAssociationCollection* otkBarrelAssCol = nullptr;
+
+        try {   
+            hitOTKBarrelCol = _inOTKBarrelTrackHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Collection " << _inOTKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            SimhitOTKBarrelCol = _inOTKBarrelColHdl.get();
+        } catch (GaudiException& e) {
+            debug() << "Sim Collection " << _inOTKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        try {
+            otkBarrelAssCol = _inOTKBarrelAssColHdl.get();
+        } catch (GaudiException& e) {
+            fatal() << "Association Collection " << _inOTKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
+            return false;
+        }
+
+        if(hitOTKBarrelCol && SimhitOTKBarrelCol)
+        {
+            int nelem = hitOTKBarrelCol->size();
+            debug() << "Number of OTKBarrel hits = " << nelem << endmsg;
+
+            std::vector<int> hit_indices;
+            std::vector<float> hit_radii;
+            for(int ielem = 0; ielem < nelem; ++ielem)
+            {
+                auto hit = hitOTKBarrelCol->at(ielem);
+
+                std::vector<edm4hep::SimTrackerHit> simHits;
+                for(auto ass : *otkBarrelAssCol){
+                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
+                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
+                }
+
+                if(simHits.size() == 0){
+                    info() << "Skip: Not found OTKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
+                    continue;
+                }
+
+                auto simhit = simHits[0];
+
+                if (simhit.isProducedBySecondary()) {
+                    continue;
+                }
+
+                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
+                    continue;
+                }
+
+                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
+                hit_indices.push_back(ielem);
+                hit_radii.push_back(hit_r);
+            }
+
+            std::sort(hit_indices.begin(), hit_indices.end(),
+                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
+
+            for (int i_hit : hit_indices)
+            {
+                auto hit = hitOTKBarrelCol->at(i_hit);
+
+                auto cellid = hit.getCellID();
+                uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
+
+                uint64_t acts_volume = OTK_volume_id;
+                uint64_t acts_layer = 2;
+                uint64_t acts_sensitive = m_module + 1;
+
+                if (!recActsSvc->ReadInput(hit,
+                    acts_volume, acts_layer, acts_sensitive))
+                    { return false; }
+            }
+        }
+    }
+
+    double p  = sqrt(px*px + py*py + pz*pz);
+    double phi = atan2(py, px);
+    double theta = atan2(sqrt(px*px + py*py), pz);
+    double qop = -1 / p;
+
+    Acts::BoundVector params = Acts::BoundVector::Zero();
+    Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
+    ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*recActsSvc->Geometry()};
+
+    auto first_hit_meas =  recActsSvc->Measurements()->at(0);
+    auto cur_meas_param = std::get<1>(first_hit_meas).parameters();
+    auto cur_meas_sl = std::get<1>(first_hit_meas).sourceLink();
+    const Acts::Surface* surface = surfaceAccessor(cur_meas_sl);
+
+    params[Acts::eBoundLoc0]   = cur_meas_param[Acts::eBoundLoc0];
+    params[Acts::eBoundLoc1]   = cur_meas_param[Acts::eBoundLoc1];
+    params[Acts::eBoundPhi]    = phi;
+    params[Acts::eBoundTheta]  = theta;
+    params[Acts::eBoundQOverP] = qop;
+    params[Acts::eBoundTime]   = 0;
+    for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
+        double sigma = initialSigmas[i];
+        sigma += initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP];
+        double var = sigma * sigma;
+        // var *= noTimeVarInflation.value();
+        var *= initialVarInflation.value()[i];
+        
+        cov(i, i) = var;
+    }
+    
+    recActsSvc->InitialParameters()->emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
+
+    return true;
+}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h
new file mode 100644
index 00000000..19672f3b
--- /dev/null
+++ b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h
@@ -0,0 +1,183 @@
+#ifndef RecActsTruthTracking_H
+#define RecActsTruthTracking_H
+
+// gaudi framework
+#include "k4FWCore/DataHandle.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+
+#include "UTIL/ILDConf.h"
+#include "DataHelper/Navigation.h"
+
+// services
+#include "RecActsSvc/IRecActsSvc.h"
+#include "DetInterface/IGeomSvc.h"
+
+// DD4hep
+#include "DD4hep/Detector.h"
+#include "DDRec/ISurface.h"
+#include "DDRec/SurfaceManager.h"
+
+// edm4hep
+#include "edm4hep/MCParticle.h"
+#include "edm4hep/MCParticleCollection.h"
+#include "edm4hep/TrackerHitCollection.h"
+#include "edm4hep/SimTrackerHitCollection.h"
+#include "edm4hep/EventHeaderCollection.h"
+
+#include "edm4hep/Track.h"
+#include "edm4hep/MutableTrack.h"
+#include "edm4hep/TrackState.h"
+#include "edm4hep/TrackCollection.h"
+// Acts
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/EventData/GenericBoundTrackParameters.hpp"
+#include "Acts/EventData/SourceLink.hpp"
+#include "Acts/EventData/TrackProxy.hpp"
+#include "Acts/EventData/VectorMultiTrajectory.hpp"
+#include "Acts/EventData/VectorTrackContainer.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Result.hpp"
+
+#include "Acts/Definitions/Direction.hpp"
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/TrackContainer.hpp"
+#include "Acts/EventData/TrackStatePropMask.hpp"
+#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/Utilities/Delegate.hpp"
+#include "Acts/Utilities/Logger.hpp"
+
+namespace Acts
+{
+    class MagneticFieldProvider;
+    class TrackingGeometry;
+}
+
+class RecActsTruthTracking : public GaudiAlgorithm
+{
+    public:
+        RecActsTruthTracking(const std::string& name, ISvcLocator* svcLoc);
+
+        StatusCode initialize();
+        StatusCode execute();
+        StatusCode finalize();
+
+    private:
+        // Input collections
+        DataHandle<edm4hep::MCParticleCollection> _inMCColHdl{"MCParticle", Gaudi::DataHandle::Reader, this};
+        
+        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKBarrelTrackHdl{"ITKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inITKEndcapTrackHdl{"ITKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inOTKBarrelTrackHdl{"OTKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::TrackerHitCollection> _inTPCTrackHdl{"TPCTrackerHits", Gaudi::DataHandle::Reader, this};        
+
+        DataHandle<edm4hep::SimTrackerHitCollection> _inVTXColHdl{"VXDCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inITKBarrelColHdl{"ITKBarrelCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inITKEndcapColHdl{"ITKEndcapCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inOTKBarrelColHdl{"OTKBarrelCollection", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::SimTrackerHitCollection> _inTPCColHdl{"TPCCollection", Gaudi::DataHandle::Reader, this};
+
+        // associations
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKBarrelAssColHdl{"ITKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKEndcapAssColHdl{"ITKEndcapTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inOTKBarrelAssColHdl{"OTKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
+        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inTPCAssColHdl{"TPCTrackerHitAss", Gaudi::DataHandle::Reader, this};
+
+        // Output collections
+        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSTruthTracks", Gaudi::DataHandle::Writer, this};
+
+        Gaudi::Property<std::string> m_particle{this, "AssumeParticle", "muon"};
+        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
+        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 1, 1, 1, 1}};
+        Gaudi::Property<double> noTimeVarInflation{this, "noTimeVarInflation", 1.};
+
+        Gaudi::Property<bool> useMCTruth{this, "useMCTruth", false};
+        Gaudi::Property<bool> useVTX{this, "useVTX", true};
+        Gaudi::Property<bool> useITKBarrel{this, "useITKBarrel", true};
+        Gaudi::Property<bool> useITKEndcap{this, "useITKEndcap", true};
+        Gaudi::Property<bool> useTPC{this, "useTPC", true};
+        Gaudi::Property<bool> useOTKBarrel{this, "useOTKBarrel", true};
+        // Gaudi::Property<bool> useOTKEndcap{this, "useOTKEndcap", true};
+        
+        // services
+        // SmartIF<IGeomSvc> m_geosvc;
+        SmartIF<IRecActsSvc> recActsSvc;
+        SmartIF<IChronoStatSvc> chronoStatSvc;
+
+        bool ReadHits(const edm4hep::MCParticle& mc);
+        
+        // Decoders
+        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
+        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
+
+        std::shared_ptr<ActsHelper::TrackFitterFunction> fit;
+        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
+        // std::shared_ptr<ActsHelper::MeasurementCalibrator> m_calibrator;
+
+        // globalChiSquareFitter config
+        bool multipleScattering = false;
+        bool energyLoss = false;
+        Acts::FreeToBoundCorrection freeToBoundCorrection;
+        std::size_t nUpdateMax = 5;
+        double relChi2changeCutOff = 1e-7;
+        
+        // context
+        Acts::GeometryContext geoContext;
+        Acts::MagneticFieldContext magFieldContext;
+        Acts::CalibrationContext calibContext;
+
+        // double noTimeVarInflation = 10.;
+        std::array<double, 6> initialSigmas = {
+            5 * Acts::UnitConstants::um,
+            5 * Acts::UnitConstants::um,
+            2e-2 * Acts::UnitConstants::degree,
+            2e-2 * Acts::UnitConstants::degree,
+            0 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
+            1 * Acts::UnitConstants::s
+        };
+        std::array<double, 6> initialSimgaQoverPCoefficients = {
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
+            0.1,
+            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)
+        };
+        
+        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
+
+        // constants
+        const double _FCT = 2.99792458E-4;
+        uint64_t TPC_volume_id = 45;
+        uint64_t OTKBarrel_volume_id = 47;
+        uint64_t OTKEndcap_positive_volume_id = 2;
+        uint64_t OTKEndcap_negative_volume_id = 48;
+        std::vector<uint64_t> VXD_volume_ids{28, 29, 30, 31, 32};
+        std::vector<uint64_t> ITKBarrel_volume_ids{35, 38, 41};
+        std::vector<uint64_t> ITKEndcap_positive_volume_ids{36, 39, 42, 43};
+        std::vector<uint64_t> ITKEndcap_negative_volume_ids{34, 17, 12, 10};
+        std::vector<std::vector<uint64_t>> ITKEndcap_modules_per_ring{{13, 20, 0}, {16, 24, 28}, {24, 36, 44}, {24, 36, 44}};
+        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
+        
+        const std::array<Acts::BoundIndices, 6> writeout_indices{
+            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
+            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
+            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
+        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
+        int _nEvt;
+};
+
+#endif // RecActsTruthTracking_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/options/RecActsTracking.py b/Reconstruction/RecActsTracking/options/RecActsTracking.py
deleted file mode 100644
index 1ba576f9..00000000
--- a/Reconstruction/RecActsTracking/options/RecActsTracking.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python
-import os
-
-from Gaudi.Configuration import *
-
-NTupleSvc().Output = ["MyTuples DATAFILE='test.root' OPT='NEW' TYP='ROOT'"]
-
-from Configurables import k4DataSvc
-dsvc = k4DataSvc("EventDataSvc", input="rec00.root")
-
-from Configurables import PodioInput
-podioinput = PodioInput("PodioReader", collections=[
-    "MCParticle",
-    "VXDCollection",
-    "SITCollection",
-    "VXDTrackerHits",
-    "SITTrackerHits",
-])
-
-##############################################################################
-# Geometry Svc
-##############################################################################
-
-# geometry_option = "TDR_o1_v01/TDR_o1_v01-onlyTracker.xml"
-geometry_option = "TDR_o1_v01/TDR_o1_v01.xml"
-
-if not os.getenv("DETCRDROOT"):
-    print("Can't find the geometry. Please setup envvar DETCRDROOT." )
-    sys.exit(-1)
-
-geometry_path = os.path.join(os.getenv("DETCRDROOT"), "compact", geometry_option)
-if not os.path.exists(geometry_path):
-    print("Can't find the compact geometry file: %s"%geometry_path)
-    sys.exit(-1)
-
-from Configurables import DetGeomSvc
-geosvc = DetGeomSvc("GeomSvc")
-geosvc.compact = geometry_path
-
-from Configurables import MarlinEvtSeeder
-evtseeder = MarlinEvtSeeder("EventSeeder")
-
-from Configurables import GearSvc
-gearsvc = GearSvc("GearSvc")
-
-from Configurables import TrackSystemSvc
-tracksystemsvc = TrackSystemSvc("TrackSystemSvc")
-
-##############################################################################
-# CEPCSWData
-##############################################################################
-cepcswdatatop ="/cvmfs/cepcsw.ihep.ac.cn/prototype/releases/data/latest"
-
-##############################################################################
-# RecActsTracking
-##############################################################################
-
-from Configurables import RecActsTracking
-actstracking = RecActsTracking("RecActsTracking")
-actstracking.TGeoFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.1.0/SiTrack-tgeo.root")
-actstracking.TGeoConfigFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.1.0/SiTrack-tgeo-config.json")
-actstracking.MaterialMapFile = os.path.join(cepcswdatatop, "CEPCSWData/offline-data/Reconstruction/RecActsTracking/data/tdr25.1.0/SiTrack-material-maps.json")
-
-# config for acts tracking
-actstracking.onSurfaceTolerance = 2e-1
-# actstracking.ExtendSeedRange = True
-# actstracking.SeedDeltaRMin = 10
-# actstracking.SeedDeltaRMax = 200
-# actstracking.SeedRMax = 600
-# actstracking.SeedRMin = 80
-# actstracking.SeedImpactMax = 4
-# actstracking.SeedRMinMiddle = 340
-# actstracking.SeedRMaxMiddle = 380
-actstracking.numMeasurementsCutOff = 2
-actstracking.CKFchi2Cut = 1
-
-##############################################################################
-# output
-##############################################################################
-from Configurables import PodioOutput
-out = PodioOutput("out")
-out.filename = "ACTS_rec00.root"
-out.outputCommands = ["keep *"]
-
-# ApplicationMgr
-from Configurables import ApplicationMgr
-ApplicationMgr( TopAlg = [podioinput, actstracking, out],
-                EvtSel = 'NONE',
-                EvtMax = 5,
-                ExtSvc = [dsvc, geosvc, evtseeder, gearsvc],
-                # OutputLevel=DEBUG
-)
diff --git a/Reconstruction/RecActsTracking/src/RecActsTracking.cpp b/Reconstruction/RecActsTracking/src/RecActsTracking.cpp
deleted file mode 100644
index ba125e91..00000000
--- a/Reconstruction/RecActsTracking/src/RecActsTracking.cpp
+++ /dev/null
@@ -1,1208 +0,0 @@
-#include <iostream>
-#include <fstream>
-#include <cstdlib>
-#include <sstream>
-
-// dependence
-#include "RecActsTracking.h"
-
-#include "GearSvc/IGearSvc.h"
-
-// csv parser
-// #include "csv2/writer.hpp"
-
-// MC
-#include "CLHEP/Units/SystemOfUnits.h"
-
-
-using namespace Acts::UnitLiterals;
-
-DECLARE_COMPONENT(RecActsTracking)
-
-RecActsTracking::RecActsTracking(const std::string& name, ISvcLocator* svcLoc)
-    : GaudiAlgorithm(name, svcLoc)
-{
-}
-
-StatusCode RecActsTracking::initialize()
-{
-    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
-    _nEvt = 0;
-
-    if (!std::filesystem::exists(TGeo_path.value())) {
-        error() << "CEPC TGeo file: " << TGeo_path.value() << " does not exist!" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    if (!std::filesystem::exists(TGeo_config_path.value())) {
-        error() << "CEPC TGeo config file: " << TGeo_config_path.value() << " does not exist!" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    if (!std::filesystem::exists(MaterialMap_path.value())) {
-        error() << "CEPC Material map file: " << MaterialMap_path.value() << " does not exist!" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    if(!m_particle.value().empty()){
-        info() << "Assume Particle: " << m_particle.value() << endmsg;
-        if(m_particle.value() == "muon"){
-            particleHypothesis = Acts::ParticleHypothesis::muon();
-        }
-        else if(m_particle.value() == "pion"){
-            particleHypothesis = Acts::ParticleHypothesis::pion();
-        }
-        else if(m_particle.value() == "electron"){
-            particleHypothesis = Acts::ParticleHypothesis::electron();
-        }
-        else if(m_particle.value() == "kaon"){
-            particleHypothesis = Acts::ParticleHypothesis::kaon();
-        }
-        else if(m_particle.value() == "proton"){
-            particleHypothesis = Acts::ParticleHypothesis::proton();
-        }
-        else if(m_particle.value() == "photon"){
-            particleHypothesis = Acts::ParticleHypothesis::photon();
-        }
-        else if(m_particle.value() == "geantino"){
-            particleHypothesis = Acts::ParticleHypothesis::geantino();
-        }
-        else if(m_particle.value() == "chargedgeantino"){
-            particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
-        }
-        else{
-            info() << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
-                                             << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
-                                             << particleNames[6] << ", " << particleNames[7] << endmsg;
-            error() << "Unsupported particle name " << m_particle.value() << endmsg;
-            return StatusCode::FAILURE;
-        }
-    }
-
-    TGeo_ROOTFilePath = TGeo_path.value();
-    TGeoConfig_jFilePath = TGeo_config_path.value();
-    MaterialMap_jFilePath = MaterialMap_path.value();
-
-    chronoStatSvc->chronoStart("read geometry");
-    m_geosvc = service<IGeomSvc>("GeomSvc");
-    if (!m_geosvc) {
-        error() << "Failed to find GeomSvc." << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    if(m_geosvc){
-        const dd4hep::Direction& field = m_geosvc->lcdd()->field().magneticField(dd4hep::Position(0,0,0));
-        m_field = field.z()/dd4hep::tesla;
-    }
-
-    m_vtx_surfaces = m_geosvc->getSurfaceMap("VXD");
-    debug() << "Surface map size: " << m_vtx_surfaces->size() << endmsg;
-
-    m_sit_surfaces = m_geosvc->getSurfaceMap("SIT");
-    debug() << "Surface map size: " << m_sit_surfaces->size() << endmsg;
-
-    m_ftd_surfaces = m_geosvc->getSurfaceMap("FTD");
-    debug() << "Surface map size: " << m_ftd_surfaces->size() << endmsg;
-
-    vxd_decoder = m_geosvc->getDecoder("VXDCollection");
-    if(!vxd_decoder){
-        return StatusCode::FAILURE;
-    }
-
-    sit_decoder = m_geosvc->getDecoder("SITCollection");
-    if(!sit_decoder){
-        return StatusCode::FAILURE;
-    }
-
-    ftd_decoder = m_geosvc->getDecoder("FTDCollection");
-    if(!ftd_decoder){
-        return StatusCode::FAILURE;
-    }
-
-    info() << "ACTS Tracking Geometry initialized successfully!" << endmsg;
-    // initialize tgeo detector
-    auto logger = Acts::getDefaultLogger("TGeoDetector", Acts::Logging::INFO);
-    trackingGeometry = buildTGeoDetector(geoContext, detElementStore, TGeo_ROOTFilePath, TGeoConfig_jFilePath, MaterialMap_jFilePath, *logger);
-
-    info() << "Seeding tools initialized successfully!" << endmsg;
-
-    // configure the acts tools
-    seed_cfg.seedFinderOptions.bFieldInZ = m_field.value();
-    seed_cfg.seedFinderConfig.deltaRMin = SeedDeltaRMin.value();
-    seed_cfg.seedFinderConfig.deltaRMax = SeedDeltaRMax.value();
-    seed_cfg.seedFinderConfig.rMax = SeedRMax.value();
-    seed_cfg.seedFinderConfig.rMin = SeedRMin.value();
-    seed_cfg.seedFinderConfig.impactMax = SeedImpactMax.value();
-    seed_cfg.seedFinderConfig.useVariableMiddleSPRange = false;
-    seed_cfg.seedFinderConfig.rMinMiddle = SeedRMinMiddle.value();
-    seed_cfg.seedFinderConfig.rMaxMiddle = SeedRMaxMiddle.value();
-
-    // initialize the acts tools
-    seed_cfg.seedFilterConfig = seed_cfg.seedFilterConfig.toInternalUnits();
-    seed_cfg.seedFinderConfig.seedFilter =
-        std::make_unique<Acts::SeedFilter<SimSpacePoint>>(seed_cfg.seedFilterConfig);
-    seed_cfg.seedFinderConfig =
-        seed_cfg.seedFinderConfig.toInternalUnits().calculateDerivedQuantities();
-    seed_cfg.seedFinderOptions =
-        seed_cfg.seedFinderOptions.toInternalUnits().calculateDerivedQuantities(seed_cfg.seedFinderConfig);
-    seed_cfg.gridConfig = seed_cfg.gridConfig.toInternalUnits();
-    seed_cfg.gridOptions = seed_cfg.gridOptions.toInternalUnits();
-
-    if (std::isnan(seed_cfg.seedFinderConfig.deltaRMaxTopSP)) {
-        seed_cfg.seedFinderConfig.deltaRMaxTopSP = seed_cfg.seedFinderConfig.deltaRMax;}
-    if (std::isnan(seed_cfg.seedFinderConfig.deltaRMinTopSP)) {
-        seed_cfg.seedFinderConfig.deltaRMinTopSP = seed_cfg.seedFinderConfig.deltaRMin;}
-    if (std::isnan(seed_cfg.seedFinderConfig.deltaRMaxBottomSP)) {
-        seed_cfg.seedFinderConfig.deltaRMaxBottomSP = seed_cfg.seedFinderConfig.deltaRMax;}
-    if (std::isnan(seed_cfg.seedFinderConfig.deltaRMinBottomSP)) {
-        seed_cfg.seedFinderConfig.deltaRMinBottomSP = seed_cfg.seedFinderConfig.deltaRMin;}
-        
-    m_bottomBinFinder = std::make_unique<const Acts::GridBinFinder<2ul>>(
-        seed_cfg.numPhiNeighbors, seed_cfg.zBinNeighborsBottom);
-    m_topBinFinder = std::make_unique<const Acts::GridBinFinder<2ul>>(
-        seed_cfg.numPhiNeighbors, seed_cfg.zBinNeighborsTop);
-
-    seed_cfg.seedFinderConfig.seedFilter =
-        std::make_unique<Acts::SeedFilter<SimSpacePoint>>(seed_cfg.seedFilterConfig);
-    m_seedFinder =
-        Acts::SeedFinder<SimSpacePoint, Acts::CylindricalSpacePointGrid<SimSpacePoint>>(seed_cfg.seedFinderConfig);
-
-    // initialize the ckf
-    findTracks = makeTrackFinderFunction(trackingGeometry, magneticField);
-
-    info() << "CKF Track Finder initialized successfully!" << endmsg;
-    chronoStatSvc->chronoStop("read geometry");
-
-    return GaudiAlgorithm::initialize();
-}
-
-StatusCode RecActsTracking::execute()
-{
-    auto trkCol = _outColHdl.createAndPut();
-
-    SpacePointPtrs.clear();
-    sourceLinks.clear();
-    measurements.clear();
-    initialParameters.clear();
-    Selected_Seeds.clear();
-
-    chronoStatSvc->chronoStart("read input hits");
-
-    int successVTX = InitialiseVTX();
-    if (successVTX == 0)
-    {
-        _nEvt++;
-        return StatusCode::SUCCESS;
-    }
-
-    int successSIT = InitialiseSIT();
-    if (successSIT == 0)
-    {
-        _nEvt++;
-        return StatusCode::SUCCESS;
-    }
-
-    int successFTD = InitialiseFTD();
-    if(successFTD == 0){
-        _nEvt++;
-        return StatusCode::SUCCESS;
-    }
-
-    chronoStatSvc->chronoStop("read input hits");
-    // info() << "Generated " << SpacePointPtrs.size() << " spacepoints for event " << _nEvt << "!" << endmsg;
-    // info() << "Generated " << measurements.size() << " measurements for event " << _nEvt << "!" << endmsg;
-
-    // --------------------------------------------
-    //                 Seeding
-    // --------------------------------------------
-    chronoStatSvc->chronoStart("seeding");
-
-    // construct the seeding tools
-    // covariance tool, extracts covariances per spacepoint as required
-    auto extractGlobalQuantities = [=](const SimSpacePoint& sp, float, float, float)
-    {
-        Acts::Vector3 position{sp.x(), sp.y(), sp.z()};
-        Acts::Vector2 covariance{sp.varianceR(), sp.varianceZ()};
-        return std::make_tuple(position, covariance, sp.t());
-    };
-
-    // extent used to store r range for middle spacepoint
-    Acts::Extent rRangeSPExtent;
-
-    // construct the seeding tool
-    Acts::CylindricalSpacePointGrid<SimSpacePoint> grid =
-        Acts::CylindricalSpacePointGridCreator::createGrid<SimSpacePoint>(seed_cfg.gridConfig, seed_cfg.gridOptions);
-    Acts::CylindricalSpacePointGridCreator::fillGrid(
-        seed_cfg.seedFinderConfig, seed_cfg.seedFinderOptions, grid,
-        SpacePointPtrs.begin(), SpacePointPtrs.end(), extractGlobalQuantities, rRangeSPExtent);
-
-    std::array<std::vector<std::size_t>, 2ul> navigation;
-    navigation[1ul] = seed_cfg.seedFinderConfig.zBinsCustomLooping;
-
-    auto spacePointsGrouping = Acts::CylindricalBinnedGroup<SimSpacePoint>(
-        std::move(grid), *m_bottomBinFinder, *m_topBinFinder, std::move(navigation));
-
-    // safely clamp double to float
-    float up = Acts::clampValue<float>(
-        std::floor(rRangeSPExtent.max(Acts::binR) / 2) * 2);
-
-    /// variable middle SP radial region of interest
-    const Acts::Range1D<float> rMiddleSPRange(
-        std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 +
-        seed_cfg.seedFinderConfig.deltaRMiddleMinSPRange,
-        up - seed_cfg.seedFinderConfig.deltaRMiddleMaxSPRange);
-
-    // run the seeding
-    static thread_local SimSeedContainer seeds;
-    seeds.clear();
-    static thread_local decltype(m_seedFinder)::SeedingState state;
-    state.spacePointData.resize(
-        SpacePointPtrs.size(),
-        seed_cfg.seedFinderConfig.useDetailedDoubleMeasurementInfo);
-
-    // use double stripe measurement
-    if (seed_cfg.seedFinderConfig.useDetailedDoubleMeasurementInfo)
-    {
-        for (std::size_t grid_glob_bin(0); grid_glob_bin < spacePointsGrouping.grid().size(); ++grid_glob_bin)
-        {
-            const auto& collection = spacePointsGrouping.grid().at(grid_glob_bin);
-            for (const auto& sp : collection)
-            {
-                std::size_t index = sp->index();
-                const float topHalfStripLength =
-                    seed_cfg.seedFinderConfig.getTopHalfStripLength(sp->sp());
-                const float bottomHalfStripLength =
-                    seed_cfg.seedFinderConfig.getBottomHalfStripLength(sp->sp());
-                const Acts::Vector3 topStripDirection =
-                    seed_cfg.seedFinderConfig.getTopStripDirection(sp->sp());
-                const Acts::Vector3 bottomStripDirection =
-                    seed_cfg.seedFinderConfig.getBottomStripDirection(sp->sp());
-
-                state.spacePointData.setTopStripVector(
-                    index, topHalfStripLength * topStripDirection);
-                state.spacePointData.setBottomStripVector(
-                    index, bottomHalfStripLength * bottomStripDirection);
-                state.spacePointData.setStripCenterDistance(
-                    index, seed_cfg.seedFinderConfig.getStripCenterDistance(sp->sp()));
-                state.spacePointData.setTopStripCenterPosition(
-                    index, seed_cfg.seedFinderConfig.getTopStripCenterPosition(sp->sp()));
-            }
-        }
-    }
-
-    for (const auto [bottom, middle, top] : spacePointsGrouping)
-    {
-        m_seedFinder.createSeedsForGroup(
-            seed_cfg.seedFinderOptions, state, spacePointsGrouping.grid(),
-            std::back_inserter(seeds), bottom, middle, top, rMiddleSPRange);
-    }
-
-    // int seed_counter = 0;
-    // for (const auto& seed : seeds)
-    // {
-    //     for (const auto& sp : seed.sp())
-    //     {
-    //         info() << "found seed #" << seed_counter << ": x:" << sp->x() << " y: " << sp->y() << " z: " << sp->z() << endmsg;
-    //     }
-    //     seed_counter++;
-    // }
-
-    chronoStatSvc->chronoStop("seeding");
-    debug() << "Found " << seeds.size() << " seeds for event " << _nEvt << "!" << endmsg;
-
-    // --------------------------------------------
-    //            track estimation
-    // --------------------------------------------
-    chronoStatSvc->chronoStart("track_param");
-
-    IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
-
-    for (std::size_t iseed = 0; iseed < seeds.size(); ++iseed)
-    {
-        const auto& seed = seeds[iseed];
-        // Get the bottom space point and its reference surface
-        const auto bottomSP = seed.sp().front();
-        const auto& sourceLink = bottomSP->sourceLinks()[0];
-        const Acts::Surface* surface = surfaceAccessor(sourceLink);
-        if (surface == nullptr) {
-            debug() << "Surface from source link is not found in the tracking geometry: iseed " << iseed << endmsg;
-            continue;
-        }
-
-        auto optParams = Acts::estimateTrackParamsFromSeed(
-            geoContext, seed.sp().begin(), seed.sp().end(), *surface, acts_field_value, bFieldMin);
-        
-        if (!optParams.has_value()) {
-            debug() << "Estimation of track parameters for seed " << iseed << " failed." << endmsg;
-            continue;
-        }
-
-        const auto& params = optParams.value();
-        Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
-        for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
-            double sigma = initialSigmas[i];
-            sigma += abs(initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP]);
-            double var = sigma * sigma;
-            if (i == Acts::eBoundTime && !bottomSP->t().has_value()) { var *= noTimeVarInflation; }
-            var *= initialVarInflation.value()[i];
-            
-            cov(i, i) = var;
-        }
-        initialParameters.emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
-        Selected_Seeds.push_back(seed);
-    }
-
-    chronoStatSvc->chronoStop("track_param");
-    debug() << "Found " << initialParameters.size() << " tracks for event " << _nEvt << "!" << endmsg;
-
-    // --------------------------------------------
-    //            CKF track finding
-    // --------------------------------------------
-    chronoStatSvc->chronoStart("ckf_findTracks");
-
-    // Construct a perigee surface as the target surface
-    auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
-    PassThroughCalibrator pcalibrator;
-    MeasurementCalibratorAdapter calibrator(pcalibrator, measurements);
-    Acts::GainMatrixUpdater kfUpdater;
-    Acts::MeasurementSelector::Config measurementSelectorCfg =
-    {
-        {Acts::GeometryIdentifier(), {{}, {CKFchi2Cut.value()}, {numMeasurementsCutOff.value()}}},
-    };
-
-    MeasurementSelector measSel{ Acts::MeasurementSelector(measurementSelectorCfg) };
-    using Extensions = Acts::CombinatorialKalmanFilterExtensions<Acts::VectorMultiTrajectory>;
-    BranchStopper branchStopper(trackSelectorCfg);
-
-    // Construct the CKF
-    Extensions extensions;
-    extensions.calibrator.connect<&MeasurementCalibratorAdapter::calibrate>(&calibrator);
-    extensions.updater.connect<&Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(&kfUpdater);
-    extensions.measurementSelector.connect<&MeasurementSelector::select>(&measSel);
-    extensions.branchStopper.connect<&BranchStopper::operator()>(&branchStopper);
-
-    IndexSourceLinkAccessor slAccessor;
-    slAccessor.container = &sourceLinks;
-    Acts::SourceLinkAccessorDelegate<IndexSourceLinkAccessor::Iterator> slAccessorDelegate;
-    slAccessorDelegate.connect<&IndexSourceLinkAccessor::range>(&slAccessor);
-
-    Acts::PropagatorPlainOptions firstPropOptions;
-    firstPropOptions.maxSteps = maxSteps;
-    firstPropOptions.direction = Acts::Direction::Forward;
-
-    Acts::PropagatorPlainOptions secondPropOptions;
-    secondPropOptions.maxSteps = maxSteps;
-    secondPropOptions.direction = firstPropOptions.direction.invert();
-
-    // Set the CombinatorialKalmanFilter options
-    TrackFinderOptions firstOptions(
-        geoContext, magFieldContext, calibContext,
-        slAccessorDelegate, extensions, firstPropOptions);
-    
-    TrackFinderOptions secondOptions(
-        geoContext, magFieldContext, calibContext,
-        slAccessorDelegate, extensions, secondPropOptions);
-    secondOptions.targetSurface = pSurface.get();
-
-    Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator> extrapolator(
-        Acts::EigenStepper<>(magneticField), Acts::Navigator({trackingGeometry}));
-
-    Acts::PropagatorOptions<Acts::ActionList<Acts::MaterialInteractor>, Acts::AbortList<Acts::EndOfWorldReached>>
-        extrapolationOptions(geoContext, magFieldContext);
-
-    auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
-    auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
-    auto trackContainerTemp = std::make_shared<Acts::VectorTrackContainer>();
-    auto trackStateContainerTemp = std::make_shared<Acts::VectorMultiTrajectory>();
-    TrackContainer tracks(trackContainer, trackStateContainer);
-    TrackContainer tracksTemp(trackContainerTemp, trackStateContainerTemp);
-
-    tracks.addColumn<unsigned int>("trackGroup");
-    tracksTemp.addColumn<unsigned int>("trackGroup");
-    Acts::ProxyAccessor<unsigned int> seedNumber("trackGroup");
-
-    unsigned int nSeed = 0;
-    // A map indicating whether a seed has been discovered already
-    std::unordered_map<SeedIdentifier, bool> discoveredSeeds;
-    auto addTrack = [&](const TrackProxy& track)
-    {
-        ++m_nFoundTracks;
-        // flag seeds which are covered by the track
-        visitSeedIdentifiers(track, [&](const SeedIdentifier& seedIdentifier)
-        {
-            if (auto it = discoveredSeeds.find(seedIdentifier); it != discoveredSeeds.end())
-            {
-                it->second = true;
-            }
-        });
-
-        if (m_trackSelector.has_value() && !m_trackSelector->isValidTrack(track)) { return; }
-
-        ++m_nSelectedTracks;
-        auto destProxy = tracks.makeTrack();
-        // make sure we copy track states!
-        destProxy.copyFrom(track, true);
-    };
-
-    for (const auto& seed : Selected_Seeds) {
-        SeedIdentifier seedIdentifier = makeSeedIdentifier(seed);
-        discoveredSeeds.emplace(seedIdentifier, false);
-    }
-
-    for (std::size_t iSeed = 0; iSeed < initialParameters.size(); ++iSeed)
-    {           
-        m_nTotalSeeds++;
-        const auto& seed = Selected_Seeds[iSeed];
-
-        SeedIdentifier seedIdentifier = makeSeedIdentifier(seed);
-        // check if the seed has been discovered already
-        if (auto it = discoveredSeeds.find(seedIdentifier); it != discoveredSeeds.end() && it->second)
-        {
-            m_nDeduplicatedSeeds++;
-            continue;
-        }
-
-        /// Whether to stick on the seed measurements during track finding.
-        // measSel.setSeed(seed);
-
-        // Clear trackContainerTemp and trackStateContainerTemp
-        tracksTemp.clear();
-
-        const Acts::BoundTrackParameters& firstInitialParameters = initialParameters.at(iSeed);
-        auto firstResult = (*findTracks)(firstInitialParameters, firstOptions, tracksTemp);
-
-        nSeed++;
-        if (!firstResult.ok())
-        {
-            m_nFailedSeeds++;
-            continue;
-        }
-
-        auto& firstTracksForSeed = firstResult.value();
-        for (auto& firstTrack : firstTracksForSeed)
-        {
-            auto trackCandidate = tracksTemp.makeTrack();
-            trackCandidate.copyFrom(firstTrack, true);
-
-            auto firstSmoothingResult = Acts::smoothTrack(geoContext, trackCandidate);
-
-            if (!firstSmoothingResult.ok())
-            {
-                m_nFailedSmoothing++;
-                debug() << "First smoothing for seed " 
-                       << iSeed << " and track " << firstTrack.index()
-                       << " failed with error " << firstSmoothingResult.error() << endmsg;
-                continue;
-            }
-
-            seedNumber(trackCandidate) = nSeed - 1;
-      
-            // second way track finding
-            std::size_t nSecond = 0;
-            if (CKFtwoWay)
-            {
-                std::optional<Acts::VectorMultiTrajectory::TrackStateProxy> firstMeasurement;
-                for (auto trackState : trackCandidate.trackStatesReversed())
-                {
-                    bool isMeasurement = trackState.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag);
-                    bool isOutlier = trackState.typeFlags().test(Acts::TrackStateFlag::OutlierFlag);
-                    if (isMeasurement && !isOutlier) { firstMeasurement = trackState; }
-                }
-                
-                if (firstMeasurement.has_value())
-                {
-                    Acts::BoundTrackParameters secondInitialParameters = trackCandidate.createParametersFromState(*firstMeasurement);
-                    auto secondResult = (*findTracks)(secondInitialParameters, secondOptions, tracksTemp);
-                    if (!secondResult.ok())
-                    {
-                        debug() << "Second track finding failed for seed "
-                                << iSeed << " with error" << secondResult.error() << endmsg;
-                    } else {
-                        auto firstState = *std::next(trackCandidate.trackStatesReversed().begin(), trackCandidate.nTrackStates() - 1);
-                        auto& secondTracksForSeed = secondResult.value();
-                        for (auto& secondTrack : secondTracksForSeed)
-                        { 
-                            if (secondTrack.nTrackStates() < 2) { continue; }
-                            auto secondTrackCopy = tracksTemp.makeTrack();
-                            secondTrackCopy.copyFrom(secondTrack, true);
-                            secondTrackCopy.reverseTrackStates(true);
-
-                            firstState.previous() = (*std::next(secondTrackCopy.trackStatesReversed().begin())).index();
-                            Acts::calculateTrackQuantities(trackCandidate);
-                            auto secondExtrapolationResult = Acts::extrapolateTrackToReferenceSurface(
-                                trackCandidate, *pSurface, extrapolator,
-                                extrapolationOptions, extrapolationStrategy);
-                            if (!secondExtrapolationResult.ok())
-                            {
-                                m_nFailedExtrapolation++;
-                                debug() << "Second extrapolation for seed " 
-                                        << iSeed << " and track " << secondTrack.index()
-                                        << " failed with error "
-                                        << secondExtrapolationResult.error() << endmsg;
-                                continue;
-                            }
-
-                            addTrack(trackCandidate);
-
-                            ++nSecond;
-                        }
-
-                        firstState.previous() = Acts::kTrackIndexInvalid;
-                        Acts::calculateTrackQuantities(trackCandidate);
-                    }
-                }
-            }
-
-            // if no second track was found, we will use only the first track
-            if (nSecond == 0) {
-                auto firstExtrapolationResult = Acts::extrapolateTrackToReferenceSurface(
-                    trackCandidate, *pSurface, extrapolator, extrapolationOptions, extrapolationStrategy);
-
-                if (!firstExtrapolationResult.ok())
-                {
-                    m_nFailedExtrapolation++;
-                    continue;
-                }
-
-                addTrack(trackCandidate);
-            }
-        }
-    }
-
-    chronoStatSvc->chronoStop("ckf_findTracks");
-    debug() << "CKF found " << tracks.size() << " tracks for event " << _nEvt << "!" << endmsg;
-
-    m_memoryStatistics.local().hist += tracks.trackStateContainer().statistics().hist;
-    auto constTrackStateContainer = std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer));
-    auto constTrackContainer = std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer));
-    ConstTrackContainer constTracks{constTrackContainer, constTrackStateContainer};
-
-    chronoStatSvc->chronoStart("writeout tracks");
-
-    if (constTracks.size() == 0) {
-        chronoStatSvc->chronoStop("writeout tracks");
-        _nEvt++;
-        return StatusCode::SUCCESS;
-    }
-    for (const auto& cur_track : constTracks)
-    {
-        auto writeout_track = trkCol->create();
-        int nVTXHit = 0, nFTDHit = 0, nSITHit = 0, nlayerHits = 9;
-        int getFirstHit = 0;
-        
-        writeout_track.setChi2(cur_track.chi2());
-        writeout_track.setNdf(cur_track.nDoF());
-        writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
-        // writeout_track.setDEdxError(cur_track.qOverP());
-        std::array<int, 6> nlayer_VTX{0, 0, 0, 0, 0, 0};
-        std::array<int, 3> nlayer_SIT{0, 0, 0};
-        std::array<int, 3> nlayer_FTD{0, 0, 0};
-
-        for (auto trackState : cur_track.trackStates())
-        {
-            if (trackState.hasUncalibratedSourceLink())
-            {
-                auto cur_measurement_sl = trackState.getUncalibratedSourceLink();
-                const auto& MeasSourceLink = cur_measurement_sl.get<IndexSourceLink>();
-                auto cur_measurement = measurements[MeasSourceLink.index()];
-                auto cur_measurement_gid = MeasSourceLink.geometryId();
-                
-                for (int i = 0; i < 5; i++)
-                {
-                    if (cur_measurement_gid.volume() == VXD_volume_ids[i])
-                    {
-                        if (i == 4){
-                            if (cur_measurement_gid.sensitive() & 1 == 1)
-                            {
-                                nlayer_VTX[5]++;
-                            } else{
-                                nlayer_VTX[4]++;
-                            }
-                        } else {
-                            nlayer_VTX[i]++;
-                        }
-                        nVTXHit++;
-                        break;
-                    }
-                }
-
-                for (int i = 0; i < 3; i++){
-                    if (cur_measurement_gid.volume() == SIT_volume_ids[i]){
-                        nlayer_SIT[i]++;
-                        nSITHit++;
-                        break;
-                    }
-                }
-
-                for (int i = 0; i < 3; i++){
-                    if (cur_measurement_gid.volume() == FTD_positive_volume_ids[i]){
-                        nlayer_FTD[i]++;
-                        nFTDHit++;
-                        break;
-                    }
-
-                    if (cur_measurement_gid.volume() == FTD_negative_volume_ids[i]){
-                        nlayer_FTD[i]++;
-                        nFTDHit++;
-                        break;
-                    }
-                }
-
-                writeout_track.addToTrackerHits(MeasSourceLink.getTrackerHit());
-
-                if (!getFirstHit){
-                    const auto& par = std::get<1>(cur_measurement).parameters();
-                    const Acts::Surface* surface = surfaceAccessor(cur_measurement_sl);
-                    auto acts_global_postion = surface->localToGlobal(geoContext, par, globalFakeMom);
-                    writeout_track.setRadiusOfInnermostHit(
-                    std::sqrt(acts_global_postion[0] * acts_global_postion[0] + 
-                              acts_global_postion[1] * acts_global_postion[1] + 
-                              acts_global_postion[2] * acts_global_postion[2])
-                    );
-                    getFirstHit = 1;
-                }
-            }
-        }
-        for (int i = 0; i < 6; i++){
-            m_nRec_VTX[i] += nlayer_VTX[i];
-            if (nlayer_VTX[i] == 0) {
-                m_n0EventHits[i]++;
-                nlayerHits--;
-            } else if (nlayer_VTX[i] == 1) {
-                m_n1EventHits[i]++;
-            } else if (nlayer_VTX[i] == 2) {
-                m_n2EventHits[i]++;
-            } else if (nlayer_VTX[i] >= 3) {
-                m_n3EventHits[i]++;
-            }
-        }
-        for (int i = 0; i < 3; i++){
-            m_nRec_SIT[i] += nlayer_SIT[i];
-            if (nlayer_SIT[i] == 0) {
-                m_n0EventHits[i+6]++;
-                nlayerHits--;
-            } else if (nlayer_SIT[i] == 1) {
-                m_n1EventHits[i+6]++;
-            } else if (nlayer_SIT[i] == 2) {
-                m_n2EventHits[i+6]++;
-            } else if (nlayer_SIT[i] >= 3) {
-                m_n3EventHits[i+6]++;
-            }
-        }
-
-        for (int i = 0; i < 3; i++){
-            m_nRec_FTD[i] += nlayer_FTD[i];
-        }
-
-        // SubdetectorHitNumbers: VXD = 0, FTD = 1, SIT = 2
-        writeout_track.setDEdxError(nlayerHits);
-        writeout_track.addToSubdetectorHitNumbers(nVTXHit);
-        writeout_track.addToSubdetectorHitNumbers(nFTDHit);
-        writeout_track.addToSubdetectorHitNumbers(nSITHit);
-        
-        // TODO: covmatrix need to be converted
-        std::array<float, 21> writeout_covMatrix;
-        auto cur_track_covariance = cur_track.covariance();
-        for (int i = 0; i < 6; i++) {
-            for (int j = 0; j < 6-i; j++) {
-                writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
-            }
-        }
-        // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
-        // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
-        edm4hep::TrackState writeout_trackState{
-            1, // location: AtOther
-            cur_track.loc0() / Acts::UnitConstants::mm, // d0
-            cur_track.phi(), // phi
-            cur_track.qOverP() * sin(cur_track.theta()) * _FCT * m_field, // omega = qop * sin(theta) * _FCT * bf
-            cur_track.loc1() / Acts::UnitConstants::mm, // z0
-            1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
-            cur_track.time(), // time
-            ::edm4hep::Vector3f(0, 0, 0), // referencePoint
-            writeout_covMatrix
-        };
-
-        debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " !" << endmsg;
-        writeout_track.addToTrackStates(writeout_trackState);
-    }
-
-    chronoStatSvc->chronoStop("writeout tracks");
-
-    _nEvt++;
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTracking::finalize()
-{
-    debug() << "finalize RecActsTracking" << endmsg;
-    info() << "Total number of events processed: " << _nEvt << endmsg;
-    info() << "Total number of **TotalSeeds** processed: " << m_nTotalSeeds << endmsg;
-    info() << "Total number of **FoundTracks** processed: " << m_nFoundTracks << endmsg;
-    info() << "Total number of **SelectedTracks** processed: " << m_nSelectedTracks << endmsg;
-    info() << "Total number of **LayerHits** processed: " << m_nLayerHits << endmsg;
-    info() << "Total number of **Rec_VTX** processed: " << m_nRec_VTX << endmsg;
-    info() << "Total number of **Rec_SIT** processed: " << m_nRec_SIT << endmsg;
-    info() << "Total number of **Rec_FTD** processed: " << m_nRec_FTD << endmsg;
-    info() << "Total number of **EventHits0** processed: " << m_n0EventHits << endmsg;
-    info() << "Total number of **EventHits1** processed: " << m_n1EventHits << endmsg;
-    info() << "Total number of **EventHits2** processed: " << m_n2EventHits << endmsg;
-    info() << "Total number of **EventHitsmore** processed: " << m_n3EventHits << endmsg;
-
-    return GaudiAlgorithm::finalize();
-}
-
-int RecActsTracking::InitialiseVTX()
-{   
-    int success = 1;
-
-    const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitVTXCol = nullptr;
-
-    try {
-        hitVTXCol = _inVTXTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        success = 0;
-    }
-
-    try {
-        SimhitVTXCol = _inVTXColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Sim Collection " << _inVTXColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        success = 0;
-    }
-
-    if(hitVTXCol && SimhitVTXCol)
-    {
-        int nelem = hitVTXCol->size();
-        debug() << "Number of VTX hits = " << nelem << endmsg;
-        if ((nelem < 3) or (nelem > 10)) { success = 0; }
-
-        // std::string truth_file = "obj/vtx/truth/event" + std::to_string(_nEvt) + ".csv";
-        // std::ofstream truth_stream(truth_file);
-        // csv2::Writer<csv2::delimiter<','>> truth_writer(truth_stream);
-        // std::vector<std::string> truth_header = {"layer", "x", "y", "z"};
-        // truth_writer.write_row(truth_header);
-
-        // std::string converted_file = "obj/vtx/converted/event" + std::to_string(_nEvt) + ".csv";
-        // std::ofstream converted_stream(converted_file);
-        // csv2::Writer<csv2::delimiter<','>> converted_writer(converted_stream);
-        // std::vector<std::string> converted_header = {"layer", "x", "y", "z"};
-        // converted_writer.write_row(converted_header);
-
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitVTXCol->at(ielem);
-            auto simhit = SimhitVTXCol->at(ielem);
-
-            auto simcellid = simhit.getCellID();
-            // system:5,side:-2,layer:9,module:8,sensor:32:8
-            uint64_t m_layer   = vxd_decoder->get(simcellid, "layer");
-            uint64_t m_module  = vxd_decoder->get(simcellid, "module");
-            uint64_t m_sensor  = vxd_decoder->get(simcellid, "sensor");
-            double acts_x = simhit.getPosition()[0];
-            double acts_y = simhit.getPosition()[1];
-            double acts_z = simhit.getPosition()[2];
-
-            double momentum_x = simhit.getMomentum()[0];
-            double momentum_y = simhit.getMomentum()[1];
-            double momentum_z = simhit.getMomentum()[2];
-
-            const Acts::Vector3 globalmom{momentum_x, momentum_y, momentum_z};
-            std::array<float, 6> m_covMatrix = hit.getCovMatrix();
-
-            dd4hep::rec::ISurface* surface = nullptr;
-            auto it = m_vtx_surfaces->find(simcellid);
-            if (it != m_vtx_surfaces->end()) {
-                surface = it->second;
-                if (!surface) {
-                    fatal() << "found surface for VTX cell id " << simcellid << ", but NULL" << endmsg;
-                    return 0;
-                }
-            }
-            else {
-                fatal() << "not found surface for VTX cell id " << simcellid << endmsg;
-                return 0;
-            }
-
-            // dd4hep::rec::Vector3D oldPos(simhit.getPosition()[0]*dd4hep::mm/CLHEP::mm, simhit.getPosition()[1]*dd4hep::mm/CLHEP::mm, simhit.getPosition()[2]*dd4hep::mm/CLHEP::mm);
-            // dd4hep::rec::Vector2D localPoint = surface->globalToLocal(oldPos);
-
-            // if (m_layer < current_layer){
-            //     info() << "ring hits happend in layer " << m_layer << " before layer " << current_layer << ", at event " << _nEvt << endmsg;
-            //     success = 0;
-            //     break;
-            // }
-            // current_layer = m_layer;
-
-            if (m_layer <= 3){
-                // set acts geometry identifier
-                uint64_t acts_volume = VXD_volume_ids[m_layer];
-                uint64_t acts_boundary = 0;
-                uint64_t acts_layer = 2;
-                uint64_t acts_approach = 0;
-                // uint64_t acts_sensitive = m_module + 1;
-                uint64_t acts_sensitive = 1;
-
-                Acts::GeometryIdentifier moduleGeoId;
-                moduleGeoId.setVolume(acts_volume);
-                moduleGeoId.setBoundary(acts_boundary);
-                moduleGeoId.setLayer(acts_layer);
-                moduleGeoId.setApproach(acts_approach);
-                moduleGeoId.setSensitive(acts_sensitive);
-        
-                // create and store the source link
-                uint32_t measurementIdx = measurements.size();
-                IndexSourceLink sourceLink{moduleGeoId, measurementIdx, hit};
-                sourceLinks.insert(sourceLinks.end(), sourceLink);
-                Acts::SourceLink sl{sourceLink};
-                boost::container::static_vector<Acts::SourceLink, 2> slinks;
-                slinks.emplace_back(sl);
-
-                // get the surface of the hit
-                IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
-                const Acts::Surface* acts_surface = surfaceAccessor(sl);
-
-                // get the local position of the hit
-                const Acts::Vector3 globalPos{acts_x, acts_y, acts_z};
-                auto acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, onSurfaceTolerance);
-                if (!acts_local_postion.ok()){
-                    info() << "Error: failed to get local position for VTX hit " << simcellid << endmsg;
-                    acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, 100*onSurfaceTolerance);
-                }
-                const std::array<Acts::BoundIndices, 2> indices{Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundLoc1};
-                const Acts::Vector2 par{acts_local_postion.value()[0], acts_local_postion.value()[1]};
-
-                // *** debug ***
-                debug() << "VXD measurements global position(x,y,z): " << simhit.getPosition()[0] << ", " << simhit.getPosition()[1] << ", " << simhit.getPosition()[2]
-                       << "; local position(loc0, loc1): "<< acts_local_postion.value()[0] << ", " << acts_local_postion.value()[1] << endmsg;
-                auto acts_global_postion = acts_surface->localToGlobal(geoContext, par, globalFakeMom);
-                debug() << "debug surface at: x:" << acts_global_postion[0] << ", y:" << acts_global_postion[1] << ", z:" << acts_global_postion[2] << endmsg;
-
-                // SimSpacePoint *hitExt = new SimSpacePoint(hit, simhit, slinks);
-                SimSpacePoint *hitExt = new SimSpacePoint(hit, acts_global_postion[0], acts_global_postion[1], acts_global_postion[2], 0.002, slinks);
-                // debug() << "debug hitExt at: x:" << hitExt->x() << ", y:" << hitExt->y() << ", z:" << hitExt->z() << endmsg;
-                SpacePointPtrs.push_back(hitExt);
-
-                // create and store the measurement
-                // Cylinder covMatrix[6] = {resU*resU/2, 0, resU*resU/2, 0, 0, resV*resV}
-                Acts::ActsSquareMatrix<2> cov = Acts::ActsSquareMatrix<2>::Identity();
-                cov(0, 0) = std::max<double>(double(std::sqrt(m_covMatrix[2]*2)), eps.value());
-                cov(1, 1) = std::max<double>(double(std::sqrt(m_covMatrix[5])), eps.value());
-                measurements.emplace_back(Acts::Measurement<Acts::BoundIndices, 2>(sl, indices, par, cov));
-
-                // std::vector<std::string> truth_col = {std::to_string(m_layer*2 + m_module), std::to_string(simhit.getPosition()[0]), std::to_string(simhit.getPosition()[1]), std::to_string(simhit.getPosition()[2])};
-                // truth_writer.write_row(truth_col);
-                // std::vector<std::string> converted_col = {std::to_string(m_layer*2 + m_module), std::to_string(acts_global_postion[0]), std::to_string(acts_global_postion[1]), std::to_string(acts_global_postion[2])};
-                // converted_writer.write_row(converted_col);
-
-            } else {
-                // set acts geometry identifier
-                uint64_t acts_volume = VXD_volume_ids[4];
-                uint64_t acts_boundary = 0;
-                uint64_t acts_layer = 2;
-                uint64_t acts_approach = 0;
-                uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;
-
-                Acts::GeometryIdentifier moduleGeoId;
-                moduleGeoId.setVolume(acts_volume);
-                moduleGeoId.setBoundary(acts_boundary);
-                moduleGeoId.setLayer(acts_layer);
-                moduleGeoId.setApproach(acts_approach);
-                moduleGeoId.setSensitive(acts_sensitive);
-        
-                // create and store the source link
-                uint32_t measurementIdx = measurements.size();
-                IndexSourceLink sourceLink{moduleGeoId, measurementIdx, hit};
-                sourceLinks.insert(sourceLinks.end(), sourceLink);
-                Acts::SourceLink sl{sourceLink};
-                boost::container::static_vector<Acts::SourceLink, 2> slinks;
-                slinks.emplace_back(sl);
-
-                // get the local position of the hit
-                IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
-                const Acts::Surface* acts_surface = surfaceAccessor(sl);
-                const Acts::Vector3 globalPos{acts_x, acts_y, acts_z};
-                auto acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, onSurfaceTolerance);
-                if (!acts_local_postion.ok()){
-                    info() << "Error: failed to get local position for VTX hit " << simcellid << endmsg;
-                    acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, 100*onSurfaceTolerance);
-                }
-                const std::array<Acts::BoundIndices, 2> indices{Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundLoc1};
-                const Acts::Vector2 par{acts_local_postion.value()[0], acts_local_postion.value()[1]};
-
-                // *** debug ***
-                debug() << "VXD measurements global position(x,y,z): " << simhit.getPosition()[0] << ", " << simhit.getPosition()[1] << ", " << simhit.getPosition()[2]
-                       << "; local position(loc0, loc1): "<< acts_local_postion.value()[0] << ", " << acts_local_postion.value()[1] << endmsg;
-                auto acts_global_postion = acts_surface->localToGlobal(geoContext, par, globalFakeMom);
-                debug() << "debug surface at: x:" << acts_global_postion[0] << ", y:" << acts_global_postion[1] << ", z:" << acts_global_postion[2] << endmsg;
-
-                if (ExtendSeedRange.value()) {
-                    SimSpacePoint *hitExt = new SimSpacePoint(hit, acts_global_postion[0], acts_global_postion[1], acts_global_postion[2], 0.002, slinks);
-                    SpacePointPtrs.push_back(hitExt);
-                }
-                
-                // create and store the measurement
-                // Plane covMatrix[6] = {u_direction[0], u_direction[1], resU, v_direction[0], v_direction[1], resV}
-                Acts::ActsSquareMatrix<2> cov = Acts::ActsSquareMatrix<2>::Identity();
-                cov(0, 0) = std::max<double>(double(m_covMatrix[2]), eps.value());
-                cov(1, 1) = std::max<double>(double(m_covMatrix[5]), eps.value());
-                measurements.emplace_back(Acts::Measurement<Acts::BoundIndices, 2>(sl, indices, par, cov));
-
-                // std::vector<std::string> truth_col = {std::to_string(m_layer+4), std::to_string(simhit.getPosition()[0]), std::to_string(simhit.getPosition()[1]), std::to_string(simhit.getPosition()[2])};
-                // truth_writer.write_row(truth_col);
-                // std::vector<std::string> converted_col = {std::to_string(m_layer+4), std::to_string(acts_global_postion[0]), std::to_string(acts_global_postion[1]), std::to_string(acts_global_postion[2])};
-                // converted_writer.write_row(converted_col);
-            }
-        }
-    } else { success = 0; }
-
-    return success;
-}
-
-int RecActsTracking::InitialiseSIT()
-{    
-    int success = 1;
-
-    const edm4hep::TrackerHitCollection* hitSITCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitSITCol = nullptr;
-
-    double min_z = 0;
-    try {
-        hitSITCol = _inSITTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inSITTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        success = 0;
-    }
-
-    try {
-        SimhitSITCol = _inSITColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Sim Collection " << _inSITColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        success = 0;
-    }
-
-    if(hitSITCol && SimhitSITCol)
-    {
-        int nelem = hitSITCol->size();
-        debug() << "Number of SIT hits = " << nelem << endmsg;
-        // SpacePointPtrs.resize(nelem);
-
-        // std::string truth_file = "obj/sit/truth/event" + std::to_string(_nEvt) + ".csv";
-        // std::ofstream truth_stream(truth_file);
-        // csv2::Writer<csv2::delimiter<','>> truth_writer(truth_stream);
-        // std::vector<std::string> truth_header = {"layer", "x", "y", "z"};
-        // truth_writer.write_row(truth_header);
-
-        // std::string converted_file = "obj/sit/converted/event" + std::to_string(_nEvt) + ".csv";
-        // std::ofstream converted_stream(converted_file);
-        // csv2::Writer<csv2::delimiter<','>> converted_writer(converted_stream);
-        // std::vector<std::string> converted_header = {"layer", "x", "y", "z"};
-        // converted_writer.write_row(converted_header);
-
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitSITCol->at(ielem);
-            auto simhit = SimhitSITCol->at(ielem);
-
-            auto simcellid = simhit.getCellID();
-            // <id>system:5,side:-2,layer:9,stave:8,module:8,sensor:5,y:-11,z:-11</id>
-            uint64_t m_layer   = sit_decoder->get(simcellid, "layer");
-            uint64_t m_stave   = sit_decoder->get(simcellid, "stave");
-            uint64_t m_module  = sit_decoder->get(simcellid, "module");
-            uint64_t m_sensor  = sit_decoder->get(simcellid, "sensor");
-            double acts_x = simhit.getPosition()[0];
-            double acts_y = simhit.getPosition()[1];
-            double acts_z = simhit.getPosition()[2];
-            double momentum_x = simhit.getMomentum()[0];
-            double momentum_y = simhit.getMomentum()[1];
-            double momentum_z = simhit.getMomentum()[2];
-            const Acts::Vector3 globalmom{momentum_x, momentum_y, momentum_z};
-            std::array<float, 6> m_covMatrix = hit.getCovMatrix();
-
-            dd4hep::rec::ISurface* surface = nullptr;
-            auto it = m_sit_surfaces->find(simcellid);
-            if (it != m_sit_surfaces->end()) {
-                surface = it->second;
-                if (!surface) {
-                    fatal() << "found surface for SIT cell id " << simcellid << ", but NULL" << endmsg;
-                    return 0;
-                }
-            }
-            else {
-                fatal() << "not found surface for SIT cell id " << simcellid << endmsg;
-                return 0;
-            }
-
-            // set acts geometry identifier
-            uint64_t acts_volume = SIT_volume_ids[m_layer];
-            uint64_t acts_boundary = 0;
-            uint64_t acts_layer = 2;
-            uint64_t acts_approach = 0;
-            uint64_t acts_sensitive = m_stave*SIT_module_nums[m_layer] + m_module + 1;
-
-            Acts::GeometryIdentifier moduleGeoId;
-            moduleGeoId.setVolume(acts_volume);
-            moduleGeoId.setBoundary(acts_boundary);
-            moduleGeoId.setLayer(acts_layer);
-            moduleGeoId.setApproach(acts_approach);
-            moduleGeoId.setSensitive(acts_sensitive);
-    
-            // create and store the source link
-            uint32_t measurementIdx = measurements.size();
-            IndexSourceLink sourceLink{moduleGeoId, measurementIdx, hit};
-            sourceLinks.insert(sourceLinks.end(), sourceLink);
-            Acts::SourceLink sl{sourceLink};
-            boost::container::static_vector<Acts::SourceLink, 2> slinks;
-            slinks.emplace_back(sl);
-
-            // get the local position of the hit
-            IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
-            const Acts::Surface* acts_surface = surfaceAccessor(sl);
-            const Acts::Vector3 globalPos{acts_x, acts_y, acts_z};
-            auto acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, onSurfaceTolerance);
-            if (!acts_local_postion.ok()){
-                info() << "Error: failed to get local position for SIT hit " << simcellid << endmsg;
-                acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, 100*onSurfaceTolerance);
-            }
-            const std::array<Acts::BoundIndices, 2> indices{Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundLoc1};
-            const Acts::Vector2 par{acts_local_postion.value()[0], acts_local_postion.value()[1]};
-
-            // *** debug ***
-            debug() << "SIT measurements global position(x,y,z): " << simhit.getPosition()[0] << ", " << simhit.getPosition()[1] << ", " << simhit.getPosition()[2]
-                << "; local position(loc0, loc1): "<< acts_local_postion.value()[0] << ", " << acts_local_postion.value()[1] << endmsg;
-            auto acts_global_postion = acts_surface->localToGlobal(geoContext, par, globalFakeMom);
-            debug() << "debug surface at: x:" << acts_global_postion[0] << ", y:" << acts_global_postion[1] << ", z:" << acts_global_postion[2] << endmsg;
-
-            if (ExtendSeedRange.value()) {
-                SimSpacePoint *hitExt = new SimSpacePoint(hit, acts_global_postion[0], acts_global_postion[1], acts_global_postion[2], 0.002, slinks);
-                SpacePointPtrs.push_back(hitExt);
-            }
-
-            // create and store the measurement
-            Acts::ActsSquareMatrix<2> cov = Acts::ActsSquareMatrix<2>::Identity();
-            cov(0, 0) = std::max<double>(double(m_covMatrix[2]), eps.value());
-            cov(1, 1) = std::max<double>(double(m_covMatrix[5]), eps.value());
-            measurements.emplace_back(Acts::Measurement<Acts::BoundIndices, 2>(sl, indices, par, cov));
-
-            // std::vector<std::string> truth_col = {std::to_string(m_layer), std::to_string(simhit.getPosition()[0]), std::to_string(simhit.getPosition()[1]), std::to_string(simhit.getPosition()[2])};
-            // truth_writer.write_row(truth_col);
-            // std::vector<std::string> converted_col = {std::to_string(m_layer), std::to_string(acts_global_postion[0]), std::to_string(acts_global_postion[1]), std::to_string(acts_global_postion[2])};
-            // converted_writer.write_row(converted_col);
-        }
-    } else { success = 0; }
-
-    return success;
-}
-
-int RecActsTracking::InitialiseFTD()
-{
-    const edm4hep::TrackerHitCollection* hitFTDCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitFTDCol = nullptr;
-
-    try {
-        hitFTDCol = _inFTDTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inFTDTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return 0;
-    }
-
-    try {
-        SimhitFTDCol = _inFTDColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inFTDColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return 0;
-    }
-
-    if (hitFTDCol && SimhitFTDCol)
-    {
-        int nelem = hitFTDCol->size();
-        debug() << "Number of FTD hits = " << nelem << endmsg;
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitFTDCol->at(ielem);
-            auto simhit = SimhitFTDCol->at(ielem);
-            auto simcellid = simhit.getCellID();
-            uint64_t m_system = ftd_decoder->get(simcellid, "system");
-            uint64_t m_side = ftd_decoder->get(simcellid, "side");
-            uint64_t m_layer = ftd_decoder->get(simcellid, "layer");
-            uint64_t m_module = ftd_decoder->get(simcellid, "module");
-            uint64_t m_sensor = ftd_decoder->get(simcellid, "sensor");
-            double acts_x = simhit.getPosition()[0];
-            double acts_y = simhit.getPosition()[1];
-            double acts_z = simhit.getPosition()[2];
-            double momentum_x = simhit.getMomentum()[0];
-            double momentum_y = simhit.getMomentum()[1];
-            double momentum_z = simhit.getMomentum()[2];
-            const Acts::Vector3 globalmom{momentum_x, momentum_y, momentum_z};
-            std::array<float, 6> m_covMatrix = hit.getCovMatrix();
-            
-            if (m_layer > 2) {
-                continue;
-            }
-
-            dd4hep::rec::ISurface* surface = nullptr;
-            auto it = m_ftd_surfaces->find(simcellid);
-            if (it != m_ftd_surfaces->end()) {
-                surface = it->second;
-                if (!surface) {
-                    fatal() << "found surface for FTD cell id " << simcellid << ", but NULL" << endmsg;
-                    return 0;
-                }
-            }
-
-            // set acts geometry identifier
-            uint64_t acts_volume = (acts_z > 0) ? FTD_positive_volume_ids[m_layer] : FTD_negative_volume_ids[m_layer];
-            uint64_t acts_boundary = 0;
-            uint64_t acts_layer = 2;
-            uint64_t acts_approach = 0;
-            uint64_t acts_sensitive = m_module + 1;
-
-            Acts::GeometryIdentifier moduleGeoId;
-            moduleGeoId.setVolume(acts_volume);
-            moduleGeoId.setBoundary(acts_boundary);
-            moduleGeoId.setLayer(acts_layer);
-            moduleGeoId.setApproach(acts_approach);
-            moduleGeoId.setSensitive(acts_sensitive);   
-
-            // create and store the source link
-            uint32_t measurementIdx = measurements.size();
-            IndexSourceLink sourceLink{moduleGeoId, measurementIdx, hit};
-            sourceLinks.insert(sourceLinks.end(), sourceLink);
-            Acts::SourceLink sl{sourceLink};    
-            boost::container::static_vector<Acts::SourceLink, 2> slinks;
-            slinks.emplace_back(sl);
-
-            // get the local position of the hit
-            IndexSourceLink::SurfaceAccessor surfaceAccessor{*trackingGeometry};
-            const Acts::Surface* acts_surface = surfaceAccessor(sl);
-            const Acts::Vector3 globalPos{acts_x, acts_y, acts_z};
-            auto acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, onSurfaceTolerance);
-            if (!acts_local_postion.ok()){
-                info() << "Error: failed to get local position for FTD layer " << m_layer << " module " << m_module << " sensor " << m_sensor << endmsg;
-                acts_local_postion = acts_surface->globalToLocal(geoContext, globalPos, globalmom, 100*onSurfaceTolerance);
-            }
-            const std::array<Acts::BoundIndices, 2> indices{Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundLoc1};
-            const Acts::Vector2 par{acts_local_postion.value()[0], acts_local_postion.value()[1]};
-
-            // debug
-            debug() << "FTD measurements global position(x,y,z): " << simhit.getPosition()[0] << ", " << simhit.getPosition()[1] << ", " << simhit.getPosition()[2]
-                << "; local position(loc0, loc1): "<< acts_local_postion.value()[0] << ", " << acts_local_postion.value()[1] << endmsg;
-            auto acts_global_postion = acts_surface->localToGlobal(geoContext, par, globalFakeMom);
-            debug() << "debug surface at: x:" << acts_global_postion[0] << ", y:" << acts_global_postion[1] << ", z:" << acts_global_postion[2] << endmsg;
-
-            if (ExtendSeedRange.value()) {
-                SimSpacePoint *hitExt = new SimSpacePoint(hit, acts_global_postion[0], acts_global_postion[1], acts_global_postion[2], 0.002, slinks);
-                SpacePointPtrs.push_back(hitExt);
-            }
-
-            // create and store the measurement
-            Acts::ActsSquareMatrix<2> cov = Acts::ActsSquareMatrix<2>::Identity();
-            cov(0, 0) = std::max<double>(double(m_covMatrix[2]), eps.value());
-            cov(1, 1) = std::max<double>(double(m_covMatrix[5]), eps.value());
-            measurements.emplace_back(Acts::Measurement<Acts::BoundIndices, 2>(sl, indices, par, cov));
-        }
-    }
-
-    return 1;
-}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/src/RecActsTracking.h b/Reconstruction/RecActsTracking/src/RecActsTracking.h
deleted file mode 100644
index f7d44417..00000000
--- a/Reconstruction/RecActsTracking/src/RecActsTracking.h
+++ /dev/null
@@ -1,320 +0,0 @@
-#ifndef RecActsTracking_H
-#define RecActsTracking_H
-
-#include <iostream>
-#include <fstream>
-#include <cstdlib>
-#include <sstream>
-#include <filesystem>
-
-#include "k4FWCore/DataHandle.h"
-#include "GaudiAlg/GaudiAlgorithm.h"
-#include "DD4hep/Detector.h"
-#include "DDRec/DetectorData.h"
-#include "DDRec/ISurface.h"
-#include "DDRec/SurfaceManager.h"
-#include "DDRec/Vector3D.h"
-
-#include "UTIL/ILDConf.h"
-#include "GaudiKernel/NTuple.h"
-#include "DetInterface/IGeomSvc.h"
-
-// gear
-#include "GearSvc/IGearSvc.h"
-#include <gear/GEAR.h>
-#include <gear/GearMgr.h>
-#include <gear/GearParameters.h>
-#include <gear/VXDLayerLayout.h>
-#include <gear/VXDParameters.h>
-#include "gear/FTDLayerLayout.h"
-#include "gear/FTDParameters.h"
-#include <gear/BField.h>
-
-// edm4hep
-#include "edm4hep/MCParticle.h"
-#include "edm4hep/Track.h"
-#include "edm4hep/MutableTrack.h"
-// #include "edm4hep/TrackerHit.h"
-// #include "edm4hep/SimTrackerHit.h"
-#include "edm4hep/TrackState.h"
-#include "edm4hep/EventHeaderCollection.h"
-#include "edm4hep/MCParticleCollection.h"
-#include "edm4hep/SimTrackerHitCollection.h"
-#include "edm4hep/TrackerHitCollection.h"
-#include "edm4hep/TrackCollection.h"
-#include "edm4hep/MCRecoTrackerAssociationCollection.h"
-
-// acts
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/Definitions/Direction.hpp"
-#include "Acts/Definitions/TrackParametrization.hpp"
-
-#include "Acts/EventData/MultiTrajectory.hpp"
-#include "Acts/EventData/ProxyAccessor.hpp"
-#include "Acts/EventData/SpacePointData.hpp"
-#include <Acts/EventData/Measurement.hpp>
-#include "Acts/EventData/TrackParameters.hpp"
-#include "Acts/EventData/TrackContainer.hpp"
-#include "Acts/EventData/VectorMultiTrajectory.hpp"
-#include "Acts/EventData/VectorTrackContainer.hpp"
-#include "Acts/EventData/ParticleHypothesis.hpp"
-
-#include "Acts/Propagator/AbortList.hpp"
-#include "Acts/Propagator/EigenStepper.hpp"
-#include "Acts/Propagator/MaterialInteractor.hpp"
-#include "Acts/Propagator/Navigator.hpp"
-#include "Acts/Propagator/Propagator.hpp"
-#include "Acts/Propagator/StandardAborters.hpp"
-
-#include "Acts/Geometry/Extent.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include <Acts/Geometry/GeometryContext.hpp>
-
-#include "Acts/Seeding/BinnedGroup.hpp"
-#include "Acts/Seeding/EstimateTrackParamsFromSeed.hpp"
-#include "Acts/Seeding/InternalSpacePoint.hpp"
-#include "Acts/Seeding/SeedFilter.hpp"
-#include "Acts/Seeding/SeedFilterConfig.hpp"
-#include "Acts/Seeding/SeedFinder.hpp"
-#include "Acts/Seeding/SeedFinderConfig.hpp"
-#include "Acts/Seeding/SpacePointGrid.hpp"
-
-#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
-#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
-#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
-#include "Acts/TrackFitting/KalmanFitter.hpp"
-
-#include "Acts/Surfaces/PerigeeSurface.hpp"
-#include "Acts/Surfaces/Surface.hpp"
-
-#include "Acts/Utilities/BinningType.hpp"
-#include "Acts/Utilities/Delegate.hpp"
-#include "Acts/Utilities/Grid.hpp"
-#include "Acts/Utilities/GridBinFinder.hpp"
-#include "Acts/Utilities/Helpers.hpp"
-#include "Acts/Utilities/Logger.hpp"
-#include "Acts/Utilities/Enumerate.hpp"
-#include "Acts/Utilities/TrackHelpers.hpp"
-
-// local include
-#include "utils/TGeoDetector.hpp"
-#include "utils/CKFhelper.hpp"
-#include "utils/MagneticField.hpp"
-
-namespace gear{
-  class GearMgr;
-}
-
-namespace dd4hep {
-    namespace DDSegmentation {
-        class BitFieldCoder;
-    }
-    namespace rec{
-        class ISurface;
-    }
-}
-
-// Seeding: Construct track seeds from space points.
-// config for seed finding
-struct SeedingConfig
-{
-    /// Input space point collections.
-    ///
-    /// We allow multiple space point collections to allow different parts of
-    /// the detector to use different algorithms for space point construction,
-    /// e.g. single-hit space points for pixel-like detectors or double-hit
-    /// space points for strip-like detectors.
-    std::vector<std::string> inputSpacePoints;
-
-    /// Output track seed collection.
-    std::string outputSeeds;
-
-
-    Acts::SeedFilterConfig seedFilterConfig;
-    Acts::SeedFinderConfig<SimSpacePoint> seedFinderConfig;
-    Acts::CylindricalSpacePointGridConfig gridConfig;
-    Acts::CylindricalSpacePointGridOptions gridOptions;
-    Acts::SeedFinderOptions seedFinderOptions;
-
-    // allow for different values of rMax in gridConfig and seedFinderConfig
-    bool allowSeparateRMax = false;
-
-    // vector containing the map of z bins in the top and bottom layers
-    std::vector<std::pair<int, int>> zBinNeighborsTop;
-    std::vector<std::pair<int, int>> zBinNeighborsBottom;
-
-    // number of phiBin neighbors at each side of the current bin that will be
-    // used to search for SPs
-    int numPhiNeighbors = 1;
-};
-
-class RecActsTracking : public GaudiAlgorithm
-{
-
-    public :
-
-        RecActsTracking(const std::string& name, ISvcLocator* svcLoc);
-
-        virtual StatusCode initialize();
-
-        virtual StatusCode execute();
-
-        virtual StatusCode finalize();
-
-    private :
-
-        // Input collections
-        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inSITTrackHdl{"SITTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inFTDTrackHdl{"FTDTrackerHits", Gaudi::DataHandle::Reader, this};
-
-        DataHandle<edm4hep::SimTrackerHitCollection> _inVTXColHdl{"VXDCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inSITColHdl{"SITCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inFTDColHdl{"FTDCollection", Gaudi::DataHandle::Reader, this};
-
-        DataHandle<edm4hep::MCParticleCollection> _inMCColHdl{"MCParticle", Gaudi::DataHandle::Reader, this};
-
-        // Output collections
-        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSSiTracks", Gaudi::DataHandle::Writer, this};
-
-        // properties
-        Gaudi::Property<std::string> TGeo_path{this, "TGeoFile"};
-        Gaudi::Property<std::string> TGeo_config_path{this, "TGeoConfigFile"};
-        Gaudi::Property<std::string> MaterialMap_path{this, "MaterialMapFile"};
-        Gaudi::Property<std::string> m_particle{this, "AssumeParticle"};
-        Gaudi::Property<double> m_field{this, "Field", 3.0}; // tesla
-        Gaudi::Property<double> onSurfaceTolerance{this, "onSurfaceTolerance", 1e-2}; // mm
-        Gaudi::Property<double> eps{this, "eps", 1e-5}; // mm
-        Gaudi::Property<bool> ExtendSeedRange{this, "ExtendSeedRange", false};
-
-        // seed finder config
-        Gaudi::Property<double> SeedDeltaRMin{this, "SeedDeltaRMin", 4}; // mm
-        Gaudi::Property<double> SeedDeltaRMax{this, "SeedDeltaRMax", 13}; // mm
-        Gaudi::Property<double> SeedRMax{this, "SeedRMax", 30}; // mm
-        Gaudi::Property<double> SeedRMin{this, "SeedRMin", 10}; // mm
-        Gaudi::Property<double> SeedImpactMax{this, "SeedImpactMax", 3}; // mm
-        Gaudi::Property<double> SeedRMinMiddle{this, "SeedRMinMiddle", 14}; // mm
-        Gaudi::Property<double> SeedRMaxMiddle{this, "SeedRMaxMiddle", 24}; // mm
-        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 20, 20, 20, 20}};
-
-        // CKF config
-        Gaudi::Property<double> CKFchi2Cut{this, "CKFchi2Cut", std::numeric_limits<double>::max()};
-        Gaudi::Property<std::size_t> numMeasurementsCutOff{this, "numMeasurementsCutOff", 1u};
-        Gaudi::Property<bool> CKFtwoWay{this, "CKFtwoWay", true};
-        
-        SmartIF<IGeomSvc> m_geosvc;
-        SmartIF<IChronoStatSvc> chronoStatSvc;
-        dd4hep::DDSegmentation::BitFieldCoder *vxd_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *sit_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ftd_decoder;
-        const dd4hep::rec::SurfaceMap* m_vtx_surfaces;
-        const dd4hep::rec::SurfaceMap* m_sit_surfaces;
-        const dd4hep::rec::SurfaceMap* m_ftd_surfaces;
-
-        // configs to build acts geometry
-        Acts::GeometryContext geoContext;
-        Acts::MagneticFieldContext magFieldContext;  
-        Acts::CalibrationContext calibContext;
-        std::string TGeo_ROOTFilePath;
-        std::string TGeoConfig_jFilePath;
-        std::string MaterialMap_jFilePath;
-        std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry;
-        std::vector<std::shared_ptr<const Acts::TGeoDetectorElement>> detElementStore;
-
-        // Store the space points & measurements & sourcelinks **per event**
-        // * only store the VXD tracker hits to the SpacePointPtrs
-        std::vector<const SimSpacePoint*> SpacePointPtrs;
-        IndexSourceLinkContainer sourceLinks;
-        std::vector<::Acts::BoundVariantMeasurement> measurements;
-        std::vector<::Acts::BoundTrackParameters> initialParameters;
-        SimSeedContainer Selected_Seeds;
-
-        // seed finder
-        SeedingConfig seed_cfg;
-        std::unique_ptr<const Acts::GridBinFinder<2ul>> m_bottomBinFinder;
-        std::unique_ptr<const Acts::GridBinFinder<2ul>> m_topBinFinder;
-        Acts::SeedFinder<SimSpacePoint, Acts::CylindricalSpacePointGrid<SimSpacePoint>> m_seedFinder;
-
-        int InitialiseVTX();
-        int InitialiseSIT();
-        int InitialiseFTD();
-        const dd4hep::rec::ISurface* getISurface(edm4hep::TrackerHit* hit);
-        const Acts::GeometryIdentifier getVTXGid(uint64_t cellid);
-        const Acts::GeometryIdentifier getSITGid(uint64_t cellid);
-
-        // utils
-        int _nEvt;
-        const double _FCT = 2.99792458E-4;
-        Acts::Vector3 acts_field_value = Acts::Vector3(0., 0., 3*_FCT); // tesla
-        // Acts::Vector3 acts_field_value = Acts::Vector3(0., 0., 3); // tesla
-        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField
-            = std::make_shared<Acts::ConstantBField>(acts_field_value);
-        double bFieldMin = 0.3 * _FCT * Acts::UnitConstants::T;  // tesla
-        // double bFieldMin = 0.1 * Acts::UnitConstants::T;  // tesla
-        const Acts::Vector3 globalFakeMom{1.e6, 1.e6, 1.e6};
-        const std::array<Acts::BoundIndices, 6> writeout_indices{
-            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
-            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
-            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
-
-        // param estimate configuration
-        double noTimeVarInflation = 100.;
-        std::array<double, 6> initialSigmas = {
-            5 * Acts::UnitConstants::um,
-            5 * Acts::UnitConstants::um,
-            2e-2 * Acts::UnitConstants::degree,
-            2e-2 * Acts::UnitConstants::degree,
-            1e-1 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
-            1 * Acts::UnitConstants::s};
-        std::array<double, 6> initialSimgaQoverPCoefficients = {
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            7.1e-2 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            2.1e-2 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            6.4e-2,
-            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)};
-        // std::array<double, 6> initialVarInflation = {10., 10., 10., 10., 10., 10.};
-        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
-        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
-        // Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
-
-        // gid convert configuration
-        std::vector<uint64_t> VXD_volume_ids{20, 21, 22, 23, 24};
-        std::vector<uint64_t> SIT_volume_ids{28, 31, 34};
-        std::vector<uint64_t> FTD_positive_volume_ids{29, 32, 35};
-        std::vector<uint64_t> FTD_negative_volume_ids{27, 7, 2};
-        std::vector<uint64_t> SIT_module_nums{7, 10, 14};
-
-        // CKF configuration
-        // Acts::MeasurementSelector::Config measurementSelectorCfg;
-        std::shared_ptr<TrackFinderFunction> findTracks;
-        std::optional<Acts::TrackSelector> m_trackSelector;
-        std::optional<std::variant<Acts::TrackSelector::Config, Acts::TrackSelector::EtaBinnedConfig>> trackSelectorCfg = std::nullopt;
-        mutable std::atomic<std::size_t> m_nTotalSeeds{0};
-        mutable std::atomic<std::size_t> m_nDeduplicatedSeeds{0};
-        mutable std::atomic<std::size_t> m_nFailedSeeds{0};
-        mutable std::atomic<std::size_t> m_nFailedSmoothing{0};
-        mutable std::atomic<std::size_t> m_nFailedExtrapolation{0};
-        mutable std::atomic<std::size_t> m_nFoundTracks{0};
-        mutable std::atomic<std::size_t> m_nSelectedTracks{0};
-        mutable std::atomic<std::size_t> m_nStoppedBranches{0};
-        // layer hits, VXD (0-5) & SIT (6-8)
-        std::array<std::atomic<size_t>, 9> m_nLayerHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
-        std::array<std::atomic<size_t>, 6> m_nRec_VTX{0, 0, 0, 0, 0, 0};
-        std::array<std::atomic<size_t>, 3> m_nRec_SIT{0, 0, 0};
-        std::array<std::atomic<size_t>, 3> m_nRec_FTD{0, 0, 0};
-        std::array<std::atomic<size_t>, 9> m_n0EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
-        std::array<std::atomic<size_t>, 9> m_n1EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
-        std::array<std::atomic<size_t>, 9> m_n2EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
-        std::array<std::atomic<size_t>, 9> m_n3EventHits{0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-        mutable tbb::combinable<Acts::VectorMultiTrajectory::Statistics> m_memoryStatistics{[]() {
-            auto mtj = std::make_shared<Acts::VectorMultiTrajectory>();
-            return mtj->statistics();
-        }};
-        bool twoWay = false; /// Run finding in two directions
-        Acts::TrackExtrapolationStrategy extrapolationStrategy = Acts::TrackExtrapolationStrategy::firstOrLast; /// Extrapolation strategy
-        unsigned int maxSteps = 100000;
-};
-
-#endif  // RecActsTracking_H
diff --git a/Reconstruction/RecActsTracking/src/utils/CKFhelper.hpp b/Reconstruction/RecActsTracking/src/utils/CKFhelper.hpp
deleted file mode 100644
index 71dae438..00000000
--- a/Reconstruction/RecActsTracking/src/utils/CKFhelper.hpp
+++ /dev/null
@@ -1,388 +0,0 @@
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/Definitions/Direction.hpp"
-#include "Acts/Definitions/TrackParametrization.hpp"
-
-#include "Acts/EventData/ProxyAccessor.hpp"
-#include "Acts/EventData/TrackParameters.hpp"
-#include "Acts/EventData/MultiTrajectory.hpp"
-#include "Acts/EventData/SourceLink.hpp"
-#include "Acts/EventData/TrackContainer.hpp"
-#include "Acts/EventData/TrackProxy.hpp"
-#include <Acts/EventData/Measurement.hpp>
-#include "Acts/EventData/VectorMultiTrajectory.hpp"
-#include "Acts/EventData/VectorTrackContainer.hpp"
-
-#include "ActsFatras/Digitization/Segmentizer.hpp"
-
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Geometry/TrackingGeometry.hpp"
-#include "Acts/Geometry/GeometryContext.hpp"
-
-#include "Acts/Surfaces/PerigeeSurface.hpp"
-#include "Acts/Surfaces/Surface.hpp"
-
-#include "Acts/Propagator/AbortList.hpp"
-#include "Acts/Propagator/EigenStepper.hpp"
-#include "Acts/Propagator/MaterialInteractor.hpp"
-#include "Acts/Propagator/Navigator.hpp"
-#include "Acts/Propagator/Propagator.hpp"
-#include "Acts/Propagator/StandardAborters.hpp"
-
-
-#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
-#include "Acts/TrackFinding/MeasurementSelector.hpp"
-#include "Acts/TrackFinding/TrackSelector.hpp"
-#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
-#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
-#include "Acts/TrackFitting/KalmanFitter.hpp"
-
-#include "Acts/Utilities/Logger.hpp"
-#include "Acts/Utilities/Result.hpp"
-#include "Acts/Utilities/TrackHelpers.hpp"
-#include "Acts/Utilities/Delegate.hpp"
-#include "Acts/Utilities/Enumerate.hpp"
-#include "Acts/Utilities/TrackHelpers.hpp"
-#include "Acts/Utilities/CalibrationContext.hpp"
-
-#include <atomic>
-#include <cstddef>
-#include <functional>
-#include <limits>
-#include <memory>
-#include <optional>
-#include <string>
-#include <variant>
-#include <vector>
-#include <cmath>
-#include <ostream>
-#include <stdexcept>
-#include <system_error>
-#include <unordered_map>
-#include <utility>
-
-#include <tbb/combinable.h>
-#include <boost/functional/hash.hpp>
-
-#include "SimSpacePoint.hpp"
-
-namespace Acts
-{
-    class MagneticFieldProvider;
-    class TrackingGeometry;
-}
-
-using Updater = Acts::GainMatrixUpdater;
-using Smoother = Acts::GainMatrixSmoother;
-using Stepper = Acts::EigenStepper<>;
-using Navigator = Acts::Navigator;
-using Propagator = Acts::Propagator<Stepper, Navigator>;
-using CKF = Acts::CombinatorialKalmanFilter<Propagator, Acts::VectorMultiTrajectory>;
-
-// track container types
-using TrackContainer = Acts::TrackContainer<Acts::VectorTrackContainer, Acts::VectorMultiTrajectory, std::shared_ptr>;
-using ConstTrackContainer = Acts::TrackContainer<Acts::ConstVectorTrackContainer, Acts::ConstVectorMultiTrajectory, std::shared_ptr>;
-using TrackParameters = ::Acts::BoundTrackParameters;
-using TrackParametersContainer = std::vector<TrackParameters>;
-using TrackIndexType = TrackContainer::IndexType;
-using TrackProxy = TrackContainer::TrackProxy;
-using ConstTrackProxy = ConstTrackContainer::ConstTrackProxy;
-
-// track finder types
-using TrackFinderOptions = Acts::CombinatorialKalmanFilterOptions<IndexSourceLinkAccessor::Iterator, Acts::VectorMultiTrajectory>;
-using TrackFinderResult = Acts::Result<std::vector<TrackContainer::TrackProxy>>;
-
-// measurement types
-using Measurement = ::Acts::BoundVariantMeasurement;
-using MeasurementContainer = std::vector<Measurement>;
-
-class TrackFinderFunction
-{
-public:
-    virtual ~TrackFinderFunction() = default;
-    virtual TrackFinderResult operator()(const TrackParameters&, const TrackFinderOptions&, TrackContainer&) const = 0;
-};
-
-static std::shared_ptr<TrackFinderFunction> makeTrackFinderFunction(
-    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
-    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField,
-    const Acts::Logger& logger);
-
-struct TrackFinderFunctionImpl : public TrackFinderFunction {
-    CKF trackFinder;
-    TrackFinderFunctionImpl(CKF&& f) : trackFinder(std::move(f)) {}
-    TrackFinderResult operator()( const TrackParameters& initialParameters,
-                                  const TrackFinderOptions& options,
-                                  TrackContainer& tracks) const override
-    {
-        return trackFinder.findTracks(initialParameters, options, tracks);
-    };
-};
-
-std::shared_ptr<TrackFinderFunction> makeTrackFinderFunction(
-    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
-    std::shared_ptr<const Acts::MagneticFieldProvider> magneticField)
-{
-  Stepper stepper(magneticField);
-  Navigator::Config cfg{trackingGeometry};
-  cfg.resolvePassive = false;
-  cfg.resolveMaterial = true;
-  cfg.resolveSensitive = true;
-  Navigator navigator(cfg);
-  Propagator propagator(std::move(stepper), std::move(navigator));
-  CKF trackFinder(std::move(propagator));
-
-  return std::make_shared<TrackFinderFunctionImpl>(std::move(trackFinder));
-}
-
-struct Cluster
-{
-  using Cell = ActsFatras::Segmentizer::ChannelSegment;
-  std::size_t sizeLoc0 = 0;
-  std::size_t sizeLoc1 = 0;
-  std::vector<Cell> channels;
-};
-
-using ClusterContainer = std::vector<Cluster>;
-
-/// Abstract base class for measurement-based calibration
-class MeasurementCalibrator
-{
-    public:
-        virtual void calibrate(
-            const MeasurementContainer& measurements, const ClusterContainer* clusters,
-            const Acts::GeometryContext& gctx, const Acts::CalibrationContext& cctx, 
-            const Acts::SourceLink& sourceLink,
-            Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const = 0;
-        virtual ~MeasurementCalibrator() = default;
-        virtual bool needsClusters() const { return false; }
-};
-
-// Calibrator to convert an index source link to a measurement as-is
-class PassThroughCalibrator : public MeasurementCalibrator
-{
-    public:
-        /// Find the measurement corresponding to the source link.
-        ///
-        /// @tparam parameters_t Track parameters type
-        /// @param gctx The geometry context (unused)
-        /// @param trackState The track state to calibrate
-        void calibrate(
-            const MeasurementContainer& measurements,
-            const ClusterContainer* clusters, const Acts::GeometryContext& gctx,
-            const Acts::CalibrationContext& cctx, const Acts::SourceLink& sourceLink,
-            Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const override;
-};
-
-// Adapter class that wraps a MeasurementCalibrator to conform to the
-// core ACTS calibration interface
-class MeasurementCalibratorAdapter
-{
-    public:
-        MeasurementCalibratorAdapter(const MeasurementCalibrator& calibrator,
-                                     const MeasurementContainer& measurements,
-                                     const ClusterContainer* clusters = nullptr);
-        MeasurementCalibratorAdapter() = delete;
-        
-        void calibrate(const Acts::GeometryContext& gctx,
-                       const Acts::CalibrationContext& cctx,
-                       const Acts::SourceLink& sourceLink,
-                       Acts::VectorMultiTrajectory::TrackStateProxy trackState) const;
-
-    private:
-        const MeasurementCalibrator& m_calibrator;
-        const MeasurementContainer& m_measurements;
-        const ClusterContainer* m_clusters;
-};
-
-void PassThroughCalibrator::calibrate(
-    const MeasurementContainer& measurements, const ClusterContainer* /*clusters*/,
-    const Acts::GeometryContext& /*gctx*/, const Acts::CalibrationContext& /*cctx*/,
-    const Acts::SourceLink& sourceLink, Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const
-{
-    trackState.setUncalibratedSourceLink(sourceLink);
-    const IndexSourceLink& idxSourceLink = sourceLink.get<IndexSourceLink>();
-
-    assert((idxSourceLink.index() < measurements.size()) &&
-            "Source link index is outside the container bounds");
-
-    std::visit(
-        [&trackState](const auto& meas) { trackState.setCalibrated(meas); },
-        measurements[idxSourceLink.index()]);
-}
-
-MeasurementCalibratorAdapter::MeasurementCalibratorAdapter(
-    const MeasurementCalibrator& calibrator, const MeasurementContainer& measurements,
-    const ClusterContainer* clusters) : m_calibrator{calibrator}, m_measurements{measurements}, m_clusters{clusters} {}
-
-void MeasurementCalibratorAdapter::calibrate(
-    const Acts::GeometryContext& gctx, const Acts::CalibrationContext& cctx, const Acts::SourceLink& sourceLink,
-    Acts::VectorMultiTrajectory::TrackStateProxy trackState) const
-{
-    return m_calibrator.calibrate(m_measurements, m_clusters, gctx, cctx, sourceLink, trackState);
-}
-
-// Specialize std::hash for SeedIdentifier
-// This is required to use SeedIdentifier as a key in an `std::unordered_map`.
-template <class T, std::size_t N>
-struct std::hash<std::array<T, N>>
-{
-    std::size_t operator()(const std::array<T, N>& array) const
-    {
-        std::hash<T> hasher;
-        std::size_t result = 0;
-        for (auto&& element : array) { boost::hash_combine(result, hasher(element)); }
-        return result;
-    }
-};
-
-// Measurement selector for seed
-class MeasurementSelector
-{
-    public:
-        using Traj = Acts::VectorMultiTrajectory;
-        explicit MeasurementSelector(Acts::MeasurementSelector selector)
-            : m_selector(std::move(selector)) {}
-        
-        void setSeed(const std::optional<SimSeed>& seed) { m_seed = seed; }
-
-        Acts::Result<std::pair<std::vector<Traj::TrackStateProxy>::iterator,
-                               std::vector<Traj::TrackStateProxy>::iterator>>
-        select(std::vector<Traj::TrackStateProxy>& candidates,
-               bool& isOutlier, const Acts::Logger& logger) const
-        {
-            if (m_seed.has_value())
-            {
-                std::vector<Traj::TrackStateProxy> newCandidates;
-                for (const auto& candidate : candidates)
-                {
-                    if (isSeedCandidate(candidate)) { newCandidates.push_back(candidate); }
-                }
-
-                if (!newCandidates.empty()) { candidates = std::move(newCandidates); }
-            }
-
-            return m_selector.select<Acts::VectorMultiTrajectory>(candidates, isOutlier, logger);
-        }
-    
-    private:
-        Acts::MeasurementSelector m_selector;
-        std::optional<SimSeed> m_seed;
-
-        bool isSeedCandidate(const Traj::TrackStateProxy& candidate) const
-        {
-            assert(candidate.hasUncalibratedSourceLink());
-            const Acts::SourceLink& sourceLink = candidate.getUncalibratedSourceLink();
-            for (const auto& sp : m_seed->sp())
-            {
-                for (const auto& sl : sp->sourceLinks())
-                {
-                    if (sourceLink.get<IndexSourceLink>() == sl.get<IndexSourceLink>()) { return true; }
-                }
-            }
-            return false;
-        }
-
-}; // class MeasurementSelector
-
-/// Source link indices of the bottom, middle, top measurements.
-/// * In case of strip seeds only the first source link of the pair is used.
-using SeedIdentifier = std::array<Index, 3>;
-
-/// Build a seed identifier from a seed.
-///
-/// @param seed The seed to build the identifier from.
-/// @return The seed identifier.
-SeedIdentifier makeSeedIdentifier(const SimSeed& seed)
-{
-    SeedIdentifier result;
-    for (const auto& [i, sp] : Acts::enumerate(seed.sp()))
-    {
-        const Acts::SourceLink& firstSourceLink = sp->sourceLinks().front();
-        result.at(i) = firstSourceLink.get<IndexSourceLink>().index();
-    }
-    return result;
-}
-
-/// Visit all possible seed identifiers of a track.
-///
-/// @param track The track to visit the seed identifiers of.
-/// @param visitor The visitor to call for each seed identifier.
-template <typename Visitor>
-void visitSeedIdentifiers(const TrackProxy& track, Visitor visitor)
-{
-    // first we collect the source link indices of the track states
-    std::vector<Index> sourceLinkIndices;
-    sourceLinkIndices.reserve(track.nMeasurements());
-    for (const auto& trackState : track.trackStatesReversed()) 
-    {
-        if (!trackState.hasUncalibratedSourceLink()) { continue; }
-        const Acts::SourceLink& sourceLink = trackState.getUncalibratedSourceLink();
-        sourceLinkIndices.push_back(sourceLink.get<IndexSourceLink>().index());
-    }
-
-    // then we iterate over all possible triplets and form seed identifiers
-    for (std::size_t i = 0; i < sourceLinkIndices.size(); ++i)
-    {
-        for (std::size_t j = i + 1; j < sourceLinkIndices.size(); ++j)
-        {
-            for (std::size_t k = j + 1; k < sourceLinkIndices.size(); ++k)
-            {
-                // Putting them into reverse order (k, j, i) to compensate for the `trackStatesReversed` above.
-                visitor({sourceLinkIndices.at(k), sourceLinkIndices.at(j), sourceLinkIndices.at(i)});
-            }
-        }
-    }
-}
-
-class BranchStopper
-{
-    public:
-        using Config = std::optional<std::variant<Acts::TrackSelector::Config, Acts::TrackSelector::EtaBinnedConfig>>;
-        using BranchStopperResult = Acts::CombinatorialKalmanFilterBranchStopperResult;
-        
-        mutable std::atomic<std::size_t> m_nStoppedBranches{0};
-        explicit BranchStopper(const Config& config) : m_config(config) {}
-        
-        BranchStopperResult operator()( const Acts::CombinatorialKalmanFilterTipState& tipState,
-                                        Acts::VectorMultiTrajectory::TrackStateProxy& trackState ) const
-        {
-            if (!m_config.has_value()) { return BranchStopperResult::Continue; }
-
-            const Acts::TrackSelector::Config* singleConfig = std::visit
-            (
-                [&](const auto& config) -> const Acts::TrackSelector::Config*
-                {
-                    using T = std::decay_t<decltype(config)>;
-                    if constexpr (std::is_same_v<T, Acts::TrackSelector::Config>) { return &config; }
-                    else if constexpr (std::is_same_v<T, Acts::TrackSelector::EtaBinnedConfig>)
-                    {
-                        double theta = trackState.parameters()[Acts::eBoundTheta];
-                        double eta = -std::log(std::tan(0.5 * theta));
-                        return config.hasCuts(eta) ? &config.getCuts(eta) : nullptr;
-                    }
-                }, *m_config
-            );
-            
-            if (singleConfig == nullptr)
-            {
-                ++m_nStoppedBranches;
-                return BranchStopperResult::StopAndDrop;
-            }
-
-            bool enoughMeasurements =
-            tipState.nMeasurements >= singleConfig->minMeasurements;
-            bool tooManyHoles = tipState.nHoles > singleConfig->maxHoles;
-            bool tooManyOutliers = tipState.nOutliers > singleConfig->maxOutliers;
-
-            if (tooManyHoles || tooManyOutliers) {
-                ++m_nStoppedBranches;
-                return enoughMeasurements ? BranchStopperResult::StopAndKeep
-                : BranchStopperResult::StopAndDrop;
-            }
-            
-            return BranchStopperResult::Continue;
-        }
-
-    private:
-    Config m_config;
-};
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/src/utils/GeometryContainers.hpp b/Reconstruction/RecActsTracking/src/utils/GeometryContainers.hpp
deleted file mode 100644
index 6d4b6dcc..00000000
--- a/Reconstruction/RecActsTracking/src/utils/GeometryContainers.hpp
+++ /dev/null
@@ -1,355 +0,0 @@
-// STL
-#include <algorithm>
-#include <cassert>
-#include <cstddef>
-#include <iostream>
-#include <utility>
-#include <iterator>
-
-// boost
-#include <boost/container/flat_map.hpp>
-#include <boost/container/flat_set.hpp>
-
-// acts
-#include "Acts/EventData/SourceLink.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Surfaces/Surface.hpp"
-
-template <typename Iterator>
-class Range
-{
-    public:
-        Range(Iterator b, Iterator e) : m_begin(b), m_end(e) {}
-        Range(Range&&) = default;
-        Range(const Range&) = default;
-        ~Range() = default;
-        Range& operator=(Range&&) = default;
-        Range& operator=(const Range&) = default;
-
-        Iterator begin() const { return m_begin; }
-        Iterator end() const { return m_end; }
-        bool empty() const { return m_begin == m_end; }
-        std::size_t size() const { return std::distance(m_begin, m_end); }
-
-    private:
-        Iterator m_begin;
-        Iterator m_end;
-};
-
-template <typename Iterator>
-Range<Iterator> makeRange(Iterator begin, Iterator end)
-{ return Range<Iterator>(begin, end); }
-
-template <typename Iterator>
-Range<Iterator> makeRange(std::pair<Iterator, Iterator> range)
-{ return Range<Iterator>(range.first, range.second); }
-
-
-/// Proxy for iterating over groups of elements within a container.
-///
-/// @note Each group will contain at least one element.
-///
-/// Consecutive elements with the same key (as defined by the KeyGetter) are
-/// placed in one group. The proxy should always be used as part of a
-/// range-based for loop. In combination with structured bindings to reduce the
-/// boilerplate, the group iteration can be written as
-///
-///     for (auto&& [key, elements] : GroupBy<...>(...)) {
-///         // do something with just the key
-///         ...
-///
-///         // iterate over the group elements
-///         for (const auto& element : elements) {
-///             ...
-///         }
-///     }
-///
-template <typename Iterator, typename KeyGetter>
-class GroupBy {
- public:
-  /// The key type that identifies elements within a group.
-  using Key = std::decay_t<decltype(KeyGetter()(*Iterator()))>;
-  /// A Group is an iterator range with the associated key.
-  using Group = std::pair<Key, Range<Iterator>>;
-  /// Iterator type representing the end of the groups.
-  ///
-  /// The end iterator will not be dereferenced in C++17 range-based loops. It
-  /// can thus be a simpler type without the overhead of the full group iterator
-  /// below.
-  using GroupEndIterator = Iterator;
-  /// Iterator type representing a group of elements.
-  class GroupIterator {
-   public:
-    using iterator_category = std::input_iterator_tag;
-    using value_type = Group;
-    using difference_type = std::ptrdiff_t;
-    using pointer = Group*;
-    using reference = Group&;
-
-    constexpr GroupIterator(const GroupBy& groupBy, Iterator groupBegin)
-        : m_groupBy(groupBy),
-          m_groupBegin(groupBegin),
-          m_groupEnd(groupBy.findEndOfGroup(groupBegin)) {}
-    /// Pre-increment operator to advance to the next group.
-    constexpr GroupIterator& operator++() {
-      // make the current end the new group beginning
-      std::swap(m_groupBegin, m_groupEnd);
-      // find the end of the next group starting from the new beginning
-      m_groupEnd = m_groupBy.findEndOfGroup(m_groupBegin);
-      return *this;
-    }
-    /// Post-increment operator to advance to the next group.
-    constexpr GroupIterator operator++(int) {
-      GroupIterator retval = *this;
-      ++(*this);
-      return retval;
-    }
-    /// Dereference operator that returns the pointed-to group of elements.
-    constexpr Group operator*() const {
-      const Key key = (m_groupBegin != m_groupEnd)
-                          ? m_groupBy.m_keyGetter(*m_groupBegin)
-                          : Key();
-      return {key, makeRange(m_groupBegin, m_groupEnd)};
-    }
-
-   private:
-    const GroupBy& m_groupBy;
-    Iterator m_groupBegin;
-    Iterator m_groupEnd;
-
-    friend constexpr bool operator==(const GroupIterator& lhs,
-                                     const GroupEndIterator& rhs) {
-      return lhs.m_groupBegin == rhs;
-    }
-    friend constexpr bool operator!=(const GroupIterator& lhs,
-                                     const GroupEndIterator& rhs) {
-      return !(lhs == rhs);
-    }
-  };
-
-  /// Construct the group-by proxy for an iterator range.
-  constexpr GroupBy(Iterator begin, Iterator end,
-                    KeyGetter keyGetter = KeyGetter())
-      : m_begin(begin), m_end(end), m_keyGetter(std::move(keyGetter)) {}
-  constexpr GroupIterator begin() const {
-    return GroupIterator(*this, m_begin);
-  }
-  constexpr GroupEndIterator end() const { return m_end; }
-  constexpr bool empty() const { return m_begin == m_end; }
-
- private:
-  Iterator m_begin;
-  Iterator m_end;
-  KeyGetter m_keyGetter;
-
-  /// Find the end of the group that starts at the given position.
-  ///
-  /// This uses a linear search from the start position and thus has linear
-  /// complexity in the group size. It does not assume any ordering of the
-  /// underlying container and is a cache-friendly access pattern.
-  constexpr Iterator findEndOfGroup(Iterator start) const {
-    // check for end so we can safely dereference the start iterator.
-    if (start == m_end) {
-      return start;
-    }
-    // search the first element that does not share a key with the start.
-    return std::find_if_not(std::next(start), m_end,
-                            [this, start](const auto& x) {
-                              return m_keyGetter(x) == m_keyGetter(*start);
-                            });
-  }
-};
-
-/// Construct the group-by proxy for a container.
-template <typename Container, typename KeyGetter>
-auto makeGroupBy(const Container& container, KeyGetter keyGetter)
-    -> GroupBy<decltype(std::begin(container)), KeyGetter> {
-  return {std::begin(container), std::end(container), std::move(keyGetter)};
-}
-
-// extract the geometry identifier from a variety of types
-struct GeometryIdGetter
-{
-    // explicit geometry identifier are just forwarded
-    constexpr Acts::GeometryIdentifier operator()(
-        Acts::GeometryIdentifier geometryId) const { return geometryId; }
-
-    // encoded geometry ids are converted back to geometry identifiers.
-    constexpr Acts::GeometryIdentifier operator()(
-        Acts::GeometryIdentifier::Value encoded) const { return Acts::GeometryIdentifier(encoded); }
-
-    // support elements in map-like structures.
-    template <typename T>
-    constexpr Acts::GeometryIdentifier operator()(
-        const std::pair<Acts::GeometryIdentifier, T>& mapItem) const { return mapItem.first; }
-
-    // support elements that implement `.geometryId()`.
-    template <typename T>
-    inline auto operator()(const T& thing) const -> 
-        decltype(thing.geometryId(), Acts::GeometryIdentifier()) { return thing.geometryId(); }
-
-    // support reference_wrappers around such types as well
-    template <typename T>
-    inline auto operator()(std::reference_wrapper<T> thing) const ->
-        decltype(thing.get().geometryId(), Acts::GeometryIdentifier()) { return thing.get().geometryId(); }
-};
-
-struct CompareGeometryId
-{
-    // indicate that comparisons between keys and full objects are allowed.
-    using is_transparent = void;
-    // compare two elements using the automatic key extraction.
-    template <typename Left, typename Right>
-    constexpr bool operator()(Left&& lhs, Right&& rhs) const
-    { return GeometryIdGetter()(lhs) < GeometryIdGetter()(rhs); }
-};
-
-/// Store elements that know their detector geometry id, e.g. simulation hits.
-///
-/// @tparam T type to be stored, must be compatible with `CompareGeometryId`
-///
-/// The container stores an arbitrary number of elements for any geometry
-/// id. Elements can be retrieved via the geometry id; elements can be selected
-/// for a specific geometry id or for a larger range, e.g. a volume or a layer
-/// within the geometry hierarchy using the helper functions below. Elements can
-/// also be accessed by index that uniquely identifies each element regardless
-/// of geometry id.
-template <typename T>
-using GeometryIdMultiset = boost::container::flat_multiset<T, CompareGeometryId>;
-
-/// Store elements indexed by an geometry id.
-///
-/// @tparam T type to be stored
-///
-/// The behaviour is the same as for the `GeometryIdMultiset` except that the
-/// stored elements do not know their geometry id themself. When iterating
-/// the iterator elements behave as for the `std::map`, i.e.
-///
-///     for (const auto& entry: elements) {
-///         auto id = entry.first; // geometry id
-///         const auto& el = entry.second; // stored element
-///     }
-///
-template <typename T>
-using GeometryIdMultimap = GeometryIdMultiset<std::pair<Acts::GeometryIdentifier, T>>;
-
-/// Select all elements within the given volume.
-template <typename T>
-inline Range<typename GeometryIdMultiset<T>::const_iterator> selectVolume(
-    const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier::Value volume)
-{
-    auto cmp = Acts::GeometryIdentifier().setVolume(volume);
-    auto beg = std::lower_bound(container.begin(), container.end(), cmp, CompareGeometryId{});
-    // WARNING overflows to volume==0 if the input volume is the last one
-    cmp = Acts::GeometryIdentifier().setVolume(volume + 1u);
-    // optimize search by using the lower bound as start point. also handles
-    // volume overflows since the geo id would be located before the start of
-    // the upper edge search window.
-    auto end = std::lower_bound(beg, container.end(), cmp, CompareGeometryId{});
-    return makeRange(beg, end);
-}
-
-/// Select all elements within the given volume.
-template <typename T>
-inline auto selectVolume(const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier id)
-{ 
-    return selectVolume(container, id.volume());
-}
-
-/// Select all elements within the given layer.
-template <typename T>
-inline Range<typename GeometryIdMultiset<T>::const_iterator> selectLayer(
-    const GeometryIdMultiset<T>& container,
-    Acts::GeometryIdentifier::Value volume,
-    Acts::GeometryIdentifier::Value layer)
-{
-    auto cmp = Acts::GeometryIdentifier().setVolume(volume).setLayer(layer);
-    auto beg = std::lower_bound(container.begin(), container.end(), cmp, CompareGeometryId{});
-    // WARNING resets to layer==0 if the input layer is the last one
-    cmp = Acts::GeometryIdentifier().setVolume(volume).setLayer(layer + 1u);
-    // optimize search by using the lower bound as start point. also handles
-    // volume overflows since the geo id would be located before the start of
-    // the upper edge search window.
-    auto end = std::lower_bound(beg, container.end(), cmp, CompareGeometryId{});
-    return makeRange(beg, end);
-}
-
-// Select all elements within the given layer.
-template <typename T>
-inline auto selectLayer(const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier id)
-{
-    return selectLayer(container, id.volume(), id.layer());
-}
-
-/// Select all elements for the given module / sensitive surface.
-template <typename T>
-inline Range<typename GeometryIdMultiset<T>::const_iterator> selectModule(
-const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier geoId)
-{
-    // module is the lowest level and defines a single geometry id value
-    return makeRange(container.equal_range(geoId));
-}
-
-/// Select all elements for the given module / sensitive surface.
-template <typename T>
-inline auto selectModule(
-    const GeometryIdMultiset<T>& container,
-    Acts::GeometryIdentifier::Value volume,
-    Acts::GeometryIdentifier::Value layer,
-    Acts::GeometryIdentifier::Value module)
-{
-    return selectModule(container,
-                        Acts::GeometryIdentifier().setVolume(volume).setLayer(layer).setSensitive(module));
-}
-
-/// Select all elements for the lowest non-zero identifier component.
-///
-/// Zero values of lower components are interpreted as wildcard search patterns
-/// that select all element at the given geometry hierarchy and below. This only
-/// applies to the lower components and not to intermediate zeros.
-///
-/// Examples:
-/// - volume=2,layer=0,module=3 -> select all elements in the module
-/// - volume=1,layer=2,module=0 -> select all elements in the layer
-/// - volume=3,layer=0,module=0 -> select all elements in the volume
-///
-/// @note An identifier with all components set to zero selects the whole input
-///   container.
-/// @note Boundary and approach surfaces do not really fit into the geometry
-///   hierarchy and must be set to zero for the selection. If they are set on an
-///   input identifier, the behaviour of this search method is undefined.
-template <typename T>
-inline Range<typename GeometryIdMultiset<T>::const_iterator>
-selectLowestNonZeroGeometryObject(const GeometryIdMultiset<T>& container, Acts::GeometryIdentifier geoId)
-{
-    assert((geoId.boundary() == 0u) && "Boundary component must be zero");
-    assert((geoId.approach() == 0u) && "Approach component must be zero");
-
-    if (geoId.sensitive() != 0u) { return selectModule(container, geoId); }
-    else if (geoId.layer() != 0u) { return selectLayer(container, geoId); }
-    else if (geoId.volume() != 0u) { return selectVolume(container, geoId); }
-    else { return makeRange(container.begin(), container.end()); }
-}
-
-/// Iterate over groups of elements belonging to each module/ sensitive surface.
-template <typename T>
-inline GroupBy<typename GeometryIdMultiset<T>::const_iterator, GeometryIdGetter>
-groupByModule(const GeometryIdMultiset<T>& container)
-{
-    return makeGroupBy(container, GeometryIdGetter());
-}
-
-/// The accessor for the GeometryIdMultiset container
-///
-/// It wraps up a few lookup methods to be used in the Combinatorial Kalman
-/// Filter
-template <typename T>
-struct GeometryIdMultisetAccessor {
-using Container = GeometryIdMultiset<T>;
-using Key = Acts::GeometryIdentifier;
-using Value = typename GeometryIdMultiset<T>::value_type;
-using Iterator = typename GeometryIdMultiset<T>::const_iterator;
-
-// pointer to the container
-const Container* container = nullptr;
-};
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/src/utils/MagneticField.hpp b/Reconstruction/RecActsTracking/src/utils/MagneticField.hpp
deleted file mode 100644
index 94ef4152..00000000
--- a/Reconstruction/RecActsTracking/src/utils/MagneticField.hpp
+++ /dev/null
@@ -1,121 +0,0 @@
-// STL
-#include <memory>
-#include <variant>
-#include <vector>
-
-// acts
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/MagneticField/ConstantBField.hpp"
-#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
-#include "Acts/MagneticField/MagneticFieldProvider.hpp"
-#include "Acts/MagneticField/MagneticFieldContext.hpp"
-#include "Acts/MagneticField/NullBField.hpp"
-#include "Acts/Utilities/Grid.hpp"
-#include "Acts/Utilities/Result.hpp"
-#include "Acts/Utilities/detail/Axis.hpp"
-#include "Acts/Utilities/detail/AxisFwd.hpp"
-#include "Acts/Utilities/detail/grid_helper.hpp"
-
-/// The ScalableBField-specific magnetic field context.
-struct ScalableBFieldContext
-{ Acts::ActsScalar scalor = 1.; };
-
-/// A constant magnetic field that is scaled depending on the event context.
-class ScalableBField final : public Acts::MagneticFieldProvider
-{
-    public:
-        struct Cache
-        {
-            Acts::ActsScalar scalor = 1.;
-            /// @brief constructor with context
-            Cache(const Acts::MagneticFieldContext& mctx)
-            { scalor = mctx.get<const ScalableBFieldContext>().scalor; }
-        };
-
-        /// @brief construct constant magnetic field from field vector
-        ///
-        /// @param [in] B magnetic field vector in global coordinate system
-        explicit ScalableBField(Acts::Vector3 B) : m_BField(std::move(B)) {}
-
-        /// @brief construct constant magnetic field from components
-        ///
-        /// @param [in] Bx magnetic field component in global x-direction
-        /// @param [in] By magnetic field component in global y-direction
-        /// @param [in] Bz magnetic field component in global z-direction
-        ScalableBField(Acts::ActsScalar Bx = 0, Acts::ActsScalar By = 0, Acts::ActsScalar Bz = 0)
-        : m_BField(Bx, By, Bz) {}
-
-        /// @brief retrieve magnetic field value
-        ///
-        /// @param [in] position global position
-        /// @param [in] cache Cache object (is ignored)
-        /// @return magnetic field vector
-        ///
-        /// @note The @p position is ignored and only kept as argument to provide
-        ///       a consistent interface with other magnetic field services.
-        Acts::Result<Acts::Vector3> getField(
-            const Acts::Vector3& /*position*/,
-            MagneticFieldProvider::Cache& gCache) const override
-        {
-            Cache& cache = gCache.as<Cache>();
-            return Acts::Result<Acts::Vector3>::success(m_BField * cache.scalor);
-        }
-
-        /// @brief retrieve magnetic field value & its gradient
-        ///
-        /// @param [in]  position   global position
-        /// @param [out] derivative gradient of magnetic field vector as (3x3)
-        /// matrix
-        /// @param [in] cache Cache object (is ignored)
-        /// @return magnetic field vector
-        ///
-        /// @note The @p position is ignored and only kept as argument to provide
-        ///       a consistent interface with other magnetic field services.
-        /// @note currently the derivative is not calculated
-        /// @todo return derivative
-        Acts::Result<Acts::Vector3> getFieldGradient(
-            const Acts::Vector3& /*position*/, Acts::ActsMatrix<3, 3>& /*derivative*/,
-            MagneticFieldProvider::Cache& gCache) const override
-        {
-            Cache& cache = gCache.as<Cache>();
-            return Acts::Result<Acts::Vector3>::success(m_BField * cache.scalor);
-        }
-
-        Acts::MagneticFieldProvider::Cache makeCache(
-            const Acts::MagneticFieldContext& mctx) const override
-        {
-            return Acts::MagneticFieldProvider::Cache(std::in_place_type<Cache>, mctx);
-        }
-
-        /// @brief check whether given 3D position is inside look-up domain
-        ///
-        /// @param [in] position global 3D position
-        /// @return @c true if position is inside the defined look-up grid,
-        ///         otherwise @c false
-        /// @note The method will always return true for the constant B-Field
-        bool isInside(const Acts::Vector3& /*position*/) const { return true; }
-
-        /// @brief update magnetic field vector from components
-        ///
-        /// @param [in] Bx magnetic field component in global x-direction
-        /// @param [in] By magnetic field component in global y-direction
-        /// @param [in] Bz magnetic field component in global z-direction
-        void setField(double Bx, double By, double Bz) { m_BField << Bx, By, Bz; }
-
-        /// @brief update magnetic field vector
-        ///
-        /// @param [in] B magnetic field vector in global coordinate system
-        void setField(const Acts::Vector3& B) { m_BField = B; }
-
-    private:
-        /// magnetic field vector
-        Acts::Vector3 m_BField;
-}; // ScalableBField
-
-using InterpolatedMagneticField2 = Acts::InterpolatedBFieldMap<
-    Acts::Grid<Acts::Vector2, Acts::detail::EquidistantAxis,
-               Acts::detail::EquidistantAxis>>;
-
-using InterpolatedMagneticField3 = Acts::InterpolatedBFieldMap<
-    Acts::Grid<Acts::Vector3, Acts::detail::EquidistantAxis,
-               Acts::detail::EquidistantAxis, Acts::detail::EquidistantAxis>>;
diff --git a/Reconstruction/RecActsTracking/src/utils/SimSpacePoint.hpp b/Reconstruction/RecActsTracking/src/utils/SimSpacePoint.hpp
deleted file mode 100644
index 4c577ccd..00000000
--- a/Reconstruction/RecActsTracking/src/utils/SimSpacePoint.hpp
+++ /dev/null
@@ -1,243 +0,0 @@
-// STL
-#include <cmath>
-#include <vector>
-#include <cstdint>
-
-// boost
-#include <boost/container/static_vector.hpp>
-
-// acts tools
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/Definitions/Common.hpp"
-#include "Acts/EventData/SourceLink.hpp"
-#include "Acts/Seeding/Seed.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Geometry/TrackingGeometry.hpp"
-
-// edm4hep
-#include "edm4hep/TrackerHit.h"
-#include "edm4hep/SimTrackerHit.h"
-
-// local
-#include "GeometryContainers.hpp"
-
-using Index = std::uint32_t;
-template <typename value_t>
-using IndexMultimap = boost::container::flat_multimap<Index, value_t>;
-
-template <typename value_t>
-inline boost::container::flat_multimap<value_t, Index> invertIndexMultimap(
-    const IndexMultimap<value_t>& multimap) {
-  using InverseMultimap = boost::container::flat_multimap<value_t, Index>;
-
-  // switch key-value without enforcing the new ordering (linear copy)
-  typename InverseMultimap::sequence_type unordered;
-  unordered.reserve(multimap.size());
-  for (auto&& [index, value] : multimap) {
-    // value is now the key and the index is now the value
-    unordered.emplace_back(value, index);
-  }
-
-  // adopting the unordered sequence will reestablish the correct order
-  InverseMultimap inverse;
-#if BOOST_VERSION < 107800
-  for (const auto& i : unordered) {
-    inverse.insert(i);
-  }
-#else
-  inverse.insert(unordered.begin(), unordered.end());
-#endif
-  return inverse;
-}
-
-/// Space point representation of a measurement suitable for track seeding.
-class SimSpacePoint {
-    using Scalar = Acts::ActsScalar;
-
-public:
-    /// Construct the space point from edm4hep::TrackerHit
-    ///
-    /// @param trackerhit tracker hit to construct the space point from
-    SimSpacePoint(const edm4hep::TrackerHit trackerhit,
-                  float x, float y, float z, float t,
-                //   const edm4hep::SimTrackerHit simtrackerhit,
-                //   Acts::GeometryIdentifier geometryId,
-                  boost::container::static_vector<Acts::SourceLink, 2> sourceLinks)
-                  : m_x(x), m_y(y), m_z(z), m_t(t),
-                  m_sourceLinks(std::move(sourceLinks))
-                //   , m_geometryId(geometryId)
-    {
-        // m_trackerHit = trackerhit;
-        // m_simtrackerHit = simtrackerhit;
-        // m_x = simtrackerhit.getPosition()[0];
-        // m_y = simtrackerhit.getPosition()[1];
-        // m_z = simtrackerhit.getPosition()[2];
-        // m_t = simtrackerhit.getTime();
-        // m_cellid = simtrackerhit.getCellID();
-        m_rho = std::sqrt(m_x * m_x + m_y * m_y + m_z * m_z);
-        m_varianceRho = trackerhit.getCovMatrix()[0];
-        m_varianceZ = trackerhit.getCovMatrix()[5];
-    }
-
-    // edm4hep::TrackerHit getTrackerHit() { return m_trackerHit; }
-    // edm4hep::SimTrackerHit getSimTrackerHit() { return m_simtrackerHit; }
-
-    constexpr Scalar x() const { return m_x; }
-    constexpr Scalar y() const { return m_y; }
-    constexpr Scalar z() const { return m_z; }
-    constexpr std::optional<Scalar> t() const { return m_t; }
-    constexpr Scalar r() const { return m_rho; }
-    constexpr Scalar varianceR() const { return m_varianceRho; }
-    constexpr Scalar varianceZ() const { return m_varianceZ; }
-    constexpr std::optional<Scalar> varianceT() const { return m_varianceT; }
-
-    // constexpr std::uint64_t cellid() const { return m_cellid; }
-    // constexpr Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
-
-    const boost::container::static_vector<Acts::SourceLink, 2>&
-        sourceLinks() const { return m_sourceLinks; }
-
-    constexpr float topHalfStripLength() const { return m_topHalfStripLength; }
-    constexpr float bottomHalfStripLength() const { return m_bottomHalfStripLength; }
-
-    Acts::Vector3 topStripDirection() const { return m_topStripDirection; }
-    Acts::Vector3 bottomStripDirection() const { return m_bottomStripDirection; }
-    Acts::Vector3 stripCenterDistance() const { return m_stripCenterDistance; }
-    Acts::Vector3 topStripCenterPosition() const { return m_topStripCenterPosition; }
-
-    constexpr bool validDoubleMeasurementDetails() const {
-        return m_validDoubleMeasurementDetails;
-    }
-
-private:
-
-    // edm4hep::TrackerHit m_trackerHit;
-    // edm4hep::SimTrackerHit m_simtrackerHit;
-    // std::uint64_t m_cellid;
-    // Acts::GeometryIdentifier m_geometryId;
-
-    // Global position
-    Scalar m_x;
-    Scalar m_y;
-    Scalar m_z;
-    std::optional<Scalar> m_t;
-    Scalar m_rho;
-    // Variance in rho/z of the global coordinates
-    Scalar m_varianceRho;
-    Scalar m_varianceZ;
-    std::optional<Scalar> m_varianceT;
-    // SourceLinks of the corresponding measurements. A Pixel (strip) SP has one
-    // (two) sourceLink(s).
-    boost::container::static_vector<Acts::SourceLink, 2> m_sourceLinks;
-
-    // half of the length of the top strip
-    float m_topHalfStripLength = 0;
-    // half of the length of the bottom strip
-    float m_bottomHalfStripLength = 0;
-    // direction of the top strip
-    Acts::Vector3 m_topStripDirection = {0, 0, 0};
-    // direction of the bottom strip
-    Acts::Vector3 m_bottomStripDirection = {0, 0, 0};
-    // distance between the center of the two strips
-    Acts::Vector3 m_stripCenterDistance = {0, 0, 0};
-    // position of the center of the bottom strip
-    Acts::Vector3 m_topStripCenterPosition = {0, 0, 0};
-    bool m_validDoubleMeasurementDetails = false;
-
-}; // SimSpacePoint
-
-inline bool operator==(const SimSpacePoint& lhs, const SimSpacePoint& rhs) {
-    // (TODO) use sourceLinks for comparison
-    return ((lhs.x() == rhs.x()) && (lhs.y() == rhs.y()) &&
-            (lhs.z() == rhs.z()) && (lhs.t() == rhs.t()));
-}
-
-/// Container of space points.
-using SimSpacePointContainer = std::vector<SimSpacePoint>;
-using SimSeed = Acts::Seed<SimSpacePoint>;
-using SimSeedContainer = std::vector<Acts::Seed<SimSpacePoint>>;
-
-
-// --------------------------
-// IndexSourceLink
-// --------------------------
-
-/// A source link that stores just an index.
-///
-/// This is intentionally kept as barebones as possible. The source link
-/// is just a reference and will be copied, moved around, etc. often.
-/// Keeping it small and separate from the actual, potentially large,
-/// measurement data should result in better overall performance.
-/// Using an index instead of e.g. a pointer, means source link and
-/// measurement are decoupled and the measurement representation can be
-/// easily changed without having to also change the source link.
-class IndexSourceLink final
-{
-    public:
-        /// Construct from geometry identifier and index.
-        IndexSourceLink(Acts::GeometryIdentifier gid, Index idx, edm4hep::TrackerHit trackhit)
-            : m_geometryId(gid), m_index(idx), m_trackhit(trackhit) {}
-
-        // Construct an invalid source link.
-        // Must be default constructible to satisfy SourceLinkConcept.
-        IndexSourceLink() = default;
-        IndexSourceLink(const IndexSourceLink&) = default;
-        IndexSourceLink(IndexSourceLink&&) = default;
-        IndexSourceLink& operator=(const IndexSourceLink&) = default;
-        IndexSourceLink& operator=(IndexSourceLink&&) = default;
-
-        /// Access the index.
-        Index index() const { return m_index; }
-        Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
-        edm4hep::TrackerHit getTrackerHit() const { return m_trackhit; }
-
-        struct SurfaceAccessor
-        {
-            const Acts::TrackingGeometry& trackingGeometry;
-            const Acts::Surface* operator()(const Acts::SourceLink& sourceLink) const
-            {
-                const auto& indexSourceLink = sourceLink.get<IndexSourceLink>();
-                return trackingGeometry.findSurface(indexSourceLink.geometryId());
-            }
-        };
-
-    private:
-        Acts::GeometryIdentifier m_geometryId;
-        Index m_index = 0;
-        edm4hep::TrackerHit m_trackhit;
-
-        friend bool operator==(const IndexSourceLink& lhs, const IndexSourceLink& rhs)
-        {
-            return (lhs.geometryId() == rhs.geometryId()) && (lhs.m_index == rhs.m_index);
-        }
-
-        friend bool operator!=(const IndexSourceLink& lhs, const IndexSourceLink& rhs)
-        {
-            return !(lhs == rhs);
-        }
-}; // IndexSourceLink
-
-/// Container of index source links.
-///
-/// Since the source links provide a `.geometryId()` accessor,
-/// they can be stored in an ordered geometry container.
-using IndexSourceLinkContainer = GeometryIdMultiset<IndexSourceLink>;
-
-/// Accessor for the above source link container
-///
-/// It wraps up a few lookup methods to be used in the Combinatorial Kalman
-/// Filter
-struct IndexSourceLinkAccessor : GeometryIdMultisetAccessor<IndexSourceLink>
-{
-    using BaseIterator = GeometryIdMultisetAccessor<IndexSourceLink>::Iterator;
-
-    using Iterator = Acts::SourceLinkAdapterIterator<BaseIterator>;
-
-    // get the range of elements with requested geoId
-    std::pair<Iterator, Iterator> range(const Acts::Surface& surface) const
-    {
-        assert(container != nullptr);
-        auto [begin, end] = container->equal_range(surface.geometryId());
-        return {Iterator{begin}, Iterator{end}};
-    }
-};
\ No newline at end of file
-- 
GitLab


From 8adfc6a98a096531f69ded4c0c4723bad504132d Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Thu, 5 Jun 2025 10:18:45 +0800
Subject: [PATCH 11/12] update ACTS for tdr25.5

---
 .../RecActsTracking/src/RecActsTruthInput.cpp | 473 -----------
 .../RecActsTracking/src/RecActsTruthInput.h   | 100 ---
 .../src/RecActsTruthTracking.cpp              | 796 ------------------
 .../src/RecActsTruthTracking.h                | 183 ----
 4 files changed, 1552 deletions(-)
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h

diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
deleted file mode 100644
index 1418bc48..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.cpp
+++ /dev/null
@@ -1,473 +0,0 @@
-// dependence
-#include "RecActsTruthInput.h"
-
-DECLARE_COMPONENT(RecActsTruthInput)
-
-RecActsTruthInput::RecActsTruthInput(const std::string& name, ISvcLocator* svcLoc)
-    : GaudiAlgorithm(name, svcLoc)
-{
-}
-
-StatusCode RecActsTruthInput::initialize()
-{
-    // --------------------------------
-    // ---- initialize properties -----
-    // --------------------------------
-
-    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
-    if(!vtx_decoder){
-        info() << "Failed to create vtx_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
-    if(!ITKBarrel_decoder){
-        info() << "Failed to create ITKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
-    if(!ITKEndcap_decoder){
-        info() << "Failed to create ITKEndcap_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
-    if(!tpc_decoder){
-        info() << "Failed to create TPC_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
-    if(!OTKBarrel_decoder){
-        info() << "Failed to create OTKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    } 
-    
-    // --------------------------------
-    // ---- initialize recActsSvc ----
-    // --------------------------------
-
-    recActsSvc = service<IRecActsSvc>("RecActsSvc");
-    if (!recActsSvc) {
-        error() << "Failed to get RecActsSvc" << endmsg;
-        return StatusCode::FAILURE;
-    }
-    
-    _nEvt = -1;
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTruthInput::execute()
-{
-    _nEvt++;
-
-    recActsSvc->Clean();
-
-    if (useVTX.value()) {
-        if (!ReadInputVTX()) {
-            debug() << "VTX input failed at event " << _nEvt << endmsg;
-        }
-    }
-    
-    if (useITKBarrel.value()) {
-        if (!ReadInputITKBarrel()) {
-            debug() << "ITKBarrel input failed at event " << _nEvt << endmsg;
-        }
-    }
-    
-    if (useITKEndcap.value()) {
-        if (!ReadInputITKEndcap()) {
-            debug() << "ITKEndcap input failed at event " << _nEvt << endmsg;
-        }
-    }
-
-    if (useTPC.value()) {
-        if (!ReadInputTPC()) {
-            debug() << "TPC input failed at event " << _nEvt << endmsg;
-        }
-    }
-
-    if (useOTKBarrel.value()) {
-        if (!ReadInputOTKBarrel()) {
-            debug() << "OTKBarrel input failed at event " << _nEvt << endmsg;
-        }
-    }
-    
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTruthInput::finalize()
-{
-    return StatusCode::SUCCESS;
-}
-
-bool RecActsTruthInput::ReadInputVTX()
-{
-    const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitVTXCol = nullptr;
-    const edm4hep::MCRecoTrackerAssociationCollection* vtxAssCol = nullptr;
-
-    try {
-        hitVTXCol = _inVTXTrackHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        SimhitVTXCol = _inVTXColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Sim Collection " << _inVTXColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        vtxAssCol = _inVTXAssColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Association Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    if(hitVTXCol && SimhitVTXCol)
-    {
-        int nelem = hitVTXCol->size();
-
-        for(int ielem = 0; ielem < nelem; ++ielem){
-            auto hit = hitVTXCol->at(ielem);
-
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *vtxAssCol){
-                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-            }
-
-            if(simHits.size() == 0){
-                info() << "Skip: Not found VTX simHit in AssociationCollection at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto simhit = simHits[0];
-
-            if (simhit.isProducedBySecondary()) {
-                continue;
-            }
-
-            auto cellid = hit.getCellID();
-            uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
-            uint64_t m_module  = vtx_decoder->get(cellid, "module");
-            uint64_t m_sensor  = vtx_decoder->get(cellid, "sensor");
-
-            if(m_layer <= 3){
-                uint64_t acts_volume = VXD_volume_ids[m_layer];
-                uint64_t acts_layer = 2;
-                uint64_t acts_sensitive = 1;
-                bool buildSpacePoint = true;
-                int moduleType = 1;
-                double onSurfaceTolerance = 1e-4;
-
-                if (!recActsSvc->ReadInput(hit,
-                        acts_volume, acts_layer, acts_sensitive,
-                        buildSpacePoint, moduleType, onSurfaceTolerance))
-                    { return false; }
-            } else {
-                uint64_t acts_volume = VXD_volume_ids[4];
-                uint64_t acts_layer = 2;
-                uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;
-                bool buildSpacePoint = true;
-                int moduleType = 0;
-                double onSurfaceTolerance = 1e-4;
-
-                if (!recActsSvc->ReadInput(hit,
-                    acts_volume, acts_layer, acts_sensitive,
-                    buildSpacePoint, moduleType, onSurfaceTolerance))
-                    { return false; }
-            }
-        }
-    }
-
-    return true;
-}
-
-bool RecActsTruthInput::ReadInputITKBarrel()
-{
-    const edm4hep::TrackerHitCollection* hitITKBarrelCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitITKBarrelCol = nullptr;
-    const edm4hep::MCRecoTrackerAssociationCollection* itkBarrelAssCol = nullptr;
-
-    try {
-        hitITKBarrelCol = _inITKBarrelTrackHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Collection " << _inITKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        SimhitITKBarrelCol = _inITKBarrelColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Sim Collection " << _inITKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        itkBarrelAssCol = _inITKBarrelAssColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Association Collection " << _inITKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    if(hitITKBarrelCol)
-    {
-        int nelem = hitITKBarrelCol->size();
-
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitITKBarrelCol->at(ielem);
-
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *itkBarrelAssCol){
-                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-            }
-            
-            if(simHits.size() == 0){
-                info() << "Skip: Not found ITKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto simhit = simHits[0];
-
-            if (simhit.isProducedBySecondary()) {
-                continue;
-            }
-
-            auto cellid = hit.getCellID();
-            uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
-            uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
-
-            uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
-            uint64_t acts_layer = 2;
-            uint64_t acts_sensitive = m_stave + 1;
-            bool buildSpacePoint = true;
-            int moduleType = 0;
-            double onSurfaceTolerance = 1e-4;
-
-            if (!recActsSvc->ReadInput(hit,
-                acts_volume, acts_layer, acts_sensitive,
-                buildSpacePoint, moduleType, onSurfaceTolerance))
-                { return false; }
-        }
-    }
-
-    return true;
-}
-
-bool RecActsTruthInput::ReadInputITKEndcap()
-{
-    const edm4hep::TrackerHitCollection* hitITKEndcapCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitITKEndcapCol = nullptr;
-    const edm4hep::MCRecoTrackerAssociationCollection* itkEndcapAssCol = nullptr;
-
-    try {
-        hitITKEndcapCol = _inITKEndcapTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inITKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        SimhitITKEndcapCol = _inITKEndcapColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Sim Collection " << _inITKEndcapColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        itkEndcapAssCol = _inITKEndcapAssColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Association Collection " << _inITKEndcapAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    if (hitITKEndcapCol)
-    {
-        int nelem = hitITKEndcapCol->size();
-
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitITKEndcapCol->at(ielem);
-
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *itkEndcapAssCol){
-                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-            }
-
-            if(simHits.size() == 0){
-                info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto simhit = simHits[0];
-
-            if (simhit.isProducedBySecondary()) {
-                continue;
-            }
-
-            auto cellid = hit.getCellID();
-            int m_side = ITKEndcap_decoder->get(cellid, "side");
-            uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
-            uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
-
-            uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
-            uint64_t acts_layer = 2;
-            uint64_t acts_sensitive = m_module + 1;
-
-            if (!recActsSvc->ReadInput(hit,
-                acts_volume, acts_layer, acts_sensitive))
-                { return false; }
-        }
-    }
-    
-    return true;
-}
-
-
-bool RecActsTruthInput::ReadInputTPC()
-{
-    const edm4hep::TrackerHitCollection* hitTPCCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitTPCCol = nullptr;
-    const edm4hep::MCRecoTrackerAssociationCollection* tpcAssCol = nullptr;
-
-    try {
-        hitTPCCol = _inTPCTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inTPCTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        SimhitTPCCol = _inTPCColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Sim Collection " << _inTPCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        tpcAssCol = _inTPCAssColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Association Collection " << _inTPCAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    if(hitTPCCol)
-    {
-        int nelem = hitTPCCol->size();
-
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitTPCCol->at(ielem);
-
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *tpcAssCol){
-                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-            }
-
-            if(simHits.size() == 0){
-                info() << "Skip: Not found TPC simHit in AssociationCollection at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto simhit = simHits[0];
-
-            if (simhit.isProducedBySecondary()) {
-                continue;
-            }
-
-            auto cellid = hit.getCellID();
-            uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
-
-            uint64_t acts_volume = TPC_volume_id;
-            uint64_t acts_layer = (m_layer + 1) * 2;
-            uint64_t acts_sensitive = 1;
-            bool buildSpacePoint = false;
-            int moduleType = 2;
-            double onSurfaceTolerance = 1e-4;
-
-            if (!recActsSvc->ReadInput(hit,
-                acts_volume, acts_layer, acts_sensitive,
-                buildSpacePoint, moduleType, onSurfaceTolerance))
-                { return false; }
-        }
-    }
-
-    return true;
-}
-
-bool RecActsTruthInput::ReadInputOTKBarrel()
-{
-    const edm4hep::TrackerHitCollection* hitOTKBarrelCol = nullptr;
-    const edm4hep::SimTrackerHitCollection* SimhitOTKBarrelCol = nullptr;
-    const edm4hep::MCRecoTrackerAssociationCollection* otkBarrelAssCol = nullptr;
-
-    try {   
-        hitOTKBarrelCol = _inOTKBarrelTrackHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Collection " << _inOTKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        SimhitOTKBarrelCol = _inOTKBarrelColHdl.get();
-    } catch (GaudiException& e) {
-        debug() << "Sim Collection " << _inOTKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    try {
-        otkBarrelAssCol = _inOTKBarrelAssColHdl.get();
-    } catch (GaudiException& e) {
-        fatal() << "Association Collection " << _inOTKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-        return false;
-    }
-
-    if(hitOTKBarrelCol)
-    {
-        int nelem = hitOTKBarrelCol->size();
-        
-        for (int ielem = 0; ielem < nelem; ++ielem)
-        {
-            auto hit = hitOTKBarrelCol->at(ielem);
-            
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *otkBarrelAssCol){
-                if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-            }
-
-            if(simHits.size() == 0){
-                info() << "Skip: Not found OTKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto simhit = simHits[0];
-
-            if (simhit.isProducedBySecondary()) {
-                continue;
-            }
-
-            auto cellid = hit.getCellID();
-            uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
-
-            uint64_t acts_volume = OTK_volume_id;
-            uint64_t acts_layer = 2;
-            uint64_t acts_sensitive = m_module + 1;
-
-            if (!recActsSvc->ReadInput(hit,
-                acts_volume, acts_layer, acts_sensitive))
-                { return false; }
-        }
-    }
-    return true;
-}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
deleted file mode 100644
index bd390199..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthInput.h
+++ /dev/null
@@ -1,100 +0,0 @@
-#ifndef RecActsTruthInput_H
-#define RecActsTruthInput_H
-
-// gaudi framework
-#include "k4FWCore/DataHandle.h"
-#include "GaudiAlg/GaudiAlgorithm.h"
-
-// services
-#include "RecActsSvc/IRecActsSvc.h"
-// #include "DetInterface/IGeomSvc.h"
-
-// DD4hep
-#include "DD4hep/Detector.h"
-#include "DDRec/ISurface.h"
-#include "DDRec/SurfaceManager.h"
-
-#include "UTIL/ILDConf.h"
-#include "DataHelper/Navigation.h"
-
-// edm4hep
-#include "edm4hep/MCParticle.h"
-#include "edm4hep/MCParticleCollection.h"
-#include "edm4hep/TrackerHitCollection.h"
-#include "edm4hep/SimTrackerHitCollection.h"
-#include "edm4hep/EventHeaderCollection.h"
-
-#include "edm4hep/Track.h"
-#include "edm4hep/MutableTrack.h"
-#include "edm4hep/TrackState.h"
-#include "edm4hep/TrackCollection.h"
-
-
-class RecActsTruthInput : public GaudiAlgorithm
-{
-    public:
-        RecActsTruthInput(const std::string& name, ISvcLocator* svcLoc);
-
-        StatusCode initialize();
-        StatusCode execute();
-        StatusCode finalize();
-
-    private:
-        // Input collections
-        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inITKBarrelTrackHdl{"ITKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inITKEndcapTrackHdl{"ITKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inOTKBarrelTrackHdl{"OTKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inTPCTrackHdl{"TPCTrackerHits", Gaudi::DataHandle::Reader, this};        
-
-        DataHandle<edm4hep::SimTrackerHitCollection> _inVTXColHdl{"VXDCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inITKBarrelColHdl{"ITKBarrelCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inITKEndcapColHdl{"ITKEndcapCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inOTKBarrelColHdl{"OTKBarrelCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inTPCColHdl{"TPCCollection", Gaudi::DataHandle::Reader, this};
-
-        // associations
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKBarrelAssColHdl{"ITKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKEndcapAssColHdl{"ITKEndcapTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inOTKBarrelAssColHdl{"OTKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inTPCAssColHdl{"TPCTrackerHitAss", Gaudi::DataHandle::Reader, this};
-        
-        // properties
-        Gaudi::Property<bool> useVTX{this, "useVTX", true};
-        Gaudi::Property<bool> useITKBarrel{this, "useITKBarrel", true};
-        Gaudi::Property<bool> useITKEndcap{this, "useITKEndcap", true};
-        Gaudi::Property<bool> useTPC{this, "useTPC", true};
-        Gaudi::Property<bool> useOTKBarrel{this, "useOTKBarrel", true};
-
-        // read input
-        bool ReadInputVTX();
-        bool ReadInputITKBarrel();
-        bool ReadInputITKEndcap();
-        bool ReadInputTPC();
-        bool ReadInputOTKBarrel();
-
-        // services
-        // SmartIF<IGeomSvc> m_geosvc;
-        SmartIF<IRecActsSvc> recActsSvc;
-        SmartIF<IChronoStatSvc> chronoStatSvc;
-
-        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
-
-        // constants
-        int _nEvt;
-        uint64_t TPC_volume_id = 43;
-        uint64_t OTK_volume_id = 45;
-        std::vector<uint64_t> VXD_volume_ids{26, 27, 28, 29, 30};
-        std::vector<uint64_t> ITKBarrel_volume_ids{33, 36, 39};
-        std::vector<uint64_t> ITKEndcap_positive_volume_ids{34, 37, 40, 41};
-        std::vector<uint64_t> ITKEndcap_negative_volume_ids{32, 15, 10, 8};
-        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
-        
-};
-
-#endif // RecActsTruthInput_H
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
deleted file mode 100644
index 8a58b67c..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.cpp
+++ /dev/null
@@ -1,796 +0,0 @@
-// dependence
-#include "RecActsTruthTracking.h"
-
-DECLARE_COMPONENT(RecActsTruthTracking)
-
-RecActsTruthTracking::RecActsTruthTracking(const std::string& name, ISvcLocator* svcLoc)
-    : GaudiAlgorithm(name, svcLoc)
-{
-}
-
-StatusCode RecActsTruthTracking::initialize()
-{
-    // --------------------------------
-    // ---- initialize properties -----
-    // --------------------------------
-
-    info() << "Assume Particle: " << m_particle.value() << endmsg;
-    if(m_particle.value() == "muon"){
-        particleHypothesis = Acts::ParticleHypothesis::muon();
-    }
-    else if(m_particle.value() == "pion"){
-        particleHypothesis = Acts::ParticleHypothesis::pion();
-    }
-    else if(m_particle.value() == "electron"){
-        particleHypothesis = Acts::ParticleHypothesis::electron();
-    }
-    else if(m_particle.value() == "kaon"){
-        particleHypothesis = Acts::ParticleHypothesis::kaon();
-    }
-    else if(m_particle.value() == "proton"){
-        particleHypothesis = Acts::ParticleHypothesis::proton();
-    }
-    else if(m_particle.value() == "photon"){
-        particleHypothesis = Acts::ParticleHypothesis::photon();
-    }
-    else if(m_particle.value() == "geantino"){
-        particleHypothesis = Acts::ParticleHypothesis::geantino();
-    }
-    else if(m_particle.value() == "chargedgeantino"){
-        particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
-    }
-    else{
-        info()  << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
-                                            << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
-                                            << particleNames[6] << ", " << particleNames[7] << endmsg;
-        error() << "Unsupported particle name " << m_particle.value() << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    // m_geosvc = service<IGeomSvc>("GeomSvc");
-    // if (!m_geosvc) {
-    //     error() << "Failed to get GeomSvc" << endmsg;
-    //     return StatusCode::FAILURE;
-    // }
-
-    // m_vtx_surfaces = m_geosvc->getSurfaceMap("VXD");
-    // debug() << "VXD Surface map size: " << m_vtx_surfaces->size() << endmsg;
-
-    // m_ITKBarrel_surfaces = m_geosvc->getSurfaceMap("ITKBarrel");
-    // debug() << "ITKBarrel Surface map size: " << m_ITKBarrel_surfaces->size() << endmsg;
-
-    // m_ITKEndcap_surfaces = m_geosvc->getSurfaceMap("ITKEndcap");
-    // debug() << "ITKEndcap Surface map size: " << m_ITKEndcap_surfaces->size() << endmsg;
-
-    // m_tpc_surfaces = m_geosvc->getSurfaceMap("TPC");
-    // debug() << "TPC Surface map size: " << m_tpc_surfaces->size() << endmsg;
-
-    // m_OTKBarrel_surfaces = m_geosvc->getSurfaceMap("OTKBarrel");
-    // debug() << "OTK Barrel Surface map size: " << m_OTKBarrel_surfaces->size() << endmsg;
-
-    // vtx_decoder = m_geosvc->getDecoder("VXDCollection");
-    // if(!vtx_decoder){
-    //     return StatusCode::FAILURE;
-    // }
-
-    // ITKBarrel_decoder = m_geosvc->getDecoder("ITKBarrelCollection");
-    // if(!ITKBarrel_decoder){
-    //     return StatusCode::FAILURE;
-    // }
-
-    // tpc_decoder = m_geosvc->getDecoder("TPCCollection");
-    // if(!tpc_decoder){
-    //     return StatusCode::FAILURE;
-    // }
-
-    // ITKEndcap_decoder = m_geosvc->getDecoder("ITKEndcapCollection");
-    // if(!ITKEndcap_decoder){
-    //     return StatusCode::FAILURE;
-    // }
-
-    // OTKBarrel_decoder = m_geosvc->getDecoder("OTKBarrelCollection");
-    // if(!OTKBarrel_decoder){
-    //     return StatusCode::FAILURE;
-    // }
-
-    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
-    if(!vtx_decoder){
-        info() << "Failed to create vtx_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
-    if(!ITKBarrel_decoder){
-        info() << "Failed to create ITKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
-    if(!ITKEndcap_decoder){
-        info() << "Failed to create ITKEndcap_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
-    if(!tpc_decoder){
-        info() << "Failed to create TPC_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
-    if(!OTKBarrel_decoder){
-        info() << "Failed to create OTKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    } 
-
-    recActsSvc = service<IRecActsSvc>("RecActsSvc");
-    if (!recActsSvc) {
-        error() << "Failed to get RecActsSvc" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    // --------------------------------
-    // ---- initialize recActsSvc ----
-    // --------------------------------
-
-    recActsSvc = service<IRecActsSvc>("RecActsSvc");
-    if (!recActsSvc) {
-        error() << "Failed to get RecActsSvc" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    chronoStatSvc = service<IChronoStatSvc>("ChronoStatSvc");
-
-    magneticField = std::make_shared<Acts::ConstantBField>(Acts::Vector3(0., 0., m_field.value()*_FCT));
-    fit = ActsHelper::makeKalmanFitterFunction(recActsSvc->Geometry(), magneticField);
-    // fit = ActsHelper::makeGsfFitterFunction(recActsSvc->Geometry(), magneticField); (not working)
-    // fit = ActsHelper::makeGlobalChiSquareFitterFunction(recActsSvc->Geometry(), magneticField); (ok)
-
-    _nEvt = -1;
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTruthTracking::execute()
-{
-    _nEvt++;
-
-    chronoStatSvc->chronoStart("Truth Tracking");
-
-    auto trkCol = _outColHdl.createAndPut();
-
-    const edm4hep::MCParticleCollection* mcCols = nullptr;
-    try { mcCols = _inMCColHdl.get(); }
-    catch ( GaudiException &e ) {
-        debug() << "Collection " << _inMCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-    }
-
-    if(mcCols){
-        for(const auto& mc : *mcCols){
-            if (mc.getGeneratorStatus() != 1){
-                info() << "MCParticle " << mc.getPDG() << " is not primary at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            recActsSvc->Clean();
-            debug() << "Processing MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
-
-            if(!ReadHits(mc)){
-                info() << "Fit MCParticle " << mc.getPDG() << " failed at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto measurements = recActsSvc->Measurements();
-            auto trackSourceLinks = recActsSvc->SourceLinks();
-            auto initialParams = recActsSvc->InitialParameters();
-
-            if (initialParams->size() == 0) {
-                info() << "No initial parameters found for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
-            ActsHelper::PassThroughCalibrator pcalibrator;
-            ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
-            ActsHelper::TrackFitterFunction::GeneralFitterOptions options{
-                geoContext, magFieldContext, calibContext, pSurface.get(), Acts::PropagatorPlainOptions()};
-            
-            auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
-            auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
-            ActsHelper::TrackContainer tracks(trackContainer, trackStateContainer);
-
-            std::vector<Acts::SourceLink> cur_trackSourceLinks;
-            for (const auto& sourceLink : *trackSourceLinks) {
-                cur_trackSourceLinks.push_back(Acts::SourceLink{sourceLink});
-            }
-            auto result = (*fit)(cur_trackSourceLinks, initialParams->at(0), options, calibrator, tracks);
-            if (!result.ok()) {
-                info() << "Track fit failed for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            std::stringstream ss;
-            trackStateContainer->statistics().toStream(ss);
-            debug() << ss.str() << endmsg;
-
-            ActsHelper::ConstTrackContainer constTracks
-            {
-                std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer)),
-                std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer))
-            };
-
-            if (constTracks.size() == 0) { 
-                info() << "No tracks fitted for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
-                continue; 
-            } else if (constTracks.size() > 1) {
-                info() << "Number of tracks fitted: " << constTracks.size() << " for MCParticle " << mc.getPDG() << " at event " << _nEvt << endmsg;
-            }
-
-            for (const auto& cur_track : constTracks)
-            {
-                auto writeout_track = trkCol->create();
-
-                writeout_track.setChi2(cur_track.chi2());
-                writeout_track.setNdf(cur_track.nDoF());
-                writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
-                writeout_track.setDEdxError(mc.getPDG());
-
-                // TODO: covmatrix need to be converted
-                std::array<float, 21> writeout_covMatrix;
-                auto cur_track_covariance = cur_track.covariance();
-                for (int i = 0; i < 6; i++) {
-                    for (int j = 0; j < 6-i; j++) {
-                        writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
-                    }
-                }
-                // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
-                // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
-                edm4hep::TrackState writeout_trackState{
-                    1, // location: AtOther
-                    cur_track.loc0() / Acts::UnitConstants::mm, // d0
-                    cur_track.phi(), // phi
-                    cur_track.qOverP() * _FCT * m_field.value() / sin(cur_track.theta()) , // omega = qop * sin(theta) * _FCT * bf
-                    cur_track.loc1() / Acts::UnitConstants::mm, // z0
-                    1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
-                    cur_track.time(), // time
-                    ::edm4hep::Vector3f(0, 0, 0), // referencePoint
-                    writeout_covMatrix
-                };
-
-                debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " GeV!" << endmsg;
-                writeout_track.addToTrackStates(writeout_trackState);
-            }
-
-            if (!useMCTruth.value()) { break; }
-        }
-    }
-
-    chronoStatSvc->chronoStop("Truth Tracking");
-    
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTruthTracking::finalize()
-{
-    return StatusCode::SUCCESS;
-}
-
-bool RecActsTruthTracking::ReadHits(const edm4hep::MCParticle& mc)
-{
-    double px = 0;
-    double py = 0;
-    double pz = 0;
-
-    if (useVTX.value())
-    {
-        const edm4hep::TrackerHitCollection* hitVTXCol = nullptr;
-        const edm4hep::SimTrackerHitCollection* SimhitVTXCol = nullptr;
-        const edm4hep::MCRecoTrackerAssociationCollection* vtxAssCol = nullptr;
-
-        try {
-            hitVTXCol = _inVTXTrackHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Collection " << _inVTXTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            SimhitVTXCol = _inVTXColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Sim Collection " << _inVTXColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            vtxAssCol = _inVTXAssColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Association Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        if(hitVTXCol && SimhitVTXCol)
-        {
-            int nelem = hitVTXCol->size();
-            debug() << "Number of VTX hits = " << nelem << endmsg;
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nelem; ++ielem){
-                auto hit = hitVTXCol->at(ielem);
-
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *vtxAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found VTX simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            for(int i_hit : hit_indices){
-                auto hit = hitVTXCol->at(i_hit);
-
-                auto cellid = hit.getCellID();
-                uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
-                uint64_t m_module  = vtx_decoder->get(cellid, "module");
-
-                if(m_layer <= 3){
-                    uint64_t acts_volume = VXD_volume_ids[m_layer];
-                    uint64_t acts_layer = 2;
-                    uint64_t acts_sensitive = 1;
-                    bool buildSpacePoint = true;
-                    int moduleType = 1;
-                    double onSurfaceTolerance = 1e-4;
-
-                    if (!recActsSvc->ReadInput(hit,
-                        acts_volume, acts_layer, acts_sensitive,
-                        buildSpacePoint, moduleType, onSurfaceTolerance))
-                        { return false; }
-                } else {
-                    uint64_t acts_volume = VXD_volume_ids[4];
-                    uint64_t acts_layer = 2;
-                    uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;;
-
-                    if (!recActsSvc->ReadInput(hit,
-                        acts_volume, acts_layer, acts_sensitive))
-                        { return false; }
-                }
-            }
-
-            if (hit_indices.size() == 0) {
-                info() << "Skip: Not found VTX simHit at event " << _nEvt << endmsg;
-                return false;
-            }
-
-            auto first_hit = hitVTXCol->at(hit_indices[0]);
-            std::vector<edm4hep::SimTrackerHit> simHits;
-            for(auto ass : *vtxAssCol){
-                if(ass.getRec().getObjectID().collectionID != first_hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==first_hit) simHits.push_back(ass.getSim());
-            }
-            auto simhit = simHits[0];
-            px = simhit.getMomentum()[0];
-            py = simhit.getMomentum()[1];
-            pz = simhit.getMomentum()[2];
-        }
-    }
-
-    if (useITKBarrel.value())
-    { 
-        const edm4hep::TrackerHitCollection* hitITKBarrelCol = nullptr;
-        const edm4hep::SimTrackerHitCollection* SimhitITKBarrelCol = nullptr;
-        const edm4hep::MCRecoTrackerAssociationCollection* itkBarrelAssCol = nullptr;
-
-        try {
-            hitITKBarrelCol = _inITKBarrelTrackHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Collection " << _inITKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            SimhitITKBarrelCol = _inITKBarrelColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Sim Collection " << _inITKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            itkBarrelAssCol = _inITKBarrelAssColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Association Collection " << _inITKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-        
-        if(hitITKBarrelCol && SimhitITKBarrelCol)
-        {
-            int nelem = hitITKBarrelCol->size();
-            debug() << "Number of ITKBarrel hits = " << nelem << endmsg;
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nelem; ++ielem){
-                auto hit = hitITKBarrelCol->at(ielem);
-                
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *itkBarrelAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-                
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found ITKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            for (int i_hit : hit_indices)
-            {
-                auto hit = hitITKBarrelCol->at(i_hit);
-
-                auto cellid = hit.getCellID();
-                uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
-                uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
-
-                uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
-                uint64_t acts_layer = 2;
-                uint64_t acts_sensitive = m_stave + 1;
-
-                if (!recActsSvc->ReadInput(hit,
-                    acts_volume, acts_layer, acts_sensitive))
-                    { return false; }
-            }
-        }
-    }
-
-    if (useITKEndcap.value())
-    {            
-        const edm4hep::TrackerHitCollection* hitITKEndcapCol = nullptr;
-        const edm4hep::SimTrackerHitCollection* SimhitITKEndcapCol = nullptr;
-        const edm4hep::MCRecoTrackerAssociationCollection* itkEndcapAssCol = nullptr;
-
-        try {
-            hitITKEndcapCol = _inITKEndcapTrackHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Collection " << _inITKEndcapTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            SimhitITKEndcapCol = _inITKEndcapColHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Sim Collection " << _inITKEndcapColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            itkEndcapAssCol = _inITKEndcapAssColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Association Collection " << _inITKEndcapAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        if (hitITKEndcapCol && SimhitITKEndcapCol)
-        {
-            int nelem = hitITKEndcapCol->size();
-            debug() << "Number of ITKEndcap hits = " << nelem << endmsg;
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nelem; ++ielem){
-                auto hit = hitITKEndcapCol->at(ielem);
-
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *itkEndcapAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            for (int i_hit : hit_indices)
-            {
-                auto hit = hitITKEndcapCol->at(i_hit);
-                
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *itkEndcapAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found ITKEndcap simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                auto cellid = hit.getCellID();
-                int m_side = ITKEndcap_decoder->get(cellid, "side");
-                uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
-                uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
-
-                uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
-                uint64_t acts_layer = 2;
-                uint64_t acts_sensitive = m_module + 1;
-
-                if (!recActsSvc->ReadInput(hit,
-                    acts_volume, acts_layer, acts_sensitive))
-                    { return false; }
-            }
-        }
-    }
-
-    if (useTPC.value())
-    {
-
-        const edm4hep::TrackerHitCollection* hitTPCCol = nullptr;
-        const edm4hep::SimTrackerHitCollection* SimhitTPCCol = nullptr;
-        const edm4hep::MCRecoTrackerAssociationCollection* tpcAssCol = nullptr;
-
-        try {
-            hitTPCCol = _inTPCTrackHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Collection " << _inTPCTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            SimhitTPCCol = _inTPCColHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Sim Collection " << _inTPCColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            tpcAssCol = _inTPCAssColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Association Collection " << _inTPCAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        if(hitTPCCol && SimhitTPCCol)
-        {
-            int nelem = hitTPCCol->size();
-            debug() << "Number of TPC hits = " << nelem << endmsg;
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nelem; ++ielem){
-                auto hit = hitTPCCol->at(ielem);
-
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *tpcAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found TPC simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            for (int i_hit : hit_indices)
-            {
-                auto hit = hitTPCCol->at(i_hit);
-                
-                auto cellid = hit.getCellID();
-                uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
-
-                uint64_t acts_volume = TPC_volume_id;
-                uint64_t acts_layer = (m_layer + 1) * 2;
-                uint64_t acts_sensitive = 1;
-                bool buildSpacePoint = false;
-                int moduleType = 2;
-                double onSurfaceTolerance = 1e-4;
-
-                if (!recActsSvc->ReadInput(hit,
-                    acts_volume, acts_layer, acts_sensitive,
-                    buildSpacePoint, moduleType, onSurfaceTolerance))
-                    { return false; }
-            }
-        }
-    }
-
-    if (useOTKBarrel.value())
-    {
-        const edm4hep::TrackerHitCollection* hitOTKBarrelCol = nullptr;
-        const edm4hep::SimTrackerHitCollection* SimhitOTKBarrelCol = nullptr;
-        const edm4hep::MCRecoTrackerAssociationCollection* otkBarrelAssCol = nullptr;
-
-        try {   
-            hitOTKBarrelCol = _inOTKBarrelTrackHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Collection " << _inOTKBarrelTrackHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            SimhitOTKBarrelCol = _inOTKBarrelColHdl.get();
-        } catch (GaudiException& e) {
-            debug() << "Sim Collection " << _inOTKBarrelColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        try {
-            otkBarrelAssCol = _inOTKBarrelAssColHdl.get();
-        } catch (GaudiException& e) {
-            fatal() << "Association Collection " << _inOTKBarrelAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg;
-            return false;
-        }
-
-        if(hitOTKBarrelCol && SimhitOTKBarrelCol)
-        {
-            int nelem = hitOTKBarrelCol->size();
-            debug() << "Number of OTKBarrel hits = " << nelem << endmsg;
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nelem; ++ielem)
-            {
-                auto hit = hitOTKBarrelCol->at(ielem);
-
-                std::vector<edm4hep::SimTrackerHit> simHits;
-                for(auto ass : *otkBarrelAssCol){
-                    if(ass.getRec().getObjectID().collectionID != hit.getObjectID().collectionID) break;
-                    else if(ass.getRec()==hit) simHits.push_back(ass.getSim());
-                }
-
-                if(simHits.size() == 0){
-                    info() << "Skip: Not found OTKBarrel simHit in AssociationCollection at event " << _nEvt << endmsg;
-                    continue;
-                }
-
-                auto simhit = simHits[0];
-
-                if (simhit.isProducedBySecondary()) {
-                    continue;
-                }
-
-                if (useMCTruth.value() && !(simhit.getMCParticle() == mc)) {
-                    continue;
-                }
-
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            for (int i_hit : hit_indices)
-            {
-                auto hit = hitOTKBarrelCol->at(i_hit);
-
-                auto cellid = hit.getCellID();
-                uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
-
-                uint64_t acts_volume = OTK_volume_id;
-                uint64_t acts_layer = 2;
-                uint64_t acts_sensitive = m_module + 1;
-
-                if (!recActsSvc->ReadInput(hit,
-                    acts_volume, acts_layer, acts_sensitive))
-                    { return false; }
-            }
-        }
-    }
-
-    double p  = sqrt(px*px + py*py + pz*pz);
-    double phi = atan2(py, px);
-    double theta = atan2(sqrt(px*px + py*py), pz);
-    double qop = -1 / p;
-
-    Acts::BoundVector params = Acts::BoundVector::Zero();
-    Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
-    ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*recActsSvc->Geometry()};
-
-    auto first_hit_meas =  recActsSvc->Measurements()->at(0);
-    auto cur_meas_param = std::get<1>(first_hit_meas).parameters();
-    auto cur_meas_sl = std::get<1>(first_hit_meas).sourceLink();
-    const Acts::Surface* surface = surfaceAccessor(cur_meas_sl);
-
-    params[Acts::eBoundLoc0]   = cur_meas_param[Acts::eBoundLoc0];
-    params[Acts::eBoundLoc1]   = cur_meas_param[Acts::eBoundLoc1];
-    params[Acts::eBoundPhi]    = phi;
-    params[Acts::eBoundTheta]  = theta;
-    params[Acts::eBoundQOverP] = qop;
-    params[Acts::eBoundTime]   = 0;
-    for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
-        double sigma = initialSigmas[i];
-        sigma += initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP];
-        double var = sigma * sigma;
-        // var *= noTimeVarInflation.value();
-        var *= initialVarInflation.value()[i];
-        
-        cov(i, i) = var;
-    }
-    
-    recActsSvc->InitialParameters()->emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
-
-    return true;
-}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h
deleted file mode 100644
index 19672f3b..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTruthTracking.h
+++ /dev/null
@@ -1,183 +0,0 @@
-#ifndef RecActsTruthTracking_H
-#define RecActsTruthTracking_H
-
-// gaudi framework
-#include "k4FWCore/DataHandle.h"
-#include "GaudiAlg/GaudiAlgorithm.h"
-
-#include "UTIL/ILDConf.h"
-#include "DataHelper/Navigation.h"
-
-// services
-#include "RecActsSvc/IRecActsSvc.h"
-#include "DetInterface/IGeomSvc.h"
-
-// DD4hep
-#include "DD4hep/Detector.h"
-#include "DDRec/ISurface.h"
-#include "DDRec/SurfaceManager.h"
-
-// edm4hep
-#include "edm4hep/MCParticle.h"
-#include "edm4hep/MCParticleCollection.h"
-#include "edm4hep/TrackerHitCollection.h"
-#include "edm4hep/SimTrackerHitCollection.h"
-#include "edm4hep/EventHeaderCollection.h"
-
-#include "edm4hep/Track.h"
-#include "edm4hep/MutableTrack.h"
-#include "edm4hep/TrackState.h"
-#include "edm4hep/TrackCollection.h"
-// Acts
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/EventData/GenericBoundTrackParameters.hpp"
-#include "Acts/EventData/SourceLink.hpp"
-#include "Acts/EventData/TrackProxy.hpp"
-#include "Acts/EventData/VectorMultiTrajectory.hpp"
-#include "Acts/EventData/VectorTrackContainer.hpp"
-#include "Acts/Propagator/Propagator.hpp"
-#include "Acts/Surfaces/PerigeeSurface.hpp"
-#include "Acts/Surfaces/Surface.hpp"
-#include "Acts/Utilities/Result.hpp"
-
-#include "Acts/Definitions/Direction.hpp"
-#include "Acts/Definitions/TrackParametrization.hpp"
-#include "Acts/EventData/MultiTrajectory.hpp"
-#include "Acts/EventData/TrackContainer.hpp"
-#include "Acts/EventData/TrackStatePropMask.hpp"
-#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Propagator/DirectNavigator.hpp"
-#include "Acts/Propagator/EigenStepper.hpp"
-#include "Acts/Propagator/Navigator.hpp"
-#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
-#include "Acts/TrackFitting/KalmanFitter.hpp"
-#include "Acts/Utilities/Delegate.hpp"
-#include "Acts/Utilities/Logger.hpp"
-
-namespace Acts
-{
-    class MagneticFieldProvider;
-    class TrackingGeometry;
-}
-
-class RecActsTruthTracking : public GaudiAlgorithm
-{
-    public:
-        RecActsTruthTracking(const std::string& name, ISvcLocator* svcLoc);
-
-        StatusCode initialize();
-        StatusCode execute();
-        StatusCode finalize();
-
-    private:
-        // Input collections
-        DataHandle<edm4hep::MCParticleCollection> _inMCColHdl{"MCParticle", Gaudi::DataHandle::Reader, this};
-        
-        DataHandle<edm4hep::TrackerHitCollection> _inVTXTrackHdl{"VXDTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inITKBarrelTrackHdl{"ITKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inITKEndcapTrackHdl{"ITKEndcapTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inOTKBarrelTrackHdl{"OTKBarrelTrackerHits", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::TrackerHitCollection> _inTPCTrackHdl{"TPCTrackerHits", Gaudi::DataHandle::Reader, this};        
-
-        DataHandle<edm4hep::SimTrackerHitCollection> _inVTXColHdl{"VXDCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inITKBarrelColHdl{"ITKBarrelCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inITKEndcapColHdl{"ITKEndcapCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inOTKBarrelColHdl{"OTKBarrelCollection", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::SimTrackerHitCollection> _inTPCColHdl{"TPCCollection", Gaudi::DataHandle::Reader, this};
-
-        // associations
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKBarrelAssColHdl{"ITKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inITKEndcapAssColHdl{"ITKEndcapTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inOTKBarrelAssColHdl{"OTKBarrelTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inTPCAssColHdl{"TPCTrackerHitAss", Gaudi::DataHandle::Reader, this};
-
-        // Output collections
-        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSTruthTracks", Gaudi::DataHandle::Writer, this};
-
-        Gaudi::Property<std::string> m_particle{this, "AssumeParticle", "muon"};
-        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
-        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 1, 1, 1, 1}};
-        Gaudi::Property<double> noTimeVarInflation{this, "noTimeVarInflation", 1.};
-
-        Gaudi::Property<bool> useMCTruth{this, "useMCTruth", false};
-        Gaudi::Property<bool> useVTX{this, "useVTX", true};
-        Gaudi::Property<bool> useITKBarrel{this, "useITKBarrel", true};
-        Gaudi::Property<bool> useITKEndcap{this, "useITKEndcap", true};
-        Gaudi::Property<bool> useTPC{this, "useTPC", true};
-        Gaudi::Property<bool> useOTKBarrel{this, "useOTKBarrel", true};
-        // Gaudi::Property<bool> useOTKEndcap{this, "useOTKEndcap", true};
-        
-        // services
-        // SmartIF<IGeomSvc> m_geosvc;
-        SmartIF<IRecActsSvc> recActsSvc;
-        SmartIF<IChronoStatSvc> chronoStatSvc;
-
-        bool ReadHits(const edm4hep::MCParticle& mc);
-        
-        // Decoders
-        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
-
-        std::shared_ptr<ActsHelper::TrackFitterFunction> fit;
-        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
-        // std::shared_ptr<ActsHelper::MeasurementCalibrator> m_calibrator;
-
-        // globalChiSquareFitter config
-        bool multipleScattering = false;
-        bool energyLoss = false;
-        Acts::FreeToBoundCorrection freeToBoundCorrection;
-        std::size_t nUpdateMax = 5;
-        double relChi2changeCutOff = 1e-7;
-        
-        // context
-        Acts::GeometryContext geoContext;
-        Acts::MagneticFieldContext magFieldContext;
-        Acts::CalibrationContext calibContext;
-
-        // double noTimeVarInflation = 10.;
-        std::array<double, 6> initialSigmas = {
-            5 * Acts::UnitConstants::um,
-            5 * Acts::UnitConstants::um,
-            2e-2 * Acts::UnitConstants::degree,
-            2e-2 * Acts::UnitConstants::degree,
-            0 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
-            1 * Acts::UnitConstants::s
-        };
-        std::array<double, 6> initialSimgaQoverPCoefficients = {
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0.1,
-            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)
-        };
-        
-        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
-
-        // constants
-        const double _FCT = 2.99792458E-4;
-        uint64_t TPC_volume_id = 45;
-        uint64_t OTKBarrel_volume_id = 47;
-        uint64_t OTKEndcap_positive_volume_id = 2;
-        uint64_t OTKEndcap_negative_volume_id = 48;
-        std::vector<uint64_t> VXD_volume_ids{28, 29, 30, 31, 32};
-        std::vector<uint64_t> ITKBarrel_volume_ids{35, 38, 41};
-        std::vector<uint64_t> ITKEndcap_positive_volume_ids{36, 39, 42, 43};
-        std::vector<uint64_t> ITKEndcap_negative_volume_ids{34, 17, 12, 10};
-        std::vector<std::vector<uint64_t>> ITKEndcap_modules_per_ring{{13, 20, 0}, {16, 24, 28}, {24, 36, 44}, {24, 36, 44}};
-        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
-        
-        const std::array<Acts::BoundIndices, 6> writeout_indices{
-            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
-            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
-            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
-        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
-        int _nEvt;
-};
-
-#endif // RecActsTruthTracking_H
\ No newline at end of file
-- 
GitLab


From 39321ba42b63c0e96a8543407345fd84162e0bcf Mon Sep 17 00:00:00 2001
From: "zhangyz@ihep.ac.cn" <zhangyz@ihep.ac.cn>
Date: Thu, 5 Jun 2025 10:20:01 +0800
Subject: [PATCH 12/12] update ACTS for tdr25.5

---
 .../RecActsTracking/CMakeLists.txt            |   4 +-
 .../src/RecActsTrackReFitting.cpp             | 401 ------------------
 .../src/RecActsTrackReFitting.h               | 153 -------
 3 files changed, 1 insertion(+), 557 deletions(-)
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
 delete mode 100644 Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h

diff --git a/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt b/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
index a0aad036..9fe46edd 100644
--- a/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
+++ b/Reconstruction/RecActsTracking/RecActsTracking/CMakeLists.txt
@@ -10,13 +10,11 @@ endif()
 gaudi_add_module(RecActsTracking
                  SOURCES
                         src/RecActsReadInput.cpp
-                        src/RecActsTruthInput.cpp
                         src/RecActsSeeding.cpp
                         src/RecActsTrackParamsEstimation.cpp
                         src/RecActsTrackFinding.cpp
                         src/RecActsTrackFitting.cpp
-                        # src/RecActsTrackReFitting.cpp
-                        # src/RecActsTruthTracking.cpp
+
                  LINK DetInterface
                       k4FWCore::k4FWCore
                       Gaudi::GaudiAlgLib Gaudi::GaudiKernel
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
deleted file mode 100644
index 87ce36e5..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.cpp
+++ /dev/null
@@ -1,401 +0,0 @@
-// dependence
-#include "RecActsTrackReFitting.h"
-
-DECLARE_COMPONENT(RecActsTrackReFitting)
-
-RecActsTrackReFitting::RecActsTrackReFitting(const std::string& name, ISvcLocator* svcLoc)
-    : GaudiAlgorithm(name, svcLoc)
-{
-}
-
-StatusCode RecActsTrackReFitting::initialize()
-{
-    // --------------------------------
-    // ---- initialize properties -----
-    // --------------------------------
-
-    info() << "Assume Particle: " << m_particle.value() << endmsg;
-    if(m_particle.value() == "muon"){
-        particleHypothesis = Acts::ParticleHypothesis::muon();
-    }
-    else if(m_particle.value() == "pion"){
-        particleHypothesis = Acts::ParticleHypothesis::pion();
-    }
-    else if(m_particle.value() == "electron"){
-        particleHypothesis = Acts::ParticleHypothesis::electron();
-    }
-    else if(m_particle.value() == "kaon"){
-        particleHypothesis = Acts::ParticleHypothesis::kaon();
-    }
-    else if(m_particle.value() == "proton"){
-        particleHypothesis = Acts::ParticleHypothesis::proton();
-    }
-    else if(m_particle.value() == "photon"){
-        particleHypothesis = Acts::ParticleHypothesis::photon();
-    }
-    else if(m_particle.value() == "geantino"){
-        particleHypothesis = Acts::ParticleHypothesis::geantino();
-    }
-    else if(m_particle.value() == "chargedgeantino"){
-        particleHypothesis = Acts::ParticleHypothesis::chargedGeantino();
-    }
-    else{
-        info()  << "Supported Assumed Particle: " << particleNames[0] << ", " << particleNames[1] << ", " << particleNames[2] << ", "
-                                            << particleNames[3] << ", " << particleNames[4] << ", " << particleNames[5] << ", "
-                                            << particleNames[6] << ", " << particleNames[7] << endmsg;
-        error() << "Unsupported particle name " << m_particle.value() << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    vtx_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:32:16");
-    if(!vtx_decoder){
-        info() << "Failed to create vtx_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,stave:8,module:8,sensor:5");
-    if(!ITKBarrel_decoder){
-        info() << "Failed to create ITKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    ITKEndcap_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,sensor:8");
-    if(!ITKEndcap_decoder){
-        info() << "Failed to create ITKEndcap_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    tpc_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:13,module:6,sensor:6");
-    if(!tpc_decoder){
-        info() << "Failed to create TPC_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    OTKBarrel_decoder = new dd4hep::DDSegmentation::BitFieldCoder("system:5,side:-2,layer:9,module:8,iladder:32:4,oladder:-4,mmodule:-6");
-    if(!OTKBarrel_decoder){
-        info() << "Failed to create OTKBarrel_decoder" << endmsg;
-        return StatusCode::FAILURE;
-    } 
-
-    recActsSvc = service<IRecActsSvc>("RecActsSvc");
-    if (!recActsSvc) {
-        error() << "Failed to get RecActsSvc" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    // --------------------------------
-    // ---- initialize recActsSvc ----
-    // --------------------------------
-
-    recActsSvc = service<IRecActsSvc>("RecActsSvc");
-    if (!recActsSvc) {
-        error() << "Failed to get RecActsSvc" << endmsg;
-        return StatusCode::FAILURE;
-    }
-
-    magneticField = std::make_shared<Acts::ConstantBField>(Acts::Vector3(0., 0., m_field.value()*_FCT));
-    fit = ActsHelper::makeKalmanFitterFunction(recActsSvc->Geometry(), magneticField);
-    // fit = ActsHelper::makeGsfFitterFunction(recActsSvc->Geometry(), magneticField); (not working)
-    // fit = ActsHelper::makeGlobalChiSquareFitterFunction(recActsSvc->Geometry(), magneticField); (ok)
-
-    _nEvt = -1;
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTrackReFitting::execute()
-{
-    _nEvt++;
-
-    auto trkCol = _outColHdl.createAndPut();
-
-    const edm4hep::TrackCollection* trackCols = nullptr;
-    try { trackCols = _inTrackColHdl.get(); }
-    catch ( GaudiException &e )
-    { debug() << "Collection " << _inTrackColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg; }
-
-    const edm4hep::MCRecoTrackerAssociationCollection* vtxHitAssCols = nullptr;
-    try { vtxHitAssCols = _inVTXAssColHdl.get(); }
-    catch ( GaudiException &e )
-    { debug() << "Collection " << _inVTXAssColHdl.fullKey() << " is unavailable in event " << _nEvt << endmsg; }
-
-    std::map<int, int> hitNumbers; 
-
-    hitNumbers[UTIL::ILDDetID::VXD] = 0;
-    hitNumbers[UTIL::ILDDetID::SIT] = 0;
-    hitNumbers[UTIL::ILDDetID::FTD] = 0;
-    hitNumbers[UTIL::ILDDetID::TPC] = 0;
-    hitNumbers[UTIL::ILDDetID::SET] = 0;
-    hitNumbers[UTIL::ILDDetID::ETD] = 0;
-
-    if(trackCols){
-        int Track_count = 0;
-        for(auto track : *trackCols){
-            recActsSvc->Clean();
-            Track_count++;
-            unsigned int nHits = track.trackerHits_size();
-
-            std::vector<int> hit_indices;
-            std::vector<float> hit_radii;
-            for(int ielem = 0; ielem < nHits; ++ielem){
-                const edm4hep::TrackerHit& hit = track.getTrackerHits(ielem);
-                float hit_r = std::hypot(hit.getPosition()[0], hit.getPosition()[1]);
-                hit_indices.push_back(ielem);
-                hit_radii.push_back(hit_r);
-            }
-
-            std::sort(hit_indices.begin(), hit_indices.end(),
-                     [&hit_radii](int i1, int i2) { return hit_radii[i1] < hit_radii[i2]; });
-
-            auto first_hit = track.getTrackerHits(hit_indices[0]);
-
-            std::vector<edm4hep::SimTrackerHit> first_simHits;
-            for(auto ass : *vtxHitAssCols){
-                if(ass.getRec().getObjectID().collectionID != first_hit.getObjectID().collectionID) break;
-                else if(ass.getRec()==first_hit) first_simHits.push_back(ass.getSim());
-            }
-
-            if(first_simHits.size() == 0){
-                info() << "Skip: Not found VTX simHit in AssociationCollection for track " << Track_count << " at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto first_simhit = first_simHits[0];
-            double px = first_simhit.getMomentum()[0];
-            double py = first_simhit.getMomentum()[1];
-            double pz = first_simhit.getMomentum()[2];
-
-            for(int i_hit : hit_indices){
-                const edm4hep::TrackerHit& hit = track.getTrackerHits(i_hit);
-                auto cellid = hit.getCellID();
-                UTIL::BitField64 encoder(UTIL::ILDCellID0::encoder_string);
-                encoder.setValue(cellid);
-                int subdet = encoder[UTIL::ILDCellID0::subdet];
-                ++hitNumbers[subdet];
-
-                if(subdet==UTIL::ILDDetID::VXD)
-                {
-                    uint64_t m_layer   = vtx_decoder->get(cellid, "layer");
-                    uint64_t m_module  = vtx_decoder->get(cellid, "module");
-
-                    if(m_layer <= 3){
-                        uint64_t acts_volume = VXD_volume_ids[m_layer];
-                        uint64_t acts_layer = 2;
-                        uint64_t acts_sensitive = 1;
-                        bool buildSpacePoint = false;
-                        int moduleType = 1;
-                        double onSurfaceTolerance = 1e-4;
-
-                        if (!recActsSvc->ReadInput(hit,
-                                acts_volume, acts_layer, acts_sensitive,
-                                buildSpacePoint, moduleType, onSurfaceTolerance))
-                            {
-                                info() << "Failed to read VXD hit: layer " << m_layer << ", module " << m_module << endmsg;
-                            }
-                    } else {
-                        uint64_t acts_volume = VXD_volume_ids[4];
-                        uint64_t acts_layer = 2;
-                        uint64_t acts_sensitive = (m_layer == 5) ? m_module*2 + 1 : m_module*2 + 2;;
-
-                        if (!recActsSvc->ReadInput(hit,
-                            acts_volume, acts_layer, acts_sensitive))
-                            {
-                                info() << "Failed to read VXD hit: layer " << m_layer << ", module " << m_module << endmsg;
-                            }
-                    }
-                }
-                else if(subdet==UTIL::ILDDetID::SIT)
-                {
-                    uint64_t m_layer   = ITKBarrel_decoder->get(cellid, "layer");
-                    uint64_t m_stave   = ITKBarrel_decoder->get(cellid, "stave");
-                    
-                    uint64_t acts_volume = ITKBarrel_volume_ids[m_layer];
-                    uint64_t acts_layer = 2;
-                    uint64_t acts_sensitive = m_stave + 1;
-                    if (!recActsSvc->ReadInput(hit,
-                            acts_volume, acts_layer, acts_sensitive))
-                        {
-                            info() << "Failed to read ITKBarrel hit: layer " << m_layer << ", stave " << m_stave << endmsg;
-                        }
-                }
-                else if(subdet==UTIL::ILDDetID::FTD)
-                {
-                    int m_side = ITKEndcap_decoder->get(cellid, "side");
-                    uint64_t m_layer = ITKEndcap_decoder->get(cellid, "layer");
-                    uint64_t m_module = ITKEndcap_decoder->get(cellid, "module");
-                    
-                    uint64_t acts_volume = (m_side == 1) ? ITKEndcap_positive_volume_ids[m_layer] : ITKEndcap_negative_volume_ids[m_layer];
-                    uint64_t acts_layer = 2;
-                    uint64_t acts_sensitive = m_module + 1;
-                    if (!recActsSvc->ReadInput(hit,
-                            acts_volume, acts_layer, acts_sensitive))
-                        {
-                            info() << "Failed to read ITKEndcap hit: side " << m_side << ", layer " << m_layer << ", module " << m_module << endmsg;
-                        }
-                }
-                else if(subdet==UTIL::ILDDetID::TPC)
-                {
-                    uint64_t m_layer   = tpc_decoder->get(cellid, "layer");
-                    
-                    uint64_t acts_volume = TPC_volume_id;
-                    uint64_t acts_layer = (m_layer + 1) * 2;
-                    uint64_t acts_sensitive = 1;
-                    bool buildSpacePoint = false;
-                    int moduleType = 2;
-                    double onSurfaceTolerance = 1e-4;
-
-                    if (!recActsSvc->ReadInput(hit,
-                            acts_volume, acts_layer, acts_sensitive,
-                            buildSpacePoint, moduleType, onSurfaceTolerance))
-                        {
-                            info() << "Failed to read TPC hit: layer " << m_layer << endmsg;
-                        }
-                }
-                else if(subdet==UTIL::ILDDetID::SET)
-                {
-                    uint64_t m_module  = OTKBarrel_decoder->get(cellid, "module");
-                    
-                    uint64_t acts_volume = OTK_volume_id;
-                    uint64_t acts_layer = 2;
-                    uint64_t acts_sensitive = m_module + 1;
-                    if (!recActsSvc->ReadInput(hit,
-                            acts_volume, acts_layer, acts_sensitive))
-                        {
-                            info() << "Failed to read OTKBarrel hit: module " << m_module << endmsg;
-                        }
-                }
-                else
-                {
-                    debug() << "not supported subdetector: " << subdet << endmsg;
-                }
-            }
-
-            double p  = sqrt(px*px + py*py + pz*pz);
-            double phi = atan2(py, px);
-            double theta = atan2(sqrt(px*px + py*py), pz);
-            double qop = -1 / p;
-
-            Acts::BoundVector params = Acts::BoundVector::Zero();
-            Acts::BoundSquareMatrix cov = Acts::BoundSquareMatrix::Zero();
-            ActsHelper::IndexSourceLink::SurfaceAccessor surfaceAccessor{*recActsSvc->Geometry()};
-
-            // (TODO) assume the first hit is the first measurement
-            auto first_hit_meas =  recActsSvc->Measurements()->at(0);
-            auto cur_meas_param = std::get<1>(first_hit_meas).parameters();
-            auto cur_meas_sl = std::get<1>(first_hit_meas).sourceLink();
-            const Acts::Surface* surface = surfaceAccessor(cur_meas_sl);
-
-            params[Acts::eBoundLoc0]   = cur_meas_param[Acts::eBoundLoc0];
-            params[Acts::eBoundLoc1]   = cur_meas_param[Acts::eBoundLoc1];
-            params[Acts::eBoundPhi]    = phi;
-            params[Acts::eBoundTheta]  = theta;
-            params[Acts::eBoundQOverP] = qop;
-            params[Acts::eBoundTime]   = 0;
-
-            for (std::size_t i = Acts::eBoundLoc0; i < Acts::eBoundSize; ++i) {
-                double sigma = initialSigmas[i];
-                sigma += abs(initialSimgaQoverPCoefficients[i] * params[Acts::eBoundQOverP]);
-                double var = sigma * sigma;
-                // var *= noTimeVarInflation.value();
-                var *= initialVarInflation.value()[i];
-                
-                cov(i, i) = var;
-            }
-
-            recActsSvc->InitialParameters()->emplace_back(surface->getSharedPtr(), params, cov, particleHypothesis);
-            
-            auto measurements = recActsSvc->Measurements();
-            auto trackSourceLinks = recActsSvc->SourceLinks();
-            auto initialParams = recActsSvc->InitialParameters();
-
-            // info() << "debug: event " << _nEvt << ", track " << Track_count << endmsg;
-            // info() << "measurements size: " << measurements->size() << endmsg;
-            // info() << "trackSourceLinks size: " << trackSourceLinks->size() << endmsg;
-            // info() << "initialParams size: " << initialParams->size() << endmsg;
-
-            if (initialParams->size() == 0) {
-                info() << "No initial parameters found for track " << Track_count << " at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3{0., 0., 0.});
-            ActsHelper::PassThroughCalibrator pcalibrator;
-            ActsHelper::MeasurementCalibratorAdapter calibrator(pcalibrator, *measurements);
-            ActsHelper::TrackFitterFunction::GeneralFitterOptions options{
-                geoContext, magFieldContext, calibContext, pSurface.get(), Acts::PropagatorPlainOptions()};
-            
-            auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
-            auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
-            ActsHelper::TrackContainer tracks(trackContainer, trackStateContainer);
-
-            std::vector<Acts::SourceLink> cur_trackSourceLinks;
-            for (const auto& sourceLink : *trackSourceLinks) {
-                cur_trackSourceLinks.push_back(Acts::SourceLink{sourceLink});
-            }
-
-            auto result = (*fit)(cur_trackSourceLinks, initialParams->at(0), options, calibrator, tracks);
-            if (!result.ok()) {
-                info() << "Track fit failed for track " << Track_count << " at event " << _nEvt << endmsg;
-                continue;
-            }
-
-            // std::stringstream ss;
-            // trackStateContainer->statistics().toStream(ss);
-            // debug() << ss.str() << endmsg;
-
-            ActsHelper::ConstTrackContainer constTracks
-            {
-                std::make_shared<Acts::ConstVectorTrackContainer>(std::move(*trackContainer)),
-                std::make_shared<Acts::ConstVectorMultiTrajectory>(std::move(*trackStateContainer))
-            };
-
-            if (constTracks.size() == 0) { 
-                info() << "No tracks fitted for track " << Track_count << " at event " << _nEvt << endmsg;
-                continue; 
-            } else if (constTracks.size() > 1) {
-                info() << "Number of tracks fitted: " << constTracks.size() << " for track " << Track_count << " at event " << _nEvt << endmsg;
-            }
-
-            for (const auto& cur_track : constTracks)
-            {
-                auto writeout_track = trkCol->create();
-
-                writeout_track.setChi2(cur_track.chi2());
-                writeout_track.setNdf(cur_track.nDoF());
-                writeout_track.setDEdx(cur_track.absoluteMomentum() / Acts::UnitConstants::GeV);
-                writeout_track.setDEdxError(0);
-
-                // TODO: covmatrix need to be converted
-                std::array<float, 21> writeout_covMatrix;
-                auto cur_track_covariance = cur_track.covariance();
-                for (int i = 0; i < 6; i++) {
-                    for (int j = 0; j < 6-i; j++) {
-                        writeout_covMatrix[int((13-i)*i/2 + j)] = cur_track_covariance(writeout_indices[i], writeout_indices[j]);
-                    }
-                }
-                // location: At{Other|IP|FirstHit|LastHit|Calorimeter|Vertex}|LastLocation
-                // TrackState: location, d0, phi, omega, z0, tanLambda, time, referencePoint, covMatrix
-                edm4hep::TrackState writeout_trackState{
-                    1, // location: AtOther
-                    cur_track.loc0() / Acts::UnitConstants::mm, // d0
-                    cur_track.phi(), // phi
-                    cur_track.qOverP() * _FCT * m_field / sin(cur_track.theta()) , // omega = qop * sin(theta) * _FCT * bf
-                    cur_track.loc1() / Acts::UnitConstants::mm, // z0
-                    1 / tan(cur_track.theta()), // tanLambda = 1 / tan(theta)
-                    cur_track.time(), // time
-                    ::edm4hep::Vector3f(0, 0, 0), // referencePoint
-                    writeout_covMatrix
-                };
-
-                debug() << "Found track with momentum " << cur_track.absoluteMomentum() / Acts::UnitConstants::GeV << " GeV!" << endmsg;
-                writeout_track.addToTrackStates(writeout_trackState);
-            }
-        }
-    }
-
-    return StatusCode::SUCCESS;
-}
-
-StatusCode RecActsTrackReFitting::finalize()
-{
-    return StatusCode::SUCCESS;
-}
\ No newline at end of file
diff --git a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h b/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h
deleted file mode 100644
index 97f091c5..00000000
--- a/Reconstruction/RecActsTracking/RecActsTracking/src/RecActsTrackReFitting.h
+++ /dev/null
@@ -1,153 +0,0 @@
-#ifndef RecActsTrackReFitting_H
-#define RecActsTrackReFitting_H
-
-// gaudi framework
-#include "k4FWCore/DataHandle.h"
-#include "GaudiAlg/GaudiAlgorithm.h"
-
-#include "UTIL/ILDConf.h"
-#include "DataHelper/Navigation.h"
-
-// services
-#include "RecActsSvc/IRecActsSvc.h"
-#include "DetInterface/IGeomSvc.h"
-
-// DD4hep
-#include "DD4hep/Detector.h"
-#include "DDRec/ISurface.h"
-#include "DDRec/SurfaceManager.h"
-
-// edm4hep
-#include "edm4hep/MCParticle.h"
-#include "edm4hep/MCParticleCollection.h"
-#include "edm4hep/TrackerHitCollection.h"
-#include "edm4hep/SimTrackerHitCollection.h"
-#include "edm4hep/EventHeaderCollection.h"
-
-#include "edm4hep/Track.h"
-#include "edm4hep/MutableTrack.h"
-#include "edm4hep/TrackState.h"
-#include "edm4hep/TrackCollection.h"
-// Acts
-#include "Acts/Definitions/Algebra.hpp"
-#include "Acts/EventData/GenericBoundTrackParameters.hpp"
-#include "Acts/EventData/SourceLink.hpp"
-#include "Acts/EventData/TrackProxy.hpp"
-#include "Acts/EventData/VectorMultiTrajectory.hpp"
-#include "Acts/EventData/VectorTrackContainer.hpp"
-#include "Acts/Propagator/Propagator.hpp"
-#include "Acts/Surfaces/PerigeeSurface.hpp"
-#include "Acts/Surfaces/Surface.hpp"
-#include "Acts/Utilities/Result.hpp"
-
-#include "Acts/Definitions/Direction.hpp"
-#include "Acts/Definitions/TrackParametrization.hpp"
-#include "Acts/EventData/MultiTrajectory.hpp"
-#include "Acts/EventData/TrackContainer.hpp"
-#include "Acts/EventData/TrackStatePropMask.hpp"
-#include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/Propagator/DirectNavigator.hpp"
-#include "Acts/Propagator/EigenStepper.hpp"
-#include "Acts/Propagator/Navigator.hpp"
-#include "Acts/TrackFitting/GlobalChiSquareFitter.hpp"
-#include "Acts/TrackFitting/KalmanFitter.hpp"
-#include "Acts/Utilities/Delegate.hpp"
-#include "Acts/Utilities/Logger.hpp"
-
-namespace Acts
-{
-    class MagneticFieldProvider;
-    class TrackingGeometry;
-}
-
-class RecActsTrackReFitting : public GaudiAlgorithm
-{
-    public:
-        RecActsTrackReFitting(const std::string& name, ISvcLocator* svcLoc);
-
-        StatusCode initialize();
-        StatusCode execute();
-        StatusCode finalize();
-
-    private:
-        // Input collections
-        DataHandle<edm4hep::TrackCollection> _inTrackColHdl{"CompleteTracks", Gaudi::DataHandle::Reader, this};
-
-        // associations
-        DataHandle<edm4hep::MCRecoTrackerAssociationCollection> _inVTXAssColHdl{"VXDTrackerHitAssociation", Gaudi::DataHandle::Reader, this};
-
-        // Output collections
-        DataHandle<edm4hep::TrackCollection> _outColHdl{"ACTSRefitTracks", Gaudi::DataHandle::Writer, this};
-
-        Gaudi::Property<std::string> m_particle{this, "AssumeParticle", "muon"};
-        Gaudi::Property<double> m_field{this, "bFieldInZ", 3.0}; // tesla
-        Gaudi::Property<std::vector<double>> initialVarInflation{this, "initialVarInflation", {1, 1, 1, 1, 1, 1}};
-        Gaudi::Property<double> noTimeVarInflation{this, "noTimeVarInflation", 1.};
-        
-        // services
-        // SmartIF<IGeomSvc> m_geosvc;
-        SmartIF<IRecActsSvc> recActsSvc;
-        SmartIF<IChronoStatSvc> chronoStatSvc;
-        
-        // Decoders
-        dd4hep::DDSegmentation::BitFieldCoder *vtx_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKBarrel_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *ITKEndcap_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *tpc_decoder;
-        dd4hep::DDSegmentation::BitFieldCoder *OTKBarrel_decoder;
-
-        std::shared_ptr<ActsHelper::TrackFitterFunction> fit;
-        std::shared_ptr<const Acts::MagneticFieldProvider> magneticField;
-        // std::shared_ptr<ActsHelper::MeasurementCalibrator> m_calibrator;
-
-        // globalChiSquareFitter config
-        bool multipleScattering = false;
-        bool energyLoss = false;
-        Acts::FreeToBoundCorrection freeToBoundCorrection;
-        std::size_t nUpdateMax = 5;
-        double relChi2changeCutOff = 1e-7;
-
-        // context
-        Acts::GeometryContext geoContext;
-        Acts::MagneticFieldContext magFieldContext;
-        Acts::CalibrationContext calibContext;
-
-        // double noTimeVarInflation = 10.;
-        std::array<double, 6> initialSigmas = {
-            5 * Acts::UnitConstants::um,
-            5 * Acts::UnitConstants::um,
-            2e-2 * Acts::UnitConstants::degree,
-            2e-2 * Acts::UnitConstants::degree,
-            1e-1 * Acts::UnitConstants::e / Acts::UnitConstants::GeV,
-            1 * Acts::UnitConstants::s
-        };
-        std::array<double, 6> initialSimgaQoverPCoefficients = {
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::mm / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            0 * Acts::UnitConstants::degree / (Acts::UnitConstants::e * Acts::UnitConstants::GeV),
-            1e-1,
-            0 * Acts::UnitConstants::ns / (Acts::UnitConstants::e * Acts::UnitConstants::GeV)
-        };
-        
-        std::array<std::string, 8> particleNames = {"muon", "pion", "electron", "kaon", "proton", "photon", "geantino", "chargedgeantino"};
-
-        // constants
-        const double _FCT = 2.99792458E-4;
-        uint64_t TPC_volume_id = 43;
-        uint64_t OTK_volume_id = 45;
-        std::vector<uint64_t> VXD_volume_ids{26, 27, 28, 29, 30};
-        std::vector<uint64_t> ITKBarrel_volume_ids{33, 36, 39};
-        std::vector<uint64_t> ITKEndcap_positive_volume_ids{34, 37, 40, 41};
-        std::vector<uint64_t> ITKEndcap_negative_volume_ids{32, 15, 10, 8};
-        std::vector<uint64_t> ITKBarrel_module_nums{7, 10, 14};
-        const std::array<Acts::BoundIndices, 6> writeout_indices{
-            Acts::BoundIndices::eBoundLoc0, Acts::BoundIndices::eBoundPhi,
-            Acts::BoundIndices::eBoundQOverP, Acts::BoundIndices::eBoundLoc1,
-            Acts::BoundIndices::eBoundTheta, Acts::BoundIndices::eBoundTime};
-        Acts::ParticleHypothesis particleHypothesis = Acts::ParticleHypothesis::muon();
-        int _nEvt;
-};
-
-#endif // RecActsTrackReFitting_H
\ No newline at end of file
-- 
GitLab