Belle II Software development
core.py
1#!/usr/bin/env python3
2
3
10
11"""
12The core module of the Belle II Analysis Software Framework.
13"""
14
15import sys as _sys
16import signal as _signal
17
18# now let's make sure we actually run in python 3
19if _sys.version_info[0] < 3:
20 print("basf2 requires python3. Please run the steering files using basf2 "
21 "(or python3), not python")
22 _sys.exit(1)
23
24# import to override print function
25from basf2 import _override_print # noqa
26
27# import the C++ library with the exported functions
28import pybasf2 # noqa
29# and also import all of them in current scope for ease of use
30from pybasf2 import logging
31from pybasf2 import * # noqa
32
33# make sure conditions objects are read only
34from basf2 import _constwrapper # noqa
35
36
37
38basf2label = 'basf2 (Belle II Analysis Software Framework)'
39
40basf2copyright = 'Copyright(C) 2010-2024 Members of the Belle II Collaboration'
41
42basf2license = '(See "basf2 --license" for more information.)'
43
44# -----------------------------------------------
45# Prepare basf2
46# -----------------------------------------------
47
48# Reset the signal handler to allow the framework execution
49# to be stopped with Ctrl-c (Python installs own handler)
50# This will again be replaced once process() is called.
51_signal.signal(_signal.SIGINT, _signal.SIG_DFL)
52
53
54def register_module(name_or_module, shared_lib_path=None, logLevel=None, debugLevel=None, **kwargs):
55 """
56 Register the module 'name' and return it (e.g. for adding to a path). This
57 function is intended to instantiate existing modules. To find out which
58 modules exist you can run :program:`basf2 -m` and to get details about the
59 parameters for each module you can use :program:`basf2 -m {modulename}`
60
61 Parameters can be passed directly to the module as keyword parameters or can
62 be set later using `Module.param`
63
64 >>> module = basf2.register_module('EventInfoSetter', evtNumList=100, logLevel=LogLevel.ERROR)
65 >>> module.param("evtNumList", 100)
66
67 Parameters:
68 name_or_module: The name of the module type, may also be an existing
69 `Module` instance for which parameters should be set
70 shared_lib_path (str): An optional path to a shared library from which the
71 module should be loaded
72 logLevel (LogLevel): indicates the minimum severity of log messages
73 to be shown from this module. See `Module.set_log_level`
74 debugLevel (int): Number indicating the detail of debug messages, the
75 default level is 100. See `Module.set_debug_level`
76 kwargs: Additional parameters to be passed to the module.
77
78 Note:
79 You can also use `Path.add_module()` directly,
80 which accepts the same name, logging and module parameter arguments. There
81 is no need to register the module by hand if you will add it to the path in
82 any case.
83 """
84
85 if isinstance(name_or_module, pybasf2.Module):
86 module = name_or_module
87 else:
88 module_name = name_or_module
89 if shared_lib_path is not None:
90 module = pybasf2._register_module(module_name, shared_lib_path)
91 else:
92 module = pybasf2._register_module(module_name)
93
94 if kwargs:
95 module.param(kwargs)
96 if logLevel is not None:
97 module.set_log_level(logLevel)
98 if debugLevel is not None:
99 module.set_debug_level(debugLevel)
100
101 return module
102
103
104def set_module_parameters(path, name=None, type=None, recursive=False, **kwargs):
105 """Set the given set of parameters for all `modules <Module>` in a path which
106 have the given ``name`` (see `Module.set_name`)
107
108 Usage is similar to `register_module()` but this function will not create
109 new modules but just adjust parameters for modules already in a `Path`
110
111 >>> set_module_parameters(path, "Geometry", components=["PXD"], logLevel=LogLevel.WARNING)
112
113 Parameters:
114 path (basf2.Path): The path to search for the modules
115 name (str): Then name of the module to set parameters for
116 type (str): The type of the module to set parameters for.
117 recursive (bool): if True also look in paths connected by conditions or `Path.for_each()`
118 kwargs: Named parameters to be set for the module, see `register_module()`
119 """
120
121 if name is None and type is None:
122 raise ValueError("At least one of name or type has to be given")
123
124 if not kwargs:
125 raise ValueError("no module parameters given")
126
127 found = False
128 for module in path.modules():
129 if (name is None or module.name() == name) and (type is None or module.type() == type):
130 # use register_module as this automatically takes care of logLevel
131 # and debugLevel parameters
132 register_module(module, **kwargs)
133 found = True
134
135 if recursive:
136 if module.has_condition():
137 for condition_path in module.get_all_condition_paths():
138 set_module_parameters(condition_path, name, type, recursive, **kwargs)
139 if module.type() == "SubEvent":
140 for subpath in [p.values for p in module.available_params() if p.name == "path"]:
141 set_module_parameters(subpath, name, type, recursive, **kwargs)
142
143 if not found:
144 raise KeyError("No module with given name found anywhere in the path")
145
146
147def remove_module(old_path, name=None):
148 """Provides a new path with all modules that were in the ``old_path`` \
149 except the one with the given ``name`` (see `Module.set_name`)
150
151 Usage is very simple, in this example we remove Geometry the path:
152
153 >>> main = remove_module(main, "Geometry")
154
155 Parameters:
156 old_path (basf2.Path): The path to search for the module
157 name (str): Then name of the module you want to remove
158 """
159
160 if name is None:
161 raise ValueError("You should provide the module name")
162
163 new_path = create_path()
164
165 for module in old_path.modules():
166 if name != module.name():
167 new_path.add_module(module)
168
169 return new_path
170
171
172def create_path():
173 """
174 Creates a new path and returns it. You can also instantiate `basf2.Path` directly.
175 """
176 return pybasf2.Path()
177
178
179def process(path, max_event=0):
180 """
181 Start processing events using the modules in the given `basf2.Path` object.
182
183 Can be called multiple times in one steering file (some restrictions apply:
184 modules need to perform proper cleanup & reinitialisation, if Geometry is
185 involved this might be difficult to achieve.)
186
187 When used in a Jupyter notebook this function will automatically print a
188 nice progress bar and display the log messages in an advanced way once the
189 processing is complete.
190
191 Note:
192 This also means that in a Jupyter Notebook, modifications to class members or global variables will not be visible after processing is complete as
193 the processing is performed in a subprocess.
194
195 To restore the old behavior you can use ``basf2.core.process()`` which
196 will behave exactly identical in Jupyter notebooks as it does in normal
197 python scripts ::
198
199 from basf2 import core
200 core.process(path)
201
202
203 Parameters:
204 path: The path with which the processing starts
205 max_event: The maximal number of events which will be processed,
206 0 for no limit
207
208 .. versionchanged:: release-03-00-00
209 automatic Jupyter integration
210 """
211
212 # if we are running in an ipython session set the steering file to the
213 # current history
214 try:
215 ipython = get_ipython() # noqa
216 history = "\n".join(e[2] for e in ipython.history_manager.get_range())
217 from ROOT import Belle2
218 Belle2.Environment.Instance().setSteering(history)
219 except NameError:
220 pass
221
222 # If a pickle path is set via --dump-path or --execute-path we do something special
223 if pybasf2.get_pickle_path() != "":
224 from basf2.pickle_path import check_pickle_path
225 path = check_pickle_path(path)
226
227 # apparently nothing to do
228 if path is None:
229 return
230
231 pybasf2.B2INFO("Starting event processing, random seed is set to '" + pybasf2.get_random_seed() + "'")
232
233 if max_event != 0:
234 pybasf2._process(path, max_event)
235 else:
236 pybasf2._process(path)
237
238
239def set_log_level(level):
240 """
241 Sets the global log level which specifies up to which level the
242 logging messages will be shown
243
244 Parameters:
245 level (basf2.LogLevel): minimum severity of messages to be logged
246 """
247
248 logging.log_level = level
249
250
251def set_debug_level(level):
252 """
253 Sets the global debug level which specifies up to which level the
254 debug messages should be shown
255
256 Parameters:
257 level (int): The debug level. The default value is 100
258 """
259
260 logging.debug_level = level
261
262
263def log_to_console(color=False):
264 """
265 Adds the standard output stream to the list of logging destinations.
266 The shell logging destination is
267 added to the list by the framework by default.
268 """
269
270 logging.add_console(color)
271
272
273def log_to_file(filename, append=False):
274 """
275 Adds a text file to the list of logging destinations.
276
277 Parameters:
278 filename: The path and filename of the text file
279 append: Should the logging system append the messages to the end of the
280 file (True) or create a new file for each event processing session (False).
281 Default is False.
282 """
283
284 logging.add_file(filename, append)
285
286
287def reset_log():
288 """
289 Resets the logging by removing all logging destinations
290 """
291
292 logging.reset()
293
294
295def _add_module(self, module, logLevel=None, debugLevel=None, **kwargs):
296 """
297 Add given module (either object or name) at the end of this path.
298 All unknown arguments are passed as module parameters.
299
300 >>> path = create_path()
301 >>> path.add_module('EventInfoSetter', evtNumList=100, logLevel=LogLevel.ERROR)
302 <pybasf2.Module at 0x1e356e0>
303
304 >>> path = create_path()
305 >>> eventinfosetter = register_module('EventInfoSetter')
306 >>> path.add_module(eventinfosetter)
307 <pybasf2.Module at 0x2289de8>
308 """
309 module = register_module(module, logLevel=logLevel, debugLevel=debugLevel, **kwargs)
310 self._add_module_object(module)
311 return module
312
313
314def _add_independent_path(self, skim_path, ds_ID='', merge_back_event=None):
315 """
316 Add given path at the end of this path and ensure all modules there
317 do not influence the main DataStore. You can thus use modules in
318 skim_path to clean up e.g. the list of particles, save a skimmed uDST file,
319 and continue working with the unmodified DataStore contents outside of
320 skim_path.
321
322 Parameters:
323 ds_ID: can be specified to give a defined ID to the temporary DataStore,
324 otherwise, a random name will be generated.
325 merge_back_event: is a list of object/array names (of event durability)
326 that will be merged back into the main path.
327 """
328 if merge_back_event is None:
329 merge_back_event = []
330 self._add_independent_path(skim_path, ds_ID, merge_back_event)
331
332
333def _add_independent_merge_path(
334 self,
335 skim_path,
336 ds_ID='',
337 merge_back_event=None,
338 consistency_check=None,
339 event_mixing=False,
340 merge_same_file=False):
341 """
342 Merge specified content of DataStore of independent path into DataStore of main path
343 on a per event level (add tracks/cluster from both events,...).
344
345 Parameters:
346 skim_path: independent path to be merged
347 ds_ID: can be specified to give a defined ID to the temporary DataStore,
348 otherwise, a random name will be generated (option for developers).
349 merge_back_event: is a list of object/array names (of event durability)
350 that will be merged back into the main path.
351 consistency_check: perform additional consistency checks on the objects from two paths.
352 If they are not satisfied, the skim_path proceeds to the next event on the path.
353 Currently supported value is "charge" that uses EventExtraInfo "charge" of the two paths,
354 that must be specified by the user, ensuring correct configuration of the combined event.
355 See CheckMergingConsistencyModule for more details.
356 event_mixing: apply event mixing (merge each event from first path with each event of second path)
357 merge_same_file: merge events from single file (useful for mixing)
358 """
359 if merge_back_event is None:
360 merge_back_event = []
361 if consistency_check is None:
362 consistency_check = ""
363 if merge_same_file:
364 if not event_mixing:
365 pybasf2.B2INFO("add_independent_merge_path: merge_same_file requires event_mixing, setting it to True")
366 event_mixing = True
367 for module in skim_path.modules():
368 if module.type() == "RootInput":
369 module.param("isSecondaryInput", True)
370 self._add_independent_merge_path(skim_path, ds_ID, merge_back_event, consistency_check, event_mixing, merge_same_file)
371
372
373pybasf2.Path.add_module = _add_module
374pybasf2.Path.add_independent_path = _add_independent_path
375pybasf2.Path.add_independent_merge_path = _add_independent_merge_path
376
static Environment & Instance()
Static method to get a reference to the Environment instance.
Definition: Environment.cc:28
def process(path, max_event=0)
Definition: core.py:179