Skip to content

Commit

Permalink
Merge pull request #115 from bluescarni/pr/pybind11_iterate
Browse files Browse the repository at this point in the history
Iterative improvements for the pybind11 integration
  • Loading branch information
bluescarni committed Jan 18, 2018
2 parents c4bd1a1 + 1c73299 commit 6e3f600
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 9 deletions.
3 changes: 3 additions & 0 deletions doc/sphinx/changelog.rst
Expand Up @@ -7,6 +7,9 @@ Changelog
New
~~~

- The pybind11 integration utilities now automatically translate mp++ exceptions into appropriate
Python exceptions (`#115 <https://github.com/bluescarni/mppp/pull/115>`__).

- Add an implementation of the binomial coefficient for rational top arguments
(`#113 <https://github.com/bluescarni/mppp/pull/113>`__).

Expand Down
2 changes: 2 additions & 0 deletions doc/sphinx/exceptions.rst
@@ -1,3 +1,5 @@
.. _exceptions:

Exceptions
==========

Expand Down
32 changes: 23 additions & 9 deletions doc/sphinx/tutorial_pybind11.rst
Expand Up @@ -92,6 +92,8 @@ Let's take a look at an example of a pybind11 module enabling automatic translat
m.def("test_unordered_map_conversion", test_unordered_map<mppp::rational<1>>);
m.def("test_unordered_map_conversion", test_unordered_map<mppp::real128>);
m.def("test_unordered_map_conversion", test_unordered_map<mppp::real>);

m.def("test_zero_division_error", []() { return mppp::integer<1>{1} / 0; });
}

Note that we have exposed functions which just return a copy of their input parameter.
Expand Down Expand Up @@ -166,23 +168,27 @@ overloads with :cpp:class:`~mppp::real128` arguments should be exposed **before*
(otherwise, the :cpp:class:`~mppp::real` overload will always be preferred).

There's an important caveat to keep in mind when translating to/from :cpp:class:`~mppp::real128`. The IEEE 754 quadruple precision
format, implemented by :cpp:class:`~mppp::real128`, has a limited exponent range. For instance, the value :math:`2^{-30000}` becomes
simply zero in quadruple precision, but, in mpmath, it doesn't:
format, implemented by :cpp:class:`~mppp::real128`, has a limited exponent range. The value :math:`2^{-30000}`, for instance, becomes
simply zero in quadruple precision, and :math:`2^{30000}` becomes :math:`+\infty`:

>>> mp.prec = 113
>>> p.test_real128_conversion(mpf(2)**-30000)
mpf('0.0')
>>> p.test_real128_conversion(mpf(2)**30000)
mpf('+inf')

In mpmath, however, :math:`2^{-30000}` and :math:`2^{30000}` are correctly computed to quadruple precision:

>>> mpf(2)**-30000
mpf('1.25930254358409145729153078521520406e-9031')
>>> mpf(2)**30000
mpf('7.94090351913296032413251784349270251e+9030')

This happens because mpmath features a much larger (practically unlimited) range for the value of the exponent.
As a consequence, a conversion from ``mpf`` to :cpp:class:`~mppp::real128` will **not** preserve the exact value if the absolute value of the
exponent is too large:

>>> p.test_real128_conversion(mpf(2)**-30000)
mpf('0.0')
>>> p.test_real128_conversion(mpf(2)**30000)
mpf('+inf')
exponent is too large.

Finally, we can verify that the conversion between mp++ and Python works also when containers are involved:
We can verify that the conversion between mp++ and Python works transparently when containers are involved:

>>> p.test_vector_conversion([1, 2, 3])
[1, 2, 3]
Expand All @@ -196,3 +202,11 @@ Finally, we can verify that the conversion between mp++ and Python works also wh
{'a': Fraction(1, 2), 'b': Fraction(1, 3)}
>>> p.test_unordered_map_conversion({'a': mpf(1), 'b': mpf(3)})
{'a': mpf('1.0'), 'b': mpf('3.0')}

Finally, the pybind11 integration utilities will automatically translate mp++ :ref:`exceptions <exceptions>` thrown
from C++ code into corresponding Python exceptions. Here is an example with :cpp:class:`~mppp::zero_division_error`:

>>> p.test_zero_division_error()
Traceback (most recent call last):
...
ZeroDivisionError: Integer division by zero
12 changes: 12 additions & 0 deletions include/mp++/extra/pybind11.hpp
Expand Up @@ -37,6 +37,7 @@

#include <cassert>
#include <cstddef>
#include <exception>
#include <iostream>
#include <limits>
#include <memory>
Expand Down Expand Up @@ -177,6 +178,17 @@ inline void init()
auto fraction_class = py::module::import("fractions").attr("Fraction");
globals::fraction_class.reset(new py::object(std::move(fraction_class)));

// Tanslation for mp++ exceptions.
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const mppp::zero_division_error &e) {
::PyErr_SetString(::PyExc_ZeroDivisionError, e.what());
}
});

// Mark as inited.
globals::inited = true;

Expand Down
2 changes: 2 additions & 0 deletions test/pybind11/pybind11_test_01.cpp
Expand Up @@ -97,4 +97,6 @@ PYBIND11_MODULE(pybind11_test_01, m)
#if defined(MPPP_WITH_MPFR)
m.def("test_unordered_map_conversion", test_unordered_map<mppp::real>);
#endif

m.def("test_zero_division_error", []() { return mppp::integer<1>{1} / 0; });
}
6 changes: 6 additions & 0 deletions test/pybind11/run_pybind11_test_01.py
Expand Up @@ -232,6 +232,12 @@ def run_mpfr_stl(self):
self.assertRaises(TypeError, lambda: p.test_unordered_map_conversion(
{'a': mpf(1), 'b': 2}))

def test_exceptions(self):
import pybind11_test_01 as p

self.assertRaises(ZeroDivisionError,
lambda: p.test_zero_division_error())


if __name__ == '__main__':
unittest.main()

0 comments on commit 6e3f600

Please sign in to comment.