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