Unlike the normal case for CallbackError, where the error message is visible when it is re-raised in Python, errors occuring during reactor network integration aren't necessarily terminal, so they can't simply be re-raised after returning to Python. But we still want to display the information they contain, so we need to extract that information from the Python exception objects in the what() function that is called by cvodes_rhs.
113 lines
3.2 KiB
C++
113 lines
3.2 KiB
C++
#ifndef CT_CYTHON_FUNC_WRAPPER
|
|
#define CT_CYTHON_FUNC_WRAPPER
|
|
|
|
#include "cantera/numerics/Func1.h"
|
|
#include "cantera/base/ctexceptions.h"
|
|
#include <stdexcept>
|
|
|
|
#include "Python.h"
|
|
|
|
typedef double(*callback_wrapper)(double, void*, void**);
|
|
|
|
// A C++ exception that holds a Python exception so that it can be re-raised
|
|
// by translate_exception()
|
|
class CallbackError : public Cantera::CanteraError
|
|
{
|
|
public:
|
|
CallbackError(void* type, void* value) :
|
|
m_type((PyObject*) type),
|
|
m_value((PyObject*) value)
|
|
{
|
|
}
|
|
const char* what() const throw() {
|
|
formattedMessage_ = "\n" + std::string(71, '*') + "\n";
|
|
formattedMessage_ += "Exception raised in Python callback function:\n";
|
|
|
|
PyObject* name = PyObject_GetAttrString(m_type, "__name__");
|
|
PyObject* value_str = PyObject_Str(m_value);
|
|
|
|
#if PY_MAJOR_VERSION > 2
|
|
PyObject* name_bytes = PyUnicode_AsASCIIString(name);
|
|
PyObject* value_bytes = PyUnicode_AsASCIIString(value_str);
|
|
#else
|
|
PyObject* name_bytes = PyObject_Bytes(name);
|
|
PyObject* value_bytes = PyObject_Bytes(value_str);
|
|
#endif
|
|
|
|
if (name_bytes) {
|
|
formattedMessage_ += PyBytes_AsString(name_bytes);
|
|
Py_DECREF(name_bytes);
|
|
} else {
|
|
formattedMessage_ += "<error determining exception type>";
|
|
}
|
|
|
|
formattedMessage_ += ": ";
|
|
|
|
if (value_bytes) {
|
|
formattedMessage_ += PyBytes_AsString(value_bytes);
|
|
Py_DECREF(value_bytes);
|
|
} else {
|
|
formattedMessage_ += "<error determining exception message>";
|
|
}
|
|
|
|
Py_XDECREF(name);
|
|
Py_XDECREF(value_str);
|
|
|
|
formattedMessage_ += "\n" + std::string(71, '*') + "\n";
|
|
return formattedMessage_.c_str();
|
|
}
|
|
|
|
PyObject* m_type;
|
|
PyObject* m_value;
|
|
};
|
|
|
|
|
|
// A function of one variable implemented as a callable Python object
|
|
class Func1Py : public Cantera::Func1
|
|
{
|
|
public:
|
|
Func1Py(callback_wrapper callback, void* pyobj) :
|
|
m_callback(callback),
|
|
m_pyobj(pyobj) {
|
|
}
|
|
|
|
double eval(double t) const {
|
|
void* err[2] = {0, 0};
|
|
double y = m_callback(t, m_pyobj, err);
|
|
if (err[0]) {
|
|
throw CallbackError(err[0], err[1]);
|
|
}
|
|
return y;
|
|
}
|
|
|
|
private:
|
|
callback_wrapper m_callback;
|
|
void* m_pyobj;
|
|
};
|
|
|
|
|
|
// Translate C++ Exceptions generated by Cantera to appropriate Python
|
|
// exceptions. Used with Cython function declarations, e.g:
|
|
// cdef double eval(double) except +translate_exception
|
|
inline int translate_exception()
|
|
{
|
|
try {
|
|
if (!PyErr_Occurred()) {
|
|
// Let the latest Python exception pass through and ignore the
|
|
// current one.
|
|
throw;
|
|
}
|
|
} catch (const CallbackError& exn) {
|
|
// Re-raise a Python exception generated in a callback
|
|
PyErr_SetObject(exn.m_type, exn.m_value);
|
|
} catch (const std::out_of_range& exn) {
|
|
PyErr_SetString(PyExc_IndexError, exn.what());
|
|
} catch (const std::exception& exn) {
|
|
PyErr_SetString(PyExc_Exception, exn.what());
|
|
} catch (...) {
|
|
PyErr_SetString(PyExc_Exception, "Unknown exception");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|