Path: blob/master/sage/algebras/quatalg/quaternion_algebra_element.pyx
4156 views
"""1Elements of Quaternion Algebras23Sage allows for computation with elements of quaternion algebras over4a nearly arbitrary base field of characteristic not 2. Sage also has5very highly optimized implementation of arithmetic in rational6quaternion algebras and quaternion algebras over number fields.7"""89#*****************************************************************************10# Copyright (C) 2009 William Stein <[email protected]>11# Copyright (C) 2009 Jonathon Bober <[email protected]>12#13# Distributed under the terms of the GNU General Public License (GPL)14#15# This code is distributed in the hope that it will be useful,16# but WITHOUT ANY WARRANTY; without even the implied warranty of17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU18# General Public License for more details.19#20# The full text of the GPL is available at:21#22# http://www.gnu.org/licenses/23#*****************************************************************************2425from sage.structure.element cimport AlgebraElement, RingElement, ModuleElement, Element26from sage.algebras.quatalg.quaternion_algebra_element cimport QuaternionAlgebraElement_abstract27from sage.rings.rational cimport Rational28from sage.rings.integer cimport Integer29from sage.rings.arith import lcm30from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_integer_dense_flint31from sage.rings.number_field.number_field_element cimport NumberFieldElement32from sage.rings.all import PolynomialRing33from sage.matrix.all import matrix3435include "../../ext/gmp.pxi"36include "../../ext/stdsage.pxi"3738include "../../libs/flint/fmpz.pxi"39include "../../libs/flint/fmpz_poly.pxi"40include "../../libs/flint/ntl_interface.pxd"4142# variables for holding temporary values computed in43# QuaternionAlgebraElement_rational_field._mul_()44cdef mpz_t T1, T2, t3, t4, t5, t6, t7, t8, s1, s2, U1, U245cdef fmpz_poly_t fT1, fT2, ft3, ft4, ft5, ft6, ft7, ft8, fs1, fs2, fU1, fU24647def _init_globals():48"""49Clear all global variables allocated for optimization of50quaternion algebra arithmetic.5152Do *not* call this unless you want to leak memory.5354EXAMPLES::5556sage: sage.algebras.quatalg.quaternion_algebra_element._clear_globals()57sage: sage.algebras.quatalg.quaternion_algebra_element._init_globals()58"""59# over QQ60mpz_init(T1)61mpz_init(T2)62mpz_init(t3)63mpz_init(t4)64mpz_init(t5)65mpz_init(t6)66mpz_init(t7)67mpz_init(t8)6869mpz_init(s1)70mpz_init(s2)7172mpz_init(U1)73mpz_init(U2)7475# Number fields76fmpz_poly_init(fT1)77fmpz_poly_init(fT2)78fmpz_poly_init(ft3)79fmpz_poly_init(ft4)80fmpz_poly_init(ft5)81fmpz_poly_init(ft6)82fmpz_poly_init(ft7)83fmpz_poly_init(ft8)8485fmpz_poly_init(fs1)86fmpz_poly_init(fs2)8788fmpz_poly_init(fU1)89fmpz_poly_init(fU2)909192# Initialize module-scope global C variables.93_init_globals()9495def _clear_globals():96"""97Clear all global variables allocated for optimization of98quaternion algebra arithmetic.99100Do *not* call this except on exit of Sage, unless you want to see segfaults.101102This is called in the function quit_sage(), which is defined in all.py.103104EXAMPLES::105106sage: sage.algebras.quatalg.quaternion_algebra_element._clear_globals()107sage: sage.algebras.quatalg.quaternion_algebra_element._init_globals()108"""109mpz_clear(T1)110mpz_clear(T2)111mpz_clear(t3)112mpz_clear(t4)113mpz_clear(t5)114mpz_clear(t6)115mpz_clear(t7)116mpz_clear(t8)117118mpz_clear(s1)119mpz_clear(s2)120121mpz_clear(U1)122mpz_clear(U2)123124fmpz_poly_clear(fT1)125fmpz_poly_clear(fT2)126fmpz_poly_clear(ft3)127fmpz_poly_clear(ft4)128fmpz_poly_clear(ft5)129fmpz_poly_clear(ft6)130fmpz_poly_clear(ft7)131fmpz_poly_clear(ft8)132133fmpz_poly_clear(fs1)134fmpz_poly_clear(fs2)135136fmpz_poly_clear(fU1)137fmpz_poly_clear(fU2)138139cdef to_quaternion(R, x):140"""141Internal function used implicitly by quaternion algebra creation.142143INPUT::144145- R -- callable146- x -- element or 4-tuple147148Given a callable R and an x that defines a quaternion, which can be a1494-tuple, list of length 4, or something that coerces to R, return1504-tuple of elements of R.151152EXAMPLES::153sage: Q.<i,j,kkkk> = QuaternionAlgebra(QQ,-7, 13)154sage: kkkk._repr_() # implicit doctest155'kkkk'156"""157if isinstance(x, (list, tuple)):158return R(x[0]), R(x[1]), R(x[2]), R(x[3])159else:160return R(x), R(0), R(0), R(0)161162cdef inline print_coeff(y, i, bint atomic):163"""164Internal function used implicitly by all quaternion algebra printing.165166INPUT::167168- y -- coefficient169- i -- string (name of a generator)170- atomic -- boolean int; whether or not elements of base ring171print atomically172173EXAMPLES::174175sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-7, 13)176sage: i._repr_() # implicit doctest177'i'178"""179if not y:180return ''181if y == 1:182return i183elif y == -1:184return "-%s"%i185y = str(y)186if not atomic and ('+' in y or '-' in y):187return '(%s)*%s'%(y, i)188else:189return '%s*%s'%(y, i)190191cdef class QuaternionAlgebraElement_abstract(AlgebraElement):192cpdef bint is_constant(self):193"""194Return True if this quaternion is constant, i.e., has no i, j, or k term.195196OUTPUT:197bool198199EXAMPLES::200201sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)202sage: A(1).is_constant()203True204sage: A(1+i).is_constant()205False206sage: A(i).is_constant()207False208"""209return not (self[1] or self[2] or self[3])210211def __int__(self):212"""213Try to coerce this quaternion to a Python int.214215EXAMPLES::216sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)217sage: int(A(-3))218-3219sage: int(A(-3/2))220-2221sage: int(-3 + i)222Traceback (most recent call last):223...224TypeError225"""226if self.is_constant():227return int(self[0])228raise TypeError229230231def __long__(self):232"""233Try to coerce this quaternion to a Python long.234235EXAMPLES::236237sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)238sage: long(A(-3))239-3L240sage: long(A(-3/2))241-2L242sage: long(-3 + i)243Traceback (most recent call last):244...245TypeError246"""247if self.is_constant():248return long(self[0])249raise TypeError250251def __float__(self):252"""253Try to coerce this quaternion to a Python float.254255EXAMPLES::256257sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)258sage: float(A(-3/2))259-1.5260sage: float(A(-3))261-3.0262sage: float(-3 + i)263Traceback (most recent call last):264...265TypeError266"""267if self.is_constant():268return float(self[0])269raise TypeError270271def _integer_(self, ZZ=None):272"""273Try to coerce this quaternion to an Integer.274275EXAMPLES::276sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)277sage: Integer(A(-3)) # indirect doctest278-3279sage: type(Integer(A(-3)))280<type 'sage.rings.integer.Integer'>281sage: Integer(A(-3/2))282Traceback (most recent call last):283...284TypeError: no conversion of this rational to integer285sage: Integer(-3 + i)286Traceback (most recent call last):287...288TypeError289"""290if self.is_constant():291return Integer(self[0])292raise TypeError293294def _rational_(self):295"""296Try to coerce this quaternion to a Rational.297298EXAMPLES::299300sage: Q.<i,j,k> = QuaternionAlgebra(Frac(QQ['x']),-5,-2)301sage: Rational(Q(2/3)) # indirect doctest3022/3303sage: Rational(2/3 + i)304Traceback (most recent call last):305...306TypeError307"""308if self.is_constant():309return Rational(self[0])310raise TypeError311312def __nonzero__(self):313"""314Return True if this quaternion is nonzero.315316EXAMPLES::317318sage: Q.<i,j,k> = QuaternionAlgebra(Frac(QQ['x']),-5,-2)319sage: bool(i+j)320True321sage: (i+j).__nonzero__()322True323sage: Q(0).__nonzero__()324False325"""326return self[0] or self[1] or self[2] or self[3]327328cdef _do_print(self, x,y,z,w):329"""330Used internally by the print function.331332EXAMPLES::333334sage: Q.<i,j,k> = QuaternionAlgebra(-17,-19)335sage: str(i+j+k-3/4) # indirect doctest336'-3/4 + i + j + k'337"""338cdef bint atomic = self._parent._base.is_atomic_repr()339v = []340i,j,k = self._parent.variable_names()341if x:342v.append(str(x))343c = print_coeff(y,i,atomic)344if c: v.append(c)345c = print_coeff(z,j,atomic)346if c: v.append(c)347c = print_coeff(w,k,atomic)348if c: v.append(c)349if len(v) == 0: return '0'350return ' + '.join(v).replace('+ -','- ')351352def _repr_(self):353"""354Return string representation of this quaternion:355356EXAMPLES::357358sage: R.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(R,-5*x,-2)359sage: a = x + i*x^3 + j*x^2 + k*x360sage: a._repr_()361'x + x^3*i + x^2*j + x*k'362sage: a = x+2/3 + i*x^3 + j*(x^2-5/2) + k*x363sage: a._repr_()364'x + 2/3 + x^3*i + (x^2 - 5/2)*j + x*k'365sage: type(a)366<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>367sage: Q(0)._repr_()368'0'369"""370return self._do_print(self[0], self[1], self[2], self[3])371372cdef int _cmp_c_impl(self, sage.structure.element.Element right) except -2:373"""374Comparing elements.375376TESTS::377378sage: Q.<i,j,k> = QuaternionAlgebra(Frac(QQ['x']),-5,-2)379sage: a = 1/2 + 2/3*i - 3/4*j + 5/7*k380sage: a == a381True382sage: a == a + 1383False384sage: a < a + 1385True386387sage: K.<x> = QQ[]; Q.<i,j,k> = QuaternionAlgebra(x, 2*x); a = x + 2*x*i + 3*j + (x-2)*k388sage: (1/a)*a == 1389True390sage: (1/a)*a3911392"""393cdef int i394for i in range(4):395c = cmp(self[i], right[i])396if c: return c397return 0398399cpdef conjugate(self):400"""401Return the conjugate of the quaternion: if `\\theta = x + yi + zj + wk`,402return `x - yi - zj - wk`; that is, return theta.reduced_trace() - theta.403404EXAMPLES::405406sage: A.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)407sage: a = 3*i - j + 2408sage: type(a)409<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>410sage: a.conjugate()4112 - 3*i + j412413The "universal" test::414415sage: K.<x,y,z,w,a,b> = QQ[]416sage: Q.<i,j,k> = QuaternionAlgebra(a,b)417sage: theta = x+y*i+z*j+w*k418sage: theta.conjugate()419x + (-y)*i + (-z)*j + (-w)*k420"""421return self.__class__(self._parent, (self[0], -self[1], -self[2], -self[3]))422423cpdef reduced_trace(self):424"""425Return the reduced trace of self: if `\\theta = x + yi + zj +426wk`, then `\\theta` has reduced trace `2x`.427428EXAMPLES::429430sage: K.<x,y,z,w,a,b> = QQ[]431sage: Q.<i,j,k> = QuaternionAlgebra(a,b)432sage: theta = x+y*i+z*j+w*k433sage: theta.reduced_trace()4342*x435"""436return 2*self[0]437438cpdef reduced_norm(self):439"""440Return the reduced norm of self: if `\\theta = x + yi + zj +441wk`, then `\\theta` has reduced norm `x^2 - ay^2 - bz^2 +442abw^2`.443444EXAMPLES::445446sage: K.<x,y,z,w,a,b> = QQ[]447sage: Q.<i,j,k> = QuaternionAlgebra(a,b)448sage: theta = x+y*i+z*j+w*k449sage: theta.reduced_norm()450w^2*a*b - y^2*a - z^2*b + x^2451"""452a,b,x,y,z,w = self._parent._a,self._parent._b,self[0],self[1],self[2],self[3]453return w*w*a*b - y*y*a - z*z*b + x*x454455def __invert__(self):456"""457Return inverse of self.458459EXAMPLES::460461sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-7,-13)462sage: theta = 1/3 - 2/3*i + 4/19*j - 17/3*k463sage: (1/theta) * theta4641465sage: type(theta)466<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>467sage: 1/Q(0)468Traceback (most recent call last):469...470ZeroDivisionError: rational division by zero471472Note that the quaternion algebra need not be a division473algebra, in which case we can get a ZeroDivisionException::474475sage: Q.<i,j,k> = QuaternionAlgebra(QQ,4,9)476sage: theta = 2-i477sage: theta.reduced_norm()4780479sage: 1/theta480Traceback (most recent call last):481...482ZeroDivisionError: rational division by zero483484The ``universal`` test:485486sage: K.<x,y,z,w,a,b> = QQ[]487sage: Q.<i,j,k> = QuaternionAlgebra(a,b)488sage: theta = x+y*i+z*j+w*k489sage: 1/theta == theta.conjugate()/theta.reduced_norm()490True491"""492return self.reduced_norm().__invert__() * self.conjugate()493494cpdef RingElement _div_(self, RingElement right):495"""496Return quotient of self by right.497498EXAMPLES::499500sage: K.<x> = QQ[]; Q.<i,j,k> = QuaternionAlgebra(x, 2*x);501sage: theta = x + 2*x*i + 3*j + (x-2)*k502sage: type(theta)503<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>504sage: theta._div_(theta)5051506sage: theta._div_(theta) == 1507True508"""509return self * right.__invert__()510511def reduced_characteristic_polynomial(self, var='x'):512"""513Return the reduced characteristic polynomial of this514quaternion algebra element, which is `X^2 - tX + n`, where `t`515is the reduced trace and `n` is the reduced norm.516517INPUT:518- var -- string (default: 'x'); indeterminate of characteristic polynomial519520EXAMPLES::521522sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)523sage: i.reduced_characteristic_polynomial()524x^2 + 1525sage: j.reduced_characteristic_polynomial()526x^2 + 2527sage: (i+j).reduced_characteristic_polynomial()528x^2 + 3529sage: (2+j+k).reduced_trace()5304531sage: (2+j+k).reduced_characteristic_polynomial('T')532T^2 - 4*T + 8533"""534535R = PolynomialRing(self.base_ring(), var)536return R([self.reduced_norm(), -self.reduced_trace(), 1])537538def matrix(self, action='right'):539"""540Return the matrix of right or left multiplication of self on541the basis for the ambient quaternion algebra. In particular,542if action is 'right' (the default), returns the matrix of the543mapping sending x to x*self.544545INPUT:546547- ``action`` -- (default: 'right') 'right' or 'left'.548549OUTPUT:550551- a matrix552553EXAMPLES::554555sage: Q.<i,j,k> = QuaternionAlgebra(-3,-19)556sage: a = 2/3 -1/2*i + 3/5*j - 4/3*k557sage: a.matrix()558[ 2/3 -1/2 3/5 -4/3]559[ 3/2 2/3 4 3/5]560[-57/5 -76/3 2/3 1/2]561[ 76 -57/5 -3/2 2/3]562sage: a.matrix() == a.matrix(action='right')563True564sage: a.matrix(action='left')565[ 2/3 -1/2 3/5 -4/3]566[ 3/2 2/3 -4 -3/5]567[-57/5 76/3 2/3 -1/2]568[ 76 57/5 3/2 2/3]569sage: (i*a,j*a,k*a)570(3/2 + 2/3*i + 4*j + 3/5*k, -57/5 - 76/3*i + 2/3*j + 1/2*k, 76 - 57/5*i - 3/2*j + 2/3*k)571sage: a.matrix(action='foo')572Traceback (most recent call last):573...574ValueError: action must be either 'left' or 'right'575576We test over a more generic base field:577578sage: K.<x> = QQ['x']579sage: Q.<i,j,k> = QuaternionAlgebra(Frac(K),-5,-2)580sage: a = 1/2*x^2 + 2/3*x*i - 3/4*j + 5/7*k581sage: type(a)582<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>583sage: a.matrix()584[1/2*x^2 2/3*x -3/4 5/7]585[-10/3*x 1/2*x^2 -25/7 -3/4]586[ 3/2 10/7 1/2*x^2 -2/3*x]587[ -50/7 3/2 10/3*x 1/2*x^2]588"""589if action == 'right':590v = [(a*self).coefficient_tuple() for a in self._parent.basis()]591elif action == 'left':592v = [(self*a).coefficient_tuple() for a in self._parent.basis()]593else:594raise ValueError, "action must be either 'left' or 'right'"595return matrix(self.base_ring(), 4, v, check=False)596597def coefficient_tuple(self):598"""599Return 4-tuple of coefficients of this quaternion.600601EXAMPLES::602603sage: K.<x> = QQ['x']604sage: Q.<i,j,k> = QuaternionAlgebra(Frac(K),-5,-2)605sage: a = 1/2*x^2 + 2/3*x*i - 3/4*j + 5/7*k606sage: type(a)607<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>608sage: a.coefficient_tuple()609(1/2*x^2, 2/3*x, -3/4, 5/7)610"""611return (self[0], self[1], self[2], self[3])612613def pair(self, right):614"""615Return the result of pairing self and right, which should both616be elements of a quaternion algebra. The pairing is617(x,y) = (x.conjugate()*y).reduced_trace().618619INPUT:620621- ``right`` -- quaternion622623EXAMPLES::624625sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)626sage: (1+i+j-2*k).pair(2/3+5*i-3*j+k)627-26/3628sage: x = 1+i+j-2*k; y = 2/3+5*i-3*j+k629sage: x.pair(y)630-26/3631sage: y.pair(x)632-26/3633sage: (x.conjugate()*y).reduced_trace()634-26/3635"""636return (self.conjugate() * right).reduced_trace()637638cdef class QuaternionAlgebraElement_generic(QuaternionAlgebraElement_abstract):639"""640TESTS:641642We test pickling::643644sage: R.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(R,-5*x,-2)645sage: theta = x + i*x^3 + j*x^2 + k*x646sage: theta == loads(dumps(theta))647True648"""649def __init__(self, parent, v):650"""651Create a quaternion over some general base field.652653EXAMPLES::654655sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)656sage: sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic(Q, (x,1,-7,2/3*x^3))657x + i + (-7)*j + 2/3*x^3*k658"""659self._parent = parent660self.x, self.y, self.z, self.w = to_quaternion(parent._base, v)661662def __getitem__(self, int i):663"""664EXAMPLES::665666sage: Q.<i,j,k> = QuaternionAlgebra(Frac(QQ['x']),-5,-2)667sage: theta = 1/2 + 2/3*i - 3/4*j + 5/7*k668sage: type(theta)669<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>670sage: list(theta)671[1/2, 2/3, -3/4, 5/7]672"""673if i == 0:674return self.x675elif i == 1:676return self.y677elif i == 2:678return self.z679elif i == 3:680return self.w681else:682raise IndexError, "quaternion element index out of range"683684def __reduce__(self):685"""686Used for pickling.687688TESTS::689sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)690sage: theta = 1/x + x*i - (x+1)*j + 2/(3*x^3+5)*k691sage: loads(dumps(theta)) == theta692True693sage: type(theta)694<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>695"""696return (unpickle_QuaternionAlgebraElement_generic_v0,697(self._parent, (self.x, self.y, self.z, self.w)))698699cpdef ModuleElement _add_(self, ModuleElement _right):700"""701Return the sum of self and _right.702703EXAMPLES::704705sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)706sage: (x+i+j+x^3*k) + (x-i-j+ (2/3*x^3+x)*k) # indirect doctest7072*x + (5/3*x^3 + x)*k708sage: type(i)709<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>710"""711cdef QuaternionAlgebraElement_generic right = _right712# TODO -- make this, etc. use PY_NEW713return QuaternionAlgebraElement_generic(self._parent, (self.x + right.x, self.y + right.y, self.z + right.z, self.w + right.w))714715cpdef ModuleElement _sub_(self, ModuleElement _right):716"""717Return the difference of self and _right.718719EXAMPLES::720721sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)722sage: type(i)723<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>724sage: (x+i+j+x^3*k)._sub_(x-i-j+ (2/3*x^3+x)*k)7252*i + 2*j + (1/3*x^3 - x)*k726"""727cdef QuaternionAlgebraElement_generic right = _right728return QuaternionAlgebraElement_generic(self._parent, (self.x - right.x, self.y - right.y, self.z - right.z, self.w - right.w))729730cpdef RingElement _mul_(self, RingElement _right):731"""732Return the product of self and _right.733734EXAMPLES::735sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)736sage: type(i)737<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>738sage: (x+i+j+x^3*k)._mul_(x-i-j+ (2/3*x^3+x)*k)739-20/3*x^6 - 10*x^4 + x^2 + 7 + (10/3*x^3 + 2*x)*i + (-25/3*x^3 - 5*x)*j + (5/3*x^4 + x^2)*k740"""741cdef QuaternionAlgebraElement_generic right = _right742743a = self._parent._a744b = self._parent._b745746x1, y1, z1, w1 = self.x, self.y, self.z, self.w747x2, y2, z2, w2 = right.x, right.y, right.z, right.w748749#x = x1*x2 + y1*y2*a + z1*z2*b - w1*w2*a*b750#y = x1*y2 + y1*x2 - z1*w2*b + w1*z2*b751#z = x1*z2 + y1*w2 + z1*x2 - w1*y2*a752#w = x1*w2 + y1*z2 - z1*y2 + w1*x2753754t1 = x1 * x2755t2 = y1 * y2756t3 = z1 * z2757t4 = w1 * w2758t5 = x2 * z1759t6 = y2 * w1760t7 = x1 * z2761t8 = y1 * w2762763x = t1 + a * t2 + b * (t3 - a*t4)764y = (x1 + y1)*(x2 + y2) - t1 - t2 + b*( (z1 + w1)*(z2 - w2) - t3 + t4)765z = t5 - a*t6 + t7 + a*t8766w = (x2 - y2)*(z1 + w1) - t5 + t6 + (x1 + y1)*(z2 + w2) - t7 - t8767768return QuaternionAlgebraElement_generic(self._parent, (x, y, z, w))769770def _repr_(self):771"""772Print representation of self.773774EXAMPLES::775776sage: K.<x> = Frac(QQ['x']); Q.<i,j,k> = QuaternionAlgebra(K,-5,-2)777sage: theta = 1/x + x*i - (x+1)*j + 2/(3*x^3+5)*k778sage: type(theta)779<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>780sage: theta._repr_()781'1/x + x*i + (-x - 1)*j + (2/(3*x^3 + 5))*k'782"""783return self._do_print(self.x, self.y, self.z, self.w)784785786cdef class QuaternionAlgebraElement_rational_field(QuaternionAlgebraElement_abstract):787"""788TESTS:789790We test pickling::791792sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)793sage: i + j + k == loads(dumps(i+j+k))794True795"""796797# Implementation Notes:798#799# A Quaternion algebra element (call it a) over Q are implemented as a 4-tuple of800# integers x, y, z, w and a denominator d, all of type mpz_t, such that801#802# theta = (1/d) * (x + y * i + z * j + w * k)803#804# (although different letters may be specified instead of i, j, and k, if desired).805#806# Inside the element we also store mpz_t integers a and b, where807#808# i^2 = a and j^2 = b809810def __cinit__(self):811"""812Initialize C variables.813814EXAMPLES::815816sage: QuaternionAlgebra(QQ,-5,-2)([1/2,-1/3,2/3,4/5]) # implicit doctest8171/2 - 1/3*i + 2/3*j + 4/5*k818"""819mpz_init(self.x)820mpz_init(self.y)821mpz_init(self.z)822mpz_init(self.w)823mpz_init(self.a)824mpz_init(self.b)825mpz_init(self.d)826827def __dealloc__(self):828mpz_clear(self.x)829mpz_clear(self.y)830mpz_clear(self.z)831mpz_clear(self.w)832mpz_clear(self.a)833mpz_clear(self.b)834mpz_clear(self.d)835836def _rational_(self):837"""838Try to coerce this quaternion to a Rational.839840EXAMPLES::841842sage: A.<i,j,k> = QuaternionAlgebra(-1,-2)843sage: Rational(A(-2/3)) # indirect doctest844-2/3845sage: Rational(i)846Traceback (most recent call last):847...848TypeError849"""850cdef Rational x = Rational()851if self.is_constant():852mpq_set_num(x.value, self.x)853mpq_set_den(x.value, self.d)854mpq_canonicalize(x.value)855return x856raise TypeError857858cpdef bint is_constant(self):859"""860Return True if this quaternion is constant, i.e., has no i, j, or k term.861862OUTPUT:863bool864865EXAMPLES::866867sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)868sage: A(1/3).is_constant()869True870sage: A(-1).is_constant()871True872sage: (1+i).is_constant()873False874sage: j.is_constant()875False876"""877return not (mpz_sgn(self.y) or mpz_sgn(self.z) or mpz_sgn(self.w))878879def __nonzero__(self):880"""881Return True if this quaternion is nonzero.882883EXAMPLES::884885sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)886sage: bool(1+j+k)887True888sage: A(0).__nonzero__()889False890"""891return bool(mpz_sgn(self.x) or mpz_sgn(self.y) or mpz_sgn(self.z) or mpz_sgn(self.w))892893cdef int _cmp_c_impl(self, sage.structure.element.Element _right) except -2:894"""895Compare two quaternions. The comparison is fairly arbitrary896-- first the denominators are compared and if equal then each897of the other coefficients are compared.898899TESTS::900901sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)902sage: i < j903False904sage: -i < j905True906sage: i == i907True908"""909cdef QuaternionAlgebraElement_rational_field right = _right910cdef int i911i = mpz_cmp(self.d, right.d)912if i < 0: return -1913elif i > 0: return 1914i = mpz_cmp(self.x, right.x)915if i < 0: return -1916elif i > 0: return 1917i = mpz_cmp(self.y, right.y)918if i < 0: return -1919elif i > 0: return 1920i = mpz_cmp(self.z, right.z)921if i < 0: return -1922elif i > 0: return 1923i = mpz_cmp(self.w, right.w)924if i < 0: return -1925elif i > 0: return 1926return 0927928def __init__(self, parent, v):929"""930Setup element data from parent and coordinates.931932EXAMPLES::933934sage: A.<i,j,k>=QuaternionAlgebra(-4,-5)935sage: A(2/3)9362/3937sage: type(A(2/3))938<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>939940sage: A([-1/2,-10/3,-2/3,-4/5]) # implicit doctest941-1/2 - 10/3*i - 2/3*j - 4/5*k942sage: A(vector([1,2/3,3/4,4/5]))9431 + 2/3*i + 3/4*j + 4/5*k944945::946947sage: QA = QuaternionAlgebra(QQ, -1, -1)948sage: foo = QA(3.0); foo9493950sage: parent(foo)951Quaternion Algebra (-1, -1) with base ring Rational Field952sage: foo[0]9533954sage: parent(foo[0])955Rational Field956"""957self._parent = parent958959# cache a and b960mpz_set(self.a, (<Integer>parent._a).value)961mpz_set(self.b, (<Integer>parent._b).value)962963cdef Rational x, y, z, w964cdef mpz_t lcm965cdef mpq_t lcm_rat966if not isinstance(v, (list,tuple)):967try:968x = Rational(v)969mpz_set(self.x, mpq_numref(x.value))970mpz_set_si(self.y, 0)971mpz_set_si(self.z, 0)972mpz_set_si(self.w, 0)973mpz_set(self.d, mpq_denref(x.value))974return975except TypeError:976pass977v = list(v)978# Now v is definitely a list or tuple, and we convert each979# entry to a rational, then clear denominators, etc.980x = Rational(v[0])981y = Rational(v[1])982z = Rational(v[2])983w = Rational(v[3])984mpz_init(lcm)985mpz_lcm(lcm, mpq_denref(x.value), mpq_denref(y.value))986mpz_lcm(lcm, lcm, mpq_denref(z.value))987mpz_lcm(lcm, lcm, mpq_denref(w.value))988if mpz_cmp_si(lcm, 1):989mpz_init_set(mpq_numref(lcm_rat), lcm)990mpz_init_set_si(mpq_denref(lcm_rat), 1)991mpq_mul(x.value, x.value, lcm_rat)992mpq_mul(y.value, y.value, lcm_rat)993mpq_mul(z.value, z.value, lcm_rat)994mpq_mul(w.value, w.value, lcm_rat)995mpq_clear(lcm_rat)996mpz_set(self.x, mpq_numref(x.value))997mpz_set(self.y, mpq_numref(y.value))998mpz_set(self.z, mpq_numref(z.value))999mpz_set(self.w, mpq_numref(w.value))1000mpz_set(self.d, lcm)1001mpz_clear(lcm)100210031004def __getitem__(self, int i):1005"""1006TESTS::10071008sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)1009sage: theta = 1/2 + 2/3*i - 3/4*j + 5/7*k1010sage: type(theta)1011<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>1012sage: list(theta)1013[1/2, 2/3, -3/4, 5/7]1014"""1015cdef Rational r = Rational()1016if i == 0:1017mpq_set_num(r.value, self.x)1018elif i == 1:1019mpq_set_num(r.value, self.y)1020elif i == 2:1021mpq_set_num(r.value, self.z)1022elif i == 3:1023mpq_set_num(r.value, self.w)1024else:1025raise IndexError, "quaternion element index out of range"1026mpq_set_den(r.value, self.d)1027mpq_canonicalize(r.value)1028return r10291030def __reduce__(self):1031"""1032Used for pickling.10331034TESTS::10351036sage: K.<x> = QQ[]1037sage: Q.<i,j,k> = QuaternionAlgebra(Frac(K),-5,-19)1038sage: theta = 1/2 + 2/3*i - 3/4*j + 5/7*k1039sage: type(theta)1040<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>1041sage: loads(dumps(theta)) == theta1042True10431044"""1045return (unpickle_QuaternionAlgebraElement_rational_field_v0,1046(self._parent, (self[0], self[1], self[2], self[3])))10471048cpdef ModuleElement _add_(self, ModuleElement _right):1049"""1050EXAMPLES::10511052sage: Q.<i,j,k> = QuaternionAlgebra(15)1053sage: type(i)1054<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>1055sage: (2/3 + 3/4*i + 5/6*j + 7/8*k)._add_(-2/3 - 3/4*i + 5/6*j + 7/8*k)10565/3*j + 7/4*k1057"""10581059# Given two quaternion algebra elements1060# theta = (1/d1)*(x1 + y1 * i + z1 * j + w1 * k)1061# nu = (1/d2)*(x2 + y2 * i + z2 * j + w2 * k)1062#1063# we compute their sum as1064#1065# (theta + nu) = (1/d3)*(x3 + y3 * i + z3 * j + w3 * k)1066#1067# with d3 = d1 * d21068# x3 = d1 * x2 + d2 * x11069# y3 = d1 * y2 + d2 * y11070# z3 = d1 * z2 + d2 * z11071# w3 = d1 * w2 + d2 * w11072#1073# and then we reduce the sum by dividing everything1074# by the gcd of d3, x3, y3, z3, and w310751076cdef QuaternionAlgebraElement_rational_field right = _right1077cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1078result._parent = self._parent10791080mpz_mul(U1, self.x, right.d) # U1 = x1 * d21081mpz_mul(U2, right.x, self.d) # U2 = x2 * d11082mpz_add(result.x, U1, U2) # x3 = x1 * d2 + x2 * d110831084mpz_mul(U1, self.y, right.d) # U1 = y1 * d21085mpz_mul(U2, right.y, self.d) # U2 = y2 * d11086mpz_add(result.y, U1, U2) # x3 = y1 * d2 + y2 * d110871088mpz_mul(U1, self.z, right.d) # U1 = z1 * d21089mpz_mul(U2, right.z, self.d) # U2 = z2 * d11090mpz_add(result.z, U1, U2) # z3 = z1 * d2 + z2 * d110911092mpz_mul(U1, self.w, right.d) # U1 = w1 * d21093mpz_mul(U2, right.w, self.d) # U2 = w2 * d11094mpz_add(result.w, U1, U2) # w3 = w1 * d2 + w2 * d110951096mpz_mul(result.d, self.d, right.d) # d3 = d1 * d210971098result.canonicalize()10991100mpz_set(result.a, self.a)1101mpz_set(result.b, self.b)1102return result11031104cpdef ModuleElement _sub_(self, ModuleElement _right):1105"""1106EXAMPLES::11071108sage: Q.<i,j,k> = QuaternionAlgebra(15)1109sage: type(i)1110<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>1111sage: (2/3 + 3/4*i + 5/6*j + 7/8*k)._sub_(-2/3 - 3/4*i + 5/6*j + 7/8*k)11124/3 + 3/2*i1113"""1114cdef QuaternionAlgebraElement_rational_field right = _right1115cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1116result._parent = self._parent11171118# Implementation Note: To obtain _sub_, we simply replace every occurrence of1119# "add" in _add_ with "sub"; that is, we s/add/sub to get _sub_11201121mpz_mul(U1, self.x, right.d)1122mpz_mul(U2, right.x, self.d)1123mpz_sub(result.x, U1, U2)11241125mpz_mul(U1, self.y, right.d)1126mpz_mul(U2, right.y, self.d)1127mpz_sub(result.y, U1, U2)11281129mpz_mul(U1, self.z, right.d)1130mpz_mul(U2, right.z, self.d)1131mpz_sub(result.z, U1, U2)11321133mpz_mul(U1, self.w, right.d)1134mpz_mul(U2, right.w, self.d)1135mpz_sub(result.w, U1, U2)11361137mpz_mul(result.d, self.d, right.d)11381139result.canonicalize()11401141mpz_set(result.a, self.a)1142mpz_set(result.b, self.b)1143return result11441145cpdef RingElement _mul_(self, RingElement _right):1146"""1147EXAMPLES::11481149sage: Q.<i,j,k> = QuaternionAlgebra(15)1150sage: type(i)1151<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>1152sage: (2/3 + 3/4*i + 5/6*j + 7/8*k)._mul_(-2/3 - 3/4*i + 5/6*j + 7/8*k)11539331/576 - i - 63/16*j + 5/4*k1154"""11551156# We use the following formula for multiplication:1157#1158# Given two quaternion algebra elements1159#1160# theta = (1/d1)*(x1 + y1 * i + z1 * j + w1 * k)1161# nu = (1/d2)*(x2 + y2 * i + z2 * j + w2 * k)1162#1163# we compute their product as1164#1165# theta*nu = (1/d3)*(x3 + y3 * i + z3 * j + w3 * k)1166#1167# where1168# x3 = t1 + a * t2 + b * (t3 - a*t4)1169# y3 = s1*(x2 + y2) - t1 - t2 + b*( s2*(z2 - w2) - t3 + t4)1170# z3 = t5 - a*t6 + t7 + a*t81171# w3 = (x2 - y2)*s2 - t5 + t6 + s1*(z2 + w2) - t7 - t81172#1173# and where1174# t1 = x1 * x21175# t2 = y1 * y21176# t3 = z1 * z21177# t4 = w1 * w21178# t5 = x2 * z11179# t6 = y2 * w11180# t7 = x1 * z21181# t8 = y1 * w21182#1183# s1 = x1 + y11184# s2 = z1 + w11185#1186# This takes more integer addition operations but fewer integer multiplication1187# operations than the "straightforward" multiplication method.1188#1189# There might be a way to optimize this formula further.119011911192cdef QuaternionAlgebraElement_rational_field right = _right1193cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1194result._parent = self._parent11951196mpz_set(result.a, self.a)1197mpz_set(result.b, self.b)11981199mpz_mul(T1, self.x, right.x) # t1 = x1 * x21200mpz_mul(T2, self.y, right.y) # t2 = y1 * y21201mpz_mul(t3, self.z, right.z) # t3 = z1 * z21202mpz_mul(t4, self.w, right.w) # t4 = w1 * w21203mpz_mul(t5, right.x, self.z) # t5 = x2 * z11204mpz_mul(t6, right.y, self.w) # t6 = y2 * w11205mpz_mul(t7, self.x, right.z) # t7 = x1 * z21206mpz_mul(t8, self.y, right.w) # t8 = y1 * w212071208mpz_add(s1, self.x, self.y) # s1 = x1 + y11209mpz_add(s2, self.z, self.w) # s2 = z1 + w112101211#------------------12121213mpz_mul(U1, self.a, t4)1214mpz_sub(U1, t3, U1)1215mpz_mul(U1, U1, self.b)1216mpz_mul(U2, self.a, T2)1217mpz_add(result.x, T1, U2)1218mpz_add(result.x, result.x, U1)12191220#------------------12211222mpz_sub(U1, right.z, right.w)1223mpz_mul(U1, U1, s2)1224mpz_sub(U1, U1, t3)1225mpz_add(U1, U1, t4)1226mpz_mul(U1, U1, self.b)1227mpz_sub(U1, U1, T2)1228mpz_sub(U1, U1, T1)1229mpz_add(U2, right.x, right.y)1230mpz_mul(U2, s1, U2)1231mpz_add(result.y, U1, U2)12321233#------------------12341235mpz_mul(U1, self.a, t8)1236mpz_add(U1, U1, t7)1237mpz_mul(U2, self.a, t6)1238mpz_sub(U1, U1, U2)1239mpz_add(result.z, U1, t5)12401241#------------------12421243mpz_add(U1, right.z, right.w)1244mpz_mul(U1, U1, s1)1245mpz_sub(U1, U1, t7)1246mpz_sub(U1, U1, t8)1247mpz_add(U1, U1, t6)1248mpz_sub(U1, U1, t5)1249mpz_sub(U2, right.x, right.y)1250mpz_mul(U2, U2, s2)1251mpz_add(result.w, U1, U2)1252125312541255mpz_mul(result.d, self.d, right.d)12561257result.canonicalize()12581259return result12601261cpdef reduced_norm(self):1262"""1263Return the reduced norm of self. Given a quaternion1264`x+yi+zj+wk`, this is `x^2 - ay^2 - bz^2 + abw^2`.12651266EXAMPLES::12671268sage: K.<i,j,k> = QuaternionAlgebra(QQ, -5, -2)1269sage: i.reduced_norm()127051271sage: j.reduced_norm()127221273sage: a = 1/3 + 1/5*i + 1/7*j + k1274sage: a.reduced_norm()127522826/22051276"""1277mpz_mul(U1, self.x, self.x) # U1 = x*x1278mpz_mul(U2, self.b, self.z) # U2 = b*x1279mpz_mul(U2, U2, self.z) # U2 = b*z*z1280mpz_sub(U2, U1, U2) # U2 = -b*z*z + x*x12811282mpz_mul(U1, self.y, self.a) # U1 = a*y1283mpz_mul(U1, U1, self.y) # U1 = a*y*y1284mpz_sub(U2, U2, U1) # U2 = -a*y*y - b*z*z + x*x12851286mpz_mul(U1, self.w, self.w) # U1 = w*w1287mpz_mul(U1, U1, self.a) # U1 = w*w*a1288mpz_mul(U1, U1, self.b) # U1 = w*w*a*b12891290mpz_add(U1, U1, U2) # U1 = w*w*a*b - a*y*y - b*z*z + x*x12911292mpz_mul(U2, self.d, self.d)12931294cdef Rational result = PY_NEW(Rational)1295mpq_set_num(result.value, U1)1296mpq_set_den(result.value, U2)1297mpq_canonicalize(result.value)12981299return result13001301cpdef conjugate(self):1302"""1303Return the conjugate of this quaternion.13041305EXAMPLES::13061307sage: A.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)1308sage: a = 3*i - j + 21309sage: type(a)1310<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>1311sage: a.conjugate()13122 - 3*i + j1313sage: b = 1 + 1/3*i + 1/5*j - 1/7*k1314sage: b.conjugate()13151 - 1/3*i - 1/5*j + 1/7*k1316"""13171318cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1319result._parent = self._parent13201321mpz_set(result.a, self.a)1322mpz_set(result.b, self.b)1323mpz_set(result.d, self.d)13241325mpz_set(result.x, self.x)1326mpz_mul_si(result.y, self.y, -1)1327mpz_mul_si(result.z, self.z, -1)1328mpz_mul_si(result.w, self.w, -1)13291330return result13311332cpdef reduced_trace(self):1333"""1334Return the reduced trace of self, which is `2x` if self is `x+iy+zj+wk`.13351336EXAMPLES::13371338sage: K.<i,j,k> = QuaternionAlgebra(QQ, -5, -2)1339sage: i.reduced_trace()134001341sage: j.reduced_trace()134201343sage: a = 1/3 + 1/5*i + 1/7*j + k1344sage: a.reduced_trace()13452/31346"""1347#return 2*self[0]13481349mpz_mul_si(U1, self.x, 2)1350cdef Rational result = PY_NEW(Rational)1351mpq_set_num(result.value, U1)1352mpq_set_den(result.value, self.d)1353mpq_canonicalize(result.value)1354return result13551356cdef inline canonicalize(self):1357"""1358Put the representation of this quaternion element into1359smallest form. For `a = (1/d)(x + yi + zj + wk)` we1360divide `a`, `x`, `y`, `z`, and `w` by the gcd of all of them.13611362TESTS::1363sage: K.<i,j,k> = QuaternionAlgebra(QQ, -10, -7)1364sage: (1/4 + 1/2 * i + 1/7 * j + 1/28 * k)*14*i # implicit doctest1365-70 + 7/2*i + 5*j - 2*k1366"""13671368# Note: this function changes the module-level global variable1369# U1, so it isn't always safe to use this in the middle of1370# another function. Normally this function is called1371# at the end of an arithmetic routine, so this is fine.13721373# Implementation-wise, we compute the GCD's one at a time,1374# and quit if it ever becomes one137513761377mpz_gcd(U1, self.d, self.x)1378if mpz_cmp_ui(U1, 1) != 0:1379mpz_gcd(U1, U1, self.y)1380if mpz_cmp_ui(U1, 1) != 0:1381mpz_gcd(U1, U1, self.z)1382if mpz_cmp_ui(U1, 1) != 0:1383mpz_gcd(U1, U1, self.w)1384if mpz_cmp_ui(U1, 1) != 0:1385# at this point U1 actually contains the gcd of all the terms, and we divide1386mpz_divexact(self.d, self.d, U1)1387mpz_divexact(self.x, self.x, U1)1388mpz_divexact(self.y, self.y, U1)1389mpz_divexact(self.z, self.z, U1)1390mpz_divexact(self.w, self.w, U1)13911392def denominator(self):1393"""1394Return the lowest common multiple of the denominators of the coefficients1395of i, j and k for this quaternion.13961397EXAMPLES::13981399sage: A = QuaternionAlgebra(QQ, -1, -1)1400sage: A.<i,j,k> = QuaternionAlgebra(QQ, -1, -1)1401sage: a = (1/2) + (1/5)*i + (5/12)*j + (1/13)*k1402sage: a14031/2 + 1/5*i + 5/12*j + 1/13*k1404sage: a.denominator()14057801406sage: lcm([2, 5, 12, 13])14077801408sage: (a * a).denominator()14096084001410sage: (a + a).denominator()14113901412"""14131414cdef Integer d = Integer()1415mpz_set(d.value, self.d)1416return d14171418def denominator_and_integer_coefficient_tuple(self):1419"""1420Return 5-tuple d, x, y, z, w, where this rational quaternion1421is equal to `(x + yi + zj + wk)/d` and x, y, z, w do not share1422a common factor with d.14231424OUTPUT:14255-tuple of Integers14261427EXAMPLES::14281429sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)1430sage: (2 + 3*i + 4/3*j - 5*k).denominator_and_integer_coefficient_tuple()1431(3, 6, 9, 4, -15)1432"""1433cdef Integer d = Integer()1434cdef Integer y = Integer()1435cdef Integer x = Integer()1436cdef Integer z = Integer()1437cdef Integer w = Integer()14381439mpz_set(d.value, self.d)1440mpz_set(x.value, self.x)1441mpz_set(y.value, self.y)1442mpz_set(z.value, self.z)1443mpz_set(w.value, self.w)14441445return (d, x, y, z, w)14461447def integer_coefficient_tuple(self):1448"""1449Returns integer part of this quaternion, ignoring the common denominator.14501451OUTPUT:14524-tuple of Integers14531454EXAMPLES::14551456sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)1457sage: (2 + 3*i + 4/3*j - 5*k).integer_coefficient_tuple()1458(6, 9, 4, -15)1459"""1460cdef Integer y = Integer()1461cdef Integer x = Integer()1462cdef Integer z = Integer()1463cdef Integer w = Integer()14641465mpz_set(x.value, self.x)1466mpz_set(y.value, self.y)1467mpz_set(z.value, self.z)1468mpz_set(w.value, self.w)14691470return (x, y, z, w)1471147214731474def coefficient_tuple(self):1475"""1476Return 4-tuple of rational numbers which are the coefficients of this quaternion.14771478EXAMPLES::14791480sage: A.<i,j,k>=QuaternionAlgebra(-1,-2)1481sage: (2/3 + 3/5*i + 4/3*j - 5/7*k).coefficient_tuple()1482(2/3, 3/5, 4/3, -5/7)1483"""1484cdef Rational x = Rational()1485cdef Rational y = Rational()1486cdef Rational z = Rational()1487cdef Rational w = Rational()14881489mpq_set_num(x.value, self.x)1490mpq_set_num(y.value, self.y)1491mpq_set_num(z.value, self.z)1492mpq_set_num(w.value, self.w)14931494mpq_set_den(x.value, self.d)1495mpq_set_den(y.value, self.d)1496mpq_set_den(z.value, self.d)1497mpq_set_den(w.value, self.d)14981499mpq_canonicalize(x.value)1500mpq_canonicalize(y.value)1501mpq_canonicalize(z.value)1502mpq_canonicalize(w.value)1503return (x, y, z, w)15041505def _multiply_by_integer(self, Integer n):1506"""1507Return the product of self times the integer n.15081509EXAMPLES::1510sage: A = QuaternionAlgebra(7)1511sage: a = A.random_element()1512sage: 5*a == a._multiply_by_integer(5)1513True1514"""1515cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1516result._parent = self._parent15171518mpz_set(result.a, self.a)1519mpz_set(result.b, self.b)15201521if mpz_divisible_p(self.d, n.value) != 0:1522mpz_divexact(result.d, self.d, n.value)1523mpz_set(result.x, self.x)1524mpz_set(result.y, self.y)1525mpz_set(result.z, self.z)1526mpz_set(result.w, self.w)1527return result1528if mpz_divisible_p(n.value, self.d):1529mpz_divexact(T1, n.value, self.d)1530else:1531mpz_set(T1, n.value)15321533mpz_set(result.d, self.d)1534mpz_mul(result.x, self.x, T1)1535mpz_mul(result.y, self.y, T1)1536mpz_mul(result.z, self.z, T1)1537mpz_mul(result.w, self.w, T1)15381539return result15401541def _divide_by_integer(self, Integer n):1542"""1543Return the quotient of self by the integer n.15441545EXAMPLES::1546sage: A = QuaternionAlgebra(7)1547sage: a = A.random_element()1548sage: a/5 == a._divide_by_integer(5)1549True1550sage: a._divide_by_integer(0)1551Traceback (most recent call last):1552...1553ZeroDivisionError1554"""1555if mpz_sgn(n.value) == 0:1556raise ZeroDivisionError15571558cdef QuaternionAlgebraElement_rational_field result = <QuaternionAlgebraElement_rational_field> PY_NEW(QuaternionAlgebraElement_rational_field)1559result._parent = self._parent15601561mpz_set(result.a, self.a)1562mpz_set(result.b, self.b)15631564mpz_mul(result.d, self.d, n.value)1565mpz_set(result.x, self.x)1566mpz_set(result.y, self.y)1567mpz_set(result.z, self.z)1568mpz_set(result.w, self.w)15691570result.canonicalize()15711572return result1573157415751576cdef class QuaternionAlgebraElement_number_field(QuaternionAlgebraElement_abstract):1577def __cinit__(self):1578"""1579Allocate memory for this quaternion over a number field.1580"""1581fmpz_poly_init(self.x)1582fmpz_poly_init(self.y)1583fmpz_poly_init(self.z)1584fmpz_poly_init(self.w)1585fmpz_poly_init(self.a)1586fmpz_poly_init(self.b)1587fmpz_poly_init(self.modulus)1588mpz_init(self.d)15891590def __dealloc__(self):1591"""1592Free memory used by this quaternion over a number field.1593"""1594fmpz_poly_clear(self.x)1595fmpz_poly_clear(self.y)1596fmpz_poly_clear(self.z)1597fmpz_poly_clear(self.w)1598fmpz_poly_clear(self.a)1599fmpz_poly_clear(self.b)1600fmpz_poly_clear(self.modulus)1601mpz_clear(self.d)16021603def __init__(self, parent, v):1604"""1605EXAMPLES::16061607sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K,-a,a+1)1608sage: Q([a,-2/3,a^2-1/2,a*2]) # implicit doctest1609a + (-2/3)*i + (a^2 - 1/2)*j + 2*a*k1610"""1611self._parent = parent1612x, y, z, w = to_quaternion(parent._base, v)1613cdef NumberFieldElement a = <NumberFieldElement>(parent._base(parent._a))1614cdef NumberFieldElement b = <NumberFieldElement>(parent._base(parent._b))1615ZZX_to_fmpz_poly(self.x, (<NumberFieldElement>x).__numerator)1616ZZX_to_fmpz_poly(self.y, (<NumberFieldElement>y).__numerator)1617ZZX_to_fmpz_poly(self.z, (<NumberFieldElement>z).__numerator)1618ZZX_to_fmpz_poly(self.w, (<NumberFieldElement>w).__numerator)16191620ZZ_to_mpz(&T1, &(<NumberFieldElement>x).__denominator)1621ZZ_to_mpz(&T2, &(<NumberFieldElement>y).__denominator)1622ZZ_to_mpz(&t3, &(<NumberFieldElement>z).__denominator)1623ZZ_to_mpz(&t4, &(<NumberFieldElement>w).__denominator)16241625mpz_lcm(self.d, T1, T2)1626mpz_lcm(self.d, self.d, t3)1627mpz_lcm(self.d, self.d, t4)16281629mpz_divexact(T1, self.d, T1)1630mpz_divexact(T2, self.d, T2)1631mpz_divexact(t3, self.d, t3)1632mpz_divexact(t4, self.d, t4)16331634fmpz_poly_scalar_mul_mpz(self.x, self.x, T1)1635fmpz_poly_scalar_mul_mpz(self.y, self.y, T2)1636fmpz_poly_scalar_mul_mpz(self.z, self.z, t3)1637fmpz_poly_scalar_mul_mpz(self.w, self.w, t4)16381639ZZX_to_fmpz_poly(self.a, a.__numerator) # we will assume that the denominator of a and b are 11640ZZX_to_fmpz_poly(self.b, b.__numerator)16411642ZZX_to_fmpz_poly(self.modulus, (<NumberFieldElement>x).__fld_numerator.x) # and same for the modulus16431644def __getitem__(self, int i):1645"""1646EXAMPLES::16471648sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K,-a,a+1)1649sage: Q([a,-2/3,a^2-1/2,a*2])1650a + (-2/3)*i + (a^2 - 1/2)*j + 2*a*k1651sage: x = Q([a,-2/3,a^2-1/2,a*2])1652sage: type(x)1653<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_number_field'>1654sage: x[0]1655a1656sage: x[1]1657-2/31658sage: x[2]1659a^2 - 1/21660sage: x[3]16612*a1662sage: list(x)1663[a, -2/3, a^2 - 1/2, 2*a]1664"""1665# general number -- this code assumes that the number field is not quadratic!!16661667cdef NumberFieldElement el = <NumberFieldElement>(self._parent.base_ring().an_element())1668cdef NumberFieldElement item = el._new()16691670if i == 0:1671fmpz_poly_to_ZZX(item.__numerator, self.x)1672elif i == 1:1673fmpz_poly_to_ZZX(item.__numerator, self.y)1674elif i == 2:1675fmpz_poly_to_ZZX(item.__numerator, self.z)1676elif i == 3:1677fmpz_poly_to_ZZX(item.__numerator, self.w)1678else:1679raise IndexError, "quaternion element index out of range"16801681mpz_to_ZZ(&item.__denominator, &self.d)16821683return item168416851686def __reduce__(self):1687"""1688EXAMPLES::16891690sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K, -3, a)1691sage: z = (i+j+k+a)^2; z1692a^2 + 4*a - 3 + 2*a*i + 2*a*j + 2*a*k1693sage: f, t = z.__reduce__()1694sage: f(*t)1695a^2 + 4*a - 3 + 2*a*i + 2*a*j + 2*a*k1696sage: loads(dumps(z)) == z1697True1698"""1699return (unpickle_QuaternionAlgebraElement_number_field_v0,1700(self._parent, (self[0], self[1], self[2], self[3])))17011702cpdef ModuleElement _add_(self, ModuleElement _right):1703"""1704Add self and _right:17051706EXAMPLES::1707sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K, -3, a)1708sage: z = a + i + (2/3)*a^3*j + (1+a)*k; w = a - i - (2/3)*a^3*j + (1/3+a)*k1709sage: type(z)1710<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_number_field'>1711sage: z._add_(w)17122*a + (2*a + 4/3)*k1713"""17141715# Given two quaternion algebra elements1716# a = (1/d1)*(x1 + y1 * i + z1 * j + w1 * k)1717# b = (1/d2)*(x2 + y2 * i + z2 * j + w2 * k)1718#1719# we compute their sum as1720#1721# (a + b) = (1/d3)*(x3 + y3 * i + z3 * j + w3 * k)1722#1723# with d3 = d1 * d21724# x3 = d1 * x2 + d2 * x11725# y3 = d1 * y2 + d2 * y11726# z3 = d1 * z2 + d2 * z11727# w3 = d1 * w2 + d2 * w11728#1729# and then we reduce the sum by calling canonicalize().17301731# Note: We are assuming in this routine that the modulus is monic. This shouldn't1732# currently be an issue because it is impossible to create a number field with1733# a modulus that is not monic.17341735cdef QuaternionAlgebraElement_number_field right = _right1736cdef QuaternionAlgebraElement_number_field result = <QuaternionAlgebraElement_number_field> PY_NEW(QuaternionAlgebraElement_number_field)17371738fmpz_poly_set(result.a, self.a)1739fmpz_poly_set(result.b, self.b)1740fmpz_poly_set(result.modulus, self.modulus)1741result._parent = self._parent17421743fmpz_poly_scalar_mul_mpz(fU1, self.x, right.d)1744fmpz_poly_scalar_mul_mpz(fU2, right.x, self.d)1745fmpz_poly_add(result.x, fU1, fU2)17461747fmpz_poly_scalar_mul_mpz(fU1, self.y, right.d)1748fmpz_poly_scalar_mul_mpz(fU2, right.y, self.d)1749fmpz_poly_add(result.y, fU1, fU2)17501751fmpz_poly_scalar_mul_mpz(fU1, self.w, right.d)1752fmpz_poly_scalar_mul_mpz(fU2, right.w, self.d)1753fmpz_poly_add(result.w, fU1, fU2)17541755fmpz_poly_scalar_mul_mpz(fU1, self.z, right.d)1756fmpz_poly_scalar_mul_mpz(fU2, right.z, self.d)1757fmpz_poly_add(result.z, fU1, fU2)17581759mpz_mul(result.d, self.d, right.d)17601761self.canonicalize()17621763return result176417651766176717681769cpdef ModuleElement _sub_(self, ModuleElement _right):1770"""1771Subtract _right from self.17721773EXAMPLES::17741775sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K, -3, a)1776sage: z = a + i + (2/3)*a^3*j + (1+a)*k; w = a - i - (2/3)*a^3*j + (1/3+a)*k1777sage: type(z)1778<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_number_field'>1779sage: z._sub_(w)17802*i + 8/3*j + 2/3*k17811782"""1783# Implementation Note: To obtain _sub_, we simply replace every occurrence of1784# "add" in _add_ with "sub"; that is, we s/add/sub to get _sub_17851786# Note: We are assuming in this routine that the modulus is monic. This shouldn't1787# currently be an issue because it is impossible to create a number field with1788# a modulus that is not monic.1789cdef QuaternionAlgebraElement_number_field right = _right1790cdef QuaternionAlgebraElement_number_field result = <QuaternionAlgebraElement_number_field> PY_NEW(QuaternionAlgebraElement_number_field)179117921793fmpz_poly_set(result.a, self.a)1794fmpz_poly_set(result.b, self.b)1795fmpz_poly_set(result.modulus, self.modulus)1796result._parent = self._parent17971798fmpz_poly_scalar_mul_mpz(fU1, self.x, right.d)1799fmpz_poly_scalar_mul_mpz(fU2, right.x, self.d)1800fmpz_poly_sub(result.x, fU1, fU2)18011802fmpz_poly_scalar_mul_mpz(fU1, self.y, right.d)1803fmpz_poly_scalar_mul_mpz(fU2, right.y, self.d)1804fmpz_poly_sub(result.y, fU1, fU2)18051806fmpz_poly_scalar_mul_mpz(fU1, self.w, right.d)1807fmpz_poly_scalar_mul_mpz(fU2, right.w, self.d)1808fmpz_poly_sub(result.w, fU1, fU2)18091810fmpz_poly_scalar_mul_mpz(fU1, self.z, right.d)1811fmpz_poly_scalar_mul_mpz(fU2, right.z, self.d)1812fmpz_poly_sub(result.z, fU1, fU2)18131814mpz_mul(result.d, self.d, right.d)18151816self.canonicalize()18171818return result18191820cpdef RingElement _mul_(self, RingElement _right):1821"""1822Multiply self and _right.18231824EXAMPLES::18251826sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K, -3, a)1827sage: z = a + i + (2/3)*a^3*j + (1+a)*k; w = a - i - (2/3)*a^3*j + (1/3+a)*k1828sage: type(z)1829<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_number_field'>1830sage: z._mul_(w)18315*a^2 - 7/9*a + 9 + (-8/3*a^2 - 16/9*a)*i + (-6*a - 4)*j + (2*a^2 + 4/3*a)*k1832"""18331834# We use the following formula for multiplication:1835#1836# Given two quaternion algebra elements1837#1838# a = (1/d1)*(x1 + y1 * i + z1 * j + w1 * k)1839# b = (1/d2)*(x2 + y2 * i + z2 * j + w2 * k)1840#1841# we compute their product as1842#1843# ab = (1/d3)*(x3 + y3 * i + z3 * j + w3 * k)1844#1845# where1846# x3 = t1 + a * t2 + b * (t3 - a*t4)1847# y3 = s1*(x2 + y2) - t1 - t2 + b*( s2*(z2 - w2) - t3 + t4)1848# z3 = t5 - a*t6 + t7 + a*t81849# w3 = (x2 - y2)*s2 - t5 + t6 + s1*(z2 + w2) - t7 - t81850#1851# and where1852# t1 = x1 * x21853# t2 = y1 * y21854# t3 = z1 * z21855# t4 = w1 * w21856# t5 = x2 * z11857# t6 = y2 * w11858# t7 = x1 * z21859# t8 = y1 * w21860#1861# s1 = x1 + y11862# s2 = z1 + w11863#1864# This takes more polynomial addition operations but fewer polynomial multiplication1865# operations than the "straightforward" multiplication method.1866#1867# There might be a way to optimize this formula further.18681869cdef QuaternionAlgebraElement_number_field right = _right1870cdef QuaternionAlgebraElement_number_field result = <QuaternionAlgebraElement_number_field> PY_NEW(QuaternionAlgebraElement_number_field)18711872mpz_set_si(result.d, 1)18731874fmpz_poly_set(result.a, self.a)1875fmpz_poly_set(result.b, self.b)1876fmpz_poly_set(result.modulus, self.modulus)1877result._parent = self._parent18781879fmpz_poly_mul(fT1, self.x, right.x) # t1 = x1 * x21880fmpz_poly_mul(fT2, self.y, right.y) # t2 = y1 * y21881fmpz_poly_mul(ft3, self.z, right.z) # t3 = x1 * x21882fmpz_poly_mul(ft4, self.w, right.w) # t4 = w1 * w21883fmpz_poly_mul(ft5, right.x, self.z) # t5 = x2 * z11884fmpz_poly_mul(ft6, right.y, self.w) # t6 = y2 * w11885fmpz_poly_mul(ft7, self.x, right.z) # t7 = x1 * z21886fmpz_poly_mul(ft8, self.y, right.w) # t8 = y1 * w218871888fmpz_poly_add(fs1, self.x, self.y) # s1 = x1 + y11889fmpz_poly_add(fs2, self.z, self.w) # s2 = z1 + w18901891#------------------18921893fmpz_poly_mul(fU1, self.a, ft4)1894fmpz_poly_sub(fU1, ft3, fU1)1895fmpz_poly_mul(fU1, fU1, self.b)1896fmpz_poly_mul(fU2, self.a, fT2)1897fmpz_poly_add(result.x, fT1, fU2)1898fmpz_poly_add(result.x, result.x, fU1)18991900#------------------19011902fmpz_poly_sub(fU1, right.z, right.w)1903fmpz_poly_mul(fU1, fU1, fs2)1904fmpz_poly_sub(fU1, fU1, ft3)1905fmpz_poly_add(fU1, fU1, ft4)1906fmpz_poly_mul(fU1, fU1, self.b)1907fmpz_poly_sub(fU1, fU1, fT2)1908fmpz_poly_sub(fU1, fU1, fT1)1909fmpz_poly_add(fU2, right.x, right.y)1910fmpz_poly_mul(fU2, fs1, fU2)1911fmpz_poly_add(result.y, fU1, fU2)19121913#------------------19141915fmpz_poly_mul(fU1, self.a, ft8)1916fmpz_poly_add(fU1, fU1, ft7)1917fmpz_poly_mul(fU2, self.a, ft6)1918fmpz_poly_sub(fU1, fU1, fU2)1919fmpz_poly_add(result.z, fU1, ft5)19201921#------------------19221923fmpz_poly_add(fU1, right.z, right.w)1924fmpz_poly_mul(fU1, fU1, fs1)1925fmpz_poly_sub(fU1, fU1, ft7)1926fmpz_poly_sub(fU1, fU1, ft8)1927fmpz_poly_add(fU1, fU1, ft6)1928fmpz_poly_sub(fU1, fU1, ft5)1929fmpz_poly_sub(fU2, right.x, right.y)1930fmpz_poly_mul(fU2, fU2, fs2)1931fmpz_poly_add(result.w, fU1, fU2)19321933# At this point we have essentially computed the product, but we still1934# need to reduce modulo the modulus, which is what the following 12 lines do.1935#1936# When this was written, the version of flint in Sage had problems with1937# fpmz_poly_divrem(). This should be fixed in the newest version of1938# flint, which also should have some new functions which should do1939# this faster (Bill Hart sent an email to Bober and William about this).1940#1941# This should be fixed in the near future. (I don't know how much1942# faster it will be when it is updated, but the following code is1943# currently quite a bottleneck.19441945fmpz_poly_div(fT1, result.x, result.modulus)1946fmpz_poly_mul(fT1, fT1, result.modulus)1947fmpz_poly_sub(result.x, result.x, fT1)19481949fmpz_poly_div(fT1, result.y, result.modulus)1950fmpz_poly_mul(fT1, fT1, result.modulus)1951fmpz_poly_sub(result.y, result.y, fT1)19521953fmpz_poly_div(fT1, result.z, result.modulus)1954fmpz_poly_mul(fT1, fT1, result.modulus)1955fmpz_poly_sub(result.z, result.z, fT1)19561957fmpz_poly_div(fT1, result.w, result.modulus)1958fmpz_poly_mul(fT1, fT1, result.modulus)1959fmpz_poly_sub(result.w, result.w, fT1)19601961mpz_mul(result.d, self.d, right.d)19621963self.canonicalize()19641965return result19661967cdef inline canonicalize(self):1968"""1969Put the representation of this quaternion element into1970smallest form. For a = `(1/d)(x + yi + zj + wk)` we1971divide `a`, `x`, `y`, `z`, and `w` by the gcd of all of them.19721973TESTS::19741975sage: F = QQ[3^(1/3)]1976sage: a = F.gen()1977sage: K.<i,j,k> = QuaternionAlgebra(F, -10 + a, -7 - a)1978sage: ((1/4 + 1/2 * i + a^3/7 * j + a/28 * k)*14*i)^3 # implicit doctest197934503/2*a^2 + 132195/2*a + 791399/4 + (203/8*a^2 - 10591*a + 169225/4)*i + (-84695/4*a^2 + 483413/8*a + 18591/4)*j + (-87/2*a^2 + 18156*a - 72525)*k1980"""19811982# Note: this function changes the module-level global variables1983# U1 and U2, so it isn't always safe to use this in the middle of1984# another function. Normally this function is called1985# at the end of an arithmetic routine, so this is fine.19861987# Implementation-wise, we compute the GCD's one at a time,1988# and quit if it ever becomes one19891990cdef fmpz_t content = fmpz_init(fmpz_poly_max_limbs(self.x)) # TODO: think about how big this should be (probably the size of d)1991# Note that we have to allocate this here, and not1992# as a global variable, because fmpz_t's do not1993# self allocate memory1994fmpz_poly_content(content, self.x)1995fmpz_to_mpz(U1, content)1996mpz_gcd(U1, self.d, U1)1997if mpz_cmp_ui(U1, 1) != 0:1998fmpz_poly_content(content, self.y)1999fmpz_to_mpz(U2, content)2000mpz_gcd(U1, U1, U2)2001if mpz_cmp_ui(U1, 1) != 0:2002fmpz_poly_content(content, self.z)2003fmpz_to_mpz(U2, content)2004mpz_gcd(U1, U1, U2)2005if mpz_cmp_ui(U1, 1) != 0:2006fmpz_poly_content(content, self.w)2007fmpz_to_mpz(U2, content)2008mpz_gcd(U1, U1, U2)2009if mpz_cmp_ui(U1, 1) != 0:2010fmpz_poly_scalar_div_mpz(self.x, self.x, U1)2011fmpz_poly_scalar_div_mpz(self.y, self.y, U1)2012fmpz_poly_scalar_div_mpz(self.z, self.z, U1)2013fmpz_poly_scalar_div_mpz(self.w, self.w, U1)2014mpz_divexact(self.d, self.d, U1)20152016fmpz_clear(content)20172018201920202021#######################################################################2022# Versioned unpickle functions2023#######################################################################20242025def unpickle_QuaternionAlgebraElement_generic_v0(*args):2026"""2027EXAMPLES::20282029sage: K.<X> = QQ[]2030sage: Q.<i,j,k> = QuaternionAlgebra(Frac(K), -5,-19); z = 2/3 + i*X - X^2*j + X^3*k2031sage: f, t = z.__reduce__()2032sage: sage.algebras.quatalg.quaternion_algebra_element.unpickle_QuaternionAlgebraElement_generic_v0(*t)20332/3 + X*i + (-X^2)*j + X^3*k2034sage: sage.algebras.quatalg.quaternion_algebra_element.unpickle_QuaternionAlgebraElement_generic_v0(*t) == z2035True2036"""2037return QuaternionAlgebraElement_generic(*args)20382039def unpickle_QuaternionAlgebraElement_rational_field_v0(*args):2040"""2041EXAMPLES::20422043sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19); a = 2/3 + i*5/7 - j*2/5 +19/22044sage: f, t = a.__reduce__()2045sage: sage.algebras.quatalg.quaternion_algebra_element.unpickle_QuaternionAlgebraElement_rational_field_v0(*t)204661/6 + 5/7*i - 2/5*j2047"""2048return QuaternionAlgebraElement_rational_field(*args)20492050def unpickle_QuaternionAlgebraElement_number_field_v0(*args):2051"""2052EXAMPLES::20532054sage: K.<a> = QQ[2^(1/3)]; Q.<i,j,k> = QuaternionAlgebra(K, -3, a); z = i + j2055sage: f, t = z.__reduce__()2056sage: sage.algebras.quatalg.quaternion_algebra_element.unpickle_QuaternionAlgebraElement_number_field_v0(*t)2057i + j2058sage: sage.algebras.quatalg.quaternion_algebra_element.unpickle_QuaternionAlgebraElement_number_field_v0(*t) == z2059True2060"""2061return QuaternionAlgebraElement_number_field(*args)206220632064