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/python/raw_function.hpp>
38 #include <boost/program_options.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] + "Module.Create() requires "
178  "two arguments: module name and a Field object; "
179  "optionally a filename.");
180  }
181 
182  std::string modName = py::extract<std::string>(args[0]);
183  ModuleKey modKey = std::make_pair(modType, modName);
184 
185  if (!py::extract<FieldSharedPtr>(args[1]).check())
186  {
187  throw NekError("Second argument to Create() should be a Field object.");
188  }
189 
190  FieldSharedPtr field = py::extract<FieldSharedPtr>(args[1]);
191  ModuleSharedPtr mod = GetModuleFactory().CreateInstance(modKey, field);
192 
193  if (modType == eInputModule)
194  {
195  // For input modules we can try to interpret the remaining arguments as
196  // input files. Assume that the file's type is identical to the module
197  // name.
198  for (int i = 2; i < py::len(args); ++i)
199  {
200  std::string in_fname = py::extract<std::string>(args[i]);
201  mod->RegisterConfig("infile", in_fname);
202  mod->AddFile(modName, in_fname);
203  }
204  }
205  else if (modType == eOutputModule && py::len(args) >= 3)
206  {
207  // For output modules we can try to interpret the remaining argument as
208  // an output file.
209  mod->RegisterConfig("outfile", py::extract<std::string>(args[2]));
210  }
211 
212  // Process keyword arguments.
213  py::list items = kwargs.items();
214 
215  for (int i = 0; i < py::len(items); ++i)
216  {
217  std::string arg = py::extract<std::string>(items[i][0]);
218 
219  if (arg == "infile" && modKey.first == eInputModule)
220  {
221  py::extract<py::dict> dict_check(items[i][1]);
222 
223  if (!dict_check.check())
224  {
225  throw NekError("infile should be a dictionary.");
226  }
227 
228  py::dict ftype_fname_dict = py::extract<py::dict>(items[i][1]);
229  py::list ft_fn_items = ftype_fname_dict.items();
230  for (int i = 0; i < py::len(ft_fn_items); ++i)
231  {
232  std::string f_type =
233  py::extract<std::string>(ft_fn_items[i][0]);
234  std::string f_name = py::extract<std::string>(
235  ft_fn_items[i][1].attr("__str__")());
236  mod->RegisterConfig(arg, f_name);
237  mod->AddFile(f_type, f_name);
238  }
239  }
240  else
241  {
242  std::string val =
243  py::extract<std::string>(items[i][1].attr("__str__")());
244  mod->RegisterConfig(arg, val);
245  }
246  }
247 
248  mod->SetDefaults();
249 
250  return mod;
251 }
252 
253 /**
254  * @brief Lightweight wrapper for FieldUtils::Module::RegisterConfig.
255  *
256  * @param mod Module to call
257  * @param key Configuration key.
258  * @param value Optional value (some configuration options are boolean).
259  */
260 void Module_RegisterConfig(std::shared_ptr<Module> mod, std::string const &key,
261  std::string const &value)
262 {
263  mod->RegisterConfig(key, value);
264 }
265 
266 template <typename MODTYPE>
268  std::string const &key,
269  std::string const &defValue,
270  std::string const &desc, bool isBool)
271 {
272  mod->AddConfigOption(key, defValue, desc, isBool);
273 }
274 
276 {
277 public:
278  ModuleRegisterHelper(py::object obj) : m_obj(obj)
279  {
280  py::incref(obj.ptr());
281  }
282 
284  {
285  py::decref(m_obj.ptr());
286  }
287 
289  {
290  py::object inst = m_obj(field);
291  return py::extract<ModuleSharedPtr>(inst);
292  }
293 
294 protected:
295  py::object m_obj;
296 };
297 
298 #if PY_MAJOR_VERSION == 2
299 void ModuleCapsuleDestructor(void *ptr)
300 {
302  delete tmp;
303 }
304 #else
305 void ModuleCapsuleDestructor(PyObject *ptr)
306 {
307  ModuleRegisterHelper *tmp =
308  (ModuleRegisterHelper *)PyCapsule_GetPointer(ptr, 0);
309  delete tmp;
310 }
311 #endif
312 
313 /**
314  * @brief Lightweight wrapper for the Module factory RegisterCreatorFunction, to
315  * support the ability for Python subclasses of Module to register themselves
316  * with the Nektar++ Module factory.
317  *
318  * This function wraps the NekFactory RegisterCreatorFunction. This function
319  * expects a function pointer to a C++ object that will construct a Module. In
320  * this case we therefore need to construct a function call that will construct
321  * our Python object (which is a subclass of Module), and then pass this back to
322  * Boost.Python to give the Python object back.
323  *
324  * We have to do some indirection here to get this to work, but we can
325  * achieve this with the following strategy:
326  *
327  * - Create a @c ModuleRegisterHelper object, which as an argument will store
328  * the Python class instance that will be instantiated from the Python side.
329  * - Using std::bind, construct a function pointer to the helper's creation
330  * function, ModuleRegisterHelper::create.
331  * - Create a Python capsule that will contain the @c ModuleRegisterHelper
332  * instance, and register this in the global namespace of the current
333  * module. This then ties the capsule to the lifetime of the module.
334  */
335 void Module_Register(ModuleType const &modType, std::string const &modName,
336  py::object &obj)
337 {
338  // Create a module register helper, which will call the C++ function to
339  // create the module.
340  ModuleRegisterHelper *helper = new ModuleRegisterHelper(obj);
341 
342  // Register this with the module factory using std::bind to grab a function
343  // pointer to that particular object's function.
345  ModuleKey(modType, modName), std::bind(&ModuleRegisterHelper::create,
346  helper, std::placeholders::_1));
347 
348  // Create a capsule that will be embedded in the __main__ namespace. So
349  // deallocation will occur, but only once Python ends or the Python module
350  // is deallocated.
351  std::string modkey =
352  "_" + std::string(ModuleTypeMap[modType]) + "_" + modName;
353 
354 #if PY_MAJOR_VERSION == 2
355  py::object capsule(
356  py::handle<>(PyCObject_FromVoidPtr(helper, ModuleCapsuleDestructor)));
357 #else
358  py::object capsule(
359  py::handle<>(PyCapsule_New(helper, 0, ModuleCapsuleDestructor)));
360 #endif
361 
362  // Embed this in __main__.
363  py::import("__main__").attr(modkey.c_str()) = capsule;
364 }
365 
366 template <typename MODTYPE> struct ModuleWrapConverter
367 {
369  {
370  // An important bit of code which will register allow
371  // shared_ptr<MODTYPE> as something that boost::python recognises,
372  // otherwise modules constructed from the factory will not work from
373  // Python.
374  py::objects::class_value_wrapper<
375  std::shared_ptr<MODTYPE>,
376  py::objects::make_ptr_instance<
377  MODTYPE, py::objects::pointer_holder<std::shared_ptr<MODTYPE>,
378  MODTYPE>>>();
379  }
380 };
381 
382 template <typename MODTYPE> struct PythonModuleClass
383 {
384  PythonModuleClass(std::string modName)
385  {
386  py::class_<ModuleWrap<MODTYPE>, std::shared_ptr<ModuleWrap<MODTYPE>>,
387  py::bases<Module>, boost::noncopyable>(
388  modName.c_str(), py::init<FieldSharedPtr>())
389 
390  .def("AddConfigOption", ModuleWrap_AddConfigOption<MODTYPE>,
391  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
392  py::arg("isBool") = false))
393 
394  // Allow direct access to field object through a property.
395  .def_readwrite("field", &ModuleWrap<MODTYPE>::m_f)
396 
397  // Process function for this module.
398  .def("Process", py::pure_virtual(&Module_Process))
399  .def("Run", py::pure_virtual(&Module_Process))
400  .def("Create", py::raw_function(Module_Create<MODTYPE>))
401  .staticmethod("Create");
402 
404  }
405 };
406 
408 {
409  // Export ModuleType enum.
411 
412  // Define ModuleWrap to be implicitly convertible to a Module, since it
413  // seems that doesn't sometimes get picked up.
414  py::implicitly_convertible<std::shared_ptr<ModuleWrap<Module>>,
415  std::shared_ptr<Module>>();
416  py::implicitly_convertible<std::shared_ptr<ModuleWrap<InputModule>>,
417  std::shared_ptr<Module>>();
418  py::implicitly_convertible<std::shared_ptr<ModuleWrap<OutputModule>>,
419  std::shared_ptr<Module>>();
420  py::implicitly_convertible<std::shared_ptr<ModuleWrap<ProcessModule>>,
421  std::shared_ptr<Module>>();
422 
423  // Wrapper for the Module class. Note that since Module contains a pure
424  // virtual function, we need the ModuleWrap helper class to handle this for
425  // us. In the lightweight wrappers above, we therefore need to ensure we're
426  // passing std::shared_ptr<Module> as the first argument, otherwise they
427  // won't accept objects constructed from Python.
428  py::class_<ModuleWrap<Module>, std::shared_ptr<ModuleWrap<Module>>,
429  boost::noncopyable>("Module", py::init<FieldSharedPtr>())
430 
431  // Process function for this module.
432  .def("Process", py::pure_virtual(&Module_Process))
433  .def("Run", py::pure_virtual(&Module_Process))
434 
435  // Configuration options.
436  .def("RegisterConfig", Module_RegisterConfig,
437  (py::arg("key"), py::arg("value") = ""))
438  .def("PrintConfig", &Module::PrintConfig)
439  .def("SetDefaults", &Module::SetDefaults)
440  .def("GetStringConfig", Module_GetConfig<std::string>)
441  .def("GetFloatConfig", Module_GetConfig<double>)
442  .def("GetIntConfig", Module_GetConfig<int>)
443  .def("GetBoolConfig", Module_GetConfig<bool>)
444  .def("AddConfigOption", ModuleWrap_AddConfigOption<Module>,
445  (py::arg("key"), py::arg("defValue"), py::arg("desc"),
446  py::arg("isBool") = false))
447 
448  // Allow direct access to field object through a property.
449  .def_readwrite("field", &ModuleWrap<Module>::m_f)
450 
451  // Factory functions.
452  .def("Register", &Module_Register)
453  .staticmethod("Register");
454 
456 
457  PythonModuleClass<InputModule>("InputModule");
458  PythonModuleClass<ProcessModule>("ProcessModule");
459  PythonModuleClass<OutputModule>("OutputModule");
460 }
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:251
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:280
Abstract base class for processing modules.
Definition: Module.h:265
tKey RegisterCreatorFunction(tKey idKey, CreatorFunction classCreator, std::string pDesc="")
Register a class with the factory.
Definition: NekFactory.hpp:200
tBaseSharedPtr CreateInstance(tKey idKey, tParam... args)
Create an instance of the class referred to by idKey.
Definition: NekFactory.hpp:145
std::shared_ptr< Field > FieldSharedPtr
Definition: Field.hpp:989
std::pair< ModuleType, std::string > ModuleKey
Definition: Module.h:290
const std::string ModuleTypeMap[]
Definition: Module.h:74
std::shared_ptr< Module > ModuleSharedPtr
Definition: Module.h:294
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:134
PythonModuleClass(std::string modName)