[Math / Special Functions] Use more than one user_error function?
I am wrapping some boost special functions as NumPy "ufuncs" for
the Python library SciPy, and I am setting a policy to control
how errors are handled. For example, the wrapper of erf_inv
(for C++ types float, double and long double) is wrapped as the
function scipy.special.erfinv (i.e. the Python function is
erfinv in the module scipy.special).
The actual core instantiation of the function for type, say, double,
in C++ that is called by the ufunc implementation is
double erfinv_double(double x)
{
return erf_inv(x, special_policy());
}
where erf_inv is the Boost function, and special_policy is
created as
template <class T>
T user_domain_error(const char *function, const char * message,
const T& val)
{
sf_error("erfinv", SF_ERROR_DOMAIN, NULL);
return std::numeric_limits<T>::quiet_NaN();
}
typedef policy<
domain_error
On 13/04/2022 17:15, Warren Weckesser via Boost-users wrote:
I am wrapping some boost special functions as NumPy "ufuncs" for the Python library SciPy, and I am setting a policy to control how errors are handled. For example, the wrapper of erf_inv (for C++ types float, double and long double) is wrapped as the function scipy.special.erfinv (i.e. the Python function is erfinv in the module scipy.special).
The actual core instantiation of the function for type, say, double, in C++ that is called by the ufunc implementation is
double erfinv_double(double x) { return erf_inv(x, special_policy()); }
where erf_inv is the Boost function, and special_policy is created as
template <class T> T user_domain_error(const char *function, const char * message, const T& val) { sf_error("erfinv", SF_ERROR_DOMAIN, NULL); return std::numeric_limits<T>::quiet_NaN(); }
typedef policy< domain_error
, overflow_error > special_policy; Note that the error handler is calling sf_error(), a function in the SciPy special library. The first argument is the name of the SciPy Python function. That function might generate a Python warning or set a Python exception, and the name displayed in the error message should be the name of the SciPy function, and not something like "boost::math::erf_inv[...]".
This leads to the problem. The above code, with a hard-coded string "erfinv" passed to sf_error(), works for erfinv, but obviously I can't use the same implementation of user_domain_error for a wrapper of, say, tgamma_ratio.
So the question is, can different policies be created that have different user-defined error handlers? Or is there a clever way to get the SciPy name into the user_domain_error() function? (I hope there is something simple--I am not a C++ guru, so I may have missed something obvious.)
I am considering creating a mapping from some parsed version of the function argument of user_domain_error to the SciPy names. E.g. the function argument could be scanned for the occurrence of "erf_inv", and know that the name "erfinv" should be passed to sf_error(). A table of regex patterns with corresponding SciPy names might do the trick. But I'm wondering if there is a more elegant (and more maintainable) solution.
That's actually a very good question, as things stand, I think the only
way you can know what the caller is, is to check the string name passed
to the error handler. It is possibly "worse" than that too as some
special functions can call other special functions internally, so in a
few rare cases, if something has gone badly wrong in the "outer"
function, the actual error may be generated in the "inner" function :(
But leaving aside that issue for the moment, I would probably create a
sorted table of std::pair
That's actually a very good question, as things stand, I think the only way you can know what the caller is, is to check the string name passed to the error handler. It is possibly "worse" than that too as some special functions can call other special functions internally, so in a few rare cases, if something has gone badly wrong in the "outer" function, the actual error may be generated in the "inner" function :(
But leaving aside that issue for the moment, I would probably create a sorted table of std::pair
, with the first member of the pair being our name, the second your name, and then do a std:::lower_bound to find a matching entry and do the name translation. I don't think our names have ever changed, so while we've never guaranteed stability of those, it's hard to imagine them changing unless someone spots a really grievous spelling mistake or something ;) You would still need to perform the rather tedious job of calling each function you're wrapping with say NaN parameters, and then logging the string name of the function in the error handler so you know what to put in the table.
Maybe we're both over-thinking this, why not just: double erfinv_double(double x) { try{ return erf_inv(x, special_policy()); } catch(const std::domain_error&) { // Python error handling here. } catch(whatever-else-may-get-thrown){ /*more error handling*/} } ?
On 4/14/22, John Maddock via Boost-users
That's actually a very good question, as things stand, I think the only way you can know what the caller is, is to check the string name passed to the error handler. It is possibly "worse" than that too as some special functions can call other special functions internally, so in a few rare cases, if something has gone badly wrong in the "outer" function, the actual error may be generated in the "inner" function :(
But leaving aside that issue for the moment, I would probably create a sorted table of std::pair
, with the first member of the pair being our name, the second your name, and then do a std:::lower_bound to find a matching entry and do the name translation. I don't think our names have ever changed, so while we've never guaranteed stability of those, it's hard to imagine them changing unless someone spots a really grievous spelling mistake or something ;) You would still need to perform the rather tedious job of calling each function you're wrapping with say NaN parameters, and then logging the string name of the function in the error handler so you know what to put in the table. Maybe we're both over-thinking this, why not just:
double erfinv_double(double x)
{
try{
return erf_inv(x, special_policy());
}
catch(const std::domain_error&)
{
// Python error handling here.
}
catch(whatever-else-may-get-thrown){ /*more error handling*/}
}
?
I had the same thought yesterday! That's what I'm doing in my latest version of the wrapper. Warren
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org https://lists.boost.org/mailman/listinfo.cgi/boost-users
participants (2)
-
John Maddock
-
Warren Weckesser