Belle II Software development
combined_quality_estimator_teacher.py
1#!/usr/bin/env python3
2
3
10
11"""
12combined_module_quality_estimator_teacher
13-----------------------------------------
14
15Information on the MVA Track Quality Indicator / Estimator can be found
16on `XWiki
17<https://xwiki.desy.de/xwiki/rest/p/0d3f4>`_.
18
19Purpose of this script
20~~~~~~~~~~~~~~~~~~~~~~
21
22This python script is used for the combined training and validation of three
23classifiers, the actual final MVA track quality estimator and the two quality
24estimators for the intermediate standalone track finders that it depends on.
25
26 - Final MVA track quality estimator:
27 The final quality estimator for fully merged and fitted tracks (RecoTracks).
28 Its classifier uses features from the track fitting, merger, hit pattern, ...
29 But it also uses the outputs from respective intermediate quality
30 estimators for the VXD and the CDC track finding as inputs. It provides
31 the final quality indicator (QI) exported to the track objects.
32
33 - VXDTF2 track quality estimator:
34 MVA quality estimator for the VXD standalone track finding.
35
36 - CDC track quality estimator:
37 MVA quality estimator for the CDC standalone track finding.
38
39Each classifier requires for its training a different training data set and they
40need to be validated on a separate testing data set. Further, the final quality
41estimator can only be trained, when the trained weights for the intermediate
42quality estimators are available. If the final estimator shall be trained without
43one or both previous estimators, the requirements have to be commented out in the
44__init__.py file of tracking.
45For all estimators, a list of variables to be ignored is specified in the MasterTask.
46The current choice is mainly based on pure data MC agreement in these quantities or
47on outdated implementations. It was decided to leave them in the hardcoded "ugly" way
48in here to remind future generations that they exist in principle and they should and
49could be added to the estimator, once their modelling becomes better in future or an
50alternative implementation is programmed.
51To avoid mistakes, b2luigi is used to create a task chain for a combined training and
52validation of all classifiers.
53
54b2luigi: Understanding the steering file
55~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56
57All trainings and validations are done in the correct order in this steering
58file. For the purpose of creating a dependency graph, the `b2luigi
59<https://b2luigi.readthedocs.io>`_ python package is used, which extends the
60`luigi <https://luigi.readthedocs.io>`_ package developed by spotify.
61
62Each task that has to be done is represented by a special class, which defines
63which defines parameters, output files and which other tasks with which
64parameters it depends on. For example a teacher task, which runs
65``basf2_mva_teacher.py`` to train the classifier, depends on a data collection
66task which runs a reconstruction and writes out track-wise variables into a root
67file for training. An evaluation/validation task for testing the classifier
68requires both the teacher task, as it needs the weightfile to be present, and
69also a data collection task, because it needs a dataset for testing classifier.
70
71The final task that defines which tasks need to be done for the steering file to
72finish is the ``MasterTask``. When you only want to run parts of the
73training/validation pipeline, you can comment out requirements in the Master
74task or replace them by lower-level tasks during debugging.
75
76Requirements
77~~~~~~~~~~~~
78
79This steering file relies on b2luigi_ for task scheduling and `uncertain_panda
80<https://github.com/nils-braun/uncertain_panda>`_ for uncertainty calculations.
81uncertain_panda is not in the externals and b2luigi is not upto v01-07-01. Both
82can be installed via pip::
83
84 python3 -m pip install [--user] b2luigi uncertain_panda
85
86Use the ``--user`` option if you have not rights to install python packages into
87your externals (e.g. because you are using cvmfs) and install them in
88``$HOME/.local`` instead.
89
90Configuration
91~~~~~~~~~~~~~
92
93Instead of command line arguments, the b2luigi script is configured via a
94``settings.json`` file. Open it in your favorite text editor and modify it to
95fit to your requirements.
96
97Usage
98~~~~~
99
100You can test the b2luigi without running it via::
101
102 python3 combined_quality_estimator_teacher.py --dry-run
103 python3 combined_quality_estimator_teacher.py --show-output
104
105This will show the outputs and show potential errors in the definitions of the
106luigi task dependencies. To run the the steering file in normal (local) mode,
107run::
108
109 python3 combined_quality_estimator_teacher.py
110
111I usually use the interactive luigi web interface via the central scheduler
112which visualizes the task graph while it is running. Therefore, the scheduler
113daemon ``luigid`` has to run in the background, which is located in
114``~/.local/bin/luigid`` in case b2luigi had been installed with ``--user``. For
115example, run::
116
117 luigid --port 8886
118
119Then, execute your steering (e.g. in another terminal) with::
120
121 python3 combined_quality_estimator_teacher.py --scheduler-port 8886
122
123To view the web interface, open your webbrowser enter into the url bar::
124
125 localhost:8886
126
127If you don't run the steering file on the same machine on which you run your web
128browser, you have two options:
129
130 1. Run both the steering file and ``luigid`` remotely and use
131 ssh-port-forwarding to your local host. Therefore, run on your local
132 machine::
133
134 ssh -N -f -L 8886:localhost:8886 <remote_user>@<remote_host>
135
136 2. Run the ``luigid`` scheduler locally and use the ``--scheduler-host <your
137 local host>`` argument when calling the steering file
138
139Accessing the results / output files
140~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141
142All output files are stored in a directory structure in the ``result_path``. The
143directory tree encodes the used b2luigi parameters. This ensures reproducibility
144and makes parameter searches easy. Sometimes, it is hard to find the relevant
145output files. You can view the whole directory structure by running ``tree
146<result_path>``. Ise the unix ``find`` command to find the files that interest
147you, e.g.::
148
149 find <result_path> -name "*.zip" # find all validation plot files
150 find <result_path> -name "*.root" # find all ROOT files
151"""
152
153import itertools
154import os
155from pathlib import Path
156import subprocess
157import textwrap
158from datetime import datetime
159from typing import Iterable
160
161import matplotlib.pyplot as plt
162import numpy as np
163import uproot
164from matplotlib.backends.backend_pdf import PdfPages
165
166import basf2
167import basf2_mva
168from packaging import version
169import background
170import simulation
171import tracking
172from tracking.path_utils import add_cdc_track_finding, add_vxd_track_finding_vxdtf2, add_track_fit_and_track_creator
173import tracking.root_utils as root_utils
174from tracking.harvesting_validation.combined_module import CombinedTrackingValidationModule
175from tracking_mva_filter_payloads.write_tracking_mva_filter_payloads_to_db import write_mva_weightfile_content_to_db
176from tracking_mva_filter_payloads.write_tracking_mva_filter_payloads_to_db import write_tracking_mva_filter_payloads_to_db
177# from basf2 import conditions
178
179# wrap python modules that are used here but not in the externals into a try except block
180install_helpstring_formatter = ("\nCould not find {module} python module.Try installing it via\n"
181 " python3 -m pip install [--user] {module}\n")
182try:
183 import b2luigi
184 from b2luigi.core.utils import get_serialized_parameters
185 from b2luigi.basf2_helper import Basf2PathTask, Basf2Task
186 from b2luigi.core.task import Task, ExternalTask
187 from b2luigi.basf2_helper.utils import get_basf2_git_hash
188except ModuleNotFoundError:
189 print(install_helpstring_formatter.format(module="b2luigi"))
190 raise
191try:
192 from uncertain_panda import pandas as upd
193except ModuleNotFoundError:
194 print(install_helpstring_formatter.format(module="uncertain_panda"))
195 raise
196
197# If b2luigi version 0.3.2 or older, it relies on $BELLE2_RELEASE being "head",
198# which is not the case in the new externals. A fix has been merged into b2luigi
199# via https://github.com/nils-braun/b2luigi/pull/17 and thus should be available
200# in future releases.
201if (
202 version.parse(b2luigi.__version__) <= version.parse("0.3.2") and
203 get_basf2_git_hash() is None and
204 os.getenv("BELLE2_LOCAL_DIR") is not None
205):
206 print(f"b2luigi version could not obtain git hash because of a bug not yet fixed in version {b2luigi.__version__}\n"
207 "Please install the latest version of b2luigi from github via\n\n"
208 " python3 -m pip install --upgrade [--user] git+https://github.com/nils-braun/b2luigi.git\n")
209 raise ImportError
210
211# Utility functions
212
213
214def create_fbdt_option_string(fast_bdt_option):
215 """
216 returns a readable string created by the fast_bdt_option array
217 """
218 return "_nTrees" + str(fast_bdt_option[0]) + "_nCuts" + str(fast_bdt_option[1]) + "_nLevels" + \
219 str(fast_bdt_option[2]) + "_shrin" + str(int(round(100*fast_bdt_option[3], 0)))
220
221
222def createV0momenta(x, mu, beta):
223 """
224 Copied from Biancas K_S0 particle gun code: Returns a realistic V0 momentum distribution
225 when running over x. Mu and Beta are properties of the function that define center and tails.
226 Used for the particle gun simulation code for K_S0 and Lambda_0
227 """
228 return (1/beta)*np.exp(-(x - mu)/beta) * np.exp(-np.exp(-(x - mu) / beta))
229
230
231def my_basf2_mva_teacher(
232 records_files,
233 tree_name,
234 weightfile_identifier,
235 target_variable="truth",
236 exclude_variables=None,
237 fast_bdt_option=[200, 8, 3, 0.1] # nTrees, nCuts, nLevels, shrinkage
238):
239 """
240 My custom wrapper for basf2 mva teacher. Adapted from code in ``trackfindingcdc_teacher``.
241
242 :param records_files: List of files with collected ("recorded") variables to use as training data for the MVA.
243 :param tree_name: Name of the TTree in the ROOT file from the ``data_collection_task``
244 that contains the training data for the MVA teacher.
245 :param weightfile_identifier: Name of the weightfile that is created.
246 Should either end in ".xml" for local weightfiles or in ".root", when
247 the weightfile needs later to be uploaded as a payload to the conditions
248 database.
249 :param target_variable: Feature/variable to use as truth label in the quality estimator MVA classifier.
250 :param exclude_variables: List of collected variables to not use in the training of the QE MVA classifier.
251 In addition to variables containing the "truth" substring, which are excluded by default.
252 :param fast_bdt_option: specified fast BDT options, default: [200, 8, 3, 0.1] [nTrees, nCuts, nLevels, shrinkage]
253 """
254
255 if exclude_variables is None:
256 exclude_variables = []
257
258 weightfile_extension = Path(weightfile_identifier).suffix
259 if weightfile_extension not in {".xml", ".root"}:
260 raise ValueError(f"Weightfile Identifier should end in .xml or .root, but ends in {weightfile_extension}")
261
262 # extract names of all variables from one record file
263 with root_utils.root_open(records_files[0]) as records_tfile:
264 input_tree = records_tfile.Get(tree_name)
265 feature_names = [leave.GetName() for leave in input_tree.GetListOfLeaves()]
266
267 # get list of variables to use for training without MC truth
268 truth_free_variable_names = [
269 name
270 for name in feature_names
271 if (
272 ("truth" not in name) and
273 (name != target_variable) and
274 (name not in exclude_variables)
275 )
276 ]
277 if "weight" in truth_free_variable_names:
278 truth_free_variable_names.remove("weight")
279 weight_variable = "weight"
280 elif "__weight__" in truth_free_variable_names:
281 truth_free_variable_names.remove("__weight__")
282 weight_variable = "__weight__"
283 else:
284 weight_variable = ""
285
286 # Set options for MVA training
287 general_options = basf2_mva.GeneralOptions()
288 general_options.m_datafiles = basf2_mva.vector(*records_files)
289 general_options.m_treename = tree_name
290 general_options.m_weight_variable = weight_variable
291 general_options.m_identifier = weightfile_identifier
292 general_options.m_variables = basf2_mva.vector(*truth_free_variable_names)
293 general_options.m_target_variable = target_variable
294 fastbdt_options = basf2_mva.FastBDTOptions()
295
296 fastbdt_options.m_nTrees = fast_bdt_option[0]
297 fastbdt_options.m_nCuts = fast_bdt_option[1]
298 fastbdt_options.m_nLevels = fast_bdt_option[2]
299 fastbdt_options.m_shrinkage = fast_bdt_option[3]
300 # Train a MVA method and store the weightfile (MVAFastBDT.root) locally.
301 basf2_mva.teacher(general_options, fastbdt_options)
302
303
304def _my_uncertain_mean(series: upd.Series):
305 """
306 Temporary Workaround bug in ``uncertain_panda`` where a ``ValueError`` is
307 thrown for ``Series.unc.mean`` if the series is empty. Can be replaced by
308 .unc.mean when the issue is fixed.
309 https://github.com/nils-braun/uncertain_panda/issues/2
310 """
311 try:
312 return series.unc.mean()
313 except ValueError:
314 if series.empty:
315 return np.nan
316 else:
317 raise
318
319
320def get_uncertain_means_for_qi_cuts(df: upd.DataFrame, column: str, qi_cuts: Iterable[float]):
321 """
322 Return a pandas series with an mean of the dataframe column and
323 uncertainty for each quality indicator cut.
324
325 :param df: Pandas dataframe with at least ``quality_indicator``
326 and another numeric ``column``.
327 :param column: Column of which we want to aggregate the means
328 and uncertainties for different QI cuts
329 :param qi_cuts: Iterable of quality indicator minimal thresholds.
330 :returns: Series of of means and uncertainties with ``qi_cuts`` as index
331 """
332
333 uncertain_means = (_my_uncertain_mean(df.query(f"quality_indicator > {qi_cut}")[column])
334 for qi_cut in qi_cuts)
335 uncertain_means_series = upd.Series(data=uncertain_means, index=qi_cuts)
336 return uncertain_means_series
337
338
339def plot_with_errobands(uncertain_series,
340 error_band_alpha=0.3,
341 plot_kwargs={},
342 fill_between_kwargs={},
343 ax=None):
344 """
345 Plot an uncertain series with error bands for y-errors
346 """
347 if ax is None:
348 ax = plt.gca()
349 uncertain_series = uncertain_series.dropna()
350 ax.plot(uncertain_series.index.values, uncertain_series.nominal_value, **plot_kwargs)
351 ax.fill_between(x=uncertain_series.index,
352 y1=uncertain_series.nominal_value - uncertain_series.std_dev,
353 y2=uncertain_series.nominal_value + uncertain_series.std_dev,
354 alpha=error_band_alpha,
355 **fill_between_kwargs)
356
357
358def format_dictionary(adict, width=80, bullet="•"):
359 """
360 Helper function to format dictionary to string as a wrapped key-value bullet
361 list. Useful to print metadata from dictionaries.
362
363 :param adict: Dictionary to format
364 :param width: Characters after which to wrap a key-value line
365 :param bullet: Character to begin a key-value line with, e.g. ``-`` for a
366 yaml-like string
367 """
368 # It might be possible to replace this function yaml.dump, but the current
369 # version in the externals does not allow to disable the sorting of the
370 # dictionary yet and also I am not sure if it is wrappable
371 return "\n".join(textwrap.fill(f"{bullet} {key}: {value}", width=width)
372 for (key, value) in adict.items())
373
374
375def flat(xss):
376 return [x for xs in xss for x in xs]
377
378# Begin definitions of b2luigi task classes
379
380
381class GenerateSimTask(Basf2PathTask):
382 """
383 Generate simulated Monte Carlo with background overlay.
384
385 Make sure to use different ``random_seed`` parameters for the training data
386 format the classifier trainings and for the test data for the respective
387 evaluation/validation tasks.
388 """
389
390
391 n_events = b2luigi.IntParameter()
392
393 experiment_number = b2luigi.IntParameter()
394
396 random_seed = b2luigi.Parameter()
397
398 bkgfiles_dir = b2luigi.Parameter(
399
400 hashed=True
401
402 )
403
404 queue = 'l'
405
406
407 def output_file_name(self, n_events=None, random_seed=None):
408 """
409 Create output file name depending on number of events and production
410 mode that is specified in the random_seed string.
411 """
412 if n_events is None:
413 n_events = self.n_events
414 if random_seed is None:
415 random_seed = self.random_seed
416 return "generated_mc_N" + str(n_events) + "_" + random_seed + ".root"
417
418 def output(self):
419 """
420 Generate list of output files that the task should produce.
421 The task is considered finished if and only if the outputs all exist.
422 """
423 yield self.add_to_output(self.output_file_name())
424
425 def create_path(self):
426 """
427 Create basf2 path to process with event generation and simulation.
428 """
429 basf2.set_random_seed(self.random_seed)
430 path = basf2.create_path()
431 if self.experiment_number in [0, 1002, 1003]:
432 runNo = 0
433 else:
434 runNo = 0
435 raise ValueError(
436 f"Simulating events with experiment number {self.experiment_number} is not implemented yet.")
437 path.add_module(
438 "EventInfoSetter", evtNumList=[self.n_events], runList=[runNo], expList=[self.experiment_number]
439 )
440 if "BBBAR" in self.random_seed:
441 path.add_module("EvtGenInput")
442 elif "V0BBBAR" in self.random_seed:
443 path.add_module("EvtGenInput")
444 path.add_module("InclusiveParticleChecker", particles=[310, 3122], includeConjugates=True)
445 else:
446 import generators as ge
447 # WARNING: There are a few differences in the production of MC13a and b like the following lines
448 # as well as ActivatePXD.. and the beamparams for bhabha... I use these from MC13b, not a... :/
449 # import beamparameters as bp
450 # beamparameters = bp.add_beamparameters(path, "Y4S")
451 # beamparameters.param("covVertex", [(14.8e-4)**2, (1.5e-4)**2, (360e-4)**2])
452 if "V0STUDY" in self.random_seed:
453 if "V0STUDYKS" in self.random_seed:
454 # Bianca looked at the Ks dists and extracted these values:
455 mu = 0.5
456 beta = 0.2
457 pdgs = [310] # Ks (has no antiparticle, Klong is different)
458 if "V0STUDYL0" in self.random_seed:
459 # I just made the lambda values up, such that they peak at 0.35 and are slightly shifted to lower values
460 mu = 0.35
461 beta = 0.15 # if this is chosen higher, one needs to make sure not to get values >0 for 0
462 pdgs = [3122, -3122] # Lambda0
463 else:
464 # also these values are made up
465 mu = 0.43
466 beta = 0.18
467 pdgs = [310, 3122, -3122] # Ks and Lambda0
468 # create realistic momentum distribution
469 myx = [i*0.01 for i in range(321)]
470 myy = []
471 for x in myx:
472 y = createV0momenta(x, mu, beta)
473 myy.append(y)
474 polParams = myx + myy
475 # define particles that are produced
476 pdg_list = pdgs
477
478 particlegun = basf2.register_module('ParticleGun')
479 particlegun.param('pdgCodes', pdg_list)
480 particlegun.param('nTracks', 8) # number of particles (not tracks!) that is created in each event
481 particlegun.param('momentumGeneration', 'polyline')
482 particlegun.param('momentumParams', polParams)
483 particlegun.param('thetaGeneration', 'uniformCos')
484 particlegun.param('thetaParams', [17, 150]) # [0, 180]) #[17, 150]
485 particlegun.param('phiGeneration', 'uniform')
486 particlegun.param('phiParams', [0, 360])
487 particlegun.param('vertexGeneration', 'fixed')
488 particlegun.param('xVertexParams', [0])
489 particlegun.param('yVertexParams', [0])
490 particlegun.param('zVertexParams', [0])
491 path.add_module(particlegun)
492 if "BHABHA" in self.random_seed:
493 ge.add_babayaganlo_generator(path=path, finalstate='ee', minenergy=0.15, minangle=10.0)
494 elif "MUMU" in self.random_seed:
495 ge.add_kkmc_generator(path=path, finalstate='mu+mu-')
496 elif "YY" in self.random_seed:
497 babayaganlo = basf2.register_module('BabayagaNLOInput')
498 babayaganlo.param('FinalState', 'gg')
499 babayaganlo.param('MaxAcollinearity', 180.0)
500 babayaganlo.param('ScatteringAngleRange', [0., 180.])
501 babayaganlo.param('FMax', 75000)
502 babayaganlo.param('MinEnergy', 0.01)
503 babayaganlo.param('Order', 'exp')
504 babayaganlo.param('DebugEnergySpread', 0.01)
505 babayaganlo.param('Epsilon', 0.00005)
506 path.add_module(babayaganlo)
507 generatorpreselection = basf2.register_module('GeneratorPreselection')
508 generatorpreselection.param('nChargedMin', 0)
509 generatorpreselection.param('nChargedMax', 999)
510 generatorpreselection.param('MinChargedPt', 0.15)
511 generatorpreselection.param('MinChargedTheta', 17.)
512 generatorpreselection.param('MaxChargedTheta', 150.)
513 generatorpreselection.param('nPhotonMin', 1)
514 generatorpreselection.param('MinPhotonEnergy', 1.5)
515 generatorpreselection.param('MinPhotonTheta', 15.0)
516 generatorpreselection.param('MaxPhotonTheta', 165.0)
517 generatorpreselection.param('applyInCMS', True)
518 path.add_module(generatorpreselection)
519 empty = basf2.create_path()
520 generatorpreselection.if_value('!=11', empty)
521 elif "EEEE" in self.random_seed:
522 ge.add_aafh_generator(path=path, finalstate='e+e-e+e-', preselection=False)
523 elif "EEMUMU" in self.random_seed:
524 ge.add_aafh_generator(path=path, finalstate='e+e-mu+mu-', preselection=False)
525 elif "TAUPAIR" in self.random_seed:
526 ge.add_kkmc_generator(path, finalstate='tau+tau-')
527 elif "DDBAR" in self.random_seed:
528 ge.add_continuum_generator(path, finalstate='ddbar')
529 elif "UUBAR" in self.random_seed:
530 ge.add_continuum_generator(path, finalstate='uubar')
531 elif "SSBAR" in self.random_seed:
532 ge.add_continuum_generator(path, finalstate='ssbar')
533 elif "CCBAR" in self.random_seed:
534 ge.add_continuum_generator(path, finalstate='ccbar')
535 # activate simulation of dead/masked pixel and reproduce detector gain, which will be
536 # applied at reconstruction level when the data GT is present in the DB chain
537 # path.add_module("ActivatePXDPixelMasker")
538 # path.add_module("ActivatePXDGainCalibrator")
540 # \cond suppress doxygen warning
541 if self.experiment_number == 1002:
542 # remove KLM because of bug in background files with release 4
543 components = ['PXD', 'SVD', 'CDC', 'ECL', 'TOP', 'ARICH', 'TRG']
544 else:
545 components = None
546 # \endcond
547 simulation.add_simulation(path, bkgfiles=bkg_files, bkgOverlay=True, components=components) # , usePXDDataReduction=False)
548
549 path.add_module(
550 "RootOutput",
551 outputFileName=self.get_output_file_name(self.output_file_name()),
552 )
553 return path
554
555
556# I don't use the default MergeTask or similar because they only work if every input file is called the same.
557# Additionally, I want to add more features like deleting the original input to save storage space.
558class SplitNMergeSimTask(Basf2Task):
559 """
560 Generate simulated Monte Carlo with background overlay.
561
562 Make sure to use different ``random_seed`` parameters for the training data
563 format the classifier trainings and for the test data for the respective
564 evaluation/validation tasks.
565 """
566
567
568 n_events = b2luigi.IntParameter()
569
570 experiment_number = b2luigi.IntParameter()
571
573 random_seed = b2luigi.Parameter()
574
575 bkgfiles_dir = b2luigi.Parameter(
576
577 hashed=True
578
579 )
580
581 queue = 'l'
582
583
584 def output_file_name(self, n_events=None, random_seed=None):
585 """
586 Create output file name depending on number of events and production
587 mode that is specified in the random_seed string.
588 """
589 if n_events is None:
590 n_events = self.n_events
591 if random_seed is None:
592 random_seed = self.random_seed
593 return "generated_mc_N" + str(n_events) + "_" + random_seed + ".root"
594
595 def output(self):
596 """
597 Generate list of output files that the task should produce.
598 The task is considered finished if and only if the outputs all exist.
599 """
600 yield self.add_to_output(self.output_file_name())
601
602 def requires(self):
603 """
604 Generate list of luigi Tasks that this Task depends on.
605 """
606 n_events_per_task = MasterTask.n_events_per_task
607 quotient, remainder = divmod(self.n_events, n_events_per_task)
608 for i in range(quotient):
609 yield GenerateSimTask(
610 bkgfiles_dir=self.bkgfiles_dir,
611 num_processes=MasterTask.num_processes,
612 random_seed=self.random_seed + '_' + str(i).zfill(3),
613 n_events=n_events_per_task,
614 experiment_number=self.experiment_number,
615 )
616 if remainder > 0:
617 yield GenerateSimTask(
618 bkgfiles_dir=self.bkgfiles_dir,
619 num_processes=MasterTask.num_processes,
620 random_seed=self.random_seed + '_' + str(quotient).zfill(3),
621 n_events=remainder,
622 experiment_number=self.experiment_number,
623 )
624
625 @b2luigi.on_temporary_files
626 def process(self):
627 """
628 When all GenerateSimTasks finished, merge the output.
629 """
630 file_list = self.get_all_input_file_names()
631 file_list = flat(file_list)
632 print("Merge the following files:\n")
633 print(file_list)
634 cmd = ["b2file-merge", "-f"]
635 args = cmd + [self.get_output_file_name(self.output_file_name())] + file_list
636 print(f"args to merge: {args}")
637 subprocess.check_call(args)
638 print("Finished merging. Removing the input files to save space.")
639 cmd2 = ["rm", "-f"]
640 args = cmd2 + file_list
641 print(f"args for deleting: {args}")
642 subprocess.check_call(args)
643
644
645class CheckExistingFile(ExternalTask):
646 """
647 Task to check if the given file really exists.
648 """
649
650 filename = b2luigi.Parameter()
651
652 def output(self):
653 """
654 Specify the output to be the file that was just checked.
655 """
656 from luigi import LocalTarget
657 return LocalTarget(self.filename)
658
659
660class VXDQEDataCollectionTask(Basf2PathTask):
661 """
662 Collect variables/features from VXDTF2 tracking and write them to a ROOT
663 file.
664
665 These variables are to be used as labelled training data for the MVA
666 classifier which is the VXD track quality estimator
667 """
668
669 n_events = b2luigi.IntParameter()
670
671 experiment_number = b2luigi.IntParameter()
672
674 random_seed = b2luigi.Parameter()
675
676 queue = 'l'
677
678
679 def get_records_file_name(self, n_events=None, random_seed=None):
680 """
681 Create output file name depending on number of events and production
682 mode that is specified in the random_seed string.
683 """
684 if n_events is None:
685 n_events = self.n_events
686 if random_seed is None:
687 random_seed = self.random_seed
688 if 'vxd' not in random_seed:
689 random_seed += '_vxd'
690 if 'DATA' in random_seed:
691 return 'qe_records_DATA_vxd.root'
692 else:
693 if 'USESIMBB' in random_seed:
694 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
695 elif 'USESIMEE' in random_seed:
696 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
697 return 'qe_records_N' + str(n_events) + '_' + random_seed + '.root'
698
699 def get_input_files(self, n_events=None, random_seed=None):
700 """
701 Get input file names depending on the use case: If they already exist, search in
702 the corresponding folders, for data check the specified list and if they are created
703 in the same run, check for the task that produced them.
704 """
705 if n_events is None:
706 n_events = self.n_events
707 if random_seed is None:
708 random_seed = self.random_seed
709 if "USESIM" in random_seed:
710 if 'USESIMBB' in random_seed:
711 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
712 elif 'USESIMEE' in random_seed:
713 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
714 return ['datafiles/' + GenerateSimTask.output_file_name(GenerateSimTask,
715 n_events=n_events, random_seed=random_seed)]
716 elif "DATA" in random_seed:
717 return MasterTask.datafiles
718 else:
719 return self.get_input_file_names(GenerateSimTask.output_file_name(
720 GenerateSimTask, n_events=n_events, random_seed=random_seed))
721
722 def requires(self):
723 """
724 Generate list of luigi Tasks that this Task depends on.
725 """
726 if "USESIM" in self.random_seed or "DATA" in self.random_seed:
727 for filename in self.get_input_files():
728 yield CheckExistingFile(
729 filename=filename,
730 )
731 else:
732 yield SplitNMergeSimTask(
733 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
734 random_seed=self.random_seed,
735 n_events=self.n_events,
736 experiment_number=self.experiment_number,
737 )
738
739 def output(self):
740 """
741 Generate list of output files that the task should produce.
742 The task is considered finished if and only if the outputs all exist.
743 """
744 yield self.add_to_output(self.get_records_file_name())
745
746 def create_path(self):
747 """
748 Create basf2 path with VXDTF2 tracking and VXD QE data collection.
749 """
750 path = basf2.create_path()
751 inputFileNames = self.get_input_files()
752 path.add_module(
753 "RootInput",
754 inputFileNames=inputFileNames,
755 )
756 path.add_module("Gearbox")
757 tracking.add_geometry_modules(path)
758 if 'DATA' in self.random_seed:
759 from rawdata import add_unpackers
760 add_unpackers(path, components=['SVD', 'PXD'])
761 tracking.add_hit_preparation_modules(path)
762 add_vxd_track_finding_vxdtf2(
763 path, components=["SVD"], add_mva_quality_indicator=False
764 )
765 if 'DATA' in self.random_seed:
766 path.add_module(
767 "VXDQETrainingDataCollector",
768 TrainingDataOutputName=self.get_output_file_name(self.get_records_file_name()),
769 SpacePointTrackCandsStoreArrayName="SPTrackCands",
770 EstimationMethod="tripletFit",
771 UseTimingInfo=False,
772 ClusterInformation="Average",
773 MCStrictQualityEstimator=False,
774 mva_target=False,
775 MCInfo=False,
776 )
777 else:
778 path.add_module(
779 "TrackFinderMCTruthRecoTracks",
780 RecoTracksStoreArrayName="MCRecoTracks",
781 WhichParticles=[],
782 UsePXDHits=False,
783 UseSVDHits=True,
784 UseCDCHits=False,
785 )
786 path.add_module(
787 "VXDQETrainingDataCollector",
788 TrainingDataOutputName=self.get_output_file_name(self.get_records_file_name()),
789 SpacePointTrackCandsStoreArrayName="SPTrackCands",
790 EstimationMethod="tripletFit",
791 UseTimingInfo=False,
792 ClusterInformation="Average",
793 MCStrictQualityEstimator=True,
794 mva_target=False,
795 )
796 return path
797
798
799class CDCQEDataCollectionTask(Basf2PathTask):
800 """
801 Collect variables/features from CDC tracking and write them to a ROOT file.
802
803 These variables are to be used as labelled training data for the MVA
804 classifier which is the CDC track quality estimator
805 """
806
807 n_events = b2luigi.IntParameter()
808
809 experiment_number = b2luigi.IntParameter()
810
812 random_seed = b2luigi.Parameter()
813
814 queue = 'l'
815
816
817 def get_records_file_name(self, n_events=None, random_seed=None):
818 """
819 Create output file name depending on number of events and production
820 mode that is specified in the random_seed string.
821 """
822 if n_events is None:
823 n_events = self.n_events
824 if random_seed is None:
825 random_seed = self.random_seed
826 if 'cdc' not in random_seed:
827 random_seed += '_cdc'
828 if 'DATA' in random_seed:
829 return 'qe_records_DATA_cdc.root'
830 else:
831 if 'USESIMBB' in random_seed:
832 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
833 elif 'USESIMEE' in random_seed:
834 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
835 return 'qe_records_N' + str(n_events) + '_' + random_seed + '.root'
836
837 def get_input_files(self, n_events=None, random_seed=None):
838 """
839 Get input file names depending on the use case: If they already exist, search in
840 the corresponding folders, for data check the specified list and if they are created
841 in the same run, check for the task that produced them.
842 """
843 if n_events is None:
844 n_events = self.n_events
845 if random_seed is None:
846 random_seed = self.random_seed
847 if "USESIM" in random_seed:
848 if 'USESIMBB' in random_seed:
849 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
850 elif 'USESIMEE' in random_seed:
851 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
852 return ['datafiles/' + GenerateSimTask.output_file_name(GenerateSimTask,
853 n_events=n_events, random_seed=random_seed)]
854 elif "DATA" in random_seed:
855 return MasterTask.datafiles
856 else:
857 return self.get_input_file_names(GenerateSimTask.output_file_name(
858 GenerateSimTask, n_events=n_events, random_seed=random_seed))
859
860 def requires(self):
861 """
862 Generate list of luigi Tasks that this Task depends on.
863 """
864 if "USESIM" in self.random_seed or "DATA" in self.random_seed:
865 for filename in self.get_input_files():
866 yield CheckExistingFile(
867 filename=filename,
868 )
869 else:
870 yield SplitNMergeSimTask(
871 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
872 random_seed=self.random_seed,
873 n_events=self.n_events,
874 experiment_number=self.experiment_number,
875 )
876
877 def output(self):
878 """
879 Generate list of output files that the task should produce.
880 The task is considered finished if and only if the outputs all exist.
881 """
882 yield self.add_to_output(self.get_records_file_name())
883
884 def create_path(self):
885 """
886 Create basf2 path with CDC standalone tracking and CDC QE with recording filter for MVA feature collection.
887 """
888 path = basf2.create_path()
889 inputFileNames = self.get_input_files()
890 path.add_module(
891 "RootInput",
892 inputFileNames=inputFileNames,
893 )
894 path.add_module("Gearbox")
895 tracking.add_geometry_modules(path)
896 if 'DATA' in self.random_seed:
897 filter_choice = "recording_data"
898 from rawdata import add_unpackers
899 add_unpackers(path, components=['CDC'])
900 else:
901 filter_choice = "recording"
902 # tracking.add_hit_preparation_modules(path) # only needed for SVD and
903 # PXD hit preparation. Does not change the CDC output.
904 add_cdc_track_finding(path, add_mva_quality_indicator=True)
905 basf2.set_module_parameters(
906 path,
907 name="TFCDC_TrackQualityEstimator",
908 filter=filter_choice,
909 filterParameters={
910 "rootFileName": self.get_output_file_name(self.get_records_file_name())
911 },
912 deactivateIfDeadBoard=False # original behavior before deactivateIfDeadBoard was introduced
913 )
914 return path
915
916
917class RecoTrackQEDataCollectionTask(Basf2PathTask):
918 """
919 Collect variables/features from the reco track reconstruction including the
920 fit and write them to a ROOT file.
921
922 These variables are to be used as labelled training data for the MVA
923 classifier which is the MVA track quality estimator. The collected
924 variables include the classifier outputs from the VXD and CDC quality
925 estimators, namely the CDC and VXD quality indicators, combined with fit,
926 merger, timing, energy loss information etc. This task requires the
927 subdetector quality estimators to be trained.
928 """
929
930
931 n_events = b2luigi.IntParameter()
932
933 experiment_number = b2luigi.IntParameter()
934
936 random_seed = b2luigi.Parameter()
937
938 cdc_training_target = b2luigi.Parameter()
939
942 recotrack_option = b2luigi.Parameter(
943
944 default='deleteCDCQI080'
945
946 )
947
948 fast_bdt_option = b2luigi.ListParameter(
949
950 hashed=True, default=[200, 8, 3, 0.1]
951
952 )
953
954 process_type = b2luigi.Parameter(
955
956 default="BBBAR"
957
958 )
959
960 queue = 'l'
961
962
963 def get_records_file_name(self, n_events=None, random_seed=None, recotrack_option=None):
964 """
965 Create output file name depending on number of events and production
966 mode that is specified in the random_seed string.
967 """
968 if n_events is None:
969 n_events = self.n_events
970 if random_seed is None:
971 random_seed = self.random_seed
972 if recotrack_option is None:
973 if isinstance(self.recotrack_option, str):
974 recotrack_option = self.recotrack_option
975 else:
976 recotrack_option = self.recotrack_option._default
977 if 'rec' not in random_seed:
978 random_seed += '_rec'
979 if 'DATA' in random_seed:
980 return 'qe_records_DATA_rec.root'
981 else:
982 if 'USESIMBB' in random_seed:
983 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
984 elif 'USESIMEE' in random_seed:
985 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
986 return 'qe_records_N' + str(n_events) + '_' + random_seed + '_' + recotrack_option + '.root'
987
988 def get_input_files(self, n_events=None, random_seed=None):
989 """
990 Get input file names depending on the use case: If they already exist, search in
991 the corresponding folders, for data check the specified list and if they are created
992 in the same run, check for the task that produced them.
993 """
994 if n_events is None:
995 n_events = self.n_events
996 if random_seed is None:
997 random_seed = self.random_seed
998 if "USESIM" in random_seed:
999 if 'USESIMBB' in random_seed:
1000 random_seed = 'BBBAR_' + random_seed.split("_", 1)[1]
1001 elif 'USESIMEE' in random_seed:
1002 random_seed = 'BHABHA_' + random_seed.split("_", 1)[1]
1003 return ['datafiles/' + GenerateSimTask.output_file_name(GenerateSimTask,
1004 n_events=n_events, random_seed=random_seed)]
1005 elif "DATA" in random_seed:
1006 return MasterTask.datafiles
1007 else:
1008 return self.get_input_file_names(GenerateSimTask.output_file_name(
1009 GenerateSimTask, n_events=n_events, random_seed=random_seed))
1010
1011 def requires(self):
1012 """
1013 Generate list of luigi Tasks that this Task depends on.
1014 """
1015 if "USESIM" in self.random_seed or "DATA" in self.random_seed:
1016 for filename in self.get_input_files():
1017 yield CheckExistingFile(
1018 filename=filename,
1019 )
1020 else:
1021 yield SplitNMergeSimTask(
1022 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
1023 random_seed=self.random_seed,
1024 n_events=self.n_events,
1025 experiment_number=self.experiment_number,
1026 )
1027 if "DATA" not in self.random_seed:
1028 if 'useCDC' not in self.recotrack_option and 'noCDC' not in self.recotrack_option:
1029 yield CDCQETeacherTask(
1030 n_events_training=MasterTask.n_events_training,
1031 experiment_number=self.experiment_number,
1032 training_target=self.cdc_training_target,
1033 process_type=self.random_seed.split("_", 1)[0],
1034 exclude_variables=MasterTask.exclude_variables_cdc,
1035 fast_bdt_option=self.fast_bdt_option,
1036 )
1037 if 'useVXD' not in self.recotrack_option and 'noVXD' not in self.recotrack_option:
1038 yield VXDQETeacherTask(
1039 n_events_training=MasterTask.n_events_training,
1040 experiment_number=self.experiment_number,
1041 process_type=self.random_seed.split("_", 1)[0],
1042 exclude_variables=MasterTask.exclude_variables_vxd,
1043 fast_bdt_option=self.fast_bdt_option,
1044 )
1045
1046 def output(self):
1047 """
1048 Generate list of output files that the task should produce.
1049 The task is considered finished if and only if the outputs all exist.
1050 """
1051 yield self.add_to_output(self.get_records_file_name())
1052
1053 def create_path(self):
1054 """
1055 Create basf2 reconstruction path that should mirror the default path
1056 from ``add_tracking_reconstruction()``, but with modules for the VXD QE
1057 and CDC QE application and for collection of variables for the reco
1058 track quality estimator.
1059 """
1060 path = basf2.create_path()
1061 inputFileNames = self.get_input_files()
1062 path.add_module(
1063 "RootInput",
1064 inputFileNames=inputFileNames,
1065 )
1066 path.add_module("Gearbox")
1067
1068 # First add tracking reconstruction with default quality estimation modules
1069 mvaCDC = True
1070 mvaVXD = True
1071 if 'noCDC' in self.recotrack_option:
1072 mvaCDC = False
1073 if 'noVXD' in self.recotrack_option:
1074 mvaVXD = False
1075 if 'DATA' in self.random_seed:
1076 from rawdata import add_unpackers
1077 add_unpackers(path)
1078 tracking.add_tracking_reconstruction(path, add_cdcTrack_QI=mvaCDC, add_vxdTrack_QI=mvaVXD, add_recoTrack_QI=True)
1079
1080 cdc_identifier = ""
1081 # if data shall be processed check if newly trained mva files are available. Otherwise use default ones (CDB payloads):
1082 # if useCDC/VXD is specified, use the identifier lying in datafiles/ Otherwise, replace weightfile identifiers from
1083 # defaults (CDB payloads) to new weightfiles created by this b2luigi script
1084 if ('DATA' in self.random_seed or 'useCDC' in self.recotrack_option) and 'noCDC' not in self.recotrack_option:
1085 cdc_identifier = 'datafiles/' + \
1086 CDCQETeacherTask.get_weightfile_identifier(CDCQETeacherTask, fast_bdt_option=self.fast_bdt_option) + '.xml'
1087 if os.path.exists(cdc_identifier):
1088 replace_cdc_qi = True
1089 elif 'useCDC' in self.recotrack_option:
1090 raise ValueError(f"CDC QI Identifier not found: {cdc_identifier}")
1091 else:
1092 replace_cdc_qi = False
1093 elif 'noCDC' in self.recotrack_option:
1094 replace_cdc_qi = False
1095 else:
1096 cdc_identifier = self.get_input_file_names(
1097 CDCQETeacherTask.get_weightfile_identifier(
1098 CDCQETeacherTask, fast_bdt_option=self.fast_bdt_option) + '.xml')[0]
1099 replace_cdc_qi = True
1100 if ('DATA' in self.random_seed or 'useVXD' in self.recotrack_option) and 'noVXD' not in self.recotrack_option:
1101 vxd_identifier = 'datafiles/' + \
1102 VXDQETeacherTask.get_weightfile_identifier(VXDQETeacherTask, fast_bdt_option=self.fast_bdt_option) + '.xml'
1103 if os.path.exists(vxd_identifier):
1104 replace_vxd_qi = True
1105 print(f"vxd_identifier is {vxd_identifier}")
1106 elif 'useVXD' in self.recotrack_option:
1107 raise ValueError(f"VXD QI Identifier not found: {vxd_identifier}")
1108 else:
1109 replace_vxd_qi = False
1110 elif 'noVXD' in self.recotrack_option:
1111 replace_vxd_qi = False
1112 else:
1113 vxd_identifier = self.get_input_file_names(
1114 VXDQETeacherTask.get_weightfile_identifier(
1115 VXDQETeacherTask, fast_bdt_option=self.fast_bdt_option) + '.xml')[0]
1116 replace_vxd_qi = True
1117
1118 cdc_qe_mva_filter_parameters = None
1119 # if tracks below a certain CDC QI index shall be deleted online, this needs to be specified in the filter parameters.
1120 # this is also possible in case of the default (CBD) payloads.
1121 cut = 0
1122 if 'deleteCDCQI' in self.recotrack_option:
1123 cut_index = self.recotrack_option.find('deleteCDCQI') + len('deleteCDCQI')
1124 cut = int(self.recotrack_option[cut_index:cut_index+3])/100.
1125 if replace_cdc_qi:
1126 cdc_qe_mva_filter_parameters = {
1127 "identifier": cdc_identifier, "cut": cut}
1128 else:
1129 cdc_qe_mva_filter_parameters = {
1130 "cut": cut}
1131 elif replace_cdc_qi:
1132 cdc_qe_mva_filter_parameters = {
1133 "identifier": cdc_identifier}
1134 basf2.conditions.prepend_testing_payloads("localdb/database.txt")
1135
1136 if cdc_qe_mva_filter_parameters is not None and cdc_identifier is not None:
1137 name = 'TrackingMVAFilterParameters'
1138 cdc_qe_mva_filter_parameters = {'DBPayloadName': name}
1139 if replace_vxd_qi and vxd_identifier is not None:
1140 vxd_name = 'VXDQualityEstimatorMVA'
1141 # Added for debugging
1142 if cdc_qe_mva_filter_parameters is not None:
1143 # if no cut is specified, the default value is at zero and nothing is deleted.
1144 print(f'cdc_qe_mva_filter_parameters is {cdc_qe_mva_filter_parameters}')
1145 basf2.set_module_parameters(
1146 path,
1147 name="TFCDC_TrackQualityEstimator",
1148 filterParameters=cdc_qe_mva_filter_parameters,
1149 deleteTracks=True,
1150 resetTakenFlag=True,
1151 deactivateIfDeadBoard=False, # original behavior before deactivateIfDeadBoard was introduced
1152 )
1153 if replace_vxd_qi:
1154 print(f"replace_vxd_qi is true and vxd_identifier is {vxd_identifier}")
1155 basf2.set_module_parameters(
1156 path,
1157 name="VXDQualityEstimatorMVA",
1158 WeightFileIdentifier=vxd_name)
1159
1160 # Replace final quality estimator module by training data collector module
1161 track_qe_module_name = "TrackQualityEstimatorMVA"
1162 mc_track_matcher_module_name = "MCRecoTracksMatcher"
1163 qe_module_found = False
1164 mc_matcher_module_found = False
1165 new_path = basf2.create_path()
1166 for module in path.modules():
1167 if module.name() == track_qe_module_name:
1168 # the TrackCreator needs to be conducted before the Collector such that
1169 # MDSTTracks are related to RecoTracks and d0 and z0 can be read out
1170 new_path.add_module(
1171 'TrackCreator',
1172 pdgCodes=[
1173 211,
1174 321,
1175 2212],
1176 recoTrackColName='RecoTracks',
1177 trackColName='MDSTTracks') # , useClosestHitToIP=True, useBFieldAtHit=True)
1178 qe_module_found = True
1179 elif module.name() == mc_track_matcher_module_name:
1180 new_path.add_module(module)
1181 # move TrackQETrainingDataCollector module after the MCRecoTracksMatcher module
1182 new_path.add_module(
1183 "TrackQETrainingDataCollector",
1184 TrainingDataOutputName=self.get_output_file_name(self.get_records_file_name()),
1185 collectEventFeatures=True
1186 )
1187 mc_matcher_module_found = True
1188 else:
1189 new_path.add_module(module)
1190 if not qe_module_found:
1191 raise KeyError(f"No module {track_qe_module_name} found in path")
1192 if not mc_matcher_module_found:
1193 raise KeyError(f"No module {mc_matcher_module_found} found in path")
1194 path = new_path
1195 return path
1196
1197
1198class TrackQETeacherBaseTask(Basf2Task):
1199 """
1200 A teacher task runs the basf2 mva teacher on the training data provided by a
1201 data collection task.
1202
1203 Since teacher tasks are needed for all quality estimators covered by this
1204 steering file and the only thing that changes is the required data
1205 collection task and some training parameters, I decided to use inheritance
1206 and have the basic functionality in this base class/interface and have the
1207 specific teacher tasks inherit from it.
1208 """
1209
1210 n_events_training = b2luigi.IntParameter()
1211
1212 experiment_number = b2luigi.IntParameter()
1213
1216 process_type = b2luigi.Parameter(
1217
1218 default="BBBAR"
1219
1220 )
1221
1222 training_target = b2luigi.Parameter(
1223
1224 default="truth"
1225
1226 )
1227
1229 exclude_variables = b2luigi.ListParameter(
1230
1231 hashed=True, default=[]
1232
1233 )
1234
1235 fast_bdt_option = b2luigi.ListParameter(
1236
1237 hashed=True, default=[200, 8, 3, 0.1]
1238
1239 )
1240
1241 @property
1243 """
1244 Property defining the basename for the .xml and .root weightfiles that are created.
1245 Has to be implemented by the inheriting teacher task class.
1246 """
1247 raise NotImplementedError(
1248 "Teacher Task must define a static weightfile_identifier"
1249 )
1250
1251 def get_weightfile_identifier(self, fast_bdt_option=None, recotrack_option=None):
1252 """
1253 Name of the xml weightfile that is created by the teacher task.
1254 It is subsequently used as a local weightfile in the following validation tasks.
1255 """
1256 if fast_bdt_option is None:
1257 fast_bdt_option = self.fast_bdt_option
1258 if recotrack_option is None and hasattr(self, 'recotrack_option'):
1259
1262 if isinstance(self.recotrack_option, str):
1263 recotrack_option = self.recotrack_option
1264 else:
1265 recotrack_option = self.recotrack_option._default
1266 else:
1267 recotrack_option = ''
1268 weightfile_details = create_fbdt_option_string(fast_bdt_option)
1269 weightfile_name = self.weightfile_identifier_basename + weightfile_details
1270 if recotrack_option != '':
1271 weightfile_name = weightfile_name + '_' + recotrack_option
1272 return weightfile_name + "_weights"
1273
1274 @property
1275 def tree_name(self):
1276 """
1277 Property defining the name of the tree in the ROOT file from the
1278 ``data_collection_task`` that contains the recorded training data. Must
1279 implemented by the inheriting specific teacher task class.
1280 """
1281 raise NotImplementedError("Teacher Task must define a static tree_name")
1282
1283 @property
1284 def random_seed(self):
1285 """
1286 Property defining random seed to be used by the ``GenerateSimTask``.
1287 Should differ from the random seed in the test data samples. Must
1288 implemented by the inheriting specific teacher task class.
1289 """
1290 raise NotImplementedError("Teacher Task must define a static random seed")
1291
1292 @property
1293 def data_collection_task(self) -> Basf2PathTask:
1294 """
1295 Property defining the specific ``DataCollectionTask`` to require. Must
1296 implemented by the inheriting specific teacher task class.
1297 """
1298 raise NotImplementedError(
1299 "Teacher Task must define a data collection task to require "
1300 )
1301
1302 def requires(self):
1303 """
1304 Generate list of luigi Tasks that this Task depends on.
1305 """
1306 if 'USEREC' in self.process_type:
1307 if 'USERECBB' in self.process_type:
1308 process = 'BBBAR'
1309 elif 'USERECEE' in self.process_type:
1310 process = 'BHABHA'
1311 yield CheckExistingFile(
1312 filename='datafiles/qe_records_N' + str(self.n_events_training) + '_' + process + '_' + self.random_seed + '.root',
1313 )
1314 else:
1315 yield self.data_collection_task(
1316 num_processes=MasterTask.num_processes,
1317 n_events=self.n_events_training,
1318 experiment_number=self.experiment_number,
1319 random_seed=self.process_type + '_' + self.random_seed,
1320 )
1321
1322 def output(self):
1323 """
1324 Generate list of output files that the task should produce.
1325 The task is considered finished if and only if the outputs all exist.
1326 """
1327 yield self.add_to_output(self.get_weightfile_identifier() + '.xml')
1328
1329 def process(self):
1330 """
1331 Use basf2_mva teacher to create MVA weightfile from collected training
1332 data variables.
1333
1334 This is the main process that is dispatched by the ``run`` method that
1335 is inherited from ``Basf2Task``.
1336 """
1337 if 'USEREC' in self.process_type:
1338 if 'USERECBB' in self.process_type:
1339 process = 'BBBAR'
1340 elif 'USERECEE' in self.process_type:
1341 process = 'BHABHA'
1342 records_files = ['datafiles/qe_records_N' + str(self.n_events_training) +
1343 '_' + process + '_' + self.random_seed + '.root']
1344 else:
1345 if hasattr(self, 'recotrack_option') and isinstance(self, RecoTrackQETeacherTask):
1346 records_files = self.get_input_file_names(
1347 self.data_collection_task.get_records_file_name(
1349 n_events=self.n_events_training,
1350 random_seed=self.process_type + '_' + self.random_seed,
1351 recotrack_option=self.recotrack_option))
1352 else:
1353 records_files = self.get_input_file_names(
1354 self.data_collection_task.get_records_file_name(
1356 n_events=self.n_events_training,
1357 random_seed=self.process_type + '_' + self.random_seed))
1358
1359 weightfile_identifier = self.get_output_file_name(self.get_weightfile_identifier() + '.xml')
1360 print('The weightfile used is:', weightfile_identifier)
1361 my_basf2_mva_teacher(
1362 records_files=records_files,
1363 tree_name=self.tree_name,
1364 weightfile_identifier=weightfile_identifier,
1365 target_variable=self.training_target,
1366 exclude_variables=self.exclude_variables,
1367 fast_bdt_option=self.fast_bdt_option,
1368 )
1369 _ = self.make_db()
1370
1371
1373 """
1374 Task to run basf2 mva teacher on collected data for VXDTF2 track quality estimator
1375 """
1376
1377 weightfile_identifier_basename = "vxdtf2_mva_qe"
1378
1380 tree_name = "tree"
1381
1382 random_seed = "train_vxd"
1383
1385 data_collection_task = VXDQEDataCollectionTask
1386
1387 object_name = 'VXDQualityEstimatorMVA'
1388
1389 def make_db(self):
1390 """
1391 Creates the local VXD payload from weightfiles.
1392 """
1393 vxd_identifier = self.get_output_file_name(self.get_weightfile_identifier() + '.xml')
1394 with open(vxd_identifier, "r") as f:
1395 weight_file_content = f.read()
1396 vxd_name = write_mva_weightfile_content_to_db(
1397 dbobj_name=self.object_name,
1398 content=weight_file_content,
1399 iovList=(0, 0, 0, -1)
1400 )
1401 return vxd_name
1402
1403
1405 """
1406 Task to run basf2 mva teacher on collected data for CDC track quality estimator
1407 """
1408
1411 recotrack_option = b2luigi.Parameter(
1412
1413 default='deleteCDCQI080'
1414
1415 )
1416
1417 weightfile_identifier_basename = "cdc_mva_qe"
1418
1420 tree_name = "records"
1421
1422 random_seed = "train_cdc"
1423
1425 data_collection_task = CDCQEDataCollectionTask
1426
1427 object_name = 'TrackingMVAFilterParameters' # "trackfindingcdc_TrackQualityIndicator"
1428
1429 def make_db(self):
1430 """
1431 Creates the local CDC payload from weightfiles.
1432 """
1433 cut_index = self.recotrack_option.find('deleteCDCQI') + len('deleteCDCQI')
1434 cut = int(self.recotrack_option[cut_index:cut_index+3])/100.
1435
1436 cdc_identifier = self.get_output_file_name(self.get_weightfile_identifier() + '.xml')
1438 dbobj_name=self.object_name,
1439 iovList=(0, 0, 0, -1),
1440 weightfile_identifier=cdc_identifier,
1441 cut_value=cut)
1442 return name
1443
1444
1446 """
1447 Task to run basf2 mva teacher on collected data for the final, combined
1448 track quality estimator
1449 """
1450
1453 recotrack_option = b2luigi.Parameter(
1454
1455 default='deleteCDCQI080'
1456
1457 )
1458
1459
1460 weightfile_identifier_basename = "recotrack_mva_qe"
1461
1463 tree_name = "tree"
1464
1465 random_seed = "train_rec"
1466
1468 data_collection_task = RecoTrackQEDataCollectionTask
1469
1470 cdc_training_target = b2luigi.Parameter()
1471
1472 object_name = 'TrackQualityEstimatorMVA'
1473
1474 def requires(self):
1475 """
1476 Generate list of luigi Tasks that this Task depends on.
1477 """
1478 if 'USEREC' in self.process_type:
1479 if 'USERECBB' in self.process_type:
1480 process = 'BBBAR'
1481 elif 'USERECEE' in self.process_type:
1482 process = 'BHABHA'
1483 yield CheckExistingFile(
1484 filename='datafiles/qe_records_N' + str(self.n_events_training) + '_' + process + '_' + self.random_seed + '.root',
1485 )
1486 else:
1487 yield self.data_collection_task(
1488 cdc_training_target=self.cdc_training_target,
1489 num_processes=MasterTask.num_processes,
1490 n_events=self.n_events_training,
1491 experiment_number=self.experiment_number,
1492 random_seed=self.process_type + '_' + self.random_seed,
1493 recotrack_option=self.recotrack_option,
1494 fast_bdt_option=self.fast_bdt_option,
1495 )
1496
1497 def make_db(self):
1498 """
1499 Creates the local Reco payload from weightfiles.
1500 """
1501 recotrack_identifier = self.get_output_file_name(self.get_weightfile_identifier() + '.xml')
1502 with open(recotrack_identifier, 'r') as f:
1503 weight_file_content = f.read()
1504 recotrack_name = write_mva_weightfile_content_to_db(
1505 dbobj_name=self.object_name,
1506 content=weight_file_content,
1507 iovList=(0, 0, 0, -1)
1508 )
1509 return recotrack_name
1510
1511
1512class HarvestingValidationBaseTask(Basf2PathTask):
1513 """
1514 Run track reconstruction with MVA quality estimator and write out
1515 (="harvest") a root file with variables useful for the validation.
1516 """
1517
1518
1519 n_events_testing = b2luigi.IntParameter()
1520
1521 n_events_training = b2luigi.IntParameter()
1522
1523 experiment_number = b2luigi.IntParameter()
1524
1527 process_type = b2luigi.Parameter(
1528
1529 default="BBBAR"
1530
1531 )
1532
1534 exclude_variables = b2luigi.ListParameter(
1535
1536 hashed=True
1537
1538 )
1539
1540 fast_bdt_option = b2luigi.ListParameter(
1541
1542 hashed=True, default=[200, 8, 3, 0.1]
1543
1544 )
1545
1546 validation_output_file_name = "harvesting_validation.root"
1547
1548 reco_output_file_name = "reconstruction.root"
1549
1550 components = None
1551
1552 cdc_training_target = "truth"
1553
1554 @property
1555 def teacher_task(self) -> TrackQETeacherBaseTask:
1556 """
1557 Teacher task to require to provide a quality estimator weightfile for ``add_tracking_with_quality_estimation``
1558 """
1559 raise NotImplementedError()
1560
1561 def add_tracking_with_quality_estimation(self, path: basf2.Path) -> None:
1562 """
1563 Add modules for track reconstruction to basf2 path that are to be
1564 validated. Besides track finding it should include MC matching, fitted
1565 track creation and a quality estimator module.
1566 """
1567 raise NotImplementedError()
1568
1569 def requires(self):
1570 """
1571 Generate list of luigi Tasks that this Task depends on.
1572 """
1573 yield self.teacher_task(
1574 n_events_training=self.n_events_training,
1575 experiment_number=self.experiment_number,
1576 process_type=self.process_type,
1577 exclude_variables=self.exclude_variables,
1578 fast_bdt_option=self.fast_bdt_option,
1579 )
1580 if 'USE' in self.process_type: # USESIM and USEREC
1581 if 'BB' in self.process_type:
1582 process = 'BBBAR'
1583 elif 'EE' in self.process_type:
1584 process = 'BHABHA'
1585 yield CheckExistingFile(
1586 filename='datafiles/generated_mc_N' + str(self.n_events_testing) + '_' + process + '_test.root'
1587 )
1588 else:
1589 yield SplitNMergeSimTask(
1590 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
1591 random_seed=self.process_type + '_test',
1592 n_events=self.n_events_testing,
1593 experiment_number=self.experiment_number,
1594 )
1595
1596 def output(self):
1597 """
1598 Generate list of output files that the task should produce.
1599 The task is considered finished if and only if the outputs all exist.
1600 """
1601 yield self.add_to_output(self.validation_output_file_name)
1602 yield self.add_to_output(self.reco_output_file_name)
1603
1604 def create_path(self):
1605 """
1606 Create a basf2 path that uses ``add_tracking_with_quality_estimation()``
1607 and adds the ``CombinedTrackingValidationModule`` to write out variables
1608 for validation.
1609 """
1610 basf2.conditions.prepend_testing_payloads("localdb/database.txt")
1611 # prepare track finding
1612 path = basf2.create_path()
1613 if 'USE' in self.process_type:
1614 if 'BB' in self.process_type:
1615 process = 'BBBAR'
1616 elif 'EE' in self.process_type:
1617 process = 'BHABHA'
1618 inputFileNames = ['datafiles/generated_mc_N' + str(self.n_events_testing) + '_' + process + '_test.root']
1619 else:
1620 inputFileNames = self.get_input_file_names(GenerateSimTask.output_file_name(
1621 GenerateSimTask, n_events=self.n_events_testing, random_seed=self.process_type + '_test'))
1622 path.add_module(
1623 "RootInput",
1624 inputFileNames=inputFileNames,
1625 )
1626 path.add_module("Gearbox")
1627 tracking.add_geometry_modules(path)
1628 tracking.add_hit_preparation_modules(path) # only needed for simulated hits
1629 # add track finding module that needs to be validated
1631 # add modules for validation
1632 path.add_module(
1634 name=None,
1635 contact=None,
1636 expert_level=200,
1637 output_file_name=self.get_output_file_name(
1639 ),
1640 )
1641 )
1642 path.add_module(
1643 "RootOutput",
1644 outputFileName=self.get_output_file_name(self.reco_output_file_name),
1645 )
1646 return path
1647
1648
1650 """
1651 Run VXDTF2 track reconstruction and write out (="harvest") a root file with
1652 variables useful for validation of the VXD Quality Estimator.
1653 """
1654
1655
1656 validation_output_file_name = "vxd_qe_harvesting_validation.root"
1657
1658 reco_output_file_name = "vxd_qe_reconstruction.root"
1659
1660 teacher_task = VXDQETeacherTask
1661
1662 def requires(self):
1663 """
1664 Generate list of luigi Tasks that this Task depends on.
1665 """
1666 yield self.teacher_task(
1667 n_events_training=self.n_events_training,
1668 experiment_number=self.experiment_number,
1669 process_type=self.process_type,
1670 exclude_variables=self.exclude_variables,
1671 fast_bdt_option=self.fast_bdt_option,
1672 )
1673 if 'USE' in self.process_type: # USESIM and USEREC
1674 if 'BB' in self.process_type:
1675 process = 'BBBAR'
1676 elif 'EE' in self.process_type:
1677 process = 'BHABHA'
1678 yield CheckExistingFile(
1679 filename='datafiles/generated_mc_N' + str(self.n_events_testing) + '_' + process + '_test.root'
1680 )
1681 else:
1682 yield SplitNMergeSimTask(
1683 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
1684 random_seed=self.process_type + '_test',
1685 n_events=self.n_events_testing,
1686 experiment_number=self.experiment_number,
1687 )
1688
1690 """
1691 Add modules for VXDTF2 tracking with VXD quality estimator to basf2 path.
1692 """
1693 add_vxd_track_finding_vxdtf2(
1694 path,
1695 components=["SVD"],
1696 reco_tracks="RecoTracks",
1697 add_mva_quality_indicator=True,
1698 )
1699 # Replace the weightfiles of all quality estimator module by those
1700 # produced in this training by b2luigi
1701 vxd_name = 'VXDQualityEstimatorMVA'
1702 basf2.set_module_parameters(
1703 path,
1704 name="VXDQualityEstimatorMVA",
1705 WeightFileIdentifier=vxd_name,
1706 )
1707 tracking.add_mc_matcher(path, components=["SVD"], relate_tracks_to_mcparticles=False)
1708 add_track_fit_and_track_creator(path, components=["SVD"])
1709
1710
1712 """
1713 Run CDC reconstruction and write out (="harvest") a root file with variables
1714 useful for validation of the CDC Quality Estimator.
1715 """
1716
1717 training_target = b2luigi.Parameter()
1718
1719 validation_output_file_name = "cdc_qe_harvesting_validation.root"
1720
1721 reco_output_file_name = "cdc_qe_reconstruction.root"
1722
1723 teacher_task = CDCQETeacherTask
1724 # overload needed due to specific training target
1725
1726 def requires(self):
1727 """
1728 Generate list of luigi Tasks that this Task depends on.
1729 """
1730 yield self.teacher_task(
1731 n_events_training=self.n_events_training,
1732 experiment_number=self.experiment_number,
1733 process_type=self.process_type,
1734 training_target=self.training_target,
1735 exclude_variables=self.exclude_variables,
1736 fast_bdt_option=self.fast_bdt_option,
1737 )
1738 if 'USE' in self.process_type: # USESIM and USEREC
1739 if 'BB' in self.process_type:
1740 process = 'BBBAR'
1741 elif 'EE' in self.process_type:
1742 process = 'BHABHA'
1743 yield CheckExistingFile(
1744 filename='datafiles/generated_mc_N' + str(self.n_events_testing) + '_' + process + '_test.root'
1745 )
1746 else:
1747 yield SplitNMergeSimTask(
1748 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
1749 random_seed=self.process_type + '_test',
1750 n_events=self.n_events_testing,
1751 experiment_number=self.experiment_number,
1752 )
1753
1755 """
1756 Add modules for CDC standalone tracking with CDC quality estimator to basf2 path.
1757 """
1758 add_cdc_track_finding(
1759 path,
1760 output_reco_tracks="RecoTracks",
1761 add_mva_quality_indicator=True,
1762 )
1763 # change weightfile of quality estimator to the one produced by this training script
1764 basf2.conditions.prepend_testing_payloads("localdb/database.txt")
1765 cdc_qe_mva_filter_parameters = {
1766 "identifier": self.get_input_file_names(
1767 CDCQETeacherTask.get_weightfile_identifier(
1768 CDCQETeacherTask,
1769 fast_bdt_option=self.fast_bdt_option) + '.xml')[0],
1770 'DBPayloadName': 'trackfindingcdc_TrackQualityEstimatorParameters'}
1771
1772 name = 'TrackingMVAFilterParameters'
1773 cdc_qe_mva_filter_parameters = {'DBPayloadName': name}
1774 basf2.set_module_parameters(
1775 path,
1776 name="TFCDC_TrackQualityEstimator",
1777 filterParameters=cdc_qe_mva_filter_parameters,
1778 deactivateIfDeadBoard=False, # original behavior before deactivateIfDeadBoard was introduced
1779 )
1780 tracking.add_mc_matcher(path, components=["CDC"], relate_tracks_to_mcparticles=False)
1781 add_track_fit_and_track_creator(path, components=["CDC"])
1782
1783
1785 """
1786 Run track reconstruction and write out (="harvest") a root file with variables
1787 useful for validation of the MVA track Quality Estimator.
1788 """
1789
1790 cdc_training_target = b2luigi.Parameter()
1791
1792 validation_output_file_name = "reco_qe_harvesting_validation.root"
1793
1794 reco_output_file_name = "reco_qe_reconstruction.root"
1795
1796 teacher_task = RecoTrackQETeacherTask
1797
1798 def requires(self):
1799 """
1800 Generate list of luigi Tasks that this Task depends on.
1801 """
1802 yield CDCQETeacherTask(
1803 n_events_training=self.n_events_training,
1804 experiment_number=self.experiment_number,
1805 process_type=self.process_type,
1806 training_target=self.cdc_training_target,
1807 exclude_variables=MasterTask.exclude_variables_cdc,
1808 fast_bdt_option=self.fast_bdt_option,
1809 )
1810 yield VXDQETeacherTask(
1811 n_events_training=self.n_events_training,
1812 experiment_number=self.experiment_number,
1813 process_type=self.process_type,
1814 exclude_variables=MasterTask.exclude_variables_vxd,
1815 fast_bdt_option=self.fast_bdt_option,
1816 )
1817
1818 yield self.teacher_task(
1819 n_events_training=self.n_events_training,
1820 experiment_number=self.experiment_number,
1821 process_type=self.process_type,
1822 exclude_variables=self.exclude_variables,
1823 cdc_training_target=self.cdc_training_target,
1824 fast_bdt_option=self.fast_bdt_option,
1825 )
1826 if 'USE' in self.process_type: # USESIM and USEREC
1827 if 'BB' in self.process_type:
1828 process = 'BBBAR'
1829 elif 'EE' in self.process_type:
1830 process = 'BHABHA'
1831 yield CheckExistingFile(
1832 filename='datafiles/generated_mc_N' + str(self.n_events_testing) + '_' + process + '_test.root'
1833 )
1834 else:
1835 yield SplitNMergeSimTask(
1836 bkgfiles_dir=MasterTask.bkgfiles_by_exp[self.experiment_number],
1837 random_seed=self.process_type + '_test',
1838 n_events=self.n_events_testing,
1839 experiment_number=self.experiment_number,
1840 )
1841
1843 """
1844 Add modules for reco tracking with all track quality estimators to basf2 path.
1845 """
1846 # add tracking reconstruction with quality estimator modules added
1847 tracking.add_tracking_reconstruction(
1848 path,
1849 add_cdcTrack_QI=True,
1850 add_vxdTrack_QI=True,
1851 add_recoTrack_QI=True,
1852 skipGeometryAdding=True,
1853 skipHitPreparerAdding=True,
1854 )
1855
1856 name = 'TrackingMVAFilterParameters'
1857 cdc_qe_mva_filter_parameters = {'DBPayloadName': name}
1858 basf2.set_module_parameters(
1859 path,
1860 name="TFCDC_TrackQualityEstimator",
1861 filterParameters=cdc_qe_mva_filter_parameters,
1862 deactivateIfDeadBoard=False, # original behavior before deactivateIfDeadBoard was introduced
1863 )
1864 vxd_name = 'VXDQualityEstimatorMVA'
1865 basf2.set_module_parameters(
1866 path,
1867 name="VXDQualityEstimatorMVA",
1868 WeightFileIdentifier=vxd_name,
1869 )
1870 recotrack_name = 'TrackQualityEstimatorMVA'
1871 basf2.set_module_parameters(
1872 path,
1873 name="TrackQualityEstimatorMVA",
1874 WeightFileIdentifier=recotrack_name,
1875 )
1876
1877
1879 """
1880 Base class for evaluating a quality estimator ``basf2_mva_evaluate.py`` on a
1881 separate test data set.
1882
1883 Evaluation tasks for VXD, CDC and combined QE can inherit from it.
1884 """
1885
1886
1891 git_hash = b2luigi.Parameter(
1892
1893 default=get_basf2_git_hash()
1894
1895 )
1896
1897 n_events_testing = b2luigi.IntParameter()
1898
1899 n_events_training = b2luigi.IntParameter()
1900
1901 experiment_number = b2luigi.IntParameter()
1902
1905 process_type = b2luigi.Parameter(
1906
1907 default="BBBAR"
1908
1909 )
1910
1911 training_target = b2luigi.Parameter(
1912
1913 default="truth"
1914
1915 )
1916
1918 exclude_variables = b2luigi.ListParameter(
1919
1920 hashed=True
1921
1922 )
1923
1924 fast_bdt_option = b2luigi.ListParameter(
1925
1926 hashed=True, default=[200, 8, 3, 0.1]
1927
1928 )
1929
1930
1931 @property
1932 def teacher_task(self) -> TrackQETeacherBaseTask:
1933 """
1934 Property defining specific teacher task to require.
1935 """
1936 raise NotImplementedError(
1937 "Evaluation Tasks must define a teacher task to require "
1938 )
1939
1940 @property
1941 def data_collection_task(self) -> Basf2PathTask:
1942 """
1943 Property defining the specific ``DataCollectionTask`` to require. Must
1944 implemented by the inheriting specific teacher task class.
1945 """
1946 raise NotImplementedError(
1947 "Evaluation Tasks must define a data collection task to require "
1948 )
1949
1950 @property
1951 def task_acronym(self):
1952 """
1953 Acronym to distinguish between cdc, vxd and rec(o) MVA
1954 """
1955 raise NotImplementedError(
1956 "Evaluation Tasks must define a task acronym."
1957 )
1958
1959 def requires(self):
1960 """
1961 Generate list of luigi Tasks that this Task depends on.
1962 """
1963 yield self.teacher_task(
1964 n_events_training=self.n_events_training,
1965 experiment_number=self.experiment_number,
1966 process_type=self.process_type,
1967 training_target=self.training_target,
1968 exclude_variables=self.exclude_variables,
1969 fast_bdt_option=self.fast_bdt_option,
1970 )
1971
1972 # Reconstruct output file path
1973 weightfile_details = create_fbdt_option_string(self.fast_bdt_option)
1974 output_basename = self.teacher_task.weightfile_identifier_basename + weightfile_details + ".zip"
1975 output_path = self.get_output_file_name(output_basename)
1976
1977 # Delete the output file if it exists so that the script does not think
1978 # that it ran succesfully if no new file is created
1979 if os.path.exists(output_path):
1980 try:
1981 os.remove(output_path)
1982 print(f"[INFO] Cleared folder of old file: {output_path}")
1983 except Exception as e:
1984 print(f"[WARNING] Failed to remove output file {output_path}: {e}")
1985 if 'USEREC' in self.process_type:
1986 if 'USERECBB' in self.process_type:
1987 process = 'BBBAR'
1988 elif 'USERECEE' in self.process_type:
1989 process = 'BHABHA'
1990 yield CheckExistingFile(
1991 filename='datafiles/qe_records_N' + str(self.n_events_testing) + '_' + process + '_test_' +
1992 self.task_acronym + '.root'
1993 )
1994 else:
1995 yield self.data_collection_task(
1996 num_processes=MasterTask.num_processes,
1997 n_events=self.n_events_testing,
1998 experiment_number=self.experiment_number,
1999 random_seed=self.process_type + '_test',
2000 )
2001
2002 def output(self):
2003 """
2004 Generate list of output files that the task should produce.
2005 The task is considered finished if and only if the outputs all exist.
2006 """
2007 weightfile_details = create_fbdt_option_string(self.fast_bdt_option)
2008 evaluation_pdf_output = self.teacher_task.weightfile_identifier_basename + weightfile_details + ".zip"
2009 yield self.add_to_output(evaluation_pdf_output)
2010
2011 def run(self):
2012 """
2013 Run ``basf2_mva_evaluate.py`` subprocess to evaluate QE MVA.
2014
2015 The MVA weight file created from training on the training data set is
2016 evaluated on separate test data.
2017 """
2018 weightfile_details = create_fbdt_option_string(self.fast_bdt_option)
2019 evaluation_pdf_output_basename = self.teacher_task.weightfile_identifier_basename + weightfile_details + ".zip"
2020 evaluation_pdf_output_path = self.get_output_file_name(evaluation_pdf_output_basename)
2021
2022 if 'USEREC' in self.process_type:
2023 if 'USERECBB' in self.process_type:
2024 process = 'BBBAR'
2025 elif 'USERECEE' in self.process_type:
2026 process = 'BHABHA'
2027 datafiles = 'datafiles/qe_records_N' + str(self.n_events_testing) + '_' + \
2028 process + '_test_' + self.task_acronym + '.root'
2029 else:
2030 datafiles = self.get_input_file_names(
2031 self.data_collection_task.get_records_file_name(
2033 n_events=self.n_events_testing,
2034 random_seed=self.process_type + '_test_' +
2035 self.task_acronym))[0]
2036 teacher_task = None
2037 for req in b2luigi.task.flatten(self.requires()):
2038 if isinstance(req, self.teacher_task):
2039 teacher_task = req
2040 break
2041 if hasattr(teacher_task, 'recotrack_option') and isinstance(self, RecoTrackQEEvaluationTask):
2042 records_files = teacher_task.get_input_file_names(
2043 self.data_collection_task.get_records_file_name(
2045 n_events=self.n_events_training,
2046 random_seed=self.process_type + '_' + teacher_task.random_seed,
2047 recotrack_option=teacher_task.recotrack_option))
2048 else:
2049 records_files = teacher_task.get_input_file_names(
2050 self.data_collection_task.get_records_file_name(
2052 n_events=self.n_events_training,
2053 random_seed=self.process_type + '_' + teacher_task.random_seed))
2054 cmd = [
2055 "basf2_mva_evaluate.py",
2056 "--identifiers",
2057 self.get_input_file_names(
2058 self.teacher_task.get_weightfile_identifier(
2059 self.teacher_task,
2060 fast_bdt_option=self.fast_bdt_option) + '.xml')[0],
2061 "--train_datafiles",
2062 records_files[0],
2063 "--datafiles",
2064 datafiles,
2065 "--treename",
2066 self.teacher_task.tree_name,
2067 "--outputfile",
2068 evaluation_pdf_output_path
2069 ]
2070 print(
2071 'The weightfile for QE Evaluate is:',
2072 self.get_input_file_names(
2073 self.teacher_task.get_weightfile_identifier(
2074
2076 self.teacher_task,
2077 fast_bdt_option=self.fast_bdt_option) +
2078 '.xml')[0])
2079
2080 subprocess.run(cmd, check=True) # code to actually run the basf2_mva_evaluate.py process
2081
2082
2084 """
2085 Run ``basf2_mva_evaluate.py`` for the VXD quality estimator on separate test data
2086 """
2087
2089 teacher_task = VXDQETeacherTask
2090
2092 data_collection_task = VXDQEDataCollectionTask
2093
2095 task_acronym = 'vxd'
2096
2097
2099 """
2100 Run ``basf2_mva_evaluate.py`` for the CDC quality estimator on separate test data
2101 """
2102
2104 teacher_task = CDCQETeacherTask
2105
2107 data_collection_task = CDCQEDataCollectionTask
2108
2110 task_acronym = 'cdc'
2111
2112
2114 """
2115 Run ``basf2_mva_evaluate.py`` for the final, combined quality estimator on
2116 separate test data
2117 """
2118
2120 teacher_task = RecoTrackQETeacherTask
2121
2123 data_collection_task = RecoTrackQEDataCollectionTask
2124
2126 task_acronym = 'rec'
2127
2128 cdc_training_target = b2luigi.Parameter()
2129
2130 def requires(self):
2131 """
2132 Generate list of luigi Tasks that this Task depends on.
2133 """
2134 yield self.teacher_task(
2135 n_events_training=self.n_events_training,
2136 experiment_number=self.experiment_number,
2137 process_type=self.process_type,
2138 training_target=self.training_target,
2139 exclude_variables=self.exclude_variables,
2140 cdc_training_target=self.cdc_training_target,
2141 fast_bdt_option=self.fast_bdt_option,
2142 )
2143 if 'USEREC' in self.process_type:
2144 if 'USERECBB' in self.process_type:
2145 process = 'BBBAR'
2146 elif 'USERECEE' in self.process_type:
2147 process = 'BHABHA'
2148 yield CheckExistingFile(
2149 filename='datafiles/qe_records_N' + str(self.n_events_testing) + '_' + process + '_test_' +
2150 self.task_acronym + '.root'
2151 )
2152 else:
2153 yield self.data_collection_task(
2154 num_processes=MasterTask.num_processes,
2155 n_events=self.n_events_testing,
2156 experiment_number=self.experiment_number,
2157 random_seed=self.process_type + "_test",
2158 cdc_training_target=self.cdc_training_target,
2159 )
2160
2161
2163 """
2164 Create a PDF file with validation plots for a quality estimator produced
2165 from the ROOT ntuples produced by a harvesting validation task
2166 """
2167
2168 n_events_testing = b2luigi.IntParameter()
2169
2170 n_events_training = b2luigi.IntParameter()
2171
2172 experiment_number = b2luigi.IntParameter()
2173
2176 process_type = b2luigi.Parameter(
2177
2178 default="BBBAR"
2179
2180 )
2181
2183 exclude_variables = b2luigi.ListParameter(
2184
2185 hashed=True
2186
2187 )
2188
2189 fast_bdt_option = b2luigi.ListParameter(
2190
2191 hashed=True, default=[200, 8, 3, 0.1]
2192
2193 )
2194
2195 primaries_only = b2luigi.BoolParameter(
2196
2197 default=True
2198
2199 ) # normalize finding efficiencies to primary MC-tracks
2200
2201 cdc_training_target = "truth"
2202
2203 @property
2204 def harvesting_validation_task_instance(self) -> HarvestingValidationBaseTask:
2205 """
2206 Specifies related harvesting validation task which produces the ROOT
2207 files with the data that is plotted by this task.
2208 """
2209 raise NotImplementedError("Must define a QI harvesting validation task for which to do the plots")
2210
2211
2212 @property
2214 """
2215 Name of the output PDF file containing the validation plots
2216 """
2217 validation_harvest_basename = self.harvesting_validation_task_instance.validation_output_file_name
2218 return validation_harvest_basename.replace(".root", "_plots.pdf")
2219
2220 def requires(self):
2221 """
2222 Generate list of luigi Tasks that this Task depends on.
2223 """
2225
2226 def output(self):
2227 """
2228 Generate list of output files that the task should produce.
2229 The task is considered finished if and only if the outputs all exist.
2230 """
2231
2232 yield self.add_to_output(self.output_pdf_file_basename)
2233
2234 @b2luigi.on_temporary_files
2235 def process(self):
2236 """
2237 Use basf2_mva teacher to create MVA weightfile from collected training
2238 data variables.
2239
2240 Main process that is dispatched by the ``run`` method that is inherited
2241 from ``Basf2Task``.
2242 """
2243 # get the validation "harvest", which is the ROOT file with ntuples for validation
2244 validation_harvest_basename = self.harvesting_validation_task_instance.validation_output_file_name
2245 validation_harvest_path = self.get_input_file_names(validation_harvest_basename)[0]
2246 print('\nThe validation harvest path is:', validation_harvest_path, '\n')
2247
2248 # Load "harvested" validation data from root files into dataframes (requires enough memory to hold data)
2249 pr_columns = [ # Restrict memory usage by only reading in columns that are used in the steering file
2250 'is_fake', 'is_clone', 'is_matched', 'quality_indicator',
2251 'experiment_number', 'run_number', 'event_number', 'pr_store_array_number',
2252 'pt_estimate', 'z0_estimate', 'd0_estimate', 'tan_lambda_estimate',
2253 'phi0_estimate', 'pt_truth', 'z0_truth', 'd0_truth', 'tan_lambda_truth',
2254 'phi0_truth',
2255 ]
2256 # In ``pr_df`` each row corresponds to a track from Pattern Recognition
2257 pr_df = uproot.open(validation_harvest_path)['pr_tree/pr_tree'].arrays(pr_columns, library='pd')
2258 mc_columns = [ # restrict mc_df to these columns
2259 'experiment_number',
2260 'run_number',
2261 'event_number',
2262 'pr_store_array_number',
2263 'is_missing',
2264 'is_primary',
2265 ]
2266 # In ``mc_df`` each row corresponds to an MC track
2267 mc_df = uproot.open(validation_harvest_path)['mc_tree/mc_tree'].arrays(mc_columns, library='pd')
2268 if self.primaries_only:
2269 mc_df = mc_df[mc_df.is_primary.eq(True)]
2270
2271 # Define QI thresholds for the FOM plots and the ROC curves
2272 qi_cuts = np.linspace(0., 1, 20, endpoint=False)
2273 # # Add more points at the very end between the previous maximum and 1
2274 # qi_cuts = np.append(qi_cuts, np.linspace(np.max(qi_cuts), 1, 20, endpoint=False))
2275
2276 # Create plots and append them to single output pdf
2277
2278 output_pdf_file_path = self.get_output_file_name(self.output_pdf_file_basename)
2279 with PdfPages(output_pdf_file_path, keep_empty=False) as pdf:
2280
2281 # Add a title page to validation plot PDF with some metadata
2282 # Remember that most metadata is in the xml file of the weightfile
2283 # and in the b2luigi directory structure
2284 titlepage_fig, titlepage_ax = plt.subplots()
2285 titlepage_ax.axis("off")
2286 title = f"Quality Estimator validation plots from {self.__class__.__name__}"
2287 titlepage_ax.set_title(title)
2288 teacher_task = self.harvesting_validation_task_instance.teacher_task
2289 weightfile_identifier = teacher_task.get_weightfile_identifier(
2290 teacher_task, fast_bdt_option=self.fast_bdt_option) + '.xml'
2291 meta_data = {
2292 "Date": datetime.today().strftime("%Y-%m-%d %H:%M"),
2293 "Created by steering file": os.path.realpath(__file__),
2294 "Created from data in": validation_harvest_path,
2295 "Background directory": MasterTask.bkgfiles_by_exp[self.experiment_number],
2296 "weight file": weightfile_identifier,
2297 }
2298 if hasattr(self, 'exclude_variables'):
2299 meta_data["Excluded variables"] = ", ".join(self.exclude_variables)
2300 meta_data_string = (format_dictionary(meta_data) +
2301 "\n\n(For all MVA training parameters look into the produced weight file)")
2302 luigi_params = get_serialized_parameters(self)
2303 luigi_param_string = (f"\n\nb2luigi parameters for {self.__class__.__name__}\n" +
2304 format_dictionary(luigi_params))
2305 title_page_text = meta_data_string + luigi_param_string
2306 titlepage_ax.text(0, 1, title_page_text, ha="left", va="top", wrap=True, fontsize=8)
2307 pdf.savefig(titlepage_fig)
2308 plt.close(titlepage_fig)
2309
2310 fake_rates = get_uncertain_means_for_qi_cuts(pr_df, "is_fake", qi_cuts)
2311 fake_fig, fake_ax = plt.subplots()
2312 fake_ax.set_title("Fake rate")
2313 plot_with_errobands(fake_rates, ax=fake_ax)
2314 fake_ax.set_ylabel("fake rate")
2315 fake_ax.set_xlabel("quality indicator requirement")
2316 pdf.savefig(fake_fig, bbox_inches="tight")
2317 plt.close(fake_fig)
2318
2319 # Plot clone rates
2320 clone_rates = get_uncertain_means_for_qi_cuts(pr_df, "is_clone", qi_cuts)
2321 clone_fig, clone_ax = plt.subplots()
2322 clone_ax.set_title("Clone rate")
2323 plot_with_errobands(clone_rates, ax=clone_ax)
2324 clone_ax.set_ylabel("clone rate")
2325 clone_ax.set_xlabel("quality indicator requirement")
2326 pdf.savefig(clone_fig, bbox_inches="tight")
2327 plt.close(clone_fig)
2328
2329 # Plot finding efficiency
2330
2331 # The Quality Indicator is only available in pr_tree and thus the
2332 # PR-track dataframe. To get the QI of the related PR track for an MC
2333 # track, merge the PR dataframe into the MC dataframe
2334 pr_track_identifiers = ['experiment_number', 'run_number', 'event_number', 'pr_store_array_number']
2335 mc_df = upd.merge(
2336 left=mc_df, right=pr_df[pr_track_identifiers + ['quality_indicator']],
2337 how='left',
2338 on=pr_track_identifiers
2339 )
2340
2341 missing_fractions = (
2342 _my_uncertain_mean(mc_df[
2343 mc_df.quality_indicator.isnull() | (mc_df.quality_indicator > qi_cut)]['is_missing'])
2344 for qi_cut in qi_cuts
2345 )
2346
2347 findeff_fig, findeff_ax = plt.subplots()
2348 findeff_ax.set_title("Finding efficiency")
2349 finding_efficiencies = 1.0 - upd.Series(data=missing_fractions, index=qi_cuts)
2350 plot_with_errobands(finding_efficiencies, ax=findeff_ax)
2351 findeff_ax.set_ylabel("finding efficiency")
2352 findeff_ax.set_xlabel("quality indicator requirement")
2353 pdf.savefig(findeff_fig, bbox_inches="tight")
2354 plt.close(findeff_fig)
2355
2356 # Plot ROC curves
2357
2358 # Fake rate vs. finding efficiency ROC curve
2359 fake_roc_fig, fake_roc_ax = plt.subplots()
2360 fake_roc_ax.set_title("Fake rate vs. finding efficiency ROC curve")
2361 fake_roc_ax.errorbar(x=finding_efficiencies.nominal_value, y=fake_rates.nominal_value,
2362 xerr=finding_efficiencies.std_dev, yerr=fake_rates.std_dev, elinewidth=0.8)
2363 fake_roc_ax.set_xlabel('finding efficiency')
2364 fake_roc_ax.set_ylabel('fake rate')
2365 pdf.savefig(fake_roc_fig, bbox_inches="tight")
2366 plt.close(fake_roc_fig)
2367
2368 # Clone rate vs. finding efficiency ROC curve
2369 clone_roc_fig, clone_roc_ax = plt.subplots()
2370 clone_roc_ax.set_title("Clone rate vs. finding efficiency ROC curve")
2371 clone_roc_ax.errorbar(x=finding_efficiencies.nominal_value, y=clone_rates.nominal_value,
2372 xerr=finding_efficiencies.std_dev, yerr=clone_rates.std_dev, elinewidth=0.8)
2373 clone_roc_ax.set_xlabel('finding efficiency')
2374 clone_roc_ax.set_ylabel('clone rate')
2375 pdf.savefig(clone_roc_fig, bbox_inches="tight")
2376 plt.close(clone_roc_fig)
2377
2378 # Plot kinematic distributions
2379
2380 # use fewer qi cuts as each cut will be it's own subplot now and not a point
2381 kinematic_qi_cuts = [0, 0.5, 0.9]
2382
2383 # Define kinematic parameters which we want to histogram and define
2384 # dictionaries relating them to latex labels, units and binnings
2385 params = ['d0', 'z0', 'pt', 'tan_lambda', 'phi0']
2386 label_by_param = {
2387 "pt": "$p_T$",
2388 "z0": "$z_0$",
2389 "d0": "$d_0$",
2390 "tan_lambda": r"$\tan{\lambda}$",
2391 "phi0": r"$\phi_0$"
2392 }
2393 unit_by_param = {
2394 "pt": "GeV",
2395 "z0": "cm",
2396 "d0": "cm",
2397 "tan_lambda": "rad",
2398 "phi0": "rad"
2399 }
2400 n_kinematic_bins = 75 # number of bins per kinematic variable
2401 bins_by_param = {
2402 "pt": np.linspace(0, np.percentile(pr_df['pt_truth'].dropna(), 95), n_kinematic_bins),
2403 "z0": np.linspace(-0.1, 0.1, n_kinematic_bins),
2404 "d0": np.linspace(0, 0.01, n_kinematic_bins),
2405 "tan_lambda": np.linspace(-2, 3, n_kinematic_bins),
2406 "phi0": np.linspace(0, 2 * np.pi, n_kinematic_bins)
2407 }
2408
2409 # Iterate over each parameter and for each make stacked histograms for different QI cuts
2410 kinematic_qi_cuts = [0, 0.5, 0.8]
2411 blue, yellow, green = plt.get_cmap("tab10").colors[0:3]
2412 for param in params:
2413 fig, axarr = plt.subplots(ncols=len(kinematic_qi_cuts), sharey=True, sharex=True, figsize=(14, 6))
2414 fig.suptitle(f"{label_by_param[param]} distributions")
2415 for i, qi in enumerate(kinematic_qi_cuts):
2416 ax = axarr[i]
2417 ax.set_title(f"QI > {qi}")
2418 incut = pr_df[(pr_df['quality_indicator'] > qi)]
2419 incut_matched = incut[incut.is_matched.eq(True)]
2420 incut_clones = incut[incut.is_clone.eq(True)]
2421 incut_fake = incut[incut.is_fake.eq(True)]
2422
2423 # if any series is empty, break out of loop and don't draw try to draw a stacked histogram
2424 if any(series.empty for series in (incut, incut_matched, incut_clones, incut_fake)):
2425 ax.text(0.5, 0.5, "Not enough data in bin", ha="center", va="center", transform=ax.transAxes)
2426 continue
2427
2428 bins = bins_by_param[param]
2429 stacked_histogram_series_tuple = (
2430 incut_matched[f'{param}_estimate'],
2431 incut_clones[f'{param}_estimate'],
2432 incut_fake[f'{param}_estimate'],
2433 )
2434 histvals, _, _ = ax.hist(stacked_histogram_series_tuple,
2435 stacked=True,
2436 bins=bins, range=(bins.min(), bins.max()),
2437 color=(blue, green, yellow),
2438 label=("matched", "clones", "fakes"))
2439 ax.set_xlabel(f'{label_by_param[param]} estimate / ({unit_by_param[param]})')
2440 ax.set_ylabel('# tracks')
2441 axarr[0].legend(loc="upper center", bbox_to_anchor=(0, -0.15))
2442 pdf.savefig(fig, bbox_inches="tight")
2443 plt.close(fig)
2444
2445
2447 """
2448 Create a PDF file with validation plots for the VXDTF2 track quality
2449 estimator produced from the ROOT ntuples produced by a VXDTF2 track QE
2450 harvesting validation task
2451 """
2452
2453 @property
2455 """
2456 Harvesting validation task to require, which produces the ROOT files
2457 with variables to produce the VXD QE validation plots.
2458 """
2460 n_events_testing=self.n_events_testing,
2461 n_events_training=self.n_events_training,
2462 process_type=self.process_type,
2463 experiment_number=self.experiment_number,
2464 exclude_variables=self.exclude_variables,
2465 num_processes=MasterTask.num_processes,
2466 fast_bdt_option=self.fast_bdt_option,
2467 )
2468
2469
2471 """
2472 Create a PDF file with validation plots for the CDC track quality estimator
2473 produced from the ROOT ntuples produced by a CDC track QE harvesting
2474 validation task
2475 """
2476
2477 training_target = b2luigi.Parameter()
2478
2479 @property
2481 """
2482 Harvesting validation task to require, which produces the ROOT files
2483 with variables to produce the CDC QE validation plots.
2484 """
2486 n_events_testing=self.n_events_testing,
2487 n_events_training=self.n_events_training,
2488 process_type=self.process_type,
2489 experiment_number=self.experiment_number,
2490 training_target=self.training_target,
2491 exclude_variables=self.exclude_variables,
2492 num_processes=MasterTask.num_processes,
2493 fast_bdt_option=self.fast_bdt_option,
2494 )
2495
2496
2498 """
2499 Create a PDF file with validation plots for the reco MVA track quality
2500 estimator produced from the ROOT ntuples produced by a reco track QE
2501 harvesting validation task
2502 """
2503
2504 cdc_training_target = b2luigi.Parameter()
2505
2506 @property
2508 """
2509 Harvesting validation task to require, which produces the ROOT files
2510 with variables to produce the final MVA track QE validation plots.
2511 """
2513 n_events_testing=self.n_events_testing,
2514 n_events_training=self.n_events_training,
2515 process_type=self.process_type,
2516 experiment_number=self.experiment_number,
2517 cdc_training_target=self.cdc_training_target,
2518 exclude_variables=self.exclude_variables,
2519 num_processes=MasterTask.num_processes,
2520 fast_bdt_option=self.fast_bdt_option,
2521 )
2522
2523
2524class MasterTask(b2luigi.WrapperTask):
2525 """
2526 Wrapper task that needs to finish for b2luigi to finish running this steering file.
2527
2528 It is done if the outputs of all required subtasks exist. It is thus at the
2529 top of the luigi task graph. Edit the ``requires`` method to steer which
2530 tasks and with which parameters you want to run.
2531 """
2532
2535 process_type = b2luigi.get_setting(
2536
2537 "process_type", default='BBBAR'
2538
2539 )
2540
2541 n_events_training = b2luigi.get_setting(
2542
2543 "n_events_training", default=20000
2544
2545 )
2546
2547 n_events_testing = b2luigi.get_setting(
2548
2549 "n_events_testing", default=5000
2550
2551 )
2552
2553 n_events_per_task = b2luigi.get_setting(
2554
2555 "n_events_per_task", default=100
2556
2557 )
2558
2559 num_processes = b2luigi.get_setting(
2560
2561 "basf2_processes_per_worker", default=0
2562
2563 )
2564
2565 datafiles = b2luigi.get_setting("datafiles")
2566
2567 bkgfiles_by_exp = b2luigi.get_setting("bkgfiles_by_exp")
2568
2569 bkgfiles_by_exp = {int(key): val for (key, val) in bkgfiles_by_exp.items()}
2570
2571 exclude_variables_cdc = [
2572 "has_matching_segment",
2573 "size",
2574 "n_tracks", # not written out per default anyway
2575 "avg_hit_dist",
2576 "cont_layer_mean",
2577 "cont_layer_variance",
2578 "cont_layer_max",
2579 "cont_layer_min",
2580 "cont_layer_first",
2581 "cont_layer_last",
2582 "cont_layer_max_vs_last",
2583 "cont_layer_first_vs_min",
2584 "cont_layer_count",
2585 "cont_layer_occupancy",
2586 "super_layer_mean",
2587 "super_layer_variance",
2588 "super_layer_max_vs_last",
2589 "super_layer_first_vs_min",
2590 "super_layer_occupancy",
2591 "drift_length_mean",
2592 "drift_length_variance",
2593 "drift_length_max",
2594 "drift_length_min",
2595 "drift_length_sum",
2596 "norm_drift_length_mean",
2597 "norm_drift_length_variance",
2598 "norm_drift_length_max",
2599 "norm_drift_length_min",
2600 "norm_drift_length_sum",
2601 "adc_mean",
2602 "adc_variance",
2603 "adc_max",
2604 "adc_min",
2605 "adc_sum",
2606 "tot_mean",
2607 "tot_variance",
2608 "tot_max",
2609 "tot_min",
2610 "tot_sum",
2611 "empty_s_mean",
2612 "empty_s_variance",
2613 "empty_s_max"
2614 ]
2615
2616 exclude_variables_vxd = [
2617 'energyLoss_max', 'energyLoss_min', 'energyLoss_mean', 'energyLoss_std', 'energyLoss_sum',
2618 'size_max', 'size_min', 'size_mean', 'size_std', 'size_sum',
2619 'seedCharge_max', 'seedCharge_min', 'seedCharge_mean', 'seedCharge_std', 'seedCharge_sum',
2620 'tripletFit_P_Mag', 'tripletFit_P_Eta', 'tripletFit_P_Phi', 'tripletFit_P_X', 'tripletFit_P_Y', 'tripletFit_P_Z']
2621
2622 exclude_variables_rec = [
2623 'background',
2624 'ghost',
2625 'fake',
2626 'clone',
2627 '__experiment__',
2628 '__run__',
2629 '__event__',
2630 'N_RecoTracks',
2631 'N_PXDRecoTracks',
2632 'N_SVDRecoTracks',
2633 'N_CDCRecoTracks',
2634 'N_diff_PXD_SVD_RecoTracks',
2635 'N_diff_SVD_CDC_RecoTracks',
2636 'Fit_Successful',
2637 'Fit_NFailedPoints',
2638 'Fit_Chi2',
2639 'N_TrackPoints_without_KalmanFitterInfo',
2640 'N_Hits_without_TrackPoint',
2641 'SVD_CDC_CDCwall_Chi2',
2642 'SVD_CDC_CDCwall_Pos_diff_Z',
2643 'SVD_CDC_CDCwall_Pos_diff_Pt',
2644 'SVD_CDC_CDCwall_Pos_diff_Theta',
2645 'SVD_CDC_CDCwall_Pos_diff_Phi',
2646 'SVD_CDC_CDCwall_Pos_diff_Mag',
2647 'SVD_CDC_CDCwall_Pos_diff_Eta',
2648 'SVD_CDC_CDCwall_Mom_diff_Z',
2649 'SVD_CDC_CDCwall_Mom_diff_Pt',
2650 'SVD_CDC_CDCwall_Mom_diff_Theta',
2651 'SVD_CDC_CDCwall_Mom_diff_Phi',
2652 'SVD_CDC_CDCwall_Mom_diff_Mag',
2653 'SVD_CDC_CDCwall_Mom_diff_Eta',
2654 'SVD_CDC_POCA_Pos_diff_Z',
2655 'SVD_CDC_POCA_Pos_diff_Pt',
2656 'SVD_CDC_POCA_Pos_diff_Theta',
2657 'SVD_CDC_POCA_Pos_diff_Phi',
2658 'SVD_CDC_POCA_Pos_diff_Mag',
2659 'SVD_CDC_POCA_Pos_diff_Eta',
2660 'SVD_CDC_POCA_Mom_diff_Z',
2661 'SVD_CDC_POCA_Mom_diff_Pt',
2662 'SVD_CDC_POCA_Mom_diff_Theta',
2663 'SVD_CDC_POCA_Mom_diff_Phi',
2664 'SVD_CDC_POCA_Mom_diff_Mag',
2665 'SVD_CDC_POCA_Mom_diff_Eta',
2666 'POCA_Pos_Pt',
2667 'POCA_Pos_Mag',
2668 'POCA_Pos_Phi',
2669 'POCA_Pos_Z',
2670 'POCA_Pos_Theta',
2671 'PXD_QI',
2672 'SVD_FitSuccessful',
2673 'CDC_FitSuccessful',
2674 'pdg_ID',
2675 'pdg_ID_Mother',
2676 'is_Vzero_Daughter',
2677 'is_Primary',
2678 'z0',
2679 'd0',
2680 'seed_Charge',
2681 'Fit_Charge',
2682 'weight_max',
2683 'weight_min',
2684 'weight_mean',
2685 'weight_std',
2686 'weight_median',
2687 'weight_n_zeros',
2688 'weight_firstCDCHit',
2689 'weight_lastSVDHit',
2690 'smoothedChi2_max',
2691 'smoothedChi2_min',
2692 'smoothedChi2_mean',
2693 'smoothedChi2_std',
2694 'smoothedChi2_median',
2695 'smoothedChi2_n_zeros',
2696 'smoothedChi2_firstCDCHit',
2697 'smoothedChi2_lastSVDHit',
2698 'SVD_QI'] + \
2699 ["SVD_" + x for x in exclude_variables_vxd] + \
2700 ["SVDbefore_" + x for x in exclude_variables_vxd]
2701
2702 def requires(self):
2703 """
2704 Generate list of tasks that needs to be done for luigi to finish running
2705 this steering file.
2706 """
2707 cdc_training_targets = [
2708 "truth", # treats clones as background, only best matched CDC tracks are true
2709 # "truth_track_is_matched" # treats clones as signal
2710 ]
2711
2712 fast_bdt_options = []
2713 # possible to run over a chosen hyperparameter space if wanted
2714 # in principle this can be extended to specific options for the three different MVAs
2715 # for i in range(250, 400, 50):
2716 # for j in range(6, 10, 2):
2717 # for k in range(2, 6):
2718 # for l in range(0, 5):
2719 # fast_bdt_options.append([100 + i, j, 3+k, 0.025+l*0.025])
2720 # fast_bdt_options.append([200, 8, 3, 0.1]) # default FastBDT option
2721 fast_bdt_options.append([350, 6, 5, 0.1])
2722
2723 experiment_numbers = b2luigi.get_setting("experiment_numbers")
2724
2725 # iterate over all possible combinations of parameters from the above defined parameter lists
2726 for experiment_number, cdc_training_target, fast_bdt_option in itertools.product(
2727 experiment_numbers, cdc_training_targets, fast_bdt_options
2728 ):
2729 # if test_selected_task is activated, only run the following tasks:
2730 if b2luigi.get_setting("test_selected_task", default=False):
2731 # for process_type in ['BHABHA', 'MUMU', 'TAUPAIR', 'YY', 'EEEE', 'EEMUMU', 'UUBAR', \
2732 # 'DDBAR', 'CCBAR', 'SSBAR', 'BBBAR', 'V0BBBAR', 'V0STUDY']:
2733 for cut in ['000', '070', '090', '095']:
2735 num_processes=self.num_processes,
2736 n_events=self.n_events_testing,
2737 experiment_number=experiment_number,
2738 random_seed=self.process_type + '_test',
2739 recotrack_option='useCDC_useVXD_deleteCDCQI'+cut,
2740 cdc_training_target=cdc_training_target,
2741 fast_bdt_option=fast_bdt_option,
2742 )
2744 num_processes=self.num_processes,
2745 n_events=self.n_events_testing,
2746 experiment_number=experiment_number,
2747 random_seed=self.process_type + '_test',
2748 )
2749 yield CDCQETeacherTask(
2750 n_events_training=self.n_events_training,
2751 process_type=self.process_type,
2752 experiment_number=experiment_number,
2753 exclude_variables=self.exclude_variables_cdc,
2754 training_target=cdc_training_target,
2755 fast_bdt_option=fast_bdt_option,
2756 )
2757 else:
2758 # if data shall be processed, it can neither be trained nor evaluated
2759 if 'DATA' in self.process_type:
2761 num_processes=self.num_processes,
2762 n_events=self.n_events_testing,
2763 experiment_number=experiment_number,
2764 random_seed=self.process_type + '_test',
2765 )
2767 num_processes=self.num_processes,
2768 n_events=self.n_events_testing,
2769 experiment_number=experiment_number,
2770 random_seed=self.process_type + '_test',
2771 )
2773 num_processes=self.num_processes,
2774 n_events=self.n_events_testing,
2775 experiment_number=experiment_number,
2776 random_seed=self.process_type + '_test',
2777 recotrack_option='deleteCDCQI080',
2778 cdc_training_target=cdc_training_target,
2779 fast_bdt_option=fast_bdt_option,
2780 )
2781 else:
2782
2783 if b2luigi.get_setting("run_validation_tasks", default=True):
2785 n_events_training=self.n_events_training,
2786 n_events_testing=self.n_events_testing,
2787 process_type=self.process_type,
2788 experiment_number=experiment_number,
2789 cdc_training_target=cdc_training_target,
2790 exclude_variables=self.exclude_variables_rec,
2791 fast_bdt_option=fast_bdt_option,
2792 )
2794 n_events_training=self.n_events_training,
2795 n_events_testing=self.n_events_testing,
2796 process_type=self.process_type,
2797 experiment_number=experiment_number,
2798 exclude_variables=self.exclude_variables_cdc,
2799 training_target=cdc_training_target,
2800 fast_bdt_option=fast_bdt_option,
2801 )
2803 n_events_training=self.n_events_training,
2804 n_events_testing=self.n_events_testing,
2805 process_type=self.process_type,
2806 exclude_variables=self.exclude_variables_vxd,
2807 experiment_number=experiment_number,
2808 fast_bdt_option=fast_bdt_option,
2809 )
2810
2811 if b2luigi.get_setting("run_mva_evaluate", default=True):
2812 # Evaluate trained weightfiles via basf2_mva_evaluate.py on separate testdatasets
2813 # requires a latex installation to work
2815 n_events_training=self.n_events_training,
2816 n_events_testing=self.n_events_testing,
2817 process_type=self.process_type,
2818 experiment_number=experiment_number,
2819 cdc_training_target=cdc_training_target,
2820 exclude_variables=self.exclude_variables_rec,
2821 fast_bdt_option=fast_bdt_option,
2822 )
2824 n_events_training=self.n_events_training,
2825 n_events_testing=self.n_events_testing,
2826 process_type=self.process_type,
2827 experiment_number=experiment_number,
2828 exclude_variables=self.exclude_variables_cdc,
2829 fast_bdt_option=fast_bdt_option,
2830 training_target=cdc_training_target,
2831 )
2833 n_events_training=self.n_events_training,
2834 n_events_testing=self.n_events_testing,
2835 process_type=self.process_type,
2836 experiment_number=experiment_number,
2837 exclude_variables=self.exclude_variables_vxd,
2838 fast_bdt_option=fast_bdt_option,
2839 )
2840
2841
2842if __name__ == "__main__":
2843 # if n_events_test_on_data is specified to be different from -1 in the settings,
2844 # then stop after N events (mainly useful to test data reconstruction):
2845 nEventsTestOnData = b2luigi.get_setting("n_events_test_on_data", default=-1)
2846 if nEventsTestOnData > 0 and 'DATA' in b2luigi.get_setting("process_type", default="BBBAR"):
2847 from ROOT import Belle2
2848 environment = Belle2.Environment.Instance()
2849 environment.setNumberEventsOverride(nEventsTestOnData)
2850 # if global tags are specified in the settings, use them:
2851 # e.g. for data use ["data_reprocessing_prompt", "online"]. Make sure to be up to date here
2852 globaltags = b2luigi.get_setting("globaltags", default=[])
2853 if len(globaltags) > 0:
2854 basf2.conditions.reset()
2855 for gt in globaltags:
2856 basf2.conditions.prepend_globaltag(gt)
2857 workers = b2luigi.get_setting("workers", default=1)
2858 b2luigi.process(MasterTask(), workers=workers)
get_background_files(folder=None, output_file_info=True)
Definition background.py:77
static Environment & Instance()
Static method to get a reference to the Environment instance.
experiment_number
Experiment number of the conditions database, e.g.
get_records_file_name(self, n_events=None, random_seed=None)
Filename of the recorded/collected data for the final QE MVA training.
training_target
Feature/variable to use as truth label in the quality estimator MVA classifier.
training_target
Feature/variable to use as truth label in the quality estimator MVA classifier.
experiment_number
Experiment number of the conditions database, e.g.
output_file_name(self, n_events=None, random_seed=None)
Name of the ROOT output file with generated and simulated events.
str reco_output_file_name
Name of the output of the RootOutput module with reconstructed events.
str cdc_training_target
Feature/variable to use as truth label for the CDC track quality estimator.
# USESIM and USEREC process_type
Define which kind of process shall be used.
n_events_training
Number of events to generate for the training data set.
str validation_output_file_name
Name of the "harvested" ROOT output file with variables that can be used for validation.
exclude_variables
List of collected variables to not use in the training of the QE MVA classifier.
list exclude_variables_rec
list of variables to exclude for the recotrack mva:
list exclude_variables_vxd
list of variables to exclude for the vxd mva:
n_events_training
Number of events to generate for the training data set.
n_events_testing
Number of events to generate for the test data set.
list exclude_variables_cdc
list of variables to exclude for the cdc mva.
num_processes
Number of basf2 processes to use in Basf2PathTasks.
str cdc_training_target
Feature/variable to use as truth label for the CDC track quality estimator.
primaries_only
Whether to normalize the track finding efficiencies to primary particles only.
exclude_variables
List of collected variables to not use in the training of the QE MVA classifier.
output_pdf_file_basename
Name of the output PDF file containing the validation plots.
cdc_training_target
Feature/variable to use as truth label for the CDC track quality estimator.
get_records_file_name(self, n_events=None, random_seed=None, recotrack_option=None)
Filename of the recorded/collected data for the final QE MVA training.
recotrack_option
RecoTrack option, use string that is additive: deleteCDCQI0XY (= deletes CDCTracks with CDC-QI below ...
cdc_training_target
Feature/variable to use as truth label for the CDC track quality estimator.
cdc_training_target
Feature/variable to use as truth label for the CDC track quality estimator.
experiment_number
Experiment number of the conditions database, e.g.
output_file_name(self, n_events=None, random_seed=None)
Name of the ROOT output file with generated and simulated events.
n_events_training
Number of events to generate for the training data set.
exclude_variables
List of collected variables to not use in the training of the QE MVA classifier.
training_target
Feature/variable to use as truth label in the quality estimator MVA classifier.
teacher_task
Task that is required by the evaluation base class to create the MVA weightfile that needs to be eval...
experiment_number
Experiment number of the conditions database, e.g.
recotrack_option
RecoTrack option, use string that is additive: deleteCDCQI0XY (= deletes CDCTracks with CDC-QI below ...
n_events_training
Number of events to generate for the training data set.
exclude_variables
List of collected variables to not use in the training of the QE MVA classifier.
training_target
Feature/variable to use as truth label in the quality estimator MVA classifier.
get_weightfile_identifier(self, fast_bdt_option=None, recotrack_option=None)
experiment_number
Experiment number of the conditions database, e.g.
get_records_file_name(self, n_events=None, random_seed=None)
Filename of the recorded/collected data for the final QE MVA training.
add_simulation(path, components=None, bkgfiles=None, bkgOverlay=True, forceSetPXDDataReduction=False, usePXDDataReduction=True, cleanupPXDDataReduction=True, generate_2nd_cdc_hits=False, simulateT0jitter=True, isCosmics=False, FilterEvents=False, usePXDGatedMode=False, skipExperimentCheckForBG=False, ignoreRunNumberForBG=False, save_slow_pions_in_mc=False, save_all_charged_particles_in_mc=False)