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 Process(po::variables_map &vm) override
63  {
64  this->get_override("Process")();
65  }
66 
67  std::string GetModuleName() override
68  {
69  py::override f = this->get_override("GetModuleName");
70  return py::call<std::string>(f.ptr());
71  }
72 
74  {
75  py::override f = this->get_override("GetModulePriority");
76  return py::call<ModulePriority>(f.ptr());
77  }
78 
79  /**
80  * @brief Defines a configuration option for this module.
81  *
82  * @param key The name of the configuration option.
83  * @param def The option's default value.
84  * @param desc A text description of the option.
85  * @param isBool If true, this option is a boolean-type (true/false).
86  */
87  void AddConfigOption(std::string key, std::string def, std::string desc,
88  bool isBool)
89  {
90  ConfigOption conf(isBool, def, desc);
91  this->m_config[key] = conf;
92  }
93 
94  // We expose Module::m_f as a public member variable so that we can
95  // adjust this using Python attributes.
96  using MODTYPE::m_f;
97 };
98 
99 // Wrapper around Module::Process(&vm).
100 // Performs switching of m_comm if nparts > 1.
102 {
103  if (m->m_f->m_nParts > 1)
104  {
105  if (m->GetModulePriority() == eOutput)
106  {
107  m->m_f->m_comm = m->m_f->m_partComm;
108  if (m->GetModuleName() != "OutputInfo")
109  {
110  m->RegisterConfig("writemultiplefiles");
111  }
112  }
113  else if (m->GetModulePriority() == eCreateGraph)
114  {
115  m->m_f->m_comm = m->m_f->m_partComm;
116  }
117  else
118  {
119  m->m_f->m_comm = m->m_f->m_defComm;
120  }
121  }
122  m->SetDefaults();
123  m->Process(m->m_f->m_vm);
124 }
125 
126 template <typename T>
127 T Module_GetConfig(std::shared_ptr<Module> mod, const std::string &key)
128 {
129  return mod->GetConfigOption(key).as<T>();
130 }
131 
132 template <typename MODTYPE> struct ModuleTypeProxy
133 {
134 };
135 
136 template <> struct ModuleTypeProxy<InputModule>
137 {
138  static const ModuleType value = eInputModule;
139 };
140 
141 template <> struct ModuleTypeProxy<ProcessModule>
142 {
143  static const ModuleType value = eProcessModule;
144 };
145 
146 template <> struct ModuleTypeProxy<OutputModule>
147 {
148  static const ModuleType value = eOutputModule;
149 };
150 
154 
155 /**
156  * @brief Lightweight wrapper for Module factory creation function.
157  *
158  * @param modType Module type (input/process/output).
159  * @param modName Module name (typically filename extension).
160  * @param field Field that will be passed between modules.
161  * @tparam MODTYPE Subclass of Module (e.g #InputModule, #OutputModule)
162  */
163 template <typename MODTYPE>
164 ModuleSharedPtr Module_Create(py::tuple args, py::dict kwargs)
165 {
167 
169 
170  if (modType == eProcessModule && py::len(args) != 2)
171  {
172  throw NekError("ProcessModule.Create() requires two arguments: "
173  "module name and a Field object.");
174  }
175  else if (modType != eProcessModule && py::len(args) < 2)
176  {
177  throw NekError(ModuleTypeMap[modType] +
178  "Module.Create() requires "
179  "two arguments: module name and a Field object; "
180  "optionally a filename.");
181  }
182 
183  std::string modName = py::extract<std::string>(args[0]);
184  ModuleKey modKey = std::make_pair(modType, modName);
185 
186  if (!py::extract<FieldSharedPtr>(args[1]).check())
187  {
188  throw NekError("Second argument to Create() should be a Field object.");
189  }
190 
191  FieldSharedPtr field = py::extract<FieldSharedPtr>(args[1]);
192  ModuleSharedPtr mod = GetModuleFactory().CreateInstance(modKey, field);
193 
194  if (modType == eInputModule)
195  {
196  // For input modules we can try to interpret the remaining arguments as
197  // input files. Assume that the file's type is identical to the module
198  // name.
199  for (int i = 2; i < py::len(args); ++i)
200  {
201  std::string in_fname = py::extract<std::string>(args[i]);
202  mod->RegisterConfig("infile", in_fname);
203  mod->AddFile(modName, in_fname);
204  }
205  }
206  else if (modType == eOutputModule && py::len(args) >= 3)
207  {
208  // For output modules we can try to interpret the remaining argument as
209  // an output file.
210  mod->RegisterConfig("outfile", py::extract<std::string>(args[2]));
211  }
212 
213  // Process keyword arguments.
214  py::list items = kwargs.items();
215 
216  for (int i = 0; i < py::len(items); ++i)
217  {
218  std::string arg = py::extract<std::string>(items[i][0]);
219 
220  if (arg == "infile" && modKey.first == eInputModule)
221  {
222  py::extract<py::dict> dict_check(items[i][1]);
223 
224  if (!dict_check.check())
225  {
226  throw NekError("infile should be a dictionary.");
227  }
228 
229  py::dict ftype_fname_dict = py::extract<py::dict>(items[i][1]);
230  py::list ft_fn_items = ftype_fname_dict.items();
231  for (int i = 0; i < py::len(ft_fn_items); ++i)
232  {
233  std::string f_type =
234  py::extract<std::string>(ft_fn_items[i][0]);
235  std::string f_name = py::extract<std::string>(
236  ft_fn_items[i][1].attr("__str__")());
237  mod->RegisterConfig(arg, f_name);
238  mod->AddFile(f_type, f_name);
239  }
240  }
241  else
242  {
243  std::string val =
244  py::extract<std::string>(items[i][1].attr("__str__")());
245  mod->RegisterConfig(arg, val);
246  }
247  }
248 
249  mod->SetDefaults();
250 
251  return mod;
252 }
253 
254 /**
255  * @brief Lightweight wrapper for FieldUtils::Module::RegisterConfig.
256  *
257  * @param mod Module to call
258  * @param key Configuration key.
259  * @param value Optional value (some configuration options are boolean).
260  */
261 void Module_RegisterConfig(std::shared_ptr<Module> mod, std::string const &key,
262  std::string const &value)
263 {
264  mod->RegisterConfig(key, value);
265 }
266 
267 template <typename MODTYPE>
269  std::string const &key,
270  std::string const &defValue,
271  std::string const &desc, bool isBool)
272 {
273  mod->AddConfigOption(key, defValue, desc, isBool);
274 }
275 
277 {
278 public:
279  ModuleRegisterHelper(py::object obj) : m_obj(obj)
280  {
281  py::incref(obj.ptr());
282  }
283 
285  {
286  py::decref(m_obj.ptr());
287  }
288 
290  {
291  py::object inst = m_obj(field);
292  return py::extract<ModuleSharedPtr>(inst);
293  }
294 
295 protected:
296  py::object m_obj;
297 };
298 
299 #if PY_MAJOR_VERSION == 2
300 void ModuleCapsuleDestructor(void *ptr)
301 {
303  delete tmp;
304 }
305 #else
306 void ModuleCapsuleDestructor(PyObject *ptr)
307 {
308  ModuleRegisterHelper *tmp =
309  (ModuleRegisterHelper *)PyCapsule_GetPointer(ptr, 0);
310  delete tmp;
311 }
312 #endif
313 
314 /**
315  * @brief Lightweight wrapper for the Module factory RegisterCreatorFunction, to
316  * support the ability for Python subclasses of Module to register themselves
317  * with the Nektar++ Module factory.
318  *
319  * This function wraps the NekFactory RegisterCreatorFunction. This function
320  * expects a function pointer to a C++ object that will construct a Module. In
321  * this case we therefore need to construct a function call that will construct
322  * our Python object (which is a subclass of Module), and then pass this back to
323  * Boost.Python to give the Python object back.
324  *
325  * We have to do some indirection here to get this to work, but we can
326  * achieve this with the following strategy:
327  *
328  * - Create a @c ModuleRegisterHelper object, which as an argument will store
329  * the Python class instance that will be instantiated from the Python side.
330  * - Using std::bind, construct a function pointer to the helper's creation
331  * function, ModuleRegisterHelper::create.
332  * - Create a Python capsule that will contain the @c ModuleRegisterHelper
333  * instance, and register this in the global namespace of the current
334  * module. This then ties the capsule to the lifetime of the module.
335  */
336 void Module_Register(ModuleType const &modType, std::string const &modName,
337  py::object &obj)
338 {
339  // Create a module register helper, which will call the C++ function to
340  // create the module.
341  ModuleRegisterHelper *helper = new ModuleRegisterHelper(obj);
342 
343  // Register this with the module factory using std::bind to grab a function
344  // pointer to that particular object's function.
346  ModuleKey(modType, modName), std::bind(&ModuleRegisterHelper::create,
347  helper, std::placeholders::_1));
348 
349  // Create a capsule that will be embedded in the __main__ namespace. So
350  // deallocation will occur, but only once Python ends or the Python module
351  // is deallocated.
352  std::string modkey =
353  "_" + std::string(ModuleTypeMap[modType]) + "_" + modName;
354 
355 #if PY_MAJOR_VERSION == 2
356  py::object capsule(
357  py::handle<>(PyCObject_FromVoidPtr(helper, ModuleCapsuleDestructor)));
358 #else
359  py::object capsule(
360  py::handle<>(PyCapsule_New(helper, 0, ModuleCapsuleDestructor)));
361 #endif
362 
363  // Embed this in __main__.
364  py::import("__main__").attr(modkey.c_str()) = capsule;
365 }
366 
367 template <typename MODTYPE> struct ModuleWrapConverter
368 {
370  {
371  // An important bit of code which will register allow
372  // shared_ptr<MODTYPE> as something that boost::python recognises,
373  // otherwise modules constructed from the factory will not work from
374  // Python.
375  py::objects::class_value_wrapper<
376  std::shared_ptr<MODTYPE>,
377  py::objects::make_ptr_instance<
378  MODTYPE, py::objects::pointer_holder<std::shared_ptr<MODTYPE>,
379  MODTYPE>>>();
380  }
381 };
382 
383 template <typename MODTYPE> struct PythonModuleClass
384 {
385  PythonModuleClass(std::string modName)
386  {
387  py::class_<ModuleWrap<MODTYPE>, std::shared_ptr<ModuleWrap<MODTYPE>>,
388  py::bases<Module>, boost::noncopyable>(
389  modName.c_str(), py::init<FieldSharedPtr>())
390 
391  .def("AddConfigOption", ModuleWrap_AddConfigOption<MODTYPE>,
392  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
393  py::arg("isBool") = false))
394 
395  // Allow direct access to field object through a property.
396  .def_readwrite("field", &ModuleWrap<MODTYPE>::m_f)
397 
398  // Process function for this module.
399  .def("Process", py::pure_virtual(&Module_Process))
400  .def("Run", py::pure_virtual(&Module_Process))
401  .def("Create", py::raw_function(Module_Create<MODTYPE>))
402  .staticmethod("Create");
403 
405  }
406 };
407 
409 {
410  // Export ModuleType enum.
412 
413  // Define ModuleWrap to be implicitly convertible to a Module, since it
414  // seems that doesn't sometimes get picked up.
415  py::implicitly_convertible<std::shared_ptr<ModuleWrap<Module>>,
416  std::shared_ptr<Module>>();
417  py::implicitly_convertible<std::shared_ptr<ModuleWrap<InputModule>>,
418  std::shared_ptr<Module>>();
419  py::implicitly_convertible<std::shared_ptr<ModuleWrap<OutputModule>>,
420  std::shared_ptr<Module>>();
421  py::implicitly_convertible<std::shared_ptr<ModuleWrap<ProcessModule>>,
422  std::shared_ptr<Module>>();
423 
424  // Wrapper for the Module class. Note that since Module contains a pure
425  // virtual function, we need the ModuleWrap helper class to handle this for
426  // us. In the lightweight wrappers above, we therefore need to ensure we're
427  // passing std::shared_ptr<Module> as the first argument, otherwise they
428  // won't accept objects constructed from Python.
429  py::class_<ModuleWrap<Module>, std::shared_ptr<ModuleWrap<Module>>,
430  boost::noncopyable>("Module", py::init<FieldSharedPtr>())
431 
432  // Process function for this module.
433  .def("Process", py::pure_virtual(&Module_Process))
434  .def("Run", py::pure_virtual(&Module_Process))
435 
436  // Configuration options.
437  .def("RegisterConfig", Module_RegisterConfig,
438  (py::arg("key"), py::arg("value") = ""))
439  .def("PrintConfig", &Module::PrintConfig)
440  .def("SetDefaults", &Module::SetDefaults)
441  .def("GetStringConfig", Module_GetConfig<std::string>)
442  .def("GetFloatConfig", Module_GetConfig<double>)
443  .def("GetIntConfig", Module_GetConfig<int>)
444  .def("GetBoolConfig", Module_GetConfig<bool>)
445  .def("AddConfigOption", ModuleWrap_AddConfigOption<Module>,
446  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
447  py::arg("isBool") = false))
448 
449  // Allow direct access to field object through a property.
450  .def_readwrite("field", &ModuleWrap<Module>::m_f)
451 
452  // Factory functions.
453  .def("Register", &Module_Register)
454  .staticmethod("Register");
455 
457 
458  PythonModuleClass<InputModule>("InputModule");
459  PythonModuleClass<ProcessModule>("ProcessModule");
460  PythonModuleClass<OutputModule>("OutputModule");
461 }
Nektar::ErrorUtil::NekError NekError
#define NEKPY_WRAP_ENUM_STRING(ENUMNAME, MAPNAME)
Definition: NekPyConfig.hpp:75
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:246
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:275
Abstract base class for processing modules.
Definition: Module.h:260
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:989
std::pair< ModuleType, std::string > ModuleKey
Definition: Module.h:285
const std::string ModuleTypeMap[]
Definition: Module.h:74
std::shared_ptr< Module > ModuleSharedPtr
Definition: Module.h:289
ModuleFactory & GetModuleFactory()
Definition: Module.cpp:49
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:1
Module wrapper to handle virtual function calls in Module and its subclasses as defined by the templa...
void Process(po::variables_map &vm) override
Concrete implementation of the Module::Process function.
std::string GetModuleName() override
void AddConfigOption(std::string key, std::string def, std::string desc, bool isBool)
Defines a configuration option for this module.
ModuleWrap(FieldSharedPtr field)
Constructor, which is identical to FieldUtils::Module.
ModulePriority GetModulePriority() override
Represents a command-line configuration option.
Definition: Module.h:131
PythonModuleClass(std::string modName)