diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigBmumuxComboHypoConfig.py b/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigBmumuxComboHypoConfig.py
index 3746904608f277e6c24ca50ae3d7acdd49c74904..2b1874949c0ad8f2721925f4851942896de29769 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigBmumuxComboHypoConfig.py
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigBmumuxComboHypoConfig.py
@@ -80,17 +80,23 @@ def BmumuxComboHypoCfg(name):
         LambdaBToMuMuProtonKaon_dimuonMassRange = (2500., 4300.),
         LambdaBToMuMuProtonKaon_massRange = (4800., 6400.),
         LambdaBToMuMuProtonKaon_chi2 = 60.,
-        # Bc -> J/psi D+ (-> phi pi)
-        BcToDsMuMuPhiPi = True,
-        Bc_DsMuMuKaon_minKaonPt = 1000,
-        Bc_rangePhiDs_MassCut = (980, 1080),
-        Bc_rangeDs_MassCut  = (1600, 2400),
-        Bc_DsMuMu_chi2 = 60,
-        # Bc -> J/psi D+ (-> K pi pi)
-        BcToDpMuMuPhiPi = True,
-        Bc_DpMuMuKaon_minKaonPt = 1000,
-        Bc_rangeDp_MassCut = (1500, 2300),
-        Bc_DpMuMu_chi2  = 60,
+        # B_c+ -> J/psi(-> mu+ mu-) D_s+(->phi(-> K+ K-) pi+)
+        BcToDsMuMu = True,
+        BcToDsMuMu_minKaonPt = 1000.,
+        BcToDsMuMu_minPionPt = 1000.,
+        BcToDsMuMu_massRange = (5500., 7300.),
+        BcToDsMuMu_dimuonMassRange = (2500., 4300.),
+        BcToDsMuMu_phiMassRange = (940., 1100.),
+        BcToDsMuMu_DsMassRange = (1850., 2100.),
+        BcToDsMuMu_chi2 = 60.,
+        # B_c+ -> J/psi(-> mu+ mu-) D+(-> K- pi+ pi+)
+        BcToDplusMuMu = True,
+        BcToDplusMuMu_minKaonPt = 1000.,
+        BcToDplusMuMu_minPionPt = 1000.,
+        BcToDplusMuMu_massRange = (5500., 7300.),
+        BcToDplusMuMu_dimuonMassRange = (2500., 4300.),
+        BcToDplusMuMu_DplusMassRange = (1750., 2000.),
+        BcToDplusMuMu_chi2 = 60.,
         MonTool = TrigBmumuxComboHypoMonitoring('TrigBmumuxComboHypoMonitoring'))
 
     return hypo
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigMultiTrkComboHypoConfig.py b/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigMultiTrkComboHypoConfig.py
index 75ae77d5b07abe6c17b8b262f988bfec40cdb091..23cf2612c8b1bb95c67d861f365301a15d8ff425 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigMultiTrkComboHypoConfig.py
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/python/TrigMultiTrkComboHypoConfig.py
@@ -63,7 +63,7 @@ def StreamerDimuEFComboHypoCfg(name):
         trigSequenceName = 'StreamerDimu',
         trigLevel = 'EF')
     hypo.chi2 = 20.
-    hypo.massRanges = [ (100., 6000.) ]
+    hypo.massRange = [ (100., 6000.) ]
     return hypo
 
 def StreamerDiElecFastComboHypoCfg(name):
@@ -78,6 +78,18 @@ def StreamerDiElecFastComboHypoCfg(name):
         trackCollection='HLT_IDTrack_Electron_FTF')
     return hypo
 
+def StreamerNoMuonDiElecFastComboHypoCfg(name):
+    log.debug('StreamerNoMuonDiElecFastComboHypoCfg.name = %s ', name)
+
+    config = TrigMultiTrkComboHypoConfig()
+    hypo = config.ConfigurationComboHypo(
+        isStreamer = True,
+        trigSequenceName = 'NoMuonDiElecFast',
+        trigLevel = 'L2',
+        doElectrons = True,
+        trackCollection='HLT_IDTrack_Electron_FTF')
+    return hypo
+
 def DiElecPrecisionComboHypoCfg(name):
     log.debug('DiElecPrecisionComboHypoCfg.name = %s ', name)
 
@@ -90,6 +102,17 @@ def DiElecPrecisionComboHypoCfg(name):
         outputTrigBphysCollection = 'HLT_DiElecPrecision')
     return hypo
 
+def NoMuonDiElecPrecisionComboHypoCfg(name):
+    log.debug('NoMuonDiElecPrecisionComboHypoCfg.name = %s ', name)
+
+    config = TrigMultiTrkComboHypoConfig()
+    hypo = config.ConfigurationComboHypo(
+        isStreamer = False,
+        trigSequenceName = 'NoMuonDiElecPrecision',
+        trigLevel = 'EF',
+        doElectrons = True,
+        outputTrigBphysCollection = 'HLT_NoMuonDiElecPrecision')
+    return hypo
 
 def TrigMultiTrkComboHypoToolFromDict(chainDict):
     config = TrigMultiTrkComboHypoConfig()
@@ -125,15 +148,14 @@ class TrigMultiTrkComboHypoConfig(object):
             MaxChi2OfVtxEstimation = 2000.)
 
         if doElectrons:
-            trackMasses = [0.511,0.511]
             tool = CompFactory.TrigMultiTrkComboHypo(
               name = baseName+'ComboHypo',
               isStreamer = isStreamer,
               doElectrons = True,
               trigLevel = trigLevel,
-              nTracks = 2,
-              trackMasses = trackMasses,
-              massRanges = [ (100., 20000.) ],
+              nTracks = [ 2 ],
+              massRange = [ (100., 20000.) ],
+              trackPtThresholds = [ [ 4000., 4000. ] ],
               TrackCollectionKey = trackCollection,
               TrigBphysCollectionKey = outputTrigBphysCollection,
               VertexFitter = VertexFitter,
@@ -146,8 +168,8 @@ class TrigMultiTrkComboHypoConfig(object):
             name = baseName+'ComboHypo',
             isStreamer = isStreamer,
             trigLevel = trigLevel,
-            nTracks = 2,
-            massRanges = [ (100., 20000.) ],
+            nTracks = [ 2 ],
+            massRange = [ (100., 20000.) ],
             TrackCollectionKey = trackCollection,
             TrigBphysCollectionKey = outputTrigBphysCollection,
             VertexFitter = VertexFitter,
@@ -179,5 +201,12 @@ class TrigMultiTrkComboHypoConfig(object):
         if 'Lxy0' in chainDict['topo']:
             tool.LxyCut = 0.0
 
+        if 'BPH-0DR3-EM7J15' in chainDict['L1item']:
+            tool.isMergedElectronChain = True
+
+        signatures = chainDict['signatures']
+        tool.isCombinedChain = (signatures.count(signatures[0]) != len(signatures))
+        tool.legMultiplicities = chainDict['chainMultiplicities']
+
         tool.MonTool = TrigMultiTrkComboHypoToolMonitoring('MonTool')
         return tool
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/ITrigBphysState.h b/Trigger/TrigHypothesis/TrigBphysHypo/src/ITrigBphysState.h
index 803de129bd8a12a91bd21f25fc6076deb62d7485..dba23410f673ae498ccf9f72b292c8825211a06a 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/ITrigBphysState.h
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/ITrigBphysState.h
@@ -1,19 +1,51 @@
 /*
-  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
 */
 
 #ifndef TRIG_ITrigBphysState_H
 #define TRIG_ITrigBphysState_H
 
+#include "xAODTrigger/TrigCompositeContainer.h"
+#include "xAODTrigBphys/TrigBphysContainer.h"
+#include "BeamSpotConditionsData/BeamSpotData.h"
+
+
+class EventContext;
+
+
 /**
  * @class ITrigBphysState
  * @brief Base class for TrigBphys state objects
  */
 
-class ITrigBphysState
-{
+class ITrigBphysState {
  public:
+  ITrigBphysState() = delete;
+  ITrigBphysState(const EventContext& context,
+                  const TrigCompositeUtils::DecisionContainer& previousDecisions,
+                  TrigCompositeUtils::DecisionContainer& decisions,
+                  xAOD::TrigBphysContainer* trigBphysCollection = nullptr,
+                  const InDet::BeamSpotData* beamSpotData = nullptr)
+      : m_context(&context),
+        m_previousDecisions(&previousDecisions),
+        m_decisions(&decisions),
+        m_trigBphysCollection(trigBphysCollection),
+        m_beamSpotData(beamSpotData) {}
   virtual ~ITrigBphysState() = default;
+
+  inline const EventContext& context() const { return *m_context; }
+  inline const TrigCompositeUtils::DecisionContainer& previousDecisions() const { return *m_previousDecisions; }
+  inline TrigCompositeUtils::DecisionContainer& decisions() { return *m_decisions; }
+  inline xAOD::TrigBphysContainer& trigBphysCollection() { return *m_trigBphysCollection; }
+  void setTrigBphysCollection(xAOD::TrigBphysContainer* trigBphysCollection) { m_trigBphysCollection = trigBphysCollection; }
+  Amg::Vector3D beamSpotPosition() const { return (m_beamSpotData ? m_beamSpotData->beamPos() : Amg::Vector3D::Zero(3)); }
+
+ private:
+  const EventContext* m_context;
+  const TrigCompositeUtils::DecisionContainer* m_previousDecisions;
+  TrigCompositeUtils::DecisionContainer* m_decisions;
+  xAOD::TrigBphysContainer* m_trigBphysCollection;
+  const InDet::BeamSpotData* m_beamSpotData;
 };
 
 #endif  // TRIG_ITrigBphysState_H
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.cxx b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.cxx
index faf37b5d48f819dcf573e90664f6f0827d8418c9..c8273b0753f356895ffdc916ee7a7a616a2b441d 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.cxx
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.cxx
@@ -25,16 +25,14 @@
 #include "AthViews/ViewHelper.h"
 #include "AthContainers/AuxElement.h"
 
-#include "Math/Vector4D.h"
 #include "Math/GenVector/VectorUtil.h"
 
+
 using TrigCompositeUtils::Decision;
 using TrigCompositeUtils::DecisionContainer;
 using TrigCompositeUtils::DecisionID;
 using TrigCompositeUtils::DecisionIDContainer;
-using TrigCompositeUtils::comboHypoAlgNodeName;
 
-using GenVecFourMom_t = ROOT::Math::LorentzVector<ROOT::Math::PxPyPzM4D<double>>;
 
 const std::vector<std::vector<double>> TrigBmumuxComboHypo::s_trkMass{
   {PDG::mMuon, PDG::mMuon},
@@ -107,11 +105,12 @@ StatusCode TrigBmumuxComboHypo::execute(const EventContext& context) const {
   ATH_CHECK( trigBphysHandle.record(std::make_unique<xAOD::TrigBphysContainer>(),
                                     std::make_unique<xAOD::TrigBphysAuxContainer>()) );
 
-  auto state = makeState(&context, previousDecisionsHandle.cptr(), outputDecisionsHandle.ptr(), trigBphysHandle.ptr());
-  ATH_CHECK( mergeMuonsFromViews(*state) );
+  auto state = std::make_unique<TrigBmumuxState>(context, *previousDecisionsHandle, *outputDecisionsHandle, trigBphysHandle.ptr());
+
+  ATH_CHECK( mergeMuonsFromDecisions(*state) );
   ATH_CHECK( findDimuonCandidates(*state) );
 
-  if ( !state->dimuons.empty() ) {
+  if (!state->dimuons.empty()) {
     ATH_CHECK( mergeTracksFromViews(*state) );
     ATH_CHECK( findBmumuxCandidates(*state) );
     ATH_CHECK( createDecisionObjects(*state) );
@@ -122,33 +121,19 @@ StatusCode TrigBmumuxComboHypo::execute(const EventContext& context) const {
 }
 
 
-std::unique_ptr<TrigBmumuxState> TrigBmumuxComboHypo::makeState(const EventContext* context,
-                                                                const DecisionContainer* previousDecisions,
-                                                                DecisionContainer* decisions,
-                                                                xAOD::TrigBphysContainer* trigBphysCollection) const {
-  auto state = std::make_unique<TrigBmumuxState>();
-  state->context = context;
-  state->previousDecisions = previousDecisions;
-  state->decisions = decisions;
-  state->trigBphysCollection = trigBphysCollection;
-  state->dimuons.setStore(&state->dimuonsStore);
-  return state;
-}
-
-
-StatusCode TrigBmumuxComboHypo::mergeMuonsFromViews(TrigBmumuxState& state) const {
+StatusCode TrigBmumuxComboHypo::mergeMuonsFromDecisions(TrigBmumuxState& state) const {
 
   auto& muons = state.muons;
   muons.clear();
 
   // all muons from views are already connected with previous decisions by TrigMuonEFHypoAlg
-  for (const Decision* decision : *state.previousDecisions) {
+  for (const Decision* decision : state.previousDecisions()) {
     ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<xAOD::MuonContainer>::ID()) );
     auto muonEL = decision->objectLink<xAOD::MuonContainer>(TrigCompositeUtils::featureString());
     const xAOD::Muon* muon = *muonEL;
     if (!muon->trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle)) continue;
 
-    auto decisionEL = TrigCompositeUtils::decisionToElementLink(decision, *state.context);
+    auto decisionEL = TrigCompositeUtils::decisionToElementLink(decision, state.context());
     auto itr = std::find_if(muons.begin(), muons.end(), [this, muon = muon](const auto& x){ return isIdenticalTracks(muon, *x.link); });
     if (itr == muons.end()) {
       muons.push_back({muonEL, ElementLinkVector<DecisionContainer>(1, decisionEL), DecisionIDContainer()});
@@ -182,6 +167,7 @@ StatusCode TrigBmumuxComboHypo::mergeMuonsFromViews(TrigBmumuxState& state) cons
       }
     }
   }
+
   return StatusCode::SUCCESS;
 }
 
@@ -192,12 +178,12 @@ StatusCode TrigBmumuxComboHypo::mergeTracksFromViews(TrigBmumuxState& state) con
   tracks.clear();
 
   size_t viewCounter = 0;
-  for (const Decision* decision : *state.previousDecisions) {
+  for (const Decision* decision : state.previousDecisions()) {
     auto viewLinkInfo = TrigCompositeUtils::findLink<ViewContainer>(decision, TrigCompositeUtils::viewString(), true);
     ATH_CHECK( viewLinkInfo.isValid() );
     auto viewEL = viewLinkInfo.link;
 
-    auto tracksHandle = ViewHelper::makeHandle(*viewEL, m_trackParticleContainerKey, *state.context);
+    auto tracksHandle = ViewHelper::makeHandle(*viewEL, m_trackParticleContainerKey, state.context());
     ATH_CHECK( tracksHandle.isValid() );
     ATH_MSG_DEBUG( "tracks handle " << m_trackParticleContainerKey << " size: " << tracksHandle->size() );
 
@@ -281,18 +267,19 @@ StatusCode TrigBmumuxComboHypo::findDimuonCandidates(TrigBmumuxState& state) con
       }
 
       // fit muons to the common vertex and pass this vertex to the dimuons collection which also takes the ownership of the created object
-      xAOD::Vertex* vertex = fit(state.context, trackParticleLinks);
+      auto vertex = fit(state.context(), trackParticleLinks);
       if (!vertex) continue;
-      state.trigBphysMuonIndices.emplace_back(std::array<size_t, 2>{itrk1, itrk2});
-      state.dimuons.push_back(vertex);
 
       // convert vertex to trigger object and add it to the output xAOD::TrigBphysContainer
-      xAOD::TrigBphys* trigBphys = makeTriggerObject(vertex);
+      xAOD::TrigBphys* trigBphys = makeTriggerObject(*vertex);
       if (!trigBphys) {
         ATH_MSG_ERROR( "xAOD::Vertex could not be converted to xAOD::TrigBphys object: please enable MakeExtendedVertex option in vertex fitter " << m_vertexFitter->name() );
         return StatusCode::FAILURE;
       }
-      state.trigBphysCollection->push_back(trigBphys);
+
+      state.dimuons.push_back(vertex.release());
+      state.trigBphysMuonIndices.emplace_back(std::array<size_t, 2>{itrk1, itrk2});
+      state.trigBphysCollection().push_back(trigBphys);
     }
   }
   mon_nDimuon = state.dimuons.size();
@@ -312,12 +299,12 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
 
   const auto& tracks = state.tracks;
   mon_nTrk = tracks.size();
-  mon_nBPhysObject = state.trigBphysCollection->size();
+  mon_nBPhysObject = state.trigBphysCollection().size();
 
   for (size_t idx = 0; idx < state.dimuons.size(); ++idx) {
     const xAOD::Vertex* dimuon = state.dimuons.get(idx);
 
-    auto dimuonTriggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(*state.trigBphysCollection, idx);
+    auto dimuonTriggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(state.trigBphysCollection(), idx);
     ATH_CHECK( dimuonTriggerObjectEL.isValid() );
 
     // vtx1 = {mu1, mu2, trk1}
@@ -328,6 +315,7 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
     std::vector<ElementLink<xAOD::TrackParticleContainer>> trackParticleLinks_vtx2(trackParticleLinks_vtx1);
     trackParticleLinks_vtx2.emplace_back();
 
+    // vtx3 = {mu1, mu2, trk1, trk2, trk3}
     std::vector<ElementLink<xAOD::TrackParticleContainer>> trackParticleLinks_vtx3(trackParticleLinks_vtx2);
     trackParticleLinks_vtx3.emplace_back();
 
@@ -363,17 +351,17 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
       auto p_trk1 = trk1->genvecP4();
       auto charge1 = trk1->charge();
 
-      xAOD::Vertex* vtx1 = nullptr;
+      std::unique_ptr<xAOD::Vertex> vtx1;
       bool makeFit_vtx1 = true;
 
       // B+ -> mu+ mu- K+
       if (m_BplusToMuMuKaon &&
           p_trk1.Pt() > m_BplusToMuMuKaon_minKaonPt &&
           isInMassRange((p_dimuon + p_trk1.SetM(PDG::mKaon)).M(), m_BplusToMuMuKaon_massRange)) {
-        vtx1 = fit(state.context, trackParticleLinks_vtx1, kB_2mu1trk, *dimuonTriggerObjectEL);
+        vtx1 = fit(state.context(), trackParticleLinks_vtx1, kB_2mu1trk, *dimuonTriggerObjectEL);
         makeFit_vtx1 = false;
         if (vtx1 && vtx1->chiSquared() < m_BplusToMuMuKaon_chi2) {
-          xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx1, xAOD::TrigBphys::BKMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon}, dimuonTriggerObjectEL);
+          xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx1, xAOD::TrigBphys::BKMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon}, dimuonTriggerObjectEL);
           ATH_CHECK( state.addTriggerObject(trigBphys) );
         }
       }
@@ -384,17 +372,15 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
           isInMassRange(p_dimuon.M(), m_BcToMuMuPion_dimuonMassRange) &&
           isInMassRange((p_dimuon + p_trk1.SetM(PDG::mPion)).M() - p_dimuon.M() + PDG::mJpsi, m_BcToMuMuPion_massRange)) {
         if (!vtx1 && makeFit_vtx1) {
-          vtx1 = fit(state.context, trackParticleLinks_vtx1, kB_2mu1trk, *dimuonTriggerObjectEL);
+          vtx1 = fit(state.context(), trackParticleLinks_vtx1, kB_2mu1trk, *dimuonTriggerObjectEL);
           makeFit_vtx1 = false;
         }
         if (vtx1 && vtx1->chiSquared() < m_BcToMuMuPion_chi2) {
-          xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx1, xAOD::TrigBphys::BCPIMUMU, {PDG::mMuon, PDG::mMuon, PDG::mPion}, dimuonTriggerObjectEL);
+          xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx1, xAOD::TrigBphys::BCPIMUMU, {PDG::mMuon, PDG::mMuon, PDG::mPion}, dimuonTriggerObjectEL);
           ATH_CHECK( state.addTriggerObject(trigBphys) );
         }
       }
