I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,5 @@
"""Module for algebraic geometry and commutative algebra."""
from .homomorphisms import homomorphism
__all__ = ['homomorphism']

View File

@ -0,0 +1,356 @@
"""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 as_expr(f):
return f.ext.to_sympy(f)
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.LC()):
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.as_expr())
__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 = 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__
@property
def has_CharacteristicZero(self):
return self.domain.has_CharacteristicZero
def characteristic(self):
return self.domain.characteristic()
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

View File

@ -0,0 +1,691 @@
"""
Computations with homomorphisms of modules and rings.
This module implements classes for representing homomorphisms of rings and
their modules. Instead of instantiating the classes directly, you should use
the function ``homomorphism(from, to, matrix)`` to create homomorphism objects.
"""
from sympy.polys.agca.modules import (Module, FreeModule, QuotientModule,
SubModule, SubQuotientModule)
from sympy.polys.polyerrors import CoercionFailed
# The main computational task for module homomorphisms is kernels.
# For this reason, the concrete classes are organised by domain module type.
class ModuleHomomorphism:
"""
Abstract base class for module homomoprhisms. Do not instantiate.
Instead, use the ``homomorphism`` function:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [0, 1]])
Matrix([
[1, 0], : QQ[x]**2 -> QQ[x]**2
[0, 1]])
Attributes:
- ring - the ring over which we are considering modules
- domain - the domain module
- codomain - the codomain module
- _ker - cached kernel
- _img - cached image
Non-implemented methods:
- _kernel
- _image
- _restrict_domain
- _restrict_codomain
- _quotient_domain
- _quotient_codomain
- _apply
- _mul_scalar
- _compose
- _add
"""
def __init__(self, domain, codomain):
if not isinstance(domain, Module):
raise TypeError('Source must be a module, got %s' % domain)
if not isinstance(codomain, Module):
raise TypeError('Target must be a module, got %s' % codomain)
if domain.ring != codomain.ring:
raise ValueError('Source and codomain must be over same ring, '
'got %s != %s' % (domain, codomain))
self.domain = domain
self.codomain = codomain
self.ring = domain.ring
self._ker = None
self._img = None
def kernel(self):
r"""
Compute the kernel of ``self``.
That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute
`ker(\phi) = \{x \in M | \phi(x) = 0\}`. This is a submodule of `M`.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).kernel()
<[x, -1]>
"""
if self._ker is None:
self._ker = self._kernel()
return self._ker
def image(self):
r"""
Compute the image of ``self``.
That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute
`im(\phi) = \{\phi(x) | x \in M \}`. This is a submodule of `N`.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).image() == F.submodule([1, 0])
True
"""
if self._img is None:
self._img = self._image()
return self._img
def _kernel(self):
"""Compute the kernel of ``self``."""
raise NotImplementedError
def _image(self):
"""Compute the image of ``self``."""
raise NotImplementedError
def _restrict_domain(self, sm):
"""Implementation of domain restriction."""
raise NotImplementedError
def _restrict_codomain(self, sm):
"""Implementation of codomain restriction."""
raise NotImplementedError
def _quotient_domain(self, sm):
"""Implementation of domain quotient."""
raise NotImplementedError
def _quotient_codomain(self, sm):
"""Implementation of codomain quotient."""
raise NotImplementedError
def restrict_domain(self, sm):
"""
Return ``self``, with the domain restricted to ``sm``.
Here ``sm`` has to be a submodule of ``self.domain``.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.restrict_domain(F.submodule([1, 0]))
Matrix([
[1, x], : <[1, 0]> -> QQ[x]**2
[0, 0]])
This is the same as just composing on the right with the submodule
inclusion:
>>> h * F.submodule([1, 0]).inclusion_hom()
Matrix([
[1, x], : <[1, 0]> -> QQ[x]**2
[0, 0]])
"""
if not self.domain.is_submodule(sm):
raise ValueError('sm must be a submodule of %s, got %s'
% (self.domain, sm))
if sm == self.domain:
return self
return self._restrict_domain(sm)
def restrict_codomain(self, sm):
"""
Return ``self``, with codomain restricted to to ``sm``.
Here ``sm`` has to be a submodule of ``self.codomain`` containing the
image.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.restrict_codomain(F.submodule([1, 0]))
Matrix([
[1, x], : QQ[x]**2 -> <[1, 0]>
[0, 0]])
"""
if not sm.is_submodule(self.image()):
raise ValueError('the image %s must contain sm, got %s'
% (self.image(), sm))
if sm == self.codomain:
return self
return self._restrict_codomain(sm)
def quotient_domain(self, sm):
"""
Return ``self`` with domain replaced by ``domain/sm``.
Here ``sm`` must be a submodule of ``self.kernel()``.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.quotient_domain(F.submodule([-x, 1]))
Matrix([
[1, x], : QQ[x]**2/<[-x, 1]> -> QQ[x]**2
[0, 0]])
"""
if not self.kernel().is_submodule(sm):
raise ValueError('kernel %s must contain sm, got %s' %
(self.kernel(), sm))
if sm.is_zero():
return self
return self._quotient_domain(sm)
def quotient_codomain(self, sm):
"""
Return ``self`` with codomain replaced by ``codomain/sm``.
Here ``sm`` must be a submodule of ``self.codomain``.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.quotient_codomain(F.submodule([1, 1]))
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
[0, 0]])
This is the same as composing with the quotient map on the left:
>>> (F/[(1, 1)]).quotient_hom() * h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
[0, 0]])
"""
if not self.codomain.is_submodule(sm):
raise ValueError('sm must be a submodule of codomain %s, got %s'
% (self.codomain, sm))
if sm.is_zero():
return self
return self._quotient_codomain(sm)
def _apply(self, elem):
"""Apply ``self`` to ``elem``."""
raise NotImplementedError
def __call__(self, elem):
return self.codomain.convert(self._apply(self.domain.convert(elem)))
def _compose(self, oth):
"""
Compose ``self`` with ``oth``, that is, return the homomorphism
obtained by first applying then ``self``, then ``oth``.
(This method is private since in this syntax, it is non-obvious which
homomorphism is executed first.)
"""
raise NotImplementedError
def _mul_scalar(self, c):
"""Scalar multiplication. ``c`` is guaranteed in self.ring."""
raise NotImplementedError
def _add(self, oth):
"""
Homomorphism addition.
``oth`` is guaranteed to be a homomorphism with same domain/codomain.
"""
raise NotImplementedError
def _check_hom(self, oth):
"""Helper to check that oth is a homomorphism with same domain/codomain."""
if not isinstance(oth, ModuleHomomorphism):
return False
return oth.domain == self.domain and oth.codomain == self.codomain
def __mul__(self, oth):
if isinstance(oth, ModuleHomomorphism) and self.domain == oth.codomain:
return oth._compose(self)
try:
return self._mul_scalar(self.ring.convert(oth))
except CoercionFailed:
return NotImplemented
# NOTE: _compose will never be called from rmul
__rmul__ = __mul__
def __truediv__(self, oth):
try:
return self._mul_scalar(1/self.ring.convert(oth))
except CoercionFailed:
return NotImplemented
def __add__(self, oth):
if self._check_hom(oth):
return self._add(oth)
return NotImplemented
def __sub__(self, oth):
if self._check_hom(oth):
return self._add(oth._mul_scalar(self.ring.convert(-1)))
return NotImplemented
def is_injective(self):
"""
Return True if ``self`` is injective.
That is, check if the elements of the domain are mapped to the same
codomain element.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_injective()
False
>>> h.quotient_domain(h.kernel()).is_injective()
True
"""
return self.kernel().is_zero()
def is_surjective(self):
"""
Return True if ``self`` is surjective.
That is, check if every element of the codomain has at least one
preimage.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_surjective()
False
>>> h.restrict_codomain(h.image()).is_surjective()
True
"""
return self.image() == self.codomain
def is_isomorphism(self):
"""
Return True if ``self`` is an isomorphism.
That is, check if every element of the codomain has precisely one
preimage. Equivalently, ``self`` is both injective and surjective.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h = h.restrict_codomain(h.image())
>>> h.is_isomorphism()
False
>>> h.quotient_domain(h.kernel()).is_isomorphism()
True
"""
return self.is_injective() and self.is_surjective()
def is_zero(self):
"""
Return True if ``self`` is a zero morphism.
That is, check if every element of the domain is mapped to zero
under self.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_zero()
False
>>> h.restrict_domain(F.submodule()).is_zero()
True
>>> h.quotient_codomain(h.image()).is_zero()
True
"""
return self.image().is_zero()
def __eq__(self, oth):
try:
return (self - oth).is_zero()
except TypeError:
return False
def __ne__(self, oth):
return not (self == oth)
class MatrixHomomorphism(ModuleHomomorphism):
r"""
Helper class for all homomoprhisms which are expressed via a matrix.
That is, for such homomorphisms ``domain`` is contained in a module
generated by finitely many elements `e_1, \ldots, e_n`, so that the
homomorphism is determined uniquely by its action on the `e_i`. It
can thus be represented as a vector of elements of the codomain module,
or potentially a supermodule of the codomain module
(and hence conventionally as a matrix, if there is a similar interpretation
for elements of the codomain module).
Note that this class does *not* assume that the `e_i` freely generate a
submodule, nor that ``domain`` is even all of this submodule. It exists
only to unify the interface.
Do not instantiate.
Attributes:
- matrix - the list of images determining the homomorphism.
NOTE: the elements of matrix belong to either self.codomain or
self.codomain.container
Still non-implemented methods:
- kernel
- _apply
"""
def __init__(self, domain, codomain, matrix):
ModuleHomomorphism.__init__(self, domain, codomain)
if len(matrix) != domain.rank:
raise ValueError('Need to provide %s elements, got %s'
% (domain.rank, len(matrix)))
converter = self.codomain.convert
if isinstance(self.codomain, (SubModule, SubQuotientModule)):
converter = self.codomain.container.convert
self.matrix = tuple(converter(x) for x in matrix)
def _sympy_matrix(self):
"""Helper function which returns a SymPy matrix ``self.matrix``."""
from sympy.matrices import Matrix
c = lambda x: x
if isinstance(self.codomain, (QuotientModule, SubQuotientModule)):
c = lambda x: x.data
return Matrix([[self.ring.to_sympy(y) for y in c(x)] for x in self.matrix]).T
def __repr__(self):
lines = repr(self._sympy_matrix()).split('\n')
t = " : %s -> %s" % (self.domain, self.codomain)
s = ' '*len(t)
n = len(lines)
for i in range(n // 2):
lines[i] += s
lines[n // 2] += t
for i in range(n//2 + 1, n):
lines[i] += s
return '\n'.join(lines)
def _restrict_domain(self, sm):
"""Implementation of domain restriction."""
return SubModuleHomomorphism(sm, self.codomain, self.matrix)
def _restrict_codomain(self, sm):
"""Implementation of codomain restriction."""
return self.__class__(self.domain, sm, self.matrix)
def _quotient_domain(self, sm):
"""Implementation of domain quotient."""
return self.__class__(self.domain/sm, self.codomain, self.matrix)
def _quotient_codomain(self, sm):
"""Implementation of codomain quotient."""
Q = self.codomain/sm
converter = Q.convert
if isinstance(self.codomain, SubModule):
converter = Q.container.convert
return self.__class__(self.domain, self.codomain/sm,
[converter(x) for x in self.matrix])
def _add(self, oth):
return self.__class__(self.domain, self.codomain,
[x + y for x, y in zip(self.matrix, oth.matrix)])
def _mul_scalar(self, c):
return self.__class__(self.domain, self.codomain, [c*x for x in self.matrix])
def _compose(self, oth):
return self.__class__(self.domain, oth.codomain, [oth(x) for x in self.matrix])
class FreeModuleHomomorphism(MatrixHomomorphism):
"""
Concrete class for homomorphisms with domain a free module or a quotient
thereof.
Do not instantiate; the constructor does not check that your data is well
defined. Use the ``homomorphism`` function instead:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [0, 1]])
Matrix([
[1, 0], : QQ[x]**2 -> QQ[x]**2
[0, 1]])
"""
def _apply(self, elem):
if isinstance(self.domain, QuotientModule):
elem = elem.data
return sum(x * e for x, e in zip(elem, self.matrix))
def _image(self):
return self.codomain.submodule(*self.matrix)
def _kernel(self):
# The domain is either a free module or a quotient thereof.
# It does not matter if it is a quotient, because that won't increase
# the kernel.
# Our generators {e_i} are sent to the matrix entries {b_i}.
# The kernel is essentially the syzygy module of these {b_i}.
syz = self.image().syzygy_module()
return self.domain.submodule(*syz.gens)
class SubModuleHomomorphism(MatrixHomomorphism):
"""
Concrete class for homomorphism with domain a submodule of a free module
or a quotient thereof.
Do not instantiate; the constructor does not check that your data is well
defined. Use the ``homomorphism`` function instead:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> M = QQ.old_poly_ring(x).free_module(2)*x
>>> homomorphism(M, M, [[1, 0], [0, 1]])
Matrix([
[1, 0], : <[x, 0], [0, x]> -> <[x, 0], [0, x]>
[0, 1]])
"""
def _apply(self, elem):
if isinstance(self.domain, SubQuotientModule):
elem = elem.data
return sum(x * e for x, e in zip(elem, self.matrix))
def _image(self):
return self.codomain.submodule(*[self(x) for x in self.domain.gens])
def _kernel(self):
syz = self.image().syzygy_module()
return self.domain.submodule(
*[sum(xi*gi for xi, gi in zip(s, self.domain.gens))
for s in syz.gens])
def homomorphism(domain, codomain, matrix):
r"""
Create a homomorphism object.
This function tries to build a homomorphism from ``domain`` to ``codomain``
via the matrix ``matrix``.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> R = QQ.old_poly_ring(x)
>>> T = R.free_module(2)
If ``domain`` is a free module generated by `e_1, \ldots, e_n`, then
``matrix`` should be an n-element iterable `(b_1, \ldots, b_n)` where
the `b_i` are elements of ``codomain``. The constructed homomorphism is the
unique homomorphism sending `e_i` to `b_i`.
>>> F = R.free_module(2)
>>> h = homomorphism(F, T, [[1, x], [x**2, 0]])
>>> h
Matrix([
[1, x**2], : QQ[x]**2 -> QQ[x]**2
[x, 0]])
>>> h([1, 0])
[1, x]
>>> h([0, 1])
[x**2, 0]
>>> h([1, 1])
[x**2 + 1, x]
If ``domain`` is a submodule of a free module, them ``matrix`` determines
a homomoprhism from the containing free module to ``codomain``, and the
homomorphism returned is obtained by restriction to ``domain``.
>>> S = F.submodule([1, 0], [0, x])
>>> homomorphism(S, T, [[1, x], [x**2, 0]])
Matrix([
[1, x**2], : <[1, 0], [0, x]> -> QQ[x]**2
[x, 0]])
If ``domain`` is a (sub)quotient `N/K`, then ``matrix`` determines a
homomorphism from `N` to ``codomain``. If the kernel contains `K`, this
homomorphism descends to ``domain`` and is returned; otherwise an exception
is raised.
>>> homomorphism(S/[(1, 0)], T, [0, [x**2, 0]])
Matrix([
[0, x**2], : <[1, 0] + <[1, 0]>, [0, x] + <[1, 0]>, [1, 0] + <[1, 0]>> -> QQ[x]**2
[0, 0]])
>>> homomorphism(S/[(0, x)], T, [0, [x**2, 0]])
Traceback (most recent call last):
...
ValueError: kernel <[1, 0], [0, 0]> must contain sm, got <[0,x]>
"""
def freepres(module):
"""
Return a tuple ``(F, S, Q, c)`` where ``F`` is a free module, ``S`` is a
submodule of ``F``, and ``Q`` a submodule of ``S``, such that
``module = S/Q``, and ``c`` is a conversion function.
"""
if isinstance(module, FreeModule):
return module, module, module.submodule(), lambda x: module.convert(x)
if isinstance(module, QuotientModule):
return (module.base, module.base, module.killed_module,
lambda x: module.convert(x).data)
if isinstance(module, SubQuotientModule):
return (module.base.container, module.base, module.killed_module,
lambda x: module.container.convert(x).data)
# an ordinary submodule
return (module.container, module, module.submodule(),
lambda x: module.container.convert(x))
SF, SS, SQ, _ = freepres(domain)
TF, TS, TQ, c = freepres(codomain)
# NOTE this is probably a bit inefficient (redundant checks)
return FreeModuleHomomorphism(SF, TF, [c(x) for x in matrix]
).restrict_domain(SS).restrict_codomain(TS
).quotient_codomain(TQ).quotient_domain(SQ)

View File

@ -0,0 +1,395 @@
"""Computations with ideals of polynomial rings."""
from sympy.polys.polyerrors import CoercionFailed
from sympy.polys.polyutils import IntegerPowerable
class Ideal(IntegerPowerable):
"""
Abstract base class for ideals.
Do not instantiate - use explicit constructors in the ring class instead:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> QQ.old_poly_ring(x).ideal(x+1)
<x + 1>
Attributes
- ring - the ring this ideal belongs to
Non-implemented methods:
- _contains_elem
- _contains_ideal
- _quotient
- _intersect
- _union
- _product
- is_whole_ring
- is_zero
- is_prime, is_maximal, is_primary, is_radical
- is_principal
- height, depth
- radical
Methods that likely should be overridden in subclasses:
- reduce_element
"""
def _contains_elem(self, x):
"""Implementation of element containment."""
raise NotImplementedError
def _contains_ideal(self, I):
"""Implementation of ideal containment."""
raise NotImplementedError
def _quotient(self, J):
"""Implementation of ideal quotient."""
raise NotImplementedError
def _intersect(self, J):
"""Implementation of ideal intersection."""
raise NotImplementedError
def is_whole_ring(self):
"""Return True if ``self`` is the whole ring."""
raise NotImplementedError
def is_zero(self):
"""Return True if ``self`` is the zero ideal."""
raise NotImplementedError
def _equals(self, J):
"""Implementation of ideal equality."""
return self._contains_ideal(J) and J._contains_ideal(self)
def is_prime(self):
"""Return True if ``self`` is a prime ideal."""
raise NotImplementedError
def is_maximal(self):
"""Return True if ``self`` is a maximal ideal."""
raise NotImplementedError
def is_radical(self):
"""Return True if ``self`` is a radical ideal."""
raise NotImplementedError
def is_primary(self):
"""Return True if ``self`` is a primary ideal."""
raise NotImplementedError
def is_principal(self):
"""Return True if ``self`` is a principal ideal."""
raise NotImplementedError
def radical(self):
"""Compute the radical of ``self``."""
raise NotImplementedError
def depth(self):
"""Compute the depth of ``self``."""
raise NotImplementedError
def height(self):
"""Compute the height of ``self``."""
raise NotImplementedError
# TODO more
# non-implemented methods end here
def __init__(self, ring):
self.ring = ring
def _check_ideal(self, J):
"""Helper to check ``J`` is an ideal of our ring."""
if not isinstance(J, Ideal) or J.ring != self.ring:
raise ValueError(
'J must be an ideal of %s, got %s' % (self.ring, J))
def contains(self, elem):
"""
Return True if ``elem`` is an element of this ideal.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x+1, x-1).contains(3)
True
>>> QQ.old_poly_ring(x).ideal(x**2, x**3).contains(x)
False
"""
return self._contains_elem(self.ring.convert(elem))
def subset(self, other):
"""
Returns True if ``other`` is is a subset of ``self``.
Here ``other`` may be an ideal.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x+1)
>>> I.subset([x**2 - 1, x**2 + 2*x + 1])
True
>>> I.subset([x**2 + 1, x + 1])
False
>>> I.subset(QQ.old_poly_ring(x).ideal(x**2 - 1))
True
"""
if isinstance(other, Ideal):
return self._contains_ideal(other)
return all(self._contains_elem(x) for x in other)
def quotient(self, J, **opts):
r"""
Compute the ideal quotient of ``self`` by ``J``.
That is, if ``self`` is the ideal `I`, compute the set
`I : J = \{x \in R | xJ \subset I \}`.
Examples
========
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ.old_poly_ring(x, y)
>>> R.ideal(x*y).quotient(R.ideal(x))
<y>
"""
self._check_ideal(J)
return self._quotient(J, **opts)
def intersect(self, J):
"""
Compute the intersection of self with ideal J.
Examples
========
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ.old_poly_ring(x, y)
>>> R.ideal(x).intersect(R.ideal(y))
<x*y>
"""
self._check_ideal(J)
return self._intersect(J)
def saturate(self, J):
r"""
Compute the ideal saturation of ``self`` by ``J``.
That is, if ``self`` is the ideal `I`, compute the set
`I : J^\infty = \{x \in R | xJ^n \subset I \text{ for some } n\}`.
"""
raise NotImplementedError
# Note this can be implemented using repeated quotient
def union(self, J):
"""
Compute the ideal generated by the union of ``self`` and ``J``.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x**2 - 1).union(QQ.old_poly_ring(x).ideal((x+1)**2)) == QQ.old_poly_ring(x).ideal(x+1)
True
"""
self._check_ideal(J)
return self._union(J)
def product(self, J):
r"""
Compute the ideal product of ``self`` and ``J``.
That is, compute the ideal generated by products `xy`, for `x` an element
of ``self`` and `y \in J`.
Examples
========
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> QQ.old_poly_ring(x, y).ideal(x).product(QQ.old_poly_ring(x, y).ideal(y))
<x*y>
"""
self._check_ideal(J)
return self._product(J)
def reduce_element(self, x):
"""
Reduce the element ``x`` of our ring modulo the ideal ``self``.
Here "reduce" has no specific meaning: it could return a unique normal
form, simplify the expression a bit, or just do nothing.
"""
return x
def __add__(self, e):
if not isinstance(e, Ideal):
R = self.ring.quotient_ring(self)
if isinstance(e, R.dtype):
return e
if isinstance(e, R.ring.dtype):
return R(e)
return R.convert(e)
self._check_ideal(e)
return self.union(e)
__radd__ = __add__
def __mul__(self, e):
if not isinstance(e, Ideal):
try:
e = self.ring.ideal(e)
except CoercionFailed:
return NotImplemented
self._check_ideal(e)
return self.product(e)
__rmul__ = __mul__
def _zeroth_power(self):
return self.ring.ideal(1)
def _first_power(self):
# Raising to any power but 1 returns a new instance. So we mult by 1
# here so that the first power is no exception.
return self * 1
def __eq__(self, e):
if not isinstance(e, Ideal) or e.ring != self.ring:
return False
return self._equals(e)
def __ne__(self, e):
return not (self == e)
class ModuleImplementedIdeal(Ideal):
"""
Ideal implementation relying on the modules code.
Attributes:
- _module - the underlying module
"""
def __init__(self, ring, module):
Ideal.__init__(self, ring)
self._module = module
def _contains_elem(self, x):
return self._module.contains([x])
def _contains_ideal(self, J):
if not isinstance(J, ModuleImplementedIdeal):
raise NotImplementedError
return self._module.is_submodule(J._module)
def _intersect(self, J):
if not isinstance(J, ModuleImplementedIdeal):
raise NotImplementedError
return self.__class__(self.ring, self._module.intersect(J._module))
def _quotient(self, J, **opts):
if not isinstance(J, ModuleImplementedIdeal):
raise NotImplementedError
return self._module.module_quotient(J._module, **opts)
def _union(self, J):
if not isinstance(J, ModuleImplementedIdeal):
raise NotImplementedError
return self.__class__(self.ring, self._module.union(J._module))
@property
def gens(self):
"""
Return generators for ``self``.
Examples
========
>>> from sympy import QQ
>>> from sympy.abc import x, y
>>> list(QQ.old_poly_ring(x, y).ideal(x, y, x**2 + y).gens)
[DMP_Python([[1], []], QQ), DMP_Python([[1, 0]], QQ), DMP_Python([[1], [], [1, 0]], QQ)]
"""
return (x[0] for x in self._module.gens)
def is_zero(self):
"""
Return True if ``self`` is the zero ideal.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x).is_zero()
False
>>> QQ.old_poly_ring(x).ideal().is_zero()
True
"""
return self._module.is_zero()
def is_whole_ring(self):
"""
Return True if ``self`` is the whole ring, i.e. one generator is a unit.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ, ilex
>>> QQ.old_poly_ring(x).ideal(x).is_whole_ring()
False
>>> QQ.old_poly_ring(x).ideal(3).is_whole_ring()
True
>>> QQ.old_poly_ring(x, order=ilex).ideal(2 + x).is_whole_ring()
True
"""
return self._module.is_full_module()
def __repr__(self):
from sympy.printing.str import sstr
gens = [self.ring.to_sympy(x) for [x] in self._module.gens]
return '<' + ','.join(sstr(g) for g in gens) + '>'
# NOTE this is the only method using the fact that the module is a SubModule
def _product(self, J):
if not isinstance(J, ModuleImplementedIdeal):
raise NotImplementedError
return self.__class__(self.ring, self._module.submodule(
*[[x*y] for [x] in self._module.gens for [y] in J._module.gens]))
def in_terms_of_generators(self, e):
"""
Express ``e`` in terms of the generators of ``self``.
Examples
========
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x**2 + 1, x)
>>> I.in_terms_of_generators(1) # doctest: +SKIP
[DMP_Python([1], QQ), DMP_Python([-1, 0], QQ)]
"""
return self._module.in_terms_of_generators([e])
def reduce_element(self, x, **options):
return self._module.reduce_element([x], **options)[0]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,196 @@
from sympy.core.symbol import symbols
from sympy.functions.elementary.trigonometric import (cos, sin)
from sympy.polys import QQ, ZZ
from sympy.polys.polytools import Poly
from sympy.polys.polyerrors import NotInvertible
from sympy.polys.agca.extensions import FiniteExtension
from sympy.polys.domainmatrix import DomainMatrix
from sympy.testing.pytest import raises
from sympy.abc import x, y, t
def test_FiniteExtension():
# Gaussian integers
A = FiniteExtension(Poly(x**2 + 1, x))
assert A.rank == 2
assert str(A) == 'ZZ[x]/(x**2 + 1)'
i = A.generator
assert i.parent() is A
assert i*i == A(-1)
raises(TypeError, lambda: i*())
assert A.basis == (A.one, i)
assert A(1) == A.one
assert i**2 == A(-1)
assert i**2 != -1 # no coercion
assert (2 + i)*(1 - i) == 3 - i
assert (1 + i)**8 == A(16)
assert A(1).inverse() == A(1)
raises(NotImplementedError, lambda: A(2).inverse())
# Finite field of order 27
F = FiniteExtension(Poly(x**3 - x + 1, x, modulus=3))
assert F.rank == 3
a = F.generator # also generates the cyclic group F - {0}
assert F.basis == (F(1), a, a**2)
assert a**27 == a
assert a**26 == F(1)
assert a**13 == F(-1)
assert a**9 == a + 1
assert a**3 == a - 1
assert a**6 == a**2 + a + 1
assert F(x**2 + x).inverse() == 1 - a
assert F(x + 2)**(-1) == F(x + 2).inverse()
assert a**19 * a**(-19) == F(1)
assert (a - 1) / (2*a**2 - 1) == a**2 + 1
assert (a - 1) // (2*a**2 - 1) == a**2 + 1
assert 2/(a**2 + 1) == a**2 - a + 1
assert (a**2 + 1)/2 == -a**2 - 1
raises(NotInvertible, lambda: F(0).inverse())
# Function field of an elliptic curve
K = FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
assert K.rank == 2
assert str(K) == 'ZZ(x)[t]/(t**2 - x**3 - x + 1)'
y = K.generator
c = 1/(x**3 - x**2 + x - 1)
assert ((y + x)*(y - x)).inverse() == K(c)
assert (y + x)*(y - x)*c == K(1) # explicit inverse of y + x
def test_FiniteExtension_eq_hash():
# Test eq and hash
p1 = Poly(x**2 - 2, x, domain=ZZ)
p2 = Poly(x**2 - 2, x, domain=QQ)
K1 = FiniteExtension(p1)
K2 = FiniteExtension(p2)
assert K1 == FiniteExtension(Poly(x**2 - 2))
assert K2 != FiniteExtension(Poly(x**2 - 2))
assert len({K1, K2, FiniteExtension(p1)}) == 2
def test_FiniteExtension_mod():
# Test mod
K = FiniteExtension(Poly(x**3 + 1, x, domain=QQ))
xf = K(x)
assert (xf**2 - 1) % 1 == K.zero
assert 1 % (xf**2 - 1) == K.zero
assert (xf**2 - 1) / (xf - 1) == xf + 1
assert (xf**2 - 1) // (xf - 1) == xf + 1
assert (xf**2 - 1) % (xf - 1) == K.zero
raises(ZeroDivisionError, lambda: (xf**2 - 1) % 0)
raises(TypeError, lambda: xf % [])
raises(TypeError, lambda: [] % xf)
# Test mod over ring
K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ))
xf = K(x)
assert (xf**2 - 1) % 1 == K.zero
raises(NotImplementedError, lambda: (xf**2 - 1) % (xf - 1))
def test_FiniteExtension_from_sympy():
# Test to_sympy/from_sympy
K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ))
xf = K(x)
assert K.from_sympy(x) == xf
assert K.to_sympy(xf) == x
def test_FiniteExtension_set_domain():
KZ = FiniteExtension(Poly(x**2 + 1, x, domain='ZZ'))
KQ = FiniteExtension(Poly(x**2 + 1, x, domain='QQ'))
assert KZ.set_domain(QQ) == KQ
def test_FiniteExtension_exquo():
# Test exquo
K = FiniteExtension(Poly(x**4 + 1))
xf = K(x)
assert K.exquo(xf**2 - 1, xf - 1) == xf + 1
def test_FiniteExtension_convert():
# Test from_MonogenicFiniteExtension
K1 = FiniteExtension(Poly(x**2 + 1))
K2 = QQ[x]
x1, x2 = K1(x), K2(x)
assert K1.convert(x2) == x1
assert K2.convert(x1) == x2
K = FiniteExtension(Poly(x**2 - 1, domain=QQ))
assert K.convert_from(QQ(1, 2), QQ) == K.one/2
def test_FiniteExtension_division_ring():
# Test division in FiniteExtension over a ring
KQ = FiniteExtension(Poly(x**2 - 1, x, domain=QQ))
KZ = FiniteExtension(Poly(x**2 - 1, x, domain=ZZ))
KQt = FiniteExtension(Poly(x**2 - 1, x, domain=QQ[t]))
KQtf = FiniteExtension(Poly(x**2 - 1, x, domain=QQ.frac_field(t)))
assert KQ.is_Field is True
assert KZ.is_Field is False
assert KQt.is_Field is False
assert KQtf.is_Field is True
for K in KQ, KZ, KQt, KQtf:
xK = K.convert(x)
assert xK / K.one == xK
assert xK // K.one == xK
assert xK % K.one == K.zero
raises(ZeroDivisionError, lambda: xK / K.zero)
raises(ZeroDivisionError, lambda: xK // K.zero)
raises(ZeroDivisionError, lambda: xK % K.zero)
if K.is_Field:
assert xK / xK == K.one
assert xK // xK == K.one
assert xK % xK == K.zero
else:
raises(NotImplementedError, lambda: xK / xK)
raises(NotImplementedError, lambda: xK // xK)
raises(NotImplementedError, lambda: xK % xK)
def test_FiniteExtension_Poly():
K = FiniteExtension(Poly(x**2 - 2))
p = Poly(x, y, domain=K)
assert p.domain == K
assert p.as_expr() == x
assert (p**2).as_expr() == 2
K = FiniteExtension(Poly(x**2 - 2, x, domain=QQ))
K2 = FiniteExtension(Poly(t**2 - 2, t, domain=K))
assert str(K2) == 'QQ[x]/(x**2 - 2)[t]/(t**2 - 2)'
eK = K2.convert(x + t)
assert K2.to_sympy(eK) == x + t
assert K2.to_sympy(eK ** 2) == 4 + 2*x*t
p = Poly(x + t, y, domain=K2)
assert p**2 == Poly(4 + 2*x*t, y, domain=K2)
def test_FiniteExtension_sincos_jacobian():
# Use FiniteExtensino to compute the Jacobian of a matrix involving sin
# and cos of different symbols.
r, p, t = symbols('rho, phi, theta')
elements = [
[sin(p)*cos(t), r*cos(p)*cos(t), -r*sin(p)*sin(t)],
[sin(p)*sin(t), r*cos(p)*sin(t), r*sin(p)*cos(t)],
[ cos(p), -r*sin(p), 0],
]
def make_extension(K):
K = FiniteExtension(Poly(sin(p)**2+cos(p)**2-1, sin(p), domain=K[cos(p)]))
K = FiniteExtension(Poly(sin(t)**2+cos(t)**2-1, sin(t), domain=K[cos(t)]))
return K
Ksc1 = make_extension(ZZ[r])
Ksc2 = make_extension(ZZ)[r]
for K in [Ksc1, Ksc2]:
elements_K = [[K.convert(e) for e in row] for row in elements]
J = DomainMatrix(elements_K, (3, 3), K)
det = J.charpoly()[-1] * (-K.one)**3
assert det == K.convert(r**2*sin(p))

View File

@ -0,0 +1,113 @@
"""Tests for homomorphisms."""
from sympy.core.singleton import S
from sympy.polys.domains.rationalfield import QQ
from sympy.abc import x, y
from sympy.polys.agca import homomorphism
from sympy.testing.pytest import raises
def test_printing():
R = QQ.old_poly_ring(x)
assert str(homomorphism(R.free_module(1), R.free_module(1), [0])) == \
'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1'
assert str(homomorphism(R.free_module(2), R.free_module(2), [0, 0])) == \
'Matrix([ \n[0, 0], : QQ[x]**2 -> QQ[x]**2\n[0, 0]]) '
assert str(homomorphism(R.free_module(1), R.free_module(1) / [[x]], [0])) == \
'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1/<[x]>'
assert str(R.free_module(0).identity_hom()) == 'Matrix(0, 0, []) : QQ[x]**0 -> QQ[x]**0'
def test_operations():
F = QQ.old_poly_ring(x).free_module(2)
G = QQ.old_poly_ring(x).free_module(3)
f = F.identity_hom()
g = homomorphism(F, F, [0, [1, x]])
h = homomorphism(F, F, [[1, 0], 0])
i = homomorphism(F, G, [[1, 0, 0], [0, 1, 0]])
assert f == f
assert f != g
assert f != i
assert (f != F.identity_hom()) is False
assert 2*f == f*2 == homomorphism(F, F, [[2, 0], [0, 2]])
assert f/2 == homomorphism(F, F, [[S.Half, 0], [0, S.Half]])
assert f + g == homomorphism(F, F, [[1, 0], [1, x + 1]])
assert f - g == homomorphism(F, F, [[1, 0], [-1, 1 - x]])
assert f*g == g == g*f
assert h*g == homomorphism(F, F, [0, [1, 0]])
assert g*h == homomorphism(F, F, [0, 0])
assert i*f == i
assert f([1, 2]) == [1, 2]
assert g([1, 2]) == [2, 2*x]
assert i.restrict_domain(F.submodule([x, x]))([x, x]) == i([x, x])
h1 = h.quotient_domain(F.submodule([0, 1]))
assert h1([1, 0]) == h([1, 0])
assert h1.restrict_domain(h1.domain.submodule([x, 0]))([x, 0]) == h([x, 0])
raises(TypeError, lambda: f/g)
raises(TypeError, lambda: f + 1)
raises(TypeError, lambda: f + i)
raises(TypeError, lambda: f - 1)
raises(TypeError, lambda: f*i)
def test_creation():
F = QQ.old_poly_ring(x).free_module(3)
G = QQ.old_poly_ring(x).free_module(2)
SM = F.submodule([1, 1, 1])
Q = F / SM
SQ = Q.submodule([1, 0, 0])
matrix = [[1, 0], [0, 1], [-1, -1]]
h = homomorphism(F, G, matrix)
h2 = homomorphism(Q, G, matrix)
assert h.quotient_domain(SM) == h2
raises(ValueError, lambda: h.quotient_domain(F.submodule([1, 0, 0])))
assert h2.restrict_domain(SQ) == homomorphism(SQ, G, matrix)
raises(ValueError, lambda: h.restrict_domain(G))
raises(ValueError, lambda: h.restrict_codomain(G.submodule([1, 0])))
raises(ValueError, lambda: h.quotient_codomain(F))
im = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
for M in [F, SM, Q, SQ]:
assert M.identity_hom() == homomorphism(M, M, im)
assert SM.inclusion_hom() == homomorphism(SM, F, im)
assert SQ.inclusion_hom() == homomorphism(SQ, Q, im)
assert Q.quotient_hom() == homomorphism(F, Q, im)
assert SQ.quotient_hom() == homomorphism(SQ.base, SQ, im)
class conv:
def convert(x, y=None):
return x
class dummy:
container = conv()
def submodule(*args):
return None
raises(TypeError, lambda: homomorphism(dummy(), G, matrix))
raises(TypeError, lambda: homomorphism(F, dummy(), matrix))
raises(
ValueError, lambda: homomorphism(QQ.old_poly_ring(x, y).free_module(3), G, matrix))
raises(ValueError, lambda: homomorphism(F, G, [0, 0]))
def test_properties():
R = QQ.old_poly_ring(x, y)
F = R.free_module(2)
h = homomorphism(F, F, [[x, 0], [y, 0]])
assert h.kernel() == F.submodule([-y, x])
assert h.image() == F.submodule([x, 0], [y, 0])
assert not h.is_injective()
assert not h.is_surjective()
assert h.restrict_codomain(h.image()).is_surjective()
assert h.restrict_domain(F.submodule([1, 0])).is_injective()
assert h.quotient_domain(
h.kernel()).restrict_codomain(h.image()).is_isomorphism()
R2 = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1]
F = R2.free_module(2)
h = homomorphism(F, F, [[x, 0], [y, y + 1]])
assert h.is_isomorphism()

View File

@ -0,0 +1,131 @@
"""Test ideals.py code."""
from sympy.polys import QQ, ilex
from sympy.abc import x, y, z
from sympy.testing.pytest import raises
def test_ideal_operations():
R = QQ.old_poly_ring(x, y)
I = R.ideal(x)
J = R.ideal(y)
S = R.ideal(x*y)
T = R.ideal(x, y)
assert not (I == J)
assert I == I
assert I.union(J) == T
assert I + J == T
assert I + T == T
assert not I.subset(T)
assert T.subset(I)
assert I.product(J) == S
assert I*J == S
assert x*J == S
assert I*y == S
assert R.convert(x)*J == S
assert I*R.convert(y) == S
assert not I.is_zero()
assert not J.is_whole_ring()
assert R.ideal(x**2 + 1, x).is_whole_ring()
assert R.ideal() == R.ideal(0)
assert R.ideal().is_zero()
assert T.contains(x*y)
assert T.subset([x, y])
assert T.in_terms_of_generators(x) == [R(1), R(0)]
assert T**0 == R.ideal(1)
assert T**1 == T
assert T**2 == R.ideal(x**2, y**2, x*y)
assert I**5 == R.ideal(x**5)
def test_exceptions():
I = QQ.old_poly_ring(x).ideal(x)
J = QQ.old_poly_ring(y).ideal(1)
raises(ValueError, lambda: I.union(x))
raises(ValueError, lambda: I + J)
raises(ValueError, lambda: I * J)
raises(ValueError, lambda: I.union(J))
assert (I == J) is False
assert I != J
def test_nontriv_global():
R = QQ.old_poly_ring(x, y, z)
def contains(I, f):
return R.ideal(*I).contains(f)
assert contains([x, y], x)
assert contains([x, y], x + y)
assert not contains([x, y], 1)
assert not contains([x, y], z)
assert contains([x**2 + y, x**2 + x], x - y)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z)
assert contains([x, 1 + x + y, 5 - 7*y], 1)
assert contains(
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
x**3)
assert not contains(
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
x**2 + y**2)
# compare local order
assert not contains([x*(1 + x + y), y*(1 + z)], x)
assert not contains([x*(1 + x + y), y*(1 + z)], x + y)
def test_nontriv_local():
R = QQ.old_poly_ring(x, y, z, order=ilex)
def contains(I, f):
return R.ideal(*I).contains(f)
assert contains([x, y], x)
assert contains([x, y], x + y)
assert not contains([x, y], 1)
assert not contains([x, y], z)
assert contains([x**2 + y, x**2 + x], x - y)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
assert contains([x*(1 + x + y), y*(1 + z)], x)
assert contains([x*(1 + x + y), y*(1 + z)], x + y)
def test_intersection():
R = QQ.old_poly_ring(x, y, z)
# SCA, example 1.8.11
assert R.ideal(x, y).intersect(R.ideal(y**2, z)) == R.ideal(y**2, y*z, x*z)
assert R.ideal(x, y).intersect(R.ideal()).is_zero()
R = QQ.old_poly_ring(x, y, z, order="ilex")
assert R.ideal(x, y).intersect(R.ideal(y**2 + y**2*z, z + z*x**3*y)) == \
R.ideal(y**2, y*z, x*z)
def test_quotient():
# SCA, example 1.8.13
R = QQ.old_poly_ring(x, y, z)
assert R.ideal(x, y).quotient(R.ideal(y**2, z)) == R.ideal(x, y)
def test_reduction():
from sympy.polys.distributedmodules import sdm_nf_buchberger_reduced
R = QQ.old_poly_ring(x, y)
I = R.ideal(x**5, y)
e = R.convert(x**3 + y**2)
assert I.reduce_element(e) == e
assert I.reduce_element(e, NF=sdm_nf_buchberger_reduced) == R.convert(x**3)

View File

@ -0,0 +1,408 @@
"""Test modules.py code."""
from sympy.polys.agca.modules import FreeModule, ModuleOrder, FreeModulePolyRing
from sympy.polys import CoercionFailed, QQ, lex, grlex, ilex, ZZ
from sympy.abc import x, y, z
from sympy.testing.pytest import raises
from sympy.core.numbers import Rational
def test_FreeModuleElement():
M = QQ.old_poly_ring(x).free_module(3)
e = M.convert([1, x, x**2])
f = [QQ.old_poly_ring(x).convert(1), QQ.old_poly_ring(x).convert(x), QQ.old_poly_ring(x).convert(x**2)]
assert list(e) == f
assert f[0] == e[0]
assert f[1] == e[1]
assert f[2] == e[2]
raises(IndexError, lambda: e[3])
g = M.convert([x, 0, 0])
assert e + g == M.convert([x + 1, x, x**2])
assert f + g == M.convert([x + 1, x, x**2])
assert -e == M.convert([-1, -x, -x**2])
assert e - g == M.convert([1 - x, x, x**2])
assert e != g
assert M.convert([x, x, x]) / QQ.old_poly_ring(x).convert(x) == [1, 1, 1]
R = QQ.old_poly_ring(x, order="ilex")
assert R.free_module(1).convert([x]) / R.convert(x) == [1]
def test_FreeModule():
M1 = FreeModule(QQ.old_poly_ring(x), 2)
assert M1 == FreeModule(QQ.old_poly_ring(x), 2)
assert M1 != FreeModule(QQ.old_poly_ring(y), 2)
assert M1 != FreeModule(QQ.old_poly_ring(x), 3)
M2 = FreeModule(QQ.old_poly_ring(x, order="ilex"), 2)
assert [x, 1] in M1
assert [x] not in M1
assert [2, y] not in M1
assert [1/(x + 1), 2] not in M1
e = M1.convert([x, x**2 + 1])
X = QQ.old_poly_ring(x).convert(x)
assert e == [X, X**2 + 1]
assert e == [x, x**2 + 1]
assert 2*e == [2*x, 2*x**2 + 2]
assert e*2 == [2*x, 2*x**2 + 2]
assert e/2 == [x/2, (x**2 + 1)/2]
assert x*e == [x**2, x**3 + x]
assert e*x == [x**2, x**3 + x]
assert X*e == [x**2, x**3 + x]
assert e*X == [x**2, x**3 + x]
assert [x, 1] in M2
assert [x] not in M2
assert [2, y] not in M2
assert [1/(x + 1), 2] in M2
e = M2.convert([x, x**2 + 1])
X = QQ.old_poly_ring(x, order="ilex").convert(x)
assert e == [X, X**2 + 1]
assert e == [x, x**2 + 1]
assert 2*e == [2*x, 2*x**2 + 2]
assert e*2 == [2*x, 2*x**2 + 2]
assert e/2 == [x/2, (x**2 + 1)/2]
assert x*e == [x**2, x**3 + x]
assert e*x == [x**2, x**3 + x]
assert e/(1 + x) == [x/(1 + x), (x**2 + 1)/(1 + x)]
assert X*e == [x**2, x**3 + x]
assert e*X == [x**2, x**3 + x]
M3 = FreeModule(QQ.old_poly_ring(x, y), 2)
assert M3.convert(e) == M3.convert([x, x**2 + 1])
assert not M3.is_submodule(0)
assert not M3.is_zero()
raises(NotImplementedError, lambda: ZZ.old_poly_ring(x).free_module(2))
raises(NotImplementedError, lambda: FreeModulePolyRing(ZZ, 2))
raises(CoercionFailed, lambda: M1.convert(QQ.old_poly_ring(x).free_module(3)
.convert([1, 2, 3])))
raises(CoercionFailed, lambda: M3.convert(1))
def test_ModuleOrder():
o1 = ModuleOrder(lex, grlex, False)
o2 = ModuleOrder(ilex, lex, False)
assert o1 == ModuleOrder(lex, grlex, False)
assert (o1 != ModuleOrder(lex, grlex, False)) is False
assert o1 != o2
assert o1((1, 2, 3)) == (1, (5, (2, 3)))
assert o2((1, 2, 3)) == (-1, (2, 3))
def test_SubModulePolyRing_global():
R = QQ.old_poly_ring(x, y)
F = R.free_module(3)
Fd = F.submodule([1, 0, 0], [1, 2, 0], [1, 2, 3])
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
assert F == Fd
assert Fd == F
assert F != M
assert M != F
assert Fd != M
assert M != Fd
assert Fd == F.submodule(*F.basis())
assert Fd.is_full_module()
assert not M.is_full_module()
assert not Fd.is_zero()
assert not M.is_zero()
assert Fd.submodule().is_zero()
assert M.contains([x**2 + y**2 + x, 1 + y, 1])
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
assert M.contains([y**2, 1 - x*y, -x])
assert not F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0])
assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F
assert not M.is_submodule(0)
m = F.convert([x**2 + y**2, 1, 0])
n = M.convert(m)
assert m.module is F
assert n.module is M
raises(ValueError, lambda: M.submodule([1, 0, 0]))
raises(TypeError, lambda: M.union(1))
raises(ValueError, lambda: M.union(R.free_module(1).submodule([x])))
assert F.submodule([x, x, x]) != F.submodule([x, x, x], order="ilex")
def test_SubModulePolyRing_local():
R = QQ.old_poly_ring(x, y, order=ilex)
F = R.free_module(3)
Fd = F.submodule([1 + x, 0, 0], [1 + y, 2 + 2*y, 0], [1, 2, 3])
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
assert F == Fd
assert Fd == F
assert F != M
assert M != F
assert Fd != M
assert M != Fd
assert Fd == F.submodule(*F.basis())
assert Fd.is_full_module()
assert not M.is_full_module()
assert not Fd.is_zero()
assert not M.is_zero()
assert Fd.submodule().is_zero()
assert M.contains([x**2 + y**2 + x, 1 + y, 1])
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
assert M.contains([y**2, 1 - x*y, -x])
assert F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0])
assert F.submodule(
[1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1 + x*y])) == F
raises(ValueError, lambda: M.submodule([1, 0, 0]))
def test_SubModulePolyRing_nontriv_global():
R = QQ.old_poly_ring(x, y, z)
F = R.free_module(1)
def contains(I, f):
return F.submodule(*[[g] for g in I]).contains([f])
assert contains([x, y], x)
assert contains([x, y], x + y)
assert not contains([x, y], 1)
assert not contains([x, y], z)
assert contains([x**2 + y, x**2 + x], x - y)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x)
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z)
assert contains([x, 1 + x + y, 5 - 7*y], 1)
assert contains(
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
x**3)
assert not contains(
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
x**2 + y**2)
# compare local order
assert not contains([x*(1 + x + y), y*(1 + z)], x)
assert not contains([x*(1 + x + y), y*(1 + z)], x + y)
def test_SubModulePolyRing_nontriv_local():
R = QQ.old_poly_ring(x, y, z, order=ilex)
F = R.free_module(1)
def contains(I, f):
return F.submodule(*[[g] for g in I]).contains([f])
assert contains([x, y], x)
assert contains([x, y], x + y)
assert not contains([x, y], 1)
assert not contains([x, y], z)
assert contains([x**2 + y, x**2 + x], x - y)
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
assert contains([x*(1 + x + y), y*(1 + z)], x)
assert contains([x*(1 + x + y), y*(1 + z)], x + y)
def test_syzygy():
R = QQ.old_poly_ring(x, y, z)
M = R.free_module(1).submodule([x*y], [y*z], [x*z])
S = R.free_module(3).submodule([0, x, -y], [z, -x, 0])
assert M.syzygy_module() == S
M2 = M / ([x*y*z],)
S2 = R.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y])
assert M2.syzygy_module() == S2
F = R.free_module(3)
assert F.submodule(*F.basis()).syzygy_module() == F.submodule()
R2 = QQ.old_poly_ring(x, y, z) / [x*y*z]
M3 = R2.free_module(1).submodule([x*y], [y*z], [x*z])
S3 = R2.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y])
assert M3.syzygy_module() == S3
def test_in_terms_of_generators():
R = QQ.old_poly_ring(x, order="ilex")
M = R.free_module(2).submodule([2*x, 0], [1, 2])
assert M.in_terms_of_generators(
[x, x]) == [R.convert(Rational(1, 4)), R.convert(x/2)]
raises(ValueError, lambda: M.in_terms_of_generators([1, 0]))
M = R.free_module(2) / ([x, 0], [1, 1])
SM = M.submodule([1, x])
assert SM.in_terms_of_generators([2, 0]) == [R.convert(-2/(x - 1))]
R = QQ.old_poly_ring(x, y) / [x**2 - y**2]
M = R.free_module(2)
SM = M.submodule([x, 0], [0, y])
assert SM.in_terms_of_generators(
[x**2, x**2]) == [R.convert(x), R.convert(y)]
def test_QuotientModuleElement():
R = QQ.old_poly_ring(x)
F = R.free_module(3)
N = F.submodule([1, x, x**2])
M = F/N
e = M.convert([x**2, 2, 0])
assert M.convert([x + 1, x**2 + x, x**3 + x**2]) == 0
assert e == [x**2, 2, 0] + N == F.convert([x**2, 2, 0]) + N == \
M.convert(F.convert([x**2, 2, 0]))
assert M.convert([x**2 + 1, 2*x + 2, x**2]) == e + [0, x, 0] == \
e + M.convert([0, x, 0]) == e + F.convert([0, x, 0])
assert M.convert([x**2 + 1, 2, x**2]) == e - [0, x, 0] == \
e - M.convert([0, x, 0]) == e - F.convert([0, x, 0])
assert M.convert([0, 2, 0]) == M.convert([x**2, 4, 0]) - e == \
[x**2, 4, 0] - e == F.convert([x**2, 4, 0]) - e
assert M.convert([x**3 + x**2, 2*x + 2, 0]) == (1 + x)*e == \
R.convert(1 + x)*e == e*(1 + x) == e*R.convert(1 + x)
assert -e == [-x**2, -2, 0]
f = [x, x, 0] + N
assert M.convert([1, 1, 0]) == f / x == f / R.convert(x)
M2 = F/[(2, 2*x, 2*x**2), (0, 0, 1)]
G = R.free_module(2)
M3 = G/[[1, x]]
M4 = F.submodule([1, x, x**2], [1, 0, 0]) / N
raises(CoercionFailed, lambda: M.convert(G.convert([1, x])))
raises(CoercionFailed, lambda: M.convert(M3.convert([1, x])))
raises(CoercionFailed, lambda: M.convert(M2.convert([1, x, x])))
assert M2.convert(M.convert([2, x, x**2])) == [2, x, 0]
assert M.convert(M4.convert([2, 0, 0])) == [2, 0, 0]
def test_QuotientModule():
R = QQ.old_poly_ring(x)
F = R.free_module(3)
N = F.submodule([1, x, x**2])
M = F/N
assert M != F
assert M != N
assert M == F / [(1, x, x**2)]
assert not M.is_zero()
assert (F / F.basis()).is_zero()
SQ = F.submodule([1, x, x**2], [2, 0, 0]) / N
assert SQ == M.submodule([2, x, x**2])
assert SQ != M.submodule([2, 1, 0])
assert SQ != M
assert M.is_submodule(SQ)
assert not SQ.is_full_module()
raises(ValueError, lambda: N/F)
raises(ValueError, lambda: F.submodule([2, 0, 0]) / N)
raises(ValueError, lambda: R.free_module(2)/F)
raises(CoercionFailed, lambda: F.convert(M.convert([1, x, x**2])))
M1 = F / [[1, 1, 1]]
M2 = M1.submodule([1, 0, 0], [0, 1, 0])
assert M1 == M2
def test_ModulesQuotientRing():
R = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1]
M1 = R.free_module(2)
assert M1 == R.free_module(2)
assert M1 != QQ.old_poly_ring(x).free_module(2)
assert M1 != R.free_module(3)
assert [x, 1] in M1
assert [x] not in M1
assert [1/(R.convert(x) + 1), 2] in M1
assert [1, 2/(1 + y)] in M1
assert [1, 2/y] not in M1
assert M1.convert([x**2, y]) == [-1, y]
F = R.free_module(3)
Fd = F.submodule([x**2, 0, 0], [1, 2, 0], [1, 2, 3])
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
assert F == Fd
assert Fd == F
assert F != M
assert M != F
assert Fd != M
assert M != Fd
assert Fd == F.submodule(*F.basis())
assert Fd.is_full_module()
assert not M.is_full_module()
assert not Fd.is_zero()
assert not M.is_zero()
assert Fd.submodule().is_zero()
assert M.contains([x**2 + y**2 + x, -x**2 + y, 1])
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
assert M.contains([y**2, 1 - x*y, -x])
assert F.submodule([x, 0, 0]) == F.submodule([1, 0, 0])
assert not F.submodule([y, 0, 0]) == F.submodule([1, 0, 0])
assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F
assert not M.is_submodule(0)
def test_module_mul():
R = QQ.old_poly_ring(x)
M = R.free_module(2)
S1 = M.submodule([x, 0], [0, x])
S2 = M.submodule([x**2, 0], [0, x**2])
I = R.ideal(x)
assert I*M == M*I == S1 == x*M == M*x
assert I*S1 == S2 == x*S1
def test_intersection():
# SCA, example 2.8.5
F = QQ.old_poly_ring(x, y).free_module(2)
M1 = F.submodule([x, y], [y, 1])
M2 = F.submodule([0, y - 1], [x, 1], [y, x])
I = F.submodule([x, y], [y**2 - y, y - 1], [x*y + y, x + 1])
I1, rel1, rel2 = M1.intersect(M2, relations=True)
assert I1 == M2.intersect(M1) == I
for i, g in enumerate(I1.gens):
assert g == sum(c*x for c, x in zip(rel1[i], M1.gens)) \
== sum(d*y for d, y in zip(rel2[i], M2.gens))
assert F.submodule([x, y]).intersect(F.submodule([y, x])).is_zero()
def test_quotient():
# SCA, example 2.8.6
R = QQ.old_poly_ring(x, y, z)
F = R.free_module(2)
assert F.submodule([x*y, x*z], [y*z, x*y]).module_quotient(
F.submodule([y, z], [z, y])) == QQ.old_poly_ring(x, y, z).ideal(x**2*y**2 - x*y*z**2)
assert F.submodule([x, y]).module_quotient(F.submodule()).is_whole_ring()
M = F.submodule([x**2, x**2], [y**2, y**2])
N = F.submodule([x + y, x + y])
q, rel = M.module_quotient(N, relations=True)
assert q == R.ideal(y**2, x - y)
for i, g in enumerate(q.gens):
assert g*N.gens[0] == sum(c*x for c, x in zip(rel[i], M.gens))
def test_groebner_extendend():
M = QQ.old_poly_ring(x, y, z).free_module(3).submodule([x + 1, y, 1], [x*y, z, z**2])
G, R = M._groebner_vec(extended=True)
for i, g in enumerate(G):
assert g == sum(c*gen for c, gen in zip(R[i], M.gens))