From 9fe896c089246ef3840dfc3eaba65978c7317a42 Mon Sep 17 00:00:00 2001
From: Baptiste Ravina <baptiste.ravina@cern.ch>
Date: Tue, 18 Feb 2025 19:33:52 +0000
Subject: [PATCH] CPAlgorithms: add new algorithm to run KLFitter
 reconstruction

CPAlgorithms: add new algorithm to run KLFitter reconstruction
---
 .../ComponentFactoryPreloader/CMakeLists.txt  |   2 +-
 .../Root/ComponentFactoryPreloader.cxx        |   2 +
 .../KLFitterAnalysisAlgorithms/CMakeLists.txt |  38 +
 .../KLFitterAnalysisAlgorithmsDict.h          |  28 +
 .../KLFitterEnums.h                           | 107 ++
 .../KLFitterFinalizeOutputAlg.h               |  39 +
 .../KLFitterResult.h                          | 487 +++++++++
 .../KLFitterResultAuxContainer.h              | 150 +++
 .../KLFitterResultContainer.h                 |  28 +
 .../RunKLFitterAlg.h                          | 218 ++++
 .../KLFitterAnalysisAlgorithms/selection.xml  |   9 +
 .../selection_ath.xml                         |   8 +
 .../Root/KLFitterFinalizeOutputAlg.cxx        |  41 +
 .../Root/KLFitterResult.cxx                   | 248 +++++
 .../Root/KLFitterResultAuxContainer.cxx       | 127 +++
 .../KLFitterAnalysisAlgorithms/Root/LinkDef.h |  21 +
 .../Root/RunKLFitterAlg.cxx                   | 931 ++++++++++++++++++
 .../Root/dict/ContainerProxies.cxx            |   8 +
 .../python/KLFitterConfig.py                  | 253 +++++
 ...FitterReconstructionAlgorithms_entries.cxx |  12 +
 20 files changed, 2756 insertions(+), 1 deletion(-)
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/CMakeLists.txt
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithmsDict.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterEnums.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResult.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultContainer.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/RunKLFitterAlg.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection.xml
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection_ath.xml
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterFinalizeOutputAlg.cxx
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResult.cxx
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResultAuxContainer.cxx
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/LinkDef.h
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/RunKLFitterAlg.cxx
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/dict/ContainerProxies.cxx
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/python/KLFitterConfig.py
 create mode 100644 PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/src/components/KLFitterReconstructionAlgorithms_entries.cxx

