Path: blob/master/sage/algebras/quatalg/quaternion_algebra.py
4158 views
"""1Quaternion Algebras23AUTHORS:45- Jon Bobber -- 2009 rewrite67- William Stein -- 2009 rewrite89This code is partly based on Sage code by David Kohel from 2005.1011TESTS:1213Pickling test::1415sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)16sage: Q == loads(dumps(Q))17True18"""1920########################################################################21# Copyright (C) 2009 William Stein <[email protected]>22# Copyright (C) 2009 Jonathon Bober <[email protected]>23#24# Distributed under the terms of the GNU General Public License (GPL)25#26# This code is distributed in the hope that it will be useful,27# but WITHOUT ANY WARRANTY; without even the implied warranty of28# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU29# General Public License for more details.30#31# The full text of the GPL is available at:32#33# http://www.gnu.org/licenses/34########################################################################353637from sage.rings.arith import (GCD,38hilbert_conductor_inverse, hilbert_conductor,39factor, gcd, lcm)40from sage.rings.all import RR, Integer41from sage.rings.integer_ring import ZZ42from sage.rings.rational import Rational43from sage.rings.finite_rings.constructor import GF4445from sage.rings.ring import Algebra, is_Field46from sage.rings.ideal import Ideal_fractional47from sage.rings.rational_field import is_RationalField, QQ48from sage.rings.number_field.number_field import is_NumberField49from sage.structure.parent_gens import ParentWithGens, normalize_names50from sage.matrix.matrix_space import MatrixSpace51from sage.matrix.constructor import diagonal_matrix, matrix52from sage.structure.sequence import Sequence53from sage.structure.element import is_RingElement54from sage.modules.free_module import VectorSpace, FreeModule55from sage.modules.free_module_element import vector5657import quaternion_algebra_element58import quaternion_algebra_cython5960from sage.modular.modsym.p1list import P1List6162from sage.misc.cachefunc import cached_method6364########################################################65# Constructor66########################################################6768_cache = {}6970def QuaternionAlgebra(arg0, arg1=None, arg2=None, names='i,j,k'):71"""72There are three input formats:7374- ``QuaternionAlgebra(a, b)``: quaternion algebra generated by ``i``, ``j``75subject to `i^2 = a`, `j^2 = b`, `j * i = -i * j`.7677- ``QuaternionAlgebra(K, a, b)``: same as above but over a field ``K``.78Here, ``a`` and ``b`` are nonzero elements of a field (``K``) of79characteristic not 2, and we set `k = i * j`.8081- ``QuaternionAlgebra(D)``: a rational quaternion algebra with82discriminant ``D``, where `D > 1` is a squarefree integer.8384EXAMPLES:8586``QuaternionAlgebra(a, b)`` - return quaternion algebra over the87*smallest* field containing the nonzero elements ``a`` and ``b`` with88generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b` and `j*i=-i*j`::8990sage: QuaternionAlgebra(-2,-3)91Quaternion Algebra (-2, -3) with base ring Rational Field92sage: QuaternionAlgebra(GF(5)(2), GF(5)(3))93Quaternion Algebra (2, 3) with base ring Finite Field of size 594sage: QuaternionAlgebra(2, GF(5)(3))95Quaternion Algebra (2, 3) with base ring Finite Field of size 596sage: QuaternionAlgebra(QQ[sqrt(2)](-1), -5)97Quaternion Algebra (-1, -5) with base ring Number Field in sqrt2 with defining polynomial x^2 - 298sage: QuaternionAlgebra(sqrt(-1), sqrt(-3))99Quaternion Algebra (I, sqrt(-3)) with base ring Symbolic Ring100sage: QuaternionAlgebra(1r,1)101Quaternion Algebra (1, 1) with base ring Rational Field102103Python ints, longs and floats may be passed to the ``QuaternionAlgebra(a, b)`` constructor, as may104all pairs of nonzero elements of a ring not of characteristic 2. The following tests address105the issues raised in trac ticket 10601::106107sage: QuaternionAlgebra(1r,1)108Quaternion Algebra (1, 1) with base ring Rational Field109sage: QuaternionAlgebra(1,1.0r)110Quaternion Algebra (1.00000000000000, 1.00000000000000) with base ring Real Field with 53 bits of precision111sage: QuaternionAlgebra(0,0)112Traceback (most recent call last):113...114ValueError: a and b must be nonzero115sage: QuaternionAlgebra(GF(2)(1),1)116Traceback (most recent call last):117...118ValueError: a and b must be elements of a ring with characteristic not 2119sage: a = PermutationGroupElement([1,2,3])120sage: QuaternionAlgebra(a, a)121Traceback (most recent call last):122...123ValueError: a and b must be elements of a ring with characteristic not 2124125``QuaternionAlgebra(K, a, b)`` - return quaternion algebra over the126field ``K`` with generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b`127and `i*j=-j*i`::128129sage: QuaternionAlgebra(QQ, -7, -21)130Quaternion Algebra (-7, -21) with base ring Rational Field131sage: QuaternionAlgebra(QQ[sqrt(2)], -2,-3)132Quaternion Algebra (-2, -3) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2133134``QuaternionAlgebra(D)`` - ``D`` is a squarefree integer; returns a135rational quaternion algebra of discriminant ``D``::136137sage: QuaternionAlgebra(1)138Quaternion Algebra (-1, 1) with base ring Rational Field139sage: QuaternionAlgebra(2)140Quaternion Algebra (-1, -1) with base ring Rational Field141sage: QuaternionAlgebra(7)142Quaternion Algebra (-1, -7) with base ring Rational Field143sage: QuaternionAlgebra(2*3*5*7)144Quaternion Algebra (-22, 210) with base ring Rational Field145146If the coefficients `a` and `b` in the definition of the quaternion147algebra are not integral, then a slower generic type is used for148arithmetic::149150sage: type(QuaternionAlgebra(-1,-3).0)151<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>152sage: type(QuaternionAlgebra(-1,-3/2).0)153<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>154155Make sure caching is sane::156157sage: A = QuaternionAlgebra(2,3); A158Quaternion Algebra (2, 3) with base ring Rational Field159sage: B = QuaternionAlgebra(GF(5)(2),GF(5)(3)); B160Quaternion Algebra (2, 3) with base ring Finite Field of size 5161sage: A is QuaternionAlgebra(2,3)162True163sage: B is QuaternionAlgebra(GF(5)(2),GF(5)(3))164True165sage: Q = QuaternionAlgebra(2); Q166Quaternion Algebra (-1, -1) with base ring Rational Field167sage: Q is QuaternionAlgebra(QQ,-1,-1)168True169sage: Q is QuaternionAlgebra(-1,-1)170True171sage: Q.<ii,jj,kk> = QuaternionAlgebra(15); Q.variable_names()172('ii', 'jj', 'kk')173sage: QuaternionAlgebra(15).variable_names()174('i', 'j', 'k')175176TESTS:177178Verify that bug found when working on trac 12006 involving coercing invariants179into the base field is fixed::180181sage: Q = QuaternionAlgebra(-1,-1); Q182Quaternion Algebra (-1, -1) with base ring Rational Field183sage: parent(Q._a)184Rational Field185sage: parent(Q._b)186Rational Field187"""188189# QuaternionAlgebra(D)190if arg1 is None and arg2 is None:191K = QQ192D = Integer(arg0)193a, b = hilbert_conductor_inverse(D)194a = Rational(a); b = Rational(b)195196elif arg2 is None:197# If arg0 or arg1 are Python data types, coerce them198# to the relevant Sage types. This is a bit inelegant.199L = []200for a in [arg0,arg1]:201if is_RingElement(a):202L.append(a)203elif isinstance(a, int) or isinstance(a, long):204L.append(Integer(a))205elif isinstance(a, float):206L.append(RR(a))207else:208raise ValueError("a and b must be elements of a ring with characteristic not 2")209210# QuaternionAlgebra(a, b)211v = Sequence(L)212K = v.universe().fraction_field()213a = K(v[0])214b = K(v[1])215216# QuaternionAlgebra(K, a, b)217else:218K = arg0219if not is_Field(K):220raise TypeError("base ring of quaternion algebra must be a field")221a = K(arg1)222b = K(arg2)223224if K.characteristic() == 2:225# Lameness!226raise ValueError("a and b must be elements of a ring with characteristic not 2")227if a == 0 or b == 0:228raise ValueError("a and b must be nonzero")229230global _cache231names = normalize_names(3, names)232key = (K, a, b, names)233if key in _cache:234return _cache[key]235A = QuaternionAlgebra_ab(K, a, b, names=names)236A._key = key237_cache[key] = A238return A239240241242243########################################################244# Classes245########################################################246247def is_QuaternionAlgebra(A):248"""249Return ``True`` if ``A`` is of the QuaternionAlgebra data type.250251EXAMPLES::252253sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(QuaternionAlgebra(QQ,-1,-1))254True255sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(ZZ)256False257"""258return isinstance(A, QuaternionAlgebra_abstract)259260class QuaternionAlgebra_abstract(Algebra):261def _repr_(self):262"""263EXAMPLES::264265sage: sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_abstract(QQ)._repr_()266'Quaternion Algebra with base ring Rational Field'267"""268return "Quaternion Algebra with base ring %s"%self.base_ring()269270def ngens(self):271"""272Return the number of generators of the quaternion algebra as a K-vector273space, not including 1. This value is always 3: the algebra is spanned274by the standard basis `1`, `i`, `j`, `k`.275276EXAMPLES::277278sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)279sage: Q.ngens()2803281sage: Q.gens()282[i, j, k]283"""284return 3285286def basis(self):287"""288Return the fixed basis of ``self``, which is `1`, `i`, `j`, `k`, where289`i`, `j`, `k` are the generators of ``self``.290291EXAMPLES::292293sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)294sage: Q.basis()295(1, i, j, k)296297sage: Q.<xyz,abc,theta> = QuaternionAlgebra(GF(9,'a'),-5,-2)298sage: Q.basis()299(1, xyz, abc, theta)300301The basis is cached::302303sage: Q.basis() is Q.basis()304True305"""306try:307return self.__basis308except AttributeError:309self.__basis = tuple([self(1)] + list(self.gens()))310return self.__basis311312def inner_product_matrix(self):313"""314Return the inner product matrix associated to ``self``, i.e. the315Gram matrix of the reduced norm as a quadratic form on ``self``.316The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix317is just the diagonal matrix with diagonal entries `2`, `2a`, `2b`,318`2ab`.319320EXAMPLES::321322sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)323sage: Q.inner_product_matrix()324[ 2 0 0 0]325[ 0 10 0 0]326[ 0 0 38 0]327[ 0 0 0 190]328"""329try: return self.__inner_product_matrix330except AttributeError: pass331332a, b = self._a, self._b333M = diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b])334M.set_immutable()335self.__inner_product_matrix = M336return M337338def is_commutative(self):339"""340Return ``False`` always, since all quaternion algebras are341noncommutative.342343EXAMPLES::344345sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3,-7)346sage: Q.is_commutative()347False348"""349return False350351def is_division_algebra(self):352"""353Return ``True`` if the quaternion algebra is a division algebra (i.e.354every nonzero element in ``self`` is invertible), and ``False`` if the355quaternion algebra is isomorphic to the 2x2 matrix algebra.356357EXAMPLES::358359sage: QuaternionAlgebra(QQ,-5,-2).is_division_algebra()360True361sage: QuaternionAlgebra(1).is_division_algebra()362False363sage: QuaternionAlgebra(2,9).is_division_algebra()364False365sage: QuaternionAlgebra(RR(2.),1).is_division_algebra()366Traceback (most recent call last):367...368NotImplementedError: base field must be rational numbers369"""370return self.discriminant() != 1371372def is_matrix_ring(self):373"""374Return ``True`` if the quaternion algebra is isomorphic to the 2x2375matrix ring, and ``False`` if ``self`` is a division algebra (i.e.376every nonzero element in ``self`` is invertible).377378EXAMPLES::379380sage: QuaternionAlgebra(QQ,-5,-2).is_matrix_ring()381False382sage: QuaternionAlgebra(1).is_matrix_ring()383True384sage: QuaternionAlgebra(2,9).is_matrix_ring()385True386sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring()387Traceback (most recent call last):388...389NotImplementedError: base field must be rational numbers390391"""392return self.discriminant() == 1393394def is_exact(self):395"""396Return ``True`` if elements of this quaternion algebra are represented397exactly, i.e. there is no precision loss when doing arithmetic. A398quaternion algebra is exact if and only if its base field is399exact.400401EXAMPLES::402403sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)404sage: Q.is_exact()405True406sage: Q.<i,j,k> = QuaternionAlgebra(Qp(7), -3, -7)407sage: Q.is_exact()408False409"""410return self.base_ring().is_exact()411412def is_field(self, proof = True):413"""414Return ``False`` always, since all quaternion algebras are415noncommutative and all fields are commutative.416417EXAMPLES::418419sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)420sage: Q.is_field()421False422"""423return False424425def is_finite(self):426"""427Return ``True`` if the quaternion algebra is finite as a set.428429Algorithm: A quaternion algebra is finite if and only if the430base field is finite.431432EXAMPLES::433434sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)435sage: Q.is_finite()436False437sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)438sage: Q.is_finite()439True440"""441return self.base_ring().is_finite()442443def is_integral_domain(self, proof = True):444"""445Return ``False`` always, since all quaternion algebras are446noncommutative and integral domains are commutative (in Sage).447448EXAMPLES::449450sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)451sage: Q.is_integral_domain()452False453"""454return False455456def is_noetherian(self):457"""458Return ``True`` always, since any quaternion algebra is a noetherian459ring (because it is a finitely generated module over a field).460461EXAMPLES::462463sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)464sage: Q.is_noetherian()465True466"""467return True468469def order(self):470"""471Return the number of elements of the quaternion algebra, or472``+Infinity`` if the algebra is not finite.473474EXAMPLES::475476sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)477sage: Q.order()478+Infinity479sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)480sage: Q.order()481625482"""483return (self.base_ring().order())**4484485def random_element(self, *args, **kwds):486"""487Return a random element of this quaternion algebra.488489The ``args`` and ``kwds`` are passed to the ``random_element`` method490of the base ring.491492EXAMPLES::493494sage: QuaternionAlgebra(QQ[sqrt(2)],-3,7).random_element()495(sqrt2 + 2)*i + (-12*sqrt2 - 2)*j + (-sqrt2 + 1)*k496sage: QuaternionAlgebra(-3,19).random_element()497-1 + 2*i - j - 6/5*k498sage: QuaternionAlgebra(GF(17)(2),3).random_element()49914 + 10*i + 4*j + 7*k500501Specify the numerator and denominator bounds::502503sage: QuaternionAlgebra(-3,19).random_element(10^6,10^6)504-979933/553629 + 255525/657688*i - 3511/6929*j - 700105/258683*k505"""506K = self.base_ring()507return self([ K.random_element(*args, **kwds) for _ in range(4) ])508509def vector_space(self):510"""511Return the vector space associated to ``self`` with inner product given512by the reduced norm.513514EXAMPLES::515516sage: QuaternionAlgebra(-3,19).vector_space()517Ambient quadratic space of dimension 4 over Rational Field518Inner product matrix:519[ 2 0 0 0]520[ 0 6 0 0]521[ 0 0 -38 0]522[ 0 0 0 -114]523"""524try:525return self.__vector_space526except AttributeError:527V = VectorSpace(self.base_ring(), 4, inner_product_matrix = self.inner_product_matrix())528self.__vector_space = V529return V530531532class QuaternionAlgebra_ab(QuaternionAlgebra_abstract):533"""534The quaternion algebra of the form `(a, b/K)`, where `i^2=a`, `j^2 = b`,535and `j*i = -i*j`. ``K`` is a field not of characteristic 2 and ``a``,536``b`` are nonzero elements of ``K``.537538See ``QuaternionAlgebra`` for many more examples.539540EXAMPLES::541542sage: QuaternionAlgebra(QQ, -7, -21) # indirect doctest543Quaternion Algebra (-7, -21) with base ring Rational Field544"""545def __init__(self, base_ring, a, b, names='i,j,k'):546"""547Create the quaternion algebra with `i^2 = a`, `j^2 = b`, and548`i*j = -j*i = k`.549550INPUT:551552- ``base_ring`` - commutative ring553- ``a, b`` - elements of ``base_ring``554- ``names`` - string (optional, default 'i, j, k') names of the generators555556TESTS:557558Test making quaternion elements (using the element constructor)::559560sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-1,-2)561sage: a = Q(2/3); a5622/3563sage: type(a)564<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>565sage: Q(a)5662/3567sage: Q([1,2,3,4])5681 + 2*i + 3*j + 4*k569sage: Q((1,2,3,4))5701 + 2*i + 3*j + 4*k571sage: Q(-3/5)572-3/5573574The base ring must be a field::575576sage: Q.<ii,jj,kk> = QuaternionAlgebra(ZZ,-5,-19)577Traceback (most recent call last):578...579TypeError: base ring of quaternion algebra must be a field580"""581ParentWithGens.__init__(self, base_ring, names=names)582self._a = a583self._b = b584if not is_Field(base_ring):585raise TypeError("base ring of quaternion algebra must be a field")586if is_RationalField(base_ring) and a.denominator() == 1 and b.denominator() == 1:587element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_rational_field588elif is_NumberField(base_ring) and base_ring.degree() > 2 and base_ring.is_absolute() and \589a.denominator() == 1 and b.denominator() == 1 and base_ring.defining_polynomial().is_monic():590# This QuaternionAlgebraElement_number_field class is not591# designed to work with elements of a quadratic field. To592# do that, the main thing would be to implement593# __getitem__, etc. This would maybe give a factor of 2594# (or more?) speedup. Much care must be taken because the595# underlying representation of quadratic fields is a bit596# tricky.597element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_number_field598else:599element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_generic600self._populate_coercion_lists_(coerce_list=[base_ring], element_constructor=element_constructor)601self._gens = [self([0,1,0,0]), self([0,0,1,0]), self([0,0,0,1])]602603def maximal_order(self):604"""605Return a maximal order in this quaternion algebra.606607OUTPUT: an order in this quaternion algebra608609EXAMPLES::610611sage: QuaternionAlgebra(-1,-7).maximal_order()612Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)613"""614try: return self.__maximal_order615except AttributeError: pass616if self.base_ring() == QQ and self.discriminant().is_prime():617from sage.modular.quatalg.brandt import maximal_order618R = maximal_order(self)619self.__maximal_order = R620return R621raise NotImplementedError("maximal order only implemented for rational quaternion algebras of prime discriminant")622623def invariants(self):624"""625Return the structural invariants `a`, `b` of this quaternion626algebra: ``self`` is generated by `i`, `j` subject to627`i^2 = a`, `j^2 = b` and `j*i = -i*j`.628629EXAMPLES::630631sage: Q.<i,j,k> = QuaternionAlgebra(15)632sage: Q.invariants()633(-3, 5)634sage: i^2635-3636sage: j^26375638"""639return self._a, self._b640641def __cmp__(self, other):642"""643Compare self and other.644645EXAMPLES::646647sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-7))6480649sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-5))650-1651sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-10))6521653"""654if not isinstance(other, QuaternionAlgebra_abstract):655return cmp(type(self), type(other))656c = cmp(self.base_ring(), other.base_ring())657if c: return c658return cmp((self._a, self._b), (other._a, other._b))659660def __reduce__(self):661"""662Internal method used for pickling.663664TESTS::665666sage: QuaternionAlgebra(QQ,-1,-2).__reduce__()667(<function unpickle_QuaternionAlgebra_v0 at ...>, (Rational Field, -1, -2, ('i', 'j', 'k')))668669Test uniqueness of parent::670671sage: Q = QuaternionAlgebra(QQ,-1,-2)672sage: loads(dumps(Q)) is Q673True674"""675return unpickle_QuaternionAlgebra_v0, self._key676677def gen(self, i=0):678"""679Return the `i^{th}` generator of ``self``.680681INPUT:682683- ``i`` - integer (optional, default 0)684685EXAMPLES::686687sage: Q.<ii,jj,kk> = QuaternionAlgebra(QQ,-1,-2); Q688Quaternion Algebra (-1, -2) with base ring Rational Field689sage: Q.gen(0)690ii691sage: Q.gen(1)692jj693sage: Q.gen(2)694kk695sage: Q.gens()696[ii, jj, kk]697"""698return self._gens[i]699700def _repr_(self):701"""702Print representation.703704TESTS::705706sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)707sage: type(Q)708<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_ab'>709sage: Q._repr_()710'Quaternion Algebra (-5, -2) with base ring Rational Field'711sage: Q712Quaternion Algebra (-5, -2) with base ring Rational Field713sage: print Q714Quaternion Algebra (-5, -2) with base ring Rational Field715sage: str(Q)716'Quaternion Algebra (-5, -2) with base ring Rational Field'717"""718return "Quaternion Algebra (%r, %r) with base ring %s"%(self._a, self._b, self.base_ring())719720def inner_product_matrix(self):721"""722Return the inner product matrix associated to ``self``, i.e. the723Gram matrix of the reduced norm as a quadratic form on ``self``.724The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix725is just the diagonal matrix with diagonal entries `1`, `a`, `b`, `ab`.726727EXAMPLES::728729sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)730sage: Q.inner_product_matrix()731[ 2 0 0 0]732[ 0 10 0 0]733[ 0 0 38 0]734[ 0 0 0 190]735736sage: R.<a,b> = QQ[]; Q.<i,j,k> = QuaternionAlgebra(Frac(R),a,b)737sage: Q.inner_product_matrix()738[ 2 0 0 0]739[ 0 -2*a 0 0]740[ 0 0 -2*b 0]741[ 0 0 0 2*a*b]742"""743a, b = self._a, self._b744return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b])745746def discriminant(self):747"""748Given a quaternion algebra `A` defined over the field of749rational numbers, return the discriminant of `A`, i.e. the750product of the ramified primes of `A`.751752EXAMPLES::753754sage: QuaternionAlgebra(210,-22).discriminant()755210756sage: QuaternionAlgebra(19).discriminant()75719758759This raises a ``NotImplementedError`` if the base field is not760the rational numbers::761762sage: QuaternionAlgebra(QQ[sqrt(2)],3,19).discriminant()763Traceback (most recent call last):764...765NotImplementedError: base field must be rational numbers766"""767try: return self.__discriminant768except AttributeError: pass769if not is_RationalField(self.base_ring()):770raise NotImplementedError("base field must be rational numbers")771self.__discriminant = hilbert_conductor(self._a, self._b)772return self.__discriminant773774def ramified_primes(self):775"""776Return the primes that ramify in this quaternion algebra. Currently777only implemented over the rational numbers.778779EXAMPLES::780781sage: QuaternionAlgebra(QQ, -1, -1).ramified_primes()782[2]783"""784#TODO: more examples785786return [f[0] for f in factor(self.discriminant())]787788def _magma_init_(self, magma):789"""790Return Magma version of this quaternion algebra.791792EXAMPLES::793794sage: Q = QuaternionAlgebra(-1,-1); Q795Quaternion Algebra (-1, -1) with base ring Rational Field796sage: Q._magma_init_(magma) # optional - magma797'QuaternionAlgebra(_sage_[...],-1/1,-1/1)'798sage: A = magma(Q); A # optional - magma799Quaternion Algebra with base ring Rational Field, defined by i^2 = -1, j^2 = -1800sage: A.RamifiedPlaces() # optional - magma801[802Ideal of Integer Ring generated by 2803]804805A more complicated example involving a quaternion algebra over a number field::806807sage: K.<a> = QQ[sqrt(2)]; Q = QuaternionAlgebra(K,-1,a); Q808Quaternion Algebra (-1, sqrt2) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2809sage: magma(Q) # optional - magma810Quaternion Algebra with base ring Number Field with defining polynomial x^2 - 2 over the Rational Field, defined by i^2 = -1, j^2 = sqrt2811sage: Q._magma_init_(magma) # optional - magma812'QuaternionAlgebra(_sage_[...],(_sage_[...]![-1, 0]),(_sage_[...]![0, 1]))'813"""814R = magma(self.base_ring())815return 'QuaternionAlgebra(%s,%s,%s)'%(R.name(),816self._a._magma_init_(magma),817self._b._magma_init_(magma))818819def quaternion_order(self, basis, check=True):820"""821Return the order of this quaternion order with given basis.822823INPUT:824825- ``basis`` - list of 4 elements of ``self``826- ``check`` - bool (default: ``True``)827828EXAMPLES::829830sage: Q.<i,j,k> = QuaternionAlgebra(-11,-1)831sage: Q.quaternion_order([1,i,j,k])832Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1, i, j, k)833834We test out ``check=False``::835836sage: Q.quaternion_order([1,i,j,k], check=False)837Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis [1, i, j, k]838sage: Q.quaternion_order([i,j,k], check=False)839Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis [i, j, k]840"""841return QuaternionOrder(self, basis, check=check)842843def ideal(self, gens, left_order=None, right_order=None, check=True, **kwds):844r"""845Return the quaternion ideal with given gens over `\ZZ`.846Neither a left or right order structure need be specified.847848INPUT:849850- ``gens`` -- a list of elements of this quaternion order851852- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must8534-tuple that forms a Hermite basis for an ideal854855- ``left_order`` -- a quaternion order or ``None``856857- ``right_order`` -- a quaternion order or ``None``858859EXAMPLES::860861sage: R = QuaternionAlgebra(-11,-1)862sage: R.ideal([2*a for a in R.basis()])863Fractional ideal (2, 2*i, 2*j, 2*k)864"""865if self.base_ring() == QQ:866return QuaternionFractionalIdeal_rational(gens, left_order=left_order, right_order=right_order, check=check)867else:868raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")869870@cached_method871def modp_splitting_data(self, p):872r"""873Return mod `p` splitting data for this quaternion algebra at874the unramified prime `p`. This is `2\times 2`875matrices `I`, `J`, `K` over the finite field `\GF{p}` such that if876the quaternion algebra has generators `i, j, k`, then `I^2 =877i^2`, `J^2 = j^2`, `IJ=K` and `IJ=-JI`.878879.. note::880881Currently only implemented when `p` is odd and the base882ring is `\QQ`.883884INPUT:885886- `p` -- unramified odd prime887888OUTPUT:889890- 2-tuple of matrices over finite field891892EXAMPLES::893894sage: Q = QuaternionAlgebra(-15, -19)895sage: Q.modp_splitting_data(7)896(897[0 6] [6 1] [6 6]898[1 0], [1 1], [6 1]899)900sage: Q.modp_splitting_data(next_prime(10^5))901(902[ 0 99988] [97311 4] [99999 59623]903[ 1 0], [13334 2692], [97311 4]904)905sage: I,J,K = Q.modp_splitting_data(23)906sage: I907[0 8]908[1 0]909sage: I^2910[8 0]911[0 8]912sage: J913[19 2]914[17 4]915sage: J^2916[4 0]917[0 4]918sage: I*J == -J*I919True920sage: I*J == K921True922923The following is a good test because of the asserts in the code::924925sage: v = [Q.modp_splitting_data(p) for p in primes(20,1000)]926927928Proper error handling::929930sage: Q.modp_splitting_data(5)931Traceback (most recent call last):932...933NotImplementedError: algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)934935sage: Q.modp_splitting_data(2)936Traceback (most recent call last):937...938NotImplementedError: p must be odd939"""940if self.base_ring() != QQ:941raise NotImplementedError("must be rational quaternion algebra")942p = ZZ(p)943if not p.is_prime():944raise ValueError("p (=%s) must be prime"%p)945if p == 2:946raise NotImplementedError("p must be odd")947if self.discriminant() % p == 0:948raise ValueError("p (=%s) must be an unramified prime"%p)949950i, j, k = self.gens()951F = GF(p)952i2 = F(i*i)953j2 = F(j*j)954955M = MatrixSpace(F, 2)956I = M([0,i2,1,0])957if i2 == 0:958raise NotImplementedError("algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)")959i2inv = 1/i2960a = None961for b in list(F):962if not b: continue963c = j2 + i2inv * b*b964if c.is_square():965a = -c.sqrt()966break967968if a is None:969# do a fallback search, maybe needed in char 3 sometimes.970for J in M:971K = I*J972if J*J == j2 and K == -J*I:973return I, J, K974975J = M([a,b,(j2-a*a)/b, -a])976K = I*J977assert K == -J*I, "bug in that I,J don't skew commute"978return I, J, K979980def modp_splitting_map(self, p):981r"""982Return Python map from the (`p`-integral) quaternion algebra to983the set of `2\times 2` matrices over `\GF{p}`.984985INPUT:986987- `p` -- prime number988989EXAMPLES::990991sage: Q.<i,j,k> = QuaternionAlgebra(-1, -7)992sage: f = Q.modp_splitting_map(13)993sage: a = 2+i-j+3*k; b = 7+2*i-4*j+k994sage: f(a*b)995[12 3]996[10 5]997sage: f(a)*f(b)998[12 3]999[10 5]1000"""1001I, J, K = self.modp_splitting_data(p)1002F = I.base_ring()1003def phi(q):1004v = [F(a) for a in q.coefficient_tuple()]1005return v[0] + I*v[1] + J*v[2] + K*v[3]1006return phi100710081009############################################################1010# Unpickling1011############################################################1012def unpickle_QuaternionAlgebra_v0(*key):1013"""1014The 0th version of pickling for quaternion algebras.10151016EXAMPLES::10171018sage: Q = QuaternionAlgebra(-5,-19)1019sage: f, t = Q.__reduce__()1020sage: sage.algebras.quatalg.quaternion_algebra.unpickle_QuaternionAlgebra_v0(*t)1021Quaternion Algebra (-5, -19) with base ring Rational Field1022sage: loads(dumps(Q)) == Q1023True1024sage: loads(dumps(Q)) is Q1025True1026"""1027return QuaternionAlgebra(*key)102810291030class QuaternionOrder(Algebra):1031"""1032An order in a quaternion algebra.10331034EXAMPLES::10351036sage: QuaternionAlgebra(-1,-7).maximal_order()1037Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1038sage: type(QuaternionAlgebra(-1,-7).maximal_order())1039<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder'>1040"""1041def __init__(self, A, basis, check=True):1042"""1043INPUT:10441045- ``A`` - a quaternion algebra1046- ``basis`` - list of 4 integral quaternions in ``A``1047- ``check`` - whether to do type and other consistency checks10481049.. note::10501051** TODO: We do *not* currently check that basis is1052closed under multiplication!! **10531054EXAMPLES::10551056sage: A.<i,j,k> = QuaternionAlgebra(-3,-5)1057sage: sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,i,j,k])1058Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, i, j, k)1059sage: R = sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,2*i,2*j,2*k]); R1060Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, 2*i, 2*j, 2*k)1061sage: type(R)1062<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder'>1063"""1064if check:1065# right data type1066if not isinstance(basis, (list, tuple)):1067raise TypeError("basis must be a list or tuple")1068# right length1069if len(basis) != 4:1070raise ValueError("basis must have length 4")1071# coerce to common parent1072basis = tuple([A(x) for x in basis])1073self.__basis = basis1074self.__quaternion_algebra = A1075ParentWithGens.__init__(self, ZZ, names=None)10761077def gens(self):1078"""1079Return generators for self.10801081EXAMPLES::10821083sage: QuaternionAlgebra(-1,-7).maximal_order().gens()1084(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1085"""1086return self.__basis10871088def ngens(self):1089"""1090Return the number of generators (which is 4).10911092EXAMPLES::10931094sage: QuaternionAlgebra(-1,-7).maximal_order().ngens()109541096"""1097return 410981099def gen(self, n):1100"""1101Return the n-th generator.11021103INPUT:11041105- ``n`` - an integer between 0 and 3, inclusive.11061107EXAMPLES::11081109sage: R = QuaternionAlgebra(-11,-1).maximal_order(); R1110Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1111sage: R.gen(0)11121/2 + 1/2*j1113sage: R.gen(1)11141/2*i + 1/2*k1115sage: R.gen(2)1116j1117sage: R.gen(3)1118k1119"""1120return self.__basis[n]11211122def __cmp__(self, R):1123"""1124Compare orders self and other. Two orders are equal if they1125have the same basis and are in the same quaternion algebra.11261127EXAMPLES::11281129sage: R = QuaternionAlgebra(-11,-1).maximal_order()1130sage: R == R # indirect doctest1131True1132sage: R == QuaternionAlgebra(-13,-1).maximal_order()1133False1134sage: R==51135False1136"""1137if not isinstance(R, QuaternionOrder):1138return cmp(type(self), type(R))1139c = cmp(self.__quaternion_algebra, R.__quaternion_algebra)1140if c: return c1141return cmp(self.__basis, R.__basis)114211431144def basis(self):1145"""1146Return fix choice of basis for this quaternion order.11471148EXAMPLES::11491150sage: QuaternionAlgebra(-11,-1).maximal_order().basis()1151(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1152"""1153return self.__basis11541155def quaternion_algebra(self):1156"""1157Return ambient quaternion algebra that contains this quaternion order.11581159EXAMPLES::11601161sage: QuaternionAlgebra(-11,-1).maximal_order().quaternion_algebra()1162Quaternion Algebra (-11, -1) with base ring Rational Field1163"""1164return self.__quaternion_algebra11651166def _repr_(self):1167"""1168Return string representation of this order.11691170EXAMPLES::11711172sage: QuaternionAlgebra(-11,-1).maximal_order()._repr_()1173'Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)'1174sage: QuaternionAlgebra(-11,-1).maximal_order()1175Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1176"""1177return 'Order of %s with basis %s'%(self.quaternion_algebra(), self.basis())11781179def random_element(self, *args, **kwds):1180"""1181Return a random element of this order.11821183The args and kwds are passed to the random_element method of1184the integer ring, and we return an element of the form11851186.. math::11871188ae_1 + be_2 + ce_3 + de_411891190where `e_1`, ..., `e_4` are the basis of this order and `a`,1191`b`, `c`, `d` are random integers.11921193EXAMPLES::11941195sage: QuaternionAlgebra(-11,-1).maximal_order().random_element()1196-4 + i - 4*j + k1197sage: QuaternionAlgebra(-11,-1).maximal_order().random_element(-10,10)1198-9/2 - 7/2*i - 7/2*j + 3/2*k1199"""1200return sum( (ZZ.random_element(*args, **kwds) * b for b in self.basis()) )12011202def intersection(self, other):1203"""1204Return the intersection of this order with other.12051206INPUT:12071208- ``other`` - a quaternion order in the same ambient quaternion algebra12091210OUTPUT: a quaternion order12111212EXAMPLES::12131214sage: R = QuaternionAlgebra(-11,-1).maximal_order()1215sage: R.intersection(R)1216Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)12171218We intersect various orders in the quaternion algebra ramified at 11::12191220sage: B = BrandtModule(11,3)1221sage: R = B.maximal_order(); S = B.order_of_level_N()1222sage: R.intersection(S)1223Order of Quaternion Algebra (-1, -11) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 5/2*k, j, 3*k)1224sage: R.intersection(S) == S1225True1226sage: B = BrandtModule(11,5)1227sage: T = B.order_of_level_N()1228sage: S.intersection(T)1229Order of Quaternion Algebra (-1, -11) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 23/2*k, j, 15*k)1230"""1231if not isinstance(other, QuaternionOrder):1232raise TypeError("other must be a QuaternionOrder")12331234A = self.quaternion_algebra()1235if other.quaternion_algebra() != A:1236raise ValueError("self and other must be in the same ambient quaternion algebra")12371238V = A.base_ring()**412391240B = V.span([V(list(g)) for g in self.basis()], ZZ)1241C = V.span([V(list(g)) for g in other.basis()], ZZ)12421243# todo -- A(list(e)) could be A(e)1244return QuaternionOrder(A, [A(list(e)) for e in B.intersection(C).basis()])12451246def free_module(self):1247"""1248Return the free `\\ZZ`-module that corresponds to this order1249inside the vector space corresponding to the ambient1250quaternion algebra.12511252OUTPUT: a free `\\ZZ`-module of rank 412531254EXAMPLES::12551256sage: R = QuaternionAlgebra(-11,-1).maximal_order()1257sage: R.basis()1258(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1259sage: R.free_module()1260Free module of degree 4 and rank 4 over Integer Ring1261Echelon basis matrix:1262[1/2 0 1/2 0]1263[ 0 1/2 0 1/2]1264[ 0 0 1 0]1265[ 0 0 0 1]1266"""1267try: return self.__free_module1268except AttributeError: pass1269V = self.quaternion_algebra().base_ring()**41270M = V.span([V(list(g)) for g in self.basis()], ZZ)1271self.__free_module = M1272return M12731274def discriminant(self):1275r"""1276Return the discriminant of this order, which we define as1277`\sqrt{ det ( Tr(e_i \bar{e_j} ) ) }`, where `\{e_i\}` is the1278basis of the order.12791280OUTPUT: rational number12811282EXAMPLES::12831284sage: QuaternionAlgebra(-11,-1).maximal_order().discriminant()1285111286sage: S = BrandtModule(11,5).order_of_level_N()1287sage: S.discriminant()1288551289sage: type(S.discriminant())1290<type 'sage.rings.rational.Rational'>1291"""1292L = []1293for d in self.basis():1294MM = []1295for e in self.basis():1296MM.append( (d * e.conjugate()).reduced_trace() )1297L.append(MM)12981299return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt()13001301def left_ideal(self, gens, check=True):1302r"""1303Return the ideal with given gens over `\ZZ`.13041305INPUT:13061307- ``gens`` -- a list of elements of this quaternion order13081309- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must13104-tuple that forms a Hermite basis for an ideal13111312EXAMPLES::13131314sage: R = QuaternionAlgebra(-11,-1).maximal_order()1315sage: R.left_ideal([2*a for a in R.basis()])1316Fractional ideal (1 + j, i + k, 2*j, 2*k)1317"""1318if self.base_ring() == ZZ:1319return QuaternionFractionalIdeal_rational(gens, left_order=self, check=check)1320else:1321raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")13221323def right_ideal(self, gens, check=True):1324r"""1325Return the ideal with given gens over `\ZZ`.13261327INPUT:13281329- ``gens`` -- a list of elements of this quaternion order13301331- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must13324-tuple that forms a Hermite basis for an ideal13331334EXAMPLES::13351336sage: R = QuaternionAlgebra(-11,-1).maximal_order()1337sage: R.right_ideal([2*a for a in R.basis()])1338Fractional ideal (1 + j, i + k, 2*j, 2*k)1339"""1340if self.base_ring() == ZZ:1341return QuaternionFractionalIdeal_rational(gens, right_order=self, check=check)1342else:1343raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")13441345def unit_ideal(self):1346"""1347Return the unit ideal in this quaternion order.13481349EXAMPLES::13501351sage: R = QuaternionAlgebra(-11,-1).maximal_order()1352sage: I = R.unit_ideal(); I1353Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1354"""1355if self.base_ring() == ZZ:1356return QuaternionFractionalIdeal_rational(self.basis(), left_order=self, right_order=self, check=False)1357else:1358raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")13591360def quadratic_form(self):1361"""1362Return the normalized quadratic form associated to this quaternion order.13631364OUTPUT: quadratic form13651366EXAMPLES::13671368sage: R = BrandtModule(11,13).order_of_level_N()1369sage: Q = R.quadratic_form(); Q1370Quadratic form in 4 variables over Rational Field with coefficients:1371[ 14 253 55 286 ]1372[ * 1455 506 3289 ]1373[ * * 55 572 ]1374[ * * * 1859 ]1375sage: Q.theta_series(10)13761 + 2*q + 2*q^4 + 4*q^6 + 4*q^8 + 2*q^9 + O(q^10)1377"""1378return self.unit_ideal().quadratic_form()13791380def ternary_quadratic_form(self, include_basis=False):1381"""1382Return the ternary quadratic form associated to this order.13831384INPUT:13851386- ``include_basis`` -- bool (default: False), if True also1387return a basis for the dimension 3 subspace `G`13881389OUTPUT:13901391- QuadraticForm13921393- optional basis for dimension 3 subspace13941395This function computes the positive definition quadratic form1396obtained by letting G be the trace zero subspace of `\ZZ` +13972* ``self``, which has rank 3, and restricting the pairing13981399(x,y) = (x.conjugate()*y).reduced_trace()14001401to `G`.14021403APPLICATIONS: Ternary quadratic forms associated to an order1404in a rational quaternion algebra are useful in computing with1405Gross points, in decided whether quaternion orders have1406embeddings from orders in quadratic imaginary fields, and in1407computing elements of the Kohnen plus subspace of modular1408forms of weight 3/2.14091410EXAMPLES::14111412sage: R = BrandtModule(11,13).order_of_level_N()1413sage: Q = R.ternary_quadratic_form(); Q1414Quadratic form in 3 variables over Rational Field with coefficients:1415[ 5820 1012 13156 ]1416[ * 55 1144 ]1417[ * * 7436 ]1418sage: factor(Q.disc())14192^4 * 11^2 * 13^214201421The following theta series is a modular form of weight 3/2 and level 4*11*13::14221423sage: Q.theta_series(100)14241 + 2*q^23 + 2*q^55 + 2*q^56 + 2*q^75 + 4*q^92 + O(q^100)1425"""1426if self.base_ring() != ZZ:1427raise NotImplementedError("ternary quadratic form of order only implemented for quaternion algebras over QQ")14281429Q = self.quaternion_algebra()1430# 2*R + ZZ1431twoR = self.free_module().scale(2)1432A = twoR.ambient_module()1433Z = twoR.span( [Q(1).coefficient_tuple()], ZZ)1434S = twoR + Z1435# Now we intersect with the trace 0 submodule1436v = [b.reduced_trace() for b in Q.basis()]1437M = matrix(QQ,4,1,v)1438tr0 = M.kernel()1439G = tr0.intersection(S)1440B = [Q(a) for a in G.basis()]1441m = matrix(QQ,[[x.pair(y) for x in B] for y in B])1442from sage.quadratic_forms.quadratic_form import QuadraticForm1443Q = QuadraticForm(m)1444if include_basis:1445return Q, B1446else:1447return Q14481449class QuaternionFractionalIdeal(Ideal_fractional):1450pass14511452class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal):1453"""1454A fractional ideal in a rational quaternion algebra.1455"""1456def __init__(self, basis, left_order=None, right_order=None, check=True):1457"""1458INPUT:14591460- ``left_order`` -- a quaternion order or ``None``14611462- ``right_order`` -- a quaternion order or ``None``14631464- ``basis`` -- tuple of length 4 of elements in of ambient1465quaternion algebra whose `\\ZZ`-span is an ideal14661467- ``check`` -- bool (default: ``True``); if ``False``, do no type1468checking, and the input basis *must* be in Hermite form.14691470EXAMPLES::14711472sage: R = QuaternionAlgebra(-11,-1).maximal_order()1473sage: R.right_ideal(R.basis())1474Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1475sage: R.right_ideal(tuple(R.basis()), check=False)1476Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1477"""1478if check:1479if left_order is not None and not isinstance(left_order, QuaternionOrder):1480raise TypeError("left_order must be a quaternion order or None")1481if right_order is not None and not isinstance(right_order, QuaternionOrder):1482raise TypeError("right_order must be a quaternion order or None")1483if not isinstance(basis, (list, tuple)):1484raise TypeError("basis must be a list or tuple")14851486self.__left_order = left_order1487self.__right_order = right_order14881489if check:1490try:1491Q = self.quaternion_order().quaternion_algebra()1492except RuntimeError:1493Q = basis[0].parent()1494basis = tuple([Q(v) for v in1495(QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()])1496self.__basis = basis14971498def scale(self, alpha, left=False):1499r"""1500Scale the fractional ideal self by multiplying the basis by alpha.15011502INPUT:15031504- `\alpha` -- element of quaternion algebra15051506- ``left`` -- bool (default: False); if true multiply1507`\alpha` on the left, otherwise multiply `\alpha` on the1508right.15091510OUTPUT:15111512- a new fractional ideal151315141515EXAMPLES::15161517sage: B = BrandtModule(5,37); I = B.right_ideals()[0]; i,j,k = B.quaternion_algebra().gens(); I1518Fractional ideal (2 + 2*j + 106*k, i + 2*j + 105*k, 4*j + 64*k, 148*k)1519sage: I.scale(i)1520Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j]1521sage: I.scale(i, left=True)1522Fractional ideal [2*i - 212*j + 2*k, -2 - 210*j + 2*k, -128*j + 4*k, -296*j]1523sage: I.scale(i, left=False)1524Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j]1525sage: i * I.gens()[0]15262*i - 212*j + 2*k1527sage: I.gens()[0] * i15282*i + 212*j - 2*k1529"""15301531Q = self.quaternion_algebra()1532alpha = Q(alpha)1533if left:1534gens = [alpha*b for b in self.basis()]1535else:1536gens = [b*alpha for b in self.basis()]1537return Q.ideal(gens, left_order = self.__left_order,1538right_order = self.__right_order, check=False)15391540def quaternion_algebra(self):1541"""1542Return the ambient quaternion algebra that contains this fractional ideal.15431544OUTPUT: a quaternion algebra15451546EXAMPLES::15471548sage: I = BrandtModule(3,5).right_ideals()[1]; I1549Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)1550sage: I.quaternion_algebra()1551Quaternion Algebra (-1, -3) with base ring Rational Field1552"""1553try: return self.__quaternion_algebra1554except AttributeError: pass1555A = self.__basis[0].parent()1556self.__quaternion_algebra = A1557return A15581559def _compute_order(self, side='left'):1560r"""1561Used internally to compute either the left or right order1562associated to an ideal in a quaternion algebra. If1563action='right', compute the left order, and if action='left'1564compute the right order.15651566INPUT:15671568- ``side`` -- 'left' or 'right'15691570EXAMPLES::15711572sage: R.<i,j,k> = QuaternionAlgebra(-1,-11)1573sage: I = R.ideal([2 + 2*j + 140*k, 2*i + 4*j + 150*k, 8*j + 104*k, 152*k])1574sage: Ol = I._compute_order('left'); Ol1575Order of Quaternion Algebra (-1, -11) with base ring Rational Field with basis (1/2 + 1/2*j + 35*k, 1/4*i + 1/2*j + 75/4*k, j + 32*k, 38*k)1576sage: Or = I._compute_order('right'); Or1577Order of Quaternion Algebra (-1, -11) with base ring Rational Field with basis (1/2 + 1/2*j + 16*k, 1/2*i + 11/2*k, j + 13*k, 19*k)1578sage: Ol.discriminant()15792091580sage: Or.discriminant()15812091582sage: I.left_order() == Ol1583True1584sage: I.right_order() == Or1585True15861587ALGORITHM: Let `b_1, b_2, b_3, b_3` be a basis for this1588fractional ideal `I`, and assume we want to compute the left1589order of `I` in the quaternion algebra `Q`. Then1590multiplication by `b_i` on the right defines a map `B_i:Q \to1591Q`. We have1592`R = B_1^{-1}(I) \cap B_2^{-1}(I) \cap B_3^{-1}(I)\cap B_4^{-1}(I).`1593This is because1594`B_n^{-1}(I) = \{\alpha \in Q : \alpha b_n \in I \},`1595and1596`R = \{\alpha \in Q : \alpha b_n \in I, n=1,2,3,4\}.`1597"""1598if side == 'left': action = 'right'1599elif side == 'right': action = 'left'1600else: ValueError, "side must be 'left' or 'right'"1601Q = self.quaternion_algebra()1602if Q.base_ring() != QQ:1603raise NotImplementedError("computation of left and right orders only implemented over QQ")1604M = [(~b).matrix(action=action) for b in self.basis()]1605B = self.basis_matrix()1606invs = [B*m for m in M]1607# Now intersect the row spans of each matrix in invs1608ISB = [Q(v) for v in intersection_of_row_modules_over_ZZ(invs).row_module(ZZ).basis()]1609return Q.quaternion_order(ISB)16101611def left_order(self):1612"""1613Return the left order associated to this fractional ideal.16141615OUTPUT: an order in a quaternion algebra16161617EXAMPLES::16181619sage: B = BrandtModule(11)1620sage: R = B.maximal_order()1621sage: I = R.unit_ideal()1622sage: I.left_order()1623Order of Quaternion Algebra (-1, -11) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)16241625We do a consistency check::16261627sage: B = BrandtModule(11,19); R = B.right_ideals()1628sage: print [r.left_order().discriminant() for r in R]1629[209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209]1630"""1631if self.__left_order is None:1632self.__left_order = self._compute_order(side='left')1633return self.__left_order16341635def right_order(self):1636"""1637Return the right order associated to this fractional ideal.16381639OUTPUT: an order in a quaternion algebra16401641EXAMPLES::16421643sage: I = BrandtModule(389).right_ideals()[1]; I1644Fractional ideal (2 + 6*j + 2*k, i + 2*j + k, 8*j, 8*k)1645sage: I.right_order()1646Order of Quaternion Algebra (-2, -389) with base ring Rational Field with basis (1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)1647sage: I.left_order()1648Order of Quaternion Algebra (-2, -389) with base ring Rational Field with basis (1/2 + 1/2*j + 3/2*k, 1/8*i + 1/4*j + 9/8*k, j + k, 2*k)16491650The following is a big consistency check. We take reps for1651all the right ideal classes of a certain order, take the1652corresponding left orders, then take ideals in the left orders1653and from those compute the right order again::16541655sage: B = BrandtModule(11,19); R = B.right_ideals()1656sage: O = [r.left_order() for r in R]1657sage: J = [O[i].left_ideal(R[i].basis()) for i in range(len(R))]1658sage: len(set(J))1659181660sage: len(set([I.right_order() for I in J]))166111662sage: J[0].right_order() == B.order_of_level_N()1663True1664"""1665if self.__right_order is None:1666self.__right_order = self._compute_order(side='right')1667return self.__right_order16681669def __repr__(self):1670"""1671Return string representation of this quaternion fractional ideal.16721673EXAMPLES::16741675sage: I = BrandtModule(11).right_ideals()[1]1676sage: type(I)1677<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionFractionalIdeal_rational'>1678sage: I.__repr__()1679'Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 2*k, 8*j, 8*k)'1680"""1681return 'Fractional ideal %s'%(self.gens(),)16821683def quaternion_order(self):1684"""1685Return the order for which this ideal is a left or right1686fractional ideal. If this ideal has both a left and right1687ideal structure, then the left order is returned. If it has1688neither structure, then an error is raised.16891690OUTPUT: QuaternionOrder16911692EXAMPLES::16931694sage: R = QuaternionAlgebra(-11,-1).maximal_order()1695sage: R.unit_ideal().quaternion_order() is R1696True1697"""1698try: return self.__quaternion_order1699except AttributeError: pass1700if self.__left_order is not None:1701A = self.__left_order1702elif self.__right_order is not None:1703A = self.__right_order1704else:1705raise RuntimeError("unable to determine quaternion order of ideal without known order")1706self.__quaternion_order = A1707return A17081709def ring(self):1710"""1711Return ring that this is a fractional ideal for.17121713EXAMPLES::17141715sage: R = QuaternionAlgebra(-11,-1).maximal_order()1716sage: R.unit_ideal().ring() is R1717True1718"""1719return self.quaternion_order()17201721def basis(self):1722"""1723Return basis for this fractional ideal. The basis is in Hermite form.17241725OUTPUT: tuple17261727EXAMPLES::17281729sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis()1730(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1731"""1732return self.__basis17331734def gens(self):1735"""1736Return the generators for this ideal, which are the same as1737the `\\ZZ`-basis for this ideal.17381739EXAMPLES::17401741sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().gens()1742(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)1743"""1744return self.__basis17451746def __cmp__(self, right):1747"""1748Compare this fractional quaternion ideal to ``right``. If1749``right`` is not a fractional quaternion ideal a TypeError is1750raised. If the fractional ideals are in different ambient1751quaternion algebras, then the quaternion algebras themselves1752are compared.17531754INPUT:17551756- ``right`` - another fractional quaternion ideal17571758EXAMPLES::17591760sage: I = QuaternionAlgebra(-11,-1).maximal_order().unit_ideal()1761sage: I == I # indirect doctest1762True1763sage: I == 51764False1765"""1766if not isinstance(right, QuaternionFractionalIdeal_rational):1767raise TypeError1768return cmp(self.__basis, right.__basis)17691770def basis_matrix(self):1771"""1772Return basis matrix `M` in Hermite normal form for self as a1773matrix with rational entries.17741775If `Q` is the ambient quaternion algebra, then the `\\ZZ`-span of1776the rows of `M` viewed as linear combinations of Q.basis() =1777`[1,i,j,k]` is the fractional ideal self. Also, M *1778M.denominator() is an integer matrix in Hermite normal form.17791780OUTPUT: matrix over `\\QQ`17811782EXAMPLES::17831784sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()1785[1/2 0 1/2 0]1786[ 0 1/2 0 1/2]1787[ 0 0 1 0]1788[ 0 0 0 1]1789"""1790try: return self.__hermite_basis_matrix1791except AttributeError: pass1792B = quaternion_algebra_cython.rational_matrix_from_rational_quaternions(self.__basis)1793self.__hermite_basis_matrix = B1794return B17951796def free_module(self):1797"""1798Return the free module associated to this quaternionic1799fractional ideal, viewed as a submodule of1800``Q.free_module()``, where ``Q`` is the ambient quaternion1801algebra.18021803OUTPUT: free `\\ZZ`-module of rank 4 embedded in an ambient `\\QQ^4`.18041805EXAMPLES::18061807sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()1808[1/2 0 1/2 0]1809[ 0 1/2 0 1/2]1810[ 0 0 1 0]1811[ 0 0 0 1]18121813This shows that the issue at trac ticket 6760 is fixed::18141815sage: R.<i,j,k> = QuaternionAlgebra(-1, -13)1816sage: I = R.ideal([2+i, 3*i, 5*j, j+k]); I1817Fractional ideal (2 + i, 3*i, j + k, 5*k)1818sage: I.free_module()1819Free module of degree 4 and rank 4 over Integer Ring1820Echelon basis matrix:1821[2 1 0 0]1822[0 3 0 0]1823[0 0 1 1]1824[0 0 0 5]1825"""1826try: return self.__free_module1827except AttributeError:1828M = self.basis_matrix().row_module(ZZ)1829self.__free_module = M1830return M18311832def theta_series_vector(self, B):1833"""1834Return theta series coefficients of self, as a vector of `B` integers.18351836INPUT:18371838- ``B`` -- positive integer18391840OUTPUT: vector over `\\ZZ` with ``B`` entries18411842EXAMPLES::18431844sage: I = BrandtModule(37).right_ideals()[1]; I1845Fractional ideal (2 + 6*j + 2*k, i + 2*j + k, 8*j, 8*k)1846sage: I.theta_series_vector(5)1847(1, 0, 2, 2, 6)1848sage: I.theta_series_vector(10)1849(1, 0, 2, 2, 6, 4, 8, 6, 10, 10)1850sage: I.theta_series_vector(5)1851(1, 0, 2, 2, 6)1852"""1853B = Integer(B)1854try:1855if len(self.__theta_series_vector)>= B: return self.__theta_series_vector[:B]1856except AttributeError: pass1857V = FreeModule(ZZ, B)1858Q = self.quadratic_form()1859v = V(Q.representation_number_list(B))1860self.__theta_series_vector = v1861return v18621863def quadratic_form(self):1864"""1865Return the normalized quadratic form associated to this quaternion ideal.18661867OUTPUT: quadratic form18681869EXAMPLES::18701871sage: I = BrandtModule(11).right_ideals()[1]1872sage: Q = I.quadratic_form(); Q1873Quadratic form in 4 variables over Rational Field with coefficients:1874[ 18 22 33 22 ]1875[ * 7 22 11 ]1876[ * * 22 0 ]1877[ * * * 22 ]1878sage: Q.theta_series(10)18791 + 12*q^2 + 12*q^3 + 12*q^4 + 12*q^5 + 24*q^6 + 24*q^7 + 36*q^8 + 36*q^9 + O(q^10)1880sage: I.theta_series(10)18811 + 12*q^2 + 12*q^3 + 12*q^4 + 12*q^5 + 24*q^6 + 24*q^7 + 36*q^8 + 36*q^9 + O(q^10)1882"""1883try: return self.__quadratic_form1884except AttributeError: pass1885from sage.quadratic_forms.quadratic_form import QuadraticForm1886# first get the gram matrix1887gram_matrix = self.gram_matrix()1888# rescale so that there are no denominators1889gram_matrix, _ = gram_matrix._clear_denom()1890# Make sure gcd of all entries is 1.1891g = gram_matrix.gcd()1892if g != 1:1893gram_matrix = gram_matrix / g1894# now get the quadratic form1895Q = QuadraticForm(gram_matrix)1896self.__quadratic_form = Q1897return Q18981899def theta_series(self, B, var='q'):1900"""1901Return normalized theta series of self, as a power series over1902`\\ZZ` in the variable ``var``, which is 'q' by default.19031904The normalized theta series is by definition19051906.. math::19071908\\theta_I(q)=\\sum_{x \\in I} q^{\\frac{N(x)}{N(I)}}19091910INPUT:19111912- ``B`` -- positive integer1913- ``var`` -- string (default: 'q')19141915OUTPUT: power series19161917EXAMPLES::19181919sage: I = BrandtModule(11).right_ideals()[1]; I1920Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 2*k, 8*j, 8*k)1921sage: I.norm()1922641923sage: I.theta_series(5)19241 + 12*q^2 + 12*q^3 + 12*q^4 + O(q^5)1925sage: I.theta_series(5,'T')19261 + 12*T^2 + 12*T^3 + 12*T^4 + O(T^5)1927sage: I.theta_series(3)19281 + 12*q^2 + O(q^3)1929"""1930try:1931if self.__theta_series.prec() >= B:1932if var == self.__theta_series.variable():1933return self.__theta_series.add_bigoh(B)1934else:1935ZZ[[var]](self.__theta_series.list()[:B+1])1936except AttributeError: pass1937v = self.theta_series_vector(B)1938theta = ZZ[[var]](v.list()).add_bigoh(B)1939self.__theta_series = theta1940return theta19411942def gram_matrix(self):1943"""1944Return the Gram matrix of this fractional ideal.19451946OUTPUT: 4x4 matrix over `\\QQ`.19471948EXAMPLES::19491950sage: I = BrandtModule(3,5).right_ideals()[1]; I1951Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)1952sage: I.gram_matrix()1953[ 640 1920 2112 1920]1954[ 1920 14080 13440 16320]1955[ 2112 13440 13056 15360]1956[ 1920 16320 15360 19200]1957"""1958try: return self.__gram_matrix1959except AttributeError: pass1960M = []1961A = self.__basis1962B = [z.conjugate() for z in self.__basis]1963two = QQ(2)1964m = [two*(a*b).reduced_trace() for b in B for a in A]1965M44 = MatrixSpace(QQ, 4)1966G = M44(m,coerce=False)1967self.__gram_matrix = G1968return G19691970def norm(self):1971"""1972Return the norm of this fractional ideal.19731974OUTPUT: rational number19751976EXAMPLES::19771978sage: C = BrandtModule(37).right_ideals()1979sage: [I.norm() for I in C]1980[32, 64, 64]1981"""1982G = self.gram_matrix()1983r = G.det().abs()1984assert r.is_square(), "first is bad!"1985r = r.sqrt()1986r/= self.quaternion_order().discriminant()1987assert r.is_square(), "second is bad!"1988return r.sqrt()19891990def conjugate(self):1991"""1992Return the ideal with generators the conjugates of the generators for self.19931994OUTPUT: a quaternionic fractional ideal19951996EXAMPLES::19971998sage: I = BrandtModule(3,5).right_ideals()[1]; I1999Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)2000sage: I.conjugate()2001Fractional ideal (2 + 2*j + 28*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)2002"""2003return self.quaternion_algebra().ideal([b.conjugate() for b in self.basis()],2004left_order=self.__right_order,2005right_order=self.__left_order)20062007def __mul__(self, right):2008"""2009Return the product of the fractional ideals ``self`` and ``right``.20102011.. note::20122013We do not keep track of left or right order structure.20142015EXAMPLES::20162017sage: I = BrandtModule(3,5).right_ideals()[1]; I2018Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)2019sage: I*I2020Fractional ideal (8 + 24*j + 16*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)2021sage: I*I.conjugate()2022Fractional ideal (16 + 16*j + 224*k, 8*i + 16*j + 136*k, 32*j + 128*k, 320*k)2023sage: I.multiply_by_conjugate(I)2024Fractional ideal (16 + 16*j + 224*k, 8*i + 16*j + 136*k, 32*j + 128*k, 320*k)2025"""2026if not isinstance(right, QuaternionFractionalIdeal_rational):2027return self._scale(right, left=False)2028gens = [a*b for a in self.basis() for b in right.basis()]2029#if self.__right_order == right.__left_order:2030# left_order = self.__left_order2031# right_order = right.__right_order2032basis = tuple(basis_for_quaternion_lattice(gens))2033A = self.quaternion_algebra()2034return A.ideal(basis, check=False)20352036@cached_method2037def free_module(self):2038r"""2039Return the underlying free `\ZZ`-module corresponding to this ideal.20402041EXAMPLES::20422043sage: X = BrandtModule(3,5).right_ideals()2044sage: X[0]2045Fractional ideal (2 + 2*j + 8*k, 2*i + 18*k, 4*j + 16*k, 20*k)2046sage: X[0].free_module()2047Free module of degree 4 and rank 4 over Integer Ring2048Echelon basis matrix:2049[ 2 0 2 8]2050[ 0 2 0 18]2051[ 0 0 4 16]2052[ 0 0 0 20]2053sage: X[0].scale(1/7).free_module()2054Free module of degree 4 and rank 4 over Integer Ring2055Echelon basis matrix:2056[ 2/7 0 2/7 8/7]2057[ 0 2/7 0 18/7]2058[ 0 0 4/7 16/7]2059[ 0 0 0 20/7]20602061The free module method is also useful since it allows for checking if one ideal2062is contained in another, computing quotients I/J, etc.::20632064sage: X = BrandtModule(3,17).right_ideals()2065sage: I = X[0].intersection(X[2]); I2066Fractional ideal (2 + 2*j + 164*k, 2*i + 4*j + 46*k, 16*j + 224*k, 272*k)2067sage: I.free_module().is_submodule(X[3].free_module())2068False2069sage: I.free_module().is_submodule(X[1].free_module())2070True2071sage: X[0].free_module() / I.free_module()2072Finitely generated module V/W over Integer Ring with invariants (4, 4)2073"""2074return self.basis_matrix().row_module(ZZ)20752076def intersection(self, J):2077"""2078Return the intersection of the ideals self and `J`.20792080EXAMPLES::20812082sage: X = BrandtModule(3,5).right_ideals()2083sage: I = X[0].intersection(X[1]); I2084Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)20852086"""2087V = self.free_module().intersection(J.free_module())2088H,d = V.basis_matrix()._clear_denom()2089A = self.quaternion_algebra()2090gens = quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d)2091return A.ideal(gens)20922093def multiply_by_conjugate(self, J):2094"""2095Return product of self and the conjugate Jbar of `J`.20962097INPUT:20982099- ``J`` -- a quaternion ideal.21002101OUTPUT: a quaternionic fractional ideal.21022103EXAMPLES::21042105sage: R = BrandtModule(3,5).right_ideals()2106sage: R[0].multiply_by_conjugate(R[1])2107Fractional ideal (8 + 8*j + 112*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)2108sage: R[0]*R[1].conjugate()2109Fractional ideal (8 + 8*j + 112*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)2110"""2111Jbar = [b.conjugate() for b in J.basis()]2112gens = [a*b for a in self.basis() for b in Jbar]2113basis = tuple(basis_for_quaternion_lattice(gens))2114R = self.quaternion_algebra()2115return R.ideal(basis, check=False)21162117def is_equivalent(I, J, B=10):2118"""2119Return ``True`` if ``I`` and ``J`` are equivalent as right ideals.21202121INPUT:21222123- ``I`` -- a fractional quaternion ideal (self)21242125- ``J`` -- a fractional quaternion ideal with same order as ``I``21262127- ``B`` -- a bound to compute and compare theta series before2128doing the full equivalence test21292130OUTPUT: bool21312132EXAMPLES::21332134sage: R = BrandtModule(3,5).right_ideals(); len(R)213522136sage: R[0].is_equivalent(R[1])2137False2138sage: R[0].is_equivalent(R[0])2139True2140sage: OO = R[0].quaternion_order()2141sage: S = OO.right_ideal([3*a for a in R[0].basis()])2142sage: R[0].is_equivalent(S)2143True2144"""2145if not isinstance(I, QuaternionFractionalIdeal_rational):2146return False21472148if I.right_order() != J.right_order():2149raise ValueError("I and J must be right ideals")21502151# Just test theta series first. If the theta series are2152# different, the ideals are definitely not equivalent.2153if B > 0 and I.theta_series_vector(B) != J.theta_series_vector(B):2154return False21552156# The theta series are the same, so perhaps the ideals are2157# equivalent. We use Prop 1.18 of [Pizer, 1980] to decide.2158# 1. Compute I * Jbar2159# see Prop. 1.17 in Pizer. Note that we use IJbar instead of2160# JbarI since we work with right ideals2161IJbar = I.multiply_by_conjugate(J)21622163# 2. Determine if there is alpha in K such2164# that N(alpha) = N(I)*N(J) as explained by Pizer.2165c = IJbar.theta_series_vector(2)[1]2166return c != 021672168def __contains__(self, x):2169"""2170Returns whether x is in self.21712172EXAMPLES::2173sage: R.<i,j,k> = QuaternionAlgebra(-3, -13)2174sage: I = R.ideal([2+i, 3*i, 5*j, j+k])2175sage: 2+i in I2176True2177sage: 2+i+j+k in I2178True2179sage: 1+i in I2180False2181sage: 101*j + k in I2182True2183"""2184try:2185x = self.quaternion_algebra()(x)2186return self.basis_matrix().transpose().solve_right(vector(x)) in ZZ**42187except (ValueError, TypeError):2188return False21892190@cached_method2191def cyclic_right_subideals(self, p, alpha=None):2192r"""2193Let `I` = ``self``. This function returns the right subideals2194`J` of `I` such that `I/J` is an `\GF{p}`-vector space of2195dimension 2.21962197INPUT:21982199- `p` -- prime number (see below)22002201- `alpha` -- (default: None) element of quaternion algebra,2202which can be used to parameterize the order of the2203ideals `J`. More precisely the `J`'s are the right annihilators2204of `(1,0) \alpha^i` for `i=0,1,2,...,p`22052206OUTPUT:22072208- list of right ideals22092210.. note::22112212Currently, `p` must satisfy a bunch of conditions, or a2213NotImplementedError is raised. In particular, `p` must be2214odd and unramified in the quaternion algebra, must be2215coprime to the index of the right order in the maximal2216order, and also coprime to the normal of self. (The Brandt2217modules code has a more general algorithm in some cases.)22182219EXAMPLES::22202221sage: B = BrandtModule(2,37); I = B.right_ideals()[0]2222sage: I.cyclic_right_subideals(3)2223[Fractional ideal (2 + 2*i + 10*j + 90*k, 4*i + 4*j + 152*k, 12*j + 132*k, 444*k), Fractional ideal (2 + 2*i + 2*j + 150*k, 4*i + 8*j + 196*k, 12*j + 132*k, 444*k), Fractional ideal (2 + 2*i + 6*j + 194*k, 4*i + 8*j + 344*k, 12*j + 132*k, 444*k), Fractional ideal (2 + 2*i + 6*j + 46*k, 4*i + 4*j + 4*k, 12*j + 132*k, 444*k)]22242225sage: B = BrandtModule(5,389); I = B.right_ideals()[0]2226sage: C = I.cyclic_right_subideals(3); C2227[Fractional ideal (2 + 10*j + 546*k, i + 6*j + 133*k, 12*j + 3456*k, 4668*k), Fractional ideal (2 + 2*j + 2910*k, i + 6*j + 3245*k, 12*j + 3456*k, 4668*k), Fractional ideal (2 + i + 2295*k, 3*i + 2*j + 3571*k, 4*j + 2708*k, 4668*k), Fractional ideal (2 + 2*i + 2*j + 4388*k, 3*i + 2*j + 2015*k, 4*j + 4264*k, 4668*k)]2228sage: [(I.free_module()/J.free_module()).invariants() for J in C]2229[(3, 3), (3, 3), (3, 3), (3, 3)]2230sage: I.scale(3).cyclic_right_subideals(3)2231[Fractional ideal (6 + 30*j + 1638*k, 3*i + 18*j + 399*k, 36*j + 10368*k, 14004*k), Fractional ideal (6 + 6*j + 8730*k, 3*i + 18*j + 9735*k, 36*j + 10368*k, 14004*k), Fractional ideal (6 + 3*i + 6885*k, 9*i + 6*j + 10713*k, 12*j + 8124*k, 14004*k), Fractional ideal (6 + 6*i + 6*j + 13164*k, 9*i + 6*j + 6045*k, 12*j + 12792*k, 14004*k)]2232sage: C = I.scale(1/9).cyclic_right_subideals(3); C2233[Fractional ideal (2/9 + 10/9*j + 182/3*k, 1/9*i + 2/3*j + 133/9*k, 4/3*j + 384*k, 1556/3*k), Fractional ideal (2/9 + 2/9*j + 970/3*k, 1/9*i + 2/3*j + 3245/9*k, 4/3*j + 384*k, 1556/3*k), Fractional ideal (2/9 + 1/9*i + 255*k, 1/3*i + 2/9*j + 3571/9*k, 4/9*j + 2708/9*k, 1556/3*k), Fractional ideal (2/9 + 2/9*i + 2/9*j + 4388/9*k, 1/3*i + 2/9*j + 2015/9*k, 4/9*j + 4264/9*k, 1556/3*k)]2234sage: [(I.scale(1/9).free_module()/J.free_module()).invariants() for J in C]2235[(3, 3), (3, 3), (3, 3), (3, 3)]22362237sage: Q.<i,j,k> = QuaternionAlgebra(-2,-5)2238sage: I = Q.ideal([Q(1),i,j,k])2239sage: I.cyclic_right_subideals(3)2240[Fractional ideal (1 + 2*j, i + k, 3*j, 3*k), Fractional ideal (1 + j, i + 2*k, 3*j, 3*k), Fractional ideal (1 + 2*i, 3*i, j + 2*k, 3*k), Fractional ideal (1 + i, 3*i, j + k, 3*k)]22412242The general algorithm is not yet implemented here::22432244sage: I.cyclic_right_subideals(3)[0].cyclic_right_subideals(3)2245Traceback (most recent call last):2246...2247NotImplementedError: general algorithm not implemented (The given basis vectors must be linearly independent.)2248"""2249R = self.right_order()2250Q = self.quaternion_algebra()2251f = Q.modp_splitting_map(p)2252if alpha is not None:2253alpha = f(alpha)2254W = GF(p)**42255try:2256A = W.span_of_basis([W(f(a).list()) for a in self.basis()])2257scale = 12258IB = self.basis_matrix()2259except (ValueError, ZeroDivisionError):2260# try rescaling the ideal.2261B, d = self.basis_matrix()._clear_denom()2262g = gcd(B.list())2263IB = B/g2264scale = g/d2265try:2266A = W.span_of_basis([W(f(Q(a.list())).list()) for a in IB.rows()])2267except (ValueError, ZeroDivisionError) as msg:2268# Here we could replace the ideal by an *equivalent*2269# ideal that works. This is always possible.2270# However, I haven't implemented that algorithm yet.2271raise NotImplementedError("general algorithm not implemented (%s)"%msg)22722273Ai = A.basis_matrix()**(-1)2274AiB = Ai.change_ring(QQ) * IB22752276# Do not care about the denominator since we're really working in I/p*I.2277AiB, _ = AiB._clear_denom()22782279pB = p*IB2280pB, d = pB._clear_denom()22812282ans = []2283Z = matrix(ZZ,2,4)22842285P1 = P1List(p)2286if alpha is None:2287lines = P12288else:2289x = alpha2290lines = []2291for i in range(p+1):2292lines.append(P1.normalize(x[0,0], x[0,1]))2293x *= alpha22942295for u,v in lines:2296# The following does:2297# z = matrix(QQ,2,4,[0,-v,0,u, -v,0,u,0],check=False) * AiB2298Z[0,1]=-v; Z[0,3]=u; Z[1,0]=-v; Z[1,2]=u2299z = Z * AiB2300# Now construct submodule of the ideal I spanned by the2301# linear combinations given by z of the basis for J along2302# with p*I.2303G = (d*z).stack(pB) # have to multiply by d since we divide by it below in the "gens = " line.2304H = G._hnf_pari(0, include_zero_rows=False)2305gens = tuple(quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(Q, H, d))2306if scale != 1:2307gens = tuple([scale*g for g in gens])2308J = R.right_ideal(gens, check=False)2309ans.append(J)2310return ans23112312#######################################################################2313# Some utility functions that are needed here and are too2314# specialized to go elsewhere.2315#######################################################################23162317def basis_for_quaternion_lattice(gens):2318"""2319Return a basis for the `\\ZZ`-lattice in a quaternion algebra2320spanned by the given gens.23212322INPUT:23232324- ``gens`` -- list of elements of a single quaternion algebra23252326EXAMPLES::23272328sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)2329sage: sage.algebras.quatalg.quaternion_algebra.basis_for_quaternion_lattice([i+j, i-j, 2*k, A(1/3)])2330[1/3, i + j, 2*j, 2*k]2331"""2332if len(gens) == 0: return []2333Z, d = quaternion_algebra_cython.integral_matrix_and_denom_from_rational_quaternions(gens)2334H = Z._hnf_pari(0, include_zero_rows=False)2335A = gens[0].parent()2336return quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d)233723382339def intersection_of_row_modules_over_ZZ(v):2340"""2341Intersects the `\ZZ`-modules with basis matrices the full rank 4x42342`\QQ`-matrices in the list v. The returned intersection is2343represented by a 4x4 matrix over `\QQ`. This can also be done using2344modules and intersection, but that would take over twice as long2345because of overhead, hence this function.23462347EXAMPLES::23482349sage: a = matrix(QQ,4,[-2, 0, 0, 0, 0, -1, -1, 1, 2, -1/2, 0, 0, 1, 1, -1, 0])2350sage: b = matrix(QQ,4,[0, -1/2, 0, -1/2, 2, 1/2, -1, -1/2, 1, 2, 1, -2, 0, -1/2, -2, 0])2351sage: c = matrix(QQ,4,[0, 1, 0, -1/2, 0, 0, 2, 2, 0, -1/2, 1/2, -1, 1, -1, -1/2, 0])2352sage: v = [a,b,c]2353sage: from sage.algebras.quatalg.quaternion_algebra import intersection_of_row_modules_over_ZZ2354sage: M = intersection_of_row_modules_over_ZZ(v); M2355[ -2 0 1 1]2356[ -4 1 1 -3]2357[ -3 19/2 -1 -4]2358[ 2 -3 -8 4]2359sage: M2 = a.row_module(ZZ).intersection(b.row_module(ZZ)).intersection(c.row_module(ZZ))2360sage: M.row_module(ZZ) == M22361True2362"""2363if len(v) <= 0:2364raise ValueError("v must have positive length")2365if len(v) == 1:2366return v[0]2367elif len(v) == 2:2368# real work - the base case2369a, b = v2370s,_ = a.stack(b)._clear_denom()2371s = s.transpose()2372K = s.right_kernel_matrix(algorithm='pari', basis='computed')2373n = a.nrows()2374return K.matrix_from_columns(range(n)) * a2375else:2376# induct2377w = intersection_of_row_modules_over_ZZ(v[:2])2378return intersection_of_row_modules_over_ZZ([w] + v[2:])2379238023812382