|
""" |
|
Computations with modules over polynomial rings. |
|
|
|
This module implements various classes that encapsulate groebner basis |
|
computations for modules. Most of them should not be instantiated by hand. |
|
Instead, use the constructing routines on objects you already have. |
|
|
|
For example, to construct a free module over ``QQ[x, y]``, call |
|
``QQ[x, y].free_module(rank)`` instead of the ``FreeModule`` constructor. |
|
In fact ``FreeModule`` is an abstract base class that should not be |
|
instantiated, the ``free_module`` method instead returns the implementing class |
|
``FreeModulePolyRing``. |
|
|
|
In general, the abstract base classes implement most functionality in terms of |
|
a few non-implemented methods. The concrete base classes supply only these |
|
non-implemented methods. They may also supply new implementations of the |
|
convenience methods, for example if there are faster algorithms available. |
|
""" |
|
|
|
|
|
from copy import copy |
|
from functools import reduce |
|
|
|
from sympy.polys.agca.ideals import Ideal |
|
from sympy.polys.domains.field import Field |
|
from sympy.polys.orderings import ProductOrder, monomial_key |
|
from sympy.polys.polyclasses import DMP |
|
from sympy.polys.polyerrors import CoercionFailed |
|
from sympy.core.basic import _aresame |
|
from sympy.utilities.iterables import iterable |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Module: |
|
""" |
|
Abstract base class for modules. |
|
|
|
Do not instantiate - use ring explicit constructors instead: |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.abc import x |
|
>>> QQ.old_poly_ring(x).free_module(2) |
|
QQ[x]**2 |
|
|
|
Attributes: |
|
|
|
- dtype - type of elements |
|
- ring - containing ring |
|
|
|
Non-implemented methods: |
|
|
|
- submodule |
|
- quotient_module |
|
- is_zero |
|
- is_submodule |
|
- multiply_ideal |
|
|
|
The method convert likely needs to be changed in subclasses. |
|
""" |
|
|
|
def __init__(self, ring): |
|
self.ring = ring |
|
|
|
def convert(self, elem, M=None): |
|
""" |
|
Convert ``elem`` into internal representation of this module. |
|
|
|
If ``M`` is not None, it should be a module containing it. |
|
""" |
|
if not isinstance(elem, self.dtype): |
|
raise CoercionFailed |
|
return elem |
|
|
|
def submodule(self, *gens): |
|
"""Generate a submodule.""" |
|
raise NotImplementedError |
|
|
|
def quotient_module(self, other): |
|
"""Generate a quotient module.""" |
|
raise NotImplementedError |
|
|
|
def __truediv__(self, e): |
|
if not isinstance(e, Module): |
|
e = self.submodule(*e) |
|
return self.quotient_module(e) |
|
|
|
def contains(self, elem): |
|
"""Return True if ``elem`` is an element of this module.""" |
|
try: |
|
self.convert(elem) |
|
return True |
|
except CoercionFailed: |
|
return False |
|
|
|
def __contains__(self, elem): |
|
return self.contains(elem) |
|
|
|
def subset(self, other): |
|
""" |
|
Returns True if ``other`` is is a subset of ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.subset([(1, x), (x, 2)]) |
|
True |
|
>>> F.subset([(1/x, x), (x, 2)]) |
|
False |
|
""" |
|
return all(self.contains(x) for x in other) |
|
|
|
def __eq__(self, other): |
|
return self.is_submodule(other) and other.is_submodule(self) |
|
|
|
def __ne__(self, other): |
|
return not (self == other) |
|
|
|
def is_zero(self): |
|
"""Returns True if ``self`` is a zero module.""" |
|
raise NotImplementedError |
|
|
|
def is_submodule(self, other): |
|
"""Returns True if ``other`` is a submodule of ``self``.""" |
|
raise NotImplementedError |
|
|
|
def multiply_ideal(self, other): |
|
""" |
|
Multiply ``self`` by the ideal ``other``. |
|
""" |
|
raise NotImplementedError |
|
|
|
def __mul__(self, e): |
|
if not isinstance(e, Ideal): |
|
try: |
|
e = self.ring.ideal(e) |
|
except (CoercionFailed, NotImplementedError): |
|
return NotImplemented |
|
return self.multiply_ideal(e) |
|
|
|
__rmul__ = __mul__ |
|
|
|
def identity_hom(self): |
|
"""Return the identity homomorphism on ``self``.""" |
|
raise NotImplementedError |
|
|
|
|
|
class ModuleElement: |
|
""" |
|
Base class for module element wrappers. |
|
|
|
Use this class to wrap primitive data types as module elements. It stores |
|
a reference to the containing module, and implements all the arithmetic |
|
operators. |
|
|
|
Attributes: |
|
|
|
- module - containing module |
|
- data - internal data |
|
|
|
Methods that likely need change in subclasses: |
|
|
|
- add |
|
- mul |
|
- div |
|
- eq |
|
""" |
|
|
|
def __init__(self, module, data): |
|
self.module = module |
|
self.data = data |
|
|
|
def add(self, d1, d2): |
|
"""Add data ``d1`` and ``d2``.""" |
|
return d1 + d2 |
|
|
|
def mul(self, m, d): |
|
"""Multiply module data ``m`` by coefficient d.""" |
|
return m * d |
|
|
|
def div(self, m, d): |
|
"""Divide module data ``m`` by coefficient d.""" |
|
return m / d |
|
|
|
def eq(self, d1, d2): |
|
"""Return true if d1 and d2 represent the same element.""" |
|
return d1 == d2 |
|
|
|
def __add__(self, om): |
|
if not isinstance(om, self.__class__) or om.module != self.module: |
|
try: |
|
om = self.module.convert(om) |
|
except CoercionFailed: |
|
return NotImplemented |
|
return self.__class__(self.module, self.add(self.data, om.data)) |
|
|
|
__radd__ = __add__ |
|
|
|
def __neg__(self): |
|
return self.__class__(self.module, self.mul(self.data, |
|
self.module.ring.convert(-1))) |
|
|
|
def __sub__(self, om): |
|
if not isinstance(om, self.__class__) or om.module != self.module: |
|
try: |
|
om = self.module.convert(om) |
|
except CoercionFailed: |
|
return NotImplemented |
|
return self.__add__(-om) |
|
|
|
def __rsub__(self, om): |
|
return (-self).__add__(om) |
|
|
|
def __mul__(self, o): |
|
if not isinstance(o, self.module.ring.dtype): |
|
try: |
|
o = self.module.ring.convert(o) |
|
except CoercionFailed: |
|
return NotImplemented |
|
return self.__class__(self.module, self.mul(self.data, o)) |
|
|
|
__rmul__ = __mul__ |
|
|
|
def __truediv__(self, o): |
|
if not isinstance(o, self.module.ring.dtype): |
|
try: |
|
o = self.module.ring.convert(o) |
|
except CoercionFailed: |
|
return NotImplemented |
|
return self.__class__(self.module, self.div(self.data, o)) |
|
|
|
def __eq__(self, om): |
|
if not isinstance(om, self.__class__) or om.module != self.module: |
|
try: |
|
om = self.module.convert(om) |
|
except CoercionFailed: |
|
return False |
|
return self.eq(self.data, om.data) |
|
|
|
def __ne__(self, om): |
|
return not self == om |
|
|
|
|
|
|
|
|
|
|
|
|
|
class FreeModuleElement(ModuleElement): |
|
"""Element of a free module. Data stored as a tuple.""" |
|
|
|
def add(self, d1, d2): |
|
return tuple(x + y for x, y in zip(d1, d2)) |
|
|
|
def mul(self, d, p): |
|
return tuple(x * p for x in d) |
|
|
|
def div(self, d, p): |
|
return tuple(x / p for x in d) |
|
|
|
def __repr__(self): |
|
from sympy.printing.str import sstr |
|
data = self.data |
|
if any(isinstance(x, DMP) for x in data): |
|
data = [self.module.ring.to_sympy(x) for x in data] |
|
return '[' + ', '.join(sstr(x) for x in data) + ']' |
|
|
|
def __iter__(self): |
|
return self.data.__iter__() |
|
|
|
def __getitem__(self, idx): |
|
return self.data[idx] |
|
|
|
|
|
class FreeModule(Module): |
|
""" |
|
Abstract base class for free modules. |
|
|
|
Additional attributes: |
|
|
|
- rank - rank of the free module |
|
|
|
Non-implemented methods: |
|
|
|
- submodule |
|
""" |
|
|
|
dtype = FreeModuleElement |
|
|
|
def __init__(self, ring, rank): |
|
Module.__init__(self, ring) |
|
self.rank = rank |
|
|
|
def __repr__(self): |
|
return repr(self.ring) + "**" + repr(self.rank) |
|
|
|
def is_submodule(self, other): |
|
""" |
|
Returns True if ``other`` is a submodule of ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> M = F.submodule([2, x]) |
|
>>> F.is_submodule(F) |
|
True |
|
>>> F.is_submodule(M) |
|
True |
|
>>> M.is_submodule(F) |
|
False |
|
""" |
|
if isinstance(other, SubModule): |
|
return other.container == self |
|
if isinstance(other, FreeModule): |
|
return other.ring == self.ring and other.rank == self.rank |
|
return False |
|
|
|
def convert(self, elem, M=None): |
|
""" |
|
Convert ``elem`` into the internal representation. |
|
|
|
This method is called implicitly whenever computations involve elements |
|
not in the internal representation. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.convert([1, 0]) |
|
[1, 0] |
|
""" |
|
if isinstance(elem, FreeModuleElement): |
|
if elem.module is self: |
|
return elem |
|
if elem.module.rank != self.rank: |
|
raise CoercionFailed |
|
return FreeModuleElement(self, |
|
tuple(self.ring.convert(x, elem.module.ring) for x in elem.data)) |
|
elif iterable(elem): |
|
tpl = tuple(self.ring.convert(x) for x in elem) |
|
if len(tpl) != self.rank: |
|
raise CoercionFailed |
|
return FreeModuleElement(self, tpl) |
|
elif _aresame(elem, 0): |
|
return FreeModuleElement(self, (self.ring.convert(0),)*self.rank) |
|
else: |
|
raise CoercionFailed |
|
|
|
def is_zero(self): |
|
""" |
|
Returns True if ``self`` is a zero module. |
|
|
|
(If, as this implementation assumes, the coefficient ring is not the |
|
zero ring, then this is equivalent to the rank being zero.) |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(0).is_zero() |
|
True |
|
>>> QQ.old_poly_ring(x).free_module(1).is_zero() |
|
False |
|
""" |
|
return self.rank == 0 |
|
|
|
def basis(self): |
|
""" |
|
Return a set of basis elements. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(3).basis() |
|
([1, 0, 0], [0, 1, 0], [0, 0, 1]) |
|
""" |
|
from sympy.matrices import eye |
|
M = eye(self.rank) |
|
return tuple(self.convert(M.row(i)) for i in range(self.rank)) |
|
|
|
def quotient_module(self, submodule): |
|
""" |
|
Return a quotient module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x).free_module(2) |
|
>>> M.quotient_module(M.submodule([1, x], [x, 2])) |
|
QQ[x]**2/<[1, x], [x, 2]> |
|
|
|
Or more conicisely, using the overloaded division operator: |
|
|
|
>>> QQ.old_poly_ring(x).free_module(2) / [[1, x], [x, 2]] |
|
QQ[x]**2/<[1, x], [x, 2]> |
|
""" |
|
return QuotientModule(self.ring, self, submodule) |
|
|
|
def multiply_ideal(self, other): |
|
""" |
|
Multiply ``self`` by the ideal ``other``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> I = QQ.old_poly_ring(x).ideal(x) |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.multiply_ideal(I) |
|
<[x, 0], [0, x]> |
|
""" |
|
return self.submodule(*self.basis()).multiply_ideal(other) |
|
|
|
def identity_hom(self): |
|
""" |
|
Return the identity homomorphism on ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(2).identity_hom() |
|
Matrix([ |
|
[1, 0], : QQ[x]**2 -> QQ[x]**2 |
|
[0, 1]]) |
|
""" |
|
from sympy.polys.agca.homomorphisms import homomorphism |
|
return homomorphism(self, self, self.basis()) |
|
|
|
|
|
class FreeModulePolyRing(FreeModule): |
|
""" |
|
Free module over a generalized polynomial ring. |
|
|
|
Do not instantiate this, use the constructor method of the ring instead: |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(3) |
|
>>> F |
|
QQ[x]**3 |
|
>>> F.contains([x, 1, 0]) |
|
True |
|
>>> F.contains([1/x, 0, 1]) |
|
False |
|
""" |
|
|
|
def __init__(self, ring, rank): |
|
from sympy.polys.domains.old_polynomialring import PolynomialRingBase |
|
FreeModule.__init__(self, ring, rank) |
|
if not isinstance(ring, PolynomialRingBase): |
|
raise NotImplementedError('This implementation only works over ' |
|
+ 'polynomial rings, got %s' % ring) |
|
if not isinstance(ring.dom, Field): |
|
raise NotImplementedError('Ground domain must be a field, ' |
|
+ 'got %s' % ring.dom) |
|
|
|
def submodule(self, *gens, **opts): |
|
""" |
|
Generate a submodule. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, x + y]) |
|
>>> M |
|
<[x, x + y]> |
|
>>> M.contains([2*x, 2*x + 2*y]) |
|
True |
|
>>> M.contains([x, y]) |
|
False |
|
""" |
|
return SubModulePolyRing(gens, self, **opts) |
|
|
|
|
|
class FreeModuleQuotientRing(FreeModule): |
|
""" |
|
Free module over a quotient ring. |
|
|
|
Do not instantiate this, use the constructor method of the ring instead: |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(3) |
|
>>> F |
|
(QQ[x]/<x**2 + 1>)**3 |
|
|
|
Attributes |
|
|
|
- quot - the quotient module `R^n / IR^n`, where `R/I` is our ring |
|
""" |
|
|
|
def __init__(self, ring, rank): |
|
from sympy.polys.domains.quotientring import QuotientRing |
|
FreeModule.__init__(self, ring, rank) |
|
if not isinstance(ring, QuotientRing): |
|
raise NotImplementedError('This implementation only works over ' |
|
+ 'quotient rings, got %s' % ring) |
|
F = self.ring.ring.free_module(self.rank) |
|
self.quot = F / (self.ring.base_ideal*F) |
|
|
|
def __repr__(self): |
|
return "(" + repr(self.ring) + ")" + "**" + repr(self.rank) |
|
|
|
def submodule(self, *gens, **opts): |
|
""" |
|
Generate a submodule. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y]) |
|
>>> M |
|
<[x + <x**2 - y**2>, x + y + <x**2 - y**2>]> |
|
>>> M.contains([y**2, x**2 + x*y]) |
|
True |
|
>>> M.contains([x, y]) |
|
False |
|
""" |
|
return SubModuleQuotientRing(gens, self, **opts) |
|
|
|
def lift(self, elem): |
|
""" |
|
Lift the element ``elem`` of self to the module self.quot. |
|
|
|
Note that self.quot is the same set as self, just as an R-module |
|
and not as an R/I-module, so this makes sense. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2) |
|
>>> e = F.convert([1, 0]) |
|
>>> e |
|
[1 + <x**2 + 1>, 0 + <x**2 + 1>] |
|
>>> L = F.quot |
|
>>> l = F.lift(e) |
|
>>> l |
|
[1, 0] + <[x**2 + 1, 0], [0, x**2 + 1]> |
|
>>> L.contains(l) |
|
True |
|
""" |
|
return self.quot.convert([x.data for x in elem]) |
|
|
|
def unlift(self, elem): |
|
""" |
|
Push down an element of self.quot to self. |
|
|
|
This undoes ``lift``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2) |
|
>>> e = F.convert([1, 0]) |
|
>>> l = F.lift(e) |
|
>>> e == l |
|
False |
|
>>> e == F.unlift(l) |
|
True |
|
""" |
|
return self.convert(elem.data) |
|
|
|
|
|
|
|
|
|
|
|
|
|
class SubModule(Module): |
|
""" |
|
Base class for submodules. |
|
|
|
Attributes: |
|
|
|
- container - containing module |
|
- gens - generators (subset of containing module) |
|
- rank - rank of containing module |
|
|
|
Non-implemented methods: |
|
|
|
- _contains |
|
- _syzygies |
|
- _in_terms_of_generators |
|
- _intersect |
|
- _module_quotient |
|
|
|
Methods that likely need change in subclasses: |
|
|
|
- reduce_element |
|
""" |
|
|
|
def __init__(self, gens, container): |
|
Module.__init__(self, container.ring) |
|
self.gens = tuple(container.convert(x) for x in gens) |
|
self.container = container |
|
self.rank = container.rank |
|
self.ring = container.ring |
|
self.dtype = container.dtype |
|
|
|
def __repr__(self): |
|
return "<" + ", ".join(repr(x) for x in self.gens) + ">" |
|
|
|
def _contains(self, other): |
|
"""Implementation of containment. |
|
Other is guaranteed to be FreeModuleElement.""" |
|
raise NotImplementedError |
|
|
|
def _syzygies(self): |
|
"""Implementation of syzygy computation wrt self generators.""" |
|
raise NotImplementedError |
|
|
|
def _in_terms_of_generators(self, e): |
|
"""Implementation of expression in terms of generators.""" |
|
raise NotImplementedError |
|
|
|
def convert(self, elem, M=None): |
|
""" |
|
Convert ``elem`` into the internal represantition. |
|
|
|
Mostly called implicitly. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, x]) |
|
>>> M.convert([2, 2*x]) |
|
[2, 2*x] |
|
""" |
|
if isinstance(elem, self.container.dtype) and elem.module is self: |
|
return elem |
|
r = copy(self.container.convert(elem, M)) |
|
r.module = self |
|
if not self._contains(r): |
|
raise CoercionFailed |
|
return r |
|
|
|
def _intersect(self, other): |
|
"""Implementation of intersection. |
|
Other is guaranteed to be a submodule of same free module.""" |
|
raise NotImplementedError |
|
|
|
def _module_quotient(self, other): |
|
"""Implementation of quotient. |
|
Other is guaranteed to be a submodule of same free module.""" |
|
raise NotImplementedError |
|
|
|
def intersect(self, other, **options): |
|
""" |
|
Returns the intersection of ``self`` with submodule ``other``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x, y).free_module(2) |
|
>>> F.submodule([x, x]).intersect(F.submodule([y, y])) |
|
<[x*y, x*y]> |
|
|
|
Some implementation allow further options to be passed. Currently, to |
|
only one implemented is ``relations=True``, in which case the function |
|
will return a triple ``(res, rela, relb)``, where ``res`` is the |
|
intersection module, and ``rela`` and ``relb`` are lists of coefficient |
|
vectors, expressing the generators of ``res`` in terms of the |
|
generators of ``self`` (``rela``) and ``other`` (``relb``). |
|
|
|
>>> F.submodule([x, x]).intersect(F.submodule([y, y]), relations=True) |
|
(<[x*y, x*y]>, [(DMP_Python([[1, 0]], QQ),)], [(DMP_Python([[1], []], QQ),)]) |
|
|
|
The above result says: the intersection module is generated by the |
|
single element `(-xy, -xy) = -y (x, x) = -x (y, y)`, where |
|
`(x, x)` and `(y, y)` respectively are the unique generators of |
|
the two modules being intersected. |
|
""" |
|
if not isinstance(other, SubModule): |
|
raise TypeError('%s is not a SubModule' % other) |
|
if other.container != self.container: |
|
raise ValueError( |
|
'%s is contained in a different free module' % other) |
|
return self._intersect(other, **options) |
|
|
|
def module_quotient(self, other, **options): |
|
r""" |
|
Returns the module quotient of ``self`` by submodule ``other``. |
|
|
|
That is, if ``self`` is the module `M` and ``other`` is `N`, then |
|
return the ideal `\{f \in R | fN \subset M\}`. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import QQ |
|
>>> from sympy.abc import x, y |
|
>>> F = QQ.old_poly_ring(x, y).free_module(2) |
|
>>> S = F.submodule([x*y, x*y]) |
|
>>> T = F.submodule([x, x]) |
|
>>> S.module_quotient(T) |
|
<y> |
|
|
|
Some implementations allow further options to be passed. Currently, the |
|
only one implemented is ``relations=True``, which may only be passed |
|
if ``other`` is principal. In this case the function |
|
will return a pair ``(res, rel)`` where ``res`` is the ideal, and |
|
``rel`` is a list of coefficient vectors, expressing the generators of |
|
the ideal, multiplied by the generator of ``other`` in terms of |
|
generators of ``self``. |
|
|
|
>>> S.module_quotient(T, relations=True) |
|
(<y>, [[DMP_Python([[1]], QQ)]]) |
|
|
|
This means that the quotient ideal is generated by the single element |
|
`y`, and that `y (x, x) = 1 (xy, xy)`, `(x, x)` and `(xy, xy)` being |
|
the generators of `T` and `S`, respectively. |
|
""" |
|
if not isinstance(other, SubModule): |
|
raise TypeError('%s is not a SubModule' % other) |
|
if other.container != self.container: |
|
raise ValueError( |
|
'%s is contained in a different free module' % other) |
|
return self._module_quotient(other, **options) |
|
|
|
def union(self, other): |
|
""" |
|
Returns the module generated by the union of ``self`` and ``other``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(1) |
|
>>> M = F.submodule([x**2 + x]) # <x(x+1)> |
|
>>> N = F.submodule([x**2 - 1]) # <(x-1)(x+1)> |
|
>>> M.union(N) == F.submodule([x+1]) |
|
True |
|
""" |
|
if not isinstance(other, SubModule): |
|
raise TypeError('%s is not a SubModule' % other) |
|
if other.container != self.container: |
|
raise ValueError( |
|
'%s is contained in a different free module' % other) |
|
return self.__class__(self.gens + other.gens, self.container) |
|
|
|
def is_zero(self): |
|
""" |
|
Return True if ``self`` is a zero module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.submodule([x, 1]).is_zero() |
|
False |
|
>>> F.submodule([0, 0]).is_zero() |
|
True |
|
""" |
|
return all(x == 0 for x in self.gens) |
|
|
|
def submodule(self, *gens): |
|
""" |
|
Generate a submodule. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([x, 1]) |
|
>>> M.submodule([x**2, x]) |
|
<[x**2, x]> |
|
""" |
|
if not self.subset(gens): |
|
raise ValueError('%s not a subset of %s' % (gens, self)) |
|
return self.__class__(gens, self.container) |
|
|
|
def is_full_module(self): |
|
""" |
|
Return True if ``self`` is the entire free module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.submodule([x, 1]).is_full_module() |
|
False |
|
>>> F.submodule([1, 1], [1, 2]).is_full_module() |
|
True |
|
""" |
|
return all(self.contains(x) for x in self.container.basis()) |
|
|
|
def is_submodule(self, other): |
|
""" |
|
Returns True if ``other`` is a submodule of ``self``. |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> M = F.submodule([2, x]) |
|
>>> N = M.submodule([2*x, x**2]) |
|
>>> M.is_submodule(M) |
|
True |
|
>>> M.is_submodule(N) |
|
True |
|
>>> N.is_submodule(M) |
|
False |
|
""" |
|
if isinstance(other, SubModule): |
|
return self.container == other.container and \ |
|
all(self.contains(x) for x in other.gens) |
|
if isinstance(other, (FreeModule, QuotientModule)): |
|
return self.container == other and self.is_full_module() |
|
return False |
|
|
|
def syzygy_module(self, **opts): |
|
r""" |
|
Compute the syzygy module of the generators of ``self``. |
|
|
|
Suppose `M` is generated by `f_1, \ldots, f_n` over the ring |
|
`R`. Consider the homomorphism `\phi: R^n \to M`, given by |
|
sending `(r_1, \ldots, r_n) \to r_1 f_1 + \cdots + r_n f_n`. |
|
The syzygy module is defined to be the kernel of `\phi`. |
|
|
|
Examples |
|
======== |
|
|
|
The syzygy module is zero iff the generators generate freely a free |
|
submodule: |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(2).submodule([1, 0], [1, 1]).syzygy_module().is_zero() |
|
True |
|
|
|
A slightly more interesting example: |
|
|
|
>>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, 2*x], [y, 2*y]) |
|
>>> S = QQ.old_poly_ring(x, y).free_module(2).submodule([y, -x]) |
|
>>> M.syzygy_module() == S |
|
True |
|
""" |
|
F = self.ring.free_module(len(self.gens)) |
|
|
|
|
|
|
|
return F.submodule(*[x for x in self._syzygies() if F.convert(x) != 0], |
|
**opts) |
|
|
|
def in_terms_of_generators(self, e): |
|
""" |
|
Express element ``e`` of ``self`` in terms of the generators. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> M = F.submodule([1, 0], [1, 1]) |
|
>>> M.in_terms_of_generators([x, x**2]) # doctest: +SKIP |
|
[DMP_Python([-1, 1, 0], QQ), DMP_Python([1, 0, 0], QQ)] |
|
""" |
|
try: |
|
e = self.convert(e) |
|
except CoercionFailed: |
|
raise ValueError('%s is not an element of %s' % (e, self)) |
|
return self._in_terms_of_generators(e) |
|
|
|
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 quotient_module(self, other, **opts): |
|
""" |
|
Return a quotient module. |
|
|
|
This is the same as taking a submodule of a quotient of the containing |
|
module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> S1 = F.submodule([x, 1]) |
|
>>> S2 = F.submodule([x**2, x]) |
|
>>> S1.quotient_module(S2) |
|
<[x, 1] + <[x**2, x]>> |
|
|
|
Or more coincisely, using the overloaded division operator: |
|
|
|
>>> F.submodule([x, 1]) / [(x**2, x)] |
|
<[x, 1] + <[x**2, x]>> |
|
""" |
|
if not self.is_submodule(other): |
|
raise ValueError('%s not a submodule of %s' % (other, self)) |
|
return SubQuotientModule(self.gens, |
|
self.container.quotient_module(other), **opts) |
|
|
|
def __add__(self, oth): |
|
return self.container.quotient_module(self).convert(oth) |
|
|
|
__radd__ = __add__ |
|
|
|
def multiply_ideal(self, I): |
|
""" |
|
Multiply ``self`` by the ideal ``I``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> I = QQ.old_poly_ring(x).ideal(x**2) |
|
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, 1]) |
|
>>> I*M |
|
<[x**2, x**2]> |
|
""" |
|
return self.submodule(*[x*g for [x] in I._module.gens for g in self.gens]) |
|
|
|
def inclusion_hom(self): |
|
""" |
|
Return a homomorphism representing the inclusion map of ``self``. |
|
|
|
That is, the natural map from ``self`` to ``self.container``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).inclusion_hom() |
|
Matrix([ |
|
[1, 0], : <[x, x]> -> QQ[x]**2 |
|
[0, 1]]) |
|
""" |
|
return self.container.identity_hom().restrict_domain(self) |
|
|
|
def identity_hom(self): |
|
""" |
|
Return the identity homomorphism on ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).identity_hom() |
|
Matrix([ |
|
[1, 0], : <[x, x]> -> <[x, x]> |
|
[0, 1]]) |
|
""" |
|
return self.container.identity_hom().restrict_domain( |
|
self).restrict_codomain(self) |
|
|
|
|
|
class SubQuotientModule(SubModule): |
|
""" |
|
Submodule of a quotient module. |
|
|
|
Equivalently, quotient module of a submodule. |
|
|
|
Do not instantiate this, instead use the submodule or quotient_module |
|
constructing methods: |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> S = F.submodule([1, 0], [1, x]) |
|
>>> Q = F/[(1, 0)] |
|
>>> S/[(1, 0)] == Q.submodule([5, x]) |
|
True |
|
|
|
Attributes: |
|
|
|
- base - base module we are quotient of |
|
- killed_module - submodule used to form the quotient |
|
""" |
|
def __init__(self, gens, container, **opts): |
|
SubModule.__init__(self, gens, container) |
|
self.killed_module = self.container.killed_module |
|
|
|
|
|
self.base = self.container.base.submodule( |
|
*[x.data for x in self.gens], **opts).union(self.killed_module) |
|
|
|
def _contains(self, elem): |
|
return self.base.contains(elem.data) |
|
|
|
def _syzygies(self): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [X[:len(self.gens)] for X in self.base._syzygies()] |
|
|
|
def _in_terms_of_generators(self, e): |
|
return self.base._in_terms_of_generators(e.data)[:len(self.gens)] |
|
|
|
def is_full_module(self): |
|
""" |
|
Return True if ``self`` is the entire free module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> F.submodule([x, 1]).is_full_module() |
|
False |
|
>>> F.submodule([1, 1], [1, 2]).is_full_module() |
|
True |
|
""" |
|
return self.base.is_full_module() |
|
|
|
def quotient_hom(self): |
|
""" |
|
Return the quotient homomorphism to self. |
|
|
|
That is, return the natural map from ``self.base`` to ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = (QQ.old_poly_ring(x).free_module(2) / [(1, x)]).submodule([1, 0]) |
|
>>> M.quotient_hom() |
|
Matrix([ |
|
[1, 0], : <[1, 0], [1, x]> -> <[1, 0] + <[1, x]>, [1, x] + <[1, x]>> |
|
[0, 1]]) |
|
""" |
|
return self.base.identity_hom().quotient_codomain(self.killed_module) |
|
|
|
|
|
_subs0 = lambda x: x[0] |
|
_subs1 = lambda x: x[1:] |
|
|
|
|
|
class ModuleOrder(ProductOrder): |
|
"""A product monomial order with a zeroth term as module index.""" |
|
|
|
def __init__(self, o1, o2, TOP): |
|
if TOP: |
|
ProductOrder.__init__(self, (o2, _subs1), (o1, _subs0)) |
|
else: |
|
ProductOrder.__init__(self, (o1, _subs0), (o2, _subs1)) |
|
|
|
|
|
class SubModulePolyRing(SubModule): |
|
""" |
|
Submodule of a free module over a generalized polynomial ring. |
|
|
|
Do not instantiate this, use the constructor method of FreeModule instead: |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x, y).free_module(2) |
|
>>> F.submodule([x, y], [1, 0]) |
|
<[x, y], [1, 0]> |
|
|
|
Attributes: |
|
|
|
- order - monomial order used |
|
""" |
|
|
|
|
|
|
|
|
|
def __init__(self, gens, container, order="lex", TOP=True): |
|
SubModule.__init__(self, gens, container) |
|
if not isinstance(container, FreeModulePolyRing): |
|
raise NotImplementedError('This implementation is for submodules of ' |
|
+ 'FreeModulePolyRing, got %s' % container) |
|
self.order = ModuleOrder(monomial_key(order), self.ring.order, TOP) |
|
self._gb = None |
|
self._gbe = None |
|
|
|
def __eq__(self, other): |
|
if isinstance(other, SubModulePolyRing) and self.order != other.order: |
|
return False |
|
return SubModule.__eq__(self, other) |
|
|
|
def _groebner(self, extended=False): |
|
"""Returns a standard basis in sdm form.""" |
|
from sympy.polys.distributedmodules import sdm_groebner, sdm_nf_mora |
|
if self._gbe is None and extended: |
|
gb, gbe = sdm_groebner( |
|
[self.ring._vector_to_sdm(x, self.order) for x in self.gens], |
|
sdm_nf_mora, self.order, self.ring.dom, extended=True) |
|
self._gb, self._gbe = tuple(gb), tuple(gbe) |
|
if self._gb is None: |
|
self._gb = tuple(sdm_groebner( |
|
[self.ring._vector_to_sdm(x, self.order) for x in self.gens], |
|
sdm_nf_mora, self.order, self.ring.dom)) |
|
if extended: |
|
return self._gb, self._gbe |
|
else: |
|
return self._gb |
|
|
|
def _groebner_vec(self, extended=False): |
|
"""Returns a standard basis in element form.""" |
|
if not extended: |
|
return [FreeModuleElement(self, |
|
tuple(self.ring._sdm_to_vector(x, self.rank))) |
|
for x in self._groebner()] |
|
gb, gbe = self._groebner(extended=True) |
|
return ([self.convert(self.ring._sdm_to_vector(x, self.rank)) |
|
for x in gb], |
|
[self.ring._sdm_to_vector(x, len(self.gens)) for x in gbe]) |
|
|
|
def _contains(self, x): |
|
from sympy.polys.distributedmodules import sdm_zero, sdm_nf_mora |
|
return sdm_nf_mora(self.ring._vector_to_sdm(x, self.order), |
|
self._groebner(), self.order, self.ring.dom) == \ |
|
sdm_zero() |
|
|
|
def _syzygies(self): |
|
"""Compute syzygies. See [SCA, algorithm 2.5.4].""" |
|
|
|
|
|
|
|
|
|
k = len(self.gens) |
|
r = self.rank |
|
zero = self.ring.convert(0) |
|
one = self.ring.convert(1) |
|
Rkr = self.ring.free_module(r + k) |
|
newgens = [] |
|
for j, f in enumerate(self.gens): |
|
m = [0]*(r + k) |
|
for i, v in enumerate(f): |
|
m[i] = f[i] |
|
for i in range(k): |
|
m[r + i] = one if j == i else zero |
|
m = FreeModuleElement(Rkr, tuple(m)) |
|
newgens.append(m) |
|
|
|
|
|
F = Rkr.submodule(*newgens, order='ilex', TOP=False) |
|
|
|
|
|
G = F._groebner_vec() |
|
|
|
|
|
G0 = [x[r:] for x in G if all(y == zero for y in x[:r])] |
|
|
|
|
|
return G0 |
|
|
|
def _in_terms_of_generators(self, e): |
|
"""Expression in terms of generators. See [SCA, 2.8.1].""" |
|
|
|
M = self.ring.free_module(self.rank).submodule(*((e,) + self.gens)) |
|
S = M.syzygy_module( |
|
order="ilex", TOP=False) |
|
G = S._groebner_vec() |
|
|
|
e = [x for x in G if self.ring.is_unit(x[0])][0] |
|
return [-x/e[0] for x in e[1:]] |
|
|
|
def reduce_element(self, x, NF=None): |
|
""" |
|
Reduce the element ``x`` of our container modulo ``self``. |
|
|
|
This applies the normal form ``NF`` to ``x``. If ``NF`` is passed |
|
as none, the default Mora normal form is used (which is not unique!). |
|
""" |
|
from sympy.polys.distributedmodules import sdm_nf_mora |
|
if NF is None: |
|
NF = sdm_nf_mora |
|
return self.container.convert(self.ring._sdm_to_vector(NF( |
|
self.ring._vector_to_sdm(x, self.order), self._groebner(), |
|
self.order, self.ring.dom), |
|
self.rank)) |
|
|
|
def _intersect(self, other, relations=False): |
|
|
|
fi = self.gens |
|
hi = other.gens |
|
r = self.rank |
|
ci = [[0]*(2*r) for _ in range(r)] |
|
for k in range(r): |
|
ci[k][k] = 1 |
|
ci[k][r + k] = 1 |
|
di = [list(f) + [0]*r for f in fi] |
|
ei = [[0]*r + list(h) for h in hi] |
|
syz = self.ring.free_module(2*r).submodule(*(ci + di + ei))._syzygies() |
|
nonzero = [x for x in syz if any(y != self.ring.zero for y in x[:r])] |
|
res = self.container.submodule(*([-y for y in x[:r]] for x in nonzero)) |
|
reln1 = [x[r:r + len(fi)] for x in nonzero] |
|
reln2 = [x[r + len(fi):] for x in nonzero] |
|
if relations: |
|
return res, reln1, reln2 |
|
return res |
|
|
|
def _module_quotient(self, other, relations=False): |
|
|
|
if relations and len(other.gens) != 1: |
|
raise NotImplementedError |
|
if len(other.gens) == 0: |
|
return self.ring.ideal(1) |
|
elif len(other.gens) == 1: |
|
|
|
|
|
|
|
|
|
|
|
g1 = list(other.gens[0]) + [1] |
|
gi = [list(x) + [0] for x in self.gens] |
|
|
|
M = self.ring.free_module(self.rank + 1).submodule(*([g1] + gi), |
|
order='ilex', TOP=False) |
|
if not relations: |
|
return self.ring.ideal(*[x[-1] for x in M._groebner_vec() if |
|
all(y == self.ring.zero for y in x[:-1])]) |
|
else: |
|
G, R = M._groebner_vec(extended=True) |
|
indices = [i for i, x in enumerate(G) if |
|
all(y == self.ring.zero for y in x[:-1])] |
|
return (self.ring.ideal(*[G[i][-1] for i in indices]), |
|
[[-x for x in R[i][1:]] for i in indices]) |
|
|
|
|
|
|
|
return reduce(lambda x, y: x.intersect(y), |
|
(self._module_quotient(self.container.submodule(x)) for x in other.gens)) |
|
|
|
|
|
class SubModuleQuotientRing(SubModule): |
|
""" |
|
Class for submodules of free modules over quotient rings. |
|
|
|
Do not instantiate this. Instead use the submodule methods. |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import QQ |
|
>>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y]) |
|
>>> M |
|
<[x + <x**2 - y**2>, x + y + <x**2 - y**2>]> |
|
>>> M.contains([y**2, x**2 + x*y]) |
|
True |
|
>>> M.contains([x, y]) |
|
False |
|
|
|
Attributes: |
|
|
|
- quot - the subquotient of `R^n/IR^n` generated by lifts of our generators |
|
""" |
|
|
|
def __init__(self, gens, container): |
|
SubModule.__init__(self, gens, container) |
|
self.quot = self.container.quot.submodule( |
|
*[self.container.lift(x) for x in self.gens]) |
|
|
|
def _contains(self, elem): |
|
return self.quot._contains(self.container.lift(elem)) |
|
|
|
def _syzygies(self): |
|
return [tuple(self.ring.convert(y, self.quot.ring) for y in x) |
|
for x in self.quot._syzygies()] |
|
|
|
def _in_terms_of_generators(self, elem): |
|
return [self.ring.convert(x, self.quot.ring) for x in |
|
self.quot._in_terms_of_generators(self.container.lift(elem))] |
|
|
|
|
|
|
|
|
|
|
|
|
|
class QuotientModuleElement(ModuleElement): |
|
"""Element of a quotient module.""" |
|
|
|
def eq(self, d1, d2): |
|
"""Equality comparison.""" |
|
return self.module.killed_module.contains(d1 - d2) |
|
|
|
def __repr__(self): |
|
return repr(self.data) + " + " + repr(self.module.killed_module) |
|
|
|
|
|
class QuotientModule(Module): |
|
""" |
|
Class for quotient modules. |
|
|
|
Do not instantiate this directly. For subquotients, see the |
|
SubQuotientModule class. |
|
|
|
Attributes: |
|
|
|
- base - the base module we are a quotient of |
|
- killed_module - the submodule used to form the quotient |
|
- rank of the base |
|
""" |
|
|
|
dtype = QuotientModuleElement |
|
|
|
def __init__(self, ring, base, submodule): |
|
Module.__init__(self, ring) |
|
if not base.is_submodule(submodule): |
|
raise ValueError('%s is not a submodule of %s' % (submodule, base)) |
|
self.base = base |
|
self.killed_module = submodule |
|
self.rank = base.rank |
|
|
|
def __repr__(self): |
|
return repr(self.base) + "/" + repr(self.killed_module) |
|
|
|
def is_zero(self): |
|
""" |
|
Return True if ``self`` is a zero module. |
|
|
|
This happens if and only if the base module is the same as the |
|
submodule being killed. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) |
|
>>> (F/[(1, 0)]).is_zero() |
|
False |
|
>>> (F/[(1, 0), (0, 1)]).is_zero() |
|
True |
|
""" |
|
return self.base == self.killed_module |
|
|
|
def is_submodule(self, other): |
|
""" |
|
Return True if ``other`` is a submodule of ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)] |
|
>>> S = Q.submodule([1, 0]) |
|
>>> Q.is_submodule(S) |
|
True |
|
>>> S.is_submodule(Q) |
|
False |
|
""" |
|
if isinstance(other, QuotientModule): |
|
return self.killed_module == other.killed_module and \ |
|
self.base.is_submodule(other.base) |
|
if isinstance(other, SubQuotientModule): |
|
return other.container == self |
|
return False |
|
|
|
def submodule(self, *gens, **opts): |
|
""" |
|
Generate a submodule. |
|
|
|
This is the same as taking a quotient of a submodule of the base |
|
module. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)] |
|
>>> Q.submodule([x, 0]) |
|
<[x, 0] + <[x, x]>> |
|
""" |
|
return SubQuotientModule(gens, self, **opts) |
|
|
|
def convert(self, elem, M=None): |
|
""" |
|
Convert ``elem`` into the internal representation. |
|
|
|
This method is called implicitly whenever computations involve elements |
|
not in the internal representation. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> F = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] |
|
>>> F.convert([1, 0]) |
|
[1, 0] + <[1, 2], [1, x]> |
|
""" |
|
if isinstance(elem, QuotientModuleElement): |
|
if elem.module is self: |
|
return elem |
|
if self.killed_module.is_submodule(elem.module.killed_module): |
|
return QuotientModuleElement(self, self.base.convert(elem.data)) |
|
raise CoercionFailed |
|
return QuotientModuleElement(self, self.base.convert(elem)) |
|
|
|
def identity_hom(self): |
|
""" |
|
Return the identity homomorphism on ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] |
|
>>> M.identity_hom() |
|
Matrix([ |
|
[1, 0], : QQ[x]**2/<[1, 2], [1, x]> -> QQ[x]**2/<[1, 2], [1, x]> |
|
[0, 1]]) |
|
""" |
|
return self.base.identity_hom().quotient_codomain( |
|
self.killed_module).quotient_domain(self.killed_module) |
|
|
|
def quotient_hom(self): |
|
""" |
|
Return the quotient homomorphism to ``self``. |
|
|
|
That is, return a homomorphism representing the natural map from |
|
``self.base`` to ``self``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import QQ |
|
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] |
|
>>> M.quotient_hom() |
|
Matrix([ |
|
[1, 0], : QQ[x]**2 -> QQ[x]**2/<[1, 2], [1, x]> |
|
[0, 1]]) |
|
""" |
|
return self.base.identity_hom().quotient_codomain( |
|
self.killed_module) |
|
|