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
247 daughters = [d + ':generic' if ':' not in d else d for d in daughters]
248
249 mvaConfig = copy.deepcopy(self.mvaConfig if mvaConfig is None else mvaConfig)
250
251 preCutConfig = copy.deepcopy(self.preCutConfig if preCutConfig is None else preCutConfig)
252
253 extraPathSpec = copy.deepcopy(self.extraPathSpec if extraPathSpec is None else extraPathSpec)
254
255
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
261
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
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