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
40using namespace Nektar;
41using 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 */
47template <class MODTYPE>
48struct 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
127template <typename T>
128T Module_GetConfig(std::shared_ptr<Module> mod, const std::string &key)
129{
130 return mod->GetConfigOption(key).as<T>();
131}
132
133template <typename MODTYPE> struct ModuleTypeProxy
134{
135};
136
137template <> struct ModuleTypeProxy<InputModule>
138{
139 static const ModuleType value = eInputModule;
140};
141
143{
144 static const ModuleType value = eProcessModule;
145};
146
147template <> 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 */
164template <typename MODTYPE>
165ModuleSharedPtr 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 */
262void Module_RegisterConfig(std::shared_ptr<Module> mod, std::string const &key,
263 std::string const &value)
264{
265 mod->RegisterConfig(key, value);
266}
267
268template <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{
279public:
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
296protected:
297 py::object m_obj;
298};
299
300#if PY_MAJOR_VERSION == 2
301void ModuleCapsuleDestructor(void *ptr)
302{
304 delete tmp;
305}
306#else
307void ModuleCapsuleDestructor(PyObject *ptr)
308{
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 */
337void 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.
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
368template <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
384template <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 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...
ModuleRegisterHelper(py::object obj)
ModuleSharedPtr create(FieldSharedPtr field)
Abstract base class for input modules.
Definition: Module.h:278
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)