wlav / cppyy

Other
407 stars 42 forks source link

Sum of bigints improperly cast to a big-rational #39

Open edcjones00 opened 2 years ago

edcjones00 commented 2 years ago

I have a Debian 11 system on a PC. I created a miniconda environment with Python 3.8.12, CGAL 5.0.1, cppyy 2.2.0, and gmp 6.2.1.

"CGAL" is The Computational Geometry Algorithms Library. It is written in C++. It contains wrappers for the Gnu multi-precision library. Class Gmpz does bigints and class Gmpq does big-rationals.

When I add two Gmpz objects, I get a Gmpq object. Assuming that I am not experienced with cppyy, how do I find out exactly what is happening?


#! /usr/bin/env python
import cppyy
cppyy.load_library('libgmp')
cppyy.include('/home/xxxxxxxx/miniconda3/envs/CPPYY/include/CGAL/Gmpz.h')

# Add two bigints, get a big rational:
three = cppyy.gbl.CGAL.Gmpz(3)
print(three, type(three))
seven = cppyy.gbl.CGAL.Gmpz(7)
print(seven, type(seven))
s = three + seven
print(s, type(s))

# "+=" behaves correctly.
sum = cppyy.gbl.CGAL.Gmpz(0)
sum += three
sum += seven
print(sum, type(sum))
111
wlav commented 2 years ago

Unfortunately, global overloads are difficult to handle (since they can be defined completely independently from the arguments they operate on), so their whole lookup is internal on the C++ side in CPyCppyy in order to be lazy but efficient after first lookup, and not easily available through reflection on the Python side.

However, the reason that this is happening, is that there is a method:

inline
Gmpq
operator+(const Gmpq &x, const Gmpq &y)
{
    Gmpq Res;
    mpq_add(Res.mpq(), x.mpq(), y.mpq());
    return Res;
}

in CGAL/GMP/Gmpq_type.h, which can take Gmpz arguments through implicit conversion, but the Gmpz equivalent is in C++ taken from the templated provided by the BOOST_BINARY_OPERATOR_COMMUTATIVE macro and is declared as a friend, which Cling doesn't consider in it's lookup for methods (this is a known, open, bug).

Until then, best I can offer is a workaround using a pythonizor, for example:

#! /usr/bin/env python
import cppyy

cppyy.load_library('libgmp')
cppyy.include('CGAL/Gmpz.h')

def CGAL_pythonizer(klass, name):
    if name == 'Gmpz':
        cppyy.cppdef("""\
           CGAL::Gmpz fixadd(const CGAL::Gmpz& x, const CGAL::Gmpz& y) { return x+y; }"""
        )

        klass.__add__ = cppyy.gbl.fixadd

cppyy.py.add_pythonization(CGAL_pythonizer, "CGAL")

# Add two bigints, get a big rational:
three = cppyy.gbl.CGAL.Gmpz(3)
print(three, type(three))
seven = cppyy.gbl.CGAL.Gmpz(7)
print(seven, type(seven))
s = three + seven
print(s, type(s))

#cppyy.gbl.tryit()

# "+=" behaves correctly.
sum = cppyy.gbl.CGAL.Gmpz(0)
sum += three
sum += seven
print(sum, type(sum))