-
-      delete vtx1;
-      vtx1 = nullptr;
+      vtx1.reset();
 
       // dimuon + 2 tracks
       for (size_t itrk2 = itrk1 + 1; itrk2 < selectedTracks.size(); ++itrk2) {
@@ -406,7 +392,7 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
         auto p_trk2 = trk2->genvecP4();
         auto charge2 = trk2->charge();
 
-        xAOD::Vertex* vtx2 = nullptr;
+        std::unique_ptr<xAOD::Vertex> vtx2;
         bool makeFit_vtx2 = true;
 
         // B_s0 -> mu+ mu- phi(-> K+ K-)
@@ -416,10 +402,10 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
             p_trk2.Pt() > m_BsToMuMuPhi1020_minKaonPt &&
             isInMassRange((p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mKaon)).M(), m_BsToMuMuPhi1020_phiMassRange) &&
             isInMassRange((p_dimuon + p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mKaon)).M(), m_BsToMuMuPhi1020_massRange)) {
-          vtx2 = fit(state.context, trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
+          vtx2 = fit(state.context(), trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
           makeFit_vtx2 = false;
           if (vtx2 && vtx2->chiSquared() < m_BsToMuMuPhi1020_chi2) {
-            xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx2, xAOD::TrigBphys::BSPHIMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mKaon}, dimuonTriggerObjectEL);
+            xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx2, xAOD::TrigBphys::BSPHIMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mKaon}, dimuonTriggerObjectEL);
             ATH_CHECK( state.addTriggerObject(trigBphys) );
           }
         }
@@ -432,11 +418,11 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
             isInMassRange((p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mPion)).M(), m_BdToMuMuKstar0_KstarMassRange) &&
             isInMassRange((p_dimuon + p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mPion)).M(), m_BdToMuMuKstar0_massRange)) {
           if (!vtx2 && makeFit_vtx2) {
-            vtx2 = fit(state.context, trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
+            vtx2 = fit(state.context(), trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
             makeFit_vtx2 = false;
           }
           if (vtx2 && vtx2->chiSquared() < m_BdToMuMuKstar0_chi2) {
-            xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx2, xAOD::TrigBphys::BDKSTMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mPion}, dimuonTriggerObjectEL);
+            xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx2, xAOD::TrigBphys::BDKSTMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mPion}, dimuonTriggerObjectEL);
             ATH_CHECK( state.addTriggerObject(trigBphys) );
           }
         }
@@ -448,11 +434,11 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
             isInMassRange((p_trk1.SetM(PDG::mPion) + p_trk2.SetM(PDG::mKaon)).M(), m_BdToMuMuKstar0_KstarMassRange) &&
             isInMassRange((p_dimuon + p_trk1.SetM(PDG::mPion) + p_trk2.SetM(PDG::mKaon)).M(), m_BdToMuMuKstar0_massRange)) {
           if (!vtx2 && makeFit_vtx2) {
-            vtx2 = fit(state.context, trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
+            vtx2 = fit(state.context(), trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
             makeFit_vtx2 = false;
           }
           if (vtx2 && vtx2->chiSquared() < m_BdToMuMuKstar0_chi2) {
-            xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx2, xAOD::TrigBphys::BDKSTMUMU, {PDG::mMuon, PDG::mMuon, PDG::mPion, PDG::mKaon}, dimuonTriggerObjectEL);
+            xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx2, xAOD::TrigBphys::BDKSTMUMU, {PDG::mMuon, PDG::mMuon, PDG::mPion, PDG::mKaon}, dimuonTriggerObjectEL);
             ATH_CHECK( state.addTriggerObject(trigBphys) );
           }
         }
@@ -466,11 +452,11 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
             isInMassRange(p_dimuon.M(), m_LambdaBToMuMuProtonKaon_dimuonMassRange) &&
             isInMassRange((p_dimuon + p_trk1.SetM(PDG::mProton) + p_trk2.SetM(PDG::mKaon)).M() - p_dimuon.M() + PDG::mJpsi, m_LambdaBToMuMuProtonKaon_massRange)) {
           if (!vtx2 && makeFit_vtx2) {
-            vtx2 = fit(state.context, trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
+            vtx2 = fit(state.context(), trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
             makeFit_vtx2 = false;
           }
           if (vtx2 && vtx2->chiSquared() < m_LambdaBToMuMuProtonKaon_chi2) {
-            xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx2, xAOD::TrigBphys::LBPQMUMU, {PDG::mMuon, PDG::mMuon, PDG::mProton, PDG::mKaon}, dimuonTriggerObjectEL);
+            xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx2, xAOD::TrigBphys::LBPQMUMU, {PDG::mMuon, PDG::mMuon, PDG::mProton, PDG::mKaon}, dimuonTriggerObjectEL);
             ATH_CHECK( state.addTriggerObject(trigBphys) );
           }
         }
@@ -483,66 +469,80 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
             isInMassRange(p_dimuon.M(), m_LambdaBToMuMuProtonKaon_dimuonMassRange) &&
             isInMassRange((p_dimuon + p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mProton)).M() - p_dimuon.M() + PDG::mJpsi, m_LambdaBToMuMuProtonKaon_massRange)) {
           if (!vtx2 && makeFit_vtx2) {
-            vtx2 = fit(state.context, trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
+            vtx2 = fit(state.context(), trackParticleLinks_vtx2, kB_2mu2trk, *dimuonTriggerObjectEL);
             makeFit_vtx2 = false;
           }
           if (vtx2 && vtx2->chiSquared() < m_LambdaBToMuMuProtonKaon_chi2) {
-            xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx2, xAOD::TrigBphys::LBPQMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mProton}, dimuonTriggerObjectEL);
+            xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx2, xAOD::TrigBphys::LBPQMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mProton}, dimuonTriggerObjectEL);
             ATH_CHECK( state.addTriggerObject(trigBphys) );
           }
         }
+        vtx2.reset();
+
+        for (size_t itrk3 = 0; itrk3 < selectedTracks.size(); ++itrk3) {
+          const xAOD::TrackParticle* trk3 = *selectedTracks[itrk3];
+          if (itrk3 == itrk1 || itrk3 == itrk2 || isIdenticalTracks(mu1, trk3) || isIdenticalTracks(mu2, trk3)) continue;
+
+          trackParticleLinks_vtx3[2] = selectedTracks[itrk1];
+          trackParticleLinks_vtx3[3] = selectedTracks[itrk2];
+          trackParticleLinks_vtx3[4] = selectedTracks[itrk3];
+          auto p_trk3 = trk3->genvecP4();
+          auto charge3 = trk3->charge();
+
+          std::unique_ptr<xAOD::Vertex> vtx3;
+          bool makeFit_vtx3 = true;
+
+          // B_c+ -> J/psi(-> mu+ mu-) D_s+(->phi(-> K+ K-) pi+)
+          p_trk1.SetM(PDG::mKaon);  // D_s+.phi.K+
+          p_trk2.SetM(PDG::mKaon);  // D_s+.phi.K-
+          p_trk3.SetM(PDG::mPion);  // D_s+.pi+
+          if (m_BcToDsMuMu &&
+              charge1 * charge2 < 0. &&
+              p_trk1.Pt() > m_BcToDsMuMu_minKaonPt &&
+              p_trk2.Pt() > m_BcToDsMuMu_minKaonPt &&
+              p_trk3.Pt() > m_BcToDsMuMu_minPionPt &&
+              isInMassRange(p_dimuon.M(), m_BcToDsMuMu_dimuonMassRange) &&
+              isInMassRange((p_trk1 + p_trk2).M(), m_BcToDsMuMu_phiMassRange) &&
+              isInMassRange((p_trk1 + p_trk2 + p_trk3).M(), m_BcToDsMuMu_DsMassRange) &&
+              isInMassRange((p_dimuon + p_trk1 + p_trk2 + p_trk3).M() - p_dimuon.M() + PDG::mJpsi, m_BcToDsMuMu_massRange)) {
+            if (!vtx3 && makeFit_vtx3) {
+              vtx3 = fit(state.context(), trackParticleLinks_vtx3, kB_2mu3trk, *dimuonTriggerObjectEL);
+              makeFit_vtx3 = false;
+            }
+            if (vtx3 && vtx3->chiSquared() < m_BcToDsMuMu_chi2) {
+              xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx3, xAOD::TrigBphys::BCDSMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mKaon, PDG::mPion}, dimuonTriggerObjectEL);
+              ATH_CHECK( state.addTriggerObject(trigBphys) );
+            }
+          }
 
-        delete vtx2;
-        vtx2 = nullptr;
-
-
-        for (size_t itrk3 = itrk2 + 1; itrk3 < selectedTracks.size(); ++itrk3) {
-           const xAOD::TrackParticle* trk3 = *selectedTracks[itrk3];
-           if (isIdenticalTracks(mu1, trk3) || isIdenticalTracks(mu2, trk3)) continue;
-           bool makeFit_vtx3 = true;
-           xAOD::Vertex* vtx3 = nullptr;
-           trackParticleLinks_vtx3[2] = selectedTracks[itrk1];
-           trackParticleLinks_vtx3[3] = selectedTracks[itrk2];
-           trackParticleLinks_vtx3[4] = selectedTracks[itrk3];
-           auto p_trk3 = trk3->genvecP4();
-           //auto charge3 = trk3->charge();
-
-
-           if(m_Bc_DsMuMuDecay &&
-                p_trk1.Pt() > m_LambdaBToMuMuProtonKaon_minKaonPt &&
-                p_trk2.Pt() > m_LambdaBToMuMuProtonKaon_minKaonPt &&
-                isInMassRange((p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mKaon)).M(), m_rangePhiDs_MassCut) &&
-                isInMassRange((p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mKaon) + p_trk3.SetM(PDG::mPion)).M(), m_rangeDs_MassCut) ){
-              if (!vtx3 && makeFit_vtx3){
-                  vtx3 = fit(state.context, trackParticleLinks_vtx3, kB_2mu3trk, *dimuonTriggerObjectEL);
-                  makeFit_vtx3 = false;
-              }
-              if(vtx3 && vtx3->chiSquared() < m_Bc_DsMuMu_chi2){
-                  xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx3, xAOD::TrigBphys::BCDSMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mKaon, PDG::mPion}, dimuonTriggerObjectEL);
-                  ATH_CHECK( state.addTriggerObject(trigBphys) );
-              }
-           }
-
-           if(m_Bc_DpMuMuDecay &&
-                p_trk1.Pt() > m_Bc_DpMuMuKaon_minKaonPt &&
-                isInMassRange((p_trk1.SetM(PDG::mKaon) + p_trk2.SetM(PDG::mPion) + p_trk3.SetM(PDG::mPion)).M(), m_rangeDp_MassCut) ){
-              if (!vtx3 && makeFit_vtx3){
-                  vtx3 = fit(state.context, trackParticleLinks_vtx3, kB_2mu3trk, *dimuonTriggerObjectEL);
-                  makeFit_vtx3 = false;
-              }
-              if(vtx3 && vtx3->chiSquared() < m_Bc_DpMuMu_chi2){
-                  xAOD::TrigBphys* trigBphys = makeTriggerObject(vtx3, xAOD::TrigBphys::BCDPMUMU, {PDG::mMuon, PDG::mMuon, PDG::mKaon, PDG::mPion, PDG::mPion}, dimuonTriggerObjectEL);
-                  ATH_CHECK( state.addTriggerObject(trigBphys) );
-              }
-           }
-           delete vtx3;
-           vtx3 = nullptr;
-        }
+          // B_c+ -> J/psi(-> mu+ mu-) D+(-> K- pi+ pi+)
+          p_trk1.SetM(PDG::mPion);  // D+.pi+
+          p_trk2.SetM(PDG::mPion);  // D+.pi+
+          p_trk3.SetM(PDG::mKaon);  // D+.K-
+          if (m_BcToDplusMuMu &&
+              charge1 * charge2 > 0. && charge1 * charge3 < 0. &&
+              p_trk1.Pt() > m_BcToDplusMuMu_minPionPt &&
+              p_trk2.Pt() > m_BcToDplusMuMu_minPionPt &&
+              p_trk3.Pt() > m_BcToDplusMuMu_minKaonPt &&
+              isInMassRange(p_dimuon.M(), m_BcToDplusMuMu_dimuonMassRange) &&
+              isInMassRange((p_trk1 + p_trk2 + p_trk3).M(), m_BcToDplusMuMu_DplusMassRange) &&
+              isInMassRange((p_dimuon + p_trk1 + p_trk2 + p_trk3).M() - p_dimuon.M() + PDG::mJpsi, m_BcToDplusMuMu_massRange)) {
+            if (!vtx3 && makeFit_vtx3) {
+              vtx3 = fit(state.context(), trackParticleLinks_vtx3, kB_2mu3trk, *dimuonTriggerObjectEL);
+              makeFit_vtx3 = false;
+            }
+            if (vtx3 && vtx3->chiSquared() < m_BcToDplusMuMu_chi2) {
+              xAOD::TrigBphys* trigBphys = makeTriggerObject(*vtx3, xAOD::TrigBphys::BCDPMUMU, {PDG::mMuon, PDG::mMuon, PDG::mPion, PDG::mPion, PDG::mKaon}, dimuonTriggerObjectEL);
+              ATH_CHECK( state.addTriggerObject(trigBphys) );
+            }
+          }
+          vtx3.reset();
 
+        }
       }
     }
   }
-  mon_nBPhysObject = state.trigBphysCollection->size() - mon_nBPhysObject;
+  mon_nBPhysObject = state.trigBphysCollection().size() - mon_nBPhysObject;
 
   return StatusCode::SUCCESS;
 }
