You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
9.0 KiB
347 lines
9.0 KiB
5 months ago
|
"""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
|