1r"""
2Make e-antic accessible from Python through cppyy
3
4EXAMPLES::
5
6    >>> from pyeantic import eantic
7    >>> K = eantic.renf("x^2 - 2", "x", "[1.4 +/- 1]")
8    >>> x = eantic.renf_elem(K, "x"); x
9    (x ~ 1.4142136)
10    >>> x + 2
11    (x+2 ~ 3.4142136)
12
13TESTS:
14
15Test that objects can be pickled::
16
17    >>> from pickle import loads, dumps
18    >>> loads(dumps(x)) == x
19    True
20    >>> loads(dumps(x.parent())) == x.parent()
21    True
22
23Test that we can recover from an invalid field definition. (This works equally
24in Python and SageMath but pytest handles the signal differently which makes it
25easier to test this in a SageMath doctest.)::
26
27    sage: from pyeantic import eantic
28    sage: K = eantic.renf("x^2 - 3", "x", "1.22 +/- 0.1")
29    Traceback (most recent call last):
30    ...
31    TypeError: ...
32
33"""
34# -*- coding: utf-8 -*-
35######################################################################
36#  This file is part of e-antic.
37#
38#        Copyright (C) 2019 Vincent Delecroix
39#        Copyright (C) 2019 Julian Rüth
40#
41#  e-antic is free software: you can redistribute it and/or modify
42#  it under the terms of the GNU Lesser General Public License as published by
43#  the Free Software Foundation, either version 3 of the License, or (at your
44#  option) any later version.
45#
46#  e-antic is distributed in the hope that it will be useful,
47#  but WITHOUT ANY WARRANTY; without even the implied warranty of
48#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
49#  GNU General Public License for more details.
50#
51#  You should have received a copy of the GNU General Public License
52#  along with e-antic. If not, see <https://www.gnu.org/licenses/>.
53#####################################################################
54
55import os
56import cppyy
57
58from cppyythonizations.printing import enable_pretty_printing
59from cppyythonizations.pickling.cereal import enable_cereal
60from cppyythonizations.operators.order import enable_total_order
61from cppyythonizations.util import filtered
62
63cppyy.py.add_pythonization(enable_pretty_printing, "eantic")
64cppyy.py.add_pythonization(filtered('renf_elem_class')(enable_total_order), "eantic")
65cppyy.py.add_pythonization(lambda proxy, name: enable_cereal(proxy, name, ["e-antic/renfxx_cereal.h"]), "eantic")
66
67def enable_arithmetic(proxy, name):
68    if name in ["renf_elem_class"]:
69        for (op, infix) in [('add', '+'), ('sub', '-'), ('mul', '*'), ('truediv', '/')]:
70            python_op = "__%s__" % (op,)
71            python_rop = "__r%s__" % (op,)
72
73            implementation = getattr(cppyy.gbl.eantic.cppyy, op)
74            def binary(lhs, rhs, implementation=implementation):
75                lhs, rhs = for_eantic(lhs), for_eantic(rhs)
76                return implementation[type(lhs), type(rhs)](lhs, rhs)
77            def rbinary(rhs, lhs, implementation=implementation):
78                lhs, rhs = for_eantic(lhs), for_eantic(rhs)
79                return implementation[type(lhs), type(rhs)](lhs, rhs)
80
81            setattr(proxy, python_op, binary)
82            setattr(proxy, python_rop, rbinary)
83
84        setattr(proxy, "__neg__", lambda self: cppyy.gbl.eantic.cppyy.neg(self))
85        setattr(proxy, "__pow__", lambda self, n: cppyy.gbl.eantic.pow(self, n))
86
87cppyy.py.add_pythonization(enable_arithmetic, "eantic")
88
89for path in os.environ.get('PYEANTIC_INCLUDE','').split(':'):
90    if path: cppyy.add_include_path(path)
91
92cppyy.include("e-antic/cppyy.h")
93
94from cppyy.gbl import eantic
95
96eantic.renf = eantic.renf_class.make
97eantic.renf.__sig2exc__ = True
98
99# cppyy is confused by template resolution, see
100# https://bitbucket.org/wlav/cppyy/issues/119/templatized-constructor-is-ignored
101# and https://github.com/flatsurf/pyeantic/issues/10
102def make_renf_elem_class(*args):
103    if len(args) == 1:
104        v = args[0]
105        if isinstance(v, eantic.renf_class):
106            return eantic.renf_elem_class(v)
107        else:
108            v = for_eantic(v)
109            return eantic.cppyy.make_renf_elem_class(v)
110    elif len(args) == 2:
111        K, v = args
112        v = for_eantic(v)
113        return eantic.cppyy.make_renf_elem_class_with_parent[type(v)](K, v)
114
115def for_eantic(x):
116    r"""
117    Attempt to convert ``x`` from something that SageMath understand to
118    something that the constructor of renf_elem_class understands.
119
120    Typically, this is the conversion of a SageMath Integer to a mpz_class and
121    such.
122
123    If no such conversion exists, leave the argument unchanged.
124    """
125    if isinstance(x, (int, eantic.renf_elem_class, cppyy.gbl.mpz_class, cppyy.gbl.mpq_class)):
126        return x
127    if isinstance(x, (tuple, list)):
128        x = [for_eantic(v) for v in x]
129        if not all([isinstance(v, (int, cppyy.gbl.mpz_class, cppyy.gbl.mpq_class)) for v in x]):
130            raise TypeError("Coefficients must be convertible to mpq")
131        x = [cppyy.gbl.mpq_class(v) for v in x]
132        x = cppyy.gbl.std.vector[cppyy.gbl.mpq_class](x)
133    if hasattr(x, '__mpq__'):
134        x = x.__mpq__()
135    if hasattr(x, '__mpz__'):
136        x = x.__mpz__()
137
138    try:
139        from gmpy2 import mpz, mpq
140    except ModuleNotFoundError:
141        return x
142
143    if isinstance(x, mpz):
144        # we need std.string, or cppyy resolves to the wrong constructor:
145        # https://bitbucket.org/wlav/cppyy/issues/127/string-argument-resolves-incorrectly
146        x = cppyy.gbl.mpz_class(cppyy.gbl.std.string(str(x)))
147    if isinstance(x, mpq):
148        # we need std.string, or cppyy resolves to the wrong constructor:
149        # https://bitbucket.org/wlav/cppyy/issues/127/string-argument-resolves-incorrectly
150        x = cppyy.gbl.mpq_class(cppyy.gbl.std.string(str(x)))
151    return x
152
153eantic.renf_elem = make_renf_elem_class
154