From 98086d895dc13efcf0509bd2c00b5e264b407e2c Mon Sep 17 00:00:00 2001
From: Gerhard Raven <gerhard.raven@nikhef.nl>
Date: Fri, 6 Jan 2023 11:38:25 +0100
Subject: [PATCH] Consolidate Linker code

- add functionality to LHCb::LinksByKey (link, typed constructor), make
  it impossible to change sourceID, targetID, ordering _after_
  construction (as that would leave any already present data in a bad
  shape)
- drop the no longer relevant OutputLinks class
- drop the unused AllLinks header
- drop the no longer releavent LinkedFromKey class
- move LinkerEntry and LinkerRange into LinkerTable as they are implementation details of iterating over the table
- add warning counter which keeps track of how often Object2MCLinker invokes an algorithm to build a linker table
- unify 'range' function names, make the inverse range in Object2MCLinker lazy
- reduce code in LinkerTool using the new functionality in LinksByKey
- reduce code in LinkerWithKey using the new functionality in LinksByKey
- remove confusing (and buggy?) Object2MCLinker::linkerTable method
---
 .../include/Associators/Associators.h         |   1 -
 .../include/Associators/Detail/Mixin.h        |  11 +-
 .../include/Associators/InputLinks.h          |  11 +-
 .../include/Associators/OutputLinks.h         |  66 ------
 .../include/Kernel/Particle2MCLinker.h        |  87 +++++---
 .../include/Kernel/Particle2MCLinker.icpp     |  42 +---
 .../include/CaloFutureUtils/CaloFuture2MC.h   |   8 -
 Event/LinkerEvent/include/Event/LinksByKey.h  |  86 +++++---
 Event/LinkerEvent/include/Linker/AllLinks.h   | 128 ------------
 Event/LinkerEvent/include/Linker/LinkedFrom.h |  14 +-
 .../include/Linker/LinkedFromKey.h            |  27 ---
 .../LinkerEvent/include/Linker/LinkerEntry.h  |  54 -----
 .../LinkerEvent/include/Linker/LinkerRange.h  |  81 --------
 .../LinkerEvent/include/Linker/LinkerTable.h  | 193 +++++++++++-------
 Event/LinkerEvent/include/Linker/LinkerTool.h |  97 ++-------
 .../include/Linker/LinkerWithKey.h            |  89 +++-----
 Event/LinkerEvent/src/LinksByKey.cpp          |   6 +-
 17 files changed, 317 insertions(+), 684 deletions(-)
 delete mode 100644 Associators/AssociatorsBase/include/Associators/OutputLinks.h
 delete mode 100644 Event/LinkerEvent/include/Linker/AllLinks.h
 delete mode 100644 Event/LinkerEvent/include/Linker/LinkedFromKey.h
 delete mode 100644 Event/LinkerEvent/include/Linker/LinkerEntry.h
 delete mode 100644 Event/LinkerEvent/include/Linker/LinkerRange.h

diff --git a/Associators/AssociatorsBase/include/Associators/Associators.h b/Associators/AssociatorsBase/include/Associators/Associators.h
index c70ddfdbf6e..ab20c3f1e31 100644
--- a/Associators/AssociatorsBase/include/Associators/Associators.h
+++ b/Associators/AssociatorsBase/include/Associators/Associators.h
@@ -10,4 +10,3 @@
 \*****************************************************************************/
 #include "InputLinks.h"
 #include "Location.h"
-#include "OutputLinks.h"
diff --git a/Associators/AssociatorsBase/include/Associators/Detail/Mixin.h b/Associators/AssociatorsBase/include/Associators/Detail/Mixin.h
index 2bc7fa4b9ae..ccc2eb3289f 100644
--- a/Associators/AssociatorsBase/include/Associators/Detail/Mixin.h
+++ b/Associators/AssociatorsBase/include/Associators/Detail/Mixin.h
@@ -15,7 +15,6 @@
 #include <GaudiKernel/LinkManager.h>
 
 #include <Associators/InputLinks.h>