@@ -550,13 +550,13 @@ StatusCode TrigBmumuxComboHypo::findBmumuxCandidates(TrigBmumuxState& state) con
 
 StatusCode TrigBmumuxComboHypo::createDecisionObjects(TrigBmumuxState& state) const {
 
-  for (const xAOD::TrigBphys* triggerObject : *state.trigBphysCollection) {
+  for (const xAOD::TrigBphys* triggerObject : state.trigBphysCollection()) {
     // skip all dimuon trigger objects, they are already linked to the Bmumux trigger objects via lowerChainLink()
     if (triggerObject->particleType() == xAOD::TrigBphys::MULTIMU) continue;
 
     ATH_MSG_DEBUG( "Found xAOD::TrigBphys object: mass = " << triggerObject->mass() );
 
-    auto triggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(*state.trigBphysCollection, triggerObject->index());
+    auto triggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(state.trigBphysCollection(), triggerObject->index());
     ATH_CHECK( triggerObjectEL.isValid() );
 
     const xAOD::TrigBphys* dimuonTriggerObject = triggerObject->lowerChain();
@@ -576,7 +576,7 @@ StatusCode TrigBmumuxComboHypo::createDecisionObjects(TrigBmumuxState& state) co
     }
 
     // create a new output Decision object, backed by the 'decisions' container.
-    Decision* decision = TrigCompositeUtils::newDecisionIn(state.decisions, comboHypoAlgNodeName());
+    Decision* decision = TrigCompositeUtils::newDecisionIn(&state.decisions(), TrigCompositeUtils::comboHypoAlgNodeName());
 
     std::vector<const DecisionIDContainer*> previousDecisionIDs;
     for (const size_t& i : state.trigBphysMuonIndices.at(muonindex)) {
@@ -600,10 +600,12 @@ StatusCode TrigBmumuxComboHypo::createDecisionObjects(TrigBmumuxState& state) co
 }
 
 
-xAOD::Vertex* TrigBmumuxComboHypo::fit(const EventContext* context,
-                                       const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
-                                       Decay decay,
-                                       const xAOD::TrigBphys* dimuon) const {
+std::unique_ptr<xAOD::Vertex> TrigBmumuxComboHypo::fit(
+    const EventContext& context,
+    const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
+    Decay decay,
+    const xAOD::TrigBphys* dimuon) const {
+
   ATH_MSG_DEBUG( "Perform vertex fit" );
 
   if (trackParticleLinks.size() < 2) {
@@ -632,17 +634,17 @@ xAOD::Vertex* TrigBmumuxComboHypo::fit(const EventContext* context,
   }
   ATH_MSG_DEBUG( "Starting point: (" << startingPoint(0) << ", " << startingPoint(1) << ", " << startingPoint(2) << ")" );
 
-  auto fitterState = m_vertexFitter->makeState(*context);
+  auto fitterState = m_vertexFitter->makeState(context);
   m_vertexFitter->setMassInputParticles(s_trkMass[static_cast<size_t>(decay)], *fitterState);
-  xAOD::Vertex* vertex = m_vertexFitter->fit(tracklist, startingPoint, *fitterState);
+  std::unique_ptr<xAOD::Vertex> vertex(m_vertexFitter->fit(tracklist, startingPoint, *fitterState));
   if (!vertex) {
     ATH_MSG_DEBUG( "Vertex fit fails" );
-    return nullptr;
+    return vertex;
   }
   if (vertex->chiSquared() > 150. || (decay == Decay::kPsi_2mu && vertex->chiSquared() > m_dimuon_chi2)) {
     ATH_MSG_DEBUG( "Fit is successful, but vertex chi2 is too high, we are not going to save it (chi2 = " << vertex->chiSquared() << ")" );
-    delete vertex;
-    return nullptr;
+    vertex.reset();
+    return vertex;
   }
   ATH_MSG_DEBUG( "Fit is successful" );
 
@@ -654,17 +656,18 @@ xAOD::Vertex* TrigBmumuxComboHypo::fit(const EventContext* context,
 }
 
 
-xAOD::TrigBphys* TrigBmumuxComboHypo::makeTriggerObject(const xAOD::Vertex* vertex,
-                                                        xAOD::TrigBphys::pType type,
-                                                        const std::vector<double>& trkMass,
-                                                        const ElementLink<xAOD::TrigBphysContainer>& dimuonLink) const {
+xAOD::TrigBphys* TrigBmumuxComboHypo::makeTriggerObject(
+    const xAOD::Vertex& vertex,
+    xAOD::TrigBphys::pType type,
+    const std::vector<double>& trkMass,
+    const ElementLink<xAOD::TrigBphysContainer>& dimuonLink) const {
 
   // refitted track momentum as a 4-vector for mass hypothesis defined by the given decay value
-  GenVecFourMom_t momentum;
-  std::vector<GenVecFourMom_t> momenta;
-  if (!vertex->vxTrackAtVertexAvailable()) return nullptr;
-  for (size_t i = 0; i < vertex->vxTrackAtVertex().size(); ++i) {
-    const Trk::TrackParameters* perigee = vertex->vxTrackAtVertex()[i].perigeeAtVertex();
+  xAOD::TrackParticle::GenVecFourMom_t momentum;
+  std::vector<xAOD::TrackParticle::GenVecFourMom_t> momenta;
+  if (!vertex.vxTrackAtVertexAvailable()) return nullptr;
+  for (size_t i = 0; i < vertex.vxTrackAtVertex().size(); ++i) {
+    const Trk::TrackParameters* perigee = vertex.vxTrackAtVertex()[i].perigeeAtVertex();
     if (!perigee) return nullptr;
     const Amg::Vector3D& p = perigee->momentum();
     momenta.emplace_back(p.x(), p.y(), p.z(), trkMass[i]);
@@ -678,14 +681,14 @@ xAOD::TrigBphys* TrigBmumuxComboHypo::makeTriggerObject(const xAOD::Vertex* vert
   result->initialise(0, momentum.Eta(), momentum.Phi(), momentum.Pt(), type, mass, xAOD::TrigBphys::EF);
 
   result->setFitmass(momentum.M());
-  result->setFitx(vertex->x());
-  result->setFity(vertex->y());
-  result->setFitz(vertex->z());
-  result->setFitchi2(vertex->chiSquared());
-  result->setFitndof(vertex->numberDoF());
+  result->setFitx(vertex.x());
+  result->setFity(vertex.y());
+  result->setFitz(vertex.z());
+  result->setFitchi2(vertex.chiSquared());
+  result->setFitndof(vertex.numberDoF());
 
   // set all the particles associated with the decay
-  result->setTrackParticleLinks(vertex->trackParticleLinks());
+  result->setTrackParticleLinks(vertex.trackParticleLinks());
 
   // use lowerChainLink() as a link to the preceding dimuon trigger object
   if (type != xAOD::TrigBphys::MULTIMU) {
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.h b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.h
index 29f8f78ae63252ca3485e69368954094a706b797..710dcfd3c4035c06c914011b05c79a9f7422c99c 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.h
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigBmumuxComboHypo.h
@@ -44,10 +44,17 @@ typedef struct PDG20 PDG;
  */
 class TrigBmumuxState: public ::ITrigBphysState {
  public:
-  const EventContext* context;
-  const TrigCompositeUtils::DecisionContainer* previousDecisions;
-  TrigCompositeUtils::DecisionContainer* decisions;
-  xAOD::TrigBphysContainer* trigBphysCollection;
+  TrigBmumuxState() = delete;
+  TrigBmumuxState(const EventContext& context,
+                  const TrigCompositeUtils::DecisionContainer& previousDecisions,
+                  TrigCompositeUtils::DecisionContainer& decisions,
+                  xAOD::TrigBphysContainer* trigBphysCollection = nullptr,
+                  const InDet::BeamSpotData* beamSpotData = nullptr)
+      : ITrigBphysState(context, previousDecisions, decisions, trigBphysCollection, beamSpotData) {
+    dimuons.setStore(&dimuonsStore);
+  }
+  virtual ~TrigBmumuxState() = default;
+
   struct Muon {
     ElementLink<xAOD::MuonContainer> link;
     ElementLinkVector<TrigCompositeUtils::DecisionContainer> decisionLinks;
@@ -62,7 +69,7 @@ class TrigBmumuxState: public ::ITrigBphysState {
     if (!triggerObject) {
       return StatusCode::FAILURE;
     }
-    trigBphysCollection->push_back(triggerObject);
+    trigBphysCollection().push_back(triggerObject);
     return StatusCode::SUCCESS;
   }
 };
@@ -85,26 +92,28 @@ class TrigBmumuxComboHypo: public ::ComboHypo {
   enum Decay : size_t {
     kPsi_2mu,      // psi -> mu+ mu-
     kB_2mu1trk,    // B -> mu+ mu- trk1
-    kB_2mu2trk,     // B -> mu+ mu- trk1 trk2
+    kB_2mu2trk,    // B -> mu+ mu- trk1 trk2
     kB_2mu3trk     // B -> mu+ mu- trk1 trk2 trk3
   };
 
  private:
-  std::unique_ptr<TrigBmumuxState> makeState(const EventContext* context,
-                                             const TrigCompositeUtils::DecisionContainer* previousDecisions,
-                                             TrigCompositeUtils::DecisionContainer* decisions,
-                                             xAOD::TrigBphysContainer* trigBphysCollection) const;
-  StatusCode mergeMuonsFromViews(TrigBmumuxState&) const;
+  StatusCode mergeMuonsFromDecisions(TrigBmumuxState&) const;
   StatusCode mergeTracksFromViews(TrigBmumuxState&) const;
   StatusCode findDimuonCandidates(TrigBmumuxState&) const;
   StatusCode findBmumuxCandidates(TrigBmumuxState&) const;
   StatusCode createDecisionObjects(TrigBmumuxState&) const;
-  xAOD::Vertex* fit(const EventContext* context, const std::vector<ElementLink<xAOD::TrackParticleContainer>>& tracklist,
-                                     Decay = kPsi_2mu, const xAOD::TrigBphys* dimuon = nullptr) const;
-  xAOD::TrigBphys* makeTriggerObject(const xAOD::Vertex*,
-                                     xAOD::TrigBphys::pType type = xAOD::TrigBphys::MULTIMU,
-                                     const std::vector<double>& trkMass = {PDG::mMuon, PDG::mMuon},
-                                     const ElementLink<xAOD::TrigBphysContainer>& dimuonLink = ElementLink<xAOD::TrigBphysContainer>()) const;
+
+  std::unique_ptr<xAOD::Vertex> fit(
+      const EventContext& context,
+      const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
+      Decay decay = kPsi_2mu,
+      const xAOD::TrigBphys* dimuon = nullptr) const;
+
+  xAOD::TrigBphys* makeTriggerObject(
+      const xAOD::Vertex&,
+      xAOD::TrigBphys::pType type = xAOD::TrigBphys::MULTIMU,
+      const std::vector<double>& trkMass = {PDG::mMuon, PDG::mMuon},
+      const ElementLink<xAOD::TrigBphysContainer>& dimuonLink = ElementLink<xAOD::TrigBphysContainer>()) const;
 
   bool isIdenticalTracks(const xAOD::TrackParticle* lhs, const xAOD::TrackParticle* rhs) const;
   bool isIdenticalTracks(const xAOD::Muon* lhs, const xAOD::Muon* rhs) const;
@@ -206,25 +215,39 @@ class TrigBmumuxComboHypo: public ::ComboHypo {
   Gaudi::Property<float> m_LambdaBToMuMuProtonKaon_chi2 {this,
     "LambdaBToMuMuProtonKaon_chi2", 60., "maximum chi2 of the fitted Lambda_b0 vertex"};
 
-  Gaudi::Property<bool> m_Bc_DsMuMuDecay {this,
-    "BcToDsMuMuPhiPi", true, "switch on/off B_c -> J/psi(-> mu+ mu-) phi Pi decay"};
-  Gaudi::Property<double> m_Bc_DsMuMuKaon_minKaonPt {this,
-    "Bc_DsMuMuKaon_minKaonPt", 1000., "minimum pT of kaon track"};
-  Gaudi::Property<std::pair<double, double>> m_rangePhiDs_MassCut {this,
-    "Bc_rangePhiDs_MassCut", {980., 1080.}, "range for mass of phi"};
-  Gaudi::Property<std::pair<double, double>> m_rangeDs_MassCut {this,
-    "Bc_rangeDs_MassCut", { 1600., 2400.} , "range for mass of Ds"};
-  Gaudi::Property<float> m_Bc_DsMuMu_chi2 {this,
-    "Bc_DsMuMu_chi2", 60., "maximum chi2 of the fitted Bc vertex"};
-
-  Gaudi::Property<bool> m_Bc_DpMuMuDecay {this,
-    "BcToDpMuMuPhiPi", true, "switch on/off B_c -> J/psi(-> mu+ mu-) phi Pi decay"};
-  Gaudi::Property<double> m_Bc_DpMuMuKaon_minKaonPt {this,
-    "Bc_DpMuMuKaon_minKaonPt", 1000., "minimum pT of kaon track"};
-  Gaudi::Property<std::pair<double, double>> m_rangeDp_MassCut {this,
-    "Bc_rangeDp_MassCut", { 1500., 2300.} , "range for mass of Ds"};
-  Gaudi::Property<float> m_Bc_DpMuMu_chi2 {this,
-    "Bc_DpMuMu_chi2", 60., "maximum chi2 of the fitted Bc vertex"};
+  // B_c+ -> J/psi(-> mu+ mu-) D_s+(->phi(-> K+ K-) pi+)
+  Gaudi::Property<bool> m_BcToDsMuMu {this,
+    "BcToDsMuMu", true, "switch on/off B_c+ -> J/psi(-> mu+ mu-) D_s+(->phi(-> K+ K-) pi+) decay"};
+  Gaudi::Property<double> m_BcToDsMuMu_minKaonPt {this,
+    "BcToDsMuMu_minKaonPt", 1000., "minimum pT of kaon track from phi(1020)"};
+  Gaudi::Property<double> m_BcToDsMuMu_minPionPt {this,
+    "BcToDsMuMu_minPionPt", 1000., "minimum pT of pion track from D_s+"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDsMuMu_massRange {this,
+    "BcToDsMuMu_massRange", {5500., 7300.}, "B_c+ mass range"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDsMuMu_dimuonMassRange {this,
+    "BcToDsMuMu_dimuonMassRange", {2500., 4300.}, "dimuon mass range for B_c+ -> J/psi D_s+ decay"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDsMuMu_phiMassRange {this,
+    "BcToDsMuMu_phiMassRange", {940., 1100.}, "phi(1020) mass range"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDsMuMu_DsMassRange {this,
+    "BcToDsMuMu_DsMassRange", {1850., 2100.}, "D_s+ mass range"};
+  Gaudi::Property<float> m_BcToDsMuMu_chi2 {this,
+    "BcToDsMuMu_chi2", 60., "maximum chi2 of the fitted B_c+ vertex"};
+
+  // B_c+ -> J/psi(-> mu+ mu-) D+(-> K- pi+ pi+)
+  Gaudi::Property<bool> m_BcToDplusMuMu {this,
+    "BcToDplusMuMu", true, "switch on/off B_c+ -> J/psi(-> mu+ mu-) D+(-> K- pi+ pi+) decay"};
+  Gaudi::Property<double> m_BcToDplusMuMu_minKaonPt {this,
+    "BcToDplusMuMu_minKaonPt", 1000., "minimum pT of kaon track from D+"};
+  Gaudi::Property<double> m_BcToDplusMuMu_minPionPt {this,
+    "BcToDplusMuMu_minPionPt", 1000., "minimum pT of pion track from D+"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDplusMuMu_massRange {this,
+    "BcToDplusMuMu_massRange", {5500., 7300.}, "B_c+ mass range"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDplusMuMu_dimuonMassRange {this,
+    "BcToDplusMuMu_dimuonMassRange", {2500., 4300.}, "dimuon mass range for B_c+ -> J/psi D+ decay"};
+  Gaudi::Property<std::pair<double, double>> m_BcToDplusMuMu_DplusMassRange {this,
+    "BcToDplusMuMu_DplusMassRange", {1750., 2000.}, "D+ mass range"};
+  Gaudi::Property<float> m_BcToDplusMuMu_chi2 {this,
+    "BcToDplusMuMu_chi2", 60., "maximum chi2 of the fitted B_c+ vertex"};
 
 
   // external tools
@@ -240,7 +263,7 @@ class TrigBmumuxComboHypo: public ::ComboHypo {
   TrigCompositeUtils::DecisionIDContainer m_allowedIDs;
 
   const static std::vector<std::vector<double>> s_trkMass;
-  
+
 };
 
 #endif  // TRIG_TrigBmumuxComboHypo_H
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.cxx b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.cxx
index 1a4b08aac892e8bcb2111714974b1a1baaf42525..e46466eb247b29f3a5cad4537fb489be6ed09351 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.cxx
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.cxx
@@ -15,6 +15,7 @@
 #include <algorithm>
 #include <numeric>
 
+#include "Constants.h"
 #include "TrigMultiTrkComboHypo.h"
 
 #include "xAODMuon/Muon.h"
@@ -36,12 +37,16 @@
 #include "Math/GenVector/VectorUtil.h"
 #include "Math/Vector2D.h"
 
+
 using TrigCompositeUtils::Decision;
 using TrigCompositeUtils::DecisionContainer;
 using TrigCompositeUtils::DecisionID;
 using TrigCompositeUtils::DecisionIDContainer;
 using ROOT::Math::XYVector;
 
+typedef struct PDG20 PDG;
+
+
 TrigMultiTrkComboHypo::TrigMultiTrkComboHypo(const std::string& name, ISvcLocator* pSvcLocator)
     : ::ComboHypo(name, pSvcLocator) {}
 
@@ -52,27 +57,45 @@ StatusCode TrigMultiTrkComboHypo::initialize() {
   ATH_CHECK( ::ComboHypo::initialize() );
 
   // check consistency of the properties
-  if (m_trkMass.size() != m_nTrk) {
-    ATH_MSG_ERROR( "Requested " << m_nTrk.value() << " tracks per vertex, but only provided " << m_trkMass.size() << " track masses");
-    return StatusCode::FAILURE;
+  ATH_CHECK( !m_nTrk.empty() );
+
+  if (m_trkMass.empty()) {
+    ATH_MSG_INFO( "trackMasses value is not specified, muon/electron mass will be used" );
+    for (const auto& n : m_nTrk) {
+      m_trkMass.value().emplace_back(std::vector<double>(n, (m_doElectrons ? PDG::mElectron : PDG::mMuon)));
+    }
   }
-  if (m_trkPt.size() != m_nTrk) {
-    ATH_MSG_ERROR( "Requested " << m_nTrk.value() << " tracks per vertex, but only provided " << m_trkPt.size() << " track pT thresholds");
-    return StatusCode::FAILURE;
+  if (m_trkPt.empty()) {
+    ATH_MSG_INFO( "trackPtThresholds value is not specified" );
+    for (const auto& n : m_nTrk) {
+      m_trkPt.value().emplace_back(std::vector<double>(n, -100.));
+    }
+  }
+  m_trkPtMin = m_trkPt[0].back();
+  for (size_t i = 0; i < m_trkPt.size(); ++i) {
+    m_trkPtMin = std::min(m_trkPtMin, m_trkPt[i].back());
+  }
+
+  ATH_CHECK( m_trkMass.size() == m_nTrk.size() );
+  ATH_CHECK( m_trkPt.size() == m_nTrk.size() );
+
+  for (size_t i = 0; i < m_nTrk.size(); ++i) {
+    ATH_CHECK( m_trkMass[i].size() == m_nTrk[i] );
+    ATH_CHECK( m_trkPt[i].size() == m_nTrk[i] );
+    ATH_CHECK( std::is_sorted(m_trkPt[i].begin(), m_trkPt[i].end(), std::greater<>()) );
   }
 
   for (const auto& range : m_massRange.value()) {
-    if (range.first > range.second) {
-      ATH_MSG_ERROR( "Mass range incorrect values: {Mi_min, Mi_max}" );
-      return StatusCode::FAILURE;
-    }
+    ATH_CHECK( range.first < range.second );
   }
 
   // dump numerical values
   if (msgLvl(MSG::DEBUG)) {
-    msg() << MSG::DEBUG << "vertex topology:" << std::endl;
-    for (size_t i = 0; i < m_nTrk; ++i) {
-      msg() << MSG::DEBUG << "  " << i << "  trk: mass = " <<  m_trkMass[i] << ", Pt > " << m_trkPt[i] << std::endl;
+    for (size_t i = 0; i < m_nTrk.size(); ++i) {
+      ATH_MSG_DEBUG( "vertex topology: nTrk = " << m_nTrk[i] );
+      for (size_t j = 0; j < m_nTrk[i]; ++j) {
+        ATH_MSG_DEBUG( "  " << j + 1 << "  trk: mass = " <<  m_trkMass[i][j] << ", Pt > " << m_trkPt[i][j] );
+      }
     }
     msg() << MSG::DEBUG << "  mass range: {";
     for (const auto& range : m_massRange.value()) {
@@ -89,6 +112,8 @@ StatusCode TrigMultiTrkComboHypo::initialize() {
     return StatusCode::FAILURE;
   }
 
+  ATH_CHECK( !(m_trigLevel == "L2IO" && m_doElectrons) );
+
   if (m_trigLevel == "L2") {
     ATH_CHECK( m_trackParticleContainerKey.initialize() );
     renounce(m_trackParticleContainerKey);
@@ -123,6 +148,29 @@ StatusCode TrigMultiTrkComboHypo::initialize() {
       ATH_MSG_DEBUG( " +++ " << HLT::Identifier(id) );
     }
   }
+  if (m_doElectrons) {
+    for (const DecisionID& id : m_allowedIDs) {
+      std::string name = HLT::Identifier(id).name();
+      bool isMergedElectronChain = false;
+      for (size_t i = 0; i < m_mergedElectronChains.size(); ++i) {
+        if (name.find(m_mergedElectronChains.value().at(i)) != std::string::npos) {
+          isMergedElectronChain = true;
+          break;
+        }
+      }
+      (isMergedElectronChain ? m_mergedElectronIDs.insert(id) : m_resolvedElectronIDs.insert(id));
+    }
+    if (msgLvl(MSG::DEBUG)) {
+      ATH_MSG_DEBUG( "Well-separated electron decisions:" );
+      for (const DecisionID& id : m_resolvedElectronIDs) {
+        ATH_MSG_DEBUG( " +++ " << HLT::Identifier(id) );
+      }
+      ATH_MSG_DEBUG( "Overlapping electron decisions:" );
+      for (const DecisionID& id : m_mergedElectronIDs) {
+        ATH_MSG_DEBUG( " +++ " << HLT::Identifier(id) );
+      }
+    }
+  }
 
   if (!m_monTool.empty()) {
     ATH_CHECK( m_monTool.retrieve() );
@@ -145,46 +193,56 @@ StatusCode TrigMultiTrkComboHypo::execute(const EventContext& context) const {
   CHECK( previousDecisionsHandle.isValid() );
   ATH_MSG_DEBUG( "Running with "<< previousDecisionsHandle->size() << " previous decisions" );
 
-  // create the mutable output DecisionContainer and register it to StoreGate
   SG::WriteHandle<DecisionContainer> outputDecisionsHandle = TrigCompositeUtils::createAndStore(decisionsOutput().at(0), context);
 
-  std::unique_ptr<TrigMultiTrkStateCand<xAOD::MuonContainer>> muonstate;
-  std::unique_ptr<TrigMultiTrkStateCand<xAOD::ElectronContainer>> electronstate;
-  TrigMultiTrkState* commonstate=nullptr;
-  if(m_doElectrons == true){
-      electronstate = std::make_unique<TrigMultiTrkStateCand<xAOD::ElectronContainer>>();
-      commonstate = electronstate.get();
-  }else{
-      muonstate = std::make_unique<TrigMultiTrkStateCand<xAOD::MuonContainer>>();
-      commonstate = muonstate.get();
+  const InDet::BeamSpotData* beamSpotData = nullptr;
+  if (!m_isStreamer) {
+    SG::ReadCondHandle<InDet::BeamSpotData> beamSpotHandle {m_beamSpotKey, context};
+    ATH_CHECK( beamSpotHandle.isValid() );
+    beamSpotData = *beamSpotHandle;
+  }
+
+  std::unique_ptr<TrigMultiTrkState<xAOD::MuonContainer>> muonState;
+  std::unique_ptr<TrigMultiTrkState<xAOD::ElectronContainer>> electronState;
+  TrigMultiTrkStateBase* commonState = nullptr;
+  if (m_doElectrons) {
+    electronState = std::make_unique<TrigMultiTrkState<xAOD::ElectronContainer>>(context, *previousDecisionsHandle, *outputDecisionsHandle, nullptr, beamSpotData);
+    commonState = electronState.get();
+  }
+  else {
+    muonState = std::make_unique<TrigMultiTrkState<xAOD::MuonContainer>>(context, *previousDecisionsHandle, *outputDecisionsHandle, nullptr, beamSpotData);
+    commonState = muonState.get();
   }
-  FillState(commonstate, &context, previousDecisionsHandle.cptr(), outputDecisionsHandle.ptr(), nullptr);
 
   if (m_isStreamer) {
     if (m_trigLevel == "L2") {
-      ATH_CHECK( mergeTracksFromViews(*commonstate) );
+      ATH_CHECK( mergeTracksFromViews(*commonState) );
     }
-    else {
-      ATH_CHECK( mergeTracksFromDecisions(*commonstate) );
+    else if (m_trigLevel == "L2IO") {
+      ATH_CHECK( mergeTracksFromDecisions<xAOD::L2CombinedMuonContainer>(*commonState) );
+    }
+    else if (m_trigLevel == "EF") {
+      ATH_CHECK( (m_doElectrons ? mergeTracksFromDecisions<xAOD::ElectronContainer>(*commonState) : mergeTracksFromDecisions<xAOD::MuonContainer>(*commonState)) );
     }
-    ATH_CHECK( filterTrackCombinations(*commonstate) );
-    ATH_CHECK( copyDecisionObjects(*commonstate) );
+    ATH_CHECK( filterTrackCombinations(*commonState) );
+    ATH_CHECK( copyDecisionObjects(*commonState) );
   }
   else {
-
     auto trigBphysHandle = SG::makeHandle(m_trigBphysContainerKey, context);
     ATH_CHECK( trigBphysHandle.record(std::make_unique<xAOD::TrigBphysContainer>(),
                                       std::make_unique<xAOD::TrigBphysAuxContainer>()) );
-    commonstate->trigBphysCollection = trigBphysHandle.ptr();
+    commonState->setTrigBphysCollection(trigBphysHandle.ptr());
 
-    if(m_doElectrons == true){
-        ATH_CHECK( mergeFromDecisions(*electronstate) );
-        ATH_CHECK( findDiTrackCandidates(*electronstate) );
-    } else{
-        ATH_CHECK( mergeFromDecisions(*muonstate) );
-        ATH_CHECK( findDiTrackCandidates(*muonstate) );
+    if (m_doElectrons) {
+      ATH_CHECK( mergeLeptonsFromDecisions(*electronState) );
+      ATH_CHECK( findMultiLeptonCandidates(*electronState) );
+      ATH_CHECK( processMergedElectrons(*electronState) );
     }
-    ATH_CHECK( createDecisionObjects(*commonstate) );
+    else {
+      ATH_CHECK( mergeLeptonsFromDecisions(*muonState) );
+      ATH_CHECK( findMultiLeptonCandidates(*muonState) );
+    }
+    ATH_CHECK( createDecisionObjects(*commonState) );
   }
 
   ATH_MSG_DEBUG( "TrigMultiTrkHypo::execute() terminates with StatusCode::SUCCESS" );
@@ -192,38 +250,37 @@ StatusCode TrigMultiTrkComboHypo::execute(const EventContext& context) const {
 }
 
 
-void TrigMultiTrkComboHypo::FillState(TrigMultiTrkState* state,
-                                                                    const EventContext* context,
-                                                                    const DecisionContainer* previousDecisions,
-                                                                    DecisionContainer* decisions,
-                                                                    xAOD::TrigBphysContainer* trigBphysCollection) const {
-  state->context = context;
-  state->previousDecisions = previousDecisions;
-  state->decisions = decisions;
-  state->trigBphysCollection = trigBphysCollection;
-}
-
 template<typename T>
-StatusCode TrigMultiTrkComboHypo::mergeFromDecisions(TrigMultiTrkStateCand<T>& state) const {
+StatusCode TrigMultiTrkComboHypo::mergeLeptonsFromDecisions(TrigMultiTrkState<T>& state) const {
 
-  auto& leptons = state.LegList;
+  auto& leptons = state.leptons();
   leptons.clear();
 
-  // all muons from views are already connected with previous decisions by TrigMuonEFHypoAlg
-  for (const Decision* decision : *state.previousDecisions) {
-    if (!TrigCompositeUtils::isAnyIDPassing(decision, m_allowedIDs)) continue;
+  // all muons/electrons from views are already connected with previous decisions by TrigMuonEFHypoAlg
+  for (const Decision* decision : state.previousDecisions()) {
+    if (!TrigCompositeUtils::isAnyIDPassing(decision, (m_doElectrons ? m_resolvedElectronIDs : m_allowedIDs))) continue;
 
     ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<T>::ID()) );
     auto leptonEL = decision->objectLink<T>(TrigCompositeUtils::featureString());
     const auto lepton = *leptonEL;
-    if constexpr (std::is_same<T, xAOD::MuonContainer>::value){
-       if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle)) continue;
-       if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle)) continue;
-    } else {
-       if (!lepton->trackParticle()) continue;
-    }
-    auto decisionEL = TrigCompositeUtils::decisionToElementLink(decision, *state.context);
-    auto itr = std::find_if(leptons.begin(), leptons.end(), [this, lepton = lepton](const auto& x){ return this->isIdenticalTracks(lepton, *x.link); });
+    if constexpr(std::is_same<T, xAOD::MuonContainer>::value) {
+      if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle)) continue;
+      if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle)) continue;
+    }
+    else if constexpr(std::is_same<T, xAOD::ElectronContainer>::value) {
+      if (!lepton->trackParticle()) continue;
+    }
+    else {
+      ATH_MSG_ERROR( "mergeLeptonsFromDecisions(): no scenario for the provided CONTAINER is specified" );
+      return StatusCode::FAILURE;
+    }
+    auto decisionEL = TrigCompositeUtils::decisionToElementLink(decision, state.context());
+
+    auto itr = leptons.end();
+    if (m_applyOverlapRemoval) {
+      itr = std::find_if(leptons.begin(), leptons.end(),
+                         [this, lepton = lepton](const auto& x){ return this->isIdenticalTracks(lepton, *x.link); });
+    }
     if (itr == leptons.end()) {
       leptons.push_back({leptonEL, ElementLinkVector<DecisionContainer>(1, decisionEL), DecisionIDContainer()});
     }
@@ -248,59 +305,60 @@ StatusCode TrigMultiTrkComboHypo::mergeFromDecisions(TrigMultiTrkStateCand<T>& s
     for (const auto& item : leptons) {
       const auto lepton = *item.link;
       const xAOD::TrackParticle* track;
-      if constexpr (std::is_same<T, xAOD::MuonContainer>::value){
-         track = *lepton->inDetTrackParticleLink();
-      }else{
-         track = *lepton->trackParticleLink();
+      if constexpr(std::is_same<T, xAOD::MuonContainer>::value) {
+        track = lepton->trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
+      }
+      else {
+        track = lepton->trackParticle();
       }
       ATH_MSG_DEBUG( " -- lepton InDetTrackParticle pt/eta/phi/q: " << track->pt() << " / " << track->eta() << " / " << track->phi() << " / " << track->charge() );
-      ATH_MSG_DEBUG( "    lepton pt (muon: CombinedTrackParticle): " << lepton->pt() );
+      ATH_MSG_DEBUG( "    lepton pt (muon: CombinedTrackParticle): " << lepton->pt() << " / " << lepton->eta() << " / " << lepton->phi() << " / " << lepton->charge() );
       ATH_MSG_DEBUG( "    allowed decisions:" );
       for (const DecisionID& id : item.decisionIDs) {
         ATH_MSG_DEBUG( "    +++ " << HLT::Identifier(id) );
       }
     }
   }
+
   return StatusCode::SUCCESS;
 }
 
 
+StatusCode TrigMultiTrkComboHypo::mergeTracksFromViews(TrigMultiTrkStateBase& state) const {
 
-
-StatusCode TrigMultiTrkComboHypo::mergeTracksFromViews(TrigMultiTrkState& state) const {
-
-  auto& tracks = state.tracks;
+  auto& tracks = state.tracks();
   tracks.clear();
 
-  size_t viewCounter = 0;
-  for (const Decision* decision : *state.previousDecisions) {
+  std::set<const SG::View*> views;
+  for (const Decision* decision : state.previousDecisions()) {
     if (!TrigCompositeUtils::isAnyIDPassing(decision, m_allowedIDs)) continue;
 
     auto viewLinkInfo = TrigCompositeUtils::findLink<ViewContainer>(decision, TrigCompositeUtils::viewString(), true);
     ATH_CHECK( viewLinkInfo.isValid() );
-    auto viewEL = viewLinkInfo.link;
+    const SG::View* view = *viewLinkInfo.link;
+    if (views.find(view) != views.end()) continue;  // tracks from this view have already been fetched
 
-    auto tracksHandle = ViewHelper::makeHandle(*viewEL, m_trackParticleContainerKey, *state.context);
+    auto tracksHandle = ViewHelper::makeHandle(view, m_trackParticleContainerKey, state.context());
     ATH_CHECK( tracksHandle.isValid() );
     ATH_MSG_DEBUG( "tracks handle " << m_trackParticleContainerKey << " size: " << tracksHandle->size() );
 
     std::vector<ElementLink<xAOD::TrackParticleContainer>> tracksFromView;
     tracksFromView.reserve(tracksHandle->size());
     for (size_t idx = 0; idx < tracksHandle->size(); ++idx) {
-      tracksFromView.emplace_back(ViewHelper::makeLink<xAOD::TrackParticleContainer>(*viewEL, tracksHandle, idx));
+      tracksFromView.emplace_back(ViewHelper::makeLink<xAOD::TrackParticleContainer>(view, tracksHandle, idx));
     }
 
     for (const auto& trackEL : tracksFromView) {
       const xAOD::TrackParticle* track = *trackEL;
-      if (track->definingParametersCovMatrixVec().empty() || track->pt() < m_trkPt.value().back()) continue;
+      if (track->definingParametersCovMatrixVec().empty() || track->pt() < m_trkPtMin) continue;
 
-      if (viewCounter == 0 ||
+      if (views.empty() || !m_applyOverlapRemoval ||
           std::find_if(tracks.begin(), tracks.end(),
                        [this,track = track](const auto& x){ return isIdenticalTracks(track, *x); }) == tracks.end()) {
         tracks.emplace_back(trackEL);
       }
     }
-    viewCounter++;
+    views.insert(view);
   }
   std::sort(tracks.begin(), tracks.end(), [](const auto& lhs, const auto& rhs){ return ((*lhs)->pt() > (*rhs)->pt()); });
 
@@ -311,41 +369,47 @@ StatusCode TrigMultiTrkComboHypo::mergeTracksFromViews(TrigMultiTrkState& state)
       ATH_MSG_DEBUG( " -- track pt/eta/phi/q: " << track->pt() << " / " << track->eta() << " / " << track->phi() << " / " << track->charge() );
     }
   }
+
   return StatusCode::SUCCESS;
 }
 
 
-StatusCode TrigMultiTrkComboHypo::mergeTracksFromDecisions(TrigMultiTrkState& state) const {
+template<typename CONTAINER>
+StatusCode TrigMultiTrkComboHypo::mergeTracksFromDecisions(TrigMultiTrkStateBase& state) const {
 
-  auto& tracks = state.tracks;
+  auto& tracks = state.tracks();
   tracks.clear();
 
-  // all muons from views are already connected with previous decisions by TrigMuonEFHypoAlg
-  for (const Decision* decision : *state.previousDecisions) {
+  // all muons/electrons from views are already connected with previous decisions by TrigMuonEFHypoAlg or by TrigEgammaPrecisionElectronHypoAlg
+  for (const Decision* decision : state.previousDecisions()) {
     if (!TrigCompositeUtils::isAnyIDPassing(decision, m_allowedIDs)) continue;
 
+    ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<CONTAINER>::ID()) );
+    auto leptonEL = decision->objectLink<CONTAINER>(TrigCompositeUtils::featureString());
+    const auto lepton = *leptonEL;
+
     ElementLink<xAOD::TrackParticleContainer> trackEL;
-    if (m_trigLevel == "EF") {
-      ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<xAOD::MuonContainer>::ID()) );
-      auto muonEL = decision->objectLink<xAOD::MuonContainer>(TrigCompositeUtils::featureString());
-      const xAOD::Muon* muon = *muonEL;
-      if (!muon->trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle)) continue;
-      if (!muon->trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle)) continue;
-      trackEL = muon->inDetTrackParticleLink();
+    if constexpr(std::is_same<CONTAINER, xAOD::MuonContainer>::value) {
+      if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle)) continue;
+      if (!lepton->trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle)) continue;
+      trackEL = lepton->inDetTrackParticleLink();
     }
-    else if (m_trigLevel == "L2IO") {
-      ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<xAOD::L2CombinedMuonContainer>::ID()) );
-      auto muonEL = decision->objectLink<xAOD::L2CombinedMuonContainer>(TrigCompositeUtils::featureString());
-      const xAOD::L2CombinedMuon* muon = *muonEL;
-      trackEL = muon->idTrackLink();
+    else if constexpr(std::is_same<CONTAINER, xAOD::L2CombinedMuonContainer>::value) {
+      if (!lepton->idTrack()) continue;
+      trackEL = lepton->idTrackLink();
+    }
+    else if constexpr(std::is_same<CONTAINER, xAOD::ElectronContainer>::value) {
+      if (!lepton->trackParticle()) continue;
+      trackEL = lepton->trackParticleLink();
     }
     else {
-      ATH_MSG_ERROR( "mergeTracksFromDecisions() expects trigLevel to be L2IO or EF, but " << m_trigLevel << " provided" );
+      ATH_MSG_ERROR( "mergeTracksFromDecisions(): no scenario for the provided CONTAINER is specified" );
       return StatusCode::FAILURE;
     }
-    if (!trackEL.isValid()) continue;
 
-    if (std::find_if(tracks.begin(), tracks.end(),
+    if (!trackEL.isValid()) continue;
+    if (!m_applyOverlapRemoval ||
+        std::find_if(tracks.begin(), tracks.end(),
                      [this, track = *trackEL](const auto& x){ return isIdenticalTracks(track, *x); }) == tracks.end()) {
       tracks.emplace_back(trackEL);
     }
@@ -359,14 +423,15 @@ StatusCode TrigMultiTrkComboHypo::mergeTracksFromDecisions(TrigMultiTrkState& st
       ATH_MSG_DEBUG( " -- track pt/eta/phi/q: " << track->pt() << " / " << track->eta() << " / " << track->phi() << " / " << track->charge() );
     }
   }
+
   return StatusCode::SUCCESS;
 }
 
 
-StatusCode TrigMultiTrkComboHypo::filterTrackCombinations(TrigMultiTrkState& state) const {
+StatusCode TrigMultiTrkComboHypo::filterTrackCombinations(TrigMultiTrkStateBase& state) const {
 
-  state.isEventAccepted = 0;
-  auto& tracks = state.tracks;
+  const auto& tracks = state.tracks();
+  state.setEventAccepted(false);
 
   // monitored variables
   auto mon_nAcceptedTrk = Monitored::Scalar<int>("nAcceptedTrk", tracks.size());
@@ -378,86 +443,84 @@ StatusCode TrigMultiTrkComboHypo::filterTrackCombinations(TrigMultiTrkState& sta
     mon_nAcceptedTrk, mon_nVertexFitterCalls, mon_isEventAccepted,
     mon_timer);
 
-  // combination is a selection of items from a collection, such that the order of selection does not matter
-  // tracks have already been sorted over pT, the algorithm will keep this order in created subsets of tracks, i.e.
-  // for m_nTrk = 2 combinations are [0, 1], [0, 2], ..., [1, 2], [1, 3], ...
-  // for m_nTrk = 3 combinations are [0, 1, 2], [0, 1, 3], ..., [0, 2, 3], [0, 2, 4], ..., [1, 2, 3], [1, 2, 4], ...
+  for (size_t iTrk = 0; iTrk < m_nTrk.size(); ++iTrk) {
+    if (state.isEventAccepted()) break;
+    size_t nTrk = m_nTrk[iTrk];
 
-  if (tracks.size() < m_nTrk) {
-    ATH_MSG_DEBUG( "Could not build a subset of " << m_nTrk.value() << " tracks from collection which contains only " << tracks.size() << " objects" );
-    return StatusCode::SUCCESS;
-  }
-  ATH_MSG_DEBUG( "Consider combinations of " << m_nTrk.value() << " tracks from collection which contains " << tracks.size() << " objects until find a good one" );
-
-  std::vector<ElementLink<xAOD::TrackParticleContainer>> tracklist(m_nTrk);
-  std::vector<xAOD::TrackParticle::GenVecFourMom_t> p(m_nTrk);
-
-  // tracks from the current combination will have non-zero value against their position in the 'idx' vector
-  // consider first m_nTrk tracks as an initial combination, then get the next one with std::prev_permutation()
-  std::vector<char> idx(tracks.size(), 0);
-  std::fill(idx.begin(), idx.begin() + m_nTrk, 1);
-  do {
-    // fill tracklist and momenta of tracks, also check that the track pT passes the threshold value
-    bool isValidCombination = true;
-    size_t j = 0;
-    for (size_t i = 0; i < idx.size(); ++i) {
-      if (!idx[i]) continue;
-      const auto& trackEL = tracks[i];
-      tracklist[j] = trackEL;
-      p[j] = (*trackEL)->genvecP4();
-      p[j].SetM(m_trkMass[j]);
-      if (p[j].Pt() < m_trkPt[j]) {
-        isValidCombination = false;
-        break;
-      }
-      ++j;
+    if (tracks.size() < nTrk) {
+      ATH_MSG_DEBUG( "Could not build a subset of " << nTrk << " tracks from collection which contains only " << tracks.size() << " objects" );
+      continue;
     }
-    if (!isValidCombination) continue;
-
-    if (msgLvl(MSG::DEBUG)) {
-      ATH_MSG_DEBUG( "Dump found tracks before vertex fit: pT / eta / phi / charge" );
-      for (size_t i = 0; i < tracklist.size(); ++i) {
-        const auto track = *tracklist[i];
-        ATH_MSG_DEBUG( "track " << i + 1 << ": " << p[i].Pt() << " / " << p[i].Eta() << " / " << p[i].Phi() << " / " << track->charge() );
+    ATH_MSG_DEBUG( "Consider combinations of " << nTrk << " tracks from collection which contains " << tracks.size() << " objects until find a good one" );
+
+    std::vector<ElementLink<xAOD::TrackParticleContainer>> tracklist(nTrk);
+    std::vector<xAOD::TrackParticle::GenVecFourMom_t> p(nTrk);
+
+    // tracks from the current combination will have non-zero value against their position in the 'idx' vector
+    // consider first nTrk tracks as an initial combination, then get the next one with std::prev_permutation()
+    std::vector<char> idx(tracks.size(), 0);
+    std::fill(idx.begin(), idx.begin() + nTrk, 1);
+    do {
+      // fill tracklist and momenta of tracks, also check that the track pT passes the threshold value
+      bool isValidCombination = true;
+      size_t j = 0;
+      for (size_t i = 0; i < idx.size(); ++i) {
+        if (!idx[i]) continue;
+        const auto& trackEL = tracks[i];
+        tracklist[j] = trackEL;
+        p[j] = (*trackEL)->genvecP4();
+        p[j].SetM(m_trkMass[iTrk][j]);
+        if (p[j].Pt() < m_trkPt[iTrk][j]) {
+          isValidCombination = false;
+          break;
+        }
+        ++j;
+      }
+      if (!isValidCombination) continue;
+
+      if (msgLvl(MSG::DEBUG)) {
+        ATH_MSG_DEBUG( "Dump found tracks before vertex fit: pT / eta / phi / charge" );
+        for (size_t i = 0; i < tracklist.size(); ++i) {
+          const auto track = *tracklist[i];
+          ATH_MSG_DEBUG( "track " << i + 1 << ": " << p[i].Pt() << " / " << p[i].Eta() << " / " << p[i].Phi() << " / " << track->charge() );
+        }
       }
-    }
 
-    auto mass = (std::accumulate(p.begin(), p.end(), xAOD::TrackParticle::GenVecFourMom_t())).M();
-    ATH_MSG_DEBUG( "invariant mass: " << mass );
+      auto mass = (std::accumulate(p.begin(), p.end(), xAOD::TrackParticle::GenVecFourMom_t())).M();
+      ATH_MSG_DEBUG( "invariant mass: " << mass );
 
-    if (!isInMassRange(mass)) continue;
+      if (!isInMassRange(mass)) continue;
 
-    auto fitterState = m_vertexFitter->makeState(*state.context);
-    std::unique_ptr<xAOD::Vertex> vertex(fit(tracklist, fitterState.get()));
-    ++mon_nVertexFitterCalls;
-    if (!vertex) continue;
+      auto fitterState = m_vertexFitter->makeState(state.context());
+      auto vertex = fit(tracklist, m_trkMass[iTrk], *fitterState);
+      ++mon_nVertexFitterCalls;
+      if (!vertex) continue;
 
-    ATH_MSG_DEBUG( "Filter found a subset of tracks which passed the rough selection: stop looking for other combinations" );
-    state.isEventAccepted = 1;
-    break;
+      ATH_MSG_DEBUG( "Filter found a subset of tracks which passed the rough selection: stop looking for other combinations" );
+      state.setEventAccepted(true);
+      break;
 
-  } while (std::prev_permutation(idx.begin(), idx.end()));
+    } while (std::prev_permutation(idx.begin(), idx.end()));
+  }
 
-  if (!state.isEventAccepted) {
+  if (!state.isEventAccepted()) {
     ATH_MSG_DEBUG( "Filter could not find a good subset of tracks" );
   }
 
-  mon_isEventAccepted = state.isEventAccepted;
+  mon_isEventAccepted = state.isEventAccepted();
 
   return StatusCode::SUCCESS;
 }
 
-template<typename T>
-StatusCode TrigMultiTrkComboHypo::findDiTrackCandidates(TrigMultiTrkStateCand<T>& state) const {
 
-  state.trigBphysIndices.clear();
-  auto& legs = state.LegList;
+template<typename T>
+StatusCode TrigMultiTrkComboHypo::findMultiLeptonCandidates(TrigMultiTrkState<T>& state) const {
 
-  SG::ReadCondHandle<InDet::BeamSpotData> beamSpotHandle {m_beamSpotKey, *state.context};
-  ATH_CHECK( beamSpotHandle.isValid() );
+  state.trigBphysLegIndices().clear();
+  const auto& leptons = state.leptons();
 
   // monitored variables
-  auto mon_nAcceptedTrk = Monitored::Scalar<int>("nAcceptedTrk", legs.size());
+  auto mon_nAcceptedTrk = Monitored::Scalar<int>("nAcceptedTrk", leptons.size());
   auto mon_nCombination = Monitored::Scalar<int>("nCombination", 0);
   auto mon_nCombinationBeforeFit = Monitored::Scalar<int>("nCombinationBeforeFit", 0);
   auto mon_nBPhysObject = Monitored::Scalar<int>("nBPhysObject", 0);
@@ -469,9 +532,9 @@ StatusCode TrigMultiTrkComboHypo::findDiTrackCandidates(TrigMultiTrkStateCand<T>
   std::vector<float> etatrack1, etatrack2;
   std::vector<int> bphysCharge;
   auto mon_trkMassBeforeFit = Monitored::Collection("trkMassBeforeFit", trkMassBeforeFit);
-  auto mon_bphysChi2 = Monitored::Collection("bphysChi2", *state.trigBphysCollection, &xAOD::TrigBphys::fitchi2);
-  auto mon_bphysLxy = Monitored::Collection("bphysLxy", *state.trigBphysCollection, &xAOD::TrigBphys::lxy);
-  auto mon_bphysFitMass = Monitored::Collection("bphysFitMass", *state.trigBphysCollection, [](const xAOD::TrigBphys* x){ return x->fitmass()*0.001; });
+  auto mon_bphysChi2 = Monitored::Collection("bphysChi2", state.trigBphysCollection(), &xAOD::TrigBphys::fitchi2);
+  auto mon_bphysLxy = Monitored::Collection("bphysLxy", state.trigBphysCollection(), &xAOD::TrigBphys::lxy);
+  auto mon_bphysFitMass = Monitored::Collection("bphysFitMass", state.trigBphysCollection(), [](const xAOD::TrigBphys* x){ return x->fitmass()*0.001; });
   auto mon_bphysMass = Monitored::Collection("bphysMass", bphysMass);
   auto mon_d0track1 = Monitored::Collection("bphysd0_trk1", d0track1);
   auto mon_d0track2 = Monitored::Collection("bphysd0_trk2", d0track2);
@@ -488,90 +551,151 @@ StatusCode TrigMultiTrkComboHypo::findDiTrackCandidates(TrigMultiTrkStateCand<T>
     mon_trkMassBeforeFit, mon_bphysChi2, mon_bphysLxy, mon_bphysFitMass, mon_bphysMass, mon_bphysCharge, mon_d0track1, mon_d0track2,
     mon_timer, mon_pttrack1, mon_pttrack2, mon_etatrack1, mon_etatrack2);
 
-  if (legs.size() < m_nTrk) {
-    ATH_MSG_DEBUG( "Could not build a subset of " << m_nTrk.value() << " legs from collection which contains only " << legs.size() << " objects" );
-    return StatusCode::SUCCESS;
-  }
-  ATH_MSG_DEBUG( "Consider all combinations of " << m_nTrk.value() << "legs from collection which contains " << legs.size() << " objects" );
-
-  std::vector<size_t> leptonIndices(m_nTrk);
-  std::vector<ElementLink<xAOD::TrackParticleContainer>> tracklist(m_nTrk);
-  std::vector<xAOD::TrackParticle::GenVecFourMom_t> p(m_nTrk);
-
-  std::vector<char> leptonTags(legs.size(), 0);
-  std::fill(leptonTags.begin(), leptonTags.begin() + m_nTrk, 1);
-  do {
-    // fill tracklist and momenta of muons in the current subset
-    bool isValidCombination = true;
-    int charge = 0;
-    size_t j = 0;
-    for (size_t i = 0; i < leptonTags.size(); ++i) {
-      if (!leptonTags[i]) continue;
-      leptonIndices[j] = i;
-      auto leg = *legs[i].link;
-      charge += static_cast<int>(lround(leg->charge()));
-      ElementLink<xAOD::TrackParticleContainer> trackEL;
-      if constexpr(std::is_same<T, xAOD::MuonContainer>::value){
-         trackEL = leg->inDetTrackParticleLink();
-      } else{
-         trackEL = leg->trackParticleLink();
+  for (size_t iTrk = 0; iTrk < m_nTrk.size(); ++iTrk) {
+    size_t nTrk = m_nTrk[iTrk];
+
+    if (leptons.size() < nTrk) {
+      ATH_MSG_DEBUG( "Could not build a subset of " << nTrk << " legs from collection which contains only " << leptons.size() << " objects" );
+      continue;
+    }
+    ATH_MSG_DEBUG( "Consider all combinations of " << nTrk << " legs from collection which contains " << leptons.size() << " objects" );
+
+    std::vector<size_t> leptonIndices(nTrk);
+    std::vector<ElementLink<xAOD::TrackParticleContainer>> tracklist(nTrk);
+    std::vector<xAOD::TrackParticle::GenVecFourMom_t> p(nTrk);
+
+    std::vector<char> combination(leptons.size(), 0);
+    std::fill(combination.begin(), combination.begin() + nTrk, 1);
+    do {
+      // fill tracklist and momenta of muons in the current subset
+      bool isValidCombination = true;
+      int charge = 0;
+      size_t j = 0;
+      for (size_t i = 0; i < combination.size(); ++i) {
+        if (!combination[i]) continue;
+        leptonIndices[j] = i;
+        auto leg = *leptons[i].link;
+        charge += static_cast<int>(lround(leg->charge()));
+        ElementLink<xAOD::TrackParticleContainer> trackEL;
+        if constexpr(std::is_same<T, xAOD::MuonContainer>::value) {
+          trackEL = leg->inDetTrackParticleLink();
+        }
+        else {
+          trackEL = leg->trackParticleLink();
+        }
+        tracklist[j] = trackEL;
+        p[j] = (*trackEL)->genvecP4();
+        p[j].SetM(m_trkMass[iTrk][j]);
+        if (p[j].Pt() < m_trkPt[iTrk][j]) {
+          isValidCombination = false;
+          break;
+        }
+        ++j;
       }
-      tracklist[j] = trackEL;
-      p[j] = (*trackEL)->genvecP4();
-      p[j].SetM(m_trkMass[j]);
-      if (p[j].Pt() < m_trkPt[j]) {
-        isValidCombination = false;
-        break;
+      if (!isValidCombination) continue;
+
+      if (msgLvl(MSG::DEBUG)) {
+        ATH_MSG_DEBUG( "Dump found leptons before vertex fit: pT / eta / phi / charge" );
+        for (size_t i = 0; i < tracklist.size(); ++i) {
+          const auto track = *tracklist[i];
+          ATH_MSG_DEBUG( "legs " << i + 1 << ": " << p[i].Pt() << " / " << p[i].Eta() << " / " << p[i].Phi() << " / " << track->charge() );
+        }
       }
-      ++j;
-    }
-    if (!isValidCombination) continue;
 
-    if (msgLvl(MSG::DEBUG)) {
-      ATH_MSG_DEBUG( "Dump found leptons before vertex fit: pT / eta / phi / charge" );
-      for (size_t i = 0; i < tracklist.size(); ++i) {
-        const auto track = *tracklist[i];
-        ATH_MSG_DEBUG( "legs " << i + 1 << ": " << p[i].Pt() << " / " << p[i].Eta() << " / " << p[i].Phi() << " / " << track->charge() );
-      }
-    }
+      auto mass = (std::accumulate(p.begin(), p.end(), xAOD::TrackParticle::GenVecFourMom_t())).M();
+      ATH_MSG_DEBUG( "invariant mass: " << mass );
 
-    auto mass = (std::accumulate(p.begin(), p.end(), xAOD::TrackParticle::GenVecFourMom_t())).M();
-    ATH_MSG_DEBUG( "invariant mass: " << mass );
+      mon_nCombination++;
+      trkMassBeforeFit.push_back(mass * 0.001);
+      if (!isInMassRange(mass)) continue;
 
-    mon_nCombination++;
-    trkMassBeforeFit.push_back(mass * 0.001);
-    if (!isInMassRange(mass)) continue;
+      mon_nCombinationBeforeFit++;
+      auto fitterState = m_vertexFitter->makeState(state.context());
+      auto vertex = fit(tracklist, m_trkMass[iTrk], *fitterState);
+      if (!vertex) continue;
+      xAOD::TrigBphys* trigBphys = makeTrigBPhys(*vertex, m_trkMass[iTrk], state.beamSpotPosition(), *fitterState);
+      state.addTrigBphysObject(trigBphys, leptonIndices);
 
-    mon_nCombinationBeforeFit++;
-    auto fitterState = m_vertexFitter->makeState(*state.context);
-    std::unique_ptr<xAOD::Vertex> vertex(fit(tracklist, fitterState.get()));
-    if (!vertex) continue;
-    xAOD::TrigBphys* trigBphys = makeTrigBPhys(vertex.get(), fitterState.get(), beamSpotHandle->beamPos());
-    state.trigBphysCollection->push_back(trigBphys);
-    state.trigBphysIndices.push_back(leptonIndices);
+      mon_nBPhysObject++;
+      bphysMass.push_back(mass * 0.001);
+      bphysCharge.push_back(charge);
+      d0track1.push_back((*tracklist[0])->d0());
+      d0track2.push_back((*tracklist[1])->d0());
+      pttrack1.push_back((*tracklist[0])->pt() * 0.001);
+      pttrack2.push_back((*tracklist[1])->pt() * 0.001);
+      etatrack1.push_back((*tracklist[0])->eta());
+      etatrack2.push_back((*tracklist[1])->eta());
+
+    } while (std::prev_permutation(combination.begin(), combination.end()));
+  }
+  return StatusCode::SUCCESS;
+}
 
-    mon_nBPhysObject++;
-    bphysMass.push_back(mass * 0.001);
-    bphysCharge.push_back(charge);
-    d0track1.push_back((*tracklist[0])->d0());
-    d0track2.push_back((*tracklist[1])->d0());
-    pttrack1.push_back((*tracklist[0])->pt() * 0.001);
-    pttrack2.push_back((*tracklist[1])->pt() * 0.001);
-    etatrack1.push_back((*tracklist[0])->eta());
-    etatrack2.push_back((*tracklist[1])->eta());
 
-  } while (std::prev_permutation(leptonTags.begin(), leptonTags.end()));
+StatusCode TrigMultiTrkComboHypo::processMergedElectrons(TrigMultiTrkState<xAOD::ElectronContainer>& state) const {
+
+  ATH_MSG_DEBUG( "Try to find electrons originating from the same EM cluster" );
+
+  // some electrons can be already attached to the list, add electrons from BPH-0DR3-EM7J15 clusters to the end
+  auto& leptons = state.leptons();
+
+  if (m_mergedElectronIDs.empty()) {
+    ATH_MSG_DEBUG( "no chains similar to BPH-0DR3-EM7J15 have been requested, should not look for close-by electrons" );
+    return StatusCode::SUCCESS;
+  }
+
+  const std::vector<double> particleMasses(2, PDG::mElectron);
+
+  for (const Decision* decision : state.previousDecisions()) {
+    if (!TrigCompositeUtils::isAnyIDPassing(decision, m_mergedElectronIDs)) continue;
+
+    auto decisionEL = TrigCompositeUtils::decisionToElementLink(decision, state.context());
+    ATH_CHECK( decision->hasObjectLink(TrigCompositeUtils::featureString(), ClassID_traits<xAOD::ElectronContainer>::ID()) );
+    auto electronEL = decision->objectLink<xAOD::ElectronContainer>(TrigCompositeUtils::featureString());
+    const auto electron = *electronEL;
+    if (!electron->trackParticle()) continue;
+
+    // get all electrons from the same SG::View, i.e. from the same initialRoI
+    const auto electronContainer = electronEL.getStorableObjectPointer();
+    if (electronContainer->size() <= 1) continue;
+
+    // add electron from decision to state.leptons
+    DecisionIDContainer decisionIDs;
+    TrigCompositeUtils::decisionIDs(decision, decisionIDs);
+    leptons.push_back({electronEL, ElementLinkVector<DecisionContainer>(1, decisionEL), decisionIDs});
+
+    // get initialRoI this electron originating from
+    auto roiInfo = TrigCompositeUtils::findLink<TrigRoiDescriptorCollection>(decision, TrigCompositeUtils::initialRoIString(), true);
+    ATH_CHECK( roiInfo.isValid() );
+    auto initialRoI = *roiInfo.link;
+
+    // try to build di-electron pairs: first electron is always from decision, second from the same SG::View
+    std::vector<ElementLink<xAOD::TrackParticleContainer>> tracklist(2);
+    tracklist[0] = electron->trackParticleLink();
+    for (size_t i = 0; i < electronContainer->size(); ++i) {
+      const auto electronFromView = electronContainer->at(i);
+      if (electronFromView == electron) continue;
+      if (!electronFromView->trackParticle()) continue;
+      tracklist[1] = electronFromView->trackParticleLink();
+
+      auto fitterState = m_vertexFitter->makeState(state.context());
+      auto vertex = fit(tracklist, particleMasses, *fitterState);
+      if (!vertex) continue;
+      xAOD::TrigBphys* trigBphys = makeTrigBPhys(*vertex, particleMasses, state.beamSpotPosition(), *fitterState);
+      trigBphys->setRoiId(initialRoI->roiWord());
+      state.addTrigBphysObject(trigBphys, std::vector<size_t>(1, leptons.size() - 1));
+    }
+  }
 
   return StatusCode::SUCCESS;
 }
 
 
+StatusCode TrigMultiTrkComboHypo::copyDecisionObjects(TrigMultiTrkStateBase& state) const {
 
-StatusCode TrigMultiTrkComboHypo::copyDecisionObjects(TrigMultiTrkState& state) const {
-
-  if (state.isEventAccepted) {
+  if (state.isEventAccepted()) {
     ATH_MSG_DEBUG( "Copying decisions from " << decisionsInput().at(0).key() << " to " << decisionsOutput().at(0).key() );
-    for (const Decision* previousDecision : *state.previousDecisions) {
+    for (const Decision* previousDecision : state.previousDecisions()) {
       if (!TrigCompositeUtils::isAnyIDPassing(previousDecision, m_allowedIDs)) continue;
 
       DecisionIDContainer previousDecisionIDs;
@@ -580,39 +704,42 @@ StatusCode TrigMultiTrkComboHypo::copyDecisionObjects(TrigMultiTrkState& state)
       std::set_intersection(previousDecisionIDs.begin(), previousDecisionIDs.end(), m_allowedIDs.begin(), m_allowedIDs.end(),
                             std::inserter(decisionIDs, decisionIDs.begin()));
 
-      Decision* decision = TrigCompositeUtils::newDecisionIn(state.decisions, TrigCompositeUtils::comboHypoAlgNodeName());
-      TrigCompositeUtils::linkToPrevious(decision, previousDecision, *state.context);
+      Decision* decision = TrigCompositeUtils::newDecisionIn(&state.decisions(), TrigCompositeUtils::comboHypoAlgNodeName());
+      TrigCompositeUtils::linkToPrevious(decision, previousDecision, state.context());
       TrigCompositeUtils::insertDecisionIDs(decisionIDs, decision);
     }
-  }
 
+    // copy additional decisions for combined chains, as 'HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_mu4_L1BPH-0M9-EM7-EM5_MU6'
+    ATH_CHECK( copyAdditionalDecisionObjects(state) );
+  }
   return StatusCode::SUCCESS;
 }
 
 
-StatusCode TrigMultiTrkComboHypo::createDecisionObjects(TrigMultiTrkState& state) const {
+StatusCode TrigMultiTrkComboHypo::createDecisionObjects(TrigMultiTrkStateBase& state) const {
 
   size_t idx = 0;
-  for (const xAOD::TrigBphys* triggerObject : *state.trigBphysCollection) {
+  for (const xAOD::TrigBphys* triggerObject : state.trigBphysCollection()) {
     ATH_MSG_DEBUG( "Found xAOD::TrigBphys: mass / chi2 = " << triggerObject->mass() << " / " << triggerObject->fitchi2() );
 
-    auto triggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(*state.trigBphysCollection, triggerObject->index());
+    auto triggerObjectEL = ElementLink<xAOD::TrigBphysContainer>(state.trigBphysCollection(), triggerObject->index());
     ATH_CHECK( triggerObjectEL.isValid() );
 
     // create a new output Decision object, backed by the 'decisions' container.
-    Decision* decision = TrigCompositeUtils::newDecisionIn(state.decisions, TrigCompositeUtils::comboHypoAlgNodeName());
+    Decision* decision = TrigCompositeUtils::newDecisionIn(&state.decisions(), TrigCompositeUtils::comboHypoAlgNodeName());
 
     std::vector<const DecisionIDContainer*> previousDecisionIDs;
-    for (const size_t& i : state.trigBphysIndices[idx]) {
+    for (const size_t& i : state.getTrigBphysLegIndices(idx)) {
 
-          // attach all previous decisions: if the same previous decision is called twice, that's fine - internally takes care of that
-          // we already have an array of links to the previous decisions, so there is no need to use TrigCompositeUtils::linkToPrevious()
-          decision->addObjectCollectionLinks(TrigCompositeUtils::seedString(), state.getDecisionLinks(i));
-          previousDecisionIDs.push_back(&state.getDecisionID(i));
+      // attach all previous decisions: if the same previous decision is called twice, that's fine - internally takes care of that
+      // we already have an array of links to the previous decisions, so there is no need to use TrigCompositeUtils::linkToPrevious()
+      decision->addObjectCollectionLinks(TrigCompositeUtils::seedString(), state.getDecisionLinks(i));
+      previousDecisionIDs.push_back(&state.getDecisionIDs(i));
     }
 
     // set mandatory feature ElementLink to xAOD::TrigBphys object
     decision->setObjectLink<xAOD::TrigBphysContainer>(TrigCompositeUtils::featureString(), triggerObjectEL);
+    decision->setDetail<int32_t>("noCombo", 1);
 
     for (const auto& tool : hypoTools()) {
       ATH_MSG_DEBUG( "Go to " << tool );
@@ -621,12 +748,46 @@ StatusCode TrigMultiTrkComboHypo::createDecisionObjects(TrigMultiTrkState& state
     ++idx;
   }
 
+  // copy additional decisions from Empty muon step for Combined chains, as 'HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_mu4_L1BPH-0M9-EM7-EM5_MU6'
+  ATH_CHECK( copyAdditionalDecisionObjects(state) );
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrigMultiTrkComboHypo::copyAdditionalDecisionObjects(TrigMultiTrkStateBase& state) const {
+
+  if (decisionsInput().size() > 1) {
+    ATH_MSG_DEBUG( "Found more than one decision input key, decisionsInput().size = " << decisionsInput().size() );
+    for (size_t i = 1; i < decisionsInput().size(); ++i) {
+      ATH_MSG_DEBUG( "Copying decisions from " << decisionsInput().at(i).key() << " to " << decisionsOutput().at(i).key() );
+      auto previousDecisionsHandle = SG::makeHandle(decisionsInput().at(i), state.context());
+      CHECK( previousDecisionsHandle.isValid() );
+      ATH_MSG_DEBUG( "Running with "<< previousDecisionsHandle->size() << " previous decisions" );
+      SG::WriteHandle<DecisionContainer> outputDecisionsHandle = TrigCompositeUtils::createAndStore(decisionsOutput().at(i), state.context());
+      for (const Decision* previousDecision : *previousDecisionsHandle) {
+        if (!TrigCompositeUtils::isAnyIDPassing(previousDecision, m_allowedIDs)) continue;
+
+        DecisionIDContainer previousDecisionIDs;
+        TrigCompositeUtils::decisionIDs(previousDecision, previousDecisionIDs);
+        DecisionIDContainer decisionIDs;
+        std::set_intersection(previousDecisionIDs.begin(), previousDecisionIDs.end(), m_allowedIDs.begin(), m_allowedIDs.end(),
+                              std::inserter(decisionIDs, decisionIDs.begin()));
+
+        Decision* decision = TrigCompositeUtils::newDecisionIn(outputDecisionsHandle.ptr(), TrigCompositeUtils::comboHypoAlgNodeName());
+        TrigCompositeUtils::linkToPrevious(decision, previousDecision, state.context());
+        TrigCompositeUtils::insertDecisionIDs(decisionIDs, decision);
+      }
+    }
+  }
   return StatusCode::SUCCESS;
 }
 
 
-xAOD::Vertex* TrigMultiTrkComboHypo::fit(const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
-         Trk::IVKalState* fitterState) const {
+std::unique_ptr<xAOD::Vertex> TrigMultiTrkComboHypo::fit(
+    const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
+    const std::vector<double>& particleMasses,
+    Trk::IVKalState& fitterState) const {
 
   ATH_MSG_DEBUG( "Perform vertex fit" );
   std::vector<const xAOD::TrackParticle*> tracklist(trackParticleLinks.size(), nullptr);
@@ -641,16 +802,16 @@ xAOD::Vertex* TrigMultiTrkComboHypo::fit(const std::vector<ElementLink<xAOD::Tra
   if (errorcode != 0) startingPoint = Amg::Vector3D::Zero(3);
   ATH_MSG_DEBUG( "Starting point: (" << startingPoint(0) << ", " << startingPoint(1) << ", " << startingPoint(2) << ")" );
 
-  m_vertexFitter->setMassInputParticles(m_trkMass, *fitterState);
-  xAOD::Vertex* vertex = m_vertexFitter->fit(tracklist, startingPoint, *fitterState);
+  m_vertexFitter->setMassInputParticles(particleMasses, fitterState);
+  std::unique_ptr<xAOD::Vertex> vertex(m_vertexFitter->fit(tracklist, startingPoint, fitterState));
   if (!vertex) {
     ATH_MSG_DEBUG( "Vertex fit fails" );
-    return nullptr;
+    return vertex;
   }
   if (vertex->chiSquared() > m_chi2) {
     ATH_MSG_DEBUG( "Fit is successful, but vertex chi2 is too high, we are not going to save it (chi2 = " << vertex->chiSquared() << " > " << m_chi2.value() << ")" );
-    delete vertex;
-    return nullptr;
+    vertex.reset();
+    return vertex;
   }
   ATH_MSG_DEBUG( "Fit is successful" );
   vertex->clearTracks();
@@ -659,18 +820,23 @@ xAOD::Vertex* TrigMultiTrkComboHypo::fit(const std::vector<ElementLink<xAOD::Tra
 }
 
 
-xAOD::TrigBphys* TrigMultiTrkComboHypo::makeTrigBPhys(xAOD::Vertex* vertex, Trk::IVKalState* fitterState, const Amg::Vector3D& beamspot) const {
+xAOD::TrigBphys* TrigMultiTrkComboHypo::makeTrigBPhys(
+    const xAOD::Vertex& vertex,
+    const std::vector<double>& particleMasses,
+    const Amg::Vector3D& beamSpot,
+    const Trk::IVKalState& fitterState) const {
+
   double invariantMass = 0.;
   double invariantMassError = 0.;
-  if (!m_vertexFitter->VKalGetMassError(invariantMass, invariantMassError, *fitterState).isSuccess()) {
+  if (!m_vertexFitter->VKalGetMassError(invariantMass, invariantMassError, fitterState).isSuccess()) {
     ATH_MSG_DEBUG( "Warning from TrkVKalVrtFitter: can not calculate uncertainties" );
     invariantMass = -9999.;
   }
 
-  xAOD::TrackParticle::GenVecFourMom_t momentum(0, 0, 0, 0);
-  for (size_t i = 0; i < vertex->nTrackParticles(); ++i) {
-    auto p = vertex->trackParticle(i)->genvecP4();
-    p.SetM(m_trkMass[i]);
+  xAOD::TrackParticle::GenVecFourMom_t momentum;
+  for (size_t i = 0; i < vertex.nTrackParticles(); ++i) {
+    auto p = vertex.trackParticle(i)->genvecP4();
+    p.SetM(particleMasses[i]);
     momentum += p;
   }
 
@@ -678,14 +844,15 @@ xAOD::TrigBphys* TrigMultiTrkComboHypo::makeTrigBPhys(xAOD::Vertex* vertex, Trk:
   result->makePrivateStore();
   result->initialise(0, momentum.Eta(), momentum.Phi(), momentum.Pt(), xAOD::TrigBphys::MULTIMU, momentum.M(), xAOD::TrigBphys::EF);
 
+  if (m_doElectrons) result->setParticleType(xAOD::TrigBphys::JPSIEE);
   result->setFitmass(invariantMass);
-  result->setFitchi2(vertex->chiSquared());
-  result->setFitndof(vertex->numberDoF());
-  result->setFitx(vertex->x());
-  result->setFity(vertex->y());
-  result->setFitz(vertex->z());
-  result->setTrackParticleLinks(vertex->trackParticleLinks());
-  result->setLxy(Lxy(result, beamspot));
+  result->setFitchi2(vertex.chiSquared());
+  result->setFitndof(vertex.numberDoF());
+  result->setFitx(vertex.x());
+  result->setFity(vertex.y());
+  result->setFitz(vertex.z());
+  result->setTrackParticleLinks(vertex.trackParticleLinks());
+  result->setLxy(Lxy(*result, beamSpot));
 
   ATH_MSG_DEBUG(
     "TrigBphys objects:\n\t  " <<
@@ -697,8 +864,8 @@ xAOD::TrigBphys* TrigMultiTrkComboHypo::makeTrigBPhys(xAOD::Vertex* vertex, Trk:
     "mass:          " << result->mass() << "\n\t  " <<
     "fitmass:       " << result->fitmass() << "\n\t  " <<
     "chi2/NDF:      " << result->fitchi2() << " / " << result->fitndof() << "\n\t  " <<
-    "vertex:        (" << result->fitx() << ", " << result->fity() << ", " << result->fitz() << "\n\t  " <<
-    "Lxy            " << result->lxy() << ")" );
+    "vertex:        (" << result->fitx() << ", " << result->fity() << ", " << result->fitz() << ")\n\t  " <<
+    "Lxy:           " << result->lxy() );
 
   return result;
 }
@@ -722,10 +889,10 @@ bool TrigMultiTrkComboHypo::isIdenticalTracks(const xAOD::Electron* lhs, const x
 }
 
 
-float TrigMultiTrkComboHypo::Lxy(const xAOD::TrigBphys* vertex, const Amg::Vector3D& beamSpot) const {
+float TrigMultiTrkComboHypo::Lxy(const xAOD::TrigBphys& vertex, const Amg::Vector3D& beamSpot) const {
 
-  XYVector R(vertex->fitx() - beamSpot.x(), vertex->fity() - beamSpot.y());
-  const auto& trackParticleLinks = vertex->trackParticleLinks();
+  XYVector R(vertex.fitx() - beamSpot.x(), vertex.fity() - beamSpot.y());
+  const auto& trackParticleLinks = vertex.trackParticleLinks();
   auto pT = std::accumulate(trackParticleLinks.begin(), trackParticleLinks.end(), XYVector(),
                             [](const auto& pT, const auto& trackEL){ const auto& p = (*trackEL)->genvecP4(); return pT + XYVector(p.x(), p.y()); });
   return R.Dot(pT.unit());
@@ -743,5 +910,3 @@ bool TrigMultiTrkComboHypo::isInMassRange(double mass) const {
   }
   return result;
 }
-
-TrigMultiTrkState::~TrigMultiTrkState(){ }
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.h b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.h
index 825222b9dc8558cf7cbd82f92c405532ecc2a36f..442527191337e7a466b6a87d88cb54bf8f5a314d 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.h
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypo.h
@@ -43,6 +43,8 @@
 #include "TrigMultiTrkComboHypoTool.h"
 #include "BeamSpotConditionsData/BeamSpotData.h"
 
+#include "AthViews/View.h"
+
 namespace Trk {
 class IVKalState;
 }
@@ -53,34 +55,66 @@ class IVKalState;
  * @brief State class for TrigMultiTrkComboHypo algorithm
  */
 
-class TrigMultiTrkState: public ::ITrigBphysState {
+class TrigMultiTrkStateBase: public ::ITrigBphysState {
  public:
-  virtual ~TrigMultiTrkState();
-  const EventContext* context;
-  const TrigCompositeUtils::DecisionContainer* previousDecisions;
-  TrigCompositeUtils::DecisionContainer* decisions;
-  xAOD::TrigBphysContainer* trigBphysCollection;
-  std::vector<ElementLink<xAOD::TrackParticleContainer>> tracks;
-  int isEventAccepted;
-  std::vector<std::vector<size_t>> trigBphysIndices;
-  virtual ElementLinkVector<TrigCompositeUtils::DecisionContainer>& getDecisionLinks(size_t) =0 ;
-  virtual TrigCompositeUtils::DecisionIDContainer& getDecisionID(size_t) =0 ;
+  TrigMultiTrkStateBase() = delete;
+  TrigMultiTrkStateBase(const EventContext& context,
+                        const TrigCompositeUtils::DecisionContainer& previousDecisions,
+                        TrigCompositeUtils::DecisionContainer& decisions,
+                        xAOD::TrigBphysContainer* trigBphysCollection = nullptr,
+                        const InDet::BeamSpotData* beamSpotData = nullptr)
+      : ITrigBphysState(context, previousDecisions, decisions, trigBphysCollection, beamSpotData),
+        m_isEventAccepted(0) {}
+  virtual ~TrigMultiTrkStateBase() = default;
+  virtual ElementLinkVector<TrigCompositeUtils::DecisionContainer>& getDecisionLinks(size_t) = 0;
+  virtual TrigCompositeUtils::DecisionIDContainer& getDecisionIDs(size_t) = 0;
+  virtual void addTrigBphysObject(xAOD::TrigBphys*, const std::vector<size_t>& legIndices) = 0;
+
+  void setEventAccepted(bool flag = true) { m_isEventAccepted = (flag ? 1 : 0); }
+  inline int isEventAccepted() const { return m_isEventAccepted; }
+  inline std::vector<std::vector<size_t>>& trigBphysLegIndices() { return m_trigBphysLegIndices; }
+  inline std::vector<size_t>& getTrigBphysLegIndices(size_t idx) { return m_trigBphysLegIndices[idx]; }
+  inline std::vector<ElementLink<xAOD::TrackParticleContainer>>& tracks() { return m_tracks; }
+
+ private:
+  int m_isEventAccepted;
+  std::vector<std::vector<size_t>> m_trigBphysLegIndices;
+  std::vector<ElementLink<xAOD::TrackParticleContainer>> m_tracks;
 };
 
+
 template<typename CONTAINER>
-class TrigMultiTrkStateCand : public TrigMultiTrkState{
+class TrigMultiTrkState : public TrigMultiTrkStateBase {
  public:
-  virtual ~TrigMultiTrkStateCand() = default;
-  struct LEG {
+  TrigMultiTrkState() = delete;
+  TrigMultiTrkState(const EventContext& context,
+                    const TrigCompositeUtils::DecisionContainer& previousDecisions,
+                    TrigCompositeUtils::DecisionContainer& decisions,
+                    xAOD::TrigBphysContainer* trigBphysCollection = nullptr,
+                    const InDet::BeamSpotData* beamSpotData = nullptr)
+      : TrigMultiTrkStateBase(context, previousDecisions, decisions, trigBphysCollection, beamSpotData) {}
+  virtual ~TrigMultiTrkState() = default;
+
+  struct LEPTON {
     ElementLink<CONTAINER> link;
     ElementLinkVector<TrigCompositeUtils::DecisionContainer> decisionLinks;
     TrigCompositeUtils::DecisionIDContainer decisionIDs;
   };
-  std::vector<LEG> LegList;
-  virtual ElementLinkVector<TrigCompositeUtils::DecisionContainer>& getDecisionLinks(size_t i)  override 
-  { return LegList.at(i).decisionLinks; }
-  virtual TrigCompositeUtils::DecisionIDContainer& getDecisionID(size_t i)  override
-  { return LegList.at(i).decisionIDs; }
+
+  std::vector<LEPTON>& leptons() { return m_leptons; }
+  virtual ElementLinkVector<TrigCompositeUtils::DecisionContainer>& getDecisionLinks(size_t i) override final {
+    return m_leptons.at(i).decisionLinks;
+  }
+  virtual TrigCompositeUtils::DecisionIDContainer& getDecisionIDs(size_t i) override final {
+    return m_leptons.at(i).decisionIDs;
+  }
+  virtual void addTrigBphysObject(xAOD::TrigBphys* trigBphysObject, const std::vector<size_t>& legIndices) override final {
+    trigBphysCollection().push_back(trigBphysObject);
+    trigBphysLegIndices().push_back(legIndices);
+  }
+
+ private:
+  std::vector<LEPTON> m_leptons;
 };
 
 
@@ -95,32 +129,88 @@ class TrigMultiTrkComboHypo: public ::ComboHypo {
 
  private:
 
-  void              FillState(TrigMultiTrkState* state,
-                       const EventContext* context,
-                       const TrigCompositeUtils::DecisionContainer* previousDecisions,
-                       TrigCompositeUtils::DecisionContainer* decisions,
-                       xAOD::TrigBphysContainer* trigBphysCollection) const;
+  /**
+   * @brief Go through state.previousDecisions() and fetch xAOD::TrackParticle objects associated with the nearest SG::View.
+   * Enable overlap removal to get collection of unique objects at state.tracks().
+   */
+  StatusCode mergeTracksFromViews(TrigMultiTrkStateBase&) const;
+
+  /**
+   * @brief Go through state.previousDecisions(), fetch xAOD::Muons/xAOD::Electron objects attached to decisions
+   * and save links to the their xAOD::TrackParticle objects in state.tracks().
+   */
+  template<typename CONTAINER>
+  StatusCode mergeTracksFromDecisions(TrigMultiTrkStateBase&) const;
+
+  /**
+   * @brief Go through state.previousDecisions(), fetch xAOD::Muons/xAOD::Electron objects attached to decisions
+   * and save links to them in state.leptons().
+   */
+  template<typename CONTAINER>
+  StatusCode mergeLeptonsFromDecisions(TrigMultiTrkState<CONTAINER>&) const;
+
+  /**
+   * @brief Make all possible combinations from state.leptons(), fit tracks to the common vertex,
+   * create xAOD::TrigBphys objects and put them into state.trigBphysCollection()
+   *
+   * combination is a selection of items from a collection, such that the order of items does not matter
+   * leptons/tracks have already been sorted over pT, the algorithm will keep this order in created subsets of tracks, i.e.
+   * for nTrk = 2 combinations are [0, 1], [0, 2], ..., [1, 2], [1, 3], ...
+   * for nTrk = 3 combinations are [0, 1, 2], [0, 1, 3], ..., [0, 2, 3], [0, 2, 4], ..., [1, 2, 3], [1, 2, 4], ...
+   */
+  template<typename CONTAINER>
+  StatusCode findMultiLeptonCandidates(TrigMultiTrkState<CONTAINER>&) const;
+
+  /**
+   * @brief Make all possible combinations from state.tracks(), fit tracks to the common vertex
+   * and set state.isEventAccepted to true if at least one good combination is found;
+   * no xAOD::TrigBphys objects will be created.
+   */
+  StatusCode filterTrackCombinations(TrigMultiTrkStateBase&) const;
+
+  /**
+   * @brief Make all possible combinations from electrons originating from the same BPH-0DR3-EM7J15 cluster,
+   * only one track should pass e5_lhvloose requirements;
+   * initialRoI word will be saved into xAOD::TrigBphys objects.
+   */
+  StatusCode processMergedElectrons(TrigMultiTrkState<xAOD::ElectronContainer>&) const;
+
+  /**
+   * @brief All appropriate decisions from state.previousDecisions() will be copied to state.decisions()
+   * if flag state.isEventAccepted() is set by filterTrackCombinations() method;
+   * to be used only in Streamer mode.
+   */
+  StatusCode copyDecisionObjects(TrigMultiTrkStateBase&) const;
+
+  /**
+   * @brief For chains from CombinedSlice (similar to 'HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_mu4_L1BPH-0M9-EM7-EM5_MU6')
+   * we have decisionsInput().size() > 1, so that we should copy decisions created by EmptySteps from muon part.
+   */
+  StatusCode copyAdditionalDecisionObjects(TrigMultiTrkStateBase&) const;
+
+  /**
+   * @brief Create a decision for each xAOD::TrigBphys object from state.trigBphysCollection() and save it to state.decisions();
+   * use hypoTools() to assign correct decisionIDs, not compatible with Streamer mode.
+   */
+  StatusCode createDecisionObjects(TrigMultiTrkStateBase&) const;
+
+
+  std::unique_ptr<xAOD::Vertex> fit(
+      const std::vector<ElementLink<xAOD::TrackParticleContainer>>& trackParticleLinks,
+      const std::vector<double>& particleMasses,
+      Trk::IVKalState& fitterState) const;
+
+  xAOD::TrigBphys* makeTrigBPhys(
+      const xAOD::Vertex& vertex,
+      const std::vector<double>& particleMasses,
+      const Amg::Vector3D& beamSpot,
+      const Trk::IVKalState& fitterState) const;
 
-  template<typename T>
-  StatusCode mergeFromDecisions(TrigMultiTrkStateCand<T>&) const;
-
-  StatusCode mergeTracksFromViews(TrigMultiTrkState&) const;
-  StatusCode mergeTracksFromDecisions(TrigMultiTrkState&) const;
-  StatusCode filterTrackCombinations(TrigMultiTrkState&) const;
-  StatusCode copyDecisionObjects(TrigMultiTrkState&) const;
-  
-  template<typename T>
-  StatusCode findDiTrackCandidates(TrigMultiTrkStateCand<T>&) const;
-
-  StatusCode createDecisionObjects(TrigMultiTrkState&) const;
-
-  xAOD::Vertex* fit(const std::vector<ElementLink<xAOD::TrackParticleContainer>>& tracklist, Trk::IVKalState*) const;
-  xAOD::TrigBphys* makeTrigBPhys(xAOD::Vertex* vertex, Trk::IVKalState* fitterState, const Amg::Vector3D& beamspot) const;
   bool isIdenticalTracks(const xAOD::TrackParticle* lhs, const xAOD::TrackParticle* rhs) const;
   bool isIdenticalTracks(const xAOD::Muon* lhs, const xAOD::Muon* rhs) const;
   bool isIdenticalTracks(const xAOD::Electron* lhs, const xAOD::Electron* rhs) const;
   bool isInMassRange(double mass) const;
-  float Lxy(const xAOD::TrigBphys*, const Amg::Vector3D&) const;
+  float Lxy(const xAOD::TrigBphys& vertex, const Amg::Vector3D& beamSpot) const;
 
   SG::ReadHandleKey<xAOD::TrackParticleContainer>
     m_trackParticleContainerKey {this, "TrackCollectionKey", "Tracks", "input TrackParticle container name"};
@@ -131,16 +221,28 @@ class TrigMultiTrkComboHypo: public ::ComboHypo {
   SG::ReadCondHandleKey<InDet::BeamSpotData>
     m_beamSpotKey {this, "BeamSpotKey", "BeamSpotData", "SG key for beam spot"};
 
-  Gaudi::Property<unsigned int> m_nTrk {this, "nTracks", 2, "number of tracks in the vertex"};
-  Gaudi::Property<std::vector<double>> m_trkMass {this, "trackMasses", {105.6583745, 105.6583745}, "track masses for vertex reco (one per track)"};
-  Gaudi::Property<std::vector<float>> m_trkPt {this, "trkPtThresholds", {3650., 3650.} ,"minimum track pTs (one per track, sorted descending)"};
-  Gaudi::Property<std::vector<std::pair<double, double>>> m_massRange {this, "massRanges", { {0., 100000.} }, "mass ranges"};
-  Gaudi::Property<float> m_deltaR {this, "deltaR", 0.01, "minimum deltaR between same-sign tracks (overlap removal)"};
-  Gaudi::Property<float> m_chi2 {this, "chi2", 150., "chi2 cut for the fitted vertex"};
-
-  Gaudi::Property<bool> m_isStreamer {this, "isStreamer", false, "if true we do not create trigger objects, just copy all appropriate decisions to the next step or break the chain"};
-  Gaudi::Property<bool> m_doElectrons {this, "doElectrons", false, "use electrons instead of muons"};
-  Gaudi::Property<std::string> m_trigLevel {this, "trigLevel", "EF", "trigger Level to set for created TrigBphys objects"};
+  Gaudi::Property<std::vector<unsigned int>> m_nTrk {this, "nTracks", {2},
+    "number of tracks to be fitted into the common vertex"};
+  Gaudi::Property<std::vector<std::vector<double>>> m_trkMass {this, "trackMasses", {},
+    "track masses for vertex reco (one per track); muon mass is used by default"};
+  Gaudi::Property<std::vector<std::vector<double>>> m_trkPt {this, "trackPtThresholds", { {3650., 3650.} },
+    "minimum track transverse momenta (one per track, sorted descending)"};
+  Gaudi::Property<std::vector<std::pair<double, double>>> m_massRange {this, "massRange", { {0., 100000.} },
+    "range of the invariant mass of the track combinations"};
+  Gaudi::Property<bool> m_applyOverlapRemoval {this, "applyOverlapRemoval", true,
+    "apply overlap removal for the close-by same-sign objects from different views"};
+  Gaudi::Property<float> m_deltaR {this, "deltaR", 0.01,
+    "minimum deltaR between same-sign tracks (overlap removal)"};
+  Gaudi::Property<float> m_chi2 {this, "chi2", 150.,
+    "chi2 cut for the fitted vertex"};
+  Gaudi::Property<bool> m_isStreamer {this, "isStreamer", false,
+    "if true we do not create trigger objects, just copy all appropriate decisions to the next step or break the chain"};
+  Gaudi::Property<bool> m_doElectrons {this, "doElectrons", false,
+    "use electrons if true, otherwise use muons"};
+  Gaudi::Property<std::string> m_trigLevel {this, "trigLevel", "EF",
+    "trigger Level to set for created TrigBphys objects: L2, L2IO or EF"};
+  Gaudi::Property<std::vector<std::string>> m_mergedElectronChains {this, "mergedElectronChains", {"BPH-0DR3-EM7J15"},
+    "patterns for BPH-0DR3-EM7J15 like chains"};
 
   ToolHandle<InDet::VertexPointEstimator> m_vertexPointEstimator {this, "VertexPointEstimator", "", "tool to find starting point for the vertex fitter"};
   ToolHandle<Trk::TrkVKalVrtFitter> m_vertexFitter {this, "VertexFitter", "", "VKalVrtFitter tool to fit tracks into the common vertex"};
@@ -148,6 +250,11 @@ class TrigMultiTrkComboHypo: public ::ComboHypo {
   ToolHandle<GenericMonitoringTool> m_monTool {this, "MonTool", "", "monitoring tool"};
 
   TrigCompositeUtils::DecisionIDContainer m_allowedIDs;
+  TrigCompositeUtils::DecisionIDContainer m_resolvedElectronIDs;
+  TrigCompositeUtils::DecisionIDContainer m_mergedElectronIDs;
+
+  double m_trkPtMin;
+
 };
 
 #endif  // TRIG_TrigMultiTrkComboHypo_H
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.cxx b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.cxx
index aeb01c226a41ece304479ed51338e7d7c021ac09..adf27b7b2c69c137635ab2307df9a2bd1a6da995 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.cxx
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.cxx
@@ -30,12 +30,32 @@ TrigMultiTrkComboHypoTool::TrigMultiTrkComboHypoTool(const std::string& type, co
 StatusCode TrigMultiTrkComboHypoTool::initialize() {
   ATH_MSG_DEBUG( "configuration for '" << this->name() << "'" << endmsg <<
                  "   AcceptAll = " << (m_acceptAll ? "True" : "False") << endmsg <<
+                 "   isCombinedChain = "  << (m_isCombinedChain ? "True" : "False") << endmsg <<
+                 "   isMergedElectronChain = "  << (m_isMergedElectronChain ? "True" : "False") << endmsg <<
                  "   mass range: ( " <<  m_massRange.value().first << ", " << m_massRange.value().second << " )" << endmsg <<
                  "   chi2 cut: " << m_chi2 << endmsg <<
-                 "   " << (m_totalCharge < 0 ? "total charge cut is disabled" : "total charge cut: only right charge combinations"));
-  ATH_MSG_DEBUG("   LxyCut: > " << m_LxyCut.value());
+                 "   " << (m_totalCharge < 0 ? "total charge cut is disabled" : "total charge cut: only right charge combinations") << endmsg <<
+                 "   LxyCut: > " << m_LxyCut.value() );
 
   ATH_CHECK( m_nTrk >= 2 );
+  ATH_CHECK( !(m_isMergedElectronChain && m_legMultiplicities[0] != 1) );
+
+  ATH_CHECK( !m_legMultiplicities.empty() );
+  if ( m_legMultiplicities.size() > 1 ) {  // chain with asymmetric legs, as HLT_mu6_2mu4_bDimu_L1MU6_3MU4
+    // here we consider each decision as an individual object, i.e. for HLT_mu6_2mu4_bDimu_L1MU6_3MU4 we use
+    // m_legDecisionIDs = {createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 0),
+    //                     createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 1),
+    //                     createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 1)};
+    for (size_t legIndex = 0; legIndex < m_legMultiplicities.size(); ++legIndex) {
+      // can not use legDecisionId(legIndex) at initialize() step, use TrigCompositeUtils::createLegName() instead
+      auto legId = TrigCompositeUtils::createLegName(decisionId(), legIndex);
+      m_legDecisionIDs.insert(m_legDecisionIDs.end(), m_legMultiplicities[legIndex], legId.numeric());
+    }
+  }
+  else {  // chain with symmetric legs, as HLT_2mu4_bDimu_L12MU4
+    m_legDecisionIDs.insert(m_legDecisionIDs.end(), m_legMultiplicities[0], decisionId().numeric());
+  }
+  ATH_CHECK( m_nTrk <= m_legDecisionIDs.size() || m_isMergedElectronChain );
 
   if (!m_monTool.empty()) {
     ATH_CHECK( m_monTool.retrieve() );
@@ -65,8 +85,11 @@ bool TrigMultiTrkComboHypoTool::passed(const xAOD::TrigBphys* trigBphys) const {
 
   auto mon = Monitored::Group( m_monTool, mon_totalCharge, mon_chi2, mon_mass, mon_pT_trk1, mon_pT_trk2, mon_Lxy, mon_D0_trk1, mon_D0_trk2, mon_Eta1, mon_Eta2);
 
-  if (m_acceptAll || (isInMassRange(trigBphys->mass()) && passedChi2Cut(trigBphys->fitchi2()) && passedChargeCut(totalCharge(trigBphys)) &&
-             trigBphys->lxy() > m_LxyCut )) {
+  if (m_acceptAll || (trigBphys->nTrackParticles() == m_nTrk &&
+                      isInMassRange(trigBphys->mass()) &&
+                      passedChi2Cut(trigBphys->fitchi2()) &&
+                      passedChargeCut(totalCharge(trigBphys)) &&
+                      trigBphys->lxy() > m_LxyCut)) {
     mon_Lxy = trigBphys->lxy();
     mon_totalCharge = totalCharge(trigBphys);
     mon_chi2 = trigBphys->fitchi2();
@@ -93,13 +116,21 @@ StatusCode TrigMultiTrkComboHypoTool::decideOnSingleObject(Decision* decision, c
   auto trigBphysEL = decision->objectLink<xAOD::TrigBphysContainer>(TrigCompositeUtils::featureString());
   ATH_CHECK( trigBphysEL.isValid() );
 
-  ATH_CHECK( previousDecisionIDs.size() == m_nTrk );
+  if (previousDecisionIDs.size() != (m_isMergedElectronChain ? 1 : m_nTrk.value())) {
+    return StatusCode::SUCCESS;
+  }
+
   if (!checkPreviousDecisionIDs(previousDecisionIDs)) {
     return StatusCode::SUCCESS;
   }
 
-  if (passed(*trigBphysEL)) {
+  if (m_acceptAll || passed(*trigBphysEL)) {
     TrigCompositeUtils::addDecisionID(decisionId(), decision);
+    if (m_isCombinedChain) {
+      for (size_t i = 0; i < m_nTrk; ++i) {
+        TrigCompositeUtils::addDecisionID(m_legDecisionIDs[i], decision);
+      }
+    }
   }
 
   return StatusCode::SUCCESS;
@@ -113,9 +144,16 @@ bool TrigMultiTrkComboHypoTool::checkPreviousDecisionIDs(const std::vector<const
   // trigger with asymmetric legs (like HLT_mu6_2mu4_bDimu_L1MU6_3MU4) is treated in a specific way:
   // all 6 possible combinations should be checked: {leg0, leg1}, {leg0, leg2}, {leg1, leg0}, {leg1, leg2}, {leg2, leg0}, {leg2, leg1}
 
-  if (decisionId() == legDecisionId(0)) {  // trigger with symmetric legs like HLT_3mu6_bDimu_L13MU6
+  if (m_isMergedElectronChain) {
+    if (!TrigCompositeUtils::passed(m_legDecisionIDs.at(0), *previousDecisionIDs[0])) {
+      ATH_MSG_DEBUG( "Trigger for close-by electrons didn't pass previous decision" );
+      return false;
+    }
+    return true;
+  }
+  else if (decisionId() == legDecisionId(0)) {  // trigger with symmetric legs like HLT_3mu6_bDimu_L13MU6
     for (size_t i = 0; i < m_nTrk; ++i) {
-      if (!TrigCompositeUtils::passed(decisionId().numeric(), *previousDecisionIDs[i])) {	
+      if (!TrigCompositeUtils::passed(decisionId().numeric(), *previousDecisionIDs[i])) {
         ATH_MSG_DEBUG( "Trigger with symmetric legs didn't pass previous decision" );
         return false;
       }
@@ -125,29 +163,14 @@ bool TrigMultiTrkComboHypoTool::checkPreviousDecisionIDs(const std::vector<const
   }
   else {  // trigger with asymmetric legs like HLT_mu6_2mu4_bDimu_L1MU6_3MU4
 
-    std::vector<size_t> a;
-    std::vector<HLT::Identifier> legDecisionIdToCheck;
-
-    size_t count = 0;
-    for (size_t legIndex = 0; legIndex < legMultiplicity().size(); ++legIndex) {
-      for (size_t objectIndex = 0; objectIndex < (size_t) legMultiplicity().at(legIndex); ++objectIndex) {
-        a.push_back(count++);
-        legDecisionIdToCheck.push_back(legDecisionId(legIndex));
-      }
-    }
-    // For HLT_mu6_2mu4_bDimu_L1MU6_3MU4
-    // a = [0,1,2]
-    // legDecisionIdToCheck = [
-    //  createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 0), 
-    //  createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 1), 
-    //  createLegName("HLT_mu6_2mu4_bDimu_L1MU6_3MU4", 1)]
-
+    std::vector<size_t> a(m_legDecisionIDs.size());
+    std::iota(a.begin(), a.end(), 0);  // {0, 1, 2, .., m_legDecisionIDs().size() - 1}
     int i = 1;
     bool result = true;
     do {
       result = true;
       for (size_t k = 0; k < m_nTrk; ++k) {
-        result = result && TrigCompositeUtils::passed(legDecisionIdToCheck.at( a.at(k) ).numeric(), *previousDecisionIDs[k]);
+        result = result && TrigCompositeUtils::passed(m_legDecisionIDs.at(a[k]), *previousDecisionIDs[k]);
       }
       if (msgLvl(MSG::DEBUG)) {
         msg() << "combination #" << i++ << ": { ";
@@ -156,7 +179,7 @@ bool TrigMultiTrkComboHypoTool::checkPreviousDecisionIDs(const std::vector<const
       }
       if (result) break;
     } while (std::next_permutation(a.begin(), a.end()));
-    ATH_MSG_DEBUG( "Trigger with asymmetric legs " << (result ? "passed" : "didn't pass" ) << " previous decision" );
+    ATH_MSG_DEBUG( "Trigger with asymmetric legs " << (result ? "passed" : "didn't pass") << " previous decision" );
     return result;
   }
   return true;
@@ -182,7 +205,7 @@ int TrigMultiTrkComboHypoTool::totalCharge(const xAOD::TrigBphys* trigBphys) con
 }
 
 
-bool TrigMultiTrkComboHypoTool::executeAlg(const std::vector<Combo::LegDecision>&) const { 
-  ATH_MSG_ERROR("executeAlg not supported for TrigBmumuxComboHypoTool.");
+bool TrigMultiTrkComboHypoTool::executeAlg(const std::vector<Combo::LegDecision>&) const {
+  ATH_MSG_ERROR( "executeAlg() is not supported by TrigMultiTrkComboHypoTool" );
   return true;
 }
diff --git a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.h b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.h
index 1be2f0790babfc1e6189f4658e9528bac7fe0ea0..3d9b9b635de063dd607aa2b92d681817c38b0e3a 100644
--- a/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.h
+++ b/Trigger/TrigHypothesis/TrigBphysHypo/src/TrigMultiTrkComboHypoTool.h
@@ -50,9 +50,14 @@ class TrigMultiTrkComboHypoTool: public ComboHypoToolBase {
   Gaudi::Property<int> m_totalCharge {this, "totalCharge", 0, "magnitude of the total charge to accept, negative is none" };
   Gaudi::Property<std::pair<double, double>> m_massRange {this, "massRange", {-99., -9.}, "range for the fitted mass, no selection applied if negative"};
   Gaudi::Property<float> m_chi2 {this, "chi2", -99. , "Chi2 cut for vertex (0 < chi2 < cut), no selection applied if negative" };
-  Gaudi::Property<bool> m_acceptAll {this, "AcceptAll", false, "if AcceptAll flag is set to true, no selection will be applied for xAOD::TrigBphys object" };
+  Gaudi::Property<bool> m_acceptAll {this, "AcceptAll", false, "if AcceptAll flag is set to true, no selection will be applied for xAOD::TrigBphys object"};
   Gaudi::Property<float> m_LxyCut {this, "LxyCut", -999., "Applies an Lxy Cut if set > -999"};
-  ToolHandle<GenericMonitoringTool> m_monTool { this, "MonTool", "", "Monitoring tool" };
+  Gaudi::Property<bool> m_isCombinedChain {this, "isCombinedChain", false, "true for chains with different signatures, e.g. HLT_e9_mu6"};
+  Gaudi::Property<bool> m_isMergedElectronChain {this, "isMergedElectronChain", false, "true for close-by electrons, e.g. HLT_e5_lhvloose_L1BPH-0DR3-EM7J15"};
+  Gaudi::Property<std::vector<unsigned int>> m_legMultiplicities {this, "legMultiplicities", {2}, "taken from dict[chainMultiplicities]"};
+  ToolHandle<GenericMonitoringTool> m_monTool {this, "MonTool", "", "Monitoring tool" };
+
+  std::vector<TrigCompositeUtils::DecisionID> m_legDecisionIDs;
 };
 
 #endif  // TRIG_TrigMultiTrkComboHypoTool_H
diff --git a/Trigger/TrigValidation/TrigAnalysisTest/share/ref_RDOtoRDOTrig_v1Dev_build.ref b/Trigger/TrigValidation/TrigAnalysisTest/share/ref_RDOtoRDOTrig_v1Dev_build.ref
index a17147e56e334db7e8204c7aa90712db0e2d1cc5..ef4d01498a1372151ec4767da41939e221aed2d2 100644
--- a/Trigger/TrigValidation/TrigAnalysisTest/share/ref_RDOtoRDOTrig_v1Dev_build.ref
+++ b/Trigger/TrigValidation/TrigAnalysisTest/share/ref_RDOtoRDOTrig_v1Dev_build.ref
@@ -70,15 +70,27 @@ HLT_2e5_lhvloose_bBeeM6000_L12EM3:
   eventCount: 0
   stepCounts:
     0: 15
-    1: 8
-    2: 6
-    3: 5
+    1: 6
+    2: 4
+    3: 4
   stepFeatures:
     0: 50
     1: 104
-    2: 20
-    3: 15
-    4: 5
+    2: 14
+    3: 10
+    4: 3
+HLT_2e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5:
+  eventCount: 0
+  stepCounts:
+    0: 6
+    1: 2
+    2: 1
+    3: 1
+  stepFeatures:
+    0: 18
+    1: 50
+    2: 4
+    3: 2
 HLT_2g10_loose_mu20_L1MU14FCH:
   eventCount: 0
   stepCounts:
@@ -455,6 +467,19 @@ HLT_2mu4_bBmumu_L1BPH-2M9-0DR15-2MU3V:
   eventCount: 0
 HLT_2mu4_bBmumu_Lxy0_L1BPH-2M9-0DR15-2MU3V:
   eventCount: 0
+HLT_2mu4_bBmumux_BcmumuDploose_L12MU3V:
+  eventCount: 0
+  stepCounts:
+    0: 4
+    1: 3
+    2: 3
+    3: 1
+  stepFeatures:
+    0: 9
+    1: 9
+    2: 10
+    3: 8
+    4: 4
 HLT_2mu4_bBmumux_BcmumuDsloose_L12MU3V:
   eventCount: 0
   stepCounts:
@@ -3421,6 +3446,19 @@ HLT_e5_lhtight_noringer_e9_etcut_Jpsiee_L1JPSI-1M5-EM7:
   eventCount: 0
 HLT_e5_lhtight_noringer_e9_etcut_probe_1invmAB5_L1JPSI-1M5-EM7:
   eventCount: 0
+HLT_e5_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15:
+  eventCount: 0
+  stepCounts:
+    0: 11
+    1: 11
+    2: 11
+    3: 11
+  stepFeatures:
+    0: 16
+    1: 50
+    2: 18
+    3: 15
+    4: 9
 HLT_e5_lhvloose_j70_0eta320_j50_0eta490_j0_DJMASS1000j50_xe50_tcpufit_L1MJJ-500-NFF:
   eventCount: 0
   stepCounts:
@@ -3617,6 +3655,27 @@ HLT_e9_lhtight_noringer_e4_etcut_Jpsiee_L1JPSI-1M5-EM7:
   eventCount: 0
 HLT_e9_lhtight_noringer_e4_etcut_probe_1invmAB5_L1JPSI-1M5-EM7:
   eventCount: 0
+HLT_e9_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15:
+  eventCount: 0
+  stepCounts:
+    0: 11
+    1: 11
+    2: 8
+    3: 8
+  stepFeatures:
+    0: 16
+    1: 50
+    2: 11
+    3: 11
+    4: 8
+HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5:
+  eventCount: 0
+  stepCounts:
+    0: 3
+  stepFeatures:
+    0: 21
+    1: 52
+    2: 3
 HLT_e9_lhvloose_mu20_mu8noL1_L1MU14FCH:
   eventCount: 0
   stepCounts:
diff --git a/Trigger/TrigValidation/TrigP1Test/share/ref_v1Dev_decodeBS_build.ref b/Trigger/TrigValidation/TrigP1Test/share/ref_v1Dev_decodeBS_build.ref
index a5b647448f10165b7c8f0fa1007fb62a40cc8a2d..0e0f85158dac209ae7f7a66c0eee8425e8bfdea9 100644
--- a/Trigger/TrigValidation/TrigP1Test/share/ref_v1Dev_decodeBS_build.ref
+++ b/Trigger/TrigValidation/TrigP1Test/share/ref_v1Dev_decodeBS_build.ref
@@ -34,6 +34,18 @@ HLT_2e5_lhvloose_bBeeM6000_L12EM3:
     1: 49
     2: 4
     3: 4
+HLT_2e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5:
+  eventCount: 0
+  stepCounts:
+    0: 1
+    1: 1
+    2: 1
+    3: 1
+  stepFeatures:
+    0: 5
+    1: 9
+    2: 2
+    3: 2
 HLT_2g10_loose_mu20_L1MU14FCH:
   eventCount: 0
 HLT_2g15_loose_25dphiAA_invmAA80_L1DPHI-M70-2eEM12M:
@@ -153,6 +165,12 @@ HLT_2mu4_bBmumu_L1BPH-2M9-0DR15-2MU3V:
   eventCount: 0
 HLT_2mu4_bBmumu_Lxy0_L1BPH-2M9-0DR15-2MU3V:
   eventCount: 0
+HLT_2mu4_bBmumux_BcmumuDploose_L12MU3V:
+  eventCount: 0
+  stepCounts:
+    0: 1
+  stepFeatures:
+    0: 2
 HLT_2mu4_bBmumux_BcmumuDsloose_L12MU3V:
   eventCount: 0
   stepCounts:
@@ -1082,6 +1100,18 @@ HLT_e5_lhtight_noringer_e9_etcut_probe_1invmAB5_L1JPSI-1M5-EM7:
     1: 8
     2: 2
     3: 2
+HLT_e5_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15:
+  eventCount: 0
+  stepCounts:
+    0: 7
+    1: 6
+    2: 5
+    3: 5
+  stepFeatures:
+    0: 9
+    1: 19
+    2: 6
+    3: 5
 HLT_e5_lhvloose_j70_0eta320_j50_0eta490_j0_DJMASS1000j50_xe50_tcpufit_L1MJJ-500-NFF:
   eventCount: 0
 HLT_e5_lhvloose_nopix_lrtloose_idperf_probe_g25_medium_L1EM20VH:
@@ -1134,6 +1164,22 @@ HLT_e9_lhtight_noringer_e4_etcut_Jpsiee_L1JPSI-1M5-EM7:
     0: 11
 HLT_e9_lhtight_noringer_e4_etcut_probe_1invmAB5_L1JPSI-1M5-EM7:
   eventCount: 0
+HLT_e9_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15:
+  eventCount: 0
+  stepCounts:
+    0: 6
+    1: 5
+    2: 2
+    3: 2
+  stepFeatures:
+    0: 7
+    1: 16
+    2: 2
+    3: 2
+HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5:
+  eventCount: 0
+  stepFeatures:
+    0: 6
 HLT_e9_lhvloose_mu20_mu8noL1_L1MU14FCH:
   eventCount: 0
 HLT_eb_low_L1RD2_FILLED:
diff --git a/Trigger/TriggerCommon/TrigEDMConfig/python/TriggerEDMRun3.py b/Trigger/TriggerCommon/TrigEDMConfig/python/TriggerEDMRun3.py
index f5a53a04448e8193ef6955a74b7ae0117a283f24..cf796e7a24bc85baf907dd7f0d852eaaeab6785f 100644
--- a/Trigger/TriggerCommon/TrigEDMConfig/python/TriggerEDMRun3.py
+++ b/Trigger/TriggerCommon/TrigEDMConfig/python/TriggerEDMRun3.py
@@ -401,6 +401,11 @@ TriggerHLTListRun3 = [
     ('xAOD::TrackParticleContainer#HLT_IDTrack_Bmumux_IDTrig',              'BS ESD AODFULL', 'Bphys', 'inViews:BmumuxViews'),
     ('xAOD::TrackParticleAuxContainer#HLT_IDTrack_Bmumux_IDTrigAux.',       'BS ESD AODFULL', 'Bphys'),
     ('TrigRoiDescriptorCollection#HLT_Roi_Bmumux',                          'BS ESD AODFULL', 'Bphys'),
+    # Bphysics Di-electron chains
+    ('xAOD::TrigBphysContainer#HLT_DiElecPrecision',                        'BS ESD AODFULL AODSLIM AODVERYSLIM AODBLSSLIM', 'Bphys'),
+    ('xAOD::TrigBphysAuxContainer#HLT_DiElecPrecisionAux.',                 'BS ESD AODFULL AODSLIM AODVERYSLIM AODBLSSLIM', 'Bphys'),
+    ('xAOD::TrigBphysContainer#HLT_NoMuonDiElecPrecision',                  'BS ESD AODFULL AODSLIM AODVERYSLIM AODBLSSLIM', 'Bphys'),
+    ('xAOD::TrigBphysAuxContainer#HLT_NoMuonDiElecPrecisionAux.',           'BS ESD AODFULL AODSLIM AODVERYSLIM AODBLSSLIM', 'Bphys'),
 
     # xAOD muons (msonly (x2: roi+FS), combined (x2: FS+RoI)
     ('xAOD::MuonContainer#HLT_Muons_RoI',                                       'BS ESD AODFULL', 'Muon', 'inViews:MUEFSAViewRoIs'),
diff --git a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Egamma/ElectronChainConfiguration.py b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Egamma/ElectronChainConfiguration.py
index f655c8ba9d44c080901ed81b7939558b826d73db..1c5f16799e3c1de04b0125b2b7efe76dcea1835b 100644
--- a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Egamma/ElectronChainConfiguration.py
+++ b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Egamma/ElectronChainConfiguration.py
@@ -21,7 +21,7 @@ from .PrecisionElectronMenuSequences_LRT import precisionElectronMenuSequence_LR
 from .PrecisionTrackingMenuSequences import precisionTrackingMenuSequence
 from .PrecisionTrackingMenuSequences_LRT import precisionTrackingMenuSequence_LRT
 
-from TrigBphysHypo.TrigMultiTrkComboHypoConfig import StreamerDiElecFastComboHypoCfg, DiElecPrecisionComboHypoCfg, TrigMultiTrkComboHypoToolFromDict
+from TrigBphysHypo.TrigMultiTrkComboHypoConfig import StreamerNoMuonDiElecFastComboHypoCfg, NoMuonDiElecPrecisionComboHypoCfg, StreamerDiElecFastComboHypoCfg, DiElecPrecisionComboHypoCfg, TrigMultiTrkComboHypoToolFromDict
 
 from AthenaMonitoringKernel.GenericMonitoringTool import GenericMonitoringTool, defineHistogram
 #----------------------------------------------------------------
@@ -201,9 +201,14 @@ class ElectronChainConfiguration(ChainConfigurationBase):
         return self.getStep(1,stepName,[ fastCaloCfg], is_probe_leg=is_probe_leg)
 
     def getFastElectron(self,is_probe_leg=False):
-        if "bBeeM6000" in self.chainDict['topo']:
-            stepName = "fast_electron_bBee"
-            return self.getStep(2,stepName,sequenceCfgArray=[fastElectronSequenceCfg], comboHypoCfg=StreamerDiElecFastComboHypoCfg)
+        if "bBeeM6000" in self.chainDict['topo'] and 'BPH-0DR3-EM7J15' not in self.chainDict['L1item']:
+            signatures = self.chainDict['signatures']
+            if signatures.count(signatures[0]) == len(signatures):
+                stepName = "noMuon_fast_electron_bBee"
+                return self.getStep(2,stepName,sequenceCfgArray=[fastElectronSequenceCfg], comboHypoCfg=StreamerNoMuonDiElecFastComboHypoCfg)
+            else:
+                stepName = "fast_electron_bBee"
+                return self.getStep(2,stepName,sequenceCfgArray=[fastElectronSequenceCfg], comboHypoCfg=StreamerDiElecFastComboHypoCfg)
         elif 'idperf' in self.chainPart['idperfInfo']:
             stepName = "fast_electron_idperf"
             return self.getStep(2,stepName,[ fastElectronSequenceCfg_idperf], is_probe_leg=is_probe_leg)
@@ -258,8 +263,13 @@ class ElectronChainConfiguration(ChainConfigurationBase):
             stepName = "precision_electron_Heg"+str(isocut)
             return self.getStep(5,stepName,sequenceCfgArray=[precisionElectronSequenceCfg], comboTools=[diEgammaHegMassComboHypoToolFromDict])
         elif "bBeeM6000" in  self.chainDict['topo']:
-            stepName = "precision_electron_bBee"+isocut
-            return self.getStep(5,stepName,sequenceCfgArray=[precisionElectronSequenceCfg], comboHypoCfg=DiElecPrecisionComboHypoCfg, comboTools=[TrigMultiTrkComboHypoToolFromDict])
+            signatures = self.chainDict['signatures']
+            if signatures.count(signatures[0]) == len(signatures):
+                stepName = "noMuon_precision_electron_bBee"+str(isocut)
+                return self.getStep(5,stepName,sequenceCfgArray=[precisionElectronSequenceCfg], comboHypoCfg=NoMuonDiElecPrecisionComboHypoCfg, comboTools=[TrigMultiTrkComboHypoToolFromDict])
+            else:
+                stepName = "precision_electron_bBee"+str(isocut)
+                return self.getStep(5,stepName,sequenceCfgArray=[precisionElectronSequenceCfg], comboHypoCfg=DiElecPrecisionComboHypoCfg, comboTools=[TrigMultiTrkComboHypoToolFromDict])
         elif self.chainPart['extra'] == 'ion':
             stepName = "precision_ion_electron" + str(isocut)
             return self.getStep(5,stepName,[precisionElectronSequenceCfg_ion], is_probe_leg=is_probe_leg)
diff --git a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/ChainMerging.py b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/ChainMerging.py
index 16111f4b91d6a5b6c0455f4c3240a8ce7b35c024..ba7b751fb448476b8eb3be8676599c8aa94f5c70 100644
--- a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/ChainMerging.py
+++ b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/ChainMerging.py
@@ -478,7 +478,8 @@ def makeCombinedStep(parallel_steps, stepNumber, chainDefList, allSteps = [], cu
             if len(step.sequences) > 1:
                 log.debug("[makeCombinedStep] combining in an already combined chain")
 
-            comboHypo = step.comboHypoCfg
+            if comboHypo is None or step.comboHypoCfg.__name__ != "ComboHypoCfg":
+                comboHypo = step.comboHypoCfg
             currentStepName = step.name
             #remove redundant instances of StepN_ and merged_ (happens when merging already merged chains)
             if re.search('^Step[0-9]_',currentStepName):
diff --git a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/LS2_v1.py b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/LS2_v1.py
index cb9c60d559732eb9f6872bb2b3c0fb6454bb72d2..75641a8e53beac556f509482f93b4ea08b5ab5b6 100644
--- a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/LS2_v1.py
+++ b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/LS2_v1.py
@@ -184,6 +184,10 @@ def setupMenu():
 
         #ATR-22749
         ChainProp(name='HLT_2e5_lhvloose_bBeeM6000_L12EM3', l1SeedThresholds=['EM3'], groups=BphysElectronGroup),
+        ChainProp(name='HLT_2e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5', l1SeedThresholds=['EM3'], groups=BphysElectronGroup),
+        ChainProp(name='HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_L1BPH-0M9-EM7-EM5', l1SeedThresholds=['EM7','EM3'], groups=BphysElectronGroup),
+        ChainProp(name='HLT_e5_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15', l1SeedThresholds=['EM7'], groups=BphysElectronGroup),
+        ChainProp(name='HLT_e9_lhvloose_bBeeM6000_L1BPH-0DR3-EM7J15', l1SeedThresholds=['EM7'], groups=BphysElectronGroup),
 
         #ART-23577
         ChainProp(name='HLT_e20_lhloose_L1EM7_AFP_A_OR_C', l1SeedThresholds=['EM7'], groups=SingleElectronGroup+LowMuGroup),
@@ -875,6 +879,7 @@ def setupMenu():
         ChainProp(name='HLT_2mu4_bBmumux_BdmumuKst_L12MU3V', stream=["BphysDelayed"], groups=BphysicsGroup+EOFBPhysL1MuGroup),
         ChainProp(name='HLT_2mu4_bBmumux_LbPqKm_L12MU3V', stream=["BphysDelayed"], groups=BphysicsGroup+EOFBPhysL1MuGroup),
         ChainProp(name='HLT_2mu4_bBmumux_BcmumuDsloose_L12MU3V', stream=["BphysDelayed"], groups=BphysicsGroup+EOFBPhysL1MuGroup),
+        ChainProp(name='HLT_2mu4_bBmumux_BcmumuDploose_L12MU3V', stream=["BphysDelayed"], groups=BphysicsGroup+EOFBPhysL1MuGroup),
 
         #ATR-23576; dimuon + L1Topo; primary triggers; should be moved to Physics after validation
         ChainProp(name='HLT_2mu4_bBmumu_Lxy0_L1BPH-2M9-0DR15-2MU3V', l1SeedThresholds=['MU3V'], stream=["BphysDelayed"], groups=BphysicsGroup+EOFBPhysL1MuGroup),
@@ -1063,9 +1068,11 @@ def setupMenu():
 
 
         #Combined BPhys
-        #ATR-22749; chain configuration is broken, temporarily comment them out, see ATR-23839
-        #ChainProp(name='HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_mu6_noL2Comb_L1BPH-0M9-EM7-EM5_MU5VF', l1SeedThresholds=['EM7','EM3','MU5VF'], groups=BphysElectronGroup),
-        #ChainProp(name='HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_2mu4_noL2Comb_L1BPH-0M9-EM7-EM5_2MU3V', l1SeedThresholds=['EM7','EM3','MU3V'], groups=BphysElectronGroup),
+        #ATR-22749; chain configuration is broken, temporarily comment them out, see ATR-23839 and ATR-23965
+        #ChainProp(name='HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_mu6_l2io_L1BPH-0M9-EM7-EM5_MU5VF', l1SeedThresholds=['EM7','EM3','MU5VF'], groups=BphysElectronGroup),
+        #ChainProp(name='HLT_e9_lhvloose_e5_lhvloose_bBeeM6000_2mu4_l2io_L1BPH-0M9-EM7-EM5_2MU3V', l1SeedThresholds=['EM7','EM3','MU3V'], groups=BphysElectronGroup),
+        #ChainProp(name='HLT_e5_lhvloose_bBeeM6000_mu6_l2io_L1BPH-0DR3-EM7J15_MU5VF', l1SeedThresholds=['EM7','MU5VF'], groups=BphysElectronGroup),
+        #ChainProp(name='HLT_e5_lhvloose_bBeeM6000_2mu4_l2io_L1BPH-0DR3-EM7J15_2MU3V', l1SeedThresholds=['EM7','MU3V'], groups=BphysElectronGroup),
 
         # Tests of potential TLA chains for cost/rate
         # ATR-19317 - dijet+ISR 
diff --git a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/SignatureDicts.py b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/SignatureDicts.py
index af05a50e449931522c2d2aa88f4bc33ea20bcffa..3eb170474bddae9b9d64ab83081d6f28eef010b1 100644
--- a/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/SignatureDicts.py
+++ b/Trigger/TriggerCommon/TriggerMenuMT/python/HLTMenuConfig/Menu/SignatureDicts.py
@@ -356,7 +356,7 @@ MuonChainParts_Default = {
 AllowedTopos_Bphysics = [
     'bJpsimumu','bJpsi','bUpsimumu','bUpsi','bBmumu','bDimu','bDimu2700','bDimu6000','bPhi','bTau','bJpsimumul2io',
     'Lxy0',
-    'bBmumux','BpmumuKp','BcmumuPi','BsmumuPhi','BdmumuKst','LbPqKm', 'BcmumuDsloose'
+    'bBmumux','BpmumuKp','BcmumuPi','BsmumuPhi','BdmumuKst','LbPqKm', 'BcmumuDsloose', 'BcmumuDploose'
 ]
 
 # ---- Bphysics Dictionary of all allowed Values ----