Belle II Software  release-08-01-10
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 
25 using namespace Belle2;
26 using namespace boost::python;
27 
28 Path::Path() = default;
29 
30 Path::~Path() = default;
31 
32 void Path::addModule(const ModulePtr& module)
33 {
34  m_elements.push_back(module);
35 }
36 
37 void 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 
45 bool Path::isEmpty() const
46 {
47  return m_elements.empty();
48 }
49 
50 std::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 
89 void Path::putModules(const std::list<std::shared_ptr<Module> >& mlist)
90 {
91  m_elements.assign(mlist.begin(), mlist.end());
92 }
93 
94 
95 void Path::forEach(const std::string& loopObjectName, const std::string& arrayName, PathPtr path)
96 {
97  ModulePtr module = ModuleManager::Instance().registerModule("SubEvent");
98  static_cast<SubEventModule&>(*module).initSubEvent(loopObjectName, arrayName, std::move(path));
99  addModule(module);
100 }
101 
102 void 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 
109 void 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
125  if (ModuleManager::allModulesHaveFlag(buildModulePathList(), flag)) {
126  switchStart->setPropertyFlags(flag);
127  switchEnd->setPropertyFlags(flag);
128  }
129 
130  addModule(switchStart);
131  addPath(independent_path);
132  addModule(switchEnd);
133 }
134 
135 void 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
158  if (ModuleManager::allModulesHaveFlag(buildModulePathList(), flag)) {
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 
178 bool 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 
186 std::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 
206 std::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 
223 namespace {
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 
251 Insert another path at the end of this one.
252 For example,
253 
254  >>> path.add_module('A')
255  >>> path.add_path(otherPath)
256  >>> path.add_module('B')
257 
258 would create a path [ A -> [ contents of otherPath ] -> B ].)
259 
260 Parameters:
261  path (Path): path to add to this path)")
262  .def("modules", &_getModulesPython, R"(modules()
263 
264 Returns 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 
267 Similar to `add_path()`, this will
268 execute the given ``path`` at the current position, but in each event it will
269 execute it once for each object in the given StoreArray ``arrayName``. It will
270 create a StoreObject named ``loop_object_name`` of same type as array which will
271 point to each element in turn for each execution.
272 
273 This has the effect of calling the ``event()`` methods of modules in ``path``
274 for each entry in ``arrayName``.
275 
276 The 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
278 of the event for a given list of candidates:
279 
280  >>> path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
281 
282 You can read this as
283 
284  "for each ``RestOfEvent`` in the array of "RestOfEvents", execute ``roe_path``"
285 
286 For example, if 'RestOfEvents' contains two elements then ``roe_path`` will be
287 executed twice and during the execution a StoreObjectPtr 'RestOfEvent' will be
288 available, which will point to the first element in the first execution, and
289 the 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 
297 Changes 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
299 the i'th item in the array looped over.)
300 
301 StoreArrays / StoreObjects (of event durability) *created* inside the loop will
302 be removed at the end of each iteration. So if you create a new particle list
303 inside a `for_each()` path execution the particle list will not exist for the
304 next iteration or after the `for_each()` is complete.
305 
306 Parameters:
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 
315 Similar to `add_path()` this will execute a path at the current position but it
316 will repeat execution of this path as long as the return value of the last
317 module in the path fulfills the given ``condition``.
318 
319 This is useful for event generation with special cuts like inclusive particle generation.
320 
321 See Also:
322  `Module.if_value` for an explanation of the condition expression.
323 
324 Parameters:
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 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).
static ModuleManager & Instance()
Exception is thrown if the requested module could not be created by the ModuleManager.
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
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:28
std::shared_ptr< Module > ModulePtr
Defines a pointer to a module object as a boost shared pointer.
Definition: Module.h:40
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.