Belle II Software light-2607-kasei
config.py
1#!/usr/bin/env python3
2
3
10
11"""
12 Configuration classes
13
14 The classes defined here are used to uniquely define a FEI training.
15 Meaning:
16 - The global configuration like database prefix, cache mode, monitoring, ... (FeiConfiguration)
17 - The reconstructed Particles (Particle)
18 - The reconstructed Channels of each particle (DecayChannel)
19 - The MVA configuration for each channel (MVAConfiguration)
20 - The Cut definitions of each channel (PreCutConfiguration)
21 - The Cut definitions of each particle (PostCutConfiguration)
22"""
23
24import collections
25import copy
26import re
27import itertools
28import typing
29import basf2
30
31# Define classes at top level to make them pickable
32# Creates new class via namedtuple, which are like a struct in C
33
34FeiConfiguration = collections.namedtuple('FeiConfiguration',
35 'prefix, cache, monitor, legacy, externTeacher, training, roundMode, monitoring_path')
36FeiConfiguration.__new__.__defaults__ = ('FEI_TEST', None, True, None, 'basf2_mva_teacher', False, 0, '')
37FeiConfiguration.__doc__ = "Fei Global Configuration class"
38FeiConfiguration.prefix.__doc__ = "The database prefix used for all weight files"
39FeiConfiguration.cache.__doc__ = "The stage which is passed as input, it is assumed that all previous stages"\
40 " do not have to be reconstructed again. Can be either a number or"\
41 " a filename containing a pickled number or"\
42 " None in this case the environment variable FEI_STAGE is used."
43FeiConfiguration.monitor.__doc__ = (
44 "Determines the level of monitoring histograms to create. "
45 "Set to False to disable monitoring. "
46 "Set to 'simple' to enable lightweight histograms. "
47 "Any other value will enable full monitoring histograms."
48)
49FeiConfiguration.legacy.__doc__ = "Pass the summary file of a legacy FEI training,"\
50 " and the algorithm will be able to apply this training."
51FeiConfiguration.externTeacher.__doc__ = "Teacher command e.g. basf2_mva_teacher, b2mva-kekcc-cluster-teacher"
52FeiConfiguration.training.__doc__ = "If you train the FEI set this to True, otherwise to False"
53FeiConfiguration.roundMode.__doc__ = "Round mode for the training. 0 default, 1 resuming, 2 finishing, 3 retraining."
54FeiConfiguration.monitoring_path.__doc__ = "Path where monitoring histograms are stored."
55
56
57MVAConfiguration = collections.namedtuple('MVAConfiguration', 'method, config, variables, target, sPlotVariable, spectators')
58MVAConfiguration.__new__.__defaults__ = ('FastBDT',
59 '--nTrees 400 --nCutLevels 10 --nLevels 3 --shrinkage 0.1 --randRatio 0.5',
60 None, 'isSignal', None, {})
61MVAConfiguration.__doc__ = "Multivariate analysis configuration class."
62MVAConfiguration.method.__doc__ = "Method used by MVAInterface."
63MVAConfiguration.config.__doc__ = "Method specific configuration string passed to basf2_mva_teacher"
64MVAConfiguration.variables.__doc__ = "List of variables from the VariableManager."\
65 " {} is expanded to one variable per daughter particle."
66MVAConfiguration.target.__doc__ = "Target variable from the VariableManager."
67MVAConfiguration.sPlotVariable.__doc__ = "Discriminating variable used by sPlot to do data-driven training."
68MVAConfiguration.spectators.__doc__ = "Dictionary of spectator variables with their ranges from the VariableManager."
69
70
71PreCutConfiguration = collections.namedtuple(
72 'PreCutConfiguration', 'userCut, vertexCut, noBackgroundSampling,'
73 'bestCandidateVariable, bestCandidateCut, bestCandidateMode, noSignalSampling, bkgSamplingFactor')
74PreCutConfiguration.__new__.__defaults__ = ('', -2, False, None, 0, 'lowest', False, 1.0)
75PreCutConfiguration.__doc__ = "PreCut configuration class. These cuts is employed before training the mva classifier."
76PreCutConfiguration.userCut.__doc__ = "The user cut is passed directly to the ParticleCombiner."\
77 " Particles which do not pass this cut are immediately discarded."
78PreCutConfiguration.vertexCut.__doc__ = "The vertex cut is passed as confidence level to the VertexFitter."
79PreCutConfiguration.noBackgroundSampling.__doc__ = "For very pure channels, the background sampling factor is too high" \
80 " and the MVA can't be trained. This disables background sampling."
81PreCutConfiguration.bestCandidateVariable.__doc__ = "Variable from the VariableManager which is used to rank all candidates."
82PreCutConfiguration.bestCandidateCut.__doc__ = "Number of best-candidates to keep after the best-candidate ranking."
83PreCutConfiguration.bestCandidateMode.__doc__ = "Either lowest or highest."
84PreCutConfiguration.noSignalSampling.__doc__ = "For channels with unknown br. frac., the signal sampling factor can be" \
85 " overestimated and you loose signal samples in the training." \
86 " This disables signal sampling."
87PreCutConfiguration.bkgSamplingFactor.__doc__ = "Add additional multiplicative bkg. sampling factor, less than 1.0 to reduce."
88
89PostCutConfiguration = collections.namedtuple('PostCutConfiguration', 'value, bestCandidateCut')
90PostCutConfiguration.__new__.__defaults__ = (0.0, 0)
91PostCutConfiguration.__doc__ = "PostCut configuration class. This cut is employed after the training of the mva classifier."
92PostCutConfiguration.value.__doc__ = "Absolute value used to cut on the SignalProbability of each candidate."
93PostCutConfiguration.bestCandidateCut.__doc__ = "Number of best-candidates to keep, ranked by SignalProbability."
94
95DecayChannel = collections.namedtuple(
96 'DecayChannel',
97 'name, label, decayString, daughters, mvaConfig, preCutConfig, decayModeID, pi0veto, extraPathSpec')
98DecayChannel.__new__.__defaults__ = (None, None, None, None, None, None, None, False, None)
99DecayChannel.__doc__ = "Decay channel of a Particle."
100DecayChannel.name.__doc__ = "str:Name of the channel e.g. :code:`D0:generic_0`"
101DecayChannel.label.__doc__ = "Label used to identify the decay channel e.g. for weight files independent of decayModeID"
102DecayChannel.decayString.__doc__ = "DecayDescriptor of the channel e.g. D0 -> K+ pi-"
103DecayChannel.daughters.__doc__ = "List of daughter particles of the decay channel e.g. [K+, pi-]"
104DecayChannel.mvaConfig.__doc__ = "MVAConfiguration object which is used for this channel."
105DecayChannel.preCutConfig.__doc__ = "PreCutConfiguration object which is used for this channel."
106DecayChannel.decayModeID.__doc__ = "DecayModeID of this channel. Unique ID for each channel of this particle."
107DecayChannel.pi0veto.__doc__ = "If true, additional pi0veto variables are added to the MVAs, useful only for decays with gammas."
108DecayChannel.extraPathSpec.__doc__ = "Dictionary with {module, function, args, kwargs} to add extra basf2 modules for this channel."
109
110MonitoringVariableBinning = {'mcErrors': ('mcErrors', 513, -0.5, 512.5),
111 'mcParticleStatus': ('mcParticleStatus', 257, -0.5, 256.5),
112 'dM': ('dM', 100, -1.0, 1.0),
113 'dQ': ('dQ', 100, -1.0, 1.0),
114 'abs(dM)': ('abs(dM)', 100, 0.0, 1.0),
115 'abs(dQ)': ('abs(dQ)', 100, 0.0, 1.0),
116 'pionID': ('pionID', 100, 0.0, 1.0),
117 'kaonID': ('kaonID', 100, 0.0, 1.0),
118 'protonID': ('protonID', 100, 0.0, 1.0),
119 'electronID': ('electronID', 100, 0.0, 1.0),
120 'muonID': ('muonID', 100, 0.0, 1.0),
121 'isSignal': ('isSignal', 2, -0.5, 1.5),
122 'isSignalAcceptMissingNeutrino': ('isSignalAcceptMissingNeutrino', 2, -0.5, 1.5),
123 'isPrimarySignal': ('isPrimarySignal', 2, -0.5, 1.5),
124 'chiProb': ('chiProb', 100, 0.0, 1.0),
125 'Mbc': ('Mbc', 100, 5.1, 5.4),
126 'cosThetaBetweenParticleAndNominalB': ('cosThetaBetweenParticleAndNominalB', 100, -10.0, 10.0),
127 'extraInfo(SignalProbability)': ('extraInfo(SignalProbability)', 100, 0.0, 1.0),
128 'extraInfo(decayModeID)': ('extraInfo(decayModeID)', 101, -0.5, 100.5),
129 'extraInfo(uniqueSignal)': ('extraInfo(uniqueSignal)', 2, -0.5, 1.5),
130 'extraInfo(preCut_rank)': ('extraInfo(preCut_rank)', 41, -0.5, 40.5),
131 'extraInfo(postCut_rank)': ('extraInfo(postCut_rank)', 41, -0.5, 40.5),
132 'daughterProductOf(extraInfo(SignalProbability))':
133 ('daughterProductOf(extraInfo(SignalProbability))', 100, 0.0, 1.0),
134 'pValueCombinationOfDaughters(extraInfo(SignalProbability))':
135 ('pValueCombinationOfDaughters(extraInfo(SignalProbability))', 100, 0.0, 1.0),
136 }
137
138
139def variables2binnings(variables):
140 """
141 Convert given variables into a tuples which can be given to VariableToHistogram
142 """
143 return [MonitoringVariableBinning[v] if v in MonitoringVariableBinning else (v, 100, -10.0, 10.0) for v in variables]
144
145
146def variables2binnings_2d(variables):
147 """
148 Convert given variables into a tuples which can be given to VariableToHistogram
149 """
150 result = []
151 for v1, v2 in variables:
152 b1 = MonitoringVariableBinning[v1] if v1 in MonitoringVariableBinning else (v1, 100, -10.0, 10.0)
153 b2 = MonitoringVariableBinning[v2] if v2 in MonitoringVariableBinning else (v2, 100, -10.0, 10.0)
154 result.append(b1 + b2)
155 return result
156
157
158def removeJPsiSlash(string: str) -> str:
159 """
160 Remove the / in the J/psi particle name
161 """
162 return string.replace('/', '')
163
164
166
167 """
168 The Particle class is the only class the end-user gets into contact with.
169 The user creates an instance of this class for every particle he wants to reconstruct with the FEI algorithm,
170 and provides MVAConfiguration, PreCutConfiguration and PostCutConfiguration. These can be overwritten per channel.
171 """
172
173 def __init__(self, identifier: str,
174 mvaConfig: MVAConfiguration,
175 preCutConfig: PreCutConfiguration = PreCutConfiguration(),
176 postCutConfig: PostCutConfiguration = PostCutConfiguration(),
177 extraPathSpec: dict | None = None):
178 """
179 Creates a Particle without any decay channels. To add decay channels use addChannel method.
180 @param identifier is the pdg name of the particle as a string
181 with an optional additional user label separated by ':'
182 @param mvaConfig multivariate analysis configuration
183 @param preCutConfig intermediate pre cut configuration
184 @param postCutConfig post cut configuration
185 @param extraPathSpec extra module specifications
186 """
187
188 self.identifier = identifier + ':generic' if len(identifier.split(':')) < 2 else identifier
189 v = self.identifier.split(':')
190
191 self.name = v[0]
192
193 self.label = v[1]
194
195 self.mvaConfig = mvaConfig
196
197 self.channels = []
198
199 self.preCutConfig = preCutConfig
200
201 self.postCutConfig = postCutConfig
202
203 self.extraPathSpec = extraPathSpec
204
205 def __eq__(self, a):
206 """
207 Compares to Particle objects.
208 They are equal if their identifier, name, label, all channels, preCutConfig and postCutConfig is equal
209 @param a another Particle object
210 """
211 return (self.identifier == a.identifier and self.name == a.name and self.label == a.label and
212 self.channels == a.channels and self.preCutConfig == a.preCutConfig and self.postCutConfig == a.postCutConfig)
213
214 def __str__(self):
215 """
216 Creates a string representation of a Particle object.
217 """
218 return str((self.identifier, self.channels, self.preCutConfig, self.postCutConfig, self.mvaConfig))
219
220 def __hash__(self):
221 """
222 Creates a hash of a Particle object.
223 This is necessary to use this as a key in a dictionary
224 """
225 return hash((self.identifier, self.channels, self.preCutConfig, self.postCutConfig, self.mvaConfig))
226
227 @property
228 def daughters(self):
229 """ Property returning list of unique daughter particles of all channels """
230 return list(frozenset([daughter for channel in self.channels for daughter in channel.daughters]))
231
232 def addChannel(self,
233 daughters: typing.Sequence[str],
234 mvaConfig: MVAConfiguration = None,
235 preCutConfig: PreCutConfiguration = None,
236 pi0veto: bool = False,
237 extraPathSpec: dict | None = None):
238 """
239 Appends a new decay channel to the Particle object.
240 @param daughters is a list of pdg particle names e.g. ['pi+','K-']
241 @param mvaConfig multivariate analysis configuration
242 @param preCutConfig pre cut configuration object
243 @param pi0veto if true, additional pi0veto variables are added to the MVA configuration
244 @param extraPathSpec extra module specifications
245 """
246 # Append generic label to all defined daughters if no label was set yet
247 daughters = [d + ':generic' if ':' not in d else d for d in daughters]
248 # Use default mvaConfig of this particle if no channel-specific config is given
249 mvaConfig = copy.deepcopy(self.mvaConfig if mvaConfig is None else mvaConfig)
250 # Use default preCutConfig of this particle if no channel-specific config is given
251 preCutConfig = copy.deepcopy(self.preCutConfig if preCutConfig is None else preCutConfig)
252 # Use default extraPathSpec of this particle if no channel-specific extraPathSpec is given
253 extraPathSpec = copy.deepcopy(self.extraPathSpec if extraPathSpec is None else extraPathSpec)
254
255 # At the moment all channels must have the same target variable. Why?
256 if mvaConfig is not None and mvaConfig.target != self.mvaConfig.target:
257 basf2.B2FATAL(
258 f'Particle {self.identifier} has common target {self.mvaConfig.target}, while channel '
259 f'{" ".join(daughters)} has {mvaConfig.target}. Each particle must have exactly one target!')
260 # Replace generic-variables with ordinary variables.
261 # All instances of {} are replaced with all combinations of daughter indices
262 mvaVars = []
263 for v in mvaConfig.variables:
264 if v.count('{') == 0:
265 mvaVars.append(v)
266 continue
267 matches = re.findall(r'\{\s*\d*\s*\.\.\s*\d*\s*\}', v)
268 if len(matches) == 0 and v.count('{}') == 0:
269 mvaVars.append(v)
270 elif v.count('{}') > 0 and len(matches) > 0:
271 basf2.B2FATAL(f'Variable {v} contains both '+'{}'+f' and {matches}. Only one is allowed!')
272 elif len(matches) > 0:
273 ranges = []
274 skip = False
275 for match in matches:
276 tempRange = match[1:-1].split('..')
277 if tempRange[0] == '':
278 tempRange[0] = 0
279 else:
280 tempRange[0] = int(tempRange[0])
281 if tempRange[0] >= len(daughters):
282 basf2.B2DEBUG(11, f'Variable {v} contains index {tempRange[0]} which is more than daughters, skipping!')
283 skip = True
284 break
285 if tempRange[1] == '':
286 tempRange[1] = len(daughters)
287 else:
288 tempRange[1] = int(tempRange[1])
289 if tempRange[1] > len(daughters):
290 basf2.B2DEBUG(11, f'Variable {v} contains index {tempRange[1]} which is more than daughters, skipping!')
291 skip = True
292 break
293 ranges.append(tempRange)
294 if skip:
295 continue
296 if len(ranges) == 1:
297 mvaVars += [v.replace(matches[0], str(c)) for c in range(ranges[0][0], ranges[0][1])]
298 else:
299 for match in matches:
300 v = v.replace(match, '{}')
301 mvaVars += [v.format(*c) for c in itertools.product(*[range(r[0], r[1]) for r in ranges])]
302 elif v.count('{}') <= len(daughters):
303 mvaVars += [v.format(*c) for c in itertools.combinations(list(range(0, len(daughters))), v.count('{}'))]
304 elif v.count('{}') > len(daughters):
305 basf2.B2DEBUG(11, f'Variable {v} contains more brackets than daughters, which is why it will be ignored!')
306 continue
307 else:
308 basf2.B2FATAL(f'Something went wrong with variable {v}!')
309 mvaConfig = mvaConfig._replace(variables=mvaVars)
310 # Add new channel
311 decayModeID = len(self.channels)
312 self.channels.append(DecayChannel(name=self.identifier + '_' + str(decayModeID),
313 label=removeJPsiSlash(self.identifier + ' ==> ' + ' '.join(daughters)),
314 decayString=self.identifier + '_' + str(decayModeID) + ' -> ' + ' '.join(daughters),
315 daughters=daughters,
316 mvaConfig=mvaConfig,
317 preCutConfig=preCutConfig,
318 decayModeID=decayModeID,
319 pi0veto=pi0veto,
320 extraPathSpec=extraPathSpec))
321 return self
mvaConfig
multivariate analysis configuration (see MVAConfiguration)
Definition config.py:195
label
Additional label like hasMissing or has2Daughters.
Definition config.py:193
postCutConfig
post cut configuration (see PostCutConfiguration)
Definition config.py:201
preCutConfig
intermediate cut configuration (see PreCutConfiguration)
Definition config.py:199
__init__(self, str identifier, MVAConfiguration mvaConfig, PreCutConfiguration preCutConfig=PreCutConfiguration(), PostCutConfiguration postCutConfig=PostCutConfiguration(), dict|None extraPathSpec=None)
Definition config.py:177
extraPathSpec
specifications for running extra modules
Definition config.py:203
addChannel(self, typing.Sequence[str] daughters, MVAConfiguration mvaConfig=None, PreCutConfiguration preCutConfig=None, bool pi0veto=False, dict|None extraPathSpec=None)
Definition config.py:237
name
The name of the particle as correct pdg name e.g.
Definition config.py:191
list channels
DecayChannel objects added by addChannel() method.
Definition config.py:197
__eq__(self, a)
Definition config.py:205
str identifier
pdg name of the particle with an optional additional user label separated by :
Definition config.py:188