| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488 |
- """
- 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
- # 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
- 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)
- ##########################################################################
- ## 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]>, [(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))
- # 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]) # 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
- # 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] = v
- 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)
|