Nektar++
Python/Module.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: Module.cpp
4 //
5 // For more information, please see: http://www.nektar.info
6 //
7 // The MIT License
8 //
9 // Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10 // Department of Aeronautics, Imperial College London (UK), and Scientific
11 // Computing and Imaging Institute, University of Utah (USA).
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining a
14 // copy of this software and associated documentation files (the "Software"),
15 // to deal in the Software without restriction, including without limitation
16 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 // and/or sell copies of the Software, and to permit persons to whom the
18 // Software is furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be included
21 // in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 // DEALINGS IN THE SOFTWARE.
30 //
31 // Description: Python wrapper for Module.
32 //
33 ///////////////////////////////////////////////////////////////////////////////
34 
35 #include <FieldUtils/Module.h>
37 #include <boost/program_options.hpp>
38 #include <boost/python/raw_function.hpp>
39 
40 using namespace Nektar;
41 using namespace Nektar::FieldUtils;
42 
43 /**
44  * @brief Module wrapper to handle virtual function calls in @c Module and its
45  * subclasses as defined by the template parameter @tparam MODTYPE.
46  */
47 template <class MODTYPE>
48 struct ModuleWrap : public MODTYPE, public py::wrapper<MODTYPE>
49 {
50  /**
51  * @brief Constructor, which is identical to FieldUtils::Module.
52  *
53  * @param field Input field.
54  */
55  ModuleWrap(FieldSharedPtr field) : MODTYPE(field), py::wrapper<MODTYPE>()
56  {
57  }
58 
59  /**
60  * @brief Concrete implementation of the Module::Process function.
61  */
62  void v_Process(po::variables_map &vm) override
63  {
64  boost::ignore_unused(vm);
65  this->get_override("Process")();
66  }
67 
68  std::string v_GetModuleName() override
69  {
70  py::override f = this->get_override("GetModuleName");
71  return py::call<std::string>(f.ptr());
72  }
73 
75  {
76  py::override f = this->get_override("GetModulePriority");
77  return py::call<ModulePriority>(f.ptr());
78  }
79 
80  /**
81  * @brief Defines a configuration option for this module.
82  *
83  * @param key The name of the configuration option.
84  * @param def The option's default value.
85  * @param desc A text description of the option.
86  * @param isBool If true, this option is a boolean-type (true/false).
87  */
88  void AddConfigOption(std::string key, std::string def, std::string desc,
89  bool isBool)
90  {
91  ConfigOption conf(isBool, def, desc);
92  this->m_config[key] = conf;
93  }
94 
95  // We expose Module::m_f as a public member variable so that we can
96  // adjust this using Python attributes.
97  using MODTYPE::m_f;
98 };
99 
100 // Wrapper around Module::Process(&vm).
101 // Performs switching of m_comm if nparts > 1.
103 {
104  if (m->m_f->m_nParts > 1)
105  {
106  if (m->GetModulePriority() == eOutput)
107  {
108  m->m_f->m_comm = m->m_f->m_partComm;
109  if (m->GetModuleName() != "OutputInfo")
110  {
111  m->RegisterConfig("writemultiplefiles");
112  }
113  }
114  else if (m->GetModulePriority() == eCreateGraph)
115  {
116  m->m_f->m_comm = m->m_f->m_partComm;
117  }
118  else
119  {
120  m->m_f->m_comm = m->m_f->m_defComm;
121  }
122  }
123  m->SetDefaults();
124  m->Process(m->m_f->m_vm);
125 }
126 
127 template <typename T>
128 T Module_GetConfig(std::shared_ptr<Module> mod, const std::string &key)
129 {
130  return mod->GetConfigOption(key).as<T>();
131 }
132 
133 template <typename MODTYPE> struct ModuleTypeProxy
134 {
135 };
136 
137 template <> struct ModuleTypeProxy<InputModule>
138 {
139  static const ModuleType value = eInputModule;
140 };
141 
142 template <> struct ModuleTypeProxy<ProcessModule>
143 {
144  static const ModuleType value = eProcessModule;
145 };
146 
147 template <> struct ModuleTypeProxy<OutputModule>
148 {
149  static const ModuleType value = eOutputModule;
150 };
151 
155 
156 /**
157  * @brief Lightweight wrapper for Module factory creation function.
158  *
159  * @param modType Module type (input/process/output).
160  * @param modName Module name (typically filename extension).
161  * @param field Field that will be passed between modules.
162  * @tparam MODTYPE Subclass of Module (e.g #InputModule, #OutputModule)
163  */
164 template <typename MODTYPE>
165 ModuleSharedPtr Module_Create(py::tuple args, py::dict kwargs)
166 {
168 
170 
171  if (modType == eProcessModule && py::len(args) != 2)
172  {
173  throw NekError("ProcessModule.Create() requires two arguments: "
174  "module name and a Field object.");
175  }
176  else if (modType != eProcessModule && py::len(args) < 2)
177  {
178  throw NekError(ModuleTypeMap[modType] +
179  "Module.Create() requires "
180  "two arguments: module name and a Field object; "
181  "optionally a filename.");
182  }
183 
184  std::string modName = py::extract<std::string>(args[0]);
185  ModuleKey modKey = std::make_pair(modType, modName);
186 
187  if (!py::extract<FieldSharedPtr>(args[1]).check())
188  {
189  throw NekError("Second argument to Create() should be a Field object.");
190  }
191 
192  FieldSharedPtr field = py::extract<FieldSharedPtr>(args[1]);
193  ModuleSharedPtr mod = GetModuleFactory().CreateInstance(modKey, field);
194 
195  if (modType == eInputModule)
196  {
197  // For input modules we can try to interpret the remaining arguments as
198  // input files. Assume that the file's type is identical to the module
199  // name.
200  for (int i = 2; i < py::len(args); ++i)
201  {
202  std::string in_fname = py::extract<std::string>(args[i]);
203  mod->RegisterConfig("infile", in_fname);
204  mod->AddFile(modName, in_fname);
205  }
206  }
207  else if (modType == eOutputModule && py::len(args) >= 3)
208  {
209  // For output modules we can try to interpret the remaining argument as
210  // an output file.
211  mod->RegisterConfig("outfile", py::extract<std::string>(args[2]));
212  }
213 
214  // Process keyword arguments.
215  py::list items = kwargs.items();
216 
217  for (int i = 0; i < py::len(items); ++i)
218  {
219  std::string arg = py::extract<std::string>(items[i][0]);
220 
221  if (arg == "infile" && modKey.first == eInputModule)
222  {
223  py::extract<py::dict> dict_check(items[i][1]);
224 
225  if (!dict_check.check())
226  {
227  throw NekError("infile should be a dictionary.");
228  }
229 
230  py::dict ftype_fname_dict = py::extract<py::dict>(items[i][1]);
231  py::list ft_fn_items = ftype_fname_dict.items();
232  for (int i = 0; i < py::len(ft_fn_items); ++i)
233  {
234  std::string f_type =
235  py::extract<std::string>(ft_fn_items[i][0]);
236  std::string f_name = py::extract<std::string>(
237  ft_fn_items[i][1].attr("__str__")());
238  mod->RegisterConfig(arg, f_name);
239  mod->AddFile(f_type, f_name);
240  }
241  }
242  else
243  {
244  std::string val =
245  py::extract<std::string>(items[i][1].attr("__str__")());
246  mod->RegisterConfig(arg, val);
247  }
248  }
249 
250  mod->SetDefaults();
251 
252  return mod;
253 }
254 
255 /**
256  * @brief Lightweight wrapper for FieldUtils::Module::RegisterConfig.
257  *
258  * @param mod Module to call
259  * @param key Configuration key.
260  * @param value Optional value (some configuration options are boolean).
261  */
262 void Module_RegisterConfig(std::shared_ptr<Module> mod, std::string const &key,
263  std::string const &value)
264 {
265  mod->RegisterConfig(key, value);
266 }
267 
268 template <typename MODTYPE>
270  std::string const &key,
271  std::string const &defValue,
272  std::string const &desc, bool isBool)
273 {
274  mod->AddConfigOption(key, defValue, desc, isBool);
275 }
276 
278 {
279 public:
280  ModuleRegisterHelper(py::object obj) : m_obj(obj)
281  {
282  py::incref(obj.ptr());
283  }
284 
286  {
287  py::decref(m_obj.ptr());
288  }
289 
291  {
292  py::object inst = m_obj(field);
293  return py::extract<ModuleSharedPtr>(inst);
294  }
295 
296 protected:
297  py::object m_obj;
298 };
299 
300 #if PY_MAJOR_VERSION == 2
301 void ModuleCapsuleDestructor(void *ptr)
302 {
304  delete tmp;
305 }
306 #else
307 void ModuleCapsuleDestructor(PyObject *ptr)
308 {
309  ModuleRegisterHelper *tmp =
310  (ModuleRegisterHelper *)PyCapsule_GetPointer(ptr, 0);
311  delete tmp;
312 }
313 #endif
314 
315 /**
316  * @brief Lightweight wrapper for the Module factory RegisterCreatorFunction, to
317  * support the ability for Python subclasses of Module to register themselves
318  * with the Nektar++ Module factory.
319  *
320  * This function wraps the NekFactory RegisterCreatorFunction. This function
321  * expects a function pointer to a C++ object that will construct a Module. In
322  * this case we therefore need to construct a function call that will construct
323  * our Python object (which is a subclass of Module), and then pass this back to
324  * Boost.Python to give the Python object back.
325  *
326  * We have to do some indirection here to get this to work, but we can
327  * achieve this with the following strategy:
328  *
329  * - Create a @c ModuleRegisterHelper object, which as an argument will store
330  * the Python class instance that will be instantiated from the Python side.
331  * - Using std::bind, construct a function pointer to the helper's creation
332  * function, ModuleRegisterHelper::create.
333  * - Create a Python capsule that will contain the @c ModuleRegisterHelper
334  * instance, and register this in the global namespace of the current
335  * module. This then ties the capsule to the lifetime of the module.
336  */
337 void Module_Register(ModuleType const &modType, std::string const &modName,
338  py::object &obj)
339 {
340  // Create a module register helper, which will call the C++ function to
341  // create the module.
342  ModuleRegisterHelper *helper = new ModuleRegisterHelper(obj);
343 
344  // Register this with the module factory using std::bind to grab a function
345  // pointer to that particular object's function.
347  ModuleKey(modType, modName), std::bind(&ModuleRegisterHelper::create,
348  helper, std::placeholders::_1));
349 
350  // Create a capsule that will be embedded in the __main__ namespace. So
351  // deallocation will occur, but only once Python ends or the Python module
352  // is deallocated.
353  std::string modkey =
354  "_" + std::string(ModuleTypeMap[modType]) + "_" + modName;
355 
356 #if PY_MAJOR_VERSION == 2
357  py::object capsule(
358  py::handle<>(PyCObject_FromVoidPtr(helper, ModuleCapsuleDestructor)));
359 #else
360  py::object capsule(
361  py::handle<>(PyCapsule_New(helper, 0, ModuleCapsuleDestructor)));
362 #endif
363 
364  // Embed this in __main__.
365  py::import("__main__").attr(modkey.c_str()) = capsule;
366 }
367 
368 template <typename MODTYPE> struct ModuleWrapConverter
369 {
371  {
372  // An important bit of code which will register allow
373  // shared_ptr<MODTYPE> as something that boost::python recognises,
374  // otherwise modules constructed from the factory will not work from
375  // Python.
376  py::objects::class_value_wrapper<
377  std::shared_ptr<MODTYPE>,
378  py::objects::make_ptr_instance<
379  MODTYPE, py::objects::pointer_holder<std::shared_ptr<MODTYPE>,
380  MODTYPE>>>();
381  }
382 };
383 
384 template <typename MODTYPE> struct PythonModuleClass
385 {
386  PythonModuleClass(std::string modName)
387  {
388  py::class_<ModuleWrap<MODTYPE>, std::shared_ptr<ModuleWrap<MODTYPE>>,
389  py::bases<Module>, boost::noncopyable>(
390  modName.c_str(), py::init<FieldSharedPtr>())
391 
392  .def("AddConfigOption", ModuleWrap_AddConfigOption<MODTYPE>,
393  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
394  py::arg("isBool") = false))
395 
396  // Allow direct access to field object through a property.
397  .def_readwrite("field", &ModuleWrap<MODTYPE>::m_f)
398 
399  // Process function for this module.
400  .def("Process", py::pure_virtual(&Module_Process))
401  .def("Run", py::pure_virtual(&Module_Process))
402  .def("Create", py::raw_function(Module_Create<MODTYPE>))
403  .staticmethod("Create");
404 
406  }
407 };
408 
410 {
411  // Export ModuleType enum.
413 
414  // Define ModuleWrap to be implicitly convertible to a Module, since it
415  // seems that doesn't sometimes get picked up.
416  py::implicitly_convertible<std::shared_ptr<ModuleWrap<Module>>,
417  std::shared_ptr<Module>>();
418  py::implicitly_convertible<std::shared_ptr<ModuleWrap<InputModule>>,
419  std::shared_ptr<Module>>();
420  py::implicitly_convertible<std::shared_ptr<ModuleWrap<OutputModule>>,
421  std::shared_ptr<Module>>();
422  py::implicitly_convertible<std::shared_ptr<ModuleWrap<ProcessModule>>,
423  std::shared_ptr<Module>>();
424 
425  // Wrapper for the Module class. Note that since Module contains a pure
426  // virtual function, we need the ModuleWrap helper class to handle this for
427  // us. In the lightweight wrappers above, we therefore need to ensure we're
428  // passing std::shared_ptr<Module> as the first argument, otherwise they
429  // won't accept objects constructed from Python.
430  py::class_<ModuleWrap<Module>, std::shared_ptr<ModuleWrap<Module>>,
431  boost::noncopyable>("Module", py::init<FieldSharedPtr>())
432 
433  // Process function for this module.
434  .def("Process", py::pure_virtual(&Module_Process))
435  .def("Run", py::pure_virtual(&Module_Process))
436 
437  // Configuration options.
438  .def("RegisterConfig", Module_RegisterConfig,
439  (py::arg("key"), py::arg("value") = ""))
440  .def("PrintConfig", &Module::PrintConfig)
441  .def("SetDefaults", &Module::SetDefaults)
442  .def("GetStringConfig", Module_GetConfig<std::string>)
443  .def("GetFloatConfig", Module_GetConfig<double>)
444  .def("GetIntConfig", Module_GetConfig<int>)
445  .def("GetBoolConfig", Module_GetConfig<bool>)
446  .def("AddConfigOption", ModuleWrap_AddConfigOption<Module>,
447  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
448  py::arg("isBool") = false))
449 
450  // Allow direct access to field object through a property.
451  .def_readwrite("field", &ModuleWrap<Module>::m_f)
452 
453  // Factory functions.
454  .def("Register", &Module_Register)
455  .staticmethod("Register");
456 
458 
459  PythonModuleClass<InputModule>("InputModule");
460  PythonModuleClass<ProcessModule>("ProcessModule");
461  PythonModuleClass<OutputModule>("OutputModule");
462 }
Nektar::ErrorUtil::NekError NekError
#define NEKPY_WRAP_ENUM_STRING(ENUMNAME, MAPNAME)
Definition: NekPyConfig.hpp:78
void ModuleCapsuleDestructor(PyObject *ptr)
void export_Module()
T Module_GetConfig(std::shared_ptr< Module > mod, const std::string &key)
ModuleSharedPtr Module_Create(py::tuple args, py::dict kwargs)
Lightweight wrapper for Module factory creation function.
void Module_RegisterConfig(std::shared_ptr< Module > mod, std::string const &key, std::string const &value)
Lightweight wrapper for FieldUtils::Module::RegisterConfig.
void ModuleWrap_AddConfigOption(std::shared_ptr< ModuleWrap< MODTYPE >> mod, std::string const &key, std::string const &defValue, std::string const &desc, bool isBool)
void Module_Process(ModuleSharedPtr m)
void Module_Register(ModuleType const &modType, std::string const &modName, py::object &obj)
Lightweight wrapper for the Module factory RegisterCreatorFunction, to support the ability for Python...
ModuleRegisterHelper(py::object obj)
ModuleSharedPtr create(FieldSharedPtr field)
Abstract base class for input modules.
Definition: Module.h:278
FIELD_UTILS_EXPORT void PrintConfig()
Print out all configuration options for a module.
Definition: Module.cpp:132
FIELD_UTILS_EXPORT void SetDefaults()
Sets default configuration options for those which have not been set.
Definition: Module.cpp:150
Abstract base class for output modules.
Definition: Module.h:307
Abstract base class for processing modules.
Definition: Module.h:292
tKey RegisterCreatorFunction(tKey idKey, CreatorFunction classCreator, std::string pDesc="")
Register a class with the factory.
Definition: NekFactory.hpp:198
tBaseSharedPtr CreateInstance(tKey idKey, tParam... args)
Create an instance of the class referred to by idKey.
Definition: NekFactory.hpp:144
std::shared_ptr< Field > FieldSharedPtr
Definition: Field.hpp:991
std::pair< ModuleType, std::string > ModuleKey
Definition: Module.h:317
const std::string ModuleTypeMap[]
Definition: Module.h:74
std::shared_ptr< Module > ModuleSharedPtr
Definition: Module.h:321
ModuleFactory & GetModuleFactory()
Definition: Module.cpp:49
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
Module wrapper to handle virtual function calls in Module and its subclasses as defined by the templa...
void AddConfigOption(std::string key, std::string def, std::string desc, bool isBool)
Defines a configuration option for this module.
std::string v_GetModuleName() override
ModulePriority v_GetModulePriority() override
void v_Process(po::variables_map &vm) override
Concrete implementation of the Module::Process function.
ModuleWrap(FieldSharedPtr field)
Constructor, which is identical to FieldUtils::Module.
Represents a command-line configuration option.
Definition: Module.h:131
PythonModuleClass(std::string modName)