layout | title | subtitle | minutes |
---|---|---|---|
page |
Second analysis steps |
Building your own decay |
15 |
- Learn the concepts behind the LHCb selection framework
- Build a decay chain
In order to perform most physics analyses we need to build a decay chain with reconstructed particles that represents the physics process we want to study.
In LHCb, this decay chain can be built through LHCb::Particle
and LHCb::MCParticle
objects that represent individual particles and contain links to their children, also represented by the same type of object.
We'll learn all the concepts involved by running through a full example:
using the DST file we downloaded in the Downloading a file from the Grid lesson, we will build our own
The typical approach is to build the decay from the bottom up. Therefore, we need to
- Get input pions and kaons and filter them according to our physics needs.
- Combine a pion and a kaon to build a
$D^0$ , and apply selection cuts to it. - Combine this
$D^0$ with a pion to build the$D^\ast$ , again filtering when necessary.
To do that, we need to know a little bit more about how the LHCb analysis framework works.
As discussed in the Gaudi introduction, Gaudi
is based on the event-by-event sequential (chained) execution of algorithms wrapped in a GaudiSequencer
, which takes care of handling the execution order such that processing stops when an algorithm is not passed.
However, it does not handle the data dependencies between these algorithms nor does it give easy access to them.
To solve this problem, the Selection Framework was created, and it is based on two types of objects: Selection
and SelectionSequence
.
The
Selection
is the basic unit of the framework. It uses otherSelections
to processLHCb::Particles
and writes them to a TES location easily findable through itsoutputLocation
method. Additionally, it knows about otherSelections
that it requires to pass in order to obtain input particles through itsRequiredSelections
argument. ASelection
requires all of itsRequiredSelections
to pass.There are several types of selections, for example
Selection
, the most basic class.MergedSelection
, which is used to join the output of severalSelection
objects into a single TES location.DataOnDemand
(also known asAutomaticData
), which builds objects from their TES location using a preconfigured map of (location, algorithm) pairs.
The
SelectionSequence
takes aSelection
object, resolves itsSelection
requirements, and builds a flat, chained and ordered list ofSelections
. It then exports (via theselection
method) a self-containedGaudiSequencer
with all the algorithm configurables necessary to run the selection. It also makes the output locations of the data written by the selection chain available via theoutputLocations
method.
To get our input particles we use the DataOnDemand
service:
from PhysSelPython.Wrappers import DataOnDemand
Pions = DataOnDemand('Phys/StdAllNoPIDsPions/Particles')
Kaons = DataOnDemand('Phys/StdAllLooseKaons/Particles')
As discussed previously, the
DataOnDemand
orAutomaticData
selection builds objects from their TES location. In Gaudi, the TES location of the output of an algorithm is generally determined asPhys/ALGO_NAME/OBJECT_TYPE
, whereOBJECT_TYPE
refers toParticles
,Vertices
, etc.The
CommonParticles
package, which you can find here, allows to access premade particles with reasonable selections for us to use withDataOnDemand
. For example, in our specific case, we use theDataOnDemand
class with thePhys/StdAllNoPIDsPions/Particles
andPhys/StdAllLooseKaons/Particles
locations to access the output of theStdAllNoPIDsPions
andStdAllLooseKaons
algorithms, respectively:
Once we have the input pions and kaons, we can combine them to build a CombineParticles
algorithm.
This algorithm performs the combinatorics for us according to a given decay descriptor and puts the resulting particle in the TES, allowing also to apply some cuts on them:
-
DaughtersCuts
is a dictionary that maps each child particle type to a LoKi particle functor that determines if that particular particle satisfies our selection criteria. Optionally, one can specify also aPreambulo
property that allows us to make imports, preprocess functors, etc (more on this in the LoKi functors lesson). For example:d0_daughters = { 'pi-': '(PT > 750*MeV) & (P > 4000*MeV) & (MIPCHI2DV(PRIMARY) > 4)', 'K+': '(PT > 750*MeV) & (P > 4000*MeV) & (MIPCHI2DV(PRIMARY) > 4)' }
-
CombinationCut
is a particle array LoKi functor (note theA
prefix, see more here) that is given the array of particles in a single combination (the children) as input (in our case a kaon and a pion). This cut is applied before the vertex fit so it is typically used to save CPU time by performing some sanity cuts such asAMAXDOCA
orADAMASS
before the CPU-consuming fit:d0_comb = "(AMAXDOCA('') < 0.2*mm) & (ADAMASS('D0') < 100*MeV)"
-
MotherCut
is a selection LoKi particle functor that acts on the particle produced by the vertex fit (the parent) from the input particles, which allows to apply cuts on those variables that require a vertex, for example:# We can split long selections across multiple lines d0_mother = ( '(VFASPF(VCHI2/VDOF)< 9)' '& (BPVDIRA > 0.9997)' "& (ADMASS('D0') < 70*MeV)" )
Then, we can build a combiner as
from Configurables import CombineParticles
d0 = CombineParticles(
'Combine_D0',
DecayDescriptor='[D0 -> pi- K+]cc',
DaughtersCuts=d0_daughters,
CombinationCut=d0_comb,
MotherCut=d0_mother
)
Now we have to build a Selection
out of it so we can later on put all pieces together:
from PhysSelPython.Wrappers import Selection
d0_sel = Selection(
'Sel_D0',
Algorithm=d0,
RequiredSelections=[Pions, Kaons]
)
This two-step process for building the Selection
(creating an algorithm and building a selection with it) can be simplified by using a helper function in the PhysSelPython.Wrappers
module, called SimpleSelection
.
It gets a selection name, the algorithm type we want to run, the inputs and any other parameters that need to be passed to the algorithm (as keyword arguments), and returns a Selection
object build in the same two-step way.
With that in mind, we can rewrite the previous two pieces of code as
import GaudiConfUtils.ConfigurableGenerators as ConfigurableGenerators
from PhysSelPython.Wrappers import SimpleSelection
d0_sel = SimpleSelection(
'Sel_D0',
ConfigurableGenerators.CombineParticles,
[Pions, Kaons],
DecayDescriptor='([D0 -> pi- K+]CC)',
DaughtersCuts=d0_daughters,
CombinationCut=d0_comb,
MotherCut=d0_mother
)
Note how we needed to use the CombineParticles
from GaudiConfUtils.ConfigurableGenerators
instead of the PhysSelPython.Wrappers
one to make this work.
This is because the LHCb algorithms are configured as singletons and it is mandatory to give them a name, which we don't want to in SimpleSelection
(we want to skip steps!).
If we had tried to simply use
CombineParticles
inside ourSimpleSelection
, we would have seen it fail with the following errorNameError: Could not instantiate Selection because input Configurable CombineParticles has default name. This is too unsafe to be allowed.
The reason for this is that all LHCb algorithms need an explicit and unique name because they are singletons, and therefore an exception is thrown if we don't do that. A singleton is a software design pattern that restricts the instantiation of a class to one object. In our case, only one algorithm with a given name can be instantiated. This allows to reuse and reload algorithms that have already been created in the configuration sequence, eg, we could have reloaded the
"Combine_D0"
CombineParticles
by name and modified it (even in another file loaded in the samegaudirun.py
call!):d0_copy = CombineParticles('Combine_D0') print d0_copy.DecayDescriptorThis is very useful to build complicated configuration chains.
The solution for the
SimpleSelection
problem, in which we actually don't care about theCombineParticles
name, is theGaudiConfUtils.ConfigurableGenerators
package: it contains wrappers around algorithms such asCombineParticles
orFilterDesktop
allowing them to be instantiated without an explicit name argument.
Now we can use another CombineParticles
to build the
dstar_daughters = {
'pi+': '(TRCHI2DOF < 3) & (PT > 100*MeV)'
}
dstar_comb = "(ADAMASS('D*(2010)+') < 400*MeV)"
dstar_mother = (
"(abs(M-MAXTREE('D0'==ABSID,M)-145.42) < 10*MeV)"
'& (VFASPF(VCHI2/VDOF)< 9)'
)
dstar_sel = SimpleSelection(
'Sel_Dstar',
ConfigurableGenerators.CombineParticles,
[d0_sel, Pions],
DecayDescriptor='[D*(2010)+ -> D0 pi+]cc',
DaughtersCuts=dstar_daughters,
CombinationCut=dstar_comb,
MotherCut=dstar_mother
)
In some cases we may want to build several decays in the same script with some common particles/selection; for example, in our case we could have been building
$D^0\rightarrow KK$ in the same script, and then we would have wanted to select the soft pion in the same way when building the$D^\ast$ . In this situation, we can make use of theFilterDesktop
algorithm, which takes a TES location and filters the particles inside according to a given LoKi functor in theCode
property, which then can be given as input to aSelection
:from Configurables import FilterDesktop soft_pion = FilterDesktop('Filter_SoftPi', Code='(TRCHI2DOF < 3) & (PT > 100*MeV)') soft_pion_sel = Selection('Sel_SoftPi', Algorithm=soft_pion, RequiredSelections=[Pions]) dstar = CombineParticles( 'CombineDstar', DecayDescriptor='[D*(2010)+ -> D0 pi+]cc', CombinationCut="(ADAMASS('D*(2010)+') < 400*MeV)", MotherCut=( "(abs(M-MAXTREE('D0'==ABSID,M)-145.42) < 10*MeV)" '& (VFASPF(VCHI2/VDOF)< 9)' ) ) dstar_sel = Selection( 'Sel_Dstar', Algorithm=dstar, RequiredSelections=[d0_sel, soft_pion_sel] )This allows us to save time by performing the filtering of the soft pions only once, and to keep all the common cuts in a single place, avoiding duplication of code.
We can now build build a SelectionSequence
to add to the DaVinci
execution sequence
from PhysSelPython.Wrappers import SelectionSequence
dstar_seq = SelectionSequence('Dstar_Seq', TopSelection=dstar_sel)
from Configurables import DaVinci
DaVinci().UserAlgorithms += [dstar_seq.sequence()]
The
PhysSelPython.Wrappers
offers a very useful utility for debugging your selection chains, calledPrintSelection
. It gets aSelection
as input and it can be used the same way, except it will print the decay tree everytime making use of thePrintDecayTree
algorithm which was discussed in the Exploring a DST lesson.For more complex debugging, one can setup
DaVinci
withgraphviz
(see more details here)SetupDaVinci --use "graphviz v* LCG_Interfaces"
and create a graph representation of the sequence of algorithms and their dependencies:
from SelPy.graph import graph graph(dstar_sel, format='png')Note that currently it is not possible to load
graphviz
withlb-run
, but it is expected it will be possible in the near future.
- Finish the script (the base of which can be found here) by adapting the basic
DaVinci
configuration from its corresponding lesson and check the output ntuple.- Replace the
"Combine_D0"
and"Sel_D0"
objects by a singleSimpleSelection
.- Do you know what the used LoKi functors (
AMAXDOCA
,ADAMASS
,MIPCHI2DV
, etc) do?- Add a
PrintSelection
in your selections and run again.- Create a
graph
of the selection.