From e36b3989457d65296e28b50e59c752b13d7361fc Mon Sep 17 00:00:00 2001
From: Tobias Bockh <tobias.boeckh@cern.ch>
Date: Wed, 23 Nov 2022 19:27:59 +0100
Subject: [PATCH] track truth matching

---
 PhysicsAnalysis/NtupleDumper/CMakeLists.txt   |   2 +-
 .../NtupleDumper/src/NtupleDumperAlg.cxx      | 180 +++++++++++++++++-
 .../NtupleDumper/src/NtupleDumperAlg.h        |  34 ++++
 .../Acts/FaserActsKalmanFilter/CMakeLists.txt |   6 +
 .../IFiducialParticleTool.h                   |  28 +++
 .../ITrackTruthMatchingTool.h                 |  21 ++
 .../src/FiducialParticleTool.cxx              |  91 +++++++++
 .../src/FiducialParticleTool.h                |  37 ++++
 .../src/TrackTruthMatchingTool.cxx            | 103 ++++++++++
 .../src/TrackTruthMatchingTool.h              |  40 ++++
 .../FaserActsKalmanFilter_entries.cxx         |   4 +
 11 files changed, 536 insertions(+), 10 deletions(-)
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IFiducialParticleTool.h
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackTruthMatchingTool.h
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.cxx
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.h
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.cxx
 create mode 100644 Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.h

