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