Belle II Software  release-08-01-10
B_specific_train.py
1 #!/usr/bin/env python3
2 
3 
10 
11 # Steering file to train the specific FEI on Belle II MC, but it can be also easily adapted for converted Belle MC.
12 # This steering file is called several times (so-called stages) during the training process of the FEI.
13 # For reference see Confluence and Thomas Keck's PhD thesis.
14 #
15 # Please adapt for your signal channel. Note that a large amount of MC is needed to train the specific FEI.
16 # I usually use 100 million of signal events for each final state, mixed and charged MC.
17 # This example is for hadronic tagging.
18 
19 import fei
20 import basf2 as b2
21 import modularAnalysis as ma
22 
23 # Create path
24 path = b2.create_path()
25 
26 # Load input ROOT file
27 ma.inputMdst(filename=b2.find_file('mdst14.root', 'validation', False),
28  path=path)
29 
30 # Max 12 tracks per event - this avoids much computing time.
31 empty_path = b2.create_path()
32 skimfilter = b2.register_module('VariableToReturnValue')
33 skimfilter.param('variable', 'nCleanedTracks(dr < 2 and abs(dz) < 4)')
34 skimfilter.if_value('>12', empty_path, b2.AfterConditionPath.END)
35 path.add_module(skimfilter)
36 
37 # Signal side reconstruction
38 ma.fillParticleList('mu+', 'muonID > 0.8 and dr < 2 and abs(dz) < 4', writeOut=True, path=path)
39 ma.fillParticleList('e+', 'electronID > 0.8 and dr < 2 and abs(dz) < 4', writeOut=True, path=path)
40 ma.fillParticleList(
41  'gamma',
42  '[[clusterReg == 1 and E > 0.10] or [clusterReg == 2 and E > 0.09] or [clusterReg == 3 and E > 0.16]]',
43  writeOut=True,
44  path=path)
45 ma.reconstructDecay(
46  'B+:sig_e -> gamma e+',
47  '1.000 < M < 6.000 and cos(useRestFrame(daughterAngle(0, 1))) < 0.6',
48  dmID=1,
49  writeOut=True,
50  path=path)
51 ma.reconstructDecay(
52  'B+:sig_mu -> gamma mu+',
53  '1.000 < M < 6.000 and cos(useRestFrame(daughterAngle(0, 1))) < 0.6',
54  dmID=2,
55  writeOut=True,
56  path=path)
57 ma.copyLists('B+:sig', ['B+:sig_e', 'B+:sig_mu'], writeOut=True, path=path)
58 ma.looseMCTruth('B+:sig', path=path)
59 ma.rankByHighest('B+:sig', 'daughter(0,E)', outputVariable='PhotonCandidateRank', path=path)
60 ma.buildRestOfEvent('B+:sig', path=path)
61 clean_roe_mask = (
62  'CleanROE',
63  'dr < 2 and abs(dz) < 4',
64  'clusterE9E25 > 0.9 and clusterTiming < 50 and E > 0.09 and clusterTrackMatch==0')
65 ma.appendROEMasks('B+:sig', [clean_roe_mask], path=path)
66 ma.applyCuts('B+:sig', 'roeDeltae(CleanROE) < 2.0 and roeMbc(CleanROE) > 4.8', path=path)
67 
68 skimfilter = b2.register_module('SkimFilter')
69 skimfilter.param('particleLists', ['B+:sig'])
70 empty_path = b2.create_path()
71 skimfilter.if_value('=0', empty_path, b2.AfterConditionPath.END)
72 path.add_module(skimfilter)
73 
74 # Prepare list for the training.
75 path.add_module('MCDecayFinder', decayString='B+ -> e+ nu_e gamma', listName='B+:FEIMC_e', writeOut=True)
76 path.add_module('MCDecayFinder', decayString='B+ -> mu+ nu_mu gamma', listName='B+:FEIMC_mu', writeOut=True)
77 ma.copyLists('B+:FEIMC', ['B+:FEIMC_e', 'B+:FEIMC_mu'], writeOut=True, path=path)
78 
79 
80 # We want the FEI to be only trained on a correctly reconstruced signal side and on wrongly reconstructed background.
81 isSignal = 'isSignalAcceptMissingNeutrino'
82 signalMC = 'eventCached(countInList(B+:FEIMC))'
83 cut = '[[{mc} > 0 and {sig} == 1] or [{mc} == 0 and {sig} != 1]]'.format(mc=signalMC, sig=isSignal)
84 ma.applyCuts('B+:sig', cut, path=path)
85 
86 # Set up FEI configuration specifying the FEI prefix
87 fei_tag = 'my_specFEI'
88 belle_particles = fei.get_default_channels(KLong=False,
89  chargedB=True,
90  neutralB=True,
91  semileptonic=False,
92  B_extra_cut='nRemainingTracksInEvent <= 3',
93  specific=True)
94 
95 # Get FEI path
96 configuration = fei.config.FeiConfiguration(prefix=fei_tag, training=True, monitor=False, cache=-1)
97 
98 
99 # Add FEI path to the path to be processed
100 feistate = fei.get_path(belle_particles, configuration)
101 
102 # FEI training
103 if feistate.stage == 0:
104  # Write out the rest of event, we train only on the rest of event of our signal side.
105  # This is the main difference compared to the generic FEI.
106  rO = b2.register_module('RootOutput')
107  rO.set_name('ROE_RootOutput')
108  rO.param('additionalBranchNames', ['RestOfEvent'])
109  feistate.path.add_module(rO)
110  roe_path = b2.create_path()
111  cond_module = b2.register_module('SignalSideParticleFilter')
112  cond_module.param('particleLists', ['B+:sig'])
113  cond_module.if_true(feistate.path, b2.AfterConditionPath.END)
114  roe_path.add_module(cond_module)
115  path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
116 else:
117  # After stage 0, the training is done only on the written out rest of event.
118  path = b2.create_path()
119  ma.inputMdstList([], path)
120  path.add_path(feistate.path)
121  r1 = b2.register_module('RootOutput')
122  r1.set_name('ROE_RootOutput')
123  r1.param('additionalBranchNames', ['RestOfEvent'])
124  path.add_module(r1)
125 
126 
127 # Process 100 events
128 b2.process(path, max_event=100)