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