12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111 |
- r"""Modules in number fields.
- The classes defined here allow us to work with finitely generated, free
- modules, whose generators are algebraic numbers.
- There is an abstract base class called :py:class:`~.Module`, which has two
- concrete subclasses, :py:class:`~.PowerBasis` and :py:class:`~.Submodule`.
- Every module is defined by its basis, or set of generators:
- * For a :py:class:`~.PowerBasis`, the generators are the first $n$ powers
- (starting with the zeroth) of an algebraic integer $\theta$ of degree $n$.
- The :py:class:`~.PowerBasis` is constructed by passing either the minimal
- polynomial of $\theta$, or an :py:class:`~.AlgebraicField` having $\theta$
- as its primitive element.
- * For a :py:class:`~.Submodule`, the generators are a set of
- $\mathbb{Q}$-linear combinations of the generators of another module. That
- other module is then the "parent" of the :py:class:`~.Submodule`. The
- coefficients of the $\mathbb{Q}$-linear combinations may be given by an
- integer matrix, and a positive integer denominator. Each column of the matrix
- defines a generator.
- >>> from sympy.polys import Poly, cyclotomic_poly, ZZ
- >>> from sympy.abc import x
- >>> from sympy.polys.matrices import DomainMatrix, DM
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5, x))
- >>> A = PowerBasis(T)
- >>> print(A)
- PowerBasis(x**4 + x**3 + x**2 + x + 1)
- >>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ), denom=3)
- >>> print(B)
- Submodule[[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]/3
- >>> print(B.parent)
- PowerBasis(x**4 + x**3 + x**2 + x + 1)
- Thus, every module is either a :py:class:`~.PowerBasis`,
- or a :py:class:`~.Submodule`, some ancestor of which is a
- :py:class:`~.PowerBasis`. (If ``S`` is a :py:class:`~.Submodule`, then its
- ancestors are ``S.parent``, ``S.parent.parent``, and so on).
- The :py:class:`~.ModuleElement` class represents a linear combination of the
- generators of any module. Critically, the coefficients of this linear
- combination are not restricted to be integers, but may be any rational
- numbers. This is necessary so that any and all algebraic integers be
- representable, starting from the power basis in a primitive element $\theta$
- for the number field in question. For example, in a quadratic field
- $\mathbb{Q}(\sqrt{d})$ where $d \equiv 1 \mod{4}$, a denominator of $2$ is
- needed.
- A :py:class:`~.ModuleElement` can be constructed from an integer column vector
- and a denominator:
- >>> U = Poly(x**2 - 5)
- >>> M = PowerBasis(U)
- >>> e = M(DM([[1], [1]], ZZ), denom=2)
- >>> print(e)
- [1, 1]/2
- >>> print(e.module)
- PowerBasis(x**2 - 5)
- The :py:class:`~.PowerBasisElement` class is a subclass of
- :py:class:`~.ModuleElement` that represents elements of a
- :py:class:`~.PowerBasis`, and adds functionality pertinent to elements
- represented directly over powers of the primitive element $\theta$.
- Arithmetic with module elements
- ===============================
- While a :py:class:`~.ModuleElement` represents a linear combination over the
- generators of a particular module, recall that every module is either a
- :py:class:`~.PowerBasis` or a descendant (along a chain of
- :py:class:`~.Submodule` objects) thereof, so that in fact every
- :py:class:`~.ModuleElement` represents an algebraic number in some field
- $\mathbb{Q}(\theta)$, where $\theta$ is the defining element of some
- :py:class:`~.PowerBasis`. It thus makes sense to talk about the number field
- to which a given :py:class:`~.ModuleElement` belongs.
- This means that any two :py:class:`~.ModuleElement` instances can be added,
- subtracted, multiplied, or divided, provided they belong to the same number
- field. Similarly, since $\mathbb{Q}$ is a subfield of every number field,
- any :py:class:`~.ModuleElement` may be added, multiplied, etc. by any
- rational number.
- >>> from sympy import QQ
- >>> from sympy.polys.numberfields.modules import to_col
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
- >>> e = A(to_col([0, 2, 0, 0]), denom=3)
- >>> f = A(to_col([0, 0, 0, 7]), denom=5)
- >>> g = C(to_col([1, 1, 1, 1]))
- >>> e + f
- [0, 10, 0, 21]/15
- >>> e - f
- [0, 10, 0, -21]/15
- >>> e - g
- [-9, -7, -9, -9]/3
- >>> e + QQ(7, 10)
- [21, 20, 0, 0]/30
- >>> e * f
- [-14, -14, -14, -14]/15
- >>> e ** 2
- [0, 0, 4, 0]/9
- >>> f // g
- [7, 7, 7, 7]/15
- >>> f * QQ(2, 3)
- [0, 0, 0, 14]/15
- However, care must be taken with arithmetic operations on
- :py:class:`~.ModuleElement`, because the module $C$ to which the result will
- belong will be the nearest common ancestor (NCA) of the modules $A$, $B$ to
- which the two operands belong, and $C$ may be different from either or both
- of $A$ and $B$.
- >>> A = PowerBasis(T)
- >>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
- >>> C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
- >>> print((B(0) * C(0)).module == A)
- True
- Before the arithmetic operation is performed, copies of the two operands are
- automatically converted into elements of the NCA (the operands themselves are
- not modified). This upward conversion along an ancestor chain is easy: it just
- requires the successive multiplication by the defining matrix of each
- :py:class:`~.Submodule`.
- Conversely, downward conversion, i.e. representing a given
- :py:class:`~.ModuleElement` in a submodule, is also supported -- namely by
- the :py:meth:`~sympy.polys.numberfields.modules.Submodule.represent` method
- -- but is not guaranteed to succeed in general, since the given element may
- not belong to the submodule. The main circumstance in which this issue tends
- to arise is with multiplication, since modules, while closed under addition,
- need not be closed under multiplication.
- Multiplication
- --------------
- Generally speaking, a module need not be closed under multiplication, i.e. need
- not form a ring. However, many of the modules we work with in the context of
- number fields are in fact rings, and our classes do support multiplication.
- Specifically, any :py:class:`~.Module` can attempt to compute its own
- multiplication table, but this does not happen unless an attempt is made to
- multiply two :py:class:`~.ModuleElement` instances belonging to it.
- >>> A = PowerBasis(T)
- >>> print(A._mult_tab is None)
- True
- >>> a = A(0)*A(1)
- >>> print(A._mult_tab is None)
- False
- Every :py:class:`~.PowerBasis` is, by its nature, closed under multiplication,
- so instances of :py:class:`~.PowerBasis` can always successfully compute their
- multiplication table.
- When a :py:class:`~.Submodule` attempts to compute its multiplication table,
- it converts each of its own generators into elements of its parent module,
- multiplies them there, in every possible pairing, and then tries to
- represent the results in itself, i.e. as $\mathbb{Z}$-linear combinations
- over its own generators. This will succeed if and only if the submodule is
- in fact closed under multiplication.
- Module Homomorphisms
- ====================
- Many important number theoretic algorithms require the calculation of the
- kernel of one or more module homomorphisms. Accordingly we have several
- lightweight classes, :py:class:`~.ModuleHomomorphism`,
- :py:class:`~.ModuleEndomorphism`, :py:class:`~.InnerEndomorphism`, and
- :py:class:`~.EndomorphismRing`, which provide the minimal necessary machinery
- to support this.
- """
- from sympy.core.numbers import igcd, ilcm
- from sympy.core.symbol import Dummy
- from sympy.polys.polyclasses import ANP
- from sympy.polys.polytools import Poly
- from sympy.polys.densetools import dup_clear_denoms
- from sympy.polys.domains.algebraicfield import AlgebraicField
- from sympy.polys.domains.finitefield import FF
- from sympy.polys.domains.rationalfield import QQ
- from sympy.polys.domains.integerring import ZZ
- from sympy.polys.matrices.domainmatrix import DomainMatrix
- from sympy.polys.matrices.exceptions import DMBadInputError
- from sympy.polys.matrices.normalforms import hermite_normal_form
- from sympy.polys.polyerrors import CoercionFailed, UnificationFailed
- from sympy.polys.polyutils import IntegerPowerable
- from .exceptions import ClosureFailure, MissingUnityError, StructureError
- from .utilities import AlgIntPowers, is_rat, get_num_denom
- def to_col(coeffs):
- r"""Transform a list of integer coefficients into a column vector."""
- return DomainMatrix([[ZZ(c) for c in coeffs]], (1, len(coeffs)), ZZ).transpose()
- class Module:
- """
- Generic finitely-generated module.
- This is an abstract base class, and should not be instantiated directly.
- The two concrete subclasses are :py:class:`~.PowerBasis` and
- :py:class:`~.Submodule`.
- Every :py:class:`~.Submodule` is derived from another module, referenced
- by its ``parent`` attribute. If ``S`` is a submodule, then we refer to
- ``S.parent``, ``S.parent.parent``, and so on, as the "ancestors" of
- ``S``. Thus, every :py:class:`~.Module` is either a
- :py:class:`~.PowerBasis` or a :py:class:`~.Submodule`, some ancestor of
- which is a :py:class:`~.PowerBasis`.
- """
- @property
- def n(self):
- """The number of generators of this module."""
- raise NotImplementedError
- def mult_tab(self):
- """
- Get the multiplication table for this module (if closed under mult).
- Explanation
- ===========
- Computes a dictionary ``M`` of dictionaries of lists, representing the
- upper triangular half of the multiplication table.
- In other words, if ``0 <= i <= j < self.n``, then ``M[i][j]`` is the
- list ``c`` of coefficients such that
- ``g[i] * g[j] == sum(c[k]*g[k], k in range(self.n))``,
- where ``g`` is the list of generators of this module.
- If ``j < i`` then ``M[i][j]`` is undefined.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> print(A.mult_tab()) # doctest: +SKIP
- {0: {0: [1, 0, 0, 0], 1: [0, 1, 0, 0], 2: [0, 0, 1, 0], 3: [0, 0, 0, 1]},
- 1: {1: [0, 0, 1, 0], 2: [0, 0, 0, 1], 3: [-1, -1, -1, -1]},
- 2: {2: [-1, -1, -1, -1], 3: [1, 0, 0, 0]},
- 3: {3: [0, 1, 0, 0]}}
- Returns
- =======
- dict of dict of lists
- Raises
- ======
- ClosureFailure
- If the module is not closed under multiplication.
- """
- raise NotImplementedError
- @property
- def parent(self):
- """
- The parent module, if any, for this module.
- Explanation
- ===========
- For a :py:class:`~.Submodule` this is its ``parent`` attribute; for a
- :py:class:`~.PowerBasis` this is ``None``.
- Returns
- =======
- :py:class:`~.Module`, ``None``
- See Also
- ========
- Module
- """
- return None
- def represent(self, elt):
- r"""
- Represent a module element as an integer-linear combination over the
- generators of this module.
- Explanation
- ===========
- In our system, to "represent" always means to write a
- :py:class:`~.ModuleElement` as a :ref:`ZZ`-linear combination over the
- generators of the present :py:class:`~.Module`. Furthermore, the
- incoming :py:class:`~.ModuleElement` must belong to an ancestor of
- the present :py:class:`~.Module` (or to the present
- :py:class:`~.Module` itself).
- The most common application is to represent a
- :py:class:`~.ModuleElement` in a :py:class:`~.Submodule`. For example,
- this is involved in computing multiplication tables.
- On the other hand, representing in a :py:class:`~.PowerBasis` is an
- odd case, and one which tends not to arise in practice, except for
- example when using a :py:class:`~.ModuleEndomorphism` on a
- :py:class:`~.PowerBasis`.
- In such a case, (1) the incoming :py:class:`~.ModuleElement` must
- belong to the :py:class:`~.PowerBasis` itself (since the latter has no
- proper ancestors) and (2) it is "representable" iff it belongs to
- $\mathbb{Z}[\theta]$ (although generally a
- :py:class:`~.PowerBasisElement` may represent any element of
- $\mathbb{Q}(\theta)$, i.e. any algebraic number).
- Examples
- ========
- >>> from sympy import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis, to_col
- >>> from sympy.abc import zeta
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> a = A(to_col([2, 4, 6, 8]))
- The :py:class:`~.ModuleElement` ``a`` has all even coefficients.
- If we represent ``a`` in the submodule ``B = 2*A``, the coefficients in
- the column vector will be halved:
- >>> B = A.submodule_from_gens([2*A(i) for i in range(4)])
- >>> b = B.represent(a)
- >>> print(b.transpose()) # doctest: +SKIP
- DomainMatrix([[1, 2, 3, 4]], (1, 4), ZZ)
- However, the element of ``B`` so defined still represents the same
- algebraic number:
- >>> print(a.poly(zeta).as_expr())
- 8*zeta**3 + 6*zeta**2 + 4*zeta + 2
- >>> print(B(b).over_power_basis().poly(zeta).as_expr())
- 8*zeta**3 + 6*zeta**2 + 4*zeta + 2
- Parameters
- ==========
- elt : :py:class:`~.ModuleElement`
- The module element to be represented. Must belong to some ancestor
- module of this module (including this module itself).
- Returns
- =======
- :py:class:`~.DomainMatrix` over :ref:`ZZ`
- This will be a column vector, representing the coefficients of a
- linear combination of this module's generators, which equals the
- given element.
- Raises
- ======
- ClosureFailure
- If the given element cannot be represented as a :ref:`ZZ`-linear
- combination over this module.
- See Also
- ========
- .Submodule.represent
- .PowerBasis.represent
- """
- raise NotImplementedError
- def ancestors(self, include_self=False):
- """
- Return the list of ancestor modules of this module, from the
- foundational :py:class:`~.PowerBasis` downward, optionally including
- ``self``.
- See Also
- ========
- Module
- """
- c = self.parent
- a = [] if c is None else c.ancestors(include_self=True)
- if include_self:
- a.append(self)
- return a
- def power_basis_ancestor(self):
- """
- Return the :py:class:`~.PowerBasis` that is an ancestor of this module.
- See Also
- ========
- Module
- """
- if isinstance(self, PowerBasis):
- return self
- c = self.parent
- if c is not None:
- return c.power_basis_ancestor()
- return None
- def nearest_common_ancestor(self, other):
- """
- Locate the nearest common ancestor of this module and another.
- Returns
- =======
- :py:class:`~.Module`, ``None``
- See Also
- ========
- Module
- """
- sA = self.ancestors(include_self=True)
- oA = other.ancestors(include_self=True)
- nca = None
- for sa, oa in zip(sA, oA):
- if sa == oa:
- nca = sa
- else:
- break
- return nca
- @property
- def number_field(self):
- r"""
- Return the associated :py:class:`~.AlgebraicField`, if any.
- Explanation
- ===========
- A :py:class:`~.PowerBasis` can be constructed on a :py:class:`~.Poly`
- $f$ or on an :py:class:`~.AlgebraicField` $K$. In the latter case, the
- :py:class:`~.PowerBasis` and all its descendant modules will return $K$
- as their ``.number_field`` property, while in the former case they will
- all return ``None``.
- Returns
- =======
- :py:class:`~.AlgebraicField`, ``None``
- """
- return self.power_basis_ancestor().number_field
- def is_compat_col(self, col):
- """Say whether *col* is a suitable column vector for this module."""
- return isinstance(col, DomainMatrix) and col.shape == (self.n, 1) and col.domain.is_ZZ
- def __call__(self, spec, denom=1):
- r"""
- Generate a :py:class:`~.ModuleElement` belonging to this module.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis, to_col
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> e = A(to_col([1, 2, 3, 4]), denom=3)
- >>> print(e) # doctest: +SKIP
- [1, 2, 3, 4]/3
- >>> f = A(2)
- >>> print(f) # doctest: +SKIP
- [0, 0, 1, 0]
- Parameters
- ==========
- spec : :py:class:`~.DomainMatrix`, int
- Specifies the numerators of the coefficients of the
- :py:class:`~.ModuleElement`. Can be either a column vector over
- :ref:`ZZ`, whose length must equal the number $n$ of generators of
- this module, or else an integer ``j``, $0 \leq j < n$, which is a
- shorthand for column $j$ of $I_n$, the $n \times n$ identity
- matrix.
- denom : int, optional (default=1)
- Denominator for the coefficients of the
- :py:class:`~.ModuleElement`.
- Returns
- =======
- :py:class:`~.ModuleElement`
- The coefficients are the entries of the *spec* vector, divided by
- *denom*.
- """
- if isinstance(spec, int) and 0 <= spec < self.n:
- spec = DomainMatrix.eye(self.n, ZZ)[:, spec].to_dense()
- if not self.is_compat_col(spec):
- raise ValueError('Compatible column vector required.')
- return make_mod_elt(self, spec, denom=denom)
- def starts_with_unity(self):
- """Say whether the module's first generator equals unity."""
- raise NotImplementedError
- def basis_elements(self):
- """
- Get list of :py:class:`~.ModuleElement` being the generators of this
- module.
- """
- return [self(j) for j in range(self.n)]
- def zero(self):
- """Return a :py:class:`~.ModuleElement` representing zero."""
- return self(0) * 0
- def one(self):
- """
- Return a :py:class:`~.ModuleElement` representing unity,
- and belonging to the first ancestor of this module (including
- itself) that starts with unity.
- """
- return self.element_from_rational(1)
- def element_from_rational(self, a):
- """
- Return a :py:class:`~.ModuleElement` representing a rational number.
- Explanation
- ===========
- The returned :py:class:`~.ModuleElement` will belong to the first
- module on this module's ancestor chain (including this module
- itself) that starts with unity.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly, QQ
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> a = A.element_from_rational(QQ(2, 3))
- >>> print(a) # doctest: +SKIP
- [2, 0, 0, 0]/3
- Parameters
- ==========
- a : int, :ref:`ZZ`, :ref:`QQ`
- Returns
- =======
- :py:class:`~.ModuleElement`
- """
- raise NotImplementedError
- def submodule_from_gens(self, gens, hnf=True, hnf_modulus=None):
- """
- Form the submodule generated by a list of :py:class:`~.ModuleElement`
- belonging to this module.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> gens = [A(0), 2*A(1), 3*A(2), 4*A(3)//5]
- >>> B = A.submodule_from_gens(gens)
- >>> print(B) # doctest: +SKIP
- Submodule[[5, 0, 0, 0], [0, 10, 0, 0], [0, 0, 15, 0], [0, 0, 0, 4]]/5
- Parameters
- ==========
- gens : list of :py:class:`~.ModuleElement` belonging to this module.
- hnf : boolean, optional (default=True)
- If True, we will reduce the matrix into Hermite Normal Form before
- forming the :py:class:`~.Submodule`.
- hnf_modulus : int, None, optional (default=None)
- Modulus for use in the HNF reduction algorithm. See
- :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`.
- Returns
- =======
- :py:class:`~.Submodule`
- See Also
- ========
- submodule_from_matrix
- """
- if not all(g.module == self for g in gens):
- raise ValueError('Generators must belong to this module.')
- n = len(gens)
- if n == 0:
- raise ValueError('Need at least one generator.')
- m = gens[0].n
- d = gens[0].denom if n == 1 else ilcm(*[g.denom for g in gens])
- B = DomainMatrix.zeros((m, 0), ZZ).hstack(*[(d // g.denom) * g.col for g in gens])
- if hnf:
- B = hermite_normal_form(B, D=hnf_modulus)
- return self.submodule_from_matrix(B, denom=d)
- def submodule_from_matrix(self, B, denom=1):
- """
- Form the submodule generated by the elements of this module indicated
- by the columns of a matrix, with an optional denominator.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly, ZZ
- >>> from sympy.polys.matrices import DM
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> B = A.submodule_from_matrix(DM([
- ... [0, 10, 0, 0],
- ... [0, 0, 7, 0],
- ... ], ZZ).transpose(), denom=15)
- >>> print(B) # doctest: +SKIP
- Submodule[[0, 10, 0, 0], [0, 0, 7, 0]]/15
- Parameters
- ==========
- B : :py:class:`~.DomainMatrix` over :ref:`ZZ`
- Each column gives the numerators of the coefficients of one
- generator of the submodule. Thus, the number of rows of *B* must
- equal the number of generators of the present module.
- denom : int, optional (default=1)
- Common denominator for all generators of the submodule.
- Returns
- =======
- :py:class:`~.Submodule`
- Raises
- ======
- ValueError
- If the given matrix *B* is not over :ref:`ZZ` or its number of rows
- does not equal the number of generators of the present module.
- See Also
- ========
- submodule_from_gens
- """
- m, n = B.shape
- if not B.domain.is_ZZ:
- raise ValueError('Matrix must be over ZZ.')
- if not m == self.n:
- raise ValueError('Matrix row count must match base module.')
- return Submodule(self, B, denom=denom)
- def whole_submodule(self):
- """
- Return a submodule equal to this entire module.
- Explanation
- ===========
- This is useful when you have a :py:class:`~.PowerBasis` and want to
- turn it into a :py:class:`~.Submodule` (in order to use methods
- belonging to the latter).
- """
- B = DomainMatrix.eye(self.n, ZZ)
- return self.submodule_from_matrix(B)
- def endomorphism_ring(self):
- """Form the :py:class:`~.EndomorphismRing` for this module."""
- return EndomorphismRing(self)
- class PowerBasis(Module):
- """The module generated by the powers of an algebraic integer."""
- def __init__(self, T):
- """
- Parameters
- ==========
- T : :py:class:`~.Poly`, :py:class:`~.AlgebraicField`
- Either (1) the monic, irreducible, univariate polynomial over
- :ref:`ZZ`, a root of which is the generator of the power basis,
- or (2) an :py:class:`~.AlgebraicField` whose primitive element
- is the generator of the power basis.
- """
- K = None
- if isinstance(T, AlgebraicField):
- K, T = T, T.ext.minpoly_of_element()
- # Sometimes incoming Polys are formally over QQ, although all their
- # coeffs are integral. We want them to be formally over ZZ.
- T = T.set_domain(ZZ)
- self.K = K
- self.T = T
- self._n = T.degree()
- self._mult_tab = None
- @property
- def number_field(self):
- return self.K
- def __repr__(self):
- return f'PowerBasis({self.T.as_expr()})'
- def __eq__(self, other):
- if isinstance(other, PowerBasis):
- return self.T == other.T
- return NotImplemented
- @property
- def n(self):
- return self._n
- def mult_tab(self):
- if self._mult_tab is None:
- self.compute_mult_tab()
- return self._mult_tab
- def compute_mult_tab(self):
- theta_pow = AlgIntPowers(self.T)
- M = {}
- n = self.n
- for u in range(n):
- M[u] = {}
- for v in range(u, n):
- M[u][v] = theta_pow[u + v]
- self._mult_tab = M
- def represent(self, elt):
- r"""
- Represent a module element as an integer-linear combination over the
- generators of this module.
- See Also
- ========
- .Module.represent
- .Submodule.represent
- """
- if elt.module == self and elt.denom == 1:
- return elt.column()
- else:
- raise ClosureFailure('Element not representable in ZZ[theta].')
- def starts_with_unity(self):
- return True
- def element_from_rational(self, a):
- return self(0) * a
- def element_from_poly(self, f):
- """
- Produce an element of this module, representing *f* after reduction mod
- our defining minimal polynomial.
- Parameters
- ==========
- f : :py:class:`~.Poly` over :ref:`ZZ` in same var as our defining poly.
- Returns
- =======
- :py:class:`~.PowerBasisElement`
- """
- n, k = self.n, f.degree()
- if k >= n:
- f = f % self.T
- if f == 0:
- return self.zero()
- d, c = dup_clear_denoms(f.rep.rep, QQ, convert=True)
- c = list(reversed(c))
- ell = len(c)
- z = [ZZ(0)] * (n - ell)
- col = to_col(c + z)
- return self(col, denom=d)
- def _element_from_rep_and_mod(self, rep, mod):
- """
- Produce a PowerBasisElement representing a given algebraic number.
- Parameters
- ==========
- rep : list of coeffs
- Represents the number as polynomial in the primitive element of the
- field.
- mod : list of coeffs
- Represents the minimal polynomial of the primitive element of the
- field.
- Returns
- =======
- :py:class:`~.PowerBasisElement`
- """
- if mod != self.T.rep.rep:
- raise UnificationFailed('Element does not appear to be in the same field.')
- return self.element_from_poly(Poly(rep, self.T.gen))
- def element_from_ANP(self, a):
- """Convert an ANP into a PowerBasisElement. """
- return self._element_from_rep_and_mod(a.rep, a.mod)
- def element_from_alg_num(self, a):
- """Convert an AlgebraicNumber into a PowerBasisElement. """
- return self._element_from_rep_and_mod(a.rep.rep, a.minpoly.rep.rep)
- class Submodule(Module, IntegerPowerable):
- """A submodule of another module."""
- def __init__(self, parent, matrix, denom=1, mult_tab=None):
- """
- Parameters
- ==========
- parent : :py:class:`~.Module`
- The module from which this one is derived.
- matrix : :py:class:`~.DomainMatrix` over :ref:`ZZ`
- The matrix whose columns define this submodule's generators as
- linear combinations over the parent's generators.
- denom : int, optional (default=1)
- Denominator for the coefficients given by the matrix.
- mult_tab : dict, ``None``, optional
- If already known, the multiplication table for this module may be
- supplied.
- """
- self._parent = parent
- self._matrix = matrix
- self._denom = denom
- self._mult_tab = mult_tab
- self._n = matrix.shape[1]
- self._QQ_matrix = None
- self._starts_with_unity = None
- self._is_sq_maxrank_HNF = None
- def __repr__(self):
- r = 'Submodule' + repr(self.matrix.transpose().to_Matrix().tolist())
- if self.denom > 1:
- r += f'/{self.denom}'
- return r
- def reduced(self):
- """
- Produce a reduced version of this submodule.
- Explanation
- ===========
- In the reduced version, it is guaranteed that 1 is the only positive
- integer dividing both the submodule's denominator, and every entry in
- the submodule's matrix.
- Returns
- =======
- :py:class:`~.Submodule`
- """
- if self.denom == 1:
- return self
- g = igcd(self.denom, *self.coeffs)
- if g == 1:
- return self
- return type(self)(self.parent, (self.matrix / g).convert_to(ZZ), denom=self.denom // g, mult_tab=self._mult_tab)
- def discard_before(self, r):
- """
- Produce a new module by discarding all generators before a given
- index *r*.
- """
- W = self.matrix[:, r:]
- s = self.n - r
- M = None
- mt = self._mult_tab
- if mt is not None:
- M = {}
- for u in range(s):
- M[u] = {}
- for v in range(u, s):
- M[u][v] = mt[r + u][r + v][r:]
- return Submodule(self.parent, W, denom=self.denom, mult_tab=M)
- @property
- def n(self):
- return self._n
- def mult_tab(self):
- if self._mult_tab is None:
- self.compute_mult_tab()
- return self._mult_tab
- def compute_mult_tab(self):
- gens = self.basis_element_pullbacks()
- M = {}
- n = self.n
- for u in range(n):
- M[u] = {}
- for v in range(u, n):
- M[u][v] = self.represent(gens[u] * gens[v]).flat()
- self._mult_tab = M
- @property
- def parent(self):
- return self._parent
- @property
- def matrix(self):
- return self._matrix
- @property
- def coeffs(self):
- return self.matrix.flat()
- @property
- def denom(self):
- return self._denom
- @property
- def QQ_matrix(self):
- """
- :py:class:`~.DomainMatrix` over :ref:`QQ`, equal to
- ``self.matrix / self.denom``, and guaranteed to be dense.
- Explanation
- ===========
- Depending on how it is formed, a :py:class:`~.DomainMatrix` may have
- an internal representation that is sparse or dense. We guarantee a
- dense representation here, so that tests for equivalence of submodules
- always come out as expected.
- Examples
- ========
- >>> from sympy.polys import Poly, cyclotomic_poly, ZZ
- >>> from sympy.abc import x
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> T = Poly(cyclotomic_poly(5, x))
- >>> A = PowerBasis(T)
- >>> B = A.submodule_from_matrix(3*DomainMatrix.eye(4, ZZ), denom=6)
- >>> C = A.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=2)
- >>> print(B.QQ_matrix == C.QQ_matrix)
- True
- Returns
- =======
- :py:class:`~.DomainMatrix` over :ref:`QQ`
- """
- if self._QQ_matrix is None:
- self._QQ_matrix = (self.matrix / self.denom).to_dense()
- return self._QQ_matrix
- def starts_with_unity(self):
- if self._starts_with_unity is None:
- self._starts_with_unity = self(0).equiv(1)
- return self._starts_with_unity
- def is_sq_maxrank_HNF(self):
- if self._is_sq_maxrank_HNF is None:
- self._is_sq_maxrank_HNF = is_sq_maxrank_HNF(self._matrix)
- return self._is_sq_maxrank_HNF
- def is_power_basis_submodule(self):
- return isinstance(self.parent, PowerBasis)
- def element_from_rational(self, a):
- if self.starts_with_unity():
- return self(0) * a
- else:
- return self.parent.element_from_rational(a)
- def basis_element_pullbacks(self):
- """
- Return list of this submodule's basis elements as elements of the
- submodule's parent module.
- """
- return [e.to_parent() for e in self.basis_elements()]
- def represent(self, elt):
- """
- Represent a module element as an integer-linear combination over the
- generators of this module.
- See Also
- ========
- .Module.represent
- .PowerBasis.represent
- """
- if elt.module == self:
- return elt.column()
- elif elt.module == self.parent:
- try:
- # The given element should be a ZZ-linear combination over our
- # basis vectors; however, due to the presence of denominators,
- # we need to solve over QQ.
- A = self.QQ_matrix
- b = elt.QQ_col
- x = A._solve(b)[0].transpose()
- x = x.convert_to(ZZ)
- except DMBadInputError:
- raise ClosureFailure('Element outside QQ-span of this basis.')
- except CoercionFailed:
- raise ClosureFailure('Element in QQ-span but not ZZ-span of this basis.')
- return x
- elif isinstance(self.parent, Submodule):
- coeffs_in_parent = self.parent.represent(elt)
- parent_element = self.parent(coeffs_in_parent)
- return self.represent(parent_element)
- else:
- raise ClosureFailure('Element outside ancestor chain of this module.')
- def is_compat_submodule(self, other):
- return isinstance(other, Submodule) and other.parent == self.parent
- def __eq__(self, other):
- if self.is_compat_submodule(other):
- return other.QQ_matrix == self.QQ_matrix
- return NotImplemented
- def add(self, other, hnf=True, hnf_modulus=None):
- """
- Add this :py:class:`~.Submodule` to another.
- Explanation
- ===========
- This represents the module generated by the union of the two modules'
- sets of generators.
- Parameters
- ==========
- other : :py:class:`~.Submodule`
- hnf : boolean, optional (default=True)
- If ``True``, reduce the matrix of the combined module to its
- Hermite Normal Form.
- hnf_modulus : :ref:`ZZ`, None, optional
- If a positive integer is provided, use this as modulus in the
- HNF reduction. See
- :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`.
- Returns
- =======
- :py:class:`~.Submodule`
- """
- d, e = self.denom, other.denom
- m = ilcm(d, e)
- a, b = m // d, m // e
- B = (a * self.matrix).hstack(b * other.matrix)
- if hnf:
- B = hermite_normal_form(B, D=hnf_modulus)
- return self.parent.submodule_from_matrix(B, denom=m)
- def __add__(self, other):
- if self.is_compat_submodule(other):
- return self.add(other)
- return NotImplemented
- __radd__ = __add__
- def mul(self, other, hnf=True, hnf_modulus=None):
- """
- Multiply this :py:class:`~.Submodule` by a rational number, a
- :py:class:`~.ModuleElement`, or another :py:class:`~.Submodule`.
- Explanation
- ===========
- To multiply by a rational number or :py:class:`~.ModuleElement` means
- to form the submodule whose generators are the products of this
- quantity with all the generators of the present submodule.
- To multiply by another :py:class:`~.Submodule` means to form the
- submodule whose generators are all the products of one generator from
- the one submodule, and one generator from the other.
- Parameters
- ==========
- other : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.ModuleElement`, :py:class:`~.Submodule`
- hnf : boolean, optional (default=True)
- If ``True``, reduce the matrix of the product module to its
- Hermite Normal Form.
- hnf_modulus : :ref:`ZZ`, None, optional
- If a positive integer is provided, use this as modulus in the
- HNF reduction. See
- :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`.
- Returns
- =======
- :py:class:`~.Submodule`
- """
- if is_rat(other):
- a, b = get_num_denom(other)
- if a == b == 1:
- return self
- else:
- return Submodule(self.parent,
- self.matrix * a, denom=self.denom * b,
- mult_tab=None).reduced()
- elif isinstance(other, ModuleElement) and other.module == self.parent:
- # The submodule is multiplied by an element of the parent module.
- # We presume this means we want a new submodule of the parent module.
- gens = [other * e for e in self.basis_element_pullbacks()]
- return self.parent.submodule_from_gens(gens, hnf=hnf, hnf_modulus=hnf_modulus)
- elif self.is_compat_submodule(other):
- # This case usually means you're multiplying ideals, and want another
- # ideal, i.e. another submodule of the same parent module.
- alphas, betas = self.basis_element_pullbacks(), other.basis_element_pullbacks()
- gens = [a * b for a in alphas for b in betas]
- return self.parent.submodule_from_gens(gens, hnf=hnf, hnf_modulus=hnf_modulus)
- return NotImplemented
- def __mul__(self, other):
- return self.mul(other)
- __rmul__ = __mul__
- def _first_power(self):
- return self
- def reduce_element(self, elt):
- r"""
- If this submodule $B$ has defining matrix $W$ in square, maximal-rank
- Hermite normal form, then, given an element $x$ of the parent module
- $A$, we produce an element $y \in A$ such that $x - y \in B$, and the
- $i$th coordinate of $y$ satisfies $0 \leq y_i < w_{i,i}$. This
- representative $y$ is unique, in the sense that every element of
- the coset $x + B$ reduces to it under this procedure.
- Explanation
- ===========
- In the special case where $A$ is a power basis for a number field $K$,
- and $B$ is a submodule representing an ideal $I$, this operation
- represents one of a few important ways of reducing an element of $K$
- modulo $I$ to obtain a "small" representative. See [Cohen00]_ Section
- 1.4.3.
- Examples
- ========
- >>> from sympy import QQ, Poly, symbols
- >>> t = symbols('t')
- >>> k = QQ.alg_field_from_poly(Poly(t**3 + t**2 - 2*t + 8))
- >>> Zk = k.maximal_order()
- >>> A = Zk.parent
- >>> B = (A(2) - 3*A(0))*Zk
- >>> B.reduce_element(A(2))
- [3, 0, 0]
- Parameters
- ==========
- elt : :py:class:`~.ModuleElement`
- An element of this submodule's parent module.
- Returns
- =======
- elt : :py:class:`~.ModuleElement`
- An element of this submodule's parent module.
- Raises
- ======
- NotImplementedError
- If the given :py:class:`~.ModuleElement` does not belong to this
- submodule's parent module.
- StructureError
- If this submodule's defining matrix is not in square, maximal-rank
- Hermite normal form.
- References
- ==========
- .. [Cohen00] Cohen, H. *Advanced Topics in Computational Number
- Theory.*
- """
- if not elt.module == self.parent:
- raise NotImplementedError
- if not self.is_sq_maxrank_HNF():
- msg = "Reduction not implemented unless matrix square max-rank HNF"
- raise StructureError(msg)
- B = self.basis_element_pullbacks()
- a = elt
- for i in range(self.n - 1, -1, -1):
- b = B[i]
- q = a.coeffs[i]*b.denom // (b.coeffs[i]*a.denom)
- a -= q*b
- return a
- def is_sq_maxrank_HNF(dm):
- r"""
- Say whether a :py:class:`~.DomainMatrix` is in that special case of Hermite
- Normal Form, in which the matrix is also square and of maximal rank.
- Explanation
- ===========
- We commonly work with :py:class:`~.Submodule` instances whose matrix is in
- this form, and it can be useful to be able to check that this condition is
- satisfied.
- For example this is the case with the :py:class:`~.Submodule` ``ZK``
- returned by :py:func:`~sympy.polys.numberfields.basis.round_two`, which
- represents the maximal order in a number field, and with ideals formed
- therefrom, such as ``2 * ZK``.
- """
- if dm.domain.is_ZZ and dm.is_square and dm.is_upper:
- n = dm.shape[0]
- for i in range(n):
- d = dm[i, i].element
- if d <= 0:
- return False
- for j in range(i + 1, n):
- if not (0 <= dm[i, j].element < d):
- return False
- return True
- return False
- def make_mod_elt(module, col, denom=1):
- r"""
- Factory function which builds a :py:class:`~.ModuleElement`, but ensures
- that it is a :py:class:`~.PowerBasisElement` if the module is a
- :py:class:`~.PowerBasis`.
- """
- if isinstance(module, PowerBasis):
- return PowerBasisElement(module, col, denom=denom)
- else:
- return ModuleElement(module, col, denom=denom)
- class ModuleElement(IntegerPowerable):
- r"""
- Represents an element of a :py:class:`~.Module`.
- NOTE: Should not be constructed directly. Use the
- :py:meth:`~.Module.__call__` method or the :py:func:`make_mod_elt()`
- factory function instead.
- """
- def __init__(self, module, col, denom=1):
- """
- Parameters
- ==========
- module : :py:class:`~.Module`
- The module to which this element belongs.
- col : :py:class:`~.DomainMatrix` over :ref:`ZZ`
- Column vector giving the numerators of the coefficients of this
- element.
- denom : int, optional (default=1)
- Denominator for the coefficients of this element.
- """
- self.module = module
- self.col = col
- self.denom = denom
- self._QQ_col = None
- def __repr__(self):
- r = str([int(c) for c in self.col.flat()])
- if self.denom > 1:
- r += f'/{self.denom}'
- return r
- def reduced(self):
- """
- Produce a reduced version of this ModuleElement, i.e. one in which the
- gcd of the denominator together with all numerator coefficients is 1.
- """
- if self.denom == 1:
- return self
- g = igcd(self.denom, *self.coeffs)
- if g == 1:
- return self
- return type(self)(self.module,
- (self.col / g).convert_to(ZZ),
- denom=self.denom // g)
- def reduced_mod_p(self, p):
- """
- Produce a version of this :py:class:`~.ModuleElement` in which all
- numerator coefficients have been reduced mod *p*.
- """
- return make_mod_elt(self.module,
- self.col.convert_to(FF(p)).convert_to(ZZ),
- denom=self.denom)
- @classmethod
- def from_int_list(cls, module, coeffs, denom=1):
- """
- Make a :py:class:`~.ModuleElement` from a list of ints (instead of a
- column vector).
- """
- col = to_col(coeffs)
- return cls(module, col, denom=denom)
- @property
- def n(self):
- """The length of this element's column."""
- return self.module.n
- def __len__(self):
- return self.n
- def column(self, domain=None):
- """
- Get a copy of this element's column, optionally converting to a domain.
- """
- return self.col.convert_to(domain)
- @property
- def coeffs(self):
- return self.col.flat()
- @property
- def QQ_col(self):
- """
- :py:class:`~.DomainMatrix` over :ref:`QQ`, equal to
- ``self.col / self.denom``, and guaranteed to be dense.
- See Also
- ========
- .Submodule.QQ_matrix
- """
- if self._QQ_col is None:
- self._QQ_col = (self.col / self.denom).to_dense()
- return self._QQ_col
- def to_parent(self):
- """
- Transform into a :py:class:`~.ModuleElement` belonging to the parent of
- this element's module.
- """
- if not isinstance(self.module, Submodule):
- raise ValueError('Not an element of a Submodule.')
- return make_mod_elt(
- self.module.parent, self.module.matrix * self.col,
- denom=self.module.denom * self.denom)
- def to_ancestor(self, anc):
- """
- Transform into a :py:class:`~.ModuleElement` belonging to a given
- ancestor of this element's module.
- Parameters
- ==========
- anc : :py:class:`~.Module`
- """
- if anc == self.module:
- return self
- else:
- return self.to_parent().to_ancestor(anc)
- def over_power_basis(self):
- """
- Transform into a :py:class:`~.PowerBasisElement` over our
- :py:class:`~.PowerBasis` ancestor.
- """
- e = self
- while not isinstance(e.module, PowerBasis):
- e = e.to_parent()
- return e
- def is_compat(self, other):
- """
- Test whether other is another :py:class:`~.ModuleElement` with same
- module.
- """
- return isinstance(other, ModuleElement) and other.module == self.module
- def unify(self, other):
- """
- Try to make a compatible pair of :py:class:`~.ModuleElement`, one
- equivalent to this one, and one equivalent to the other.
- Explanation
- ===========
- We search for the nearest common ancestor module for the pair of
- elements, and represent each one there.
- Returns
- =======
- Pair ``(e1, e2)``
- Each ``ei`` is a :py:class:`~.ModuleElement`, they belong to the
- same :py:class:`~.Module`, ``e1`` is equivalent to ``self``, and
- ``e2`` is equivalent to ``other``.
- Raises
- ======
- UnificationFailed
- If ``self`` and ``other`` have no common ancestor module.
- """
- if self.module == other.module:
- return self, other
- nca = self.module.nearest_common_ancestor(other.module)
- if nca is not None:
- return self.to_ancestor(nca), other.to_ancestor(nca)
- raise UnificationFailed(f"Cannot unify {self} with {other}")
- def __eq__(self, other):
- if self.is_compat(other):
- return self.QQ_col == other.QQ_col
- return NotImplemented
- def equiv(self, other):
- """
- A :py:class:`~.ModuleElement` may test as equivalent to a rational
- number or another :py:class:`~.ModuleElement`, if they represent the
- same algebraic number.
- Explanation
- ===========
- This method is intended to check equivalence only in those cases in
- which it is easy to test; namely, when *other* is either a
- :py:class:`~.ModuleElement` that can be unified with this one (i.e. one
- which shares a common :py:class:`~.PowerBasis` ancestor), or else a
- rational number (which is easy because every :py:class:`~.PowerBasis`
- represents every rational number).
- Parameters
- ==========
- other : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.ModuleElement`
- Returns
- =======
- bool
- Raises
- ======
- UnificationFailed
- If ``self`` and ``other`` do not share a common
- :py:class:`~.PowerBasis` ancestor.
- """
- if self == other:
- return True
- elif isinstance(other, ModuleElement):
- a, b = self.unify(other)
- return a == b
- elif is_rat(other):
- if isinstance(self, PowerBasisElement):
- return self == self.module(0) * other
- else:
- return self.over_power_basis().equiv(other)
- return False
- def __add__(self, other):
- """
- A :py:class:`~.ModuleElement` can be added to a rational number, or to
- another :py:class:`~.ModuleElement`.
- Explanation
- ===========
- When the other summand is a rational number, it will be converted into
- a :py:class:`~.ModuleElement` (belonging to the first ancestor of this
- module that starts with unity).
- In all cases, the sum belongs to the nearest common ancestor (NCA) of
- the modules of the two summands. If the NCA does not exist, we return
- ``NotImplemented``.
- """
- if self.is_compat(other):
- d, e = self.denom, other.denom
- m = ilcm(d, e)
- u, v = m // d, m // e
- col = to_col([u * a + v * b for a, b in zip(self.coeffs, other.coeffs)])
- return type(self)(self.module, col, denom=m).reduced()
- elif isinstance(other, ModuleElement):
- try:
- a, b = self.unify(other)
- except UnificationFailed:
- return NotImplemented
- return a + b
- elif is_rat(other):
- return self + self.module.element_from_rational(other)
- return NotImplemented
- __radd__ = __add__
- def __neg__(self):
- return self * -1
- def __sub__(self, other):
- return self + (-other)
- def __rsub__(self, other):
- return -self + other
- def __mul__(self, other):
- """
- A :py:class:`~.ModuleElement` can be multiplied by a rational number,
- or by another :py:class:`~.ModuleElement`.
- Explanation
- ===========
- When the multiplier is a rational number, the product is computed by
- operating directly on the coefficients of this
- :py:class:`~.ModuleElement`.
- When the multiplier is another :py:class:`~.ModuleElement`, the product
- will belong to the nearest common ancestor (NCA) of the modules of the
- two operands, and that NCA must have a multiplication table. If the NCA
- does not exist, we return ``NotImplemented``. If the NCA does not have
- a mult. table, ``ClosureFailure`` will be raised.
- """
- if self.is_compat(other):
- M = self.module.mult_tab()
- A, B = self.col.flat(), other.col.flat()
- n = self.n
- C = [0] * n
- for u in range(n):
- for v in range(u, n):
- c = A[u] * B[v]
- if v > u:
- c += A[v] * B[u]
- if c != 0:
- R = M[u][v]
- for k in range(n):
- C[k] += c * R[k]
- d = self.denom * other.denom
- return self.from_int_list(self.module, C, denom=d)
- elif isinstance(other, ModuleElement):
- try:
- a, b = self.unify(other)
- except UnificationFailed:
- return NotImplemented
- return a * b
- elif is_rat(other):
- a, b = get_num_denom(other)
- if a == b == 1:
- return self
- else:
- return make_mod_elt(self.module,
- self.col * a, denom=self.denom * b).reduced()
- return NotImplemented
- __rmul__ = __mul__
- def _zeroth_power(self):
- return self.module.one()
- def _first_power(self):
- return self
- def __floordiv__(self, a):
- if is_rat(a):
- a = QQ(a)
- return self * (1/a)
- elif isinstance(a, ModuleElement):
- return self * (1//a)
- return NotImplemented
- def __rfloordiv__(self, a):
- return a // self.over_power_basis()
- def __mod__(self, m):
- r"""
- Reduce this :py:class:`~.ModuleElement` mod a :py:class:`~.Submodule`.
- Parameters
- ==========
- m : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.Submodule`
- If a :py:class:`~.Submodule`, reduce ``self`` relative to this.
- If an integer or rational, reduce relative to the
- :py:class:`~.Submodule` that is our own module times this constant.
- See Also
- ========
- .Submodule.reduce_element
- """
- if is_rat(m):
- m = m * self.module.whole_submodule()
- if isinstance(m, Submodule) and m.parent == self.module:
- return m.reduce_element(self)
- return NotImplemented
- class PowerBasisElement(ModuleElement):
- r"""
- Subclass for :py:class:`~.ModuleElement` instances whose module is a
- :py:class:`~.PowerBasis`.
- """
- @property
- def T(self):
- """Access the defining polynomial of the :py:class:`~.PowerBasis`."""
- return self.module.T
- def numerator(self, x=None):
- """Obtain the numerator as a polynomial over :ref:`ZZ`."""
- x = x or self.T.gen
- return Poly(reversed(self.coeffs), x, domain=ZZ)
- def poly(self, x=None):
- """Obtain the number as a polynomial over :ref:`QQ`."""
- return self.numerator(x=x) // self.denom
- @property
- def is_rational(self):
- """Say whether this element represents a rational number."""
- return self.col[1:, :].is_zero_matrix
- @property
- def generator(self):
- """
- Return a :py:class:`~.Symbol` to be used when expressing this element
- as a polynomial.
- If we have an associated :py:class:`~.AlgebraicField` whose primitive
- element has an alias symbol, we use that. Otherwise we use the variable
- of the minimal polynomial defining the power basis to which we belong.
- """
- K = self.module.number_field
- return K.ext.alias if K and K.ext.is_aliased else self.T.gen
- def as_expr(self, x=None):
- """Create a Basic expression from ``self``. """
- return self.poly(x or self.generator).as_expr()
- def norm(self, T=None):
- """Compute the norm of this number."""
- T = T or self.T
- x = T.gen
- A = self.numerator(x=x)
- return T.resultant(A) // self.denom ** self.n
- def inverse(self):
- f = self.poly()
- f_inv = f.invert(self.T)
- return self.module.element_from_poly(f_inv)
- def __rfloordiv__(self, a):
- return self.inverse() * a
- def _negative_power(self, e, modulo=None):
- return self.inverse() ** abs(e)
- def to_ANP(self):
- """Convert to an equivalent :py:class:`~.ANP`. """
- return ANP(list(reversed(self.QQ_col.flat())), QQ.map(self.T.rep.rep), QQ)
- def to_alg_num(self):
- """
- Try to convert to an equivalent :py:class:`~.AlgebraicNumber`.
- Explanation
- ===========
- In general, the conversion from an :py:class:`~.AlgebraicNumber` to a
- :py:class:`~.PowerBasisElement` throws away information, because an
- :py:class:`~.AlgebraicNumber` specifies a complex embedding, while a
- :py:class:`~.PowerBasisElement` does not. However, in some cases it is
- possible to convert a :py:class:`~.PowerBasisElement` back into an
- :py:class:`~.AlgebraicNumber`, namely when the associated
- :py:class:`~.PowerBasis` has a reference to an
- :py:class:`~.AlgebraicField`.
- Returns
- =======
- :py:class:`~.AlgebraicNumber`
- Raises
- ======
- StructureError
- If the :py:class:`~.PowerBasis` to which this element belongs does
- not have an associated :py:class:`~.AlgebraicField`.
- """
- K = self.module.number_field
- if K:
- return K.to_alg_num(self.to_ANP())
- raise StructureError("No associated AlgebraicField")
- class ModuleHomomorphism:
- r"""A homomorphism from one module to another."""
- def __init__(self, domain, codomain, mapping):
- r"""
- Parameters
- ==========
- domain : :py:class:`~.Module`
- The domain of the mapping.
- codomain : :py:class:`~.Module`
- The codomain of the mapping.
- mapping : callable
- An arbitrary callable is accepted, but should be chosen so as
- to represent an actual module homomorphism. In particular, should
- accept elements of *domain* and return elements of *codomain*.
- Examples
- ========
- >>> from sympy import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis, ModuleHomomorphism
- >>> T = Poly(cyclotomic_poly(5))
- >>> A = PowerBasis(T)
- >>> B = A.submodule_from_gens([2*A(j) for j in range(4)])
- >>> phi = ModuleHomomorphism(A, B, lambda x: 6*x)
- >>> print(phi.matrix()) # doctest: +SKIP
- DomainMatrix([[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]], (4, 4), ZZ)
- """
- self.domain = domain
- self.codomain = codomain
- self.mapping = mapping
- def matrix(self, modulus=None):
- r"""
- Compute the matrix of this homomorphism.
- Parameters
- ==========
- modulus : int, optional
- A positive prime number $p$ if the matrix should be reduced mod
- $p$.
- Returns
- =======
- :py:class:`~.DomainMatrix`
- The matrix is over :ref:`ZZ`, or else over :ref:`GF(p)` if a
- modulus was given.
- """
- basis = self.domain.basis_elements()
- cols = [self.codomain.represent(self.mapping(elt)) for elt in basis]
- if not cols:
- return DomainMatrix.zeros((self.codomain.n, 0), ZZ).to_dense()
- M = cols[0].hstack(*cols[1:])
- if modulus:
- M = M.convert_to(FF(modulus))
- return M
- def kernel(self, modulus=None):
- r"""
- Compute a Submodule representing the kernel of this homomorphism.
- Parameters
- ==========
- modulus : int, optional
- A positive prime number $p$ if the kernel should be computed mod
- $p$.
- Returns
- =======
- :py:class:`~.Submodule`
- This submodule's generators span the kernel of this
- homomorphism over :ref:`ZZ`, or else over :ref:`GF(p)` if a
- modulus was given.
- """
- M = self.matrix(modulus=modulus)
- if modulus is None:
- M = M.convert_to(QQ)
- # Note: Even when working over a finite field, what we want here is
- # the pullback into the integers, so in this case the conversion to ZZ
- # below is appropriate. When working over ZZ, the kernel should be a
- # ZZ-submodule, so, while the conversion to QQ above was required in
- # order for the nullspace calculation to work, conversion back to ZZ
- # afterward should always work.
- # TODO:
- # Watch <https://github.com/sympy/sympy/issues/21834>, which calls
- # for fraction-free algorithms. If this is implemented, we can skip
- # the conversion to `QQ` above.
- K = M.nullspace().convert_to(ZZ).transpose()
- return self.domain.submodule_from_matrix(K)
- class ModuleEndomorphism(ModuleHomomorphism):
- r"""A homomorphism from one module to itself."""
- def __init__(self, domain, mapping):
- r"""
- Parameters
- ==========
- domain : :py:class:`~.Module`
- The common domain and codomain of the mapping.
- mapping : callable
- An arbitrary callable is accepted, but should be chosen so as
- to represent an actual module endomorphism. In particular, should
- accept and return elements of *domain*.
- """
- super().__init__(domain, domain, mapping)
- class InnerEndomorphism(ModuleEndomorphism):
- r"""
- An inner endomorphism on a module, i.e. the endomorphism corresponding to
- multiplication by a fixed element.
- """
- def __init__(self, domain, multiplier):
- r"""
- Parameters
- ==========
- domain : :py:class:`~.Module`
- The domain and codomain of the endomorphism.
- multiplier : :py:class:`~.ModuleElement`
- The element $a$ defining the mapping as $x \mapsto a x$.
- """
- super().__init__(domain, lambda x: multiplier * x)
- self.multiplier = multiplier
- class EndomorphismRing:
- r"""The ring of endomorphisms on a module."""
- def __init__(self, domain):
- """
- Parameters
- ==========
- domain : :py:class:`~.Module`
- The domain and codomain of the endomorphisms.
- """
- self.domain = domain
- def inner_endomorphism(self, multiplier):
- r"""
- Form an inner endomorphism belonging to this endomorphism ring.
- Parameters
- ==========
- multiplier : :py:class:`~.ModuleElement`
- Element $a$ defining the inner endomorphism $x \mapsto a x$.
- Returns
- =======
- :py:class:`~.InnerEndomorphism`
- """
- return InnerEndomorphism(self.domain, multiplier)
- def represent(self, element):
- r"""
- Represent an element of this endomorphism ring, as a single column
- vector.
- Explanation
- ===========
- Let $M$ be a module, and $E$ its ring of endomorphisms. Let $N$ be
- another module, and consider a homomorphism $\varphi: N \rightarrow E$.
- In the event that $\varphi$ is to be represented by a matrix $A$, each
- column of $A$ must represent an element of $E$. This is possible when
- the elements of $E$ are themselves representable as matrices, by
- stacking the columns of such a matrix into a single column.
- This method supports calculating such matrices $A$, by representing
- an element of this endomorphism ring first as a matrix, and then
- stacking that matrix's columns into a single column.
- Examples
- ========
- Note that in these examples we print matrix transposes, to make their
- columns easier to inspect.
- >>> from sympy import Poly, cyclotomic_poly
- >>> from sympy.polys.numberfields.modules import PowerBasis
- >>> from sympy.polys.numberfields.modules import ModuleHomomorphism
- >>> T = Poly(cyclotomic_poly(5))
- >>> M = PowerBasis(T)
- >>> E = M.endomorphism_ring()
- Let $\zeta$ be a primitive 5th root of unity, a generator of our field,
- and consider the inner endomorphism $\tau$ on the ring of integers,
- induced by $\zeta$:
- >>> zeta = M(1)
- >>> tau = E.inner_endomorphism(zeta)
- >>> tau.matrix().transpose() # doctest: +SKIP
- DomainMatrix(
- [[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [-1, -1, -1, -1]],
- (4, 4), ZZ)
- The matrix representation of $\tau$ is as expected. The first column
- shows that multiplying by $\zeta$ carries $1$ to $\zeta$, the second
- column that it carries $\zeta$ to $\zeta^2$, and so forth.
- The ``represent`` method of the endomorphism ring ``E`` stacks these
- into a single column:
- >>> E.represent(tau).transpose() # doctest: +SKIP
- DomainMatrix(
- [[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1]],
- (1, 16), ZZ)
- This is useful when we want to consider a homomorphism $\varphi$ having
- ``E`` as codomain:
- >>> phi = ModuleHomomorphism(M, E, lambda x: E.inner_endomorphism(x))
- and we want to compute the matrix of such a homomorphism:
- >>> phi.matrix().transpose() # doctest: +SKIP
- DomainMatrix(
- [[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
- [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1],
- [0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1, 1, 0, 0, 0],
- [0, 0, 0, 1, -1, -1, -1, -1, 1, 0, 0, 0, 0, 1, 0, 0]],
- (4, 16), ZZ)
- Note that the stacked matrix of $\tau$ occurs as the second column in
- this example. This is because $\zeta$ is the second basis element of
- ``M``, and $\varphi(\zeta) = \tau$.
- Parameters
- ==========
- element : :py:class:`~.ModuleEndomorphism` belonging to this ring.
- Returns
- =======
- :py:class:`~.DomainMatrix`
- Column vector equalling the vertical stacking of all the columns
- of the matrix that represents the given *element* as a mapping.
- """
- if isinstance(element, ModuleEndomorphism) and element.domain == self.domain:
- M = element.matrix()
- # Transform the matrix into a single column, which should reproduce
- # the original columns, one after another.
- m, n = M.shape
- if n == 0:
- return M
- return M[:, 0].vstack(*[M[:, j] for j in range(1, n)])
- raise NotImplementedError
- def find_min_poly(alpha, domain, x=None, powers=None):
- r"""
- Find a polynomial of least degree (not necessarily irreducible) satisfied
- by an element of a finitely-generated ring with unity.
- Examples
- ========
- For the $n$th cyclotomic field, $n$ an odd prime, consider the quadratic
- equation whose roots are the two periods of length $(n-1)/2$. Article 356
- of Gauss tells us that we should get $x^2 + x - (n-1)/4$ or
- $x^2 + x + (n+1)/4$ according to whether $n$ is 1 or 3 mod 4, respectively.
- >>> from sympy import Poly, cyclotomic_poly, primitive_root, QQ
- >>> from sympy.abc import x
- >>> from sympy.polys.numberfields.modules import PowerBasis, find_min_poly
- >>> n = 13
- >>> g = primitive_root(n)
- >>> C = PowerBasis(Poly(cyclotomic_poly(n, x)))
- >>> ee = [g**(2*k+1) % n for k in range((n-1)//2)]
- >>> eta = sum(C(e) for e in ee)
- >>> print(find_min_poly(eta, QQ, x=x).as_expr())
- x**2 + x - 3
- >>> n = 19
- >>> g = primitive_root(n)
- >>> C = PowerBasis(Poly(cyclotomic_poly(n, x)))
- >>> ee = [g**(2*k+2) % n for k in range((n-1)//2)]
- >>> eta = sum(C(e) for e in ee)
- >>> print(find_min_poly(eta, QQ, x=x).as_expr())
- x**2 + x + 5
- Parameters
- ==========
- alpha : :py:class:`~.ModuleElement`
- The element whose min poly is to be found, and whose module has
- multiplication and starts with unity.
- domain : :py:class:`~.Domain`
- The desired domain of the polynomial.
- x : :py:class:`~.Symbol`, optional
- The desired variable for the polynomial.
- powers : list, optional
- If desired, pass an empty list. The powers of *alpha* (as
- :py:class:`~.ModuleElement` instances) from the zeroth up to the degree
- of the min poly will be recorded here, as we compute them.
- Returns
- =======
- :py:class:`~.Poly`, ``None``
- The minimal polynomial for alpha, or ``None`` if no polynomial could be
- found over the desired domain.
- Raises
- ======
- MissingUnityError
- If the module to which alpha belongs does not start with unity.
- ClosureFailure
- If the module to which alpha belongs is not closed under
- multiplication.
- """
- R = alpha.module
- if not R.starts_with_unity():
- raise MissingUnityError("alpha must belong to finitely generated ring with unity.")
- if powers is None:
- powers = []
- one = R(0)
- powers.append(one)
- powers_matrix = one.column(domain=domain)
- ak = alpha
- m = None
- for k in range(1, R.n + 1):
- powers.append(ak)
- ak_col = ak.column(domain=domain)
- try:
- X = powers_matrix._solve(ak_col)[0]
- except DMBadInputError:
- # This means alpha^k still isn't in the domain-span of the lower powers.
- powers_matrix = powers_matrix.hstack(ak_col)
- ak *= alpha
- else:
- # alpha^k is in the domain-span of the lower powers, so we have found a
- # minimal-degree poly for alpha.
- coeffs = [1] + [-c for c in reversed(X.to_list_flat())]
- x = x or Dummy('x')
- if domain.is_FF:
- m = Poly(coeffs, x, modulus=domain.mod)
- else:
- m = Poly(coeffs, x, domain=domain)
- break
- return m
|