diff --git a/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/CMakeLists.txt b/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/CMakeLists.txt
index eb570cea6d27..ed37bc96503c 100644
--- a/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/CMakeLists.txt
+++ b/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/CMakeLists.txt
@@ -11,7 +11,7 @@ atlas_add_library( ComponentFactoryPreloaderLib
    PUBLIC_HEADERS ComponentFactoryPreloader
    LINK_LIBRARIES
    PRIVATE_LINK_LIBRARIES AsgTools
-      AsgAnalysisAlgorithmsLib EgammaAnalysisAlgorithmsLib EventSelectionAlgorithmsLib FTagAnalysisAlgorithmsLib JetAnalysisAlgorithmsLib MetAnalysisAlgorithmsLib MuonAnalysisAlgorithmsLib StandaloneAnalysisAlgorithmsLib TauAnalysisAlgorithmsLib TrackingAnalysisAlgorithmsLib TriggerAnalysisAlgorithmsLib TruthParticleLevelAnalysisAlgorithmsLib
+      AsgAnalysisAlgorithmsLib EgammaAnalysisAlgorithmsLib KLFitterAnalysisAlgorithmsLib EventSelectionAlgorithmsLib FTagAnalysisAlgorithmsLib JetAnalysisAlgorithmsLib MetAnalysisAlgorithmsLib MuonAnalysisAlgorithmsLib StandaloneAnalysisAlgorithmsLib TauAnalysisAlgorithmsLib TrackingAnalysisAlgorithmsLib TriggerAnalysisAlgorithmsLib TruthParticleLevelAnalysisAlgorithmsLib
       AssociationUtilsLib ElectronPhotonFourMomentumCorrectionLib ElectronPhotonSelectorToolsLib IsolationCorrectionsLib IsolationSelectionLib MuonMomentumCorrectionsLib MuonSelectorToolsLib PileupReweightingLib TauAnalysisToolsLib xAODBTaggingEfficiencyLib
       JetCalibToolsLib JetJvtEfficiencyLib JetMomentToolsLib JetUncertaintiesLib METUtilitiesLib egammaMVACalibLib tauRecToolsLib
       GoodRunsListsLib
diff --git a/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/Root/ComponentFactoryPreloader.cxx b/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/Root/ComponentFactoryPreloader.cxx
index 3d1fa269785c..1bd7746a1db9 100644
--- a/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/Root/ComponentFactoryPreloader.cxx
+++ b/PhysicsAnalysis/Algorithms/ComponentFactoryPreloader/Root/ComponentFactoryPreloader.cxx
@@ -76,6 +76,7 @@
 #include <ElectronEfficiencyCorrection/AsgElectronEfficiencyCorrectionTool.h>
 #include <ElectronPhotonFourMomentumCorrection/EgammaCalibrationAndSmearingTool.h>
 #include <ElectronPhotonSelectorTools/AsgDeadHVCellRemovalTool.h>
+#include <KLFitterAnalysisAlgorithms/RunKLFitterAlg.h>
 #include <EventSelectionAlgorithms/ChargeSelectorAlg.h>
 #include <EventSelectionAlgorithms/DileptonInvariantMassSelectorAlg.h>
 #include <EventSelectionAlgorithms/DileptonInvariantMassWindowSelectorAlg.h>
@@ -322,6 +323,7 @@ namespace CP
     ANA_CHECK (asg::registerAlgorithmFactory<CP::VertexSelectionAlg>("CP::VertexSelectionAlg"));
     ANA_CHECK (asg::registerAlgorithmFactory<CP::VGammaORAlg>("CP::VGammaORAlg"));
     ANA_CHECK (asg::registerAlgorithmFactory<CP::xAODWriterAlg>("CP::xAODWriterAlg"));
+    ANA_CHECK (asg::registerAlgorithmFactory<EventReco::RunKLFitterAlg>("EventReco::RunKLFitterAlg"));
 
     ANA_CHECK (asg::registerToolFactory<AsgDeadHVCellRemovalTool> ("AsgDeadHVCellRemovalTool"));
     ANA_CHECK (asg::registerToolFactory<AsgElectronEfficiencyCorrectionTool> ("AsgElectronEfficiencyCorrectionTool"));
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/CMakeLists.txt b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/CMakeLists.txt
new file mode 100644
index 000000000000..efa82a62b69a
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/CMakeLists.txt
@@ -0,0 +1,38 @@
+# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+#
+# @author Oliver Majersky
+# @author Baptiste Ravina
+
+atlas_subdir( KLFitterAnalysisAlgorithms )
+
+if(XAOD_STANDALONE)
+  # Find KLFitter package in atlasexternals,
+  # source from https://www.github.com/KLFitter/KLFitter
+  find_package(KLFitter)
+
+  find_package( ROOT COMPONENTS Core )
+
+  # Generate a CINT dictionary source file:
+  atlas_add_root_dictionary(KLFitterAnalysisAlgorithmsLib _cintDictSource
+    ROOT_HEADERS Root/LinkDef.h
+    EXTERNAL_PACKAGES ROOT )
+
+  atlas_add_library( KLFitterAnalysisAlgorithmsLib
+    KLFitterAnalysisAlgorithms/*.h Root/*.cxx ${_cintDictSource}
+    PUBLIC_HEADERS KLFitterAnalysisAlgorithms
+    INCLUDE_DIRS ${KLFITTER_INCLUDE_DIRS}
+    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+    LINK_LIBRARIES ${KLFITTER_LIBRARIES}
+    AnaAlgorithmLib FourMomUtils SystematicsHandlesLib SelectionHelpersLib xAODJet xAODMuon xAODTau xAODEgamma xAODMissingET
+    FTagAnalysisInterfacesLib PathResolver
+    PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} PATInterfaces xAODCore xAODMetaData )
+
+  atlas_add_dictionary( KLFitterAnalysisAlgorithmsDict
+    KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithmsDict.h
+    KLFitterAnalysisAlgorithms/selection.xml
+    LINK_LIBRARIES KLFitterAnalysisAlgorithmsLib
+    EXTRA_FILES Root/dict/*.cxx )
+
+  atlas_install_python_modules( python/*.py )
+
+endif()
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithmsDict.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithmsDict.h
new file mode 100644
index 000000000000..e218b495b0e7
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithmsDict.h
@@ -0,0 +1,28 @@
+/*
+   Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERANALYSIS_ALGORITHMS_DICT_H
+#define KLFITTERANALYSIS_ALGORITHMS_DICT_H
+
+#include "KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResult.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h"
+#include "KLFitterAnalysisAlgorithms/RunKLFitterAlg.h"
+
+// EDM include(s).
+#include "xAODCore/tools/DictHelpers.h"
+// Instantiate all necessary types for the dictionary.
+namespace {
+struct GCCXML_DUMMY_INSTANTIATION_KLFITTERANALYSISALGORITHMS {
+  // Local type(s).
+  XAOD_INSTANTIATE_NS_CONTAINER_TYPES(xAOD, KLFitterResultContainer);
+  XAOD_INSTANTIATE_NS_OBJECT_TYPES(xAOD, KLFitterResult);
+};
+}  // namespace
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterEnums.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterEnums.h
new file mode 100644
index 000000000000..ed6cdafacd9f
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterEnums.h
@@ -0,0 +1,107 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_KLFITTERENUMS_H_
+#define KLFITTERNANALYSISALGORITHMS_KLFITTERENUMS_H_
+
+#include <map>
+#include <sstream>
+
+#include "KLFitter/LikelihoodBase.h"
+
+namespace EventReco {
+namespace KLFEnums {
+enum Likelihood {
+  ttbar,
+  ttbar_AllHad,
+  ttbar_JetAngles,
+  ttbar_Angular,
+  ttbar_BoostedLJets,
+  ttH,
+  ttZTrilepton
+};
+enum LeptonType { kNoLepton, kElectron, kMuon, kTriElectron, kTriMuon };
+
+enum JetSelectionMode {
+  kLeadingThree,
+  kLeadingFour,
+  kLeadingFive,
+  kLeadingSix,
+  kLeadingSeven,
+  kLeadingEight,
+  // keep btag priority and non-btag priority enum values separate
+  kBtagPriorityThreeJets,
+  kBtagPriorityFourJets,
+  kBtagPriorityFiveJets,
+  kBtagPrioritySixJets,
+  kBtagPrioritySevenJets,
+  kBtagPriorityEightJets
+};
+
+static const std::map<std::string, LeptonType> strToLeptonType{
+    {"kNoLepton", kNoLepton},
+    {"kElectron", kElectron},
+    {"kMuon", kMuon},
+    {"kTriElectron", kTriElectron},
+    {"kTriMuon", kTriMuon}};
+
+static const std::map<std::string, Likelihood> strToLikelihood{
+    {"ttbar", ttbar},
+    {"ttbar_AllHad", ttbar_AllHad},
+    {"ttbar_JetAngles", ttbar_JetAngles},
+    {"ttbar_Angular", ttbar_Angular},
+    {"ttbar_BoostedLJets", ttbar_BoostedLJets},
+    {"ttH", ttH},
+    {"ttZTrilepton", ttZTrilepton}};
+
+static const std::map<std::string, JetSelectionMode> strToJetSelection{
+    {"kLeadingThree", kLeadingThree},
+    {"kLeadingFour", kLeadingFour},
+    {"kLeadingFive", kLeadingFive},
+    {"kLeadingSix", kLeadingSix},
+    {"kLeadingSeven", kLeadingSeven},
+    {"kLeadingEight", kLeadingEight},
+    {"kBtagPriorityThreeJets", kBtagPriorityThreeJets},
+    {"kBtagPriorityFourJets", kBtagPriorityFourJets},
+    {"kBtagPriorityFiveJets", kBtagPriorityFiveJets},
+    {"kBtagPrioritySixJets", kBtagPrioritySixJets},
+    {"kBtagPrioritySevenJets", kBtagPrioritySevenJets},
+    {"kBtagPriorityEightJets", kBtagPriorityEightJets}};
+
+using KLFitter::LikelihoodBase;
+static const std::map<std::string, LikelihoodBase::BtaggingMethod>
+    strToBtagMethod{
+        {"kNotag", LikelihoodBase::BtaggingMethod::kNotag},
+        {"kVetoNoFit", LikelihoodBase::BtaggingMethod::kVetoNoFit},
+        {"kVetoNoFitLight", LikelihoodBase::BtaggingMethod::kVetoNoFitLight},
+        {"kVetoNoFitBoth", LikelihoodBase::BtaggingMethod::kVetoNoFitBoth},
+        {"kVetoHybridNoFit", LikelihoodBase::BtaggingMethod::kVetoHybridNoFit},
+        {"kWorkingPoint", LikelihoodBase::BtaggingMethod::kWorkingPoint},
+        {"kVeto", LikelihoodBase::BtaggingMethod::kVeto},
+        {"kVetoLight", LikelihoodBase::BtaggingMethod::kVetoLight},
+        {"kVetoBoth", LikelihoodBase::BtaggingMethod::kVetoBoth}};
+
+static const std::map<JetSelectionMode, size_t> jetSelToNumber{
+    {kLeadingThree, 3},          {kLeadingFour, 4},
+    {kLeadingFive, 5},           {kLeadingSix, 6},
+    {kLeadingSeven, 7},          {kLeadingEight, 8},
+    {kBtagPriorityThreeJets, 3}, {kBtagPriorityFourJets, 4},
+    {kBtagPriorityFiveJets, 5},  {kBtagPrioritySixJets, 6},
+    {kBtagPrioritySevenJets, 7}, {kBtagPriorityEightJets, 8}};
+
+template <class T>
+std::string printEnumOptions(const std::map<std::string, T>& availOpts) {
+  std::stringstream sstream;
+  for (const auto& elem : availOpts) {
+    sstream << elem.first << " ";
+  }
+  return sstream.str();
+}
+}  // namespace KLFEnums
+}  // namespace EventReco
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h
new file mode 100644
index 000000000000..bd193c245617
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_KLFITTERFINALIZEOUTPUTALG_H_
+#define KLFITTERNANALYSISALGORITHMS_KLFITTERFINALIZEOUTPUTALG_H_
+
+// Algorithm includes
+#include <AnaAlgorithm/AnaAlgorithm.h>
+#include <SystematicsHandles/SysReadHandle.h>
+#include <SystematicsHandles/SysWriteHandle.h>
+
+#include "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h"
+
+namespace EventReco {
+class KLFitterFinalizeOutputAlg final : public EL::AnaAlgorithm {
+ public:
+  KLFitterFinalizeOutputAlg(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual StatusCode initialize() final;
+  virtual StatusCode execute() final;
+
+ private:
+  CP::SysListHandle m_systematicsList{this};
+
+  CP::SysReadHandle<xAOD::KLFitterResultContainer> m_inHandle{
+      this, "resultContainerToCheck", "KLFitterResult_%SYS%",
+      "the input KLFitterResultContainer to check for existence"};
+  CP::SysWriteHandle<xAOD::KLFitterResultContainer,
+                     xAOD::KLFitterResultAuxContainer>
+      m_outHandle{this, "resultContainerToWrite", "KLFitterResult_%SYS%",
+                  "the output KLFitterResultContainer"};
+};
+}  // namespace EventReco
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResult.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResult.h
new file mode 100644
index 000000000000..3c77ebc7fbf5
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResult.h
@@ -0,0 +1,487 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_KLFITTERRESULT_H_
+#define KLFITTERNANALYSISALGORITHMS_KLFITTERRESULT_H_
+
+// EDM include(s).
+#include "AthContainers/AuxElement.h"
+#include "xAODCore/CLASS_DEF.h"
+
+// System include(s).
+#include <vector>
+
+namespace xAOD {
+
+/**
+ * @author John Morris <john.morris@cern.ch>
+ *
+ * @brief KLFitterResult
+ *   A simple xAOD class which we can persist into a mini-xAOD
+ *   The xAOD EDM is way too complex, so let's simplify it
+ *   It's not like ROOT can do schema evolution......
+ *
+ *   This class contains the result of the KLFitter algorithm
+ **/
+class KLFitterResult : public SG::AuxElement {
+ public:
+  /// Default constructor
+  KLFitterResult() = default;
+
+  /// get selectionName
+  std::size_t selectionCode() const;
+  /// set selectionName
+  void setSelectionCode(std::size_t);
+
+  /// get minuitDidNotConverge
+  short minuitDidNotConverge() const;
+  /// set minuitDidNotConverge
+  void setMinuitDidNotConverge(short);
+
+  /// get fitAbortedDueToNaN
+  short fitAbortedDueToNaN() const;
+  /// set fitAbortedDueToNaN
+  void setFitAbortedDueToNaN(short);
+
+  /// get atLeastOneFitParameterAtItsLimit
+  short atLeastOneFitParameterAtItsLimit() const;
+  /// set atLeastOneFitParameterAtItsLimit
+  void setAtLeastOneFitParameterAtItsLimit(short);
+
+  /// get invalidTransferFunctionAtConvergence
+  short invalidTransferFunctionAtConvergence() const;
+  /// set invalidTransferFunctionAtConvergence
+  void setInvalidTransferFunctionAtConvergence(short);
+
+  /// get bestPermutation
+  unsigned int bestPermutation() const;
+  /// set bestPermutation
+  void setBestPermutation(unsigned int);
+
+  /// get logLikelihood
+  float logLikelihood() const;
+  /// set logLikelihood
+  void setLogLikelihood(float);
+
+  /// get eventProbability
+  float eventProbability() const;
+  /// set eventProbability
+  void setEventProbability(float);
+
+  /// get parameters
+  const std::vector<double>& parameters() const;
+  /// set parameters
+  void setParameters(const std::vector<double>&);
+
+  /// get parameterErrors
+  const std::vector<double>& parameterErrors() const;
+  /// set parameterErrors
+  void setParameterErrors(const std::vector<double>&);
+
+  /// get model_bhad_pt
+  float model_bhad_pt() const;
+  /// set model_bhad_pt
+  void setModel_bhad_pt(float);
+
+  /// get model_bhad_eta
+  float model_bhad_eta() const;
+  /// set model_bhad_eta
+  void setModel_bhad_eta(float);
+
+  /// get model_bhad_phi
+  float model_bhad_phi() const;
+  /// set model_bhad_phi
+  void setModel_bhad_phi(float);
+
+  /// get model_bhad_E
+  float model_bhad_E() const;
+  /// set model_bhad_E
+  void setModel_bhad_E(float);
+
+  /// get model_bhad_jetIndex
+  unsigned int model_bhad_jetIndex() const;
+  /// set model_bhad_jetIndex
+  void setModel_bhad_jetIndex(unsigned int);
+
+  /// get model_blep_pt
+  float model_blep_pt() const;
+  /// set model_blep_pt
+  void setModel_blep_pt(float);
+
+  /// get model_blep_eta
+  float model_blep_eta() const;
+  /// set model_blep_eta
+  void setModel_blep_eta(float);
+
+  /// get model_blep_phi
+  float model_blep_phi() const;
+  /// set model_blep_phi
+  void setModel_blep_phi(float);
+
+  /// get model_blep_E
+  float model_blep_E() const;
+  /// set model_blep_E
+  void setModel_blep_E(float);
+
+  /// get model_blep_jetIndex
+  unsigned int model_blep_jetIndex() const;
+  /// set model_blep_jetIndex
+  void setModel_blep_jetIndex(unsigned int);
+
+  /// get model_lq1_pt
+  float model_lq1_pt() const;
+  /// set model_lq1_pt
+  void setModel_lq1_pt(float);
+
+  /// get model_lq1_eta
+  float model_lq1_eta() const;
+  /// set model_lq1_eta
+  void setModel_lq1_eta(float);
+
+  /// get model_lq1_phi
+  float model_lq1_phi() const;
+  /// set model_lq1_phi
+  void setModel_lq1_phi(float);
+
+  /// get model_lq1_E
+  float model_lq1_E() const;
+  /// set model_lq1_E
+  void setModel_lq1_E(float);
+
+  /// get model_lq1_jetIndex
+  unsigned int model_lq1_jetIndex() const;
+  /// set model_lq1_jetIndex
+  void setModel_lq1_jetIndex(unsigned int);
+
+  /// get model_lq2_pt
+  float model_lq2_pt() const;
+  /// set model_lq2_pt
+  void setModel_lq2_pt(float);
+
+  /// get model_lq2_eta
+  float model_lq2_eta() const;
+  /// set model_lq2_eta
+  void setModel_lq2_eta(float);
+
+  /// get model_lq2_phi
+  float model_lq2_phi() const;
+  /// set model_lq2_phi
+  void setModel_lq2_phi(float);
+
+  /// get model_lq2_E
+  float model_lq2_E() const;
+  /// set model_lq2_E
+  void setModel_lq2_E(float);
+
+  /// get model_lq2_jetIndex
+  unsigned int model_lq2_jetIndex() const;
+  /// set model_lq2_jetIndex
+  void setModel_lq2_jetIndex(unsigned int);
+
+  /// get model_Higgs_b1_pt
+  float model_Higgs_b1_pt() const;
+  /// set model_Higgs_b1_pt
+  void setModel_Higgs_b1_pt(float);
+
+  /// get model_Higgs_b1_eta
+  float model_Higgs_b1_eta() const;
+  /// set model_Higgs_b1_eta
+  void setModel_Higgs_b1_eta(float);
+
+  /// get model_Higgs_b1_phi
+  float model_Higgs_b1_phi() const;
+  /// set model_Higgs_b1_phi
+  void setModel_Higgs_b1_phi(float);
+
+  /// get model_Higgs_b1_E
+  float model_Higgs_b1_E() const;
+  /// set model_Higgs_b1_E
+  void setModel_Higgs_b1_E(float);
+
+  /// get model_Higgs_b1_jetIndex
+  unsigned int model_Higgs_b1_jetIndex() const;
+  /// set model_Higgs_b1_jetIndex
+  void setModel_Higgs_b1_jetIndex(unsigned int);
+
+  /// get model_Higgs_b2_pt
+  float model_Higgs_b2_pt() const;
+  /// set model_Higgs_b2_pt
+  void setModel_Higgs_b2_pt(float);
+
+  /// get model_Higgs_b2_eta
+  float model_Higgs_b2_eta() const;
+  /// set model_Higgs_b2_eta
+  void setModel_Higgs_b2_eta(float);
+
+  /// get model_Higgs_b2_phi
+  float model_Higgs_b2_phi() const;
+  /// set model_Higgs_b2_phi
+  void setModel_Higgs_b2_phi(float);
+
+  /// get model_Higgs_b2_E
+  float model_Higgs_b2_E() const;
+  /// set model_Higgs_b2_E
+  void setModel_Higgs_b2_E(float);
+
+  /// get model_Higgs_b2_jetIndex
+  unsigned int model_Higgs_b2_jetIndex() const;
+  /// set model_Higgs_b2_jetIndex
+  void setModel_Higgs_b2_jetIndex(unsigned int);
+
+  /// get model_lep_pt
+  float model_lep_pt() const;
+  /// set model_lep_pt
+  void setModel_lep_pt(float);
+
+  /// get model_lep_eta
+  float model_lep_eta() const;
+  /// set model_lep_eta
+  void setModel_lep_eta(float);
+
+  /// get model_lep_phi
+  float model_lep_phi() const;
+  /// set model_lep_phi
+  void setModel_lep_phi(float);
+
+  /// get model_lep_E
+  float model_lep_E() const;
+  /// set model_lep_E
+  void setModel_lep_E(float);
+
+  /// get model_lep_index
+  unsigned int model_lep_index() const;
+  /// set model_lep_index
+  void setModel_lep_index(unsigned int);
+
+  /// get model_lep_pt
+  float model_lepZ1_pt() const;
+  /// set model_lepZ1_pt
+  void setModel_lepZ1_pt(float);
+
+  /// get model_lepZ1_eta
+  float model_lepZ1_eta() const;
+  /// set model_lepZ1_eta
+  void setModel_lepZ1_eta(float);
+
+  /// get model_lepZ1_phi
+  float model_lepZ1_phi() const;
+  /// set model_lepZ1_phi
+  void setModel_lepZ1_phi(float);
+
+  /// get model_lepZ1_E
+  float model_lepZ1_E() const;
+  /// set model_lepZ1_E
+  void setModel_lepZ1_E(float);
+
+  /// get model_lepZ1_index
+  unsigned int model_lepZ1_index() const;
+  /// set model_lepZ1_index
+  void setModel_lepZ1_index(unsigned int);
+
+  /// get model_lepZ2_pt
+  float model_lepZ2_pt() const;
+  /// set model_lepZ2_pt
+  void setModel_lepZ2_pt(float);
+
+  /// get model_lepZ2_eta
+  float model_lepZ2_eta() const;
+  /// set model_lepZ2_eta
+  void setModel_lepZ2_eta(float);
+
+  /// get model_lepZ2_phi
+  float model_lepZ2_phi() const;
+  /// set model_lepZ2_phi
+  void setModel_lepZ2_phi(float);
+
+  /// get model_lepZ2_E
+  float model_lepZ2_E() const;
+  /// set model_lepZ2_E
+  void setModel_lepZ2_E(float);
+
+  /// get model_lepZ2_index
+  unsigned int model_lepZ2_index() const;
+  /// set model_lepZ2_index
+  void setModel_lepZ2_index(unsigned int);
+
+  /// get model_nu_pt
+  float model_nu_pt() const;
+  /// set model_nu_pt
+  void setModel_nu_pt(float);
+
+  /// get model_nu_eta
+  float model_nu_eta() const;
+  /// set model_nu_eta
+  void setModel_nu_eta(float);
+
+  /// get model_nu_phi
+  float model_nu_phi() const;
+  /// set model_nu_phi
+  void setModel_nu_phi(float);
+
+  /// get model_nu_E
+  float model_nu_E() const;
+  /// set model_nu_E
+  void setModel_nu_E(float);
+
+  /// get model_b_from_top1_pt
+  float model_b_from_top1_pt() const;
+  /// set model_b_from_top1_pt
+  void setModel_b_from_top1_pt(float);
+
+  /// get model_b_from_top1_eta
+  float model_b_from_top1_eta() const;
+  /// set model_b_from_top1_eta
+  void setModel_b_from_top1_eta(float);
+
+  /// get model_b_from_top1_phi
+  float model_b_from_top1_phi() const;
+  /// set model_b_from_top1_phi
+  void setModel_b_from_top1_phi(float);
+
+  /// get model_b_from_top1_E
+  float model_b_from_top1_E() const;
+  /// set model_b_from_top1_E
+  void setModel_b_from_top1_E(float);
+
+  /// get model_b_from_top1_jetIndex
+  unsigned int model_b_from_top1_jetIndex() const;
+  /// set model_b_from_top1_jetIndex
+  void setModel_b_from_top1_jetIndex(unsigned int);
+
+  /// get model_b_from_top2_pt
+  float model_b_from_top2_pt() const;
+  /// set model_b_from_top2_pt
+  void setModel_b_from_top2_pt(float);
+
+  /// get model_b_from_top2_eta
+  float model_b_from_top2_eta() const;
+  /// set model_b_from_top2_eta
+  void setModel_b_from_top2_eta(float);
+
+  /// get model_b_from_top2_phi
+  float model_b_from_top2_phi() const;
+  /// set model_b_from_top2_phi
+  void setModel_b_from_top2_phi(float);
+
+  /// get model_b_from_top2_E
+  float model_b_from_top2_E() const;
+  /// set model_b_from_top2_E
+  void setModel_b_from_top2_E(float);
+
+  /// get model_b_from_top2_jetIndex
+  unsigned int model_b_from_top2_jetIndex() const;
+  /// set model_b_from_top2_jetIndex
+  void setModel_b_from_top2_jetIndex(unsigned int);
+
+  /// get model_lj1_from_top1_pt
+  float model_lj1_from_top1_pt() const;
+  /// set model_lj1_from_top1_pt
+  void setModel_lj1_from_top1_pt(float);
+
+  /// get model_lj1_from_top1_eta
+  float model_lj1_from_top1_eta() const;
+  /// set model_lj1_from_top1_eta
+  void setModel_lj1_from_top1_eta(float);
+
+  /// get model_lj1_from_top1_phi
+  float model_lj1_from_top1_phi() const;
+  /// set model_lj1_from_top1_phi
+  void setModel_lj1_from_top1_phi(float);
+
+  /// get model_lj1_from_top1_E
+  float model_lj1_from_top1_E() const;
+  /// set model_lj1_from_top1_E
+  void setModel_lj1_from_top1_E(float);
+
+  /// get model_lj1_from_top1_jetIndex
+  unsigned int model_lj1_from_top1_jetIndex() const;
+  /// set model_lj1_from_top1_jetIndex
+  void setModel_lj1_from_top1_jetIndex(unsigned int);
+
+  /// get model_lj2_from_top1_pt
+  float model_lj2_from_top1_pt() const;
+  /// set model_lj2_from_top1_pt
+  void setModel_lj2_from_top1_pt(float);
+
+  /// get model_lj2_from_top1_eta
+  float model_lj2_from_top1_eta() const;
+  /// set model_lj2_from_top1_eta
+  void setModel_lj2_from_top1_eta(float);
+
+  /// get model_lj2_from_top1_phi
+  float model_lj2_from_top1_phi() const;
+  /// set model_lj2_from_top1_phi
+  void setModel_lj2_from_top1_phi(float);
+
+  /// get model_lj2_from_top1_E
+  float model_lj2_from_top1_E() const;
+  /// set model_lj2_from_top1_E
+  void setModel_lj2_from_top1_E(float);
+
+  /// get model_lj2_from_top1_jetIndex
+  unsigned int model_lj2_from_top1_jetIndex() const;
+  /// set model_lj2_from_top1_jetIndex
+  void setModel_lj2_from_top1_jetIndex(unsigned int);
+
+  /// get model_lj1_from_top2_pt
+  float model_lj1_from_top2_pt() const;
+  /// set model_lj1_from_top2_pt
+  void setModel_lj1_from_top2_pt(float);
+
+  /// get model_lj1_from_top2_eta
+  float model_lj1_from_top2_eta() const;
+  /// set model_lj1_from_top2_eta
+  void setModel_lj1_from_top2_eta(float);
+
+  /// get model_lj1_from_top2_phi
+  float model_lj1_from_top2_phi() const;
+  /// set model_lj1_from_top2_phi
+  void setModel_lj1_from_top2_phi(float);
+
+  /// get model_lj1_from_top2_E
+  float model_lj1_from_top2_E() const;
+  /// set model_lj1_from_top2_E
+  void setModel_lj1_from_top2_E(float);
+
+  /// get model_lj1_from_top2_jetIndex
+  unsigned int model_lj1_from_top2_jetIndex() const;
+  /// set model_lj1_from_top2_jetIndex
+  void setModel_lj1_from_top2_jetIndex(unsigned int);
+
+  /// get model_lj2_from_top2_pt
+  float model_lj2_from_top2_pt() const;
+  /// set model_lj2_from_top2_pt
+  void setModel_lj2_from_top2_pt(float);
+
+  /// get model_lj2_from_top2_eta
+  float model_lj2_from_top2_eta() const;
+  /// set model_lj2_from_top2_eta
+  void setModel_lj2_from_top2_eta(float);
+
+  /// get model_lj2_from_top2_phi
+  float model_lj2_from_top2_phi() const;
+  /// set model_lj2_from_top2_phi
+  void setModel_lj2_from_top2_phi(float);
+
+  /// get model_lj2_from_top2_E
+  float model_lj2_from_top2_E() const;
+  /// set model_lj2_from_top2_E
+  void setModel_lj2_from_top2_E(float);
+
+  /// get model_lj2_from_top2_jetIndex
+  unsigned int model_lj2_from_top2_jetIndex() const;
+  /// set model_lj2_from_top2_jetIndex
+  void setModel_lj2_from_top2_jetIndex(unsigned int);
+
+};  // class KLFitterResult
+
+}  // namespace xAOD
+
+// Define a ClassID for the type.
+CLASS_DEF(xAOD::KLFitterResult, 103465656, 1)
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h
new file mode 100644
index 000000000000..0c4b84f8043e
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h
@@ -0,0 +1,150 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_KLFITTERRESULTAUXCONTAINER_H_
+#define KLFITTERNANALYSISALGORITHMS_KLFITTERRESULTAUXCONTAINER_H_
+
+// EDM include(s).
+#include "xAODCore/AuxContainerBase.h"
+#include "xAODCore/CLASS_DEF.h"
+
+// System include(s).
+#include <vector>
+
+namespace xAOD {
+
+/// Auxiliary container for @c xAOD::KLFitterResultContainer
+class KLFitterResultAuxContainer : public AuxContainerBase {
+
+ public:
+  /// Default constructor
+  KLFitterResultAuxContainer();
+
+ private:
+  /// Name of selection run
+  std::vector<std::size_t> selectionCode;
+
+  /// Error flags
+  std::vector<short> minuitDidNotConverge;
+  std::vector<short> fitAbortedDueToNaN;
+  std::vector<short> atLeastOneFitParameterAtItsLimit;
+  std::vector<short> invalidTransferFunctionAtConvergence;
+
+  /// Global result
+  std::vector<unsigned int> bestPermutation;
+  std::vector<float> logLikelihood;
+  std::vector<float> eventProbability;
+  std::vector<std::vector<double> > parameters;
+  std::vector<std::vector<double> > parameterErrors;
+
+  /// Model
+  std::vector<float> model_bhad_pt;
+  std::vector<float> model_bhad_eta;
+  std::vector<float> model_bhad_phi;
+  std::vector<float> model_bhad_E;
+  std::vector<unsigned int> model_bhad_jetIndex;
+
+  std::vector<float> model_blep_pt;
+  std::vector<float> model_blep_eta;
+  std::vector<float> model_blep_phi;
+  std::vector<float> model_blep_E;
+  std::vector<unsigned int> model_blep_jetIndex;
+
+  std::vector<float> model_lq1_pt;
+  std::vector<float> model_lq1_eta;
+  std::vector<float> model_lq1_phi;
+  std::vector<float> model_lq1_E;
+  std::vector<unsigned int> model_lq1_jetIndex;
+
+  std::vector<float> model_lq2_pt;
+  std::vector<float> model_lq2_eta;
+  std::vector<float> model_lq2_phi;
+  std::vector<float> model_lq2_E;
+  std::vector<unsigned int> model_lq2_jetIndex;
+
+  // model particles in case of TTH topology
+  std::vector<float> model_Higgs_b1_pt;
+  std::vector<float> model_Higgs_b1_eta;
+  std::vector<float> model_Higgs_b1_phi;
+  std::vector<float> model_Higgs_b1_E;
+  std::vector<unsigned int> model_Higgs_b1_jetIndex;
+
+  std::vector<float> model_Higgs_b2_pt;
+  std::vector<float> model_Higgs_b2_eta;
+  std::vector<float> model_Higgs_b2_phi;
+  std::vector<float> model_Higgs_b2_E;
+  std::vector<unsigned int> model_Higgs_b2_jetIndex;
+
+  std::vector<float> model_lep_pt;
+  std::vector<float> model_lep_eta;
+  std::vector<float> model_lep_phi;
+  std::vector<float> model_lep_E;
+  std::vector<unsigned int> model_lep_index;
+
+  // model particles for the TTZ trilepton channel
+  std::vector<float> model_lepZ1_pt;
+  std::vector<float> model_lepZ1_eta;
+  std::vector<float> model_lepZ1_phi;
+  std::vector<float> model_lepZ1_E;
+  std::vector<unsigned int> model_lepZ1_index;
+
+  std::vector<float> model_lepZ2_pt;
+  std::vector<float> model_lepZ2_eta;
+  std::vector<float> model_lepZ2_phi;
+  std::vector<float> model_lepZ2_E;
+  std::vector<unsigned int> model_lepZ2_index;
+
+  std::vector<float> model_nu_pt;
+  std::vector<float> model_nu_eta;
+  std::vector<float> model_nu_phi;
+  std::vector<float> model_nu_E;
+
+  // model particles for ttbar allhadronic channel
+  std::vector<float> model_b_from_top1_pt;
+  std::vector<float> model_b_from_top1_eta;
+  std::vector<float> model_b_from_top1_phi;
+  std::vector<float> model_b_from_top1_E;
+  std::vector<unsigned int> model_b_from_top1_jetIndex;
+
+  std::vector<float> model_b_from_top2_pt;
+  std::vector<float> model_b_from_top2_eta;
+  std::vector<float> model_b_from_top2_phi;
+  std::vector<float> model_b_from_top2_E;
+  std::vector<unsigned int> model_b_from_top2_jetIndex;
+
+  std::vector<float> model_lj1_from_top1_pt;
+  std::vector<float> model_lj1_from_top1_eta;
+  std::vector<float> model_lj1_from_top1_phi;
+  std::vector<float> model_lj1_from_top1_E;
+  std::vector<unsigned int> model_lj1_from_top1_jetIndex;
+
+  std::vector<float> model_lj2_from_top1_pt;
+  std::vector<float> model_lj2_from_top1_eta;
+  std::vector<float> model_lj2_from_top1_phi;
+  std::vector<float> model_lj2_from_top1_E;
+  std::vector<unsigned int> model_lj2_from_top1_jetIndex;
+
+  std::vector<float> model_lj1_from_top2_pt;
+  std::vector<float> model_lj1_from_top2_eta;
+  std::vector<float> model_lj1_from_top2_phi;
+  std::vector<float> model_lj1_from_top2_E;
+  std::vector<unsigned int> model_lj1_from_top2_jetIndex;
+
+  std::vector<float> model_lj2_from_top2_pt;
+  std::vector<float> model_lj2_from_top2_eta;
+  std::vector<float> model_lj2_from_top2_phi;
+  std::vector<float> model_lj2_from_top2_E;
+  std::vector<unsigned int> model_lj2_from_top2_jetIndex;
+
+};  // class KLFitterResultAuxContainer
+
+}  // namespace xAOD
+
+// Define a ClassID for the type.
+CLASS_DEF(xAOD::KLFitterResultAuxContainer, 1292529835, 1)
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultContainer.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultContainer.h
new file mode 100644
index 000000000000..f4e741dcdc1e
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/KLFitterResultContainer.h
@@ -0,0 +1,28 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_KLFITTERRESULTCONTAINER_H_
+#define KLFITTERNANALYSISALGORITHMS_KLFITTERRESULTCONTAINER_H_
+
+// EDM include(s).
+#include "AthContainers/DataVector.h"
+#include "xAODCore/CLASS_DEF.h"
+
+// Local include(s).
+#include "KLFitterAnalysisAlgorithms/KLFitterResult.h"
+
+namespace xAOD {
+
+/// Definition of the @c KLFitterResultContainer type
+typedef DataVector<xAOD::KLFitterResult> KLFitterResultContainer;
+
+}  // namespace xAOD
+
+// Define a ClassID for the type.
+CLASS_DEF(xAOD::KLFitterResultContainer, 1116647492, 1)
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/RunKLFitterAlg.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/RunKLFitterAlg.h
new file mode 100644
index 000000000000..bcef869a3602
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/RunKLFitterAlg.h
@@ -0,0 +1,218 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#ifndef KLFITTERNANALYSISALGORITHMS_RUNKLFITTERALG_H_
+#define KLFITTERNANALYSISALGORITHMS_RUNKLFITTERALG_H_
+
+// Algorithm includes
+#include <AnaAlgorithm/AnaAlgorithm.h>
+#include <AsgTools/PropertyWrapper.h>
+#include <SelectionHelpers/SysReadSelectionHandle.h>
+#include <SystematicsHandles/SysReadDecorHandle.h>
+#include <SystematicsHandles/SysReadHandle.h>
+#include <SystematicsHandles/SysWriteHandle.h>
+
+// Framework includes
+#include <xAODEgamma/ElectronContainer.h>
+#include <xAODEventInfo/EventInfo.h>
+#include <xAODJet/JetContainer.h>
+#include <xAODMissingET/MissingETContainer.h>
+#include <xAODMuon/MuonContainer.h>
+
+#include "FTagAnalysisInterfaces/IBTaggingEfficiencyTool.h"
+#include "xAODJet/JetAuxContainer.h"
+
+// Externals
+#include "KLFitterAnalysisAlgorithms/KLFitterEnums.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h"
+#include "KLFitter/BoostedLikelihoodTopLeptonJets.h"
+#include "KLFitter/DetectorAtlas_8TeV.h"
+#include "KLFitter/Fitter.h"
+#include "KLFitter/LikelihoodTTHLeptonJets.h"
+#include "KLFitter/LikelihoodTTZTrilepton.h"
+#include "KLFitter/LikelihoodTopAllHadronic.h"
+#include "KLFitter/LikelihoodTopLeptonJets.h"
+#include "KLFitter/LikelihoodTopLeptonJets_Angular.h"
+#include "KLFitter/LikelihoodTopLeptonJets_JetAngles.h"
+#include "KLFitter/Permutations.h"
+
+namespace EventReco {
+
+class RunKLFitterAlg final : public EL::AnaAlgorithm {
+
+ public:
+  using EL::AnaAlgorithm::AnaAlgorithm;
+  virtual StatusCode initialize() final;
+  virtual StatusCode execute() final;
+
+ private:
+  StatusCode execute_syst(const CP::SystematicSet &sys);
+  StatusCode add_leptons(
+      const std::vector<const xAOD::Electron *> &selected_electrons,
+      const std::vector<const xAOD::Muon *> &selected_muons,
+      KLFitter::Particles *myParticles);
+
+  StatusCode add_jets(const std::vector<const xAOD::Jet *> &selected_jets,
+                      KLFitter::Particles *myParticles);
+
+  StatusCode setJetskLeadingN(const std::vector<const xAOD::Jet *> &jets,
+                              KLFitter::Particles *inputParticles,
+                              const size_t njets);
+
+  StatusCode retrieveEfficiencies(const xAOD::Jet *jet, float *eff,
+                                  float *ineff);
+
+  StatusCode setJetskBtagPriority(const std::vector<const xAOD::Jet *> &jets,
+                                  KLFitter::Particles *inputParticles,
+                                  const size_t maxJets);
+
+  StatusCode evaluatePermutations(const CP::SystematicSet &sys,
+                                  const std::vector<size_t> &electron_indices,
+                                  const std::vector<size_t> &muon_indices,
+                                  const std::vector<size_t> &jet_indices);
+
+  template <typename T>
+  std::vector<const T *> sortPt(const std::vector<const T *> &particles,
+                                std::vector<size_t> &indices) {
+    std::vector<std::pair<const T *, size_t>> particle_index(particles.size());
+    size_t indx{0};
+    for (const T *const p : particles) {
+      particle_index[indx] = {p, indx};
+      ++indx;
+    }
+    std::sort(
+        particle_index.begin(), particle_index.end(),
+        [](std::pair<const T *, size_t> &x, std::pair<const T *, size_t> &y) {
+          return x.first->pt() > y.first->pt();
+        });
+    std::vector<const T *> sorted_particles(particles.size());
+    indx = 0;
+    indices.resize(particles.size());
+    for (auto &elem : particle_index) {
+      sorted_particles[indx] = elem.first;
+      indices[indx] = elem.second;
+      ++indx;
+    }
+    return sorted_particles;
+  }
+
+  // systematics
+  CP::SysListHandle m_systematicsList{this};
+
+  // inputs needed for reconstruction
+  CP::SysReadHandle<xAOD::ElectronContainer> m_electronsHandle{
+      this, "electrons", "", "the electron container to use"};
+  CP::SysReadSelectionHandle m_electronSelection{
+      this, "electronSelection", "", "the selection on the input electrons"};
+
+  CP::SysReadHandle<xAOD::MuonContainer> m_muonsHandle{
+      this, "muons", "", "the muon container to use"};
+  CP::SysReadSelectionHandle m_muonSelection{
+      this, "muonSelection", "", "the selection on the input muons"};
+
+  CP::SysReadHandle<xAOD::JetContainer> m_jetsHandle{
+      this, "jets", "", "the jet container to use"};
+  CP::SysReadSelectionHandle m_jetSelection{this, "jetSelection", "",
+                                            "the selection on the input jets"};
+
+  CP::SysReadHandle<xAOD::MissingETContainer> m_metHandle{
+      this, "met", "", "the MET container to use"};
+
+  CP::SysReadHandle<xAOD::EventInfo> m_eventInfoHandle{
+      this, "eventInfo", "EventInfo",
+      "the EventInfo container to read selection deciosions from"};
+
+  // output container
+  CP::SysWriteHandle<xAOD::KLFitterResultContainer,
+                     xAOD::KLFitterResultAuxContainer>
+      m_outHandle{this, "result", "KLFitterResult_%SYS%",
+                  "the output KLFitterResultContainer"};
+
+  CP::SysReadSelectionHandle m_selection{this, "selectionDecorationName", "",
+                                         "Name of the selection on which this "
+                                         "KLFitter instance is allowed to run"};
+
+  // configurable properties
+  Gaudi::Property<std::string> m_leptonType{this, "LeptonType", "kUndefined",
+                                            "Define the lepton type"};
+  Gaudi::Property<std::string> m_LHType{this, "LHType", "kUndefined",
+                                        "Define the Likelihood type"};
+  Gaudi::Property<std::string> m_transferFunctionsPath{
+      this, "TransferFunctionsPath",
+      "dev/AnalysisTop/KLFitterTFs/mc12a/akt4_LCtopo_PP6/",
+      "Path to transfer functions"};
+  Gaudi::Property<std::string> m_jetSelectionMode{
+      this, "JetSelectionMode", "kBtagPriorityFourJets",
+      "Define the behavior for selecting jets"};
+  Gaudi::Property<std::string> m_bTaggingMethod{
+      this, "BTaggingMethod", "kNotag",
+      "Method for accounting b-tagging information"};
+  Gaudi::Property<std::string> m_bTagDecoration{
+      this, "BTaggingDecoration", "",
+      "Name of the btag decision decoration for jets"};
+  Gaudi::Property<std::string> m_METterm{this, "METterm", "Final",
+                                         "Which MET term should be used"};
+  Gaudi::Property<float> m_massTop{
+      this, "TopMass", 172.5,
+      "The mass of top quark used in KLFitter likelihood (assuming the fixed "
+      "m_top mode is used)"};
+  Gaudi::Property<bool> m_fixedTopMass{
+      this, "TopMassFixed", true,
+      "If the top quark mass is fixed in the likelihood to the value of "
+      "TopMass parameter"};
+  Gaudi::Property<bool> m_saveAllPermutations{
+      this, "SaveAllPermutations", false,
+      "Whether to store only the permutation with highest KLFitter event "
+      "probability, or all"};
+  Gaudi::Property<bool> m_failOnLessThanXJets{
+      this, "FailOnLessThanXJets", false,
+      "Fail if kLeadingX or kBtagPriorityXJets is set and the number of jets "
+      "in the event is less than X"};
+
+  KLFEnums::LeptonType m_leptonTypeEnum{};
+  KLFEnums::Likelihood m_LHTypeEnum{};
+  KLFEnums::JetSelectionMode m_jetSelectionModeEnum{};
+
+  bool m_useBtagPriority{false};
+  size_t m_njetsRequirement{0};
+
+  std::unique_ptr<KLFitter::Fitter> m_myFitter;
+  std::unique_ptr<KLFitter::DetectorAtlas_8TeV> m_myDetector;
+  KLFEnums::JetSelectionMode m_jetSelectionModeKLFitterEnum{};
+  KLFitter::LikelihoodBase::BtaggingMethod m_bTaggingMethodEnum{};
+
+  KLFitter::LikelihoodTopLeptonJets::LeptonType m_leptonTypeKLFitterEnum{};
+  KLFitter::LikelihoodTTHLeptonJets::LeptonType m_leptonTypeKLFitterEnum_TTH{};
+  KLFitter::LikelihoodTopLeptonJets_JetAngles::LeptonType
+      m_leptonTypeKLFitterEnum_JetAngles{};
+  KLFitter::LikelihoodTopLeptonJets_Angular::LeptonType
+      m_leptonTypeKLFitterEnum_Angular{};
+  KLFitter::LikelihoodTTZTrilepton::LeptonType m_leptonTypeKLFitterEnum_TTZ{};
+  KLFitter::BoostedLikelihoodTopLeptonJets::LeptonType
+      m_leptonTypeKLFitterEnum_BoostedLJets{};
+
+  std::unique_ptr<KLFitter::LikelihoodTopLeptonJets> m_myLikelihood;
+  std::unique_ptr<KLFitter::LikelihoodTTHLeptonJets> m_myLikelihood_TTH;
+  std::unique_ptr<KLFitter::LikelihoodTopLeptonJets_JetAngles>
+      m_myLikelihood_JetAngles;
+  std::unique_ptr<KLFitter::LikelihoodTopLeptonJets_Angular>
+      m_myLikelihood_Angular;
+  std::unique_ptr<KLFitter::LikelihoodTTZTrilepton> m_myLikelihood_TTZ;
+  std::unique_ptr<KLFitter::LikelihoodTopAllHadronic>
+      m_myLikelihood_AllHadronic;
+  std::unique_ptr<KLFitter::BoostedLikelihoodTopLeptonJets>
+      m_myLikelihood_BoostedLJets;
+
+  ToolHandle<IBTaggingEfficiencyTool> m_btagging_eff_tool{
+      this, "btagEffTool", "", "the b-tagging efficiency tool"};
+
+  std::unique_ptr<SG::AuxElement::ConstAccessor<char>> m_bTagDecoAcc;
+};
+}  // namespace EventReco
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection.xml b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection.xml
new file mode 100644
index 000000000000..d47e2c50cc3e
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection.xml
@@ -0,0 +1,9 @@
+<lcgdict>
+
+  <class name="EventReco::RunKLFitterAlg" />
+  <class name="EventReco::KLFitterResult" />
+  <class name="EventReco::KLFitterResultContainer" />
+  <class name="EventReco::KLFitterResultAuxContainer" />
+  <class name="EventReco::KLFitterFinalizeOutputAlg" />
+
+</lcgdict>
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection_ath.xml b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection_ath.xml
new file mode 100644
index 000000000000..3b2cc8811c81
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/KLFitterAnalysisAlgorithms/selection_ath.xml
@@ -0,0 +1,8 @@
+<lcgdict>
+
+  <class name="xAOD::KLFitterResult" />
+  <class name="DataVector<xAOD::KLFitterResult>" />
+  <typedef name="xAOD::KLFitterResultContainer" />
+  <class name="xAOD::KLFitterResultAuxContainer" />
+
+</lcgdict>
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterFinalizeOutputAlg.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterFinalizeOutputAlg.cxx
new file mode 100644
index 000000000000..41aee08c1bbc
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterFinalizeOutputAlg.cxx
@@ -0,0 +1,41 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#include "KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h"
+
+namespace EventReco {
+
+KLFitterFinalizeOutputAlg::KLFitterFinalizeOutputAlg(const std::string &name,
+                                                     ISvcLocator *pSvcLocator)
+    : EL::AnaAlgorithm(name, pSvcLocator) {}
+
+StatusCode KLFitterFinalizeOutputAlg::initialize() {
+  ANA_CHECK(m_inHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_outHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_systematicsList.initialize());
+  return StatusCode::SUCCESS;
+}
+
+StatusCode KLFitterFinalizeOutputAlg::execute() {
+  for (const auto &sys : m_systematicsList.systematicsVector()) {
+    // for events, where the KLFitterResultContainer was not created (e.g. event
+    // didn't pass any of the regions' selections) we have to record an empty
+    // container -- this ensures that every single event has at least empty
+    // container (no KLFitter permutations)
+    if (!m_inHandle.isValid(sys)) {
+      auto resultAuxContainer =
+          std::make_unique<xAOD::KLFitterResultAuxContainer>();
+      auto resultContainer = std::make_unique<xAOD::KLFitterResultContainer>();
+      resultContainer->setStore(resultAuxContainer.get());
+      ANA_CHECK(m_outHandle.record(std::move(resultContainer),
+                                   std::move(resultAuxContainer), sys));
+    }
+  }
+  return StatusCode::SUCCESS;
+}
+
+}  // namespace EventReco
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResult.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResult.cxx
new file mode 100644
index 000000000000..8af2821ed333
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResult.cxx
@@ -0,0 +1,248 @@
+/*
+   Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+ */
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+// Local include(s).
+#include "KLFitterAnalysisAlgorithms/KLFitterResult.h"
+
+// EDM includes(s):
+#include "xAODCore/AuxStoreAccessorMacros.h"
+
+namespace xAOD {
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, std::size_t, selectionCode,
+                                     setSelectionCode)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, short,
+                                     minuitDidNotConverge,
+                                     setMinuitDidNotConverge)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, short, fitAbortedDueToNaN,
+                                     setFitAbortedDueToNaN)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, short,
+                                     atLeastOneFitParameterAtItsLimit,
+                                     setAtLeastOneFitParameterAtItsLimit)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, short,
+                                     invalidTransferFunctionAtConvergence,
+                                     setInvalidTransferFunctionAtConvergence)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     bestPermutation, setBestPermutation)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, logLikelihood,
+                                     setLogLikelihood)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, eventProbability,
+                                     setEventProbability)
+AUXSTORE_OBJECT_SETTER_AND_GETTER(KLFitterResult, std::vector<double>,
+                                  parameters, setParameters)
+AUXSTORE_OBJECT_SETTER_AND_GETTER(KLFitterResult, std::vector<double>,
+                                  parameterErrors, setParameterErrors)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_bhad_pt,
+                                     setModel_bhad_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_bhad_eta,
+                                     setModel_bhad_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_bhad_phi,
+                                     setModel_bhad_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_bhad_E,
+                                     setModel_bhad_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_bhad_jetIndex,
+                                     setModel_bhad_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_blep_pt,
+                                     setModel_blep_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_blep_eta,
+                                     setModel_blep_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_blep_phi,
+                                     setModel_blep_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_blep_E,
+                                     setModel_blep_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_blep_jetIndex,
+                                     setModel_blep_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq1_pt,
+                                     setModel_lq1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq1_eta,
+                                     setModel_lq1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq1_phi,
+                                     setModel_lq1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq1_E,
+                                     setModel_lq1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lq1_jetIndex, setModel_lq1_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq2_pt,
+                                     setModel_lq2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq2_eta,
+                                     setModel_lq2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq2_phi,
+                                     setModel_lq2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lq2_E,
+                                     setModel_lq2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lq2_jetIndex, setModel_lq2_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b1_pt,
+                                     setModel_Higgs_b1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b1_eta,
+                                     setModel_Higgs_b1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b1_phi,
+                                     setModel_Higgs_b1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b1_E,
+                                     setModel_Higgs_b1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_Higgs_b1_jetIndex,
+                                     setModel_Higgs_b1_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b2_pt,
+                                     setModel_Higgs_b2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b2_eta,
+                                     setModel_Higgs_b2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b2_phi,
+                                     setModel_Higgs_b2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_Higgs_b2_E,
+                                     setModel_Higgs_b2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_Higgs_b2_jetIndex,
+                                     setModel_Higgs_b2_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lep_pt,
+                                     setModel_lep_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lep_eta,
+                                     setModel_lep_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lep_phi,
+                                     setModel_lep_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lep_E,
+                                     setModel_lep_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lep_index, setModel_lep_index)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ1_pt,
+                                     setModel_lepZ1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ1_eta,
+                                     setModel_lepZ1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ1_phi,
+                                     setModel_lepZ1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ1_E,
+                                     setModel_lepZ1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lepZ1_index, setModel_lepZ1_index)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ2_pt,
+                                     setModel_lepZ2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ2_eta,
+                                     setModel_lepZ2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ2_phi,
+                                     setModel_lepZ2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_lepZ2_E,
+                                     setModel_lepZ2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lepZ2_index, setModel_lepZ2_index)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_nu_pt,
+                                     setModel_nu_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_nu_eta,
+                                     setModel_nu_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_nu_phi,
+                                     setModel_nu_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_nu_E,
+                                     setModel_nu_E)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top1_pt,
+                                     setModel_b_from_top1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top1_eta,
+                                     setModel_b_from_top1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top1_phi,
+                                     setModel_b_from_top1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_b_from_top1_E,
+                                     setModel_b_from_top1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_b_from_top1_jetIndex,
+                                     setModel_b_from_top1_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top2_pt,
+                                     setModel_b_from_top2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top2_eta,
+                                     setModel_b_from_top2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_b_from_top2_phi,
+                                     setModel_b_from_top2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float, model_b_from_top2_E,
+                                     setModel_b_from_top2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_b_from_top2_jetIndex,
+                                     setModel_b_from_top2_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top1_pt,
+                                     setModel_lj1_from_top1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top1_eta,
+                                     setModel_lj1_from_top1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top1_phi,
+                                     setModel_lj1_from_top1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top1_E,
+                                     setModel_lj1_from_top1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lj1_from_top1_jetIndex,
+                                     setModel_lj1_from_top1_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top1_pt,
+                                     setModel_lj2_from_top1_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top1_eta,
+                                     setModel_lj2_from_top1_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top1_phi,
+                                     setModel_lj2_from_top1_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top1_E,
+                                     setModel_lj2_from_top1_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lj2_from_top1_jetIndex,
+                                     setModel_lj2_from_top1_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top2_pt,
+                                     setModel_lj1_from_top2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top2_eta,
+                                     setModel_lj1_from_top2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top2_phi,
+                                     setModel_lj1_from_top2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj1_from_top2_E,
+                                     setModel_lj1_from_top2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lj1_from_top2_jetIndex,
+                                     setModel_lj1_from_top2_jetIndex)
+
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top2_pt,
+                                     setModel_lj2_from_top2_pt)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top2_eta,
+                                     setModel_lj2_from_top2_eta)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top2_phi,
+                                     setModel_lj2_from_top2_phi)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, float,
+                                     model_lj2_from_top2_E,
+                                     setModel_lj2_from_top2_E)
+AUXSTORE_PRIMITIVE_SETTER_AND_GETTER(KLFitterResult, unsigned int,
+                                     model_lj2_from_top2_jetIndex,
+                                     setModel_lj2_from_top2_jetIndex)
+
+}  // namespace xAOD
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResultAuxContainer.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResultAuxContainer.cxx
new file mode 100644
index 000000000000..135af7042a99
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/KLFitterResultAuxContainer.cxx
@@ -0,0 +1,127 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+// Local include(s).
+#include "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h"
+
+// EDM includes(s).
+#include "xAODCore/AuxStoreAccessorMacros.h"
+
+namespace xAOD {
+
+KLFitterResultAuxContainer::KLFitterResultAuxContainer() : AuxContainerBase() {
+
+  AUX_VARIABLE(selectionCode);
+
+  AUX_VARIABLE(minuitDidNotConverge);
+  AUX_VARIABLE(fitAbortedDueToNaN);
+  AUX_VARIABLE(atLeastOneFitParameterAtItsLimit);
+  AUX_VARIABLE(invalidTransferFunctionAtConvergence);
+
+  AUX_VARIABLE(bestPermutation);
+  AUX_VARIABLE(logLikelihood);
+  AUX_VARIABLE(eventProbability);
+  AUX_VARIABLE(parameters);
+  AUX_VARIABLE(parameterErrors);
+
+  AUX_VARIABLE(model_bhad_pt);
+  AUX_VARIABLE(model_bhad_eta);
+  AUX_VARIABLE(model_bhad_phi);
+  AUX_VARIABLE(model_bhad_E);
+  AUX_VARIABLE(model_bhad_jetIndex);
+
+  AUX_VARIABLE(model_blep_pt);
+  AUX_VARIABLE(model_blep_eta);
+  AUX_VARIABLE(model_blep_phi);
+  AUX_VARIABLE(model_blep_E);
+  AUX_VARIABLE(model_blep_jetIndex);
+
+  AUX_VARIABLE(model_lq1_pt);
+  AUX_VARIABLE(model_lq1_eta);
+  AUX_VARIABLE(model_lq1_phi);
+  AUX_VARIABLE(model_lq1_E);
+  AUX_VARIABLE(model_lq1_jetIndex);
+
+  AUX_VARIABLE(model_lq2_pt);
+  AUX_VARIABLE(model_lq2_eta);
+  AUX_VARIABLE(model_lq2_phi);
+  AUX_VARIABLE(model_lq2_E);
+  AUX_VARIABLE(model_lq2_jetIndex);
+
+  AUX_VARIABLE(model_Higgs_b1_pt);
+  AUX_VARIABLE(model_Higgs_b1_eta);
+  AUX_VARIABLE(model_Higgs_b1_phi);
+  AUX_VARIABLE(model_Higgs_b1_E);
+  AUX_VARIABLE(model_Higgs_b1_jetIndex);
+
+  AUX_VARIABLE(model_Higgs_b2_pt);
+  AUX_VARIABLE(model_Higgs_b2_eta);
+  AUX_VARIABLE(model_Higgs_b2_phi);
+  AUX_VARIABLE(model_Higgs_b2_E);
+  AUX_VARIABLE(model_Higgs_b2_jetIndex);
+
+  AUX_VARIABLE(model_lep_pt);
+  AUX_VARIABLE(model_lep_eta);
+  AUX_VARIABLE(model_lep_phi);
+  AUX_VARIABLE(model_lep_E);
+  AUX_VARIABLE(model_lep_index);
+
+  AUX_VARIABLE(model_lepZ1_pt);
+  AUX_VARIABLE(model_lepZ1_eta);
+  AUX_VARIABLE(model_lepZ1_phi);
+  AUX_VARIABLE(model_lepZ1_E);
+  AUX_VARIABLE(model_lepZ1_index);
+
+  AUX_VARIABLE(model_lepZ2_pt);
+  AUX_VARIABLE(model_lepZ2_eta);
+  AUX_VARIABLE(model_lepZ2_phi);
+  AUX_VARIABLE(model_lepZ2_E);
+  AUX_VARIABLE(model_lepZ2_index);
+
+  AUX_VARIABLE(model_nu_pt);
+  AUX_VARIABLE(model_nu_eta);
+  AUX_VARIABLE(model_nu_phi);
+  AUX_VARIABLE(model_nu_E);
+
+  AUX_VARIABLE(model_b_from_top1_pt);
+  AUX_VARIABLE(model_b_from_top1_eta);
+  AUX_VARIABLE(model_b_from_top1_phi);
+  AUX_VARIABLE(model_b_from_top1_E);
+  AUX_VARIABLE(model_b_from_top1_jetIndex);
+
+  AUX_VARIABLE(model_b_from_top2_pt);
+  AUX_VARIABLE(model_b_from_top2_eta);
+  AUX_VARIABLE(model_b_from_top2_phi);
+  AUX_VARIABLE(model_b_from_top2_E);
+  AUX_VARIABLE(model_b_from_top2_jetIndex);
+
+  AUX_VARIABLE(model_lj1_from_top1_pt);
+  AUX_VARIABLE(model_lj1_from_top1_eta);
+  AUX_VARIABLE(model_lj1_from_top1_phi);
+  AUX_VARIABLE(model_lj1_from_top1_E);
+  AUX_VARIABLE(model_lj1_from_top1_jetIndex);
+
+  AUX_VARIABLE(model_lj2_from_top1_pt);
+  AUX_VARIABLE(model_lj2_from_top1_eta);
+  AUX_VARIABLE(model_lj2_from_top1_phi);
+  AUX_VARIABLE(model_lj2_from_top1_E);
+  AUX_VARIABLE(model_lj2_from_top1_jetIndex);
+
+  AUX_VARIABLE(model_lj1_from_top2_pt);
+  AUX_VARIABLE(model_lj1_from_top2_eta);
+  AUX_VARIABLE(model_lj1_from_top2_phi);
+  AUX_VARIABLE(model_lj1_from_top2_E);
+  AUX_VARIABLE(model_lj1_from_top2_jetIndex);
+
+  AUX_VARIABLE(model_lj2_from_top2_pt);
+  AUX_VARIABLE(model_lj2_from_top2_eta);
+  AUX_VARIABLE(model_lj2_from_top2_phi);
+  AUX_VARIABLE(model_lj2_from_top2_E);
+  AUX_VARIABLE(model_lj2_from_top2_jetIndex);
+}
+
+}  // namespace xAOD
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/LinkDef.h b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/LinkDef.h
new file mode 100644
index 000000000000..b6932d508dee
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/LinkDef.h
@@ -0,0 +1,21 @@
+#include "KLFitterAnalysisAlgorithms/KLFitterResult.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h"
+#include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h"
+
+#ifdef __CINT__
+
+#pragma extra_include "KLFitterAnalysisAlgorithms/KLFitterResult.h";
+#pragma extra_include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h";
+#pragma extra_include \
+    "KLFitterAnalysisAlgorithms/KLFitterResultAuxContainer.h";
+
+#pragma link off all globals;
+#pragma link off all classes;
+#pragma link off all functions;
+#pragma link C++ nestedclass;
+
+#pragma link C++ class xAOD::KLFitterResult + ;
+#pragma link C++ class xAOD::KLFitterResultContainer + ;
+#pragma link C++ class xAOD::KLFitterResultAuxContainer + ;
+
+#endif
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/RunKLFitterAlg.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/RunKLFitterAlg.cxx
new file mode 100644
index 000000000000..1e345913232a
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/RunKLFitterAlg.cxx
@@ -0,0 +1,931 @@
+/*
+    Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#include "KLFitterAnalysisAlgorithms/RunKLFitterAlg.h"
+
+#include "AthContainers/ConstDataVector.h"
+#include "PathResolver/PathResolver.h"
+
+namespace EventReco {
+
+StatusCode RunKLFitterAlg::initialize() {
+  ANA_CHECK(m_electronsHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_muonsHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_jetsHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_metHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_eventInfoHandle.initialize(m_systematicsList));
+  ANA_CHECK(m_electronSelection.initialize(m_systematicsList, m_electronsHandle,
+                                           SG::AllowEmpty));
+  ANA_CHECK(m_muonSelection.initialize(m_systematicsList, m_muonsHandle,
+                                       SG::AllowEmpty));
+  ANA_CHECK(m_jetSelection.initialize(m_systematicsList, m_jetsHandle,
+                                      SG::AllowEmpty));
+
+  ANA_CHECK(m_selection.initialize(m_systematicsList, m_eventInfoHandle));
+
+  ANA_CHECK(m_outHandle.initialize(m_systematicsList));
+
+  ANA_CHECK(m_systematicsList.initialize());
+
+  // parse likelihood type
+  try {
+    m_LHTypeEnum = KLFEnums::strToLikelihood.at(m_LHType.value());
+  } catch (std::out_of_range &) {
+    ANA_MSG_ERROR("Unrecognized KLFitter likelihood: "
+                  << m_LHType.value() << ". Available options: "
+                  << KLFEnums::printEnumOptions(KLFEnums::strToLikelihood));
+    return StatusCode::FAILURE;
+  }
+
+  if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_JetAngles) {
+    ANA_MSG_ERROR("The ttbar_JetAngles likelihood is currently not supported!");
+    return StatusCode::FAILURE;
+  }
+
+  // parse lepton type
+  try {
+    m_leptonTypeEnum = KLFEnums::strToLeptonType.at(m_leptonType.value());
+    if (m_leptonTypeEnum != KLFEnums::LeptonType::kNoLepton &&
+        m_LHTypeEnum == KLFEnums::Likelihood::ttbar_AllHad) {
+      ANA_MSG_ERROR(
+          "If using ttbar_AllHad likelihood, please use leptonType = "
+          "kNoLepton.");
+      return StatusCode::FAILURE;
+    }
+  } catch (std::out_of_range &) {
+    ANA_MSG_ERROR("Unrecognized KLFitter leptonType: "
+                  << m_leptonType.value() << ". Available options: "
+                  << KLFEnums::printEnumOptions(KLFEnums::strToLeptonType));
+    return StatusCode::FAILURE;
+  }
+
+  // parse jet selection
+  try {
+    m_jetSelectionModeEnum =
+        KLFEnums::strToJetSelection.at(m_jetSelectionMode.value());
+  } catch (std::out_of_range &) {
+    ANA_MSG_ERROR("Unrecognized KLFitter JetSelectionMode: "
+                  << m_jetSelectionMode.value() << ". Available options: "
+                  << KLFEnums::printEnumOptions(KLFEnums::strToJetSelection));
+    return StatusCode::FAILURE;
+  }
+
+  if (m_jetSelectionModeEnum > KLFEnums::JetSelectionMode::kLeadingEight)
+    m_useBtagPriority = true;
+  try {
+    m_njetsRequirement = KLFEnums::jetSelToNumber.at(m_jetSelectionModeEnum);
+  } catch (std::out_of_range &) {
+    ANA_MSG_ERROR(
+        "Could not parse the number of required jets from KLFitter jet "
+        "selection mode: "
+        << m_jetSelectionMode.value());
+    return StatusCode::FAILURE;
+  }
+
+  // parse b-tagging method
+  try {
+    m_bTaggingMethodEnum =
+        KLFEnums::strToBtagMethod.at(m_bTaggingMethod.value());
+  } catch (std::out_of_range &) {
+    ANA_MSG_ERROR("Unrecognized KLFitter BTaggingMethod: "
+                  << m_bTaggingMethod.value() << ". Available options: "
+                  << KLFEnums::printEnumOptions(KLFEnums::strToBtagMethod));
+    return StatusCode::FAILURE;
+  }
+
+  // setup the KLFitter::Fitter instance
+  m_myFitter = std::make_unique<KLFitter::Fitter>();
+  const std::string transferFunctionAbsPath =
+      PathResolverFindCalibDirectory(m_transferFunctionsPath.value());
+  m_myDetector =
+      std::make_unique<KLFitter::DetectorAtlas_8TeV>(transferFunctionAbsPath);
+  if (!m_myFitter->SetDetector(m_myDetector.get())) {
+    ANA_MSG_ERROR(
+        "Failed to set KLFitter::Detector for KLFitter::Fitter instance.");
+    return StatusCode::FAILURE;
+  }
+
+  // create the likelihoods
+  m_myLikelihood = std::make_unique<KLFitter::LikelihoodTopLeptonJets>();
+  m_myLikelihood_TTH = std::make_unique<KLFitter::LikelihoodTTHLeptonJets>();
+  m_myLikelihood_JetAngles =
+      std::make_unique<KLFitter::LikelihoodTopLeptonJets_JetAngles>();
+  m_myLikelihood_Angular =
+      std::make_unique<KLFitter::LikelihoodTopLeptonJets_Angular>();
+  m_myLikelihood_TTZ = std::make_unique<KLFitter::LikelihoodTTZTrilepton>();
+  m_myLikelihood_AllHadronic =
+      std::make_unique<KLFitter::LikelihoodTopAllHadronic>();
+  m_myLikelihood_BoostedLJets =
+      std::make_unique<KLFitter::BoostedLikelihoodTopLeptonJets>();
+
+  // SetleptonType
+  if (m_LHTypeEnum != KLFEnums::Likelihood::ttbar_AllHad) {
+    if (m_leptonTypeEnum == KLFEnums::LeptonType::kElectron) {
+      m_leptonTypeKLFitterEnum =
+          KLFitter::LikelihoodTopLeptonJets::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_TTH =
+          KLFitter::LikelihoodTTHLeptonJets::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_JetAngles =
+          KLFitter::LikelihoodTopLeptonJets_JetAngles::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_Angular =
+          KLFitter::LikelihoodTopLeptonJets_Angular::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_TTZ =
+          KLFitter::LikelihoodTTZTrilepton::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_BoostedLJets =
+          KLFitter::BoostedLikelihoodTopLeptonJets::LeptonType::kElectron;
+    } else if (m_leptonTypeEnum == KLFEnums::LeptonType::kMuon) {
+      m_leptonTypeKLFitterEnum =
+          KLFitter::LikelihoodTopLeptonJets::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_TTH =
+          KLFitter::LikelihoodTTHLeptonJets::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_JetAngles =
+          KLFitter::LikelihoodTopLeptonJets_JetAngles::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_Angular =
+          KLFitter::LikelihoodTopLeptonJets_Angular::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_TTZ =
+          KLFitter::LikelihoodTTZTrilepton::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_BoostedLJets =
+          KLFitter::BoostedLikelihoodTopLeptonJets::LeptonType::kMuon;
+    } else if (m_leptonTypeEnum == KLFEnums::LeptonType::kTriElectron) {
+      if (m_LHType.value() != KLFEnums::Likelihood::ttZTrilepton) {
+        ANA_MSG_ERROR(
+            " LeptonType kTriElectron is only defined for the ttZTrilepton "
+            "likelihood");
+        return StatusCode::FAILURE;
+      }
+      m_leptonTypeKLFitterEnum =
+          KLFitter::LikelihoodTopLeptonJets::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_TTH =
+          KLFitter::LikelihoodTTHLeptonJets::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_JetAngles =
+          KLFitter::LikelihoodTopLeptonJets_JetAngles::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_TTZ =
+          KLFitter::LikelihoodTTZTrilepton::LeptonType::kElectron;
+      m_leptonTypeKLFitterEnum_BoostedLJets =
+          KLFitter::BoostedLikelihoodTopLeptonJets::LeptonType::kElectron;
+    } else if (m_leptonTypeEnum == KLFEnums::LeptonType::kTriMuon) {
+      if (m_LHType.value() != KLFEnums::Likelihood::ttZTrilepton) {
+        ANA_MSG_ERROR(
+            " LeptonType kTriMuon is only defined for the ttZTrilepton "
+            "likelihood");
+        return StatusCode::FAILURE;
+      }
+      m_leptonTypeKLFitterEnum =
+          KLFitter::LikelihoodTopLeptonJets::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_TTH =
+          KLFitter::LikelihoodTTHLeptonJets::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_JetAngles =
+          KLFitter::LikelihoodTopLeptonJets_JetAngles::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_TTZ =
+          KLFitter::LikelihoodTTZTrilepton::LeptonType::kMuon;
+      m_leptonTypeKLFitterEnum_BoostedLJets =
+          KLFitter::BoostedLikelihoodTopLeptonJets::LeptonType::kMuon;
+    } else {
+      ANA_MSG_ERROR(" Please supply a valid LeptonType : kElectron or kMuon");
+      return StatusCode::FAILURE;
+    }
+
+    m_myLikelihood->SetLeptonType(m_leptonTypeKLFitterEnum);
+    m_myLikelihood_TTH->SetLeptonType(m_leptonTypeKLFitterEnum_TTH);
+    m_myLikelihood_JetAngles->SetLeptonType(m_leptonTypeKLFitterEnum_JetAngles);
+    m_myLikelihood_Angular->SetLeptonType(m_leptonTypeKLFitterEnum_Angular);
+    m_myLikelihood_TTZ->SetLeptonType(m_leptonTypeKLFitterEnum_TTZ);
+    m_myLikelihood_BoostedLJets->SetLeptonType(
+        m_leptonTypeKLFitterEnum_BoostedLJets);
+  }
+
+  m_myLikelihood->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_TTH->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_JetAngles->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_Angular->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_TTZ->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_AllHadronic->SetBTagging(m_bTaggingMethodEnum);
+  m_myLikelihood_BoostedLJets->SetBTagging(m_bTaggingMethodEnum);
+  // set top mass
+  m_myLikelihood->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_TTH->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_JetAngles->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_Angular->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_TTZ->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_AllHadronic->PhysicsConstants()->SetMassTop(m_massTop);
+  m_myLikelihood_BoostedLJets->PhysicsConstants()->SetMassTop(m_massTop);
+
+  // whether the top mass is fixed to the constant in likelihood or not
+  m_myLikelihood->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_TTH->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_JetAngles->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_Angular->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_TTZ->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_AllHadronic->SetFlagTopMassFixed(m_fixedTopMass);
+  m_myLikelihood_BoostedLJets->SetFlagTopMassFixed(m_fixedTopMass);
+
+  // configure which likelihood to use in the fitter
+  int klfitter_returncode = 0;
+  if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar) {
+    klfitter_returncode = m_myFitter->SetLikelihood(m_myLikelihood.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttH) {
+    klfitter_returncode = m_myFitter->SetLikelihood(m_myLikelihood_TTH.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_JetAngles) {
+    klfitter_returncode =
+        m_myFitter->SetLikelihood(m_myLikelihood_JetAngles.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_Angular) {
+    klfitter_returncode =
+        m_myFitter->SetLikelihood(m_myLikelihood_Angular.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttZTrilepton &&
+             (m_leptonTypeEnum == KLFEnums::LeptonType::kTriElectron ||
+              m_leptonTypeEnum == KLFEnums::LeptonType::kTriMuon)) {
+    // For ttZ->trilepton, we can have difficult combinations of leptons in the
+    // final state (3x same flavour, or mixed case). The latter is trivial, for
+    // which we can default back to the ljets likelihood. So we distinguish
+    // here:
+    //  - kTriMuon, kTriElectron: dedicated TTZ->trilepton likelihood,
+    //  - kMuon, kElectron: standard ttbar->l+jets likelihood.
+    klfitter_returncode = m_myFitter->SetLikelihood(m_myLikelihood_TTZ.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttZTrilepton) {
+    klfitter_returncode = m_myFitter->SetLikelihood(m_myLikelihood.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_AllHad) {
+    klfitter_returncode =
+        m_myFitter->SetLikelihood(m_myLikelihood_AllHadronic.get());
+  } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_BoostedLJets) {
+    klfitter_returncode =
+        m_myFitter->SetLikelihood(m_myLikelihood_BoostedLJets.get());
+  } else {
+    ANA_MSG_ERROR("Unrecognized KLFitter likelihood: " << m_LHType.value());
+    return StatusCode::FAILURE;
+  }
+
+  if (!klfitter_returncode) {
+    ANA_MSG_ERROR("Failed to SetLikelihood for likelihood "
+                  << m_LHType.value());
+    return StatusCode::FAILURE;
+  }
+
+  if (m_bTagDecoration.value().find("Continuous") != std::string::npos) {
+    ANA_MSG_ERROR("KLFitter cannot run using Continuous b-tag working point!");
+    return StatusCode::FAILURE;
+  }
+  m_bTagDecoAcc = std::make_unique<SG::AuxElement::ConstAccessor<char>>(
+      m_bTagDecoration.value());
+
+  if (m_bTaggingMethodEnum ==
+      KLFitter::LikelihoodBase::BtaggingMethod::kWorkingPoint) {
+    ANA_CHECK(m_btagging_eff_tool.retrieve());
+  }
+
+  ANA_MSG_INFO("++++++++++++++++++++++++++++++");
+  ANA_MSG_INFO("Configured KLFitter with name " << name());
+  ANA_MSG_INFO("  Using " << m_btagging_eff_tool);
+  ANA_MSG_INFO("  Using transfer functions with full path "
+               << transferFunctionAbsPath);
+  ANA_MSG_INFO("  Using Lepton \t\t" << m_leptonType.value());
+  ANA_MSG_INFO("  Using JetSelectionMode \t" << m_jetSelectionMode.value());
+  ANA_MSG_INFO("  Using BTaggingMethod \t" << m_bTaggingMethod.value());
+  ANA_MSG_INFO("  Using TopMassFixed \t" << m_fixedTopMass);
+
+  if (m_saveAllPermutations)
+    ANA_MSG_INFO("  Saving All permutations");
+  else
+    ANA_MSG_INFO(
+        "  Saving only the permutation with the highest event probability");
+  ANA_MSG_INFO("++++++++++++++++++++++++++++++");
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::execute() {
+  for (const auto &sys : m_systematicsList.systematicsVector()) {
+    ANA_CHECK(execute_syst(sys));
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::execute_syst(const CP::SystematicSet &sys) {
+  // run KLFitter
+  // create an instance of the particles class filled with the particles to be
+  // fitted; here, you need to make sure that
+  // - the particles are in the range allowed by the transfer functions (eta and
+  // pt)
+  // - the energies and momenta are in GeV
+  // - be aware that *all* particles you're adding are considered in the fit
+  //   (many particles lead to many permutations to be considered and hence a
+  //   long running time and not necessarily good fitting results due to the
+  //   many available permutations)
+  // the arguments taken py AddParticle() are
+  // - TLorentzVector of the physics 4-momentum
+  // - detector eta for the evaluation of the transfer functions (for muons:
+  // just use the physics eta)
+  // - type of particle
+  // - an optional name of the particle (pass empty string in case you don't
+  // want to give your particle a name)
+  // - index of the particle in your original collection (for convenience)
+  // - for jets:
+  //   * bool isBtagged : mandatory only if you want to use b-tagging in the fit
+
+  // first figure out if this event even passes the selection in which we are to
+  // run this KLFitter instance
+  const xAOD::EventInfo *evtInfo = nullptr;
+  ANA_CHECK(m_eventInfoHandle.retrieve(evtInfo, sys));
+
+  if (!m_selection.getBool(*evtInfo, sys))
+    return StatusCode::SUCCESS;
+
+  const xAOD::ElectronContainer *electrons = nullptr;
+  ANA_CHECK(m_electronsHandle.retrieve(electrons, sys));
+  const xAOD::MuonContainer *muons = nullptr;
+  ANA_CHECK(m_muonsHandle.retrieve(muons, sys));
+  const xAOD::JetContainer *jets = nullptr;
+  ANA_CHECK(m_jetsHandle.retrieve(jets, sys));
+  const xAOD::MissingETContainer *met = nullptr;
+  ANA_CHECK(m_metHandle.retrieve(met, sys));
+
+  // perform selection of objects
+  KLFitter::Particles *myParticles = new KLFitter::Particles{};
+
+  std::vector<const xAOD::Electron *> selected_electrons;
+  std::vector<const xAOD::Muon *> selected_muons;
+  std::vector<const xAOD::Jet *> selected_jets;
+
+  // select particles
+  for (const xAOD::Electron *el : *electrons) {
+    if (m_electronSelection.getBool(*el, sys))
+      selected_electrons.push_back(el);
+  }
+
+  for (const xAOD::Muon *mu : *muons) {
+    if (m_muonSelection.getBool(*mu, sys))
+      selected_muons.push_back(mu);
+  }
+
+  for (const xAOD::Jet *jet : *jets) {
+    if (m_jetSelection.getBool(*jet, sys))
+      selected_jets.push_back(jet);
+  }
+
+  std::vector<size_t> electron_indices;
+  const std::vector<const xAOD::Electron *> selected_sorted_electrons =
+      sortPt(selected_electrons, electron_indices);
+  std::vector<size_t> muon_indices;
+  const std::vector<const xAOD::Muon *> selected_sorted_muons =
+      sortPt(selected_muons, muon_indices);
+  std::vector<size_t> jet_indices;
+  const std::vector<const xAOD::Jet *> selected_sorted_jets =
+      sortPt(selected_jets, jet_indices);
+
+  // add leptons to KLFitter particles (not for ttbar all hadronic)
+  if (m_LHTypeEnum != KLFEnums::Likelihood::ttbar_AllHad)
+    ANA_CHECK(add_leptons(selected_sorted_electrons, selected_sorted_muons,
+                          myParticles));
+
+  // add jets to KLFitter particles
+  ANA_CHECK(add_jets(selected_sorted_jets, myParticles));
+
+  // add the particles to the fitter itself
+  if (!m_myFitter->SetParticles(myParticles)) {
+    ANA_MSG_ERROR("Error adding particles to KLFitter");
+    return StatusCode::FAILURE;
+  }
+
+  // add MET
+  auto *met_finalTrk = (*met)[m_METterm.value()];
+  if (!met_finalTrk) {
+    ANA_MSG_ERROR("RunKLFitterAlg: Error retrieving MET term "
+                  << m_METterm.value());
+    return StatusCode::FAILURE;
+  }
+  if (!m_myFitter->SetET_miss_XY_SumET(met_finalTrk->mpx() / 1.e3,
+                                       met_finalTrk->mpy() / 1.e3,
+                                       met_finalTrk->sumet())) {
+    ANA_MSG_ERROR("Error adding MET term to KLFitter");
+    return StatusCode::FAILURE;
+  }
+
+  ANA_CHECK(
+      evaluatePermutations(sys, electron_indices, muon_indices, jet_indices));
+
+  delete myParticles;
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::add_leptons(
+    const std::vector<const xAOD::Electron *> &selected_electrons,
+    const std::vector<const xAOD::Muon *> &selected_muons,
+    KLFitter::Particles *myParticles) {
+  // likelihoods with single lepton (either l+jets or ttZ 3lepton mixed lepton
+  // flavour)
+  if (m_leptonTypeEnum == KLFEnums::LeptonType::kElectron) {
+    // for the lep+jets channel, we assume that your leading-pT lepton is the
+    // only selected lepton
+    TLorentzVector el;
+    if (selected_electrons.size() == 0) {
+      ANA_MSG_ERROR(
+          "For single-lepton kElectron KLFitter likelihoods, at least one "
+          "electron is required");
+      return StatusCode::FAILURE;
+    }
+    const xAOD::Electron *xaod_el = selected_electrons.at(0);
+    el.SetPtEtaPhiE(xaod_el->pt() / 1.e3, xaod_el->eta(), xaod_el->phi(),
+                    xaod_el->e() / 1.e3);
+    myParticles->AddParticle(&el, xaod_el->caloCluster()->etaBE(2),
+                             KLFitter::Particles::kElectron);
+  } else if (m_leptonTypeEnum == KLFEnums::LeptonType::kMuon) {
+    TLorentzVector mu;
+    if (selected_muons.size() == 0) {
+      ANA_MSG_ERROR(
+          "For single-lepton kMuon KLFitter likelihoods, at least one muon is "
+          "required");
+      return StatusCode::FAILURE;
+    }
+    const xAOD::Muon *xaod_mu = selected_muons.at(0);
+    mu.SetPtEtaPhiE(xaod_mu->pt() / 1.e3, xaod_mu->eta(), xaod_mu->phi(),
+                    xaod_mu->e() / 1.e3);
+    myParticles->AddParticle(&mu, mu.Eta(), KLFitter::Particles::kMuon);
+  } else if (m_leptonTypeEnum ==
+             KLFEnums::LeptonType::kTriElectron) {  // ttZ trilep
+    if (selected_electrons.size() < 3) {
+      ANA_MSG_ERROR(
+          "For tri-lepton kTriElectron KLFitter likelihoods, at least 3 "
+          "electrons are required");
+      return StatusCode::FAILURE;
+    }
+    TLorentzVector el;
+    for (size_t i = 0; i < 3; ++i) {
+      const xAOD::Electron *electron = selected_electrons.at(i);
+      el.SetPtEtaPhiE(electron->pt() / 1.e3, electron->eta(), electron->phi(),
+                      electron->e() / 1.e3);
+      myParticles->AddParticle(&el, electron->caloCluster()->etaBE(2),
+                               KLFitter::Particles::kElectron, "", i);
+    }
+  } else if (m_leptonTypeEnum ==
+             KLFEnums::LeptonType::kTriMuon) {  // ttZ trilep
+    if (selected_muons.size() < 3) {
+      ANA_MSG_ERROR(
+          "For tr-lepton kTriMuons KLFitter likelihoods, at least 3 muons are "
+          "required");
+      return StatusCode::FAILURE;
+    }
+    TLorentzVector mu;
+    for (size_t i = 0; i < 3; ++i) {
+      const xAOD::Muon *muon = selected_muons.at(i);
+      mu.SetPtEtaPhiE(muon->pt() / 1.e3, muon->eta(), muon->phi(),
+                      muon->e() / 1.e3);
+      myParticles->AddParticle(&mu, mu.Eta(), KLFitter::Particles::kMuon, "",
+                               i);
+    }
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::add_jets(const std::vector<const xAOD::Jet *> &jets,
+                                    KLFitter::Particles *myParticles) {
+  if (m_useBtagPriority) {
+    ANA_CHECK(setJetskBtagPriority(jets, myParticles, m_njetsRequirement));
+  } else {
+    ANA_CHECK(setJetskLeadingN(jets, myParticles, m_njetsRequirement));
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::setJetskLeadingN(
+    const std::vector<const xAOD::Jet *> &jets,
+    KLFitter::Particles *inputParticles, size_t njets) {
+
+  // If container has less jets than required, raise error
+  if (m_failOnLessThanXJets) {
+    if (jets.size() < njets) {
+      ANA_MSG_ERROR("KLFitterTool::setJetskLeadingX: You required "
+                    << njets << " jets. Event has " << jets.size() << " jets!");
+      return StatusCode::FAILURE;
+    }
+  }
+
+  size_t index(0);
+
+  for (const xAOD::Jet *jet : jets) {
+    if (index > njets - 1)
+      break;
+
+    TLorentzVector jet_p4;
+    jet_p4.SetPtEtaPhiE(jet->pt() / 1.e3, jet->eta(), jet->phi(),
+                        jet->e() / 1.e3);
+
+    float eff(0), ineff(0);
+
+    if (!m_bTagDecoAcc->isAvailable(*jet)) {
+      ANA_MSG_ERROR("RunKLFitterAlg::setJetskLeadingX: jet does not have "
+                    << m_bTagDecoration.value() << " aux variable!");
+      return StatusCode::FAILURE;
+    }
+
+    const bool isTagged = (*m_bTagDecoAcc)(*jet);
+
+    if (m_bTaggingMethodEnum ==
+        KLFitter::LikelihoodBase::BtaggingMethod::kWorkingPoint) {
+      ANA_CHECK(retrieveEfficiencies(jet, &eff, &ineff))
+
+      inputParticles->AddParticle(
+          &jet_p4, jet_p4.Eta(), KLFitter::Particles::kParton, "", index,
+          isTagged, eff, 1. / ineff, KLFitter::Particles::kNone);
+    } else {
+      inputParticles->AddParticle(&jet_p4, jet_p4.Eta(),
+                                  KLFitter::Particles::kParton, "", index,
+                                  isTagged);
+    }
+    ++index;
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::retrieveEfficiencies(const xAOD::Jet *jet,
+                                                float *eff, float *ineff) {
+  // need to make a copy of the jet, so that we can manipulate its flavour to
+  // get the various efficiencies
+  xAOD::JetContainer jets;
+  xAOD::JetAuxContainer jetsAux;
+  jets.setStore(&jetsAux);
+  xAOD::Jet *jet_copy = new xAOD::Jet();
+  jets.push_back(jet_copy);
+  *jet_copy = *jet;
+  jet_copy->setJetP4(jet->jetP4());
+  // treat jet as b-tagged
+  jet_copy->setAttribute("HadronConeExclTruthLabelID", 5);
+  ANA_CHECK(m_btagging_eff_tool->getMCEfficiency(*jet_copy, *eff));
+  // treat jet as light
+  jet_copy->setAttribute("HadronConeExclTruthLabelID", 0);
+  ANA_CHECK(m_btagging_eff_tool->getMCEfficiency(*jet_copy, *ineff));
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::setJetskBtagPriority(
+    const std::vector<const xAOD::Jet *> &jets,
+    KLFitter::Particles *inputParticles, const size_t maxJets) {
+  // kBtagPriority mode first adds the b jets, then the light jets
+  // If your 6th or 7th jet is a b jet, then you probably want this option
+
+  // If container has less jets than required, raise error
+  if (m_failOnLessThanXJets) {
+    if (jets.size() < maxJets) {
+      ANA_MSG_ERROR("KLFitterTool::setJetskBtagPriority: You required "
+                    << maxJets << " jets. Event has " << jets.size()
+                    << " jets!");
+      return StatusCode::FAILURE;
+    }
+  }
+
+  unsigned int totalJets(0);
+
+  // First find the b-jets
+  unsigned int index(0);
+  for (const xAOD::Jet *jet : jets) {
+    if (totalJets >= maxJets)
+      break;
+
+    if (!m_bTagDecoAcc->isAvailable(*jet)) {
+      ANA_MSG_ERROR("RunKLFitterAlg::setJetskLeadingX: jet does not have "
+                    << m_bTagDecoration.value() << " aux variable!");
+      return StatusCode::FAILURE;
+    }
+
+    if ((*m_bTagDecoAcc)(*jet)) {
+      TLorentzVector jet_p4;
+      jet_p4.SetPtEtaPhiE(jet->pt() / 1.e3, jet->eta(), jet->phi(),
+                          jet->e() / 1.e3);
+
+      if (m_bTaggingMethodEnum ==
+          KLFitter::LikelihoodBase::BtaggingMethod::kWorkingPoint) {
+        float eff(0), ineff(0);
+        ANA_CHECK(retrieveEfficiencies(jet, &eff, &ineff));
+
+        inputParticles->AddParticle(
+            &jet_p4, jet_p4.Eta(), KLFitter::Particles::kParton, "", index,
+            true, eff, 1. / ineff, KLFitter::Particles::kNone);
+      } else {
+        inputParticles->AddParticle(&jet_p4, jet_p4.Eta(),
+                                    KLFitter::Particles::kParton, "", index,
+                                    true);
+      }
+      ++totalJets;
+    }  // is b-tagged
+
+    ++index;
+  }  // for (jet)
+
+  // Second, find the light jets
+  index = 0;
+  for (const xAOD::Jet *jet : jets) {
+    if (totalJets >= maxJets)
+      break;
+    if (!(*m_bTagDecoAcc)(*jet)) {
+      TLorentzVector jet_p4;
+      jet_p4.SetPtEtaPhiE(jet->pt() / 1.e3, jet->eta(), jet->phi(),
+                          jet->e() / 1.e3);
+
+      if (m_bTaggingMethodEnum ==
+          KLFitter::LikelihoodBase::BtaggingMethod::kWorkingPoint) {
+        float eff(0), ineff(0);
+        ANA_CHECK(retrieveEfficiencies(jet, &eff, &ineff));
+
+        inputParticles->AddParticle(
+            &jet_p4, jet_p4.Eta(), KLFitter::Particles::kParton, "", index,
+            false, eff, 1. / ineff, KLFitter::Particles::kNone);
+      } else {
+        inputParticles->AddParticle(&jet_p4, jet_p4.Eta(),
+                                    KLFitter::Particles::kParton, "", index,
+                                    false);
+      }
+      ++totalJets;
+    }  // not-btagged jet
+
+    ++index;
+  }  // for (jet)
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RunKLFitterAlg::evaluatePermutations(
+    const CP::SystematicSet &sys, const std::vector<size_t> &electron_indices,
+    const std::vector<size_t> &muon_indices,
+    const std::vector<size_t> &jet_indices) {
+  // create or retrieve (if existent) the xAOD::KLFitterResultContainer
+  auto resultAuxContainer =
+      std::make_unique<xAOD::KLFitterResultAuxContainer>();
+  auto resultContainer = std::make_unique<xAOD::KLFitterResultContainer>();
+  resultContainer->setStore(resultAuxContainer.get());
+
+  // loop over all permutations
+  const int nperm = m_myFitter->Permutations()->NPermutations();
+  for (int iperm = 0; iperm < nperm; ++iperm) {
+    // Perform the fit
+    m_myFitter->Fit(iperm);
+    // create a result
+    xAOD::KLFitterResult *result = new xAOD::KLFitterResult{};
+    resultContainer->push_back(result);
+
+    // Set name hash. This is because it seems std::string is not supported by
+    // AuxContainers...
+    std::hash<std::string> hash_string;
+    result->setSelectionCode(hash_string(sys.name()));
+
+    unsigned int ConvergenceStatusBitWord = m_myFitter->ConvergenceStatus();
+    bool MinuitDidNotConverge =
+        (ConvergenceStatusBitWord & m_myFitter->MinuitDidNotConvergeMask) != 0;
+    bool FitAbortedDueToNaN =
+        (ConvergenceStatusBitWord & m_myFitter->FitAbortedDueToNaNMask) != 0;
+    bool AtLeastOneFitParameterAtItsLimit =
+        (ConvergenceStatusBitWord &
+         m_myFitter->AtLeastOneFitParameterAtItsLimitMask) != 0;
+    bool InvalidTransferFunctionAtConvergence =
+        (ConvergenceStatusBitWord &
+         m_myFitter->InvalidTransferFunctionAtConvergenceMask) != 0;
+
+    result->setMinuitDidNotConverge(((MinuitDidNotConverge) ? 1 : 0));
+    result->setFitAbortedDueToNaN(((FitAbortedDueToNaN) ? 1 : 0));
+    result->setAtLeastOneFitParameterAtItsLimit(
+        ((AtLeastOneFitParameterAtItsLimit) ? 1 : 0));
+    result->setInvalidTransferFunctionAtConvergence(
+        ((InvalidTransferFunctionAtConvergence) ? 1 : 0));
+
+    result->setLogLikelihood(m_myFitter->Likelihood()->LogLikelihood(
+        m_myFitter->Likelihood()->GetBestFitParameters()));
+    result->setEventProbability(
+        std::exp(m_myFitter->Likelihood()->LogEventProbability()));
+    result->setParameters(m_myFitter->Likelihood()->GetBestFitParameters());
+    result->setParameterErrors(
+        m_myFitter->Likelihood()->GetBestFitParameterErrors());
+
+    KLFitter::Particles *myModelParticles =
+        m_myFitter->Likelihood()->ParticlesModel();
+    KLFitter::Particles **myPermutedParticles =
+        m_myFitter->Likelihood()->PParticlesPermuted();
+
+    if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar ||
+        m_LHTypeEnum == KLFEnums::Likelihood::ttH ||
+        m_LHTypeEnum == KLFEnums::Likelihood::ttbar_JetAngles ||
+        m_LHTypeEnum == KLFEnums::Likelihood::ttbar_Angular ||
+        m_LHTypeEnum == KLFEnums::Likelihood::ttZTrilepton ||
+        m_LHTypeEnum == KLFEnums::Likelihood::ttbar_BoostedLJets) {
+      result->setModel_bhad_pt(myModelParticles->Parton(0)->Pt());
+      result->setModel_bhad_eta(myModelParticles->Parton(0)->Eta());
+      result->setModel_bhad_phi(myModelParticles->Parton(0)->Phi());
+      result->setModel_bhad_E(myModelParticles->Parton(0)->E());
+      result->setModel_bhad_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(0)));
+
+      result->setModel_blep_pt(myModelParticles->Parton(1)->Pt());
+      result->setModel_blep_eta(myModelParticles->Parton(1)->Eta());
+      result->setModel_blep_phi(myModelParticles->Parton(1)->Phi());
+      result->setModel_blep_E(myModelParticles->Parton(1)->E());
+      result->setModel_blep_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(1)));
+
+      result->setModel_lq1_pt(myModelParticles->Parton(2)->Pt());
+      result->setModel_lq1_eta(myModelParticles->Parton(2)->Eta());
+      result->setModel_lq1_phi(myModelParticles->Parton(2)->Phi());
+      result->setModel_lq1_E(myModelParticles->Parton(2)->E());
+      result->setModel_lq1_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(2)));
+
+      // boosted likelihood has only one light jet
+      if (m_LHTypeEnum != KLFEnums::Likelihood::ttbar_BoostedLJets) {
+        result->setModel_lq2_pt(myModelParticles->Parton(3)->Pt());
+        result->setModel_lq2_eta(myModelParticles->Parton(3)->Eta());
+        result->setModel_lq2_phi(myModelParticles->Parton(3)->Phi());
+        result->setModel_lq2_E(myModelParticles->Parton(3)->E());
+        result->setModel_lq2_jetIndex(
+            jet_indices.at((*myPermutedParticles)->JetIndex(3)));
+
+        if (m_LHTypeEnum == KLFEnums::Likelihood::ttH) {
+          result->setModel_Higgs_b1_pt(myModelParticles->Parton(4)->Pt());
+          result->setModel_Higgs_b1_eta(myModelParticles->Parton(4)->Eta());
+          result->setModel_Higgs_b1_phi(myModelParticles->Parton(4)->Phi());
+          result->setModel_Higgs_b1_E(myModelParticles->Parton(4)->E());
+          result->setModel_Higgs_b1_jetIndex(
+              jet_indices.at((*myPermutedParticles)->JetIndex(4)));
+
+          result->setModel_Higgs_b2_pt(myModelParticles->Parton(5)->Pt());
+          result->setModel_Higgs_b2_eta(myModelParticles->Parton(5)->Eta());
+          result->setModel_Higgs_b2_phi(myModelParticles->Parton(5)->Phi());
+          result->setModel_Higgs_b2_E(myModelParticles->Parton(5)->E());
+          result->setModel_Higgs_b2_jetIndex(
+              jet_indices.at((*myPermutedParticles)->JetIndex(5)));
+        }
+      }
+
+      if (m_leptonTypeEnum == KLFEnums::LeptonType::kElectron ||
+          m_leptonTypeEnum == KLFEnums::LeptonType::kTriElectron) {
+        result->setModel_lep_pt(myModelParticles->Electron(0)->Pt());
+        result->setModel_lep_eta(myModelParticles->Electron(0)->Eta());
+        result->setModel_lep_phi(myModelParticles->Electron(0)->Phi());
+        result->setModel_lep_E(myModelParticles->Electron(0)->E());
+
+        if (m_leptonTypeEnum == KLFEnums::LeptonType::kTriElectron) {
+          result->setModel_lep_index(
+              electron_indices.at((*myPermutedParticles)->ElectronIndex(0)));
+
+          result->setModel_lepZ1_pt(myModelParticles->Electron(1)->Pt());
+          result->setModel_lepZ1_eta(myModelParticles->Electron(1)->Eta());
+          result->setModel_lepZ1_phi(myModelParticles->Electron(1)->Phi());
+          result->setModel_lepZ1_E(myModelParticles->Electron(1)->E());
+          result->setModel_lepZ1_index(
+              electron_indices.at((*myPermutedParticles)->ElectronIndex(1)));
+
+          result->setModel_lepZ2_pt(myModelParticles->Electron(2)->Pt());
+          result->setModel_lepZ2_eta(myModelParticles->Electron(2)->Eta());
+          result->setModel_lepZ2_phi(myModelParticles->Electron(2)->Phi());
+          result->setModel_lepZ2_E(myModelParticles->Electron(2)->E());
+          result->setModel_lepZ2_index(
+              electron_indices.at((*myPermutedParticles)->ElectronIndex(2)));
+        }
+      }
+
+      if (m_leptonTypeEnum == KLFEnums::LeptonType::kMuon ||
+          m_leptonTypeEnum == KLFEnums::LeptonType::kTriMuon) {
+        result->setModel_lep_pt(myModelParticles->Muon(0)->Pt());
+        result->setModel_lep_eta(myModelParticles->Muon(0)->Eta());
+        result->setModel_lep_phi(myModelParticles->Muon(0)->Phi());
+        result->setModel_lep_E(myModelParticles->Muon(0)->E());
+
+        if (m_leptonTypeEnum == KLFEnums::LeptonType::kTriMuon) {
+          result->setModel_lep_index(
+              muon_indices.at((*myPermutedParticles)->MuonIndex(0)));
+
+          result->setModel_lepZ1_pt(myModelParticles->Muon(1)->Pt());
+          result->setModel_lepZ1_eta(myModelParticles->Muon(1)->Eta());
+          result->setModel_lepZ1_phi(myModelParticles->Muon(1)->Phi());
+          result->setModel_lepZ1_E(myModelParticles->Muon(1)->E());
+          result->setModel_lepZ1_index(
+              muon_indices.at((*myPermutedParticles)->MuonIndex(1)));
+
+          result->setModel_lepZ2_pt(myModelParticles->Muon(2)->Pt());
+          result->setModel_lepZ2_eta(myModelParticles->Muon(2)->Eta());
+          result->setModel_lepZ2_phi(myModelParticles->Muon(2)->Phi());
+          result->setModel_lepZ2_E(myModelParticles->Muon(2)->E());
+          result->setModel_lepZ2_index(
+              muon_indices.at((*myPermutedParticles)->MuonIndex(2)));
+        }
+      }
+
+      result->setModel_nu_pt(myModelParticles->Neutrino(0)->Pt());
+      result->setModel_nu_eta(myModelParticles->Neutrino(0)->Eta());
+      result->setModel_nu_phi(myModelParticles->Neutrino(0)->Phi());
+      result->setModel_nu_E(myModelParticles->Neutrino(0)->E());
+    } else if (m_LHTypeEnum == KLFEnums::Likelihood::ttbar_AllHad) {
+      result->setModel_b_from_top1_pt(myModelParticles->Parton(0)->Pt());
+      result->setModel_b_from_top1_eta(myModelParticles->Parton(0)->Eta());
+      result->setModel_b_from_top1_phi(myModelParticles->Parton(0)->Phi());
+      result->setModel_b_from_top1_E(myModelParticles->Parton(0)->E());
+      result->setModel_b_from_top1_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(0)));
+
+      result->setModel_b_from_top2_pt(myModelParticles->Parton(1)->Pt());
+      result->setModel_b_from_top2_eta(myModelParticles->Parton(1)->Eta());
+      result->setModel_b_from_top2_phi(myModelParticles->Parton(1)->Phi());
+      result->setModel_b_from_top2_E(myModelParticles->Parton(1)->E());
+      result->setModel_b_from_top2_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(1)));
+
+      result->setModel_lj1_from_top1_pt(myModelParticles->Parton(2)->Pt());
+      result->setModel_lj1_from_top1_eta(myModelParticles->Parton(2)->Eta());
+      result->setModel_lj1_from_top1_phi(myModelParticles->Parton(2)->Phi());
+      result->setModel_lj1_from_top1_E(myModelParticles->Parton(2)->E());
+      result->setModel_lj1_from_top1_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(2)));
+
+      result->setModel_lj2_from_top1_pt(myModelParticles->Parton(3)->Pt());
+      result->setModel_lj2_from_top1_eta(myModelParticles->Parton(3)->Eta());
+      result->setModel_lj2_from_top1_phi(myModelParticles->Parton(3)->Phi());
+      result->setModel_lj2_from_top1_E(myModelParticles->Parton(3)->E());
+      result->setModel_lj2_from_top1_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(3)));
+
+      result->setModel_lj1_from_top2_pt(myModelParticles->Parton(4)->Pt());
+      result->setModel_lj1_from_top2_eta(myModelParticles->Parton(4)->Eta());
+      result->setModel_lj1_from_top2_phi(myModelParticles->Parton(4)->Phi());
+      result->setModel_lj1_from_top2_E(myModelParticles->Parton(4)->E());
+      result->setModel_lj1_from_top2_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(4)));
+
+      result->setModel_lj2_from_top2_pt(myModelParticles->Parton(5)->Pt());
+      result->setModel_lj2_from_top2_eta(myModelParticles->Parton(5)->Eta());
+      result->setModel_lj2_from_top2_phi(myModelParticles->Parton(5)->Phi());
+      result->setModel_lj2_from_top2_E(myModelParticles->Parton(5)->E());
+      result->setModel_lj2_from_top2_jetIndex(
+          jet_indices.at((*myPermutedParticles)->JetIndex(5)));
+    }
+  }  // Loop over permutations
+
+  // Normalize event probability to unity
+  // work out best permutation
+  float sumEventProbability(0.), bestEventProbability(0.);
+  size_t bestPermutation(999), iPerm(0);
+
+  // First loop
+  for (auto x : *resultContainer) {
+    float prob = x->eventProbability();
+    short minuitDidNotConverge = x->minuitDidNotConverge();
+    short fitAbortedDueToNaN = x->fitAbortedDueToNaN();
+    short atLeastOneFitParameterAtItsLimit =
+        x->atLeastOneFitParameterAtItsLimit();
+    short invalidTransferFunctionAtConvergence =
+        x->invalidTransferFunctionAtConvergence();
+    sumEventProbability += prob;
+    ++iPerm;
+
+    // check if the best value has the highest event probability AND converged
+    if (minuitDidNotConverge)
+      continue;
+    if (fitAbortedDueToNaN)
+      continue;
+    if (atLeastOneFitParameterAtItsLimit)
+      continue;
+    if (invalidTransferFunctionAtConvergence)
+      continue;
+
+    if (prob > bestEventProbability) {
+      bestEventProbability = prob;
+      // Using iPerm -1 because it has already been incremented before
+      bestPermutation = iPerm - 1;
+    }
+  }
+
+  // Second loop
+  iPerm = 0;
+  for (auto x : *resultContainer) {
+    x->setEventProbability(x->eventProbability() / sumEventProbability);
+    if (iPerm == bestPermutation) {
+      x->setBestPermutation(1);
+    } else {
+      x->setBestPermutation(0);
+    }
+    ++iPerm;
+  }
+
+  // Save all permutations
+  if (m_saveAllPermutations) {
+    ANA_CHECK(m_outHandle.record(std::move(resultContainer),
+                                 std::move(resultAuxContainer), sys));
+  } else {  // Save only the best permutation
+    // create or retrieve the xAOD::KLFitterResultContainer
+    auto bestContainer = std::make_unique<xAOD::KLFitterResultContainer>();
+    auto bestAuxContainer =
+        std::make_unique<xAOD::KLFitterResultAuxContainer>();
+    bestContainer->setStore(bestAuxContainer.get());
+
+    for (auto x : *resultContainer) {
+      if (x->bestPermutation() == 1) {
+        xAOD::KLFitterResult *result = new xAOD::KLFitterResult{};
+        result->makePrivateStore(*x);
+        bestContainer->push_back(result);
+      }
+    }
+    ANA_CHECK(m_outHandle.record(std::move(bestContainer),
+                                 std::move(bestAuxContainer), sys));
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+}  // namespace EventReco
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/dict/ContainerProxies.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/dict/ContainerProxies.cxx
new file mode 100644
index 000000000000..71d326627a71
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/Root/dict/ContainerProxies.cxx
@@ -0,0 +1,8 @@
+/*
+   Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "KLFitterAnalysisAlgorithms/KLFitterResultContainer.h"
+#include "xAODCore/AddDVProxy.h"
+
+ADD_NS_DV_PROXY(xAOD, KLFitterResultContainer);
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/python/KLFitterConfig.py b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/python/KLFitterConfig.py
new file mode 100644
index 000000000000..e8f1a8984f2b
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/python/KLFitterConfig.py
@@ -0,0 +1,253 @@
+# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+
+from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
+from FTagAnalysisAlgorithms.FTagHelpers import getRecommendedBTagCalib
+
+
+class KLFitterBlock(ConfigBlock):
+    """ConfigBlock for KLFitter algorithms"""
+
+    def __init__(self, containerName):
+        super(KLFitterBlock, self).__init__()
+        self.containerName = containerName
+        self.addOption(
+            "electrons",
+            "",
+            type=str,
+            info="the input electron container, with a possible selection, in the format container or container.selection. The default is '' (empty string).",
+        )
+        self.addOption(
+            "muons",
+            "",
+            type=str,
+            info="the input muon container, with a possible selection, in the format container or container.selection. The default is '' (empty string).",
+        )
+        self.addOption(
+            "jets",
+            "",
+            type=str,
+            info="the input jet container, with a possible selection, in the format container or container.selection. The default is '' (empty string).",
+        )
+        self.addOption(
+            "met",
+            "",
+            type=str,
+            info="the input MET container. The default is '' (empty string).",
+        )
+        self.addOption(
+            "likelihoodType",
+            "",
+            type=str,
+            info="KLFitter likelihood, if only one is needed. See KLFitterEnums.h for possible values. The default is '' (empty string).",
+        )
+        self.addOption(
+            "leptonType",
+            "",
+            type=str,
+            info="type of lepton to use (only relevant to certain likelihood types), if only one is needed. See KLFitterEnums.h for possible values. The default is '' (empty string).",
+        )
+        self.addOption(
+            "jetSelectionMode",
+            "",
+            type=str,
+            info="jet selection mode to use, if only one is needed. See KLFitterEnums.h for possible values. The default is '' (empty string).",
+        )
+        self.addOption(
+            "btaggingMethod",
+            "kNoTag",
+            type=str,
+            info="strategy to handle b-jets, if only one is needed. See KLFitterEnums.h for possible values. The default is '' (empty string).",
+        )
+        self.addOption(
+            "bTagCDIFile",
+            None,
+            type=str,
+            info="CDI file to pass to the b-tagging efficiency tool",
+        )
+        self.addOption(
+            "btagger",
+            "GN2v01",
+            type=str,
+            info="b-tagging algorithm to use, if only one is needed. The default is 'GN2v01'.",
+        )
+        self.addOption(
+            "btagWP",
+            "",
+            type=str,
+            info="b-tagging efficiency WP to use, if only one is needed.",
+        )
+        self.addOption(
+            "btagIgnoreOutOfValidityRange",
+            False,
+            type=bool,
+            info="whether or not the b-tagger should ignore (and not fail) when a jet is outside the calibration range. The default is False.",
+        )
+        self.addOption(
+            "selectionRegionsConfig",
+            "",
+            type=str,
+            info="string of the form 'selectionName: sel1, optionA: opA, optionB: opB; selectionName: sel2, ...' where options can be likelihoodType, leptonType, jetSelectionMode, btaggingMethod, btagger or btagWP. The default is '' (empty string).",
+        )
+        self.addOption(
+            "saveAllPermutations",
+            False,
+            type=bool,
+            info="whether to save all permutations, or just the best one. The default is False (only save the best one).",
+        )
+        # list of dictionaries for the per-region config options
+        self.perRegionConfiguration = list()
+
+    def parseSelectionRegionsConfig(self):
+        regions = self.selectionRegionsConfig.split(";")
+        if len(regions) == 0:
+            raise Exception(
+                "KLFitterConfig: Could not determine any regions in your SelectionRegionsConfig"
+            )
+        for reg in regions:
+            regstrip = reg.replace(" ", "")
+            regionopts = dict(
+                tuple(option.split(":")) for option in regstrip.split(",")
+            )
+            if "selectionName" not in regionopts:
+                raise Exception(
+                    "KLFitterConfig: Could not parse SelectionRegionsConfig selectionName for region ",
+                    reg,
+                )
+            if "likelihoodType" in regionopts:
+                raise Exception(
+                    "KLFitterConfig: likelihoodType cannot be overriden per region. Create a separate instance of KLFitter block with different likelihoodType instead."
+                )
+
+            self.perRegionConfiguration.append(regionopts)
+
+    def makeAlgs(self, config):
+        self.parseSelectionRegionsConfig()
+        for perRegionConfig in self.perRegionConfiguration:
+            selectionName = perRegionConfig["selectionName"]
+            alg = config.createAlgorithm(
+                "EventReco::RunKLFitterAlg",
+                f"RunKLFitterAlg_{self.containerName}_{selectionName}",
+            )
+            # input objects and their object selections
+            alg.electrons, alg.electronSelection = config.readNameAndSelection(
+                self.electrons
+            )
+            alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
+            alg.jets, alg.jetSelection = config.readNameAndSelection(self.jets)
+            alg.met = config.readName(self.met)
+            alg.result = self.containerName + "_%SYS%"
+
+            # global settings, in future expect to expose more options for configuration
+            alg.SaveAllPermutations = self.saveAllPermutations
+
+            # these settings can be defined per-region, but if not, we fallback to global setting
+            alg.selectionDecorationName = selectionName + "_%SYS%,as_char"
+            alg.LHType = self.likelihoodType
+            alg.LeptonType = perRegionConfig.get("leptonType", self.leptonType)
+            alg.JetSelectionMode = perRegionConfig.get(
+                "jetSelectionMode", self.jetSelectionMode
+            )
+            btagAlgo = perRegionConfig.get("btagger", self.btagger)
+            btagWP = perRegionConfig.get("btagWP", self.btagWP)
+            alg.BTaggingDecoration = f"ftag_select_{btagAlgo}_{btagWP}"
+
+            alg.BTaggingMethod = perRegionConfig.get(
+                "btaggingMethod", self.btaggingMethod
+            )
+            if alg.BTaggingMethod == "kWorkingPoint":
+                config.addPrivateTool("btagEffTool", "BTaggingEfficiencyTool")
+                alg.btagEffTool.TaggerName = self.btagger
+                alg.btagEffTool.OperatingPoint = self.btagWP
+                jetCollection = config.originalName(self.jets.split(".")[0])
+                alg.btagEffTool.JetAuthor = jetCollection
+                alg.btagEffTool.ScaleFactorFileName = (
+                    getRecommendedBTagCalib(config.geometry())
+                    if self.bTagCDIFile is None
+                    else self.bTagCDIFile
+                )
+                alg.btagEffTool.IgnoreOutOfValidityRange = (
+                    self.btagIgnoreOutOfValidityRange
+                )
+                alg.btagEffTool.MinPt = (
+                    20e3  # hardcoded to the recommendation for EMPFlow at the moment
+                )
+                # NOTE the efficiency tool is simply set to the default generator,
+                # meaning the results are not correct for alternative showering generators!!
+
+        finalizeAlg = config.createAlgorithm(
+            "EventReco::KLFitterFinalizeOutputAlg",
+            "KLFitterFinalizeOutputAlg_" + self.containerName,
+        )
+        finalizeAlg.resultContainerToCheck = self.containerName + "_%SYS%"
+        finalizeAlg.resultContainerToWrite = self.containerName + "_%SYS%"
+
+        config.setSourceName(self.containerName, self.containerName)
+        config.addOutputContainer(self.containerName, self.containerName + "_%SYS%")
+
+        config.addOutputVar(self.containerName, "eventProbability", "eventProbability")
+        config.addOutputVar(self.containerName, "logLikelihood", "logLikelihood")
+        if self.saveAllPermutations:
+            config.addOutputVar(self.containerName, "selected", "selected")
+
+        if self.likelihoodType != "ttbar_AllHad":
+            config.addOutputVar(
+                self.containerName, "model_bhad_jetIndex", "bhad_jetIndex"
+            )
+            config.addOutputVar(
+                self.containerName, "model_blep_jetIndex", "blep_jetIndex"
+            )
+            config.addOutputVar(
+                self.containerName, "model_lq1_jetIndex", "lq1_jetIndex"
+            )
+            if self.likelihoodType != "ttbar_BoostedLJets":
+                config.addOutputVar(
+                    self.containerName, "model_lq2_jetIndex", "lq2_jetIndex"
+                )
+            if self.likelihoodType == "ttH":
+                config.addOutputVar(
+                    self.containerName, "model_Higgs_b1_jetIndex", "Higgs_b1_jetIndex"
+                )
+                config.addOutputVar(
+                    self.containerName, "model_Higgs_b2_jetIndex", "Higgs_b2_jetIndex"
+                )
+
+            config.addOutputVar(self.containerName, "model_nu_pt", "nu_pt")
+            config.addOutputVar(self.containerName, "model_nu_eta", "nu_eta")
+            config.addOutputVar(self.containerName, "model_nu_phi", "nu_phi")
+            config.addOutputVar(self.containerName, "model_nu_E", "nu_E")
+
+            if self.likelihoodType == "ttZTrilepton":
+                config.addOutputVar(self.containerName, "model_lep_index", "lep_index")
+                config.addOutputVar(
+                    self.containerName, "model_lepZ1_index", "lepZ1_index"
+                )
+                config.addOutputVar(
+                    self.containerName, "model_lepZ2_index", "lepZ2_index"
+                )
+        else:
+            config.addOutputVar(
+                self.containerName, "model_b_from_top1_jetIndex", "b_from_top1_jetIndex"
+            )
+            config.addOutputVar(
+                self.containerName, "model_b_from_top2_jetIndex", "b_from_top2_jetIndex"
+            )
+            config.addOutputVar(
+                self.containerName,
+                "model_lj1_from_top1_jetIndex",
+                "lj1_from_top1_jetIndex",
+            )
+            config.addOutputVar(
+                self.containerName,
+                "model_lj2_from_top1_jetIndex",
+                "lj2_from_top1_jetIndex",
+            )
+            config.addOutputVar(
+                self.containerName,
+                "model_lj1_from_top2_jetIndex",
+                "lj1_from_top2_jetIndex",
+            )
+            config.addOutputVar(
+                self.containerName,
+                "model_lj2_from_top2_jetIndex",
+                "lj2_from_top2_jetIndex",
+            )
diff --git a/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/src/components/KLFitterReconstructionAlgorithms_entries.cxx b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/src/components/KLFitterReconstructionAlgorithms_entries.cxx
new file mode 100644
index 000000000000..efef96451754
--- /dev/null
+++ b/PhysicsAnalysis/Algorithms/KLFitterAnalysisAlgorithms/src/components/KLFitterReconstructionAlgorithms_entries.cxx
@@ -0,0 +1,12 @@
+/*
+  Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Oliver Majersky
+/// @author Baptiste Ravina
+
+#include <KLFitterAnalysisAlgorithms/RunKLFitterAlg.h>
+#include <KLFitterAnalysisAlgorithms/KLFitterFinalizeOutputAlg.h>
+
+DECLARE_COMPONENT (EventReco::KLFitterFinalizeOutputAlg)
+DECLARE_COMPONENT (EventReco::RunKLFitterAlg)
-- 
GitLab