-#include <Associators/OutputLinks.h>
 
 namespace {
   struct DefaultType {};
@@ -52,8 +51,9 @@ namespace Detail {
     // ContainedObject and the specified type, respectively.
     template <typename Src, typename Target = DefaultType,
               typename std::enable_if_t<std::is_same<DefaultType, Target>::value>* = nullptr>
-    OutputLinks<Source, Src> outputLinks() const {
-      return OutputLinks<Source, Src>{};
+    auto outputLinks() const {
+      return LHCb::LinksByKey{std::in_place_type<Source>, std::in_place_type<Src>,
+                              LHCb::LinksByKey::Order::decreasingWeight};
     }
 
     // Bit of a trick here with enable if: if Target is specified, so
@@ -62,8 +62,9 @@ namespace Detail {
     // types.
     template <typename Src, typename Target = DefaultType,
               typename std::enable_if_t<!std::is_same<DefaultType, Target>::value>* = nullptr>
-    OutputLinks<Src, Target> outputLinks() const {
-      return OutputLinks<Src, Target>{};
+    auto outputLinks() const {
+      return LHCb::LinksByKey{std::in_place_type<Src>, std::in_place_type<Target>,
+                              LHCb::LinksByKey::Order::decreasingWeight};
     }
   };
 
diff --git a/Associators/AssociatorsBase/include/Associators/InputLinks.h b/Associators/AssociatorsBase/include/Associators/InputLinks.h
index 68a127d6c17..1b1de8353fe 100644
--- a/Associators/AssociatorsBase/include/Associators/InputLinks.h
+++ b/Associators/AssociatorsBase/include/Associators/InputLinks.h
@@ -11,17 +11,13 @@
 #ifndef ASSOCIATORS_INPUTLINKS_H
 #define ASSOCIATORS_INPUTLINKS_H
 
-#include <iostream>
-#include <type_traits>
-
 #include <Event/LinksByKey.h>
 #include <GaudiKernel/GaudiException.h>
 #include <GaudiKernel/IRegistry.h>
 #include <GaudiKernel/LinkManager.h>
 #include <Relations/RelationWeighted2D.h>
-
-// template <class Source, class Targed, class Enabled = void>
-// class InputLinks {};
+#include <iostream>
+#include <type_traits>
 
 namespace {
 
@@ -99,8 +95,7 @@ public:
   };
 
   typename LHCb::RelationWeighted2D<unsigned int, Target, double>::Range from( unsigned int key ) const {
-    auto range = m_keyRelations.relations( key );
-    return range;
+    return m_keyRelations.relations( key );
   }
 
   typename LHCb::RelationWeighted2D<Source, Target, double>::Range from( const Source* source ) const {
diff --git a/Associators/AssociatorsBase/include/Associators/OutputLinks.h b/Associators/AssociatorsBase/include/Associators/OutputLinks.h
deleted file mode 100644
index a8f12e4c183..00000000000
--- a/Associators/AssociatorsBase/include/Associators/OutputLinks.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*****************************************************************************\
-* (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration      *
-*                                                                             *
-* This software is distributed under the terms of the GNU General Public      *
-* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
-*                                                                             *
-* In applying this licence, CERN does not waive the privileges and immunities *
-* granted to it by virtue of its status as an Intergovernmental Organization  *
-* or submit itself to any jurisdiction.                                       *
-\*****************************************************************************/
-#ifndef ASSOCIATORS_OUTPUTLINKS_H
-#define ASSOCIATORS_OUTPUTLINKS_H
-
-#include <Event/LinksByKey.h>
-
-#include <GaudiKernel/LinkManager.h>
-
-template <typename Source, typename Target>
-class OutputLinks final {
-public:
-  OutputLinks() {
-    m_links.setTargetClassID( Target::classID() );
-    m_links.setSourceClassID( Source::classID() );
-  }
-
-  operator LHCb::LinksByKey() {
-    // This is horrible, but needed because the copy constructor of
-    // DataObject creates a new (and empty) LinkManager. The
-    // existing move constructor moves the linkmanager, which would
-    // work, expect the using auto conversion twice would result in
-    // undefined behaviour. So, create a copy, insert all the links
-    // it's and then return it.
-    LHCb::LinksByKey r{m_links};
-    for ( long linkID = 0; linkID != m_links.linkMgr()->size(); ++linkID ) {
-      auto link = m_links.linkMgr()->link( linkID );
-      r.linkMgr()->addLink( link->path(), link->object() );
-    }
-    return r;
-  }
-
-  const LHCb::LinksByKey& links() const { return m_links; }
-  LHCb::LinksByKey&       links() { return m_links; }
-
-  void reset() { m_links.reset(); }
-
-  void link( const Source* source, const Target* dest, double weight = 1. ) {
-    if ( !source || !dest ) return;
-
-    m_links.addReference( source->index(), m_links.linkID( source->parent() ), dest->index(),
-                          m_links.linkID( dest->parent() ), weight );
-  }
-
-  void link( int key, const Target* dest, double weight = 1. ) {
-    if ( !dest ) return;
-
-    m_links.addReference( key, -1, dest->index(), m_links.linkID( dest->parent() ), weight );
-  }
-
-  void setIncreasingWeight() { m_links.setIncreasing(); }
-  void setDecreasingWeight() { m_links.setDecreasing(); }
-
-private:
-  LHCb::LinksByKey m_links;
-};
-
-#endif
diff --git a/Associators/MCAssociators/include/Kernel/Particle2MCLinker.h b/Associators/MCAssociators/include/Kernel/Particle2MCLinker.h
index 160dbda6a1c..066aac5ae74 100644
--- a/Associators/MCAssociators/include/Kernel/Particle2MCLinker.h
+++ b/Associators/MCAssociators/include/Kernel/Particle2MCLinker.h
@@ -25,7 +25,6 @@
 #include "GaudiKernel/IAlgManager.h"
 #include "GaudiKernel/IRegistry.h"
 #include "GaudiKernel/KeyedObject.h"
-#include "GaudiKernel/compose.h"
 
 /**
  *  Class providing association functionality to MCParticles
@@ -35,7 +34,7 @@
  *  @author Philippe Charpentier
  *  @date   2004-04-29
  */
-template <typename SOURCE = LHCb::Particle, typename PARENT = GaudiAlgorithm>
+template <typename SOURCE, typename PARENT = GaudiAlgorithm>
 class Object2MCLinker {
   // Typedef for RangeFrom return type
   using ToRange =
@@ -51,10 +50,9 @@ public:
       : m_parent( myMother )
       , m_extension( extension )
       , m_linkerAlgType( algType )
-      , m_containerList( std::move( containerList ) )
-      , m_linkerTable( myMother->evtSvc(), nullptr, "" ) {}
+      , m_containerList( std::move( containerList ) ) {}
 
-  Object2MCLinker( const PARENT* myMother ) : m_parent( myMother ), m_linkerTable( myMother->evtSvc(), nullptr, "" ) {}
+  Object2MCLinker( const PARENT* myMother ) : m_parent( myMother ) {}
 
   Object2MCLinker( const PARENT* myMother, const int method, std::vector<std::string> containerList )
       : Object2MCLinker( myMother, Particle2MCMethod::algType[method], Particle2MCMethod::extension[method],
@@ -92,14 +90,11 @@ public:
     return std::distance( r.begin(), r.end() );
   }
 
-  bool notFound();
   bool checkAssociation( const SOURCE* obj, const LHCb::MCParticle* mcPart );
 
   using Linker = LinkerWithKey<LHCb::MCParticle, SOURCE>;
-  Linker* linkerTable( const std::string& name );
 
 private:
-  Linker*            linkerTable( const std::string& name, LinkedTo<LHCb::MCParticle> test );
   const std::string& name() const { return m_parent->name(); }
   const std::string& context() const { return m_parent->context(); }
 
@@ -140,9 +135,10 @@ private:
   mutable Gaudi::Accumulators::MsgCounter<MSG::ERROR> m_setInputErr{m_parent, "Unable to set Property InputData", 10};
   mutable Gaudi::Accumulators::MsgCounter<MSG::ERROR> m_setRITErr{m_parent, "Unable to set Property RootInTES", 10};
   mutable Gaudi::Accumulators::MsgCounter<MSG::WARNING> m_ipropWarn{m_parent, "Unable to get IProperty pointer", 10};
+  mutable Gaudi::Accumulators::MsgCounter<MSG::WARNING> m_executing_linker_builder_algo{
+      m_parent, "Explicitly Executing Linker Building algorithm", 0};
 
   LHCb::LinksByKey const* m_links = nullptr;
-  Linker                  m_linkerTable;
 
   // Private methods
 protected:
@@ -151,17 +147,16 @@ protected:
   void              createLinks( const std::string& contName );
 
 private:
-  StatusCode  locateAlgorithm( const std::string& algType, const std::string& algName, IAlgorithm*& alg,
-                               const std::vector<std::string>& inputData );
-  std::string getGaudiRootInTES();
-  StatusCode  setAlgInputData( IAlgorithm*& alg, const std::vector<std::string>& inputData );
+  StatusCode locateAlgorithm( const std::string& algType, const std::string& algName, IAlgorithm*& alg,
+                              const std::vector<std::string>& inputData );
+  StatusCode setAlgInputData( IAlgorithm*& alg, const std::vector<std::string>& inputData );
 
-  inline std::string containerName( const ContainedObject* obj ) const {
+  std::string containerName( const ContainedObject* obj ) const {
     return ( obj->parent() && obj->parent()->registry() ) ? obj->parent()->registry()->identifier() : std::string{};
   }
 };
 
-template <typename OBJ2MCP = LHCb::Particle, typename PARENT = GaudiAlgorithm>
+template <typename OBJ2MCP, typename PARENT = GaudiAlgorithm>
 class Object2FromMC : public Object2MCLinker<OBJ2MCP, PARENT> {
 
   std::vector<LHCb::LinksByKey const*> m_linkerList;
@@ -173,9 +168,7 @@ public:
     if ( !this->hasValidParent() || this->m_linkerAlgType.empty() ) return;
     m_linkerList.reserve( this->m_containerList.size() );
     for ( const auto& cont : this->m_containerList ) {
-      const std::string containerName = cont + this->m_extension;
-      const std::string name =
-          "Link/" + ( "/Event/" == containerName.substr( 0, 7 ) ? containerName.substr( 7 ) : containerName );
+      const std::string name = LHCb::LinksByKey::linkerName( cont + this->m_extension );
       this->debug() << "trying to retrieve " << name << endmsg;
       SmartDataPtr<LHCb::LinksByKey> links( this->evtSvc(), name );
       if ( !links ) {
@@ -192,15 +185,59 @@ public:
     const OBJ2MCP* part = dynamic_cast<const OBJ2MCP*>( obj );
     if ( part ) return !range( part ).empty();
     const LHCb::MCParticle* mcPart = dynamic_cast<const LHCb::MCParticle*>( obj );
-    return mcPart && !rangeP( mcPart ).empty();
+    return mcPart && !range( mcPart ).empty();
   }
 
-  auto rangeP( const LHCb::MCParticle* mcPart ) {
-    std::vector<std::pair<OBJ2MCP const*, double>> r;
-    for ( const auto& links : this->m_linkerList ) {
-      for ( const auto& [part, w] : LinkedFrom<OBJ2MCP>{links}.weightedRange( mcPart ) ) r.emplace_back( &part, w );
-    }
-    return r;
+  using Object2MCLinker<OBJ2MCP, PARENT>::range;
+  auto range( const LHCb::MCParticle* mcPart ) const {
+    class InverseRange {
+      LHCb::span<LHCb::LinksByKey const* const> m_linkerList;
+      const LHCb::MCParticle*                   m_part = nullptr;
+      class Sentinel {};
+      class Iterator {
+        using InnerRange =
+            decltype( LinkedFrom<OBJ2MCP>{nullptr}.weightedRange( static_cast<LHCb::MCParticle*>( nullptr ) ) );
+        using InnerIter = decltype( std::declval<InnerRange>().begin() );
+        LHCb::span<LHCb::LinksByKey const* const> m_linkerList;
+        InnerRange m_range = LinkedFrom<OBJ2MCP>{nullptr}.weightedRange( static_cast<LHCb::MCParticle*>( nullptr ) );
+        InnerIter  m_current =
+            LinkedFrom<OBJ2MCP>{nullptr}.weightedRange( static_cast<LHCb::MCParticle*>( nullptr ) ).begin();
+        const LHCb::MCParticle* m_part = nullptr;
+
+      public:
+        Iterator( LHCb::span<LHCb::LinksByKey const* const> linkers, const LHCb::MCParticle* p )
+            : m_linkerList{linkers}, m_part{p} {
+          for ( ; m_range.empty() && !m_linkerList.empty(); m_linkerList = m_linkerList.subspan<1>() ) {
+            m_range = LinkedFrom<OBJ2MCP>{m_linkerList.front()}.weightedRange( m_part );
+          }
+          m_current = m_range.begin();
+        }
+        Iterator& operator++() {
+          if ( m_current != m_range.end() ) {
+            ++m_current;
+          } else if ( !m_linkerList.empty() ) {
+            *this = Iterator{m_linkerList.subspan<1>(), m_part};
+          }
+          return *this;
+        }
+        decltype( auto ) operator*() const { return *m_current; }
+        bool             operator!=( Sentinel ) const { return !m_linkerList.empty(); }
+      };
+
+    public:
+      InverseRange( LHCb::span<LHCb::LinksByKey const* const> links, const LHCb::MCParticle* p )
+          : m_linkerList{links}, m_part{p} {}
+      Iterator         begin() const { return {m_linkerList, m_part}; }
+      Sentinel         end() const { return {}; }
+      bool             empty() const { return !( begin() != end() ); }
+      decltype( auto ) front() const { return *begin(); }
+      auto const*      try_front() const {
+        auto i = begin();
+        return i != end() ? &std::get<0>( *i ) : nullptr;
+      }
+    };
+
+    return InverseRange{this->m_linkerList, mcPart};
   }
 };
 
diff --git a/Associators/MCAssociators/include/Kernel/Particle2MCLinker.icpp b/Associators/MCAssociators/include/Kernel/Particle2MCLinker.icpp
index ae2896a10c6..9692bb4a84c 100644
--- a/Associators/MCAssociators/include/Kernel/Particle2MCLinker.icpp
+++ b/Associators/MCAssociators/include/Kernel/Particle2MCLinker.icpp
@@ -110,10 +110,7 @@ StatusCode Object2MCLinker<SOURCE, PARENT>::locateAlgorithm( const std::string&
   }
   return sc;
 }
-template <typename SOURCE, typename PARENT>
-std::string Object2MCLinker<SOURCE, PARENT>::getGaudiRootInTES() {
-  return m_parent ? m_parent->rootInTES() : std::string{};
-}
+
 template <typename SOURCE, typename PARENT>
 StatusCode Object2MCLinker<SOURCE, PARENT>::setAlgInputData( IAlgorithm*&                    alg,
                                                              const std::vector<std::string>& inputData ) {
@@ -121,7 +118,6 @@ StatusCode Object2MCLinker<SOURCE, PARENT>::setAlgInputData( IAlgorithm*&
   if ( inputData.empty() ) return StatusCode::SUCCESS;
 
   IProperty* prop = dynamic_cast<IProperty*>( alg );
-
   if ( !prop ) {
     ++m_ipropWarn;
     return StatusCode::SUCCESS;
@@ -143,7 +139,7 @@ StatusCode Object2MCLinker<SOURCE, PARENT>::setAlgInputData( IAlgorithm*&
   std::string sep        = "\"";
   for ( const auto& inpStr : inputData ) {
     propString += sep;
-    if ( std::string::npos == inpStr.find( "/Particles" ) && std::string::npos == inpStr.find( "/ProtoP" ) )
+    if ( inpStr.npos == inpStr.find( "/Particles" ) && inpStr.npos == inpStr.find( "/ProtoP" ) )
       propString += "/Particles";
     propString += inpStr;
     sep = "\",\"";
@@ -155,7 +151,7 @@ StatusCode Object2MCLinker<SOURCE, PARENT>::setAlgInputData( IAlgorithm*&
     return sc;
   }
   debug() << "Property InputData set to " << propString << " in algo " << alg->name() << endmsg;
-  const std::string rit( getGaudiRootInTES() );
+  const std::string rit( m_parent ? m_parent->rootInTES() : std::string{} );
   if ( !rit.empty() ) {
     std::string check; // check current RootInTES, if it exists
     sc = prop->getProperty( "RootInTES", check );
@@ -176,12 +172,6 @@ StatusCode Object2MCLinker<SOURCE, PARENT>::setAlgInputData( IAlgorithm*&
   return sc;
 }
 
-template <typename SOURCE, typename PARENT>
-bool Object2MCLinker<SOURCE, PARENT>::notFound() {
-  return std::all_of( m_containerList.begin(), m_containerList.end(),
-                      [&]( const std::string& s ) { return notFound( s ); } );
-}
-
 template <typename SOURCE, typename PARENT>
 void Object2MCLinker<SOURCE, PARENT>::createLinks( const std::string& contName ) {
   // First find the contname is in the list
@@ -210,6 +200,8 @@ void Object2MCLinker<SOURCE, PARENT>::createLinks( const std::string& contName )
       }
       // Call the algorithm to get the table done
       debug() << "==> Executing Linker builder algorithm " << m_linkerAlg->name() << endmsg;
+      // FIXME: this is a _major_ _design_ mistake: only the scheduler should execute algorithms...
+      ++m_executing_linker_builder_algo;
       StatusCode sc = m_linkerAlg->sysExecute( Gaudi::Hive::currentContext() );
       if ( sc.isFailure() ) {
         error() << "Failed executing Linker builder algorithm " << m_linkerAlg->name() << endmsg;
@@ -226,7 +218,7 @@ typename Object2MCLinker<SOURCE, PARENT>::ToRange Object2MCLinker<SOURCE, PARENT
     m_links = nullptr;
     return LinkedTo<LHCb::MCParticle>{nullptr}.weightedRange( static_cast<SOURCE*>( nullptr ) );
   }
-  std::string contName = containerName( dynamic_cast<const ContainedObject*>( part ) );
+  std::string contName = containerName( part );
   if ( contName.compare( 0, 7, "/Event/" ) == 0 ) { contName = contName.substr( 7 ); }
   if ( contName.empty() ) {
     m_links = nullptr;
@@ -241,28 +233,6 @@ typename Object2MCLinker<SOURCE, PARENT>::ToRange Object2MCLinker<SOURCE, PARENT
   return LinkedTo<LHCb::MCParticle>{m_links}.weightedRange( part );
 }
 
-// Helper methods to create a LinkerWithKey table if needed
-template <typename SOURCE, typename PARENT>
-typename Object2MCLinker<SOURCE, PARENT>::Linker*
-Object2MCLinker<SOURCE, PARENT>::linkerTable( const std::string& name ) {
-  if ( !hasValidParent() ) return nullptr;
-  auto links = SmartDataPtr<LHCb::LinksByKey>{evtSvc(), LHCb::LinksByKey::linkerName( name )};
-  return linkerTable( name, LinkedTo<LHCb::MCParticle>{links} );
-}
-
-template <typename SOURCE, typename PARENT>
-typename Object2MCLinker<SOURCE, PARENT>::Linker*
-Object2MCLinker<SOURCE, PARENT>::linkerTable( const std::string& name, LinkedTo<LHCb::MCParticle> test ) {
-  if ( !hasValidParent() ) return nullptr;
-
-  if ( !test ) {
-    m_linkerTable = typename Object2MCLinker<SOURCE, PARENT>::Linker( evtSvc(), 0, name );
-    return &m_linkerTable;
-  } else {
-    debug() << "Linker table " << name << " found" << endmsg;
-  }
-  return nullptr;
-}
 template <typename SOURCE, typename PARENT>
 bool Object2MCLinker<SOURCE, PARENT>::checkAssociation( const SOURCE* obj, const LHCb::MCParticle* mcPart ) {
   auto r = range( obj );
diff --git a/CaloFuture/CaloFutureUtils/include/CaloFutureUtils/CaloFuture2MC.h b/CaloFuture/CaloFutureUtils/include/CaloFutureUtils/CaloFuture2MC.h
index 12cdfffbd13..1068f57f969 100644
--- a/CaloFuture/CaloFutureUtils/include/CaloFutureUtils/CaloFuture2MC.h
+++ b/CaloFuture/CaloFutureUtils/include/CaloFutureUtils/CaloFuture2MC.h
@@ -22,10 +22,6 @@ namespace LHCb {
 
 template <class FROM, class TO, class WEIGHT>
 class IRelationWeighted;
-template <class FROM, class TO>
-class LinkerWithKey;
-template <class FROM>
-class LinkedTo;
 namespace LHCb {
   template <class FROM, class TO, class WEIGHT>
   class RelationWeighted1D;
@@ -53,8 +49,4 @@ namespace LHCb::CaloFuture2MC {
   using IHypoTable = IRelationWeighted<LHCb::Detector::Calo::CellID, LHCb::MCParticle, float>;
   using HypoTable  = RelationWeighted1D<LHCb::Detector::Calo::CellID, LHCb::MCParticle, float>;
 
-  using DigitLink = LinkerWithKey<LHCb::MCParticle, LHCb::CaloDigit>;
-
-  using HypoLink = LinkerWithKey<LHCb::MCParticle, LHCb::CaloHypo>;
-
 } // namespace LHCb::CaloFuture2MC
diff --git a/Event/LinkerEvent/include/Event/LinksByKey.h b/Event/LinkerEvent/include/Event/LinksByKey.h
index 44f36a82251..2a6bece82ff 100644
--- a/Event/LinkerEvent/include/Event/LinksByKey.h
+++ b/Event/LinkerEvent/include/Event/LinksByKey.h
@@ -16,10 +16,12 @@
 #include "Kernel/STLExtensions.h"
 
 #include "GaudiKernel/DataObject.h"
+#include "GaudiKernel/GaudiException.h"
 #include "GaudiKernel/IDataProviderSvc.h"
+#include "GaudiKernel/IRegistry.h"
 #include "GaudiKernel/KeyedObject.h"
-#include "GaudiKernel/SerializeSTL.h"
 
+#include <fmt/format.h>
 #include <ostream>
 #include <utility>
 #include <vector>
@@ -45,12 +47,33 @@ namespace LHCb {
 
   class LinksByKey final : public DataObject {
   public:
+    enum struct Order { increasingWeight, decreasingWeight };
+
     static std::string linkerName( const std::string& containerName ) {
       return "Link/" + ( "/Event/" == containerName.substr( 0, 7 ) ? containerName.substr( 7 ) : containerName );
     }
+    static std::string inverseLinkerName( std::string const& containerName ) {
+      return linkerName( containerName ) + "Inv";
+    }
+
+    /// Default constructor, reserve space (needed for ROOT I/O streaming unfortunately)
+    /* [[deprecated]] */ LinksByKey() {
+      m_keyIndex.reserve( 1000 );
+      m_linkReference.reserve( 1000 );
+    }
+
+    template <typename SOURCE, typename TARGET, typename = std::enable_if_t<!std::is_void_v<SOURCE>>>
+    LinksByKey( std::in_place_type_t<SOURCE>, std::in_place_type_t<TARGET>, Order order = Order::decreasingWeight )
+        : m_increasing{order == Order::increasingWeight}
+        , m_sourceClassID{SOURCE::classID()}
+        , m_targetClassID{TARGET::classID()} {
+      m_keyIndex.reserve( 1000 );
+      m_linkReference.reserve( 1000 );
+    }
 
-    /// Default constructor, reserve space
-    LinksByKey() {
+    template <typename TARGET>
+    LinksByKey( std::in_place_type_t<void>, std::in_place_type_t<TARGET>, Order order = Order::decreasingWeight )
+        : m_increasing{order == Order::increasingWeight}, m_targetClassID{TARGET::classID()} {
       m_keyIndex.reserve( 1000 );
       m_linkReference.reserve( 1000 );
     }
@@ -62,17 +85,8 @@ namespace LHCb {
     /// Fill the ASCII output stream
     std::ostream& fillStream( std::ostream& s ) const override;
 
-    /// Define the increasing order for the references
-    LinksByKey& setIncreasing() {
-      m_increasing = true;
-      return *this;
-    }
-
-    /// Define the decreasing order for the references
-    LinksByKey& setDecreasing() {
-      m_increasing = false;
-      return *this;
-    }
+    /// Return the order of the references
+    Order order() const { return m_increasing ? Order::increasingWeight : Order::decreasingWeight; }
 
     /// Get the pointers to the linked containers
     void resolveLinks( IDataProviderSvc* eventSvc );
@@ -80,6 +94,21 @@ namespace LHCb {
     /// Add a reference to the specified key
     void addReference( int srcKey, int srcLinkID, int destKey, int destLinkID, double weight = 1. );
 
+    template <typename SOURCE, typename TARGET>
+    void link( const SOURCE* source, const TARGET* dest, double weight = 1. ) {
+      requireTargetID( TARGET::classID() );
+      requireSourceID( SOURCE::classID() );
+      if ( !source || !dest ) return;
+      addReference( source->index(), linkID( source->parent() ), dest->index(), linkID( dest->parent() ), weight );
+    }
+
+    template <typename TARGET>
+    void link( int key, const TARGET* dest, double weight = 1. ) {
+      requireTargetID( TARGET::classID() );
+      if ( !dest ) return;
+      addReference( key, -1, dest->index(), linkID( dest->parent() ), weight );
+    }
+
     /// Retrieve the first LinkReference for this key
     bool firstReference( int key, const DataObject* container, LHCb::LinkReference& reference ) const;
 
@@ -96,7 +125,7 @@ namespace LHCb {
     int nextSource( LHCb::LinkReference& reference, std::vector<std::pair<int, int>>::const_iterator& iter ) const;
 
     /// Returns the LinkManager linkID for this data object (Container)
-    int linkID( const DataObject* obj ) const;
+    int linkID( const DataObject* obj );
 
     /// Reset the content of the objects
     void reset() {
@@ -113,19 +142,25 @@ namespace LHCb {
     /// Retrieve const  Class ID of the source of the Link
     unsigned int sourceClassID() const { return m_sourceClassID; }
 
-    /// Update  Class ID of the source of the Link
-    LinksByKey& setSourceClassID( unsigned int value ) {
-      m_sourceClassID = value;
-      return *this;
+    void requireSourceID( const CLID& sourceID ) const {
+      if ( sourceClassID() != sourceID ) {
+        throw GaudiException(
+            fmt::format( "Incompatible SOURCE type for location {} : requested classID is : {} expected {}", location(),
+                         sourceID, sourceClassID() ),
+            "LinksByKey", StatusCode::FAILURE );
+      }
     }
 
     /// Retrieve const  Class ID of the target of the Link
     unsigned int targetClassID() const { return m_targetClassID; }
 
-    /// Update  Class ID of the target of the Link
-    LinksByKey& setTargetClassID( unsigned int value ) {
-      m_targetClassID = value;
-      return *this;
+    void requireTargetID( const CLID& targetID ) const {
+      if ( targetClassID() != targetID ) {
+        throw GaudiException(
+            fmt::format( "Incompatible TARGET type for location {} : Request classID is {} expected {}", location(),
+                         targetID, targetClassID() ),
+            "LinksByKey", StatusCode::FAILURE );
+      }
     }
 
     friend std::ostream& operator<<( std::ostream& str, const LinksByKey& obj ) { return obj.fillStream( str ); }
@@ -158,11 +193,10 @@ namespace LHCb {
       if ( int key; findIndex( srcIndex, key ) ) { internalApply( srcIndex, m_keyIndex[key].second, func ); }
     }
 
-  protected:
+  private:
     /// Returns the index of the key in m_keyIndex. True if key exist, else inserting position
     bool findIndex( int key, int& index ) const;
 
-  private:
     /**
      * Internal helper function applying the given function to all links of the
      * chain given by refIndex
@@ -170,6 +204,8 @@ namespace LHCb {
     template <typename Function>
     void internalApply( unsigned int srcIndex, int refIndex, Function&& func ) const;
 
+    std::string location() const { return registry() ? registry()->identifier() : "UnRegistered"; }
+
   private:
     bool                             m_increasing = false; ///< Type of ordering
     std::vector<std::pair<int, int>> m_keyIndex;           ///< List of linked objects
diff --git a/Event/LinkerEvent/include/Linker/AllLinks.h b/Event/LinkerEvent/include/Linker/AllLinks.h
deleted file mode 100644
index e47964f6ab2..00000000000
--- a/Event/LinkerEvent/include/Linker/AllLinks.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*****************************************************************************\
-* (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration      *
-*                                                                             *
-* This software is distributed under the terms of the GNU General Public      *
-* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
-*                                                                             *
-* In applying this licence, CERN does not waive the privileges and immunities *
-* granted to it by virtue of its status as an Intergovernmental Organization  *
-* or submit itself to any jurisdiction.                                       *
-\*****************************************************************************/
-#ifndef LINKER_ALLLINKS_H
-#define LINKER_ALLLINKS_H 1
-
-// STL
-#include <sstream>
-
-// Include files
-#include "Event/LinksByKey.h"
-#include "GaudiKernel/ContainedObject.h"
-#include "GaudiKernel/GaudiException.h"
-#include "GaudiKernel/IDataProviderSvc.h"
-#include "GaudiKernel/LinkManager.h"
-#include "GaudiKernel/ObjectContainerBase.h"
-#include "GaudiKernel/SmartDataPtr.h"
-#include "GaudiKernel/SmartIF.h"
-
-/** @class AllLinks AllLinks.h Linker/AllLinks.h
- *  Templated interface to the LinksByKey, direct version
- *
- *  @author Olivier Callot
- *  @date   2004-01-06
- */
-
-template <class TARGET, class SOURCE = ContainedObject>
-class AllLinks final {
-public:
-  /// Standard constructor
-  AllLinks( IDataProviderSvc* eventSvc, IMessageSvc* msgSvc, std::string containerName ) {
-    m_eventSvc       = eventSvc;
-    std::string name = "Link/" + containerName;
-    if ( containerName.compare( 0, 7, "/Event/" ) == 0 ) { name = "Link/" + containerName.substr( 7 ); }
-    SmartDataPtr<LHCb::LinksByKey> links( eventSvc, name );
-    if ( !links ) {
-      if ( msgSvc ) {
-        MsgStream msg( msgSvc, "AllLinks::" + containerName );
-        msg << MSG::ERROR << "*** Link container " << name << " not found." << endmsg;
-      }
-    } else {
-      //== Check proper template, only if specified.
-      if ( links->sourceClassID() != SOURCE::classID() && CLID_ContainedObject != SOURCE::classID() ) {
-        std::ostringstream message;
-        message << "Incompatible SOURCE type for location " << containerName << " : Template classID is "
-                << SOURCE::classID() << " expected " << links->sourceClassID();
-        throw GaudiException( message.str(), "AllLinks", StatusCode::FAILURE );
-      }
-      if ( links->targetClassID() != TARGET::classID() ) {
-        std::ostringstream message;
-        message << "Incompatible TARGET type for location " << containerName << " : Template classID is "
-                << TARGET::classID() << " expected " << links->targetClassID();
-        throw GaudiException( message.str(), "AllLinks", StatusCode::FAILURE );
-      }
-    }
-    m_links = links;
-    m_curReference.setNextIndex( -1 );
-    m_curReference.setWeight( 0. );
-  };
-
-  bool notFound() const { return !m_links; }
-
-  TARGET* first() {
-    if ( !m_links ) return nullptr;
-    m_iter = m_links->keyIndex().begin();
-    if ( m_links->keyIndex().end() == m_iter ) return nullptr;
-    m_curReference.setNextIndex( ( *m_iter ).second );
-    return next();
-  }
-
-  TARGET* next() {
-    if ( !m_links ) return nullptr;
-    int nn = m_curReference.nextIndex();
-    while ( 0 > nn ) {
-      if ( m_links->keyIndex().end() == m_iter ) return nullptr;
-      m_iter++;
-      if ( m_links->keyIndex().end() == m_iter ) return nullptr;
-      nn = ( *m_iter ).second;
-    }
-    m_curReference = m_links->linkReference()[nn];
-    return currentTarget();
-  }
-
-  double  weight() { return m_curReference.weight(); }
-  int     key() { return ( *m_iter ).first; }
-  SOURCE* source() { return currentSource( ( *m_iter ).first ); }
-
-private:
-  TARGET* currentTarget() {
-    if ( !m_links ) return nullptr;
-    int                myLinkID = m_curReference.linkID();
-    LinkManager::Link* link     = m_links->linkMgr()->link( myLinkID );
-    if ( 0 == link->object() ) {
-      SmartDataPtr<DataObject> tmp( m_eventSvc.get(), link->path() );
-      link->setObject( tmp );
-      if ( !tmp ) return nullptr;
-    }
-    auto* parent = dynamic_cast<ObjectContainerBase*>( link->object() );
-    return parent ? static_cast<TARGET*>( parent->containedObject( m_curReference.objectKey() ) ) : nullptr;
-  }
-
-  SOURCE* currentSource( int key ) {
-    if ( !m_links ) return nullptr;
-    int myLinkID = m_curReference.srcLinkID();
-    if ( 0 > myLinkID ) return nullptr;
-    LinkManager::Link* link = m_links->linkMgr()->link( myLinkID );
-    if ( !link->object() ) {
-      SmartDataPtr<DataObject> tmp( m_eventSvc.get(), link->path() );
-      link->setObject( tmp );
-      if ( !tmp ) return nullptr;
-    }
-    auto* parent = dynamic_cast<ObjectContainerBase*>( link->object() );
-    return parent ? static_cast<SOURCE*>( parent->containedObject( key ) ) : nullptr;
-  }
-
-  SmartIF<IDataProviderSvc>                        m_eventSvc;
-  LHCb::LinksByKey*                                m_links;
-  LHCb::LinkReference                              m_curReference;
-  std::vector<std::pair<int, int>>::const_iterator m_iter;
-};
-#endif // LINKER_ALLLINKS_H
diff --git a/Event/LinkerEvent/include/Linker/LinkedFrom.h b/Event/LinkerEvent/include/Linker/LinkedFrom.h
index bcbbbcd778e..c14b9f7995b 100644
--- a/Event/LinkerEvent/include/Linker/LinkedFrom.h
+++ b/Event/LinkerEvent/include/Linker/LinkedFrom.h
@@ -42,7 +42,7 @@ class LinkedFrom final {
       std::vector<std::pair<int, int>>::const_iterator m_srcIterator;
       long                                             m_wantedKey = -1;
 
-      SOURCE const* currentSource( int index ) {
+      SOURCE const* source( int index ) {
         assert( m_links );
         LinkManager::Link const* link = m_links->linkMgr()->link( m_curReference.srcLinkID() );
         if ( !link->object() ) {
@@ -53,7 +53,7 @@ class LinkedFrom final {
         return container ? static_cast<SOURCE const*>( container->containedObject( index ) ) : nullptr;
       }
       Iterator( LHCb::LinksByKey const* links, ContainedObject const* target )
-          : m_links{links}, m_wantedKey{target->index()} {
+          : m_links{links}, m_wantedKey{target ? target->index() : -1} {
         if ( !m_links ) return;
         //== check that the target's container is known.
         const DataObject*        container = target->parent();
@@ -68,7 +68,7 @@ class LinkedFrom final {
         m_curReference.setLinkID( short( link->ID() ) );
         m_curReference.setObjectKey( m_wantedKey );
         int index = m_links->firstSource( m_curReference, m_srcIterator );
-        m_current = ( m_wantedKey == m_curReference.objectKey() ? currentSource( index ) : nullptr );
+        m_current = ( m_wantedKey == m_curReference.objectKey() ? source( index ) : nullptr );
       }
 
     public:
@@ -82,7 +82,7 @@ class LinkedFrom final {
       Iterator& operator++() {
         if ( m_current ) {
           int index = m_links->nextSource( m_curReference, m_srcIterator );
-          m_current = ( m_wantedKey == m_curReference.objectKey() ? currentSource( index ) : nullptr );
+          m_current = ( m_wantedKey == m_curReference.objectKey() ? source( index ) : nullptr );
         }
         return *this;
       }
@@ -147,8 +147,9 @@ private:
     }
     if ( m_links->sourceClassID() != SOURCE::classID() && CLID_ContainedObject != SOURCE::classID() ) {
       throw GaudiException(
-          fmt::format( "Incompatible SOURCE type for location {} : Template classID is {} expected {}",
-                       m_links->registry()->identifier(), SOURCE::classID(), m_links->sourceClassID() ),
+          fmt::format( "Incompatible SOURCE type for location {} @ {} : Template classID is {} expected {}",
+                       m_links->registry()->identifier(), (const void*)m_links, SOURCE::classID(),
+                       m_links->sourceClassID() ),
           "LinkedFrom", StatusCode::FAILURE );
     }
   }
@@ -164,4 +165,5 @@ private:
 
   LHCb::LinksByKey const* m_links;
 };
+
 #endif // LINKER_LINKEDFROM_H
diff --git a/Event/LinkerEvent/include/Linker/LinkedFromKey.h b/Event/LinkerEvent/include/Linker/LinkedFromKey.h
deleted file mode 100644
index d643cd42e66..00000000000
--- a/Event/LinkerEvent/include/Linker/LinkedFromKey.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*****************************************************************************\
-* (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration      *
-*                                                                             *
-* This software is distributed under the terms of the GNU General Public      *
-* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
-*                                                                             *
-* In applying this licence, CERN does not waive the privileges and immunities *
-* granted to it by virtue of its status as an Intergovernmental Organization  *
-* or submit itself to any jurisdiction.                                       *
-\*****************************************************************************/
-#ifndef LINKER_LINKEDFROMKEY_H
-#define LINKER_LINKEDFROMKEY_H 1
-
-// Include files
-#include "Linker/LinkedFrom.h"
-
-/** @class LinkedFromKey LinkedFromKey.h Linker/LinkedFromKey.h
- *  Simple helper class to handle LinkedFrom with no source, i.e. key linker
- *
- *  @author Olivier Callot
- *  @date   2008-03-14
- */
-
-template <class TARGET, class KEY = int>
-using LinkedFromKey = LinkedFrom<ContainedObject, TARGET, KEY>;
-
-#endif // LINKER_LINKEDFROMKEY_H
diff --git a/Event/LinkerEvent/include/Linker/LinkerEntry.h b/Event/LinkerEvent/include/Linker/LinkerEntry.h
deleted file mode 100644
index 0514c2c3afd..00000000000
--- a/Event/LinkerEvent/include/Linker/LinkerEntry.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************************\
-* (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration      *
-*                                                                             *
-* This software is distributed under the terms of the GNU General Public      *
-* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
-*                                                                             *
-* In applying this licence, CERN does not waive the privileges and immunities *
-* granted to it by virtue of its status as an Intergovernmental Organization  *
-* or submit itself to any jurisdiction.                                       *
-\*****************************************************************************/
-#ifndef LINKER_LINKERENTRY_H
-#define LINKER_LINKERENTRY_H 1
-
-/** @class LinkerEntry LinkerEntry.h Linker/LinkerEntry.h
- *  Describe an entry of a relation, expanded to pointer
- *
- *  @author Olivier Callot
- *  @date   2005-01-19
- */
-template <class SOURCE, class TARGET>
-class LinkerEntry final {
-public:
-  /** constructor, with the needed information
-   *  @param src     source of the relation
-   *  @param tgt     target of the relation
-   *  @param weight  weight of the relation
-   */
-  LinkerEntry( const SOURCE* src, const TARGET* tgt, double weight ) {
-    m_src    = src;
-    m_target = tgt;
-    m_weight = weight;
-  };
-
-  /** accessor to the source
-   *  @return   the source information
-   */
-  const SOURCE* from() const { return m_src; }
-
-  /** accessor to the target
-   *  @return   the target information
-   */
-  const TARGET* to() const { return m_target; }
-
-  /** accessor to the weight
-   *  @return   the weight information
-   */
-  double weight() const { return m_weight; }
-
-private:
-  const SOURCE* m_src;
-  const TARGET* m_target;
-  double        m_weight;
-};
-#endif // LINKER_LINKERENTRY_H
diff --git a/Event/LinkerEvent/include/Linker/LinkerRange.h b/Event/LinkerEvent/include/Linker/LinkerRange.h
deleted file mode 100644
index 228467e2300..00000000000
--- a/Event/LinkerEvent/include/Linker/LinkerRange.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*****************************************************************************\
-* (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration      *
-*                                                                             *
-* This software is distributed under the terms of the GNU General Public      *
-* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
-*                                                                             *
-* In applying this licence, CERN does not waive the privileges and immunities *
-* granted to it by virtue of its status as an Intergovernmental Organization  *
-* or submit itself to any jurisdiction.                                       *
-\*****************************************************************************/
-#ifndef LINKER_LINKERRANGE_H
-#define LINKER_LINKERRANGE_H 1
-
-// Include files
-#include "Linker/LinkerEntry.h"
-#include <vector>
-
-/** @class LinkerRange LinkerRange.h Linker/LinkerRange.h
- *  Holds a range, a collection of LinkerEntry
- *
- *  @author Olivier Callot
- *  @date   2005-01-19
- */
-
-template <class SOURCE, class TARGET>
-class LinkerRange final {
-public:
-  typedef typename std::vector<LinkerEntry<SOURCE, TARGET>>::const_iterator iterator;
-
-  /** Add a LinkerEntry inside the range
-   *  @param src    pointer to the SOURCE of the entry
-   *  @param tgt    pointer to the TARGET of the entry
-   *  @param weight Weight of the relation
-   */
-  void addEntry( const SOURCE* src, const TARGET* tgt, double weight ) { emplace_back( src, tgt, weight ); }
-
-  template <typename... Args>
-  void emplace_back( Args&&... args ) {
-    m_entries.emplace_back( std::forward<Args>( args )... );
-  }
-
-  void push_back( const LinkerEntry<SOURCE, TARGET>& le ) { m_entries.push_back( le ); }
-
-  void push_back( LinkerEntry<SOURCE, TARGET>&& le ) { m_entries.push_back( std::move( le ) ); }
-
-  /** returns an iterator to the beginning of the table of entries = range
-   *  @return  iterator
-   */
-  iterator begin() const { return m_entries.begin(); }
-
-  /** returns an iterator to the end of the table of entries = range
-   *  @return  iterator
-   */
-  iterator end() const { return m_entries.end(); }
-
-  /** returns the size of the range, the number of entries
-   *  @return  size
-   */
-  int size() const { return m_entries.size(); }
-
-  /** returns true if the range is empty
-   *  @return  is range empty ?
-   */
-  bool empty() const { return m_entries.empty(); }
-
-  /** clear the range, remove all entries
-   */
-  void clear() { m_entries.clear(); }
-
-  /** returns a reference to the first element
-   */
-  const LinkerEntry<SOURCE, TARGET>& front() const { return m_entries.front(); }
-
-  /** returns a reference to the last element
-   */
-  const LinkerEntry<SOURCE, TARGET>& back() const { return m_entries.back(); }
-
-private:
-  std::vector<LinkerEntry<SOURCE, TARGET>> m_entries;
-};
-#endif // LINKER_LINKERRANGE_H
diff --git a/Event/LinkerEvent/include/Linker/LinkerTable.h b/Event/LinkerEvent/include/Linker/LinkerTable.h
index f4bb0d7dccd..fcb59745b31 100644
--- a/Event/LinkerEvent/include/Linker/LinkerTable.h
+++ b/Event/LinkerEvent/include/Linker/LinkerTable.h
@@ -15,7 +15,6 @@
 #include "Event/LinksByKey.h"
 #include "GaudiKernel/LinkManager.h"
 #include "GaudiKernel/ObjectContainerBase.h"
-#include "Linker/LinkerRange.h"
 
 /** @class LinkerTable LinkerTable.h Linker/LinkerTable.h
  *  This is a table, the interface to build LinkerRange.
@@ -25,45 +24,132 @@
  */
 template <class SOURCE, class TARGET>
 class LinkerTable final {
-public:
-  typedef LinkerRange<SOURCE, TARGET>                    Range;
-  typedef typename LinkerRange<SOURCE, TARGET>::iterator iterator;
+  class Range final {
+    struct AlwaysTrue {
+      template <typename... Args>
+      [[nodiscard]] constexpr bool operator()( Args&&... ) const noexcept {
+        return true;
+      }
+    };
 
-  /** retrieve all relations in the table
-   *  @return       Range with all relations
-   */
-  Range relations() const {
-    Range range;
-    if ( !m_links ) return range;
-    for ( auto iter = m_links->keyIndex().begin(); m_links->keyIndex().end() != iter; iter++ ) {
+    /** return the pointer to the TARGET object of the specified reference
+     *  @param  curReference   reference entry to the object in the LinksByKeys
+     *  @return pointer to the TARGET object
+     */
+    static const TARGET* currentTarget( LHCb::LinksByKey const* links, LHCb::LinkReference const& curReference ) {
+      if ( !links ) return nullptr;
+      int                        myLinkID = curReference.linkID();
+      const LinkManager::Link*   link     = links->linkMgr()->link( myLinkID );
+      const ObjectContainerBase* parent   = dynamic_cast<const ObjectContainerBase*>( link->object() );
+      return parent ? static_cast<const TARGET*>( parent->containedObject( curReference.objectKey() ) ) : nullptr;
+    }
+
+    /** return the pointer to the SOURCE object of the specified reference
+     *  @param  key            key of the object. Its link is in the curReference argument
+     *  @param  curReference   reference entry to the object in the LinksByKeys
+     *  @return pointer to the SOURCE object
+     */
+    static const SOURCE* currentSource( LHCb::LinksByKey const* links, int key, LHCb::LinkReference curReference ) {
+      if ( !links ) return nullptr;
+      int myLinkID = curReference.srcLinkID();
+      if ( myLinkID < 0 ) return nullptr;
+      const LinkManager::Link*   link   = links->linkMgr()->link( myLinkID );
+      const ObjectContainerBase* parent = dynamic_cast<const ObjectContainerBase*>( link->object() );
+      return parent ? static_cast<const SOURCE*>( parent->containedObject( key ) ) : nullptr;
+    }
+
+    class LinkerEntry final {
+      const SOURCE* m_src;
+      const TARGET* m_target;
+      double        m_weight;
+
+    public:
+      /** constructor, with the needed information
+       *  @param src     source of the relation
+       *  @param tgt     target of the relation
+       *  @param weight  weight of the relation
+       */
+      LinkerEntry( const SOURCE* src, const TARGET* tgt, double weight ) {
+        m_src    = src;
+        m_target = tgt;
+        m_weight = weight;
+      };
+
+      /** accessor to the source
+       *  @return   the source
+       */
+      const SOURCE* from() const { return m_src; }
+
+      /** accessor to the target
+       *  @return   the target
+       */
+      const TARGET* to() const { return m_target; }
+
+      /** accessor to the weight
+       *  @return   the weight
+       */
+      double weight() const { return m_weight; }
+    };
+
+    std::vector<LinkerEntry> m_entries;
+
+  public:
+    Range( const LHCb::LinksByKey* links ) {
+      if ( !links ) return;
+      for ( auto const [source, index] : links->keyIndex() ) {
+        LHCb::LinkReference curReference;
+        curReference.setNextIndex( index );
+        while ( 0 <= curReference.nextIndex() ) {
+          curReference      = links->linkReference()[curReference.nextIndex()];
+          const SOURCE* src = currentSource( links, source, curReference );
+          if ( src ) { // ignore int links
+            m_entries.emplace_back( src, currentTarget( links, curReference ), curReference.weight() );
+          }
+        }
+      }
+    }
+
+    template <typename Predicate = AlwaysTrue>
+    Range( const LHCb::LinksByKey* links, const SOURCE* reqSrc, Predicate predicate = {} ) {
+      if ( !links || !reqSrc ) return;
       LHCb::LinkReference curReference;
-      curReference.setNextIndex( ( *iter ).second );
-      while ( 0 <= curReference.nextIndex() ) {
-        curReference      = m_links->linkReference()[curReference.nextIndex()];
-        const SOURCE* src = currentSource( iter->first, curReference );
-        if ( src ) { // ignore int links
-          range.addEntry( src, currentTarget( curReference ), curReference.weight() );
+      bool                status = links->firstReference( reqSrc->index(), reqSrc->parent(), curReference );
+      while ( status ) {
+        if ( predicate( curReference ) ) {
+          m_entries.emplace_back( reqSrc, currentTarget( links, curReference ), curReference.weight() );
         }
+        status = links->nextReference( curReference );
       }
     }
-    return range;
+
+    auto               begin() const { return m_entries.begin(); }
+    auto               end() const { return m_entries.end(); }
+    int                size() const { return m_entries.size(); }
+    bool               empty() const { return m_entries.empty(); }
+    const LinkerEntry& front() const { return m_entries.front(); }
+    const LinkerEntry& back() const { return m_entries.back(); }
   };
 
+public:
+  LinkerTable( const LHCb::LinksByKey* links ) : m_links{links} {
+    if ( links ) {
+      links->requireSourceID( SOURCE::classID() );
+      links->requireTargetID( TARGET::classID() );
+    }
+  }
+
+  explicit operator bool() const { return m_links != nullptr; }
+
+  /** retrieve all relations in the table
+   *  @return       Range with all relations
+   */
+  Range relations() const { return Range{m_links}; };
+
   /** Returns all the relations in the table for a given object.
    *  @param  reqSrc     Object for which the relations is wanted
       @return Range of relations
    */
-  Range relations( const SOURCE* reqSrc ) const {
-    Range range;
-    if ( !m_links || !reqSrc ) return range;
-    LHCb::LinkReference curReference;
-    bool                status = m_links->firstReference( reqSrc->index(), reqSrc->parent(), curReference );
-    while ( status ) {
-      range.addEntry( reqSrc, currentTarget( curReference ), curReference.weight() );
-      status = m_links->nextReference( curReference );
-    }
-    return range;
-  };
+  Range relations( const SOURCE* reqSrc ) const { return Range{m_links, reqSrc}; };
 
   /** Returns the relations with selected weight in the table for a given object.
    *  @param  reqSrc  Object for which the relations is wanted
@@ -72,52 +158,9 @@ public:
    *  @return Range of selected relations
    */
   Range relations( const SOURCE* reqSrc, double cut, bool order ) const {
-    Range range;
-    if ( !m_links ) return range;
-    if ( !reqSrc ) return range;
-    LHCb::LinkReference curReference;
-    bool                status = m_links->firstReference( reqSrc->index(), reqSrc->parent(), curReference );
-    while ( status ) {
-      double weight = curReference.weight();
-      if ( ( order && weight >= cut ) || ( !order && weight <= cut ) ) {
-        TARGET* tgt = currentTarget( curReference );
-        range.addEntry( reqSrc, tgt, weight );
-      }
-      status = m_links->nextReference( curReference );
-    }
-    return range;
-  };
-
-  /** load the LinksByKeys object
-   *  @param links  pointer to the LinksByKey object
-   */
-  void load( const LHCb::LinksByKey* links ) { m_links = links; }
-
-protected:
-  /** return the pointer to the TARGET object of the specified reference
-   *  @param  curReference   reference entry to the object in the LinksByKeys
-   *  @return pointer to the TARGET object
-   */
-  const TARGET* currentTarget( LHCb::LinkReference& curReference ) const {
-    if ( !m_links ) return nullptr;
-    int                        myLinkID = curReference.linkID();
-    const LinkManager::Link*   link     = m_links->linkMgr()->link( myLinkID );
-    const ObjectContainerBase* parent   = dynamic_cast<const ObjectContainerBase*>( link->object() );
-    return parent ? static_cast<const TARGET*>( parent->containedObject( curReference.objectKey() ) ) : nullptr;
-  }
-
-  /** return the pointer to the SOURCE object of the specified reference
-   *  @param  key            key of the object. Its link is in the curReference argument
-   *  @param  curReference   reference entry to the object in the LinksByKeys
-   *  @return pointer to the SOURCE object
-   */
-  const SOURCE* currentSource( int key, LHCb::LinkReference curReference ) const {
-    if ( !m_links ) return nullptr;
-    int myLinkID = curReference.srcLinkID();
-    if ( 0 > myLinkID ) return nullptr;
-    const LinkManager::Link*   link   = m_links->linkMgr()->link( myLinkID );
-    const ObjectContainerBase* parent = dynamic_cast<const ObjectContainerBase*>( link->object() );
-    return parent ? static_cast<const SOURCE*>( parent->containedObject( key ) ) : nullptr;
+    return Range{m_links, reqSrc, [cut, order]( const LHCb::LinkReference& r ) {
+                   return order ? ( r.weight() >= cut ) : ( r.weight() <= cut );
+                 }};
   }
 
 private:
diff --git a/Event/LinkerEvent/include/Linker/LinkerTool.h b/Event/LinkerEvent/include/Linker/LinkerTool.h
index 4342b2865e9..9bbb4d97beb 100644
--- a/Event/LinkerEvent/include/Linker/LinkerTool.h
+++ b/Event/LinkerEvent/include/Linker/LinkerTool.h
@@ -10,21 +10,11 @@
 \*****************************************************************************/
 #ifndef LINKER_LINKERTOOL_H
 #define LINKER_LINKERTOOL_H 1
-
-// STL
-#include <sstream>
-
-// Include files
-#include "GaudiKernel/GaudiException.h"
+#include "Event/LinksByKey.h"
 #include "GaudiKernel/IDataProviderSvc.h"
-#include "GaudiKernel/LinkManager.h"
-#include "GaudiKernel/ObjectContainerBase.h"
+#include "GaudiKernel/SmartDataPtr.h"
 #include "GaudiKernel/SmartIF.h"
-
-#include "Event/LinksByKey.h"
-
 #include "Linker/LinkerTable.h"
-#include "Linker/LinkerWithKey.h"
 
 /** @class LinkerTool LinkerTool.h Linker/LinkerTool.h
  *  Mimic the relation tool as much as possible
@@ -36,95 +26,50 @@ template <class SOURCE, class TARGET>
 class LinkerTool final {
 
 public:
-  typedef LinkerTable<SOURCE, TARGET>   DirectType;
-  typedef LinkerTable<TARGET, SOURCE>   InverseType;
-  typedef typename DirectType::Range    Range;
-  typedef typename DirectType::iterator iterator;
+  using DirectType  = LinkerTable<SOURCE, TARGET>;
+  using InverseType = LinkerTable<TARGET, SOURCE>;
 
   /** Standard constructor
    *  @param  svc             event service
    *  @param  containerName   name of the container of links.
    */
-  LinkerTool( IDataProviderSvc* svc, std::string containerName ) {
-    m_evtSvc         = svc;
-    std::string name = "Link/" + containerName;
-    if ( "/Event/" == containerName.substr( 0, 7 ) ) { name = "Link/" + containerName.substr( 7 ); }
-    m_location    = name;
-    m_invLocation = name + "Inv";
-  };
+  LinkerTool( IDataProviderSvc* svc, std::string containerName )
+      : m_evtSvc{svc}
+      , m_location{LHCb::LinksByKey::linkerName( containerName )}
+      , m_invLocation{LHCb::LinksByKey::inverseLinkerName( containerName )} {}
 
   /** retrieve the direct relation
    *  @return     The direct table of relation.
    */
-
-  DirectType* direct() {
+  DirectType direct() const {
     SmartDataPtr<LHCb::LinksByKey> links( m_evtSvc.get(), m_location );
-    if ( links ) {
-      links->resolveLinks( m_evtSvc.get() );
-
-      if ( links->sourceClassID() != SOURCE::classID() ) {
-        std::ostringstream message;
-        message << "Incompatible SOURCE type for location " << m_location
-                << " : Template classID is : " << SOURCE::classID() << " expected " << links->sourceClassID();
-        throw GaudiException( message.str(), "LinkerTool", StatusCode::FAILURE );
-      }
-      if ( links->targetClassID() != TARGET::classID() ) {
-        std::ostringstream message;
-        message << "Incompatible TARGET type for location " << m_location << " : Template classID is "
-                << TARGET::classID() << " expected " << links->targetClassID();
-        throw GaudiException( message.str(), "LinkerTool", StatusCode::FAILURE );
-      }
-    }
-
-    const LHCb::LinksByKey* linkPtr = links;
-    m_table.load( linkPtr );
-    return linkPtr ? &m_table : nullptr;
+    if ( links ) links->resolveLinks( m_evtSvc.get() );
+    return {links};
   }
 
   /** retrieve the inverse relation, build it if not yet done
    *  @return     The inverse table of relation.
    */
 
-  InverseType* inverse() {
+  InverseType inverse() const {
     SmartDataPtr<LHCb::LinksByKey> links( m_evtSvc.get(), m_invLocation );
-    LHCb::LinksByKey*              linkPtr = links;
-    if ( 0 == linkPtr ) {
+    if ( !links ) {
       //== Invert the table...
-      const DirectType* tmp = direct();
-      if ( 0 != tmp ) {
-        // Create, with name shortened to remove the "Link/" prefix
-        LinkerWithKey<SOURCE, TARGET> makeLink( m_evtSvc.get(), 0, m_invLocation.substr( 5 ) );
-        Range                         rd = tmp->relations();
-        for ( iterator it = rd.begin(); rd.end() != it; ++it ) { makeLink.link( it->to(), it->from(), it->weight() ); }
-        SmartDataPtr<LHCb::LinksByKey> newLinks( m_evtSvc.get(), m_invLocation );
-        linkPtr = newLinks;
+      if ( const auto tmp = direct(); tmp ) {
+        links         = new LHCb::LinksByKey{std::in_place_type<TARGET>, std::in_place_type<SOURCE>,
+                                     LHCb::LinksByKey::Order::decreasingWeight};
+        StatusCode sc = m_evtSvc->registerObject( m_invLocation, links );
+        if ( !sc ) { throw GaudiException( "Failed to register " + m_invLocation, "LinkerTool::inverse", sc ); }
+        for ( auto const& r : tmp.relations() ) links->link( r.to(), r.from(), r.weight() );
       }
     }
-    if ( linkPtr ) linkPtr->resolveLinks( m_evtSvc.get() );
-    m_invTable.load( linkPtr );
-    if ( !linkPtr ) return nullptr;
-
-    //== TARGET and SOURCE are exchanged for the inverse table
-    if ( linkPtr->targetClassID() != SOURCE::classID() ) {
-      throw GaudiException( "Incompatible SOURCE type for location " + m_location + " : Template classID is " +
-                                std::to_string( SOURCE::classID() ) + " expected " +
-                                std::to_string( links->targetClassID() ),
-                            "LinkerTool", StatusCode::FAILURE );
-    }
-    if ( linkPtr->sourceClassID() != TARGET::classID() ) {
-      throw GaudiException( "Incompatible TARGET type for location " + m_location + " : Template classID is " +
-                                std::to_string( TARGET::classID() ) + " expected " +
-                                std::to_string( links->sourceClassID() ),
-                            "LinkerTool", StatusCode::FAILURE );
-    }
-    return &m_invTable;
+    if ( links ) links->resolveLinks( m_evtSvc.get() );
+    return {links};
   }
 
 private:
   SmartIF<IDataProviderSvc> m_evtSvc;
   std::string               m_location;
   std::string               m_invLocation;
-  DirectType                m_table;
-  InverseType               m_invTable;
 };
 #endif // LINKER_LINKERTOOL_H
diff --git a/Event/LinkerEvent/include/Linker/LinkerWithKey.h b/Event/LinkerEvent/include/Linker/LinkerWithKey.h
index cd3a891d365..1a9be3b4ae0 100644
--- a/Event/LinkerEvent/include/Linker/LinkerWithKey.h
+++ b/Event/LinkerEvent/include/Linker/LinkerWithKey.h
@@ -8,15 +8,11 @@
 * granted to it by virtue of its status as an Intergovernmental Organization  *
 * or submit itself to any jurisdiction.                                       *
 \*****************************************************************************/
-#ifndef LINKER_LINKERWITHKEY_H
-#define LINKER_LINKERWITHKEY_H 1
-
-// Include files
+#pragma once
 #include "Event/LinksByKey.h"
 #include "GaudiKernel/ContainedObject.h"
+#include "GaudiKernel/GaudiException.h"
 #include "GaudiKernel/IDataProviderSvc.h"
-#include "GaudiKernel/IMessageSvc.h"
-#include "GaudiKernel/MsgStream.h"
 #include "GaudiKernel/SmartDataPtr.h"
 
 /** @class LinkerWithKey LinkerWithKey.h Linker/LinkerWithKey.h
@@ -25,64 +21,35 @@
  *  @author Olivier Callot
  *  @date   2004-01-06
  */
-template <class TARGET, class SOURCE = ContainedObject>
-class LinkerWithKey final {
-public:
-  /// Standard constructor
-  LinkerWithKey( IDataProviderSvc* evtSvc, IMessageSvc* msgSvc, std::string containerName ) : m_links( nullptr ) {
-    if ( !containerName.empty() ) {
-      std::string name = "Link/" + containerName;
-      if ( "/Event/" == containerName.substr( 0, 7 ) ) { name = "Link/" + containerName.substr( 7 ); }
 
-      //== If it exists, just append to it.
+template <class TARGET, class SOURCE = ContainedObject>
+struct LinkerWithKey final {
 
-      SmartDataPtr<LHCb::LinksByKey> links( evtSvc, name );
-      if ( links ) {
-        m_links = links;
-      } else {
-        m_links       = new LHCb::LinksByKey();
-        StatusCode sc = evtSvc->registerObject( name, m_links );
-        if ( !sc && msgSvc ) {
-          MsgStream msg( msgSvc, "LinkerWithKey::" + containerName );
-          msg << MSG::ERROR << "*** Link container " << name << " cannot be registered, Status " << sc << endmsg;
-        }
+  static LHCb::LinksByKey* create( IDataProviderSvc* evtSvc, std::string containerName,
+                                   LHCb::LinksByKey::Order order ) {
+    LHCb::LinksByKey* lbk = nullptr;
+    if ( containerName.empty() ) {
+      throw GaudiException{"Empty containerName", "LinkerWithKey::create", StatusCode::FAILURE};
+    }
+    std::string name = LHCb::LinksByKey::linkerName( containerName );
+
+    //== If it exists, just append to it.
+    SmartDataPtr<LHCb::LinksByKey> links( evtSvc, name );
+    if ( links ) {
+      lbk = links;
+      lbk->requireSourceID( SOURCE::classID() );
+      lbk->requireTargetID( TARGET::classID() );
+      if ( lbk->order() != order ) {
+        throw GaudiException( "Invalid LinkerWithKey: inconsistent prior ordering;", "LinkerWithKey::LinkerWithKey",
+                              StatusCode::FAILURE );
       }
-
-      m_links->setTargetClassID( TARGET::classID() );
-      m_links->setSourceClassID( SOURCE::classID() );
+    } else {
+      lbk           = new LHCb::LinksByKey{std::in_place_type<SOURCE>, std::in_place_type<TARGET>,
+                                 LHCb::LinksByKey::Order::decreasingWeight};
+      StatusCode sc = evtSvc->registerObject( name, lbk );
+      if ( !sc ) { throw GaudiException( "Failed to register " + name, "LinkerWithKey::LinkerWithKey", sc ); }
     }
-  };
 
-  void reset() { m_links->reset(); }
-
-  void link( const SOURCE* source, const TARGET* dest, double weight = 1. ) {
-    if ( !source ) return;
-    if ( !dest ) return;
-    if ( !m_links ) return;
-    int srcIndex   = source->index();
-    int srcLinkID  = m_links->linkID( source->parent() );
-    int destIndex  = dest->index();
-    int destLinkID = m_links->linkID( dest->parent() );
-
-    m_links->addReference( srcIndex, srcLinkID, destIndex, destLinkID, weight );
-  }
-
-  void link( int key, const TARGET* dest, double weight = 1. ) {
-    if ( !dest ) return;
-    if ( !m_links ) return;
-    int destIndex  = dest->index();
-    int destLinkID = m_links->linkID( dest->parent() );
-    m_links->addReference( key, -1, destIndex, destLinkID, weight );
-  }
-
-  void setIncreasingWeight() {
-    if ( m_links ) m_links->setIncreasing();
-  }
-  void setDecreasingWeight() {
-    if ( m_links ) m_links->setDecreasing();
-  }
-
-private:
-  LHCb::LinksByKey* m_links;
+    return lbk;
+  };
 };
-#endif // LINKER_LINKERWITHKEYNEW_H
diff --git a/Event/LinkerEvent/src/LinksByKey.cpp b/Event/LinkerEvent/src/LinksByKey.cpp
index 7a6d273e82d..110e2729ece 100644
--- a/Event/LinkerEvent/src/LinksByKey.cpp
+++ b/Event/LinkerEvent/src/LinksByKey.cpp
@@ -47,6 +47,8 @@ void LHCb::LinksByKey::addReference( int srcKey, int srcLinkID, int destKey, int
 
   int indx;
   if ( !findIndex( srcKey, indx ) ) {
+    // TODO: use lower_bound to find right place, then use that with insert...
+    //      instead of doing this manually
     m_keyIndex.emplace_back( 0, 0 );
     int iL = m_keyIndex.size() - 1;
     while ( iL > indx ) {
@@ -223,11 +225,11 @@ int LHCb::LinksByKey::nextSource( LHCb::LinkReference&
 //=========================================================================
 //  Returns the ID in the link table of the given object
 //=========================================================================
-int LHCb::LinksByKey::linkID( const DataObject* obj ) const {
+int LHCb::LinksByKey::linkID( const DataObject* obj ) {
   int                      id;
   LinkManager::Link const* link = linkMgr()->link( obj );
   if ( !link ) {
-    id = const_cast<LinksByKey*>( this )->linkMgr()->addLink( obj->registry()->identifier(), obj );
+    id = linkMgr()->addLink( obj->registry()->identifier(), obj );
   } else {
     id = link->ID();
   }
-- 
GitLab