12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484 |
- """
- 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.polyerrors import CoercionFailed
- from sympy.core.basic import _aresame
- from sympy.utilities.iterables import iterable
- # TODO
- # - module saturation
- # - module quotient/intersection for quotient rings
- # - free resoltutions / syzygies
- # - finding small/minimal generating sets
- # - ...
- ##########################################################################
- ## Abstract base classes #################################################
- ##########################################################################
- 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
- ##########################################################################
- ## Free Modules ##########################################################
- ##########################################################################
- 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
- return '[' + ', '.join(sstr(x) for x in self.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)
- ##########################################################################
- ## Submodules and subquotients ###########################################
- ##########################################################################
- 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]>, [(y,)], [(x,)])
- 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>, [[1]])
- 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))
- # NOTE we filter out zero syzygies. This is for convenience of the
- # _syzygies function and not meant to replace any real "generating set
- # reduction" algorithm
- 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])
- [-x**2 + x, x**2]
- """
- 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
- # XXX it is important for some code below that the generators of base
- # are in this particular order!
- 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):
- # let N = self.killed_module be generated by e_1, ..., e_r
- # let F = self.base be generated by f_1, ..., f_s and e_1, ..., e_r
- # Then self = F/N.
- # Let phi: R**s --> self be the evident surjection.
- # Similarly psi: R**(s + r) --> F.
- # We need to find generators for ker(phi). Let chi: R**s --> F be the
- # evident lift of phi. For X in R**s, phi(X) = 0 iff chi(X) is
- # contained in N, iff there exists Y in R**r such that
- # psi(X, Y) = 0.
- # Hence if alpha: R**(s + r) --> R**s is the projection map, then
- # ker(phi) = alpha ker(psi).
- 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
- """
- #self._gb - cached groebner basis
- #self._gbe - cached groebner basis relations
- 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]."""
- # NOTE if self.gens is a standard basis, this can be done more
- # efficiently using Schreyer's theorem
- # First bullet point
- 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)
- # Note: we need *descending* order on module index, and TOP=False to
- # get an elimination order
- F = Rkr.submodule(*newgens, order='ilex', TOP=False)
- # Second bullet point: standard basis of F
- G = F._groebner_vec()
- # Third bullet point: G0 = G intersect the new k components
- G0 = [x[r:] for x in G if all(y == zero for y in x[:r])]
- # Fourth and fifth bullet points: we are done
- return G0
- def _in_terms_of_generators(self, e):
- """Expression in terms of generators. See [SCA, 2.8.1]."""
- # NOTE: if gens is a standard basis, this can be done more efficiently
- M = self.ring.free_module(self.rank).submodule(*((e,) + self.gens))
- S = M.syzygy_module(
- order="ilex", TOP=False) # We want decreasing order!
- G = S._groebner_vec()
- # This list cannot not be empty since e is an element
- 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):
- # See: [SCA, section 2.8.2]
- 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):
- # See: [SCA, section 2.8.4]
- if relations and len(other.gens) != 1:
- raise NotImplementedError
- if len(other.gens) == 0:
- return self.ring.ideal(1)
- elif len(other.gens) == 1:
- # We do some trickery. Let f be the (vector!) generating ``other``
- # and f1, .., fn be the (vectors) generating self.
- # Consider the submodule of R^{r+1} generated by (f, 1) and
- # {(fi, 0) | i}. Then the intersection with the last module
- # component yields the quotient.
- g1 = list(other.gens[0]) + [1]
- gi = [list(x) + [0] for x in self.gens]
- # NOTE: We *need* to use an elimination order
- 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])
- # For more generators, we use I : <h1, .., hn> = intersection of
- # {I : <hi> | i}
- # TODO this can be done more efficiently
- 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))]
- ##########################################################################
- ## Quotient Modules ######################################################
- ##########################################################################
- 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)
|