"""Finite extensions of ring domains.""" from sympy.polys.domains.domain import Domain from sympy.polys.domains.domainelement import DomainElement from sympy.polys.polyerrors import (CoercionFailed, NotInvertible, GeneratorsError) from sympy.polys.polytools import Poly from sympy.printing.defaults import DefaultPrinting class ExtensionElement(DomainElement, DefaultPrinting): """ Element of a finite extension. A class of univariate polynomials modulo the ``modulus`` of the extension ``ext``. It is represented by the unique polynomial ``rep`` of lowest degree. Both ``rep`` and the representation ``mod`` of ``modulus`` are of class DMP. """ __slots__ = ('rep', 'ext') def __init__(self, rep, ext): self.rep = rep self.ext = ext def parent(f): return f.ext def __bool__(f): return bool(f.rep) def __pos__(f): return f def __neg__(f): return ExtElem(-f.rep, f.ext) def _get_rep(f, g): if isinstance(g, ExtElem): if g.ext == f.ext: return g.rep else: return None else: try: g = f.ext.convert(g) return g.rep except CoercionFailed: return None def __add__(f, g): rep = f._get_rep(g) if rep is not None: return ExtElem(f.rep + rep, f.ext) else: return NotImplemented __radd__ = __add__ def __sub__(f, g): rep = f._get_rep(g) if rep is not None: return ExtElem(f.rep - rep, f.ext) else: return NotImplemented def __rsub__(f, g): rep = f._get_rep(g) if rep is not None: return ExtElem(rep - f.rep, f.ext) else: return NotImplemented def __mul__(f, g): rep = f._get_rep(g) if rep is not None: return ExtElem((f.rep * rep) % f.ext.mod, f.ext) else: return NotImplemented __rmul__ = __mul__ def _divcheck(f): """Raise if division is not implemented for this divisor""" if not f: raise NotInvertible('Zero divisor') elif f.ext.is_Field: return True elif f.rep.is_ground and f.ext.domain.is_unit(f.rep.rep[0]): return True else: # Some cases like (2*x + 2)/2 over ZZ will fail here. It is # unclear how to implement division in general if the ground # domain is not a field so for now it was decided to restrict the # implementation to division by invertible constants. msg = (f"Can not invert {f} in {f.ext}. " "Only division by invertible constants is implemented.") raise NotImplementedError(msg) def inverse(f): """Multiplicative inverse. Raises ====== NotInvertible If the element is a zero divisor. """ f._divcheck() if f.ext.is_Field: invrep = f.rep.invert(f.ext.mod) else: R = f.ext.ring invrep = R.exquo(R.one, f.rep) return ExtElem(invrep, f.ext) def __truediv__(f, g): rep = f._get_rep(g) if rep is None: return NotImplemented g = ExtElem(rep, f.ext) try: ginv = g.inverse() except NotInvertible: raise ZeroDivisionError(f"{f} / {g}") return f * ginv __floordiv__ = __truediv__ def __rtruediv__(f, g): try: g = f.ext.convert(g) except CoercionFailed: return NotImplemented return g / f __rfloordiv__ = __rtruediv__ def __mod__(f, g): rep = f._get_rep(g) if rep is None: return NotImplemented g = ExtElem(rep, f.ext) try: g._divcheck() except NotInvertible: raise ZeroDivisionError(f"{f} % {g}") # Division where defined is always exact so there is no remainder return f.ext.zero def __rmod__(f, g): try: g = f.ext.convert(g) except CoercionFailed: return NotImplemented return g % f def __pow__(f, n): if not isinstance(n, int): raise TypeError("exponent of type 'int' expected") if n < 0: try: f, n = f.inverse(), -n except NotImplementedError: raise ValueError("negative powers are not defined") b = f.rep m = f.ext.mod r = f.ext.one.rep while n > 0: if n % 2: r = (r*b) % m b = (b*b) % m n //= 2 return ExtElem(r, f.ext) def __eq__(f, g): if isinstance(g, ExtElem): return f.rep == g.rep and f.ext == g.ext else: return NotImplemented def __ne__(f, g): return not f == g def __hash__(f): return hash((f.rep, f.ext)) def __str__(f): from sympy.printing.str import sstr return sstr(f.rep) __repr__ = __str__ @property def is_ground(f): return f.rep.is_ground def to_ground(f): [c] = f.rep.to_list() return c ExtElem = ExtensionElement class MonogenicFiniteExtension(Domain): r""" Finite extension generated by an integral element. The generator is defined by a monic univariate polynomial derived from the argument ``mod``. A shorter alias is ``FiniteExtension``. Examples ======== Quadratic integer ring $\mathbb{Z}[\sqrt2]$: >>> from sympy import Symbol, Poly >>> from sympy.polys.agca.extensions import FiniteExtension >>> x = Symbol('x') >>> R = FiniteExtension(Poly(x**2 - 2)); R ZZ[x]/(x**2 - 2) >>> R.rank 2 >>> R(1 + x)*(3 - 2*x) x - 1 Finite field $GF(5^3)$ defined by the primitive polynomial $x^3 + x^2 + 2$ (over $\mathbb{Z}_5$). >>> F = FiniteExtension(Poly(x**3 + x**2 + 2, modulus=5)); F GF(5)[x]/(x**3 + x**2 + 2) >>> F.basis (1, x, x**2) >>> F(x + 3)/(x**2 + 2) -2*x**2 + x + 2 Function field of an elliptic curve: >>> t = Symbol('t') >>> FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True)) ZZ(x)[t]/(t**2 - x**3 - x + 1) """ is_FiniteExtension = True dtype = ExtensionElement def __init__(self, mod): if not (isinstance(mod, Poly) and mod.is_univariate): raise TypeError("modulus must be a univariate Poly") # Using auto=True (default) potentially changes the ground domain to a # field whereas auto=False raises if division is not exact. We'll let # the caller decide whether or not they want to put the ground domain # over a field. In most uses mod is already monic. mod = mod.monic(auto=False) self.rank = mod.degree() self.modulus = mod self.mod = mod.rep # DMP representation self.domain = dom = mod.domain self.ring = mod.rep.ring or dom.old_poly_ring(*mod.gens) self.zero = self.convert(self.ring.zero) self.one = self.convert(self.ring.one) gen = self.ring.gens[0] self.symbol = self.ring.symbols[0] self.generator = self.convert(gen) self.basis = tuple(self.convert(gen**i) for i in range(self.rank)) # XXX: It might be necessary to check mod.is_irreducible here self.is_Field = self.domain.is_Field def new(self, arg): rep = self.ring.convert(arg) return ExtElem(rep % self.mod, self) def __eq__(self, other): if not isinstance(other, FiniteExtension): return False return self.modulus == other.modulus def __hash__(self): return hash((self.__class__.__name__, self.modulus)) def __str__(self): return "%s/(%s)" % (self.ring, self.modulus.as_expr()) __repr__ = __str__ def convert(self, f, base=None): rep = self.ring.convert(f, base) return ExtElem(rep % self.mod, self) def convert_from(self, f, base): rep = self.ring.convert(f, base) return ExtElem(rep % self.mod, self) def to_sympy(self, f): return self.ring.to_sympy(f.rep) def from_sympy(self, f): return self.convert(f) def set_domain(self, K): mod = self.modulus.set_domain(K) return self.__class__(mod) def drop(self, *symbols): if self.symbol in symbols: raise GeneratorsError('Can not drop generator from FiniteExtension') K = self.domain.drop(*symbols) return self.set_domain(K) def quo(self, f, g): return self.exquo(f, g) def exquo(self, f, g): rep = self.ring.exquo(f.rep, g.rep) return ExtElem(rep % self.mod, self) def is_negative(self, a): return False def is_unit(self, a): if self.is_Field: return bool(a) elif a.is_ground: return self.domain.is_unit(a.to_ground()) FiniteExtension = MonogenicFiniteExtension