|
|
"""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
|