diff --git a/PhysicsAnalysis/NtupleDumper/CMakeLists.txt b/PhysicsAnalysis/NtupleDumper/CMakeLists.txt
index 944ed968..b81b9818 100644
--- a/PhysicsAnalysis/NtupleDumper/CMakeLists.txt
+++ b/PhysicsAnalysis/NtupleDumper/CMakeLists.txt
@@ -5,7 +5,7 @@ atlas_add_component(
         src/NtupleDumperAlg.h
         src/NtupleDumperAlg.cxx
         src/component/NtupleDumper_entries.cxx
-        LINK_LIBRARIES AthenaBaseComps StoreGateLib xAODFaserWaveform xAODFaserTrigger ScintIdentifier FaserCaloIdentifier GeneratorObjects FaserActsGeometryLib TrackerSimEvent TrackerSimData TrackerIdentifier TrackerReadoutGeometry TrkTrack GeoPrimitives TrackerRIO_OnTrack TrackerSpacePoint
+        LINK_LIBRARIES AthenaBaseComps StoreGateLib xAODFaserWaveform xAODFaserTrigger ScintIdentifier FaserCaloIdentifier GeneratorObjects FaserActsGeometryLib TrackerSimEvent TrackerSimData TrackerIdentifier TrackerReadoutGeometry TrkTrack GeoPrimitives TrackerRIO_OnTrack TrackerSpacePoint FaserActsKalmanFilterLib
 )
 
 atlas_install_python_modules(python/*.py)
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
index d1a07f42..587a894b 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
@@ -16,6 +16,9 @@
 #include "xAODTruth/TruthParticle.h"
 #include <cmath>
 #include <TH1F.h>
+#include <numeric>
+
+constexpr float NaN = std::numeric_limits<double>::quiet_NaN();
 
 
 NtupleDumperAlg::NtupleDumperAlg(const std::string &name, 
@@ -70,7 +73,7 @@ void NtupleDumperAlg::FillWaveBranches(const xAOD::WaveformHitContainer &wave) c
   }
 }
 
-StatusCode NtupleDumperAlg::initialize() 
+StatusCode NtupleDumperAlg::initialize()
 {
   ATH_CHECK(m_truthEventContainer.initialize());
   ATH_CHECK(m_truthParticleContainer.initialize());
@@ -85,6 +88,7 @@ StatusCode NtupleDumperAlg::initialize()
   ATH_CHECK(m_simDataCollection.initialize());
   ATH_CHECK(m_FaserTriggerData.initialize());
   ATH_CHECK(m_ClockWaveformContainer.initialize());
+  ATH_CHECK(m_siHitCollectionKey.initialize());
 
   ATH_CHECK(detStore()->retrieve(m_sctHelper,       "FaserSCT_ID"));
   ATH_CHECK(detStore()->retrieve(m_vetoNuHelper,    "VetoNuID"));
@@ -96,6 +100,8 @@ StatusCode NtupleDumperAlg::initialize()
   ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
   ATH_CHECK(m_extrapolationTool.retrieve());
   ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackTruthMatchingTool.retrieve());
+  ATH_CHECK(m_fiducialParticleTool.retrieve());
 
   ATH_CHECK(m_spacePointContainerKey.initialize());
 
@@ -220,6 +226,41 @@ StatusCode NtupleDumperAlg::initialize()
   m_tree->Branch("Track_ThetaX_atCalo", &m_thetaxCalo);
   m_tree->Branch("Track_ThetaY_atCalo", &m_thetayCalo);
 
+  m_tree->Branch("t_pdg", &m_t_pdg);
+  m_tree->Branch("t_barcode", &m_t_barcode);
+  m_tree->Branch("t_truthHitRatio", &m_t_truthHitRatio);
+  m_tree->Branch("t_prodVtx_x", &m_t_prodVtx_x);
+  m_tree->Branch("t_prodVtx_y", &m_t_prodVtx_y);
+  m_tree->Branch("t_prodVtx_z", &m_t_prodVtx_z);
+  m_tree->Branch("t_decayVtx_x", &m_t_decayVtx_x);
+  m_tree->Branch("t_decayVtx_y", &m_t_decayVtx_y);
+  m_tree->Branch("t_decayVtx_z", &m_t_decayVtx_z);
+  m_tree->Branch("t_px", &m_t_px);
+  m_tree->Branch("t_py", &m_t_py);
+  m_tree->Branch("t_pz", &m_t_pz);
+  m_tree->Branch("t_theta", &m_t_theta);
+  m_tree->Branch("t_phi", &m_t_phi);
+  m_tree->Branch("t_p", &m_t_p);
+  m_tree->Branch("t_pT", &m_t_pT);
+  m_tree->Branch("t_eta", &m_t_eta);
+  m_tree->Branch("t_st0_x", &m_t_st_x[0]);
+  m_tree->Branch("t_st0_y", &m_t_st_y[0]);
+  m_tree->Branch("t_st0_z", &m_t_st_z[0]);
+  m_tree->Branch("t_st1_x", &m_t_st_x[1]);
+  m_tree->Branch("t_st1_y", &m_t_st_y[1]);
+  m_tree->Branch("t_st1_z", &m_t_st_z[1]);
+  m_tree->Branch("t_st2_x", &m_t_st_x[2]);
+  m_tree->Branch("t_st2_y", &m_t_st_y[2]);
+  m_tree->Branch("t_st2_z", &m_t_st_z[2]);
+  m_tree->Branch("t_st3_x", &m_t_st_x[3]);
+  m_tree->Branch("t_st3_y", &m_t_st_y[3]);
+  m_tree->Branch("t_st3_z", &m_t_st_z[3]);
+  m_tree->Branch("isFiducial", &m_isFiducial);
+
+  m_tree->Branch("truthParticleBarcode", &m_truthParticleBarcode);
+  m_tree->Branch("truthParticleMatchedTracks", &m_truthParticleMatchedTracks);
+  m_tree->Branch("truthParticleIsFiducial", &m_truthParticleIsFiducial);
+
   m_tree->Branch("pTruthLepton", &m_truthLeptonMomentum, "pTruthLepton/D");
   m_tree->Branch("truthBarcode", &m_truthBarcode, "truthBarcode/I");
   m_tree->Branch("truthPdg", &m_truthPdg, "truthPdg/I");
@@ -273,7 +314,7 @@ StatusCode NtupleDumperAlg::initialize()
 }
 
 
-StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const 
+StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
 {
   clearTree();
 
@@ -339,7 +380,7 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
 
       return StatusCode::SUCCESS; // finished with this event
 
-    } else if ( ((m_tap&8)==0) && (((m_tap&4)==0)||((m_tap&2)==0)) && (((m_tap&4)==0)||((m_tap&1)==0)) && (((m_tap&2)==0)||((m_tap&1)==0)) ) { // don't process events that don't trigger coincidence triggers: 1=calo, 2=veotnu|neto1|preshower, 4=TimingLayer, 8=(VetoNu|Veto2)&Preshower 
+    } else if ( ((m_tap&8)==0) && (((m_tap&4)==0)||((m_tap&2)==0)) && (((m_tap&4)==0)||((m_tap&1)==0)) && (((m_tap&2)==0)||((m_tap&1)==0)) ) { // don't process events that don't trigger coincidence triggers: 1=calo, 2=veotnu|neto1|preshower, 4=TimingLayer, 8=(VetoNu|Veto2)&Preshower
       return StatusCode::SUCCESS;
     }
     m_tbp=triggerData->tbp();
@@ -420,7 +461,7 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
   FillWaveBranches(*triggerContainer);
   FillWaveBranches(*preshowerContainer);
   FillWaveBranches(*ecalContainer);
-  
+
   m_calo_total=m_wave_charge[0]+m_wave_charge[1]+m_wave_charge[2]+m_wave_charge[3];
   m_calo_rawtotal=m_wave_raw_charge[0]+m_wave_raw_charge[1]+m_wave_raw_charge[2]+m_wave_raw_charge[3];
 
@@ -430,13 +471,13 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     m_Calo1_Edep = (m_wave_charge[1] / 24.333) * m_MIP_sim_Edep_calo;
     m_Calo2_Edep = (m_wave_charge[2] / 24.409) * m_MIP_sim_Edep_calo;
     m_Calo3_Edep = (m_wave_charge[3] / 25.555) * m_MIP_sim_Edep_calo;
-  } else if (m_CaloConfig == "Low_gain") { // assume low gain calo 
+  } else if (m_CaloConfig == "Low_gain") { // assume low gain calo
     m_Calo0_Edep = (m_wave_charge[0] / 0.7909) * m_MIP_sim_Edep_calo;
     m_Calo1_Edep = (m_wave_charge[1] / 0.8197) * m_MIP_sim_Edep_calo;
     m_Calo2_Edep = (m_wave_charge[2] / 0.8256) * m_MIP_sim_Edep_calo;
     m_Calo3_Edep = (m_wave_charge[3] / 0.8821) * m_MIP_sim_Edep_calo;
   } else {
-   ATH_MSG_WARNING("Run config is neither High_gain nor Low_gain, it is " << m_CaloConfig << ", calo calibration will be zero"); 
+   ATH_MSG_WARNING("Run config is neither High_gain nor Low_gain, it is " << m_CaloConfig << ", calo calibration will be zero");
   }
   m_Calo_Total_Edep = m_Calo0_Edep + m_Calo1_Edep + m_Calo2_Edep + m_Calo3_Edep;
 
@@ -517,6 +558,16 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     m_trackseg_pz.push_back(SegMomentum.z());
   }
 
+  std::map<int, size_t> truthParticleCount {};
+  if (!realData) {
+    SG::ReadHandle<xAOD::TruthParticleContainer> truthParticleContainer { m_truthParticleContainer, ctx };
+    ATH_CHECK(truthParticleContainer.isValid() && truthParticleContainer->size() > 0);
+    for (const xAOD::TruthParticle *tp : *truthParticleContainer) {
+      if (tp->p4().P() > m_minMomentum)
+        truthParticleCount[tp->barcode()] = 0;
+    }
+  }
+
   SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection, ctx};
   ATH_CHECK(trackCollection.isValid());
   const Trk::TrackParameters* candidateParameters {nullptr};
@@ -580,6 +631,56 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     m_pzdown.push_back(candidateDownParameters->momentum().z());
     m_pdown.push_back(sqrt( pow(candidateDownParameters->momentum().x(),2) + pow(candidateDownParameters->momentum().y(),2) + pow(candidateDownParameters->momentum().z(),2) ));
 
+    if (!realData) {
+      auto [truthParticle, hitCount] = m_trackTruthMatchingTool->getTruthParticle(track);
+      if (truthParticle != nullptr) {
+        if (truthParticleCount.count(truthParticle->barcode()) > 0)
+          truthParticleCount[truthParticle->barcode()] += 1;
+        m_t_pdg.push_back(truthParticle->pdgId());
+        m_t_barcode.push_back(truthParticle->barcode());
+        // the track fit eats up 5 degrees of freedom, thus the number of hits on track is m_DoF + 5
+        m_t_truthHitRatio.push_back(hitCount / (m_DoF.back() + 5));
+        m_isFiducial.push_back(m_fiducialParticleTool->isFiducial(truthParticle->barcode()));
+        auto positions = m_fiducialParticleTool->getTruthPositions(truthParticle->barcode());
+        for (int station = 0; station < 4; ++station) {
+          m_t_st_x[station].push_back(positions[station].x());
+          m_t_st_y[station].push_back(positions[station].y());
+          m_t_st_z[station].push_back(positions[station].z());
+        }
+        if (truthParticle->hasProdVtx()) {
+          m_t_prodVtx_x.push_back(truthParticle->prodVtx()->x());
+          m_t_prodVtx_y.push_back(truthParticle->prodVtx()->y());
+          m_t_prodVtx_z.push_back(truthParticle->prodVtx()->z());
+        } else {
+          m_t_prodVtx_x.push_back(NaN);
+          m_t_prodVtx_y.push_back(NaN);
+          m_t_prodVtx_z.push_back(NaN);
+        }
+        if (truthParticle->hasDecayVtx()) {
+          m_t_decayVtx_x.push_back(truthParticle->decayVtx()->x());
+          m_t_decayVtx_y.push_back(truthParticle->decayVtx()->y());
+          m_t_decayVtx_z.push_back(truthParticle->decayVtx()->z());
+        } else {
+          m_t_decayVtx_x.push_back(NaN);
+          m_t_decayVtx_y.push_back(NaN);
+          m_t_decayVtx_z.push_back(NaN);
+        }
+        m_t_px.push_back(truthParticle->px());
+        m_t_py.push_back(truthParticle->py());
+        m_t_pz.push_back(truthParticle->pz());
+        m_t_theta.push_back(truthParticle->p4().Theta());
+        m_t_phi.push_back(truthParticle->p4().Phi());
+        m_t_p.push_back(truthParticle->p4().P());
+        m_t_pT.push_back(truthParticle->p4().Pt());
+        m_t_eta.push_back(truthParticle->p4().Eta());
+      } else {
+        setNaN();
+      }
+    } else {
+      ATH_MSG_WARNING("Can not find truthParticle.");
+      setNaN();
+    }
+
     // fill extrapolation vectors with filler values that get changed iif the track extrapolation succeeds
     m_xVetoNu.push_back(-10000);
     m_yVetoNu.push_back(-10000);
@@ -676,7 +777,7 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
         ATH_MSG_INFO("Trig null targetParameters");
       }
 
-    } 
+    }
 
     // extrapolate track from tracking station 3
     if (stationMap.count(3) > 0) { // extrapolation crashes if the track does not end in the Station 3, as it is too far away to extrapolate
@@ -735,6 +836,14 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     m_longTracks++;
   }
 
+  if (!realData) {
+    for (auto &tp : truthParticleCount) {
+      m_truthParticleBarcode.push_back(tp.first);
+      m_truthParticleMatchedTracks.push_back(tp.second);
+      m_truthParticleIsFiducial.push_back(m_fiducialParticleTool->isFiducial(tp.first));
+    }
+  }
+
   /*
   // Here we apply the signal selection
   // Very simple/unrealistic to start
@@ -751,7 +860,7 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
 }
 
 
-StatusCode NtupleDumperAlg::finalize() 
+StatusCode NtupleDumperAlg::finalize()
 {
   return StatusCode::SUCCESS;
 }
@@ -843,7 +952,7 @@ NtupleDumperAlg::clearTree() const
   m_charge.clear();
   m_nLayers.clear();
   m_longTracks = 0;
- 
+
   m_nHit0.clear();
   m_nHit1.clear();
   m_nHit2.clear();
@@ -884,7 +993,60 @@ NtupleDumperAlg::clearTree() const
   m_thetaxCalo.clear();
   m_thetayCalo.clear();
 
+  m_t_pdg.clear();
+  m_t_barcode.clear();
+  m_t_truthHitRatio.clear();
+  m_t_prodVtx_x.clear();
+  m_t_prodVtx_y.clear();
+  m_t_prodVtx_z.clear();
+  m_t_decayVtx_x.clear();
+  m_t_decayVtx_y.clear();
+  m_t_decayVtx_z.clear();
+  m_t_px.clear();
+  m_t_py.clear();
+  m_t_pz.clear();
+  m_t_theta.clear();
+  m_t_phi.clear();
+  m_t_p.clear();
+  m_t_pT.clear();
+  m_t_eta.clear();
+  m_isFiducial.clear();
+  for (int station = 0; station < 4; ++station) {
+    m_t_st_x[station].clear();
+    m_t_st_y[station].clear();
+    m_t_st_z[station].clear();
+  }
+  m_truthParticleBarcode.clear();
+  m_truthParticleMatchedTracks.clear();
+  m_truthParticleIsFiducial.clear();
+
   m_truthLeptonMomentum = 0;
   m_truthBarcode = 0;
   m_truthPdg = 0;
 }
+
+void NtupleDumperAlg::setNaN() const {
+  m_t_pdg.push_back(0);
+  m_t_barcode.push_back(-1);
+  m_t_truthHitRatio.push_back(NaN);
+  m_t_prodVtx_x.push_back(NaN);
+  m_t_prodVtx_y.push_back(NaN);
+  m_t_prodVtx_z.push_back(NaN);
+  m_t_decayVtx_x.push_back(NaN);
+  m_t_decayVtx_y.push_back(NaN);
+  m_t_decayVtx_z.push_back(NaN);
+  m_t_px.push_back(NaN);
+  m_t_py.push_back(NaN);
+  m_t_pz.push_back(NaN);
+  m_t_theta.push_back(NaN);
+  m_t_phi.push_back(NaN);
+  m_t_p.push_back(NaN);
+  m_t_pT.push_back(NaN);
+  m_t_eta.push_back(NaN);
+  for (int station = 0; station < 4; ++station) {
+    m_t_st_x[station].push_back(NaN);
+    m_t_st_y[station].push_back(NaN);
+    m_t_st_z[station].push_back(NaN);
+  }
+  m_isFiducial.push_back(false);
+}
\ No newline at end of file
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
index 1b26caad..fb805829 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
@@ -15,6 +15,9 @@
 #include "TrackerSimData/TrackerSimDataCollection.h"
 #include "FaserActsGeometryInterfaces/IFaserActsExtrapolationTool.h"
 #include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/IFiducialParticleTool.h"
+#include "FaserActsKalmanFilter/ITrackTruthMatchingTool.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
 
 #include <vector>
 
@@ -44,6 +47,7 @@ private:
 
   bool waveformHitOK(const xAOD::WaveformHit* hit) const;
   void clearTree() const;
+  void setNaN() const;
   void addBranch(const std::string &name,float* var);
   void addBranch(const std::string &name,unsigned int* var);
   void addWaveBranches(const std::string &name, int nchannels, int first);
@@ -64,11 +68,14 @@ private:
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_ecalContainer { this, "EcalContainer", "CaloWaveformHits", "Ecal hit container name" };
   SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainer { this, "ClusterContainer", "SCT_ClusterContainer", "Tracker cluster container name" };
   SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_spacePointContainerKey { this, "SpacePoints", "SCT_SpacePointContainer", "space point container"};
+  SG::ReadHandleKey<FaserSiHitCollection> m_siHitCollectionKey{this, "FaserSiHitCollection", "SCT_Hits"};
 
   SG::ReadHandleKey<xAOD::FaserTriggerData> m_FaserTriggerData     { this, "FaserTriggerDataKey", "FaserTriggerData", "ReadHandleKey for xAOD::FaserTriggerData"};
   SG::ReadHandleKey<xAOD::WaveformClock> m_ClockWaveformContainer     { this, "WaveformClockKey", "WaveformClock", "ReadHandleKey for ClockWaveforms Container"};
   ToolHandle<IFaserActsExtrapolationTool> m_extrapolationTool { this, "ExtrapolationTool", "FaserActsExtrapolationTool" };  
   ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  ToolHandle<ITrackTruthMatchingTool> m_trackTruthMatchingTool {this, "TrackTruthMatchingTool", "TrackTruthMatchingTool"};
+  ToolHandle<IFiducialParticleTool> m_fiducialParticleTool {this, "FiducialParticleTool", "FiducialParticleTool"};
 
   const TrackerDD::SCT_DetectorManager* m_detMgr {nullptr};
 
@@ -86,6 +93,7 @@ private:
   IntegerProperty m_flukaCollisions   { this, "FlukaCollisions", 137130000, "Number of proton-proton collisions in FLUKA sample." };
   DoubleProperty  m_flukaCrossSection { this, "FlukaCrossSection", 80.0, "Fluka p-p inelastic cross-section in millibarns." };
   DoubleProperty  m_genieLuminosity   { this, "GenieLuminosity", 150.0, "Genie luminosity in inverse fb." };
+  DoubleProperty  m_minMomentum       { this, "MinMomentum", 50000.0, "Write out all truth particles with a momentum larger MinMomentum"};
 
   double m_baseEventCrossSection {1.0};
   const double kfemtoBarnsPerMilliBarn {1.0e12};
@@ -203,6 +211,32 @@ private:
   mutable std::vector<double> m_thetaxCalo;
   mutable std::vector<double> m_thetayCalo;
 
+  mutable std::vector<int> m_t_pdg; // pdg code of the truth matched particle
+  mutable std::vector<int> m_t_barcode; // barcode of the truth matched particle
+  mutable std::vector<double> m_t_truthHitRatio; // ratio of hits on track matched to the truth particle over all hits on track
+  mutable std::vector<double> m_t_prodVtx_x; // x component of the production vertex in mm
+  mutable std::vector<double> m_t_prodVtx_y; // y component of the production vertex in mm
+  mutable std::vector<double> m_t_prodVtx_z; // z component of the production vertex in mm
+  mutable std::vector<double> m_t_decayVtx_x; // x component of the decay vertex in mm
+  mutable std::vector<double> m_t_decayVtx_y; // y component of the decay vertex in mm
+  mutable std::vector<double> m_t_decayVtx_z; // z component of the decay vertex in mm
+  mutable std::vector<double> m_t_px; // truth momentum px in MeV
+  mutable std::vector<double> m_t_py;  // truth momentum py in MeV
+  mutable std::vector<double> m_t_pz;  // truth momentum pz in MeV
+  mutable std::vector<double> m_t_theta; // angle of truth particle with respsect to the beam axis in rad, theta = arctan(sqrt(px * px + py * py) / pz)
+  mutable std::vector<double> m_t_phi; // polar angle of truth particle in rad, phi = arctan(py / px)
+  mutable std::vector<double> m_t_p; // truth momentum p in MeV
+  mutable std::vector<double> m_t_pT; // transverse truth momentum pT in MeV
+  mutable std::vector<double> m_t_eta; // eta of truth particle
+  mutable std::array<std::vector<double>, 4> m_t_st_x; // vector of the x components of the simulated hits of the truth particle for each station
+  mutable std::array<std::vector<double>, 4> m_t_st_y; // vector of the y components of the simulated hits of the truth particle for each station
+  mutable std::array<std::vector<double>, 4> m_t_st_z; // vector of the z components of the simulated hits of the truth particle for each station
+  mutable std::vector<bool> m_isFiducial; // track is fiducial if there are simulated hits for stations 1 - 3 and the distance from the center is smaller than 100 mm
+
+  mutable std::vector<int> m_truthParticleBarcode; // vector of barcodes of all truth particles with a momentum larger 50 GeV
+  mutable std::vector<int> m_truthParticleMatchedTracks; // vector of number of tracks to which a truth particle is matched to
+  mutable std::vector<bool> m_truthParticleIsFiducial; // vector of boolean showing whether a truth particle is fiducial
+
   mutable double m_truthLeptonMomentum;
   mutable int    m_truthBarcode;
   mutable int    m_truthPdg;
diff --git a/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt b/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
index 9377c471..f93c0942 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
+++ b/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
@@ -39,8 +39,10 @@ atlas_add_component(FaserActsKalmanFilter
     MyTrackSeedTool.h
     FaserActsKalmanFilter/IdentifierLink.h
     FaserActsKalmanFilter/IndexSourceLink.h
+    FaserActsKalmanFilter/IFiducialParticleTool.h
     FaserActsKalmanFilter/ITrackFinderTool.h
     FaserActsKalmanFilter/ITrackSeedTool.h
+    FaserActsKalmanFilter/ITrackTruthMatchingTool.h
     KalmanFitterTool.h
     LinearFit.h
 #    ClusterTrackSeedTool.h
@@ -77,6 +79,8 @@ atlas_add_component(FaserActsKalmanFilter
     src/CombinatorialKalmanFilterAlg.cxx
     src/EffPlotTool.cxx
     src/FaserActsKalmanFilterAlg.cxx
+    src/FiducialParticleTool.h
+    src/FiducialParticleTool.cxx
     src/GhostBusters.cxx
     src/MyTrackSeedTool.cxx
     src/KalmanFitterTool.cxx
@@ -101,6 +105,8 @@ atlas_add_component(FaserActsKalmanFilter
     src/TrackClassification.cxx
     src/TrackSeedWriterTool.cxx
     src/TrackSelection.cxx
+    src/TrackTruthMatchingTool.h
+    src/TrackTruthMatchingTool.cxx
 #    src/TruthTrackFinderTool.cxx
 #    src/TruthSeededTrackFinderTool.cxx
     src/ThreeStationTrackSeedTool.cxx
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IFiducialParticleTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IFiducialParticleTool.h
new file mode 100644
index 00000000..1df9aeab
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IFiducialParticleTool.h
@@ -0,0 +1,28 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS and FASER
+  collaborations
+*/
+
+#ifndef FASERACTSKALMANFILTER_IFIDUCIALEVENTSELECTIONTOOL_H
+#define FASERACTSKALMANFILTER_IFIDUCIALEVENTSELECTIONTOOL_H
+
+#include "CLHEP/Geometry/Point3D.h"
+
+class IFiducialParticleTool : virtual public IAlgTool {
+public:
+  DeclareInterfaceID(IFiducialParticleTool, 1, 0);
+
+  /** Check if truth hits for the given barcode exist and are in the
+   * fiducial volume (r < 100mm) in stations 1 to 3.
+   * @param barcode of a xAOD::TruthParticle
+   */
+  virtual bool isFiducial(int barcode) const = 0;
+
+  /** Return average truth position in each station.
+   * @param barcode of a xAOD::TruthParticle
+   */
+  virtual std::array<HepGeom::Point3D<double>, 4>
+  getTruthPositions(int barcode) const = 0;
+};
+
+#endif // FASERACTSKALMANFILTER_IFIDUCIALEVENTSELECTIONTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackTruthMatchingTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackTruthMatchingTool.h
new file mode 100644
index 00000000..045e597a
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackTruthMatchingTool.h
@@ -0,0 +1,21 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS and FASER
+  collaborations
+*/
+
+#ifndef FASERACTSKALMANFILTER_ITRACKTRUTHMATCHINGTOOL_H
+#define FASERACTSKALMANFILTER_ITRACKTRUTHMATCHINGTOOL_H
+
+#include "GaudiKernel/IAlgTool.h"
+#include "TrkTrack/Track.h"
+#include "xAODTruth/TruthParticle.h"
+
+class ITrackTruthMatchingTool : virtual public IAlgTool {
+public:
+  DeclareInterfaceID(ITrackTruthMatchingTool, 1, 0);
+
+  virtual std::pair<const xAOD::TruthParticle*, int>
+  getTruthParticle(const Trk::Track *track) const = 0;
+};
+
+#endif /* FASERACTSKALMANFILTER_ITRACKTRUTHMATCHINGTOOL_H */
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.cxx
new file mode 100644
index 00000000..101ddfdf
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.cxx
@@ -0,0 +1,91 @@
+#include "FiducialParticleTool.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+
+constexpr float NaN = std::numeric_limits<double>::quiet_NaN();
+
+FiducialParticleTool::FiducialParticleTool(const std::string &type,
+                                           const std::string &name,
+                                           const IInterface *parent)
+    : base_class(type, name, parent) {}
+
+StatusCode FiducialParticleTool::initialize() {
+  ATH_CHECK(m_siHitCollectionKey.initialize());
+  ATH_CHECK(detStore()->retrieve(m_sctHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
+  return StatusCode::SUCCESS;
+}
+
+StatusCode FiducialParticleTool::finalize() { return StatusCode::SUCCESS; }
+
+bool FiducialParticleTool::isFiducial(int barcode) const {
+  auto positions = getTruthPositions(barcode);
+  bool isFiducial{true};
+  for (int station = 1; station < 4; ++station) {
+    double x{positions[station].x()};
+    double y{positions[station].y()};
+    if (std::isnan(x) || std::isnan(y) || (x * x + y * y > 100 * 100))
+      isFiducial = false;
+  }
+  return isFiducial;
+}
+
+HepGeom::Point3D<double>
+FiducialParticleTool::getGlobalPosition(const FaserSiHit &hit) const {
+  Identifier waferId =
+      m_sctHelper->wafer_id(hit.getStation(), hit.getPlane(), hit.getRow(),
+                            hit.getModule(), hit.getSensor());
+  auto localStartPos = hit.localStartPosition();
+  auto localEndPos = hit.localEndPosition();
+  HepGeom::Point3D<double> localPos = 0.5 * (localEndPos + localStartPos);
+  const TrackerDD::SiDetectorElement *element =
+      m_detMgr->getDetectorElement(waferId);
+  auto globalPosition =
+      Amg::EigenTransformToCLHEP(element->transformHit()) * localPos;
+  return globalPosition;
+}
+
+std::array<HepGeom::Point3D<double>, 4>
+FiducialParticleTool::getTruthPositions(int barcode) const {
+  // initialize positions as NaN
+  std::array<HepGeom::Point3D<double>, 4> positions{};
+  for (auto &station : positions) {
+    station.setX(NaN);
+    station.setY(NaN);
+    station.setZ(NaN);
+  }
+
+  // get simulated hits
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection(m_siHitCollectionKey);
+  if (!siHitCollection.isValid()) {
+    ATH_MSG_WARNING("FaserSiHitCollection not valid.");
+    return positions;
+  }
+
+  // create map with truth positions in each station
+  std::array<std::vector<HepGeom::Point3D<double>>, 4> hitMap{};
+  for (const FaserSiHit &hit : *siHitCollection) {
+    if (hit.trackNumber() == barcode) {
+      auto position = getGlobalPosition(hit);
+      hitMap[hit.getStation()].push_back(position);
+    }
+  }
+
+  // calculate average position in each station
+  for (int station = 0; station < 4; ++station) {
+    std::vector<HepGeom::Point3D<double>> &hits{hitMap[station]};
+    if (hits.empty()) {
+      continue;
+    } else {
+      // calculate average position of all FaserSiHits in a station
+      auto const count = static_cast<double>(hits.size());
+      HepGeom::Point3D<double> sums{};
+      for (const HepGeom::Point3D<double> &hit : hits)
+        sums += hit;
+      positions[station] = sums / count;
+    }
+  }
+  return positions;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.h b/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.h
new file mode 100644
index 00000000..8d143573
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/FiducialParticleTool.h
@@ -0,0 +1,37 @@
+#ifndef FASERACTSKALMANFILTER_FIDUCIALEVENTSELECTIONTOOL_H
+#define FASERACTSKALMANFILTER_FIDUCIALEVENTSELECTIONTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserActsKalmanFilter/IFiducialParticleTool.h"
+#include "GeoPrimitives/GeoPrimitives.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+class FiducialParticleTool : public extends<AthAlgTool, IFiducialParticleTool> {
+public:
+  FiducialParticleTool(const std::string &type, const std::string &name,
+                       const IInterface *parent);
+  virtual ~FiducialParticleTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+
+  bool isFiducial(int barcode) const override;
+
+  std::array<HepGeom::Point3D<double>, 4>
+  getTruthPositions(int barcode) const override;
+
+private:
+  HepGeom::Point3D<double> getGlobalPosition(const FaserSiHit &hit) const;
+
+  SG::ReadHandleKey<FaserSiHitCollection> m_siHitCollectionKey{
+      this, "FaserSiHitCollection", "SCT_Hits"};
+
+  const FaserSCT_ID *m_sctHelper{nullptr};
+  const TrackerDD::SCT_DetectorManager *m_detMgr{nullptr};
+};
+
+#endif // FASERACTSKALMANFILTER_FIDUCIALEVENTSELECTIONTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.cxx
new file mode 100644
index 00000000..502faa3d
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.cxx
@@ -0,0 +1,103 @@
+#include "TrackTruthMatchingTool.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+
+TrackTruthMatchingTool::TrackTruthMatchingTool(const std::string &type,
+                                               const std::string &name,
+                                               const IInterface *parent)
+    : base_class(type, name, parent) {}
+
+StatusCode TrackTruthMatchingTool::initialize() {
+  ATH_CHECK(m_simDataCollectionKey.initialize());
+  ATH_CHECK(m_truthParticleContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+std::pair<const xAOD::TruthParticle *, int>
+TrackTruthMatchingTool::getTruthParticle(const Trk::Track *track) const {
+  const xAOD::TruthParticle *truthParticle = nullptr;
+  const EventContext &ctx = Gaudi::Hive::currentContext();
+  SG::ReadHandle<xAOD::TruthParticleContainer> truthParticleContainer{
+      m_truthParticleContainerKey, ctx};
+  if (!truthParticleContainer.isValid()) {
+    ATH_MSG_WARNING("xAOD::TruthParticleContainer is not valid.");
+    return {truthParticle, -1};
+  }
+  SG::ReadHandle<TrackerSimDataCollection> simDataCollection{
+      m_simDataCollectionKey, ctx};
+  if (!simDataCollection.isValid()) {
+    ATH_MSG_WARNING("TrackerSimDataCollection is not valid.");
+    return {truthParticle, -1};
+  }
+  std::vector<ParticleHitCount> particleHitCounts{};
+  identifyContributingParticles(*track, *simDataCollection, particleHitCounts);
+  if (particleHitCounts.empty()) {
+    ATH_MSG_WARNING("Cannot find any truth particle matched to the track.");
+    return {truthParticle, -1};
+  }
+  int barcode = particleHitCounts.front().barcode;
+  int hitCount = particleHitCounts.front().hitCount;
+  auto it = std::find_if(truthParticleContainer->begin(),
+                         truthParticleContainer->end(),
+                         [barcode](const xAOD::TruthParticle_v1 *particle) {
+                           return particle->barcode() == barcode;
+                         });
+  if (it == truthParticleContainer->end()) {
+    ATH_MSG_WARNING("Cannot find particle with barcode "
+                    << barcode << " in truth particle container.");
+    return {truthParticle, -1};
+  }
+  truthParticle = *it;
+  return {truthParticle, hitCount};
+}
+
+StatusCode TrackTruthMatchingTool::finalize() { return StatusCode::SUCCESS; }
+
+void TrackTruthMatchingTool::increaseHitCount(
+    std::vector<ParticleHitCount> &particleHitCounts, int barcode) {
+  auto it = std::find_if(
+      particleHitCounts.begin(), particleHitCounts.end(),
+      [=](const ParticleHitCount &phc) { return (phc.barcode == barcode); });
+  // either increase count if we saw the particle before or add it
+  if (it != particleHitCounts.end()) {
+    it->hitCount += 1u;
+  } else {
+    particleHitCounts.push_back({barcode, 1u});
+  }
+}
+
+void TrackTruthMatchingTool::sortHitCount(
+    std::vector<ParticleHitCount> &particleHitCounts) {
+  std::sort(particleHitCounts.begin(), particleHitCounts.end(),
+            [](const ParticleHitCount &lhs, const ParticleHitCount &rhs) {
+              return (lhs.hitCount > rhs.hitCount);
+            });
+}
+
+void TrackTruthMatchingTool::identifyContributingParticles(
+    const Trk::Track &track, const TrackerSimDataCollection &simDataCollection,
+    std::vector<ParticleHitCount> &particleHitCounts) {
+  for (const Trk::MeasurementBase *meas : *track.measurementsOnTrack()) {
+    const auto *clusterOnTrack =
+        dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack *>(meas);
+    if (!clusterOnTrack)
+      continue;
+    std::vector<int> barcodes{};
+    const Tracker::FaserSCT_Cluster *cluster = clusterOnTrack->prepRawData();
+    for (Identifier id : cluster->rdoList()) {
+      if (simDataCollection.count(id) == 0)
+        continue;
+      const auto &deposits = simDataCollection.at(id).getdeposits();
+      for (const TrackerSimData::Deposit &deposit : deposits) {
+        int barcode = deposit.first->barcode();
+        // count each barcode only once for a wafer
+        if (std::find(barcodes.begin(), barcodes.end(), barcode) ==
+            barcodes.end()) {
+          barcodes.push_back(barcode);
+          increaseHitCount(particleHitCounts, barcode);
+        }
+      }
+    }
+  }
+  sortHitCount(particleHitCounts);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.h b/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.h
new file mode 100644
index 00000000..94ab4405
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackTruthMatchingTool.h
@@ -0,0 +1,40 @@
+#ifndef FASERACTSKALMANFILTER_TRACKTRUTHMATCHINGTOOL_H
+#define FASERACTSKALMANFILTER_TRACKTRUTHMATCHINGTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserActsKalmanFilter/ITrackTruthMatchingTool.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "TrkTrack/Track.h"
+#include "xAODTruth/TruthParticleContainer.h"
+
+class TrackTruthMatchingTool
+    : public extends<AthAlgTool, ITrackTruthMatchingTool> {
+public:
+  TrackTruthMatchingTool(const std::string &type, const std::string &name,
+                         const IInterface *parent);
+  virtual ~TrackTruthMatchingTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+
+  std::pair<const xAOD::TruthParticle*, int>
+  getTruthParticle(const Trk::Track *track) const;
+
+private:
+  struct ParticleHitCount {
+    int barcode;
+    size_t hitCount;
+  };
+  static void increaseHitCount(std::vector<ParticleHitCount> &particleHitCounts,
+                               int particleId);
+  static void sortHitCount(std::vector<ParticleHitCount> &particleHitCounts);
+  static void identifyContributingParticles(
+      const Trk::Track &track, const TrackerSimDataCollection &simDataCollection,
+      std::vector<ParticleHitCount> &particleHitCounts);
+
+  SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey{
+      this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+  SG::ReadHandleKey<xAOD::TruthParticleContainer> m_truthParticleContainerKey{
+      this, "ParticleContainer", "TruthParticles"};
+};
+
+#endif /* FASERACTSKALMANFILTER_TRACKTRUTHMATCHINGTOOL_H */
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
index 967f8064..d9fefbdd 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
@@ -29,6 +29,8 @@
 #include "../CircleFitTrackSeedTool.h"
 #include "../GhostBusters.h"
 #include "../CreateTrkTrackTool.h"
+#include "../TrackTruthMatchingTool.h"
+#include "../FiducialParticleTool.h"
 
 DECLARE_COMPONENT(FaserActsKalmanFilterAlg)
 DECLARE_COMPONENT(CombinatorialKalmanFilterAlg)
@@ -57,3 +59,5 @@ DECLARE_COMPONENT(SeedingAlg)
 DECLARE_COMPONENT(CircleFitTrackSeedTool)
 DECLARE_COMPONENT(GhostBusters)
 DECLARE_COMPONENT(CreateTrkTrackTool)
+DECLARE_COMPONENT(TrackTruthMatchingTool)
+DECLARE_COMPONENT(FiducialParticleTool)
-- 
GitLab