Cantera  3.0.0
Loading...
Searching...
No Matches
funcWrapper.h
1// This file is part of Cantera. See License.txt in the top-level directory or
2// at https://cantera.org/license.txt for license and copyright information.
3
4#ifndef CT_CYTHON_FUNC_WRAPPER
5#define CT_CYTHON_FUNC_WRAPPER
6
9#include <stdexcept>
10#include "Python.h"
11
12typedef double(*callback_wrapper)(double, void*, void**);
13
14//! A class to hold information needed to call Python functions from delegated
15//! methods (see class Delegator).
17public:
19 : m_func(nullptr)
20 , m_exception_type(nullptr)
21 , m_exception_value(nullptr)
22 {
23 }
24
25 PyFuncInfo(const PyFuncInfo& other)
26 : m_func(other.m_func)
27 , m_exception_type(other.m_exception_type)
28 , m_exception_value(other.m_exception_value)
29 {
30 Py_XINCREF(m_exception_type);
31 Py_XINCREF(m_exception_value);
32 }
33
34 ~PyFuncInfo() {
35 Py_XDECREF(m_exception_type);
36 Py_XDECREF(m_exception_value);
37 }
38
39 PyObject* func() {
40 return m_func;
41 }
42 void setFunc(PyObject* f) {
43 m_func = f;
44 }
45
46 PyObject* exceptionType() {
47 return m_exception_type;
48 }
49 void setExceptionType(PyObject* obj) {
50 Py_XDECREF(m_exception_type);
51 Py_XINCREF(obj);
52 m_exception_type = obj;
53 }
54
55 PyObject* exceptionValue() {
56 return m_exception_value;
57 }
58 void setExceptionValue(PyObject* obj) {
59 Py_XDECREF(m_exception_value);
60 Py_XINCREF(obj);
61 m_exception_value = obj;
62 }
63
64private:
65 PyObject* m_func;
66 PyObject* m_exception_type;
67 PyObject* m_exception_value;
68};
69
70
71// A C++ exception that holds a Python exception so that it can be re-raised
72// by translate_exception()
73class CallbackError : public Cantera::CanteraError
74{
75public:
76 //! Constructor used by Func1Py
77 CallbackError(void* type, void* value) :
78 CanteraError("Python callback function"),
79 m_type((PyObject*) type),
80 m_value((PyObject*) value)
81 {
82 Py_XINCREF(m_type);
83 Py_XINCREF(m_value);
84 }
85
86 //! Constructor used by pyOverride()
87 explicit CallbackError(PyFuncInfo& info) :
88 CanteraError("Python callback function"),
89 m_type(info.exceptionType()),
90 m_value(info.exceptionValue())
91 {
92 Py_XINCREF(m_type);
93 Py_XINCREF(m_value);
94 info.setExceptionType(0);
95 info.setExceptionValue(0);
96 }
97
98 ~CallbackError() {
99 Py_XDECREF(m_type);
100 Py_XDECREF(m_value);
101 }
102
103 std::string getMessage() const override {
104 std::string msg;
105
106 PyObject* name = PyObject_GetAttrString(m_type, "__name__");
107 PyObject* value_str = PyObject_Str(m_value);
108
109 PyObject* name_bytes = PyUnicode_AsASCIIString(name);
110 PyObject* value_bytes = PyUnicode_AsASCIIString(value_str);
111
112 if (name_bytes) {
113 msg += PyBytes_AsString(name_bytes);
114 Py_DECREF(name_bytes);
115 } else {
116 msg += "<error determining exception type>";
117 }
118
119 msg += ": ";
120
121 if (value_bytes) {
122 msg += PyBytes_AsString(value_bytes);
123 Py_DECREF(value_bytes);
124 } else {
125 msg += "<error determining exception message>";
126 }
127
128 Py_XDECREF(name);
129 Py_XDECREF(value_str);
130 return msg;
131 }
132
133 std::string getClass() const override {
134 return "Exception";
135 }
136
137 PyObject* m_type;
138 PyObject* m_value;
139};
140
141
142// A function of one variable implemented as a callable Python object
143class Func1Py : public Cantera::Func1
144{
145public:
146 Func1Py(callback_wrapper callback, void* pyobj) :
147 m_callback(callback),
148 m_pyobj(pyobj) {
149 }
150
151 double eval(double t) const override {
152 void* err[2] = {0, 0};
153 double y = m_callback(t, m_pyobj, err);
154 if (err[0]) {
155 throw CallbackError(err[0], err[1]);
156 }
157 return y;
158 }
159
160private:
161 callback_wrapper m_callback;
162 void* m_pyobj;
163};
164
165// @todo Remove the second case here when Cython 0.29.x is no longer supported
166#if defined(CYTHON_HEX_VERSION) && CYTHON_HEX_VERSION >= 0x001DFFFF
167extern PyObject* pyCanteraError;
168#else
169extern "C" {
170 extern PyObject* pyCanteraError;
171}
172#endif
173
174
175//! Take a function which requires Python function information (as a PyFuncInfo
176//! object) and capture that object to generate a function that does not require
177//! any Python-specific arguments.
178//!
179//! The inner function is responsible for catching Python exceptions and
180//! stashing their details in the PyFuncInfo object. The wrapper function
181//! generated here examines the stashed exception and throws a C++ exception
182//!
183//! The caller of pyOverride must continue holding a reference to pyFunc for the
184//! lifetime of the pyOverride object to prevent it from being prematurely garbage
185//! collected.
186template <class ... Args>
187std::function<void(Args ...)> pyOverride(PyObject* pyFunc, void func(PyFuncInfo&, Args ... args)) {
188 PyFuncInfo func_info;
189 func_info.setFunc(pyFunc);
190 return [func_info, func](Args ... args) mutable {
191 func(func_info, args ...);
192 if (func_info.exceptionType()) {
193 throw CallbackError(func_info);
194 }
195 };
196}
197
198//! Same as above, but for functions that return an int.
199template <class ... Args>
200std::function<int(Args ...)> pyOverride(PyObject* pyFunc, int func(PyFuncInfo&, Args ... args)) {
201 PyFuncInfo func_info;
202 func_info.setFunc(pyFunc);
203 return [func_info, func](Args ... args) mutable {
204 int ret = func(func_info, args ...);
205 if (func_info.exceptionType()) {
206 throw CallbackError(func_info);
207 }
208 return ret;
209 };
210}
211
212// Translate C++ Exceptions generated by Cantera to appropriate Python
213// exceptions. Used with Cython function declarations, e.g:
214// cdef double eval(double) except +translate_exception
215inline int translate_exception()
216{
217 try {
218 if (!PyErr_Occurred()) {
219 // Let the latest Python exception pass through and ignore the
220 // current one.
221 throw;
222 }
223 } catch (const CallbackError& exn) {
224 // Re-raise a Python exception generated in a callback
225 PyErr_SetObject(exn.m_type, exn.m_value);
226 } catch (const std::out_of_range& exn) {
227 PyErr_SetString(PyExc_IndexError, exn.what());
228 } catch (const Cantera::NotImplementedError& exn) {
229 PyErr_SetString(PyExc_NotImplementedError, exn.what());
230 } catch (const Cantera::CanteraError& exn) {
231 PyErr_SetString(pyCanteraError, exn.what());
232 } catch (const std::exception& exn) {
233 PyErr_SetString(PyExc_RuntimeError, exn.what());
234 } catch (...) {
235 PyErr_SetString(PyExc_Exception, "Unknown exception");
236 }
237 return 0;
238}
239
240#endif
Base class for exceptions thrown by Cantera classes.
const char * what() const override
Get a description of the error.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
CanteraError()
Protected default constructor discourages throwing errors containing no information.
virtual string getClass() const
Method overridden by derived classes to indicate their type.
Base class for 'functor' classes that evaluate a function of one variable.
Definition Func1.h:96
virtual double eval(double t) const
Evaluate the function.
Definition Func1.cpp:61
An error indicating that an unimplemented function has been called.
A class to hold information needed to call Python functions from delegated methods (see class Delegat...
Definition funcWrapper.h:16
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...