diff --git a/B2munuee/B2Vub_MCoptions.py b/B2munuee/B2Vub_MCoptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5046506ae94f9966cbcf30228d0cb41291167da
--- /dev/null
+++ b/B2munuee/B2Vub_MCoptions.py
@@ -0,0 +1,12 @@
+from B2munuee import my_selections as selections
+
+# B+ -> mu nu Xu0 (-> X gamma), resonant
+# B+ -> mu nu Xu0 (-> X gamma), non-resonant
+# B0 -> mu nu Xu (-> X gamma), resonant
+# B0 -> mu nu Xu (-> X gamma), non-resonant
+
+trees = ['default','SameSign','pions','eta']
+isMC=True
+addJpsiConstraints=False
+run_stripping = False
+selections.MySelection(trees, isMC, run_stripping, addJpsiConstraints)
\ No newline at end of file
diff --git a/B2munuee/Bc2munucc_MCoptions.py b/B2munuee/Bc2munucc_MCoptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..110e2a6c13a60da0446cddabf717a490a1515ca9
--- /dev/null
+++ b/B2munuee/Bc2munucc_MCoptions.py
@@ -0,0 +1,11 @@
+from B2munuee import my_selections as selections
+
+# Bc -> mu nu chi_c (-> Jpsi gamma)
+# Bc -> mu nu Psi2S (-> Jpsi X)
+# Bc -> mu nu Psi2S (-> ee)
+
+trees = ['default', 'SameSign']
+isMC = True
+addJpsiConstraints = True
+run_stripping = False
+selections.MySelection(trees, isMC, run_stripping, addJpsiConstraints)
\ No newline at end of file
diff --git a/B2munuee/Bu2JpsiK2ee_MCoptions.py b/B2munuee/Bu2JpsiK2ee_MCoptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5edcef4254d1b9e6f6ea84346b645636a756e6a
--- /dev/null
+++ b/B2munuee/Bu2JpsiK2ee_MCoptions.py
@@ -0,0 +1,9 @@
+from B2munuee import my_selections as selections
+
+# B+ -> mu nu eta' (-> e e gamma)
+
+trees = ['JpsiKee']
+isMC = True
+addJpsiConstraints = True
+run_stripping = True
+selections.MySelection(trees, isMC, run_stripping, addJpsiConstraints)
\ No newline at end of file
diff --git a/B2munuee/Bu2munuee_MCoptions.py b/B2munuee/Bu2munuee_MCoptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f12913a23ec51e8640b9f51de4d68daaa64bf11
--- /dev/null
+++ b/B2munuee/Bu2munuee_MCoptions.py
@@ -0,0 +1,9 @@
+from B2munuee import my_selections as selections
+
+# B+ -> mu nu eta' (-> e e gamma)
+
+trees = ['default', 'SameSign']
+isMC = True
+addJpsiConstraints = False
+run_stripping = False
+selections.MySelection(trees, isMC, run_stripping, addJpsiConstraints)
\ No newline at end of file
diff --git a/B2munuee/StrippingB23MuNu.py b/B2munuee/StrippingB23MuNu.py
new file mode 100644
index 0000000000000000000000000000000000000000..858afff4b3f4039fd02d73c481f7c7a5ced69141
--- /dev/null
+++ b/B2munuee/StrippingB23MuNu.py
@@ -0,0 +1,496 @@
+###############################################################################
+# (c) Copyright 2000-2019 CERN for the benefit of the LHCb Collaboration      #
+#                                                                             #
+# This software is distributed under the terms of the GNU General Public      #
+# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   #
+#                                                                             #
+# In applying this licence, CERN does not waive the privileges and immunities #
+# granted to it by virtue of its status as an Intergovernmental Organization  #
+# or submit itself to any jurisdiction.                                       #
+###############################################################################
+
+
+###############################################################################
+# Custom stripping selection of B23MuNu for restripping in Analysis Production#
+#                                                                             #
+# The physics of the selection is identical to the selection of the published # 
+# version in S28r2p1, S29r2p2 and S34r0p1 but has no RelInfoTools added.      #
+#                                                                             #
+###############################################################################
+
+__author__ = 'P. Owen, T.Mombacher'
+__date__ = '11/03/2021'
+__version__ = '$Revision: 2.0 $'
+
+__all__ = ( 'B23MuNuConf', 'default_config' )
+
+"""
+Stripping selection for B to three muons and a neutrino.
+"""
+
+from GaudiConfUtils.ConfigurableGenerators import  CombineParticles, FilterDesktop
+
+from PhysSelPython.Wrappers import Selection, AutomaticData, MergedSelection
+from StrippingConf.StrippingLine import StrippingLine
+from StrippingUtils.Utils import LineBuilder
+
+
+#################
+#
+#  Define Cuts here
+#
+#################
+
+default_config = {
+    'NAME'        : 'B23MuNu',
+    'WGs'         : ['Semileptonic'],
+    'BUILDERTYPE' : 'B23MuNuConf',
+    'CONFIG'      : {
+      #  (dimu) cuts
+      'FlightChi2'      :    30.0,
+      'DIRA'            :   0.99,
+      'BPT'             :  2000.0,
+      'VertexCHI2'      :     4.0,
+      'LOWERMASS'       :     0.0, # MeV
+      'UPPERMASS'       :  7500.0, # MeV
+      'CORRM_MIN'       :  2500.0, # MeV
+      'CORRM_MAX'       : 10000.0, # MeV
+      # Track cuts
+      'Track_CHI2nDOF'      :    3.0,
+      'Track_GhostProb'     :    0.35,  
+
+      # Muon cuts
+      'Muon_MinIPCHI2'   :    9.0,
+      'Muon_PIDmu'       :    0.0,
+      'Muon_PIDmuK'      :    0.0,
+      'Muon_PT'          :    0.0,
+
+      # Electron cuts
+      'Electron_PIDe'       :    2.0,
+      'Electron_PIDeK'      :    0.0,
+      'Electron_MinIPCHI2'  :    25.0,
+      'Electron_PT'         :    200.0,
+
+      # GEC
+      'SpdMult'             :  900,
+
+      'MisIDPrescale'       : 0.01,
+    },
+   'STREAMS'     : ['Semileptonic']   
+}
+
+defaultName = "B23MuNu"
+
+
+class B23MuNuConf(LineBuilder) :
+
+    __configuration_keys__ = default_config['CONFIG'].keys()
+    
+    def __init__(self, name, config) :
+
+
+        LineBuilder.__init__(self, name, config)
+
+        self.name = name
+        
+
+        self.TriMuCut = "(BPVCORRM > %(CORRM_MIN)s *MeV) & " \
+                           "(BPVCORRM < %(CORRM_MAX)s *MeV) & " \
+                                "(BPVDIRA > %(DIRA)s) & " \
+                                "(PT > %(BPT)s) & " \
+                                "(BPVVDCHI2 > %(FlightChi2)s) & " \
+                                "(VFASPF(VCHI2/VDOF) < %(VertexCHI2)s) & " \
+                                "(M > %(LOWERMASS)s) & " \
+                                "(M < %(UPPERMASS)s) " %config
+
+        self.TrackCuts = "(TRCHI2DOF < %(Track_CHI2nDOF)s) & (TRGHP < %(Track_GhostProb)s)" \
+                         " & (MIPCHI2DV(PRIMARY) > %(Muon_MinIPCHI2)s) " \
+                         " & (PT > %(Muon_PT)s)" %config
+
+        self.TrackCutsElectron = "(TRCHI2DOF < %(Track_CHI2nDOF)s) & (TRGHP < %(Track_GhostProb)s)" \
+                         " & (MIPCHI2DV(PRIMARY) > %(Electron_MinIPCHI2)s) " \
+                         " & (PT > %(Electron_PT)s)" %config
+
+        
+        self.MuonCut = self.TrackCuts + "& (PIDmu> %(Muon_PIDmu)s) & " \
+                                        " (PIDmu-PIDK> %(Muon_PIDmuK)s)" %config
+
+
+        self.ElectronCut = self.TrackCutsElectron + "& (PIDe> %(Electron_PIDe)s) & " \
+                                        " (PIDe-PIDK> %(Electron_PIDeK)s)" %config
+
+
+        self.Muons = self.__Muons__(config)
+        self.Electrons = self.__Electrons__(config)
+        self.FakeMuons = self.__FakeMuons__(config)
+        self.FakeElectrons = self.__FakeElectrons__(config)
+        self.Jpsi = self.__Jpsi__(config)
+        self.Jpsiee = self.__Jpsiee__(config)
+        self.Trimu = self.__Trimu__(config)
+        self.Trie = self.__Trie__(config)
+        self.MuMue = self.__MuMue__(config)
+        self.Muee = self.__Muee__(config)
+        self.FakeTrimu = self.__FakeTrimu__(config)
+        self.FakeTrie = self.__FakeTrie__(config)
+        self.FakeMuMue = self.__FakeMuMue__(config)
+        self.FakeMuee = self.__FakeMuee__(config)
+
+        self.TriMu_line =  StrippingLine(
+            self.name+"_TriMuLine",
+            prescale = 1,
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.Trimu]
+            )
+
+        self.Trie_line =  StrippingLine(
+            self.name+"_TrieLine",
+            prescale = 1,
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.Trie]
+            )       
+
+        self.MuMue_line =  StrippingLine(
+            self.name+"_MuMueLine",
+            prescale = 1,
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.MuMue]
+            ) 
+
+        self.Muee_line =  StrippingLine(
+            self.name+"_MueeLine",
+            prescale = 1,
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.Muee]
+            )      
+
+        self.FakeTriMu_line =  StrippingLine(
+            self.name+"_TriFakeMuLine",
+            prescale = config['MisIDPrescale'],
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.FakeTrimu]
+            )
+
+        self.FakeTrie_line =  StrippingLine(
+            self.name+"_TrieFakeLine",
+            prescale = config['MisIDPrescale'],
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.FakeTrie]
+            )
+
+        self.FakeMuMue_line =  StrippingLine(
+            self.name+"_MuMueFakeLine",
+            prescale = config['MisIDPrescale'],
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.FakeMuMue]
+            )
+
+        self.FakeMuee_line =  StrippingLine(
+            self.name+"_MueeFakeLine",
+            prescale = config['MisIDPrescale'],
+            FILTER = {
+            'Code' : " ( recSummary(LHCb.RecSummary.nSPDhits,'Raw/Spd/Digits') < %(SpdMult)s )" %config ,
+            'Preambulo' : [
+            "from LoKiNumbers.decorators import *", "from LoKiCore.basic import LHCb"
+            ]
+            },
+            algos=[self.FakeMuee]
+            )
+
+
+        self.registerLine( self.TriMu_line )
+        self.registerLine( self.Trie_line )
+        self.registerLine( self.Muee_line )
+        self.registerLine( self.MuMue_line )
+        self.registerLine( self.FakeTriMu_line )
+        self.registerLine( self.FakeTrie_line )
+        self.registerLine( self.FakeMuMue_line )
+        self.registerLine( self.FakeMuee_line )
+
+
+
+    def __Muons__(self, conf):
+        """
+        Filter muons from StdAllLooseMuons
+        """  
+        from StandardParticles import StdAllLooseMuons
+        _muons = StdAllLooseMuons
+        _filter = FilterDesktop(Code = self.MuonCut)
+        _sel = Selection("Selection_"+self.name+"_Muons",
+                         RequiredSelections = [ _muons ] ,
+                         Algorithm = _filter)
+        return _sel
+
+    def __Electrons__(self, conf):
+        """
+        Filter Electrons from StdLooseElectrons
+        """  
+        from StandardParticles import StdAllLooseElectrons
+        _electrons = StdAllLooseElectrons
+        _filter = FilterDesktop(Code = self.ElectronCut)
+        _sel = Selection("Selection_"+self.name+"_Electrons",
+                         RequiredSelections = [ _electrons ] ,
+                         Algorithm = _filter)
+        return _sel
+ 
+        
+    def __FakeMuons__(self, conf):
+        """
+        Filter muons from StdAllNoPIDsMuons
+        """  
+        from StandardParticles import StdAllNoPIDsMuons
+        _fakemuons = StdAllNoPIDsMuons
+        _filter = FilterDesktop(Code = self.TrackCuts)
+        _sel = Selection("Selection_"+self.name+"_FakeMuons",
+                         RequiredSelections = [ _fakemuons ] ,
+                         Algorithm = _filter)
+        return _sel
+
+    def __FakeElectrons__(self, conf):
+        """
+        Filter electrons from StdAllNoPIDsElectrons
+        """  
+        from StandardParticles import StdAllNoPIDsElectrons
+        _fakeelectrons = StdAllNoPIDsElectrons
+        _filter = FilterDesktop(Code = self.TrackCutsElectron)
+        _sel = Selection("Selection_"+self.name+"_FakeElectrons",
+                         RequiredSelections = [ _fakeelectrons ] ,
+                         Algorithm = _filter)
+        return _sel
+
+
+    def __Jpsi__(self, conf):
+        """
+        Creates Jpsi as proxy for dimuon
+        """
+        from PhysSelPython.Wrappers import AutomaticData, Selection, SelectionSequence, DataOnDemand, MergedSelection
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        #from Configurables import CombineParticles 
+
+        CombineJpsi= CombineParticles(DecayDescriptors = ["J/psi(1S) -> mu+ mu-","[J/psi(1S) -> mu+ mu+]cc"],                       
+                     MotherCut = "ALL")
+        
+        sel_name = "Jpsi"
+        
+        from PhysSelPython.Wrappers import Selection
+        SelJpsi = Selection("Sel_" + self.name + "_Jpsi", Algorithm = CombineJpsi,
+                              RequiredSelections = [ self.Muons ] )
+        return SelJpsi
+
+    def __Jpsiee__(self, conf):
+        """
+        Creates Jpsi as proxy for dielectron
+        """
+        from PhysSelPython.Wrappers import AutomaticData, Selection, SelectionSequence, DataOnDemand, MergedSelection
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        #from Configurables import CombineParticles 
+
+        CombineJpsi= CombineParticles(DecayDescriptors = ["J/psi(1S) -> e+ e-","[J/psi(1S) -> e+ e+]cc"],                       
+                     MotherCut = "ALL")
+        
+        sel_name = "Jpsi"
+        
+        from PhysSelPython.Wrappers import Selection
+        SelJpsi = Selection("Sel_" + self.name + "_Jpsiee", Algorithm = CombineJpsi,
+                              RequiredSelections = [ self.Electrons ] )
+        return SelJpsi
+
+
+
+    def __Trimu__(self, conf):
+        '''
+        Create trimuon
+        '''
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptors = ["[B+ -> mu+ mu+ mu-]cc", "[B+ -> mu+ mu+ mu+]cc"]
+        sel_name="TriMu"
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, 
+                              Algorithm = CombineTriMuon,
+                              RequiredSelections = [ self.Muons ] )
+        return SelTriMuon
+   
+    def __Trie__(self, conf):
+        '''
+        Create trielectron
+        '''
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptors = ["[B+ -> e+ e+ e-]cc", "[B+ -> e+ e+ e+]cc"]
+        sel_name="Trie"
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, 
+                              Algorithm = CombineTriMuon,
+                              RequiredSelections = [ self.Electrons ] )
+        return SelTriMuon
+
+
+    def __MuMue__(self, conf):
+        '''
+        Create MuMue combination
+        '''
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptors = ["[B+ -> e+ mu+ mu-]cc","[B+ -> mu+ mu+ e-]cc", "[B+ -> mu+ mu+ e+]cc"]
+        sel_name="MuMue"
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, 
+                              Algorithm = CombineTriMuon,
+                              RequiredSelections = [ self.Muons, self.Electrons ] )
+        return SelTriMuon
+
+
+    def __Muee__(self, conf):
+        '''
+        Create Muee combination
+        '''
+        from  GaudiConfUtils.ConfigurableGenerators import CombineParticles
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptors = ["[B+ -> mu+ e+ e-]cc", "[B+ -> e+ e+ mu-]cc", "[B+ -> mu+ e+ e+]cc"]
+        sel_name="Muee"
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, 
+                              Algorithm = CombineTriMuon,
+                              RequiredSelections = [ self.Muons, self.Electrons ] )
+        return SelTriMuon
+
+
+    def __FakeTrimu__(self, conf):
+        """
+        Create fake trimuon
+        """
+
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptor = "[B+ -> J/psi(1S) mu+]cc"
+        sel_name="FakeTriMu"
+        #CombineTriMuon.CombinationCut = self.TriMuLowQ2CombCut
+        #["[B+ ->mu+ mu+ mu-]cc", "[B+ ->mu+ mu+ mu+]cc"]
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        #SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, Algorithm = CombineTriMuon,
+        #                      RequiredSelections = [self.Jpsi, self.FakeMuon ])
+        SelTriMuon = Selection(sel_name,
+                 Algorithm = CombineTriMuon,
+                 RequiredSelections = [ self.FakeMuons, self.Jpsi ])
+
+
+        return SelTriMuon
+
+    def __FakeTrie__(self, conf):
+        """
+        Create fake trielectron
+        """
+
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptor = "[B+ -> J/psi(1S) e+]cc"
+        sel_name="FakeTrie"
+        #CombineTriMuon.CombinationCut = self.TriMuLowQ2CombCut
+        #["[B+ ->mu+ mu+ mu-]cc", "[B+ ->mu+ mu+ mu+]cc"]
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        #SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, Algorithm = CombineTriMuon,
+        #                      RequiredSelections = [self.Jpsi, self.FakeMuon ])
+        SelTriMuon = Selection(sel_name,
+                 Algorithm = CombineTriMuon,
+                 RequiredSelections = [ self.FakeElectrons, self.Jpsiee ])
+
+
+        return SelTriMuon
+
+ 
+    def __FakeMuMue__(self, conf):
+        """
+        Create fake MuMue
+        """
+
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptor = "[B+ -> J/psi(1S) e+]cc"
+        sel_name="FakeMuMue"
+        #CombineTriMuon.CombinationCut = self.TriMuLowQ2CombCut
+        #["[B+ ->mu+ mu+ mu-]cc", "[B+ ->mu+ mu+ mu+]cc"]
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        #SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, Algorithm = CombineTriMuon,
+        #                      RequiredSelections = [self.Jpsi, self.FakeMuon ])
+        SelTriMuon = Selection(sel_name,
+                 Algorithm = CombineTriMuon,
+                 RequiredSelections = [ self.FakeElectrons, self.Jpsi ])
+
+
+        return SelTriMuon
+
+    def __FakeMuee__(self, conf):
+        """
+        Create fake Muee
+        """
+
+        CombineTriMuon = CombineParticles()
+        CombineTriMuon.DecayDescriptor = "[B+ -> J/psi(1S) mu+]cc"
+        sel_name="FakeMuee"
+        #CombineTriMuon.CombinationCut = self.TriMuLowQ2CombCut
+        #["[B+ ->mu+ mu+ mu-]cc", "[B+ ->mu+ mu+ mu+]cc"]
+        CombineTriMuon.MotherCut     = self.TriMuCut
+        # choose
+
+        from PhysSelPython.Wrappers import Selection
+        #SelTriMuon = Selection("Sel_" + self.name + "_"+sel_name, Algorithm = CombineTriMuon,
+        #                      RequiredSelections = [self.Jpsi, self.FakeMuon ])
+        SelTriMuon = Selection(sel_name,
+                 Algorithm = CombineTriMuon,
+                 RequiredSelections = [ self.FakeMuons, self.Jpsiee ])
+
+
+        return SelTriMuon
diff --git a/B2munuee/__init__.py b/B2munuee/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/B2munuee/info.yaml b/B2munuee/info.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..80e4ed68c1529e53128e68967b71ab14b3956e15
--- /dev/null
+++ b/B2munuee/info.yaml
@@ -0,0 +1,119 @@
+defaults:
+    application: DaVinci/v46r7
+    wg: SL
+    automatically_configure: yes
+    turbo: no
+    inform:
+        - fabian.christoph.glaser@cern.ch
+
+{%- set mc_datasets = [
+    ('2016', 'Sim09m', 'Trig0x6139160F', '16', '03a', '28r2p1'),
+    ('2017', 'Sim09m', 'Trig0x62661709', '17', '04a-WithTurcal', '29r2p2'),
+    ('2018', 'Sim09m', 'Trig0x617d18a4', '18', '05-WithTurcal', '34r0p2'),
+]%}
+
+{%- for year, sim, trig, reco, turbo, strip in mc_datasets %}
+  {%- for polarity in ['MagDown','MagUp'] %}
+
+{{year}}_{{polarity}}_MC_Bu2munuetap2eeg:
+    #B+ -> mu nu eta' (-> e+ e- gamma)
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-Pythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/12513200/ALLSTREAMS.DST
+    output: Bu2munuetap2eeg_MC.ROOT
+    options: Bu2munuee_MCoptions.py
+
+{{year}}_{{polarity}}_MC_Bc2munuchic2Jpsi:
+    #Bc+ -> mu nu chi_c (-> Jpsi X)
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-BcVegPyPythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/14643230/ALLSTREAMS.DST
+    output: Bc2munuchic2Jpsi_MC.ROOT
+    options: Bc2munucc_MCoptions.py
+
+{{year}}_{{polarity}}_MC_Bc2munuPsi2Jpsi:
+    #Bc+ -> mu nu psi(2S) (-> Jpsi X)
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-BcVegPyPythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/14845020/ALLSTREAMS.DST
+    output: Bc2munuPsi2Jpsi_MC.ROOT
+    options: Bc2munucc_MCoptions.py
+
+{{year}}_{{polarity}}_MC_Bc2munuPsi2ee:
+    #Bc+ -> mu nu psi(2S)
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-BcVegPyPythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/14543050/ALLSTREAMS.DST
+    output: Bc2munuPsi2ee_MC.ROOT
+    options: Bc2munucc_MCoptions.py
+
+  {%- endfor %}
+{%- endfor %}
+
+
+######
+# MC samples generated using SplitSim for photon conversion
+######
+
+{%- set conversion_datasets = [
+    ('2016', 'Sim09m-SplitSim01', 'Trig0x6139160F', '16', '03a', '28r2p1'),
+    ('2017', 'Sim09m-SplitSim01', 'Trig0x62661709', '17', '04a-WithTurcal', '29r2p2'),
+    ('2018', 'Sim09m-SplitSim01', 'Trig0x617d18a4', '18', '05-WithTurcal', '34r0p2'),
+]%}
+
+{%- for year, sim, trig, reco, turbo, strip in conversion_datasets %}
+  {%- for polarity in ['MagDown','MagUp'] %}
+
+    #B+ -> mu nu Xu resonant
+    #input:
+    #    bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-Pythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/12711000/ALLSTREAMS.DST
+    #output: Bu2munuXu_MC.ROOT
+    #options: B2Vub_MCoptions.py
+
+    #B0 -> mu nu Xu resonant
+    #input:
+    #    bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-Pythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/11512400/ALLSTREAMS.DST
+    #output: Bd2munuXu_MC.ROOT
+    #options: B2Vub_MCoptions.py
+
+{{year}}_{{polarity}}_MC_Bu2munuVub:
+    #B+ -> mu nu Xu non-resonant
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-Pythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/12511005/ALLSTREAMS.DST
+    output: Bu2munuVub_MC.ROOT
+    options: B2Vub_MCoptions.py
+
+{{year}}_{{polarity}}_MC_Bd2munuVub:
+    #B0 -> mu nu Xu non-resonant
+    input:
+        bk_query: /MC/{{year}}/Beam6500GeV-{{year}}-{{polarity}}-Nu1.6-25ns-Pythia8/{{sim}}/{{trig}}/Reco{{reco}}/Turbo{{turbo}}/Stripping{{strip}}NoPrescalingFlagged/11511003/ALLSTREAMS.DST
+    output: Bd2munuVub_MC.ROOT
+    options: B2Vub_MCoptions.py
+
+  {%- endfor %}
+{%- endfor %}
+
+######
+# MC samples of B+ -> Jpsi K reference channel
+######
+
+{%- for polarity in ['MagDown','MagUp'] %}
+
+2016_{{polarity}}_MC_Bu2JpsiK2ee:
+    #B+ -> Jpsi (-> e+ e-) K+
+    input:
+        bk_query: /MC/2016/Beam6500GeV-2016-{{polarity}}-Nu1.6-25ns-Pythia8/Sim09g/Trig0x6139160F/Reco16/Turbo03/Stripping28r1NoPrescalingFlagged/12153001/ALLSTREAMS.DST
+    output: Bu2JpsiK2ee_MC.ROOT
+    options: Bu2JpsiK2ee_MCoptions.py
+
+2017_{{polarity}}_MC_Bu2JpsiK2ee:
+    #B+ -> Jpsi (-> e+ e-) K+
+    input:
+        bk_query: /MC/2017/Beam6500GeV-2017-{{polarity}}-Nu1.6-25ns-Pythia8/Sim09i/Trig0x62661709/Reco17/Turbo04a-WithTurcal/Stripping29r2NoPrescalingFlagged/12153001/ALLSTREAMS.DST
+    output: Bu2JpsiK2ee_MC.ROOT
+    options: Bu2JpsiK2ee_MCoptions.py
+
+2018_{{polarity}}_MC_Bu2JpsiK2ee:
+    #B+ -> Jpsi (-> e+ e-) K+
+    input:
+        bk_query: /MC/2018/Beam6500GeV-2018-{{polarity}}-Nu1.6-25ns-Pythia8/Sim09i/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/12153001/ALLSTREAMS.DST
+    output: Bu2JpsiK2ee_MC.ROOT
+    options: Bu2JpsiK2ee_MCoptions.py
+
+{%- endfor %}
\ No newline at end of file
diff --git a/B2munuee/my_selections.py b/B2munuee/my_selections.py
new file mode 100644
index 0000000000000000000000000000000000000000..70213828ef9fa22649165b4520f0053d1c9a626f
--- /dev/null
+++ b/B2munuee/my_selections.py
@@ -0,0 +1,366 @@
+from Configurables import DaVinci, DecayTreeTuple, GaudiSequencer
+from PhysSelPython.Wrappers import SelectionSequence
+from DecayTreeTuple.Configuration import *
+
+from Configurables import FilterInTrees, CombineParticles, DiElectronMaker
+from PhysSelPython.Wrappers import Selection, AutomaticData
+
+from CommonParticles.Utils import updateDoD
+
+from GaudiKernel.SystemOfUnits import MeV
+from B2munuee import my_utils as utils
+
+from StrippingConf.Configuration import StrippingConf, StrippingStream
+from Configurables import EventNodeKiller, ProcStatusCheck
+
+from B2munuee import StrippingB23MuNu
+
+cuts = {
+    # B mother cuts
+    "CORRM_MIN": 2500.0 * MeV,
+    "CORRM_MAX": 10000.0 * MeV,
+    "DIRA": 0.99,
+    "BPT": 2000.0 * MeV,
+    "FlightChi2": 30.0,
+    "VertexChi2": 4.0,
+    "LOWERMASS": 0.0 * MeV,
+    "UPPERMASS": 7500.0 * MeV,
+
+    # cuts on electron
+    "Electron_PT": 200.0 * MeV,
+
+    # cuts on di-electron
+    "DiElectron_PT": 0.0 * MeV,
+    "DiElectron_MinMass": 0.0 * MeV,
+    "DiElectron_MaxMass": 6300.0 * MeV,
+
+    # cuts for photon selection
+    "Pi0_gamma_PT": 250.0 * MeV,
+    "Pi0_Jpsi_M_lower": 90.0 * MeV,
+    "Pi0_Jpsi_M_upper": 210.0 * MeV,
+
+    "Eta_gamma_PT": 400.0 * MeV,
+    "Eta_Jpsi_M_lower": 450.0 * MeV,
+    "Eta_Jpsi_M_upper": 650.0 * MeV,
+}
+
+def RunB23MuNuStripping(B23MuNu_lines):
+    '''
+    Run selected lines of the B23MuNu stripping on MC
+    Physics of the stripping selection is identical to the 
+    published versions of S28r2p1, S29r2p2 and S34r0p1
+    but has no RelInfoTool added due to missing muon raw coordinates
+    '''
+    STRIPPING_LINES = ['Stripping'+l for l in B23MuNu_lines]
+
+    STRIPPING_VERSIONS = {
+        '2016': 'Stripping28r2p1',
+        '2017': 'Stripping29r2p2',
+        '2018': 'Stripping34r0p1'
+    }
+    event_node_killer = EventNodeKiller('StripKiller')
+    event_node_killer.Nodes = ['/Event/AllStreams', '/Event/Strip']
+
+    # get year of MC
+    data_type = DaVinci().DataType
+
+    stripping_version = STRIPPING_VERSIONS[data_type]
+
+    # declare custom stream
+    custom_stream = StrippingStream('AllStreams')
+
+    my_linebuilder = StrippingB23MuNu.B23MuNuConf(
+        'B23MuNu', StrippingB23MuNu.default_config['CONFIG']
+    )
+
+    for line in my_linebuilder.lines():
+        if line.name() in STRIPPING_LINES:
+            line._prescale = 1.0
+            custom_stream.appendLines([line])
+
+    filterBadEvents = ProcStatusCheck()
+
+    sc = StrippingConf(Streams=[custom_stream],
+                    MaxCandidates=2000,
+                    AcceptBadEvents=False,
+                    BadEventSelection=filterBadEvents)
+
+    DaVinci().appendToMainSequence([event_node_killer, sc.sequence()])
+
+def RunDiElectronMaker(line, oppositeSign=True):
+    '''
+    Run DiElectronMaker on the electrons that passed the stripping selection
+    This instance does not use the proto-particles
+    '''
+    if oppositeSign:
+        dieLL = DiElectronMaker('DiElectron_' + line)
+    else:
+        dieLL = DiElectronMaker('DiElectron_' + line + '_SS')
+
+    dieLL.Particle = "J/psi(1S)"
+    dieLL.ElectronInputs = ['Phys/Electrons_'+line+'/Particles']
+
+    # some of these cuts do not differ from the default values
+    # but are implemented to allow easy configuration
+    # cut on electron pt is already applied in the selection for the input container
+    # is kept nonetheless in case the default requirement of DiElectronMaker
+    # is tighter than the custom requirement
+    dieLL.DiElectronMassMax = cuts['DiElectron_MaxMass']
+    dieLL.DiElectronMassMin = cuts['DiElectron_MinMass']
+    dieLL.DiElectronPtMin = cuts['DiElectron_PT']
+    dieLL.ElectronPtMin = cuts['Electron_PT']
+    dieLL.AddBrem = True
+    dieLL.OppositeSign = oppositeSign
+
+    updateDoD(dieLL)
+
+def MakePion(Dielectron):
+    '''
+    Conbine di-electron with a photon that is compatible with coming from a pi0 decay
+    '''
+
+    pionDaughter_cuts = {
+        'gamma': ("(PT > {Pi0_gamma_PT})").format(**cuts),
+        'J/psi(1S)': ("(MM < {Pi0_Jpsi_M_upper})").format(**cuts)
+    }
+    pionCombination_cut = ("(AM > {Pi0_Jpsi_M_lower})"
+                           " & (AM < {Pi0_Jpsi_M_upper})").format(**cuts)
+
+    photons = AutomaticData('Phys/StdLooseAllPhotons/Particles')
+
+    pion = CombineParticles(
+        "pion_withPhoton",
+        DecayDescriptor='pi0 -> J/psi(1S) gamma',
+        DaughtersCuts=pionDaughter_cuts,
+        CombinationCut=pionCombination_cut,
+        MotherCut=("(M > {Pi0_Jpsi_M_lower})"
+                   " & (M < {Pi0_Jpsi_M_upper})").format(**cuts)
+    )
+
+    pion_sel = Selection(
+        'pi0_selection',
+        Algorithm=pion,
+        RequiredSelections=[
+            Dielectron,
+            photons])
+
+    return pion_sel
+
+
+def MakeEta(Dielectron):
+    '''
+    Conbine di-electron with a photon that is compatible with coming from a eta decay
+    '''
+    etaDaughter_cuts = {
+        'gamma': ("(PT > {Eta_gamma_PT})").format(**cuts),
+        'J/psi(1S)': ("(MM < {Eta_Jpsi_M_upper})").format(**cuts)
+    }
+    etaCombination_cut = ("(AM > {Eta_Jpsi_M_lower})"
+                          " & (AM < {Eta_Jpsi_M_upper})").format(**cuts)
+
+    photons = AutomaticData('Phys/StdLooseAllPhotons/Particles')
+
+    eta = CombineParticles(
+        "eta_withPhoton",
+        DecayDescriptor='eta -> J/psi(1S) gamma',
+        DaughtersCuts=etaDaughter_cuts,
+        CombinationCut=etaCombination_cut,
+        MotherCut=("(M > {Eta_Jpsi_M_lower})"
+                   " & (M < {Eta_Jpsi_M_upper})").format(**cuts)
+    )
+
+    eta_sel = Selection(
+        'eta_selection',
+        Algorithm=eta,
+        RequiredSelections=[
+            Dielectron,
+            photons])
+
+    return eta_sel
+
+
+def MakeMother(tree):
+    '''
+    Combine di-electon object and muon to a B candidate and apply default cuts
+    '''
+    BMotherCut = (
+        "(BPVCORRM > {CORRM_MIN} *MeV) & "
+        "(BPVCORRM < {CORRM_MAX} *MeV) & "
+        "(BPVDIRA > {DIRA}) & "
+        "(PT > {BPT}) & "
+        "(BPVVDCHI2 > {FlightChi2}) & "
+        "(VFASPF(VCHI2/VDOF) < {VertexChi2}) &"
+        "(M > {LOWERMASS}) & "
+        "(M < {UPPERMASS})").format(**cuts)
+
+    if tree in ['pions']:
+        muons = AutomaticData('Phys/Muons_B23MuNu_MueeLine/Particles')
+        Jpsi = AutomaticData('Phys/DiElectron_B23MuNu_MueeLine/Particles')
+
+        pi0 = MakePion(Jpsi)
+        B = CombineParticles('MakeMother_' + tree,
+                             DecayDescriptor='[B+ -> pi0 mu+]cc',
+                             MotherCut=BMotherCut)
+        B_sel = Selection('Sel_B_' + tree, Algorithm=B,
+                          RequiredSelections=[pi0, muons])
+    elif tree in ['eta']:
+        muons = AutomaticData('Phys/Muons_B23MuNu_MueeLine/Particles')
+        Jpsi = AutomaticData('Phys/DiElectron_B23MuNu_MueeLine/Particles')
+
+        eta = MakeEta(Jpsi)
+        B = CombineParticles('MakeMother_' + tree,
+                             DecayDescriptor='[B+ -> eta mu+]cc',
+                             MotherCut=BMotherCut)
+        B_sel = Selection('Sel_B_' + tree, Algorithm=B,
+                          RequiredSelections=[eta, muons])
+    elif tree in ['SameSign']:
+        muons = AutomaticData('Phys/Muons_B23MuNu_MueeLine/Particles')
+        Jpsi = AutomaticData('Phys/DiElectron_B23MuNu_MueeLine_SS/Particles')
+
+        B = CombineParticles('MakeMother_' + tree,
+                             DecayDescriptors=[
+                                 '[B+ -> J/psi(1S) mu+]cc', '[B+ -> J/psi(1S) mu-]cc'],
+                             MotherCut=BMotherCut)
+        B_sel = Selection('Sel_B_' + tree, Algorithm=B,
+                          RequiredSelections=[Jpsi, muons])
+    elif tree in ['default']:
+        muons = AutomaticData('Phys/Muons_B23MuNu_MueeLine/Particles')
+        Jpsi = AutomaticData('Phys/DiElectron_B23MuNu_MueeLine/Particles')
+
+        B = CombineParticles('MakeMother_' + tree,
+                             DecayDescriptors=['[B+ -> J/psi(1S) mu+]cc'],
+                             MotherCut=BMotherCut)
+        B_sel = Selection('Sel_B_' + tree, Algorithm=B,
+                          RequiredSelections=[Jpsi, muons])
+    elif tree in ['JpsiKee']:
+        muons = AutomaticData('Phys/Muons_B23MuNu_MueeFakeLine/Particles')
+        Jpsi = AutomaticData('Phys/DiElectron_B23MuNu_MueeFakeLine/Particles')
+        
+        B = CombineParticles('MakeMother_' + tree,
+                             DecayDescriptors=['[B+ -> J/psi(1S) mu+]cc'],
+                             MotherCut=BMotherCut)
+        B_sel = Selection('Sel_B_' + tree, Algorithm=B,
+                          RequiredSelections=[Jpsi, muons])
+    else:
+        raise ValueError(
+            tree + ' is no valid tree for MakeMother')
+
+    return B_sel
+
+
+def MakeNTuples(B, name):
+    '''
+    Make nTuples with di-lepton object
+    Reference channels follow the naming convention of the respective signal channel
+    '''
+    dtt = DecayTreeTuple('B2MuNuEE_' + name)
+    if name in ['default','JpsiKee']:
+        dtt.setDescriptorTemplate(
+            '${B}[B+ -> ${Jpsi}(J/psi(1S) -> ${ep}e+ ${em}e-) ${mu}mu+]CC')
+    elif name in ['SameSign']:
+        dtt.setDescriptorTemplate(
+            '${B}[B+ -> ${Jpsi}(J/psi(1S) -> ${ep}e+ ${em}e+) ${mu}[mu+]cc]CC')
+    elif name in ['pions']:
+        dtt.setDescriptorTemplate(
+            '${B}[B+ -> ${pi}(pi0 -> ${Jpsi}(J/psi(1S) -> ${ep}e+ ${em}e-) ${gamma}gamma) ${mu}mu+]CC')
+    elif name in ['eta']:
+        dtt.setDescriptorTemplate(
+            '${B}[B+ -> ${eta}(eta -> ${Jpsi}(J/psi(1S) -> ${ep}e+ ${em}e-) ${gamma}gamma) ${mu}mu+]CC')
+    else:
+        raise ValueError(
+            name + ' is not a valid option for MakeNTuples(B, name)')
+
+    Seq = SelectionSequence('Sequence_' + name, TopSelection=B)
+    dtt.Inputs = Seq.outputLocations()
+
+    _seq = GaudiSequencer('MySequencer_' + name)
+    _seq.Members += [Seq.sequence(), dtt]
+    DaVinci().appendToMainSequence([_seq])
+
+    return dtt
+
+def MySelection(trees, isMC, run_stripping, addJpsiConstraints):
+
+    if isMC and run_stripping:
+        stream = 'Phys'
+    elif isMC and not run_stripping:
+        stream = '/Event/AllStreams/Phys'
+    else:
+        stream = '/Event/Semileptonic/Phys'
+
+    # check if trees are valid
+    valid_trees = ['default', 'SameSign', 'pions', 'eta', 'JpsiKee'] 
+    if not all(item in valid_trees for item in trees):
+        raise ValueError('At least one requested tree is not supported')
+
+    # run restripping if required
+    if run_stripping is True:
+        lines = []
+        if any(item in ['default','pions','eta','SameSign'] for item in trees):
+            lines.append('B23MuNu_MueeLine')
+        if 'JpsiKee' in trees:
+            lines.append('B23MuNu_MueeFakeLine')
+        RunB23MuNuStripping(lines)
+
+    # make filter for MueeLine if used in any tree
+    if any(item in ['default','pions','eta','SameSign'] for item in trees):
+        # select muons from default MueeLine
+        muon_Muee = FilterInTrees('Muons_B23MuNu_MueeLine',
+                            Inputs=['{}/B23MuNu_MueeLine/Particles'.format(stream)],
+                            Code="('mu+' == ABSID)")
+        updateDoD(muon_Muee)
+
+        # select electrons from default MueeLine
+        electrons_Muee = FilterInTrees('Electrons_B23MuNu_MueeLine',
+                            Inputs=['{}/B23MuNu_MueeLine/Particles'.format(stream)],
+                            Code="('e+' == ABSID)")
+        updateDoD(electrons_Muee)
+
+    # run DiElectronMaker for electrons from MueeLine if used in any tree
+    if any(item in ['default','pions','eta'] for item in trees):
+        RunDiElectronMaker('B23MuNu_MueeLine')
+
+
+    for tree in trees:
+        # if tree does not use MueeLine
+        if tree in ['JpsiKee']:
+            line = 'B23MuNu_MueeFakeLine'
+
+            # select muons from stripping or std container
+            muon_filter = FilterInTrees('Muons_'+line,
+                                Inputs=['{}/{}/Particles'.format(stream, line)],
+                                Code="('mu+' == ABSID)")
+            updateDoD(muon_filter)
+
+            # select electrons from stripping or std container
+            electrons_filter = FilterInTrees('Electrons_'+line,
+                                Inputs=['{}/{}/Particles'.format(stream, line)],
+                                Code="('e+' == ABSID)")
+            updateDoD(electrons_filter)
+
+            # run dieelctron maker
+            RunDiElectronMaker('B23MuNu_MueeFakeLine')
+
+        # if SameSign also need to run DiElectronMaker again
+        if tree == 'SameSign':
+            RunDiElectronMaker('B23MuNu_MueeLine', oppositeSign=False)
+
+        # make Mother particle
+        mother = MakeMother(tree)
+
+        # make nTuple
+        dtt = MakeNTuples(mother, tree)
+        
+        # now fill the tuples
+        utils.DefaultToolList(dtt)
+        if isMC:
+            utils.MCToolList(dtt)
+        utils.TriggerToolList(dtt)
+        utils.DefaultLoKi(dtt)
+        if tree in ['pions','eta']:
+            utils.AddProtoPInfo(dtt)
+        utils.AddDOCA(dtt)
+        if tree in ['default'] and addJpsiConstraints:
+            utils.JpsiConstraints(dtt)
+        utils.TupleToolConeIsolation(dtt)
+        utils.TupleToolSubstitution(dtt)
\ No newline at end of file
diff --git a/B2munuee/my_utils.py b/B2munuee/my_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc2c402519ec3efbf23751083323ce886c406033
--- /dev/null
+++ b/B2munuee/my_utils.py
@@ -0,0 +1,300 @@
+from Configurables import TupleToolTISTOS, BackgroundCategory
+from Configurables import LoKi__Hybrid__TupleTool, LoKi__Hybrid__Dict2Tuple, LoKi__Hybrid__DTFDict, LoKi__Hybrid__DictOfFunctors
+
+
+def DefaultToolList(dtt):
+    '''Add default list of TupleTools'''
+    dtt.ToolList = [
+        # default tools
+        "TupleToolKinematic",
+        "TupleToolEventInfo",
+        "TupleToolGeometry",
+        "TupleToolANNPID",
+
+        # additional tools w/o configuration required
+        "TupleToolRecoStats",
+        "TupleToolPhotonInfo",
+        "TupleToolPropertime",
+        'TupleToolCorrectedMass']
+
+    # add PID info
+    PID = dtt.addTupleTool("TupleToolPid")
+    PID.Verbose = True
+
+    # add VELO track info
+    TrackInfo = dtt.addTupleTool('TupleToolTrackInfo')
+    TrackInfo.Verbose = True
+
+    # add Brem info for single and di-eletcron
+    BremInfo = dtt.addTupleTool('TupleToolBremInfo')
+    BremInfo.fullDST = True
+    BremInfo.Particle = ['e+', 'J/psi(1S)']
+
+    # add tool for isolation
+    IsoGeneric = dtt.addTupleTool("TupleToolIsoGeneric")
+    IsoGeneric.Verbose = True
+
+    dtt.ep.addTupleTool('TupleToolL0Calo/ep_L0Calo')
+    dtt.ep.ep_L0Calo.WhichCalo = "ECAL"
+    dtt.em.addTupleTool('TupleToolL0Calo/em_L0Calo')
+    dtt.em.em_L0Calo.WhichCalo = "ECAL"
+
+def MCToolList(dtt):
+    backgroundinfo = dtt.addTupleTool("TupleToolMCBackgroundInfo")
+    backgroundinfo.addTool(BackgroundCategory('BackgroundCategory'))
+
+    MCTruth = dtt.addTupleTool("TupleToolMCTruth")
+    MCTruth.addTupleTool("MCTupleToolHierarchy")
+
+def TriggerToolList(dtt):
+    '''Add trigger information'''
+
+    L0trigger = ["L0ElectronDecision",
+                 "L0MuonDecision",
+                 "L0HadronDecision",
+                 "L0PhotonDecision"]
+
+    Hlt1trigger = ["Hlt1TrackMVADecision",
+                   "Hlt1TwoTrackMVADecision",
+                   "Hlt1TrackMVALooseDecision",
+                   "Hlt1TwoTrackMVALooseDecision",
+                   "Hlt1TrackMuonDecision",
+                   "Hlt1TrackMuonMVADecision",
+                   "Hlt1L0AnyDecision"]
+
+    Hlt2trigger = [
+        "Hlt2SingleMuonDecision",
+        # Topo
+        "Hlt2Topo2BodyDecision",
+        "Hlt2Topo3BodyDecision",
+        "Hlt2TopoE2BodyDecision",
+        "Hlt2TopoE3BodyDecision",
+        "Hlt2TopoEE2BodyDecision",
+        "Hlt2TopoEE3BodyDecision",
+        "Hlt2TopoMu2BodyDecision",
+        "Hlt2TopoMu3BodyDecision",
+        "Hlt2TopoMuE2BodyDecision",
+        "Hlt2TopoMuE3BodyDecision",
+        "Hlt2TopoMu2BodyBBDTDecision",
+        "Hlt2TopoMu3BodyBBDTDecision"]
+
+    triggerList = L0trigger + Hlt1trigger + Hlt2trigger
+
+    TISTOS = TupleToolTISTOS('TISTOS')
+    TISTOS.VerboseL0 = True
+    TISTOS.VerboseHlt1 = True
+    TISTOS.VerboseHlt2 = True
+    TISTOS.TriggerList = triggerList
+    dtt.B.addTupleTool(TISTOS)
+
+    # for electrons and muon only need L0 information
+    TISTOS_Leptons = TupleToolTISTOS('TISTOS_Leptons')
+    TISTOS_Leptons.VerboseL0 = True
+    TISTOS_Leptons.VerboseHlt1 = False
+    TISTOS_Leptons.VerboseHlt2 = False
+    TISTOS_Leptons.TriggerList = L0trigger
+    dtt.ep.addTupleTool(TISTOS_Leptons)
+    dtt.em.addTupleTool(TISTOS_Leptons)
+    dtt.mu.addTupleTool(TISTOS_Leptons)
+
+
+def DefaultLoKi(dtt):
+    '''
+    Add default LoKi functors for decay topology and kinematics
+    '''
+
+    LoKi_All = dtt.addTupleTool("LoKi::Hybrid::TupleTool/LoKi_All")
+    LoKi_All.Variables = {
+        'MINIPCHI2': "MIPCHI2DV(PRIMARY)",
+        'MINIP': "MIPDV(PRIMARY)",
+        'ETA': 'ETA',
+        'PHI': 'PHI'
+    }
+
+    # add track information
+    LoKi_Track = LoKi__Hybrid__TupleTool("LoKi_Track")
+    LoKi_Track.Preambulo = ["from LoKiTracks.decorators import *"]
+    LoKi_Track.Variables = {
+        "TrPX": "TRFUN(TrPX)",
+        "TrPY": "TRFUN(TrPY)",
+        "TrPZ": "TRFUN(TrPZ)",
+        "TrP": "TRFUN(TrP)",
+    }
+
+    dtt.ep.ToolList += ["LoKi::Hybrid::TupleTool/LoKi_Track"]
+    dtt.ep.addTool(LoKi_Track)
+    dtt.em.ToolList += ["LoKi::Hybrid::TupleTool/LoKi_Track"]
+    dtt.em.addTool(LoKi_Track)
+
+    LoKi_B = dtt.B.addTupleTool("LoKi::Hybrid::TupleTool/LoKi_B")
+    LoKi_B.Variables = {
+        'DIRA_OWNPV': "BPVDIRA",
+        'ENDVERTEX_CHI2': "VFASPF(VCHI2/VDOF)",
+        'X_travelled': "VFASPF(VX)-BPV(VX)",
+        'Y_travelled': "VFASPF(VY)-BPV(VY)",
+        'Z_travelled': "VFASPF(VZ)-BPV(VZ)",
+        'P_Parallel': "BPVDIRA*P",
+        'P_Perp': "sin(acos(BPVDIRA))*P",
+        'BPVVDZ': "BPVVDZ",
+    }
+
+def AddProtoPInfo(dtt):
+
+    LokiVars = {
+        "PP_CaloNeutralID": "PPINFO(LHCb.ProtoParticle.CaloNeutralID, -1000)",
+        "PP_CaloChargedID": "PPINFO(LHCb.ProtoParticle.CaloChargedID, -1000)",
+        "PP_CaloDepositID": "PPINFO(LHCb.ProtoParticle.CaloDepositID, -1000)",
+        "PP_CaloBremMatch": "PPINFO(LHCb.ProtoParticle.CaloBremMatch, -1000)",
+        "PP_CaloBremChi2": "PPINFO(LHCb.ProtoParticle.CaloBremChi2, -1000)",
+    }
+
+    LoKi_ep = dtt.ep.addTupleTool("LoKi::Hybrid::TupleTool")
+    LoKi_ep.Variables = LokiVars
+
+    LoKi_em = dtt.em.addTupleTool("LoKi::Hybrid::TupleTool")
+    LoKi_em.Variables = LokiVars
+
+    if dtt.Decay in ['[B+ -> ^(pi0 -> ^(J/psi(1S) -> ^e+ ^e-) ^gamma) ^mu+]CC',
+                     '[B+ -> ^(eta -> ^(J/psi(1S) -> ^e+ ^e-) ^gamma) ^mu+]CC']:
+
+        LoKi_gamma = dtt.gamma.addTupleTool("LoKi::Hybrid::TupleTool")
+        LoKi_gamma.Variables = LokiVars
+
+def AddDOCA(dtt):
+    '''
+    Add distance of closest approach DOCA for the three lepton tracks
+    '''
+    if dtt.Decay == '[B+ -> ^(J/psi(1S) -> ^e+ ^e-) ^mu+]CC':
+        doca_name, location1, location2 = zip(
+            ['epem', 
+             '[B+ -> (J/psi(1S) -> ^e+ e-) mu+]CC',
+             '[B+ -> (J/psi(1S) -> e+ ^e-) mu+]CC'],
+            ['epmu', 
+             '[B+ -> (J/psi(1S) -> ^e+ e-) mu+]CC',
+             '[B+ -> (J/psi(1S) -> e+ e-) ^mu+]CC'],
+            ['emmu', 
+             '[B+ -> (J/psi(1S) -> e+ ^e-) mu+]CC', 
+             '[B+ -> (J/psi(1S) -> e+ e-) ^mu+]CC'])
+    elif dtt.Decay == '[B+ -> ^(J/psi(1S) -> ^e+ ^e+) ^[mu+]cc]CC':
+        doca_name, location1, location2 = zip(
+            ['epem', 
+             '[B+ -> (J/psi(1S) -> ^e+ e+) [mu+]cc]CC',
+             '[B+ -> (J/psi(1S) -> e+ ^e+) [mu+]cc]CC'],
+            ['epmu', 
+             '[B+ -> (J/psi(1S) -> ^e+ e+) [mu+]cc]CC',
+             '[B+ -> (J/psi(1S) -> e+ e+) ^[mu+]cc]CC'],
+            ['emmu', 
+             '[B+ -> (J/psi(1S) -> e+ ^e+) [mu+]cc]CC', 
+             '[B+ -> (J/psi(1S) -> e+ e+) ^[mu+]cc]CC'])
+    elif dtt.Decay == '[B+ -> ^(pi0 -> ^(J/psi(1S) -> ^e+ ^e-) ^gamma) ^mu+]CC':
+        doca_name, location1, location2 = zip(
+            ['epem',
+             '[B+ -> (pi0 -> (J/psi(1S) -> ^e+ e-) gamma) mu+]CC',
+             '[B+ -> (pi0 -> (J/psi(1S) -> e+ ^e-) gamma) mu+]CC'],
+            ['epmu',
+             '[B+ -> (pi0 -> (J/psi(1S) -> ^e+ e-) gamma) mu+]CC',
+             '[B+ -> (pi0 -> (J/psi(1S) -> e+ e-) gamma) ^mu+]CC'],
+            ['emmu', 
+             '[B+ -> (pi0 -> (J/psi(1S) -> e+ ^e-) gamma) mu+]CC', 
+             '[B+ -> (pi0 -> (J/psi(1S) -> e+ e-) gamma) ^mu+]CC'])
+    elif dtt.Decay == '[B+ -> ^(eta -> ^(J/psi(1S) -> ^e+ ^e-) ^gamma) ^mu+]CC':
+        doca_name, location1, location2 = zip(
+            ['epem',
+             '[B+ -> (eta -> (J/psi(1S) -> ^e+ e-) gamma) mu+]CC',
+             '[B+ -> (eta -> (J/psi(1S) -> e+ ^e-) gamma) mu+]CC'],
+            ['epmu',
+             '[B+ -> (eta -> (J/psi(1S) -> ^e+ e-) gamma) mu+]CC',
+             '[B+ -> (eta -> (J/psi(1S) -> e+ e-) gamma) ^mu+]CC'],
+            ['emmu', 
+             '[B+ -> (eta -> (J/psi(1S) -> e+ ^e-) gamma) mu+]CC', 
+             '[B+ -> (eta -> (J/psi(1S) -> e+ e-) gamma) ^mu+]CC'])
+    else:
+        raise ValueError('Decay decsriptor is not valid to call TupleToolDOCA')
+
+    DOCA = dtt.B.addTupleTool("TupleToolDOCA")
+    DOCA.Name = list(doca_name)
+    DOCA.Location1 = list(location1)
+    DOCA.Location2 = list(location2)
+
+def JpsiConstraints(dtt):
+    '''
+    Add variables with constraints on the di-electron mass
+    The variables listed allow to calculate a fully constrained m2miss offline
+    '''
+
+    if dtt.Decay not in [
+        '[B+ -> ^(J/psi(1S) -> ^e+ ^e-) ^mu+]CC',
+        '[B+ -> ^(J/psi(1S) -> ^e+ ^e+) ^[mu+]cc]CC']:
+        raise ValueError(
+            'Decay decsriptor is not valid to call JpsiConstraints')
+
+    DTF_vars = {
+        'M': 'M',
+        'PX': 'PX',
+        'PY': 'PY',
+        'PZ': 'PZ',
+        'PT': 'PT',
+
+        'Jpsi_M': 'CHILD(1, M)',
+        'Jpsi_PX': 'CHILD(1, PX)',
+        'Jpsi_PY': 'CHILD(1, PY)',
+        'Jpsi_PZ': 'CHILD(1, PZ)',
+
+        'X_travelled': 'VFASPF(VX)-BPV(VX)',
+        'Y_travelled': 'VFASPF(VY)-BPV(VY)',
+        'Z_travelled': 'VFASPF(VZ)-BPV(VZ)',
+
+        'PVCORRM': 'BPVCORRM',
+        'PVCORRMERR': 'BPVCORRMERROR',
+    }
+
+    JpsiTuple = dtt.B.addTupleTool(LoKi__Hybrid__Dict2Tuple, 'JpsiTuple')
+
+    JpsiTuple.addTool(LoKi__Hybrid__DTFDict, 'JpsiDTF')
+    JpsiTuple.Source = 'LoKi::Hybrid::DTFDict/JpsiDTF'
+    JpsiTuple.NumVar = 15
+    JpsiTuple.JpsiDTF.constrainToOriginVertex = False
+    JpsiTuple.JpsiDTF.daughtersToConstrain = ['J/psi(1S)']
+
+    JpsiTuple.JpsiDTF.addTool(LoKi__Hybrid__DictOfFunctors, 'dict_Jpsi')
+    JpsiTuple.JpsiDTF.Source = 'LoKi::Hybrid::DictOfFunctors/dict_Jpsi'
+    JpsiTuple.JpsiDTF.dict_Jpsi.Variables = DTF_vars
+
+
+def TupleToolConeIsolation(dtt):
+    '''Add cone isolation variables'''
+
+    dtt.Jpsi.addTupleTool('TupleToolConeIsolation')
+
+    dtt.Jpsi.TupleToolConeIsolation.ExtraParticlesLocation = 'Phys/StdAllNoPIDsPions/Particles'
+    dtt.Jpsi.TupleToolConeIsolation.MaxPtParticlesLocation = 'Phys/StdAllLoosePions/Particles'
+    dtt.Jpsi.TupleToolConeIsolation.ExtraPhotonsLocation = 'Phys/StdLooseAllPhotons/Particles'
+
+    dtt.Jpsi.TupleToolConeIsolation.MinConeSize = 0.4
+    dtt.Jpsi.TupleToolConeIsolation.MaxConeSize = 0.6
+    dtt.Jpsi.TupleToolConeIsolation.SizeStep = 0.2
+
+    dtt.Jpsi.TupleToolConeIsolation.FillCharged = True
+    dtt.Jpsi.TupleToolConeIsolation.FillNeutral = True
+
+    dtt.Jpsi.TupleToolConeIsolation.FillAsymmetry = True
+    dtt.Jpsi.TupleToolConeIsolation.FillDeltas = True
+    dtt.Jpsi.TupleToolConeIsolation.FillIsolation = True
+
+    dtt.Jpsi.TupleToolConeIsolation.FillMaxPt = True
+    dtt.Jpsi.TupleToolConeIsolation.FillComponents = True
+    dtt.Jpsi.TupleToolConeIsolation.FillPi0Info = False
+    dtt.Jpsi.TupleToolConeIsolation.FillMergedPi0Info = False
+
+
+def TupleToolSubstitution(dtt):
+    '''Add variables for lepton -> hadron (double-)substitutions'''
+
+    substitutions = [
+        'mu+ => K+',
+        'mu+ => pi+',
+        'mu+ => p+'
+    ]
+    SubstTool = dtt.B.addTupleTool('TupleToolSubMass/B_SubMass')
+    SubstTool.Substitution += substitutions
+