Belle II Software development
Path.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <boost/python/register_ptr_to_python.hpp>
10#include <boost/python/class.hpp>
11#include <boost/python/list.hpp>
12#include <boost/python/docstring_options.hpp>
13#include <utility>
14
15#include <framework/core/Path.h>
16#include <framework/core/Module.h>
17#include <framework/core/ModuleManager.h>
18#include <framework/core/SubEventModule.h>
19#include <framework/core/SwitchDataStoreModule.h>
20#include <framework/core/MergeDataStoreModule.h>
21#include <framework/core/SteerRootInputModule.h>
22#include <framework/core/CreateConsistencyInfoModule.h>
23#include <framework/core/PyObjConvUtils.h>
24
25using namespace Belle2;
26using namespace boost::python;
27
28Path::Path() = default;
29
30Path::~Path() = default;
31
32void Path::addModule(const ModulePtr& module)
33{
34 m_elements.push_back(module);
35}
36
37void Path::addPath(const PathPtr& path)
38{
39 if (path.get() == this) {
40 B2FATAL("Attempting to add a path to itself!");
41 }
42 m_elements.push_back(path);
43}
44
45bool Path::isEmpty() const
46{
47 return m_elements.empty();
48}
49
50std::list<ModulePtr> Path::getModules() const
51{
52 std::list<ModulePtr> modules;
53 for (const std::shared_ptr<PathElement>& elem : m_elements) {
54 if (dynamic_cast<Module*>(elem.get()) != nullptr) {
55 //this path element is a Module, create a ModulePtr that shares ownership with 'elem'
56 modules.push_back(std::static_pointer_cast<Module>(elem));
57 } else {
58 //some PathElement with submodules
59 const std::list<ModulePtr>& modulesInElem = elem->getModules();
60 modules.insert(modules.end(), modulesInElem.begin(), modulesInElem.end());
61 }
62 }
63 return modules;
64}
65
66
68{
69 ModulePtrList modList;
70 for (const ModulePtr& module : getModules()) {
71 if (!unique or find(modList.begin(), modList.end(), module) == modList.end()) {
72 modList.push_back(module);
73
74 //If the module has a condition, call the method recursively
75 if (module->hasCondition()) {
76 for (const auto& conditionPath : module->getAllConditionPaths()) {
77 // Avoid loops in path conditions
78 if (conditionPath.get() == this) B2FATAL("Found recursion in conditional path");
79 const std::list<ModulePtr>& modulesInElem = conditionPath->buildModulePathList(unique);
80 modList.insert(modList.end(), modulesInElem.begin(), modulesInElem.end());
81 }
82 }
83 }
84 }
85
86 return modList;
87}
88
89void Path::putModules(const std::list<std::shared_ptr<Module> >& mlist)
90{
91 m_elements.assign(mlist.begin(), mlist.end());
92}
93
94
95void Path::forEach(const std::string& loopObjectName, const std::string& arrayName, PathPtr path)
96{
98 static_cast<SubEventModule&>(*module).initSubEvent(loopObjectName, arrayName, std::move(path));
99 addModule(module);
100}
101
102void Path::doWhile(PathPtr path, const std::string& condition, unsigned int maxIterations)
103{
104 ModulePtr module = ModuleManager::Instance().registerModule("SubEvent");
105 static_cast<SubEventModule&>(*module).initSubLoop(std::move(path), condition, maxIterations);
106 addModule(module);
107}
108
109void Path::addIndependentPath(const PathPtr& independent_path, std::string ds_ID, const boost::python::list& merge_back)
110{
111 if (ds_ID.empty()) {
112 static int dscount = 1;
113 ds_ID = "DS " + std::to_string(dscount++);
114 }
115 auto mergeBack = PyObjConvUtils::convertPythonObject(merge_back, std::vector<std::string>());
116 ModulePtr switchStart = ModuleManager::Instance().registerModule("SwitchDataStore");
117 static_cast<SwitchDataStoreModule&>(*switchStart).init(ds_ID, true, mergeBack);
118 ModulePtr switchEnd = ModuleManager::Instance().registerModule("SwitchDataStore");
119 static_cast<SwitchDataStoreModule&>(*switchEnd).init("", false, mergeBack);
120 switchStart->setName("SwitchDataStore ('' -> '" + ds_ID + "')");
121 switchEnd->setName("SwitchDataStore ('' <- '" + ds_ID + "')");
122
123 //set c_ParallelProcessingCertified flag if _all_ modules have it set
126 switchStart->setPropertyFlags(flag);
127 switchEnd->setPropertyFlags(flag);
128 }
129
130 addModule(switchStart);
131 addPath(independent_path);
132 addModule(switchEnd);
133}
134
135void Path::addIndependentMergePath(const PathPtr& independent_path, std::string ds_ID, const boost::python::list& merge_back,
136 std::string consistency_check, bool event_mixing, bool merge_same_file)
137{
138 if (ds_ID.empty()) {
139 static int dscount = 1;
140 ds_ID = "DS " + std::to_string(dscount++);
141 }
142 auto mergeBack = PyObjConvUtils::convertPythonObject(merge_back, std::vector<std::string>());
143 ModulePtr switchStart = ModuleManager::Instance().registerModule("MergeDataStore");
144 static_cast<MergeDataStoreModule&>(*switchStart).init(ds_ID, true, mergeBack);
145 ModulePtr switchEnd = ModuleManager::Instance().registerModule("MergeDataStore");
146 static_cast<MergeDataStoreModule&>(*switchEnd).init("", false, mergeBack);
147 switchStart->setName("MergeDataStore ('' -> '" + ds_ID + "')");
148 switchEnd->setName("MergeDataStore ('' <- '" + ds_ID + "')");
149
150 ModulePtr fillConsistencyInfo = ModuleManager::Instance().registerModule("CreateConsistencyInfo");
151 static_cast<CreateConsistencyInfoModule&>(*fillConsistencyInfo).init(consistency_check, event_mixing);
152
153 ModulePtr steerInput = ModuleManager::Instance().registerModule("SteerRootInput");
154 static_cast<SteerRootInputModule&>(*steerInput).init(event_mixing, merge_same_file);
155
156 //set c_ParallelProcessingCertified flag if _all_ modules have it set
159 switchStart->setPropertyFlags(flag);
160 switchEnd->setPropertyFlags(flag);
161 }
162
163 // switch to the second (empty) data store
164 addModule(switchStart);
165 // execute independent path
166 addPath(independent_path);
167 // do the merging
168 addModule(switchEnd);
169 // check events to be merged is consistent (typically charge)
170 addModule(fillConsistencyInfo);
171 // decide which events have to be processed next
172 addModule(steerInput);
173 // the current combination of events might not be sensible or unphysical
174 // in this case end path and try next combination
175 steerInput->if_value("==0", std::make_shared<Path>());
176}
177
178bool Path::contains(const std::string& moduleType) const
179{
180 const std::list<ModulePtr>& modules = getModules();
181
182 auto sameTypeFunc = [moduleType](const ModulePtr & m) -> bool { return m->getType() == moduleType; };
183 return std::find_if(modules.begin(), modules.end(), sameTypeFunc) != modules.end();
184}
185
186std::shared_ptr<PathElement> Path::clone() const
187{
188 PathPtr path(new Path);
189 for (const auto& elem : m_elements) {
190 const auto* m = dynamic_cast<const Module*>(elem.get());
191 if (m and m->getType() == "PyModule") {
192 //B2WARNING("Python module " << m->getName() << " encountered, please make sure it correctly reinitialises itself to ensure multiple process() calls work.");
193 path->addModule(std::static_pointer_cast<Module>(elem));
194 } else {
195 path->m_elements.push_back(elem->clone());
196 }
197 }
198 return path;
199}
200
201
202//=====================================================================
203// Python API
204//=====================================================================
205
206std::string Path::getPathString() const
207{
208 std::string out("");
209 bool firstElem = true;
210 for (const std::shared_ptr<PathElement>& elem : m_elements) {
211 if (!firstElem) {
212 out += " -> ";
213 } else {
214 firstElem = false;
215 }
216
217 out += elem->getPathString();
218 }
219 return "[" + out + "]";
220}
221
222
223namespace {
227 boost::python::list _getModulesPython(const Path* path)
228 {
229 boost::python::list returnList;
230
231 for (const ModulePtr& module : path->getModules())
232 returnList.append(boost::python::object(ModulePtr(module)));
233
234 return returnList;
235 }
236}
237
239{
240 docstring_options options(true, false, false); //userdef, py sigs, c++ sigs
241 using bparg = boost::python::arg;
242
243 class_<Path>("Path",
244 R"(Implements a path consisting of Module and/or Path objects (arranged in a linear order).
245
246.. seealso:: :func:`basf2.process`)")
247 .def("__str__", &Path::getPathString)
248 .def("_add_module_object", &Path::addModule) // actual add_module() is found in basf2.py
249 .def("add_path", &Path::addPath, args("path"), R"(add_path(path)
250
251Insert another path at the end of this one.
252For example,
253
254 >>> path.add_module('A')
255 >>> path.add_path(otherPath)
256 >>> path.add_module('B')
257
258would create a path [ A -> [ contents of otherPath ] -> B ].)
259
260Parameters:
261 path (Path): path to add to this path)")
262 .def("modules", &_getModulesPython, R"(modules()
263
264Returns an ordered list of all modules in this path.)")
265 .def("for_each", &Path::forEach, R"(for_each(loop_object_name, array_name, path)
266
267Similar to `add_path()`, this will
268execute the given ``path`` at the current position, but in each event it will
269execute it once for each object in the given StoreArray ``arrayName``. It will
270create a StoreObject named ``loop_object_name`` of same type as array which will
271point to each element in turn for each execution.
272
273This has the effect of calling the ``event()`` methods of modules in ``path``
274for each entry in ``arrayName``.
275
276The main use case is to use it after using the `RestOfEventBuilder` on a
277``ParticeList``, where you can use this feature to perform actions on only a part
278of the event for a given list of candidates:
279
280 >>> path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
281
282You can read this as
283
284 "for each ``RestOfEvent`` in the array of "RestOfEvents", execute ``roe_path``"
285
286For example, if 'RestOfEvents' contains two elements then ``roe_path`` will be
287executed twice and during the execution a StoreObjectPtr 'RestOfEvent' will be
288available, which will point to the first element in the first execution, and
289the second element in the second execution.
290
291.. seealso::
292 A working example of this `for_each` RestOfEvent is to build a veto against
293 photons from :math:`\pi^0\to\gamma\gamma`. It is described in `HowToVeto`.
294
295.. note:: This feature is used by both the `FlavorTagger` and :ref:`FullEventInterpretation` algorithms.
296
297Changes to existing arrays / objects will be available to all modules after the
298`for_each()`, including those made to the loop object itself (it will simply modify
299the i'th item in the array looped over.)
300
301StoreArrays / StoreObjects (of event durability) *created* inside the loop will
302be removed at the end of each iteration. So if you create a new particle list
303inside a `for_each()` path execution the particle list will not exist for the
304next iteration or after the `for_each()` is complete.
305
306Parameters:
307 loop_object_name (str): The name of the object in the datastore during each execution
308 array_name (str): The name of the StoreArray to loop over where the i-th
309 element will be available as ``loop_object_name`` during the i-th execution
310 of ``path``
311 path (basf2.Path): The path to execute for each element in ``array_name``)",
312 args("loop_object_name", "array_name", "path"))
313 .def("do_while", &Path::doWhile, R"(do_while(path, condition='<1', max_iterations=10000)
314
315Similar to `add_path()` this will execute a path at the current position but it
316will repeat execution of this path as long as the return value of the last
317module in the path fulfills the given ``condition``.
318
319This is useful for event generation with special cuts like inclusive particle generation.
320
321See Also:
322 `Module.if_value` for an explanation of the condition expression.
323
324Parameters:
325 path (basf2.Path): sub path to execute repeatedly
326 condition (str): condition on the return value of the last module in ``path``.
327 The execution will be repeated as long as this condition is fulfilled.
328 max_iterations (int): Maximum number of iterations per event. If this number is exceeded
329 the execution is aborted.
330 )", (bparg("path"), bparg("condition") = "<1", bparg("max_iterations") = 10000))
331 .def("_add_independent_path", &Path::addIndependentPath)
332 .def("_add_independent_merge_path", &Path::addIndependentMergePath)
333 .def("__contains__", &Path::contains, R"(Does this Path contain a module of the given type?
334
335 >>> path = basf2.Path()
336 >>> 'RootInput' in path
337 False
338 >>> path.add_module('RootInput')
339 >>> 'RootInput' in path
340 True)", args("moduleType"))
341 ;
342
343 register_ptr_to_python<PathPtr>();
344}
If you want to merge two events with the 'MergeDataStoreModule', it might be necessary to make sure t...
void init(const std::string &option, bool eventMixing)
setter for Path.
Internal module used by Path.add_independent_merge_path().
void init(const std::string &to, bool doCopy, const std::vector< std::string > &mergeBack)
setter for Path.
std::shared_ptr< Module > registerModule(const std::string &moduleName, std::string sharedLibPath="") noexcept(false)
Creates an instance of a module and registers it to the ModuleManager.
static ModuleManager & Instance()
Exception is thrown if the requested module could not be created by the ModuleManager.
static bool allModulesHaveFlag(const std::list< std::shared_ptr< Module > > &list, unsigned int flag)
Returns true if and only if all modules in list have the given flag (or list is empty).
Base class for Modules.
Definition: Module.h:72
@ c_ParallelProcessingCertified
This module can be run in parallel processing mode safely (All I/O must be done through the data stor...
Definition: Module.h:80
Implements a path consisting of Module and/or Path objects.
Definition: Path.h:38
void addIndependentMergePath(const PathPtr &independent_path, std::string ds_ID, const boost::python::list &merge_back, std::string consistency_check, bool event_mixing, bool mergeSameFile)
See 'pydoc3 basf2.Path'.
Definition: Path.cc:135
void forEach(const std::string &loopObjectName, const std::string &arrayName, PathPtr path)
See 'pydoc3 basf2.Path'.
Definition: Path.cc:95
std::list< std::shared_ptr< PathElement > > m_elements
The list of path elements (Modules and sub-Paths)
Definition: Path.h:137
void doWhile(PathPtr path, const std::string &condition, unsigned int maxIterations)
See 'pydoc3 basf2.Path'.
Definition: Path.cc:102
std::list< std::shared_ptr< Module > > buildModulePathList(bool unique=true) const
Builds a list of all modules which could be executed during the data processing.
Definition: Path.cc:67
static void exposePythonAPI()
Exposes methods of the Path class to Python.
Definition: Path.cc:238
void putModules(const std::list< std::shared_ptr< Module > > &mlist)
Replaces all Modules and sub-Paths with the specified Module list.
Definition: Path.cc:89
Path()
Constructor.
void addPath(const PathPtr &path)
See 'pydoc3 basf2.Path'.
Definition: Path.cc:37
void addModule(const std::shared_ptr< Module > &module)
Adds a module to the path.
Definition: Path.cc:32
bool isEmpty() const
Returns true if this Path doesn't contain any elements.
Definition: Path.cc:45
~Path()
Destructor.
std::shared_ptr< PathElement > clone() const override
Create an independent copy of this path, recreating all contained modules with the same parameters.
Definition: Path.cc:186
void addIndependentPath(const PathPtr &independent_path, std::string ds_ID, const boost::python::list &merge_back)
See 'pydoc3 basf2.Path'.
Definition: Path.cc:109
bool contains(const std::string &moduleType) const
Does this Path contain a module of the given type?
Definition: Path.cc:178
std::list< std::shared_ptr< Module > > getModules() const override
Returns a list of the modules in this path.
Definition: Path.cc:50
std::string getPathString() const override
return a string of the form [module a -> module b -> [another path]]
Definition: Path.cc:206
Internal module used by Path.add_independent_merge_path().
void init(bool eventMixing, bool mergeSameFile)
setter for Path.
Framework-internal module that implements the functionality of Path::forEach() as well as Path::doWhi...
void initSubLoop(std::shared_ptr< Path > path, const std::string &condition, unsigned int maxIterations)
ised by Path::doWhile() to actually set parameters
void initSubEvent(const std::string &objectName, const std::string &loopOver, std::shared_ptr< Path > path)
used by Path::forEach() to actually set parameters.
Internal module used by Path.add_independent_path().
void init(const std::string &to, bool doCopy, const std::vector< std::string > &mergeBack)
setter for Path.
std::shared_ptr< Path > PathPtr
Defines a pointer to a path object as a boost shared pointer.
Definition: Path.h:35
std::shared_ptr< Module > ModulePtr
Defines a pointer to a module object as a boost shared pointer.
Definition: Module.h:43
std::list< ModulePtr > ModulePtrList
Defines a std::list of shared module pointers.
Definition: Module.h:584
Scalar convertPythonObject(const boost::python::object &pyObject, Scalar)
Convert from Python to given type.
Abstract base class for different kinds of events.