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