Nektar++
FilterPython.cpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: FilterPython.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: Run a Python script during runtime through Filters.
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#include <boost/core/ignore_unused.hpp>
36
38
39#include <fstream>
40#include <streambuf>
41#include <string>
42
43namespace Nektar::SolverUtils
44{
45
46/// Temporarily stolen from boost examples
48{
49 PyObject *type_ptr = nullptr, *value_ptr = nullptr,
50 *traceback_ptr = nullptr;
51 // Fetch the exception info from the Python C API
52 PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);
53
54 // Fallback error
55 std::string ret("Unfetchable Python error");
56
57 // If the fetch got a type pointer, parse the type into the exception string
58 if (type_ptr != nullptr)
59 {
60 py::handle<> h_type(type_ptr);
61 py::str type_pstr(h_type);
62 // Extract the string from the boost::python object
63 py::extract<std::string> e_type_pstr(type_pstr);
64 // If a valid string extraction is available, use it; otherwise use
65 // fallback.
66 if (e_type_pstr.check())
67 {
68 ret = e_type_pstr();
69 }
70 else
71 {
72 ret = "Unknown exception type";
73 }
74 }
75
76 // Do the same for the exception value (the stringification of the
77 // exception)
78 if (value_ptr != nullptr)
79 {
80 py::handle<> h_val(value_ptr);
81 py::str a(h_val);
82 py::extract<std::string> returned(a);
83 if (returned.check())
84 {
85 ret += ": " + returned();
86 }
87 else
88 {
89 ret += std::string(": Unparseable Python error: ");
90 }
91 }
92
93 // Parse lines from the traceback using the Python traceback module
94 if (traceback_ptr != nullptr)
95 {
96 py::handle<> h_tb(traceback_ptr);
97
98 // Load the traceback module and the format_tb function
99 py::object tb(py::import("traceback"));
100 py::object fmt_tb(tb.attr("format_tb"));
101
102 // Call format_tb to get a list of traceback strings
103 py::object tb_list(fmt_tb(h_tb));
104
105 // Join the traceback strings into a single string
106 py::object tb_str(py::str("\n").join(tb_list));
107
108 // Extract the string, check the extraction, and fallback in necessary
109 py::extract<std::string> returned(tb_str);
110 if (returned.check())
111 {
112 ret += ": " + returned();
113 }
114 else
115 {
116 ret += std::string(": Unparseable Python traceback");
117 }
118 }
119 return ret;
120}
121
122// Converts a OneD array of ExpLists to a Python list.
123inline py::list ArrayOneDToPyList(
125{
126 py::list expLists;
127
128 for (int i = 0; i < pFields.size(); ++i)
129 {
130 expLists.append(py::object(pFields[i]));
131 }
132
133 return expLists;
134}
135
136std::string FilterPython::className =
138
140 const std::shared_ptr<EquationSystem> &pEquation,
141 const ParamMap &pParams)
142 : Filter(pSession, pEquation)
143{
144 auto it = pParams.find("PythonFile");
145 ASSERTL0(it != pParams.end(), "Empty parameter 'PythonFile'.");
146 std::string pythonFile = it->second;
147
148 Py_Initialize();
149 std::string pythonCode;
150
151 // Set the global namespace.
152 m_global = py::import("__main__").attr("__dict__");
153
154 try
155 {
156 std::ifstream t(pythonFile);
157 std::string str((std::istreambuf_iterator<char>(t)),
158 std::istreambuf_iterator<char>());
159 pythonCode = str;
160 }
161 catch (...)
162 {
163 ASSERTL0(false, "Error reading Python file: " + pythonFile);
164 }
165
166 // Print out the loaded session file if we're running in verbose mode.
167 if (pSession->DefinesCmdLineArgument("verbose"))
168 {
169 std::cout << "-----------------" << std::endl;
170 std::cout << "BEGIN PYTHON CODE" << std::endl;
171 std::cout << "-----------------" << std::endl;
172 std::cout << pythonCode << std::endl;
173 std::cout << "-----------------" << std::endl;
174 std::cout << "END PYTHON CODE" << std::endl;
175 std::cout << "-----------------" << std::endl;
176 }
177
178 try
179 {
180 // Import NekPy libraries. This ensures we have all of our boost python
181 // wrappers. I guess this could also be done by the calling script...
182 auto nekpy = py::import("NekPy");
183 auto multireg = py::import("NekPy.MultiRegions");
184
185 // Eval the python code. We can then grab functions etc from the global
186 // namespace.
187 py::exec(pythonCode.c_str(), m_global, m_global);
188 }
189 catch (py::error_already_set const &)
190 {
191 // Parse and output the exception
192 ASSERTL0(false,
193 "Failed to load Python code: " + parse_python_exception());
194 }
195
196 // Give the option as well to just create a filter that's defined via
197 // Python.
198 it = pParams.find("FilterName");
199 if (it != pParams.end())
200 {
201 std::string filterName = it->second;
202
203 // Check filter factory to make sure we have this class.
204 ASSERTL0(GetFilterFactory().ModuleExists(filterName),
205 "Unable to locate filter named '" + filterName + "'");
206
207 try
208 {
209 m_pyFilter = GetFilterFactory().CreateInstance(filterName, pSession,
210 pEquation, pParams);
211 }
212 catch (py::error_already_set const &)
213 {
214 // Parse and output the exception
215 ASSERTL0(false,
216 "Failed to load Python code: " + parse_python_exception());
217 }
218 }
219}
220
222{
223}
224
227 const NekDouble &time)
228{
229 try
230 {
231 if (m_pyFilter)
232 {
233 m_pyFilter->Initialise(pFields, time);
234 }
235 else
236 {
237 m_global["filter_initialise"](ArrayOneDToPyList(pFields), time);
238 }
239 }
240 catch (py::error_already_set const &)
241 {
242 std::cout << "Error in Python: " << parse_python_exception()
243 << std::endl;
244 }
245}
246
249 const NekDouble &time)
250{
251 try
252 {
253 if (m_pyFilter)
254 {
255 m_pyFilter->Update(pFields, time);
256 }
257 else
258 {
259 m_global["filter_update"](ArrayOneDToPyList(pFields), time);
260 }
261 }
262 catch (py::error_already_set const &)
263 {
264 std::cout << "Error in Python: " << parse_python_exception()
265 << std::endl;
266 }
267}
268
271 const NekDouble &time)
272{
273 try
274 {
275 if (m_pyFilter)
276 {
277 m_pyFilter->Finalise(pFields, time);
278 }
279 else
280 {
281 m_global["filter_finalise"](ArrayOneDToPyList(pFields), time);
282 }
283 }
284 catch (py::error_already_set const &)
285 {
286 std::cout << "Error in Python: " << parse_python_exception()
287 << std::endl;
288 }
289}
290
292{
293 if (m_pyFilter)
294 {
295 return m_pyFilter->IsTimeDependent();
296 }
297
298 return true;
299}
300
301} // namespace Nektar::SolverUtils
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:208
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::map< std::string, std::string > ParamMap
Definition: Filter.h:65
void v_Finalise(const Array< OneD, const MultiRegions::ExpListSharedPtr > &pFields, const NekDouble &time) override
std::shared_ptr< Filter > m_pyFilter
Definition: FilterPython.h:83
SOLVER_UTILS_EXPORT ~FilterPython() override
void v_Update(const Array< OneD, const MultiRegions::ExpListSharedPtr > &pFields, const NekDouble &time) override
SOLVER_UTILS_EXPORT FilterPython(const LibUtilities::SessionReaderSharedPtr &pSession, const std::shared_ptr< EquationSystem > &pEquation, const ParamMap &pParams)
static FilterSharedPtr create(const LibUtilities::SessionReaderSharedPtr &pSession, const std::shared_ptr< EquationSystem > &pEquation, const std::map< std::string, std::string > &pParams)
Creates an instance of this class.
Definition: FilterPython.h:49
static std::string className
Name of the class.
Definition: FilterPython.h:60
void v_Initialise(const Array< OneD, const MultiRegions::ExpListSharedPtr > &pFields, const NekDouble &time) override
std::shared_ptr< SessionReader > SessionReaderSharedPtr
std::string parse_python_exception()
Temporarily stolen from boost examples.
py::list ArrayOneDToPyList(const Array< OneD, const MultiRegions::ExpListSharedPtr > &pFields)
FilterFactory & GetFilterFactory()
double NekDouble