Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/algebras/quatalg/quaternion_algebra.py
8822 views
1
"""
2
Quaternion Algebras
3
4
AUTHORS:
5
6
- Jon Bobber -- 2009 rewrite
7
8
- William Stein -- 2009 rewrite
9
10
This code is partly based on Sage code by David Kohel from 2005.
11
12
TESTS:
13
14
Pickling test::
15
16
sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
17
sage: Q == loads(dumps(Q))
18
True
19
"""
20
21
########################################################################
22
# Copyright (C) 2009 William Stein <[email protected]>
23
# Copyright (C) 2009 Jonathon Bober <[email protected]>
24
#
25
# Distributed under the terms of the GNU General Public License (GPL)
26
#
27
# This code is distributed in the hope that it will be useful,
28
# but WITHOUT ANY WARRANTY; without even the implied warranty of
29
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30
# General Public License for more details.
31
#
32
# The full text of the GPL is available at:
33
#
34
# http://www.gnu.org/licenses/
35
########################################################################
36
37
38
from sage.rings.arith import (GCD,
39
hilbert_conductor_inverse, hilbert_conductor,
40
factor, gcd, lcm, kronecker_symbol, valuation)
41
from sage.rings.all import RR, Integer
42
from sage.rings.integer_ring import ZZ
43
from sage.rings.rational import Rational
44
from sage.rings.finite_rings.constructor import GF
45
46
from sage.rings.ring import Algebra
47
from sage.rings.ideal import Ideal_fractional
48
from sage.rings.rational_field import is_RationalField, QQ
49
from sage.rings.infinity import infinity
50
from sage.rings.number_field.number_field import is_NumberField
51
from sage.structure.parent_gens import ParentWithGens, normalize_names
52
from sage.matrix.matrix_space import MatrixSpace
53
from sage.matrix.constructor import diagonal_matrix, matrix
54
from sage.structure.sequence import Sequence
55
from sage.structure.element import is_RingElement
56
from sage.modules.free_module import VectorSpace, FreeModule
57
from sage.modules.free_module_element import vector
58
59
from operator import itemgetter
60
61
import quaternion_algebra_element
62
import quaternion_algebra_cython
63
64
from sage.modular.modsym.p1list import P1List
65
66
from sage.misc.cachefunc import cached_method
67
68
from sage.categories.fields import Fields
69
_Fields = Fields()
70
71
########################################################
72
# Constructor
73
########################################################
74
75
_cache = {}
76
77
def QuaternionAlgebra(arg0, arg1=None, arg2=None, names='i,j,k'):
78
"""
79
There are three input formats:
80
81
- ``QuaternionAlgebra(a, b)``: quaternion algebra generated by ``i``, ``j``
82
subject to `i^2 = a`, `j^2 = b`, `j * i = -i * j`.
83
84
- ``QuaternionAlgebra(K, a, b)``: same as above but over a field ``K``.
85
Here, ``a`` and ``b`` are nonzero elements of a field (``K``) of
86
characteristic not 2, and we set `k = i * j`.
87
88
- ``QuaternionAlgebra(D)``: a rational quaternion algebra with
89
discriminant ``D``, where `D > 1` is a squarefree integer.
90
91
EXAMPLES:
92
93
``QuaternionAlgebra(a, b)`` - return quaternion algebra over the
94
*smallest* field containing the nonzero elements ``a`` and ``b`` with
95
generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b` and `j*i=-i*j`::
96
97
sage: QuaternionAlgebra(-2,-3)
98
Quaternion Algebra (-2, -3) with base ring Rational Field
99
sage: QuaternionAlgebra(GF(5)(2), GF(5)(3))
100
Quaternion Algebra (2, 3) with base ring Finite Field of size 5
101
sage: QuaternionAlgebra(2, GF(5)(3))
102
Quaternion Algebra (2, 3) with base ring Finite Field of size 5
103
sage: QuaternionAlgebra(QQ[sqrt(2)](-1), -5)
104
Quaternion Algebra (-1, -5) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2
105
sage: QuaternionAlgebra(sqrt(-1), sqrt(-3))
106
Quaternion Algebra (I, sqrt(-3)) with base ring Symbolic Ring
107
sage: QuaternionAlgebra(1r,1)
108
Quaternion Algebra (1, 1) with base ring Rational Field
109
110
Python ints, longs and floats may be passed to the ``QuaternionAlgebra(a, b)`` constructor, as may
111
all pairs of nonzero elements of a ring not of characteristic 2. The following tests address
112
the issues raised in trac ticket 10601::
113
114
sage: QuaternionAlgebra(1r,1)
115
Quaternion Algebra (1, 1) with base ring Rational Field
116
sage: QuaternionAlgebra(1,1.0r)
117
Quaternion Algebra (1.00000000000000, 1.00000000000000) with base ring Real Field with 53 bits of precision
118
sage: QuaternionAlgebra(0,0)
119
Traceback (most recent call last):
120
...
121
ValueError: a and b must be nonzero
122
sage: QuaternionAlgebra(GF(2)(1),1)
123
Traceback (most recent call last):
124
...
125
ValueError: a and b must be elements of a ring with characteristic not 2
126
sage: a = PermutationGroupElement([1,2,3])
127
sage: QuaternionAlgebra(a, a)
128
Traceback (most recent call last):
129
...
130
ValueError: a and b must be elements of a ring with characteristic not 2
131
132
``QuaternionAlgebra(K, a, b)`` - return quaternion algebra over the
133
field ``K`` with generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b`
134
and `i*j=-j*i`::
135
136
sage: QuaternionAlgebra(QQ, -7, -21)
137
Quaternion Algebra (-7, -21) with base ring Rational Field
138
sage: QuaternionAlgebra(QQ[sqrt(2)], -2,-3)
139
Quaternion Algebra (-2, -3) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2
140
141
``QuaternionAlgebra(D)`` - ``D`` is a squarefree integer; returns a
142
rational quaternion algebra of discriminant ``D``::
143
144
sage: QuaternionAlgebra(1)
145
Quaternion Algebra (-1, 1) with base ring Rational Field
146
sage: QuaternionAlgebra(2)
147
Quaternion Algebra (-1, -1) with base ring Rational Field
148
sage: QuaternionAlgebra(7)
149
Quaternion Algebra (-1, -7) with base ring Rational Field
150
sage: QuaternionAlgebra(2*3*5*7)
151
Quaternion Algebra (-22, 210) with base ring Rational Field
152
153
If the coefficients `a` and `b` in the definition of the quaternion
154
algebra are not integral, then a slower generic type is used for
155
arithmetic::
156
157
sage: type(QuaternionAlgebra(-1,-3).0)
158
<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>
159
sage: type(QuaternionAlgebra(-1,-3/2).0)
160
<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>
161
162
Make sure caching is sane::
163
164
sage: A = QuaternionAlgebra(2,3); A
165
Quaternion Algebra (2, 3) with base ring Rational Field
166
sage: B = QuaternionAlgebra(GF(5)(2),GF(5)(3)); B
167
Quaternion Algebra (2, 3) with base ring Finite Field of size 5
168
sage: A is QuaternionAlgebra(2,3)
169
True
170
sage: B is QuaternionAlgebra(GF(5)(2),GF(5)(3))
171
True
172
sage: Q = QuaternionAlgebra(2); Q
173
Quaternion Algebra (-1, -1) with base ring Rational Field
174
sage: Q is QuaternionAlgebra(QQ,-1,-1)
175
True
176
sage: Q is QuaternionAlgebra(-1,-1)
177
True
178
sage: Q.<ii,jj,kk> = QuaternionAlgebra(15); Q.variable_names()
179
('ii', 'jj', 'kk')
180
sage: QuaternionAlgebra(15).variable_names()
181
('i', 'j', 'k')
182
183
TESTS:
184
185
Verify that bug found when working on trac 12006 involving coercing invariants
186
into the base field is fixed::
187
188
sage: Q = QuaternionAlgebra(-1,-1); Q
189
Quaternion Algebra (-1, -1) with base ring Rational Field
190
sage: parent(Q._a)
191
Rational Field
192
sage: parent(Q._b)
193
Rational Field
194
"""
195
196
# QuaternionAlgebra(D)
197
if arg1 is None and arg2 is None:
198
K = QQ
199
D = Integer(arg0)
200
a, b = hilbert_conductor_inverse(D)
201
a = Rational(a); b = Rational(b)
202
203
elif arg2 is None:
204
# If arg0 or arg1 are Python data types, coerce them
205
# to the relevant Sage types. This is a bit inelegant.
206
L = []
207
for a in [arg0,arg1]:
208
if is_RingElement(a):
209
L.append(a)
210
elif isinstance(a, int) or isinstance(a, long):
211
L.append(Integer(a))
212
elif isinstance(a, float):
213
L.append(RR(a))
214
else:
215
raise ValueError("a and b must be elements of a ring with characteristic not 2")
216
217
# QuaternionAlgebra(a, b)
218
v = Sequence(L)
219
K = v.universe().fraction_field()
220
a = K(v[0])
221
b = K(v[1])
222
223
# QuaternionAlgebra(K, a, b)
224
else:
225
K = arg0
226
if K not in _Fields:
227
raise TypeError("base ring of quaternion algebra must be a field")
228
a = K(arg1)
229
b = K(arg2)
230
231
if K.characteristic() == 2:
232
# Lameness!
233
raise ValueError("a and b must be elements of a ring with characteristic not 2")
234
if a == 0 or b == 0:
235
raise ValueError("a and b must be nonzero")
236
237
global _cache
238
names = normalize_names(3, names)
239
key = (K, a, b, names)
240
if key in _cache:
241
return _cache[key]
242
A = QuaternionAlgebra_ab(K, a, b, names=names)
243
A._key = key
244
_cache[key] = A
245
return A
246
247
248
249
250
########################################################
251
# Classes
252
########################################################
253
254
def is_QuaternionAlgebra(A):
255
"""
256
Return ``True`` if ``A`` is of the QuaternionAlgebra data type.
257
258
EXAMPLES::
259
260
sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(QuaternionAlgebra(QQ,-1,-1))
261
True
262
sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(ZZ)
263
False
264
"""
265
return isinstance(A, QuaternionAlgebra_abstract)
266
267
class QuaternionAlgebra_abstract(Algebra):
268
def _repr_(self):
269
"""
270
EXAMPLES::
271
272
sage: sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_abstract(QQ)._repr_()
273
'Quaternion Algebra with base ring Rational Field'
274
"""
275
return "Quaternion Algebra with base ring %s"%self.base_ring()
276
277
def ngens(self):
278
"""
279
Return the number of generators of the quaternion algebra as a K-vector
280
space, not including 1. This value is always 3: the algebra is spanned
281
by the standard basis `1`, `i`, `j`, `k`.
282
283
EXAMPLES::
284
285
sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
286
sage: Q.ngens()
287
3
288
sage: Q.gens()
289
[i, j, k]
290
"""
291
return 3
292
293
def basis(self):
294
"""
295
Return the fixed basis of ``self``, which is `1`, `i`, `j`, `k`, where
296
`i`, `j`, `k` are the generators of ``self``.
297
298
EXAMPLES::
299
300
sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
301
sage: Q.basis()
302
(1, i, j, k)
303
304
sage: Q.<xyz,abc,theta> = QuaternionAlgebra(GF(9,'a'),-5,-2)
305
sage: Q.basis()
306
(1, xyz, abc, theta)
307
308
The basis is cached::
309
310
sage: Q.basis() is Q.basis()
311
True
312
"""
313
try:
314
return self.__basis
315
except AttributeError:
316
self.__basis = tuple([self(1)] + list(self.gens()))
317
return self.__basis
318
319
def inner_product_matrix(self):
320
"""
321
Return the inner product matrix associated to ``self``, i.e. the
322
Gram matrix of the reduced norm as a quadratic form on ``self``.
323
The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix
324
is just the diagonal matrix with diagonal entries `2`, `2a`, `2b`,
325
`2ab`.
326
327
EXAMPLES::
328
329
sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)
330
sage: Q.inner_product_matrix()
331
[ 2 0 0 0]
332
[ 0 10 0 0]
333
[ 0 0 38 0]
334
[ 0 0 0 190]
335
"""
336
try: return self.__inner_product_matrix
337
except AttributeError: pass
338
339
a, b = self._a, self._b
340
M = diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b])
341
M.set_immutable()
342
self.__inner_product_matrix = M
343
return M
344
345
def is_commutative(self):
346
"""
347
Return ``False`` always, since all quaternion algebras are
348
noncommutative.
349
350
EXAMPLES::
351
352
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3,-7)
353
sage: Q.is_commutative()
354
False
355
"""
356
return False
357
358
def is_division_algebra(self):
359
"""
360
Return ``True`` if the quaternion algebra is a division algebra (i.e.
361
every nonzero element in ``self`` is invertible), and ``False`` if the
362
quaternion algebra is isomorphic to the 2x2 matrix algebra.
363
364
EXAMPLES::
365
366
sage: QuaternionAlgebra(QQ,-5,-2).is_division_algebra()
367
True
368
sage: QuaternionAlgebra(1).is_division_algebra()
369
False
370
sage: QuaternionAlgebra(2,9).is_division_algebra()
371
False
372
sage: QuaternionAlgebra(RR(2.),1).is_division_algebra()
373
Traceback (most recent call last):
374
...
375
NotImplementedError: base field must be rational numbers
376
"""
377
if not is_RationalField(self.base_ring()):
378
raise NotImplementedError("base field must be rational numbers")
379
return self.discriminant() != 1
380
381
def is_matrix_ring(self):
382
"""
383
Return ``True`` if the quaternion algebra is isomorphic to the 2x2
384
matrix ring, and ``False`` if ``self`` is a division algebra (i.e.
385
every nonzero element in ``self`` is invertible).
386
387
EXAMPLES::
388
389
sage: QuaternionAlgebra(QQ,-5,-2).is_matrix_ring()
390
False
391
sage: QuaternionAlgebra(1).is_matrix_ring()
392
True
393
sage: QuaternionAlgebra(2,9).is_matrix_ring()
394
True
395
sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring()
396
Traceback (most recent call last):
397
...
398
NotImplementedError: base field must be rational numbers
399
400
"""
401
if not is_RationalField(self.base_ring()):
402
raise NotImplementedError("base field must be rational numbers")
403
return self.discriminant() == 1
404
405
def is_exact(self):
406
"""
407
Return ``True`` if elements of this quaternion algebra are represented
408
exactly, i.e. there is no precision loss when doing arithmetic. A
409
quaternion algebra is exact if and only if its base field is
410
exact.
411
412
EXAMPLES::
413
414
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
415
sage: Q.is_exact()
416
True
417
sage: Q.<i,j,k> = QuaternionAlgebra(Qp(7), -3, -7)
418
sage: Q.is_exact()
419
False
420
"""
421
return self.base_ring().is_exact()
422
423
def is_field(self, proof = True):
424
"""
425
Return ``False`` always, since all quaternion algebras are
426
noncommutative and all fields are commutative.
427
428
EXAMPLES::
429
430
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
431
sage: Q.is_field()
432
False
433
"""
434
return False
435
436
def is_finite(self):
437
"""
438
Return ``True`` if the quaternion algebra is finite as a set.
439
440
Algorithm: A quaternion algebra is finite if and only if the
441
base field is finite.
442
443
EXAMPLES::
444
445
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
446
sage: Q.is_finite()
447
False
448
sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)
449
sage: Q.is_finite()
450
True
451
"""
452
return self.base_ring().is_finite()
453
454
def is_integral_domain(self, proof = True):
455
"""
456
Return ``False`` always, since all quaternion algebras are
457
noncommutative and integral domains are commutative (in Sage).
458
459
EXAMPLES::
460
461
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
462
sage: Q.is_integral_domain()
463
False
464
"""
465
return False
466
467
def is_noetherian(self):
468
"""
469
Return ``True`` always, since any quaternion algebra is a noetherian
470
ring (because it is a finitely generated module over a field).
471
472
EXAMPLES::
473
474
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
475
sage: Q.is_noetherian()
476
True
477
"""
478
return True
479
480
def order(self):
481
"""
482
Return the number of elements of the quaternion algebra, or
483
``+Infinity`` if the algebra is not finite.
484
485
EXAMPLES::
486
487
sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
488
sage: Q.order()
489
+Infinity
490
sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)
491
sage: Q.order()
492
625
493
"""
494
return (self.base_ring().order())**4
495
496
def random_element(self, *args, **kwds):
497
"""
498
Return a random element of this quaternion algebra.
499
500
The ``args`` and ``kwds`` are passed to the ``random_element`` method
501
of the base ring.
502
503
EXAMPLES::
504
505
sage: QuaternionAlgebra(QQ[sqrt(2)],-3,7).random_element()
506
(sqrt2 + 2)*i + (-12*sqrt2 - 2)*j + (-sqrt2 + 1)*k
507
sage: QuaternionAlgebra(-3,19).random_element()
508
-1 + 2*i - j - 6/5*k
509
sage: QuaternionAlgebra(GF(17)(2),3).random_element()
510
14 + 10*i + 4*j + 7*k
511
512
Specify the numerator and denominator bounds::
513
514
sage: QuaternionAlgebra(-3,19).random_element(10^6,10^6)
515
-979933/553629 + 255525/657688*i - 3511/6929*j - 700105/258683*k
516
"""
517
K = self.base_ring()
518
return self([ K.random_element(*args, **kwds) for _ in range(4) ])
519
520
def vector_space(self):
521
"""
522
Return the vector space associated to ``self`` with inner product given
523
by the reduced norm.
524
525
EXAMPLES::
526
527
sage: QuaternionAlgebra(-3,19).vector_space()
528
Ambient quadratic space of dimension 4 over Rational Field
529
Inner product matrix:
530
[ 2 0 0 0]
531
[ 0 6 0 0]
532
[ 0 0 -38 0]
533
[ 0 0 0 -114]
534
"""
535
try:
536
return self.__vector_space
537
except AttributeError:
538
V = VectorSpace(self.base_ring(), 4, inner_product_matrix = self.inner_product_matrix())
539
self.__vector_space = V
540
return V
541
542
543
class QuaternionAlgebra_ab(QuaternionAlgebra_abstract):
544
"""
545
The quaternion algebra of the form `(a, b/K)`, where `i^2=a`, `j^2 = b`,
546
and `j*i = -i*j`. ``K`` is a field not of characteristic 2 and ``a``,
547
``b`` are nonzero elements of ``K``.
548
549
See ``QuaternionAlgebra`` for many more examples.
550
551
EXAMPLES::
552
553
sage: QuaternionAlgebra(QQ, -7, -21) # indirect doctest
554
Quaternion Algebra (-7, -21) with base ring Rational Field
555
"""
556
def __init__(self, base_ring, a, b, names='i,j,k'):
557
"""
558
Create the quaternion algebra with `i^2 = a`, `j^2 = b`, and
559
`i*j = -j*i = k`.
560
561
INPUT:
562
563
- ``base_ring`` - commutative ring
564
- ``a, b`` - elements of ``base_ring``
565
- ``names`` - string (optional, default 'i, j, k') names of the generators
566
567
TESTS:
568
569
Test making quaternion elements (using the element constructor)::
570
571
sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-1,-2)
572
sage: a = Q(2/3); a
573
2/3
574
sage: type(a)
575
<type 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>
576
sage: Q(a)
577
2/3
578
sage: Q([1,2,3,4])
579
1 + 2*i + 3*j + 4*k
580
sage: Q((1,2,3,4))
581
1 + 2*i + 3*j + 4*k
582
sage: Q(-3/5)
583
-3/5
584
585
The base ring must be a field::
586
587
sage: Q.<ii,jj,kk> = QuaternionAlgebra(ZZ,-5,-19)
588
Traceback (most recent call last):
589
...
590
TypeError: base ring of quaternion algebra must be a field
591
"""
592
ParentWithGens.__init__(self, base_ring, names=names)
593
self._a = a
594
self._b = b
595
if base_ring not in _Fields:
596
raise TypeError("base ring of quaternion algebra must be a field")
597
if is_RationalField(base_ring) and a.denominator() == 1 and b.denominator() == 1:
598
element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_rational_field
599
elif is_NumberField(base_ring) and base_ring.degree() > 2 and base_ring.is_absolute() and \
600
a.denominator() == 1 and b.denominator() == 1 and base_ring.defining_polynomial().is_monic():
601
# This QuaternionAlgebraElement_number_field class is not
602
# designed to work with elements of a quadratic field. To
603
# do that, the main thing would be to implement
604
# __getitem__, etc. This would maybe give a factor of 2
605
# (or more?) speedup. Much care must be taken because the
606
# underlying representation of quadratic fields is a bit
607
# tricky.
608
element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_number_field
609
else:
610
element_constructor = quaternion_algebra_element.QuaternionAlgebraElement_generic
611
self._populate_coercion_lists_(coerce_list=[base_ring], element_constructor=element_constructor)
612
self._gens = [self([0,1,0,0]), self([0,0,1,0]), self([0,0,0,1])]
613
614
def maximal_order(self, take_shortcuts = True):
615
"""
616
Return a maximal order in this quaternion algebra.
617
618
The algorithm used is from [Voi2012].
619
620
INPUT:
621
622
- ``take_shortcuts`` - (default: True) If the discriminant is prime and the invariants of the algebra are of a nice form, use Proposition 5.2 of [Pizer, 1980].
623
624
OUTPUT: a maximal order in this quaternion algebra
625
626
EXAMPLES:
627
628
::
629
630
sage: QuaternionAlgebra(-1,-7).maximal_order()
631
Order 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)
632
633
sage: QuaternionAlgebra(-1,-1).maximal_order().basis()
634
(1/2 + 1/2*i + 1/2*j + 1/2*k, i, j, k)
635
636
sage: QuaternionAlgebra(-1,-11).maximal_order().basis()
637
(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
638
639
sage: QuaternionAlgebra(-1,-3).maximal_order().basis()
640
(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
641
642
sage: QuaternionAlgebra(-3,-1).maximal_order().basis()
643
(1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
644
645
sage: QuaternionAlgebra(-2,-5).maximal_order().basis()
646
(1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)
647
648
sage: QuaternionAlgebra(-5,-2).maximal_order().basis()
649
(1/2 + 1/2*i - 1/2*k, 1/2*i + 1/4*j - 1/4*k, i, -k)
650
651
sage: QuaternionAlgebra(-17,-3).maximal_order().basis()
652
(1/2 + 1/2*j, 1/2*i + 1/2*k, -1/3*j - 1/3*k, k)
653
654
sage: QuaternionAlgebra(-3,-17).maximal_order().basis()
655
(1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k)
656
657
sage: QuaternionAlgebra(-17*9,-3).maximal_order().basis()
658
(1, 1/3*i, 1/6*i + 1/2*j, 1/2 + 1/3*j + 1/18*k)
659
660
sage: QuaternionAlgebra(-2, -389).maximal_order().basis()
661
(1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)
662
663
If you want bases containing 1, switch off ``take_shortcuts``:
664
665
::
666
667
sage: QuaternionAlgebra(-3,-89).maximal_order(take_shortcuts=False)
668
Order of Quaternion Algebra (-3, -89) with base ring Rational Field with basis (1, 1/2 + 1/2*i, j, 1/2 + 1/6*i + 1/2*j + 1/6*k)
669
670
sage: QuaternionAlgebra(1,1).maximal_order(take_shortcuts=False) # Matrix ring
671
Order of Quaternion Algebra (1, 1) with base ring Rational Field with basis (1, 1/2 + 1/2*i, j, 1/2*j + 1/2*k)
672
673
sage: QuaternionAlgebra(-22,210).maximal_order(take_shortcuts=False)
674
Order of Quaternion Algebra (-22, 210) with base ring Rational Field with basis (1, i, 1/2*i + 1/2*j, 1/2 + 17/22*i + 1/44*k)
675
676
sage: for d in ( m for m in range(1, 750) if is_squarefree(m) ): # long time (3s)
677
... A = QuaternionAlgebra(d)
678
... R = A.maximal_order(take_shortcuts=False)
679
... assert A.discriminant() == R.discriminant()
680
681
We don't support number fields other than the rationals yet:
682
683
::
684
685
sage: K = QuadraticField(5)
686
sage: QuaternionAlgebra(K,-1,-1).maximal_order()
687
Traceback (most recent call last):
688
...
689
NotImplementedError: maximal order only implemented for rational quaternion algebras
690
691
692
REFERENCES:
693
694
.. [Piz1980] A. Pizer. An Algorithm for Computing Modular Forms on `\\Gamma_0(N)`,
695
J. Algebra 64 (1980), 340-390.
696
697
.. [Voi2012] J. Voight. Identifying the matrix ring: algorithms
698
for quaternion algebras and quadratic forms, to appear.
699
"""
700
try: return self.__maximal_order
701
except AttributeError: pass
702
703
if self.base_ring() != QQ:
704
raise NotImplementedError("maximal order only implemented for rational quaternion algebras")
705
706
d_A = self.discriminant()
707
708
# The following only works over QQ if the discriminant is prime
709
# and if the invariants are of the special form
710
# (every quaternion algebra of prime discriminant has a representation
711
# of such a form though)
712
a, b = self.invariants()
713
if take_shortcuts and d_A.is_prime() and a in ZZ and b in ZZ:
714
a = ZZ(a)
715
b = ZZ(b)
716
i,j,k = self.gens()
717
718
# if necessary, try to swap invariants to match Pizer's paper
719
if (a != -1 and b == -1) or (b == -2) \
720
or (a != -1 and a != -2 and (-a) % 8 != 1):
721
a, b = b, a
722
i, j = j, i
723
k = i*j
724
725
basis = []
726
if (a,b) == (-1,-1):
727
basis = [(1+i+j+k)/2, i, j, k]
728
elif a == -1 and (-b).is_prime() and ((-b) % 4 == 3):
729
basis = [(1+j)/2, (i+k)/2, j, k]
730
elif a == -2 and (-b).is_prime() and ((-b) % 8 == 5):
731
basis = [(1+j+k)/2, (i+2*j+k)/4, j, k]
732
elif (-a).is_prime() and (-b).is_prime():
733
q = -b
734
p = -a
735
736
if q % 4 == 3 and kronecker_symbol(p,q) == -1:
737
a = 0
738
while (a*a*p + 1)%q != 0:
739
a += 1
740
basis = [(1+j)/2, (i+k)/2, -(j+a*k)/q, k]
741
742
if basis:
743
self.__maximal_order = self.quaternion_order(basis)
744
return self.__maximal_order
745
746
# The following code should always work (over QQ)
747
# Start with <1,i,j,k>
748
R = self.quaternion_order([1] + self.gens())
749
d_R = R.discriminant()
750
751
e_new_gens = []
752
753
# For each prime at which R is not yet maximal, make it bigger
754
for (p,p_val) in d_R.factor():
755
e = R.basis()
756
while self.quaternion_order(e).discriminant().valuation(p) > d_A.valuation(p):
757
# Compute a normalized basis at p
758
f = normalize_basis_at_p(list(e), p)
759
760
# Ensure the basis lies in R by clearing denominators
761
# (this may make the order smaller at q != p)
762
# Also saturate the basis (divide out p as far as possible)
763
V = self.base_ring()**4
764
A = matrix(self.base_ring(), 4, 4, [ list(g) for g in e ]);
765
766
e_n = []
767
x_rows = A.solve_left(matrix([ V(vec.coefficient_tuple()) for (vec,val) in f ]), check=False).rows()
768
denoms = [ x.denominator() for x in x_rows ]
769
for i in range(4):
770
vec = f[i][0]
771
val = f[i][1]
772
773
v = (val/2).floor()
774
e_n.append(denoms[i] / p**(v) * vec)
775
776
# for e_n to become p-saturated we still need to sort by
777
# ascending valuation of the quadratic form
778
lst = sorted(zip(e_n, [f[m][1].mod(2) for m in range(4)]),
779
key = itemgetter(1))
780
e_n = list(zip(*lst)[0])
781
782
# Final step: Enlarge the basis at p
783
if p != 2:
784
# ensure that v_p(e_n[1]**2) = 0 by swapping basis elements
785
if ZZ(e_n[1]**2).valuation(p) != 0:
786
if ZZ(e_n[2]**2).valuation(p) == 0:
787
e_n[1], e_n[2] = e_n[2], e_n[1]
788
else:
789
e_n[1], e_n[3] = e_n[3], e_n[1]
790
791
a = ZZ(e_n[1]**2)
792
b = ZZ(e_n[2]**2)
793
794
if b.valuation(p) > 0: # if v_p(b) = 0, then already p-maximal
795
F = ZZ.quo(p)
796
if F(a).is_square():
797
x = F(a).sqrt().lift()
798
if (x**2 - a).mod(p**2) == 0: # make sure v_p(x**2 - a) = 1
799
x = x + p
800
g = 1/p*(x - e_n[1])*e_n[2]
801
e_n[2] = g
802
e_n[3] = e_n[1]*g
803
804
else: # p == 2
805
t = e_n[1].reduced_trace()
806
a = -e_n[1].reduced_norm()
807
b = ZZ(e_n[2]**2)
808
809
if t.valuation(p) == 0:
810
if b.valuation(p) > 0:
811
x = a
812
if (x**2 - t*x + a).mod(p**2) == 0: # make sure v_p(...) = 1
813
x = x + p
814
g = 1/p*(x - e_n[1])*e_n[2]
815
e_n[2] = g
816
e_n[3] = e_n[1]*g
817
818
else: # t.valuation(p) > 0
819
(y,z,w) = maxord_solve_aux_eq(a, b, p)
820
g = 1/p*(1 + y*e_n[1] + z*e_n[2] + w*e_n[1]*e_n[2])
821
h = (z*b)*e_n[1] - (y*a)*e_n[2]
822
e_n[1:4] = [g,h,g*h]
823
if (1 - a*y**2 - b*z**2 + a*b*w**2).valuation(2) > 2:
824
e_n = basis_for_quaternion_lattice(list(e) + e_n[1:], reverse=True)
825
826
# e_n now contains elements that locally at p give a bigger order,
827
# but the basis may be messed up at other primes (it might not even
828
# be an order). We will join them all together at the end
829
e = e_n
830
831
e_new_gens.extend(e[1:])
832
833
e_new = basis_for_quaternion_lattice(list(R.basis()) + e_new_gens, reverse=True)
834
self.__maximal_order = self.quaternion_order(e_new)
835
return self.__maximal_order
836
837
838
def invariants(self):
839
"""
840
Return the structural invariants `a`, `b` of this quaternion
841
algebra: ``self`` is generated by `i`, `j` subject to
842
`i^2 = a`, `j^2 = b` and `j*i = -i*j`.
843
844
EXAMPLES::
845
846
sage: Q.<i,j,k> = QuaternionAlgebra(15)
847
sage: Q.invariants()
848
(-3, 5)
849
sage: i^2
850
-3
851
sage: j^2
852
5
853
"""
854
return self._a, self._b
855
856
def __cmp__(self, other):
857
"""
858
Compare self and other.
859
860
EXAMPLES::
861
862
sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-7))
863
0
864
sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-5))
865
-1
866
sage: cmp(QuaternionAlgebra(-1,-7), QuaternionAlgebra(-1,-10))
867
1
868
"""
869
if not isinstance(other, QuaternionAlgebra_abstract):
870
return cmp(type(self), type(other))
871
c = cmp(self.base_ring(), other.base_ring())
872
if c: return c
873
return cmp((self._a, self._b), (other._a, other._b))
874
875
def __reduce__(self):
876
"""
877
Internal method used for pickling.
878
879
TESTS::
880
881
sage: QuaternionAlgebra(QQ,-1,-2).__reduce__()
882
(<function unpickle_QuaternionAlgebra_v0 at ...>, (Rational Field, -1, -2, ('i', 'j', 'k')))
883
884
Test uniqueness of parent::
885
886
sage: Q = QuaternionAlgebra(QQ,-1,-2)
887
sage: loads(dumps(Q)) is Q
888
True
889
"""
890
return unpickle_QuaternionAlgebra_v0, self._key
891
892
def gen(self, i=0):
893
"""
894
Return the `i^{th}` generator of ``self``.
895
896
INPUT:
897
898
- ``i`` - integer (optional, default 0)
899
900
EXAMPLES::
901
902
sage: Q.<ii,jj,kk> = QuaternionAlgebra(QQ,-1,-2); Q
903
Quaternion Algebra (-1, -2) with base ring Rational Field
904
sage: Q.gen(0)
905
ii
906
sage: Q.gen(1)
907
jj
908
sage: Q.gen(2)
909
kk
910
sage: Q.gens()
911
[ii, jj, kk]
912
"""
913
return self._gens[i]
914
915
def _repr_(self):
916
"""
917
Print representation.
918
919
TESTS::
920
921
sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
922
sage: type(Q)
923
<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_ab'>
924
sage: Q._repr_()
925
'Quaternion Algebra (-5, -2) with base ring Rational Field'
926
sage: Q
927
Quaternion Algebra (-5, -2) with base ring Rational Field
928
sage: print Q
929
Quaternion Algebra (-5, -2) with base ring Rational Field
930
sage: str(Q)
931
'Quaternion Algebra (-5, -2) with base ring Rational Field'
932
"""
933
return "Quaternion Algebra (%r, %r) with base ring %s"%(self._a, self._b, self.base_ring())
934
935
def inner_product_matrix(self):
936
"""
937
Return the inner product matrix associated to ``self``, i.e. the
938
Gram matrix of the reduced norm as a quadratic form on ``self``.
939
The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix
940
is just the diagonal matrix with diagonal entries `1`, `a`, `b`, `ab`.
941
942
EXAMPLES::
943
944
sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)
945
sage: Q.inner_product_matrix()
946
[ 2 0 0 0]
947
[ 0 10 0 0]
948
[ 0 0 38 0]
949
[ 0 0 0 190]
950
951
sage: R.<a,b> = QQ[]; Q.<i,j,k> = QuaternionAlgebra(Frac(R),a,b)
952
sage: Q.inner_product_matrix()
953
[ 2 0 0 0]
954
[ 0 -2*a 0 0]
955
[ 0 0 -2*b 0]
956
[ 0 0 0 2*a*b]
957
"""
958
a, b = self._a, self._b
959
return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b])
960
961
def discriminant(self):
962
"""
963
Given a quaternion algebra `A` defined over a number field,
964
return the discriminant of `A`, i.e. the
965
product of the ramified primes of `A`.
966
967
EXAMPLES::
968
969
sage: QuaternionAlgebra(210,-22).discriminant()
970
210
971
sage: QuaternionAlgebra(19).discriminant()
972
19
973
974
sage: F.<a> = NumberField(x^2-x-1)
975
sage: B.<i,j,k> = QuaternionAlgebra(F, 2*a,F(-1))
976
sage: B.discriminant()
977
Fractional ideal (2)
978
979
sage: QuaternionAlgebra(QQ[sqrt(2)],3,19).discriminant()
980
Fractional ideal (1)
981
"""
982
try: return self.__discriminant
983
except AttributeError: pass
984
if not is_RationalField(self.base_ring()):
985
try:
986
F = self.base_ring()
987
self.__discriminant = F.hilbert_conductor(self._a,self._b)
988
except NotImplementedError: raise "base field must be rational numbers or number field"
989
else:
990
self.__discriminant = hilbert_conductor(self._a, self._b)
991
return self.__discriminant
992
993
def ramified_primes(self):
994
"""
995
Return the primes that ramify in this quaternion algebra. Currently
996
only implemented over the rational numbers.
997
998
EXAMPLES::
999
1000
sage: QuaternionAlgebra(QQ, -1, -1).ramified_primes()
1001
[2]
1002
"""
1003
#TODO: more examples
1004
1005
return [f[0] for f in factor(self.discriminant())]
1006
1007
def _magma_init_(self, magma):
1008
"""
1009
Return Magma version of this quaternion algebra.
1010
1011
EXAMPLES::
1012
1013
sage: Q = QuaternionAlgebra(-1,-1); Q
1014
Quaternion Algebra (-1, -1) with base ring Rational Field
1015
sage: Q._magma_init_(magma) # optional - magma
1016
'QuaternionAlgebra(_sage_[...],-1/1,-1/1)'
1017
sage: A = magma(Q); A # optional - magma
1018
Quaternion Algebra with base ring Rational Field, defined by i^2 = -1, j^2 = -1
1019
sage: A.RamifiedPlaces() # optional - magma
1020
[
1021
Ideal of Integer Ring generated by 2
1022
]
1023
1024
A more complicated example involving a quaternion algebra over a number field::
1025
1026
sage: K.<a> = QQ[sqrt(2)]; Q = QuaternionAlgebra(K,-1,a); Q
1027
Quaternion Algebra (-1, sqrt2) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2
1028
sage: magma(Q) # optional - magma
1029
Quaternion Algebra with base ring Number Field with defining polynomial x^2 - 2 over the Rational Field, defined by i^2 = -1, j^2 = sqrt2
1030
sage: Q._magma_init_(magma) # optional - magma
1031
'QuaternionAlgebra(_sage_[...],(_sage_[...]![-1, 0]),(_sage_[...]![0, 1]))'
1032
"""
1033
R = magma(self.base_ring())
1034
return 'QuaternionAlgebra(%s,%s,%s)'%(R.name(),
1035
self._a._magma_init_(magma),
1036
self._b._magma_init_(magma))
1037
1038
def quaternion_order(self, basis, check=True):
1039
"""
1040
Return the order of this quaternion order with given basis.
1041
1042
INPUT:
1043
1044
- ``basis`` - list of 4 elements of ``self``
1045
- ``check`` - bool (default: ``True``)
1046
1047
EXAMPLES::
1048
1049
sage: Q.<i,j,k> = QuaternionAlgebra(-11,-1)
1050
sage: Q.quaternion_order([1,i,j,k])
1051
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1, i, j, k)
1052
1053
We test out ``check=False``::
1054
1055
sage: Q.quaternion_order([1,i,j,k], check=False)
1056
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis [1, i, j, k]
1057
sage: Q.quaternion_order([i,j,k], check=False)
1058
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis [i, j, k]
1059
"""
1060
return QuaternionOrder(self, basis, check=check)
1061
1062
def ideal(self, gens, left_order=None, right_order=None, check=True, **kwds):
1063
r"""
1064
Return the quaternion ideal with given gens over `\ZZ`.
1065
Neither a left or right order structure need be specified.
1066
1067
INPUT:
1068
1069
- ``gens`` -- a list of elements of this quaternion order
1070
1071
- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must
1072
4-tuple that forms a Hermite basis for an ideal
1073
1074
- ``left_order`` -- a quaternion order or ``None``
1075
1076
- ``right_order`` -- a quaternion order or ``None``
1077
1078
EXAMPLES::
1079
1080
sage: R = QuaternionAlgebra(-11,-1)
1081
sage: R.ideal([2*a for a in R.basis()])
1082
Fractional ideal (2, 2*i, 2*j, 2*k)
1083
"""
1084
if self.base_ring() == QQ:
1085
return QuaternionFractionalIdeal_rational(gens, left_order=left_order, right_order=right_order, check=check)
1086
else:
1087
raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
1088
1089
@cached_method
1090
def modp_splitting_data(self, p):
1091
r"""
1092
Return mod `p` splitting data for this quaternion algebra at
1093
the unramified prime `p`. This is `2\times 2`
1094
matrices `I`, `J`, `K` over the finite field `\GF{p}` such that if
1095
the quaternion algebra has generators `i, j, k`, then `I^2 =
1096
i^2`, `J^2 = j^2`, `IJ=K` and `IJ=-JI`.
1097
1098
.. note::
1099
1100
Currently only implemented when `p` is odd and the base
1101
ring is `\QQ`.
1102
1103
INPUT:
1104
1105
- `p` -- unramified odd prime
1106
1107
OUTPUT:
1108
1109
- 2-tuple of matrices over finite field
1110
1111
EXAMPLES::
1112
1113
sage: Q = QuaternionAlgebra(-15, -19)
1114
sage: Q.modp_splitting_data(7)
1115
(
1116
[0 6] [6 1] [6 6]
1117
[1 0], [1 1], [6 1]
1118
)
1119
sage: Q.modp_splitting_data(next_prime(10^5))
1120
(
1121
[ 0 99988] [97311 4] [99999 59623]
1122
[ 1 0], [13334 2692], [97311 4]
1123
)
1124
sage: I,J,K = Q.modp_splitting_data(23)
1125
sage: I
1126
[0 8]
1127
[1 0]
1128
sage: I^2
1129
[8 0]
1130
[0 8]
1131
sage: J
1132
[19 2]
1133
[17 4]
1134
sage: J^2
1135
[4 0]
1136
[0 4]
1137
sage: I*J == -J*I
1138
True
1139
sage: I*J == K
1140
True
1141
1142
The following is a good test because of the asserts in the code::
1143
1144
sage: v = [Q.modp_splitting_data(p) for p in primes(20,1000)]
1145
1146
1147
Proper error handling::
1148
1149
sage: Q.modp_splitting_data(5)
1150
Traceback (most recent call last):
1151
...
1152
NotImplementedError: algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)
1153
1154
sage: Q.modp_splitting_data(2)
1155
Traceback (most recent call last):
1156
...
1157
NotImplementedError: p must be odd
1158
"""
1159
if self.base_ring() != QQ:
1160
raise NotImplementedError("must be rational quaternion algebra")
1161
p = ZZ(p)
1162
if not p.is_prime():
1163
raise ValueError("p (=%s) must be prime"%p)
1164
if p == 2:
1165
raise NotImplementedError("p must be odd")
1166
if self.discriminant() % p == 0:
1167
raise ValueError("p (=%s) must be an unramified prime"%p)
1168
1169
i, j, k = self.gens()
1170
F = GF(p)
1171
i2 = F(i*i)
1172
j2 = F(j*j)
1173
1174
M = MatrixSpace(F, 2)
1175
I = M([0,i2,1,0])
1176
if i2 == 0:
1177
raise NotImplementedError("algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)")
1178
i2inv = 1/i2
1179
a = None
1180
for b in list(F):
1181
if not b: continue
1182
c = j2 + i2inv * b*b
1183
if c.is_square():
1184
a = -c.sqrt()
1185
break
1186
1187
if a is None:
1188
# do a fallback search, maybe needed in char 3 sometimes.
1189
for J in M:
1190
K = I*J
1191
if J*J == j2 and K == -J*I:
1192
return I, J, K
1193
1194
J = M([a,b,(j2-a*a)/b, -a])
1195
K = I*J
1196
assert K == -J*I, "bug in that I,J don't skew commute"
1197
return I, J, K
1198
1199
def modp_splitting_map(self, p):
1200
r"""
1201
Return Python map from the (`p`-integral) quaternion algebra to
1202
the set of `2\times 2` matrices over `\GF{p}`.
1203
1204
INPUT:
1205
1206
- `p` -- prime number
1207
1208
EXAMPLES::
1209
1210
sage: Q.<i,j,k> = QuaternionAlgebra(-1, -7)
1211
sage: f = Q.modp_splitting_map(13)
1212
sage: a = 2+i-j+3*k; b = 7+2*i-4*j+k
1213
sage: f(a*b)
1214
[12 3]
1215
[10 5]
1216
sage: f(a)*f(b)
1217
[12 3]
1218
[10 5]
1219
"""
1220
I, J, K = self.modp_splitting_data(p)
1221
F = I.base_ring()
1222
def phi(q):
1223
v = [F(a) for a in q.coefficient_tuple()]
1224
return v[0] + I*v[1] + J*v[2] + K*v[3]
1225
return phi
1226
1227
1228
############################################################
1229
# Unpickling
1230
############################################################
1231
def unpickle_QuaternionAlgebra_v0(*key):
1232
"""
1233
The 0th version of pickling for quaternion algebras.
1234
1235
EXAMPLES::
1236
1237
sage: Q = QuaternionAlgebra(-5,-19)
1238
sage: f, t = Q.__reduce__()
1239
sage: sage.algebras.quatalg.quaternion_algebra.unpickle_QuaternionAlgebra_v0(*t)
1240
Quaternion Algebra (-5, -19) with base ring Rational Field
1241
sage: loads(dumps(Q)) == Q
1242
True
1243
sage: loads(dumps(Q)) is Q
1244
True
1245
"""
1246
return QuaternionAlgebra(*key)
1247
1248
1249
class QuaternionOrder(Algebra):
1250
"""
1251
An order in a quaternion algebra.
1252
1253
EXAMPLES::
1254
1255
sage: QuaternionAlgebra(-1,-7).maximal_order()
1256
Order 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)
1257
sage: type(QuaternionAlgebra(-1,-7).maximal_order())
1258
<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder'>
1259
"""
1260
def __init__(self, A, basis, check=True):
1261
"""
1262
INPUT:
1263
1264
- ``A`` - a quaternion algebra
1265
- ``basis`` - list of 4 integral quaternions in ``A``
1266
- ``check`` - whether to do type and other consistency checks
1267
1268
.. WARNING::
1269
1270
Currently most methods silently assume that the A.base_ring() is QQ
1271
1272
EXAMPLES::
1273
1274
sage: A.<i,j,k> = QuaternionAlgebra(-3,-5)
1275
sage: sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,i,j,k])
1276
Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, i, j, k)
1277
sage: R = sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,2*i,2*j,2*k]); R
1278
Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, 2*i, 2*j, 2*k)
1279
sage: type(R)
1280
<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder'>
1281
1282
Over QQ and number fields it is checked whether the given
1283
basis actually gives a an order (as a module over the maximal order):
1284
1285
sage: A.<i,j,k> = QuaternionAlgebra(-1,-1)
1286
sage: A.quaternion_order([1,i,j,i-j])
1287
Traceback (most recent call last):
1288
...
1289
ValueError: basis must have rank 4
1290
sage: A.quaternion_order([2,i,j,k])
1291
Traceback (most recent call last):
1292
...
1293
ValueError: lattice must contain 1
1294
sage: A.quaternion_order([1,i/2,j/2,k/2])
1295
Traceback (most recent call last):
1296
...
1297
ValueError: given lattice must be a ring
1298
1299
sage: K = QuadraticField(10)
1300
sage: A.<i,j,k> = QuaternionAlgebra(K,-1,-1)
1301
sage: A.quaternion_order([1,i,j,k])
1302
Order of Quaternion Algebra (-1, -1) with base ring Number Field in a with defining polynomial x^2 - 10 with basis (1, i, j, k)
1303
sage: A.quaternion_order([1,i/2,j,k])
1304
Traceback (most recent call last):
1305
...
1306
ValueError: given lattice must be a ring
1307
"""
1308
if check:
1309
# right data type
1310
if not isinstance(basis, (list, tuple)):
1311
raise TypeError("basis must be a list or tuple")
1312
# right length
1313
if len(basis) != 4:
1314
raise ValueError("basis must have length 4")
1315
# coerce to common parent
1316
basis = tuple([A(x) for x in basis])
1317
1318
# has rank 4
1319
V = A.base_ring()**4
1320
if V.span([ V(x.coefficient_tuple()) for x in basis]).dimension() != 4:
1321
raise ValueError, "basis must have rank 4"
1322
1323
# The additional checks will work over QQ and over number fields,
1324
# but we can't actually do much with an order defined over a number
1325
# field
1326
1327
if A.base_ring() == QQ: # fast code over QQ
1328
M = matrix(QQ, 4, 4, [ x.coefficient_tuple() for x in basis])
1329
v = M.solve_left(V([1,0,0,0]))
1330
1331
if v.denominator() != 1:
1332
raise ValueError, "lattice must contain 1"
1333
1334
# check if multiplicatively closed
1335
M1 = basis_for_quaternion_lattice(basis)
1336
M2 = basis_for_quaternion_lattice(list(basis) + [ x*y for x in basis for y in basis])
1337
if M1 != M2:
1338
raise ValueError, "given lattice must be a ring"
1339
1340
if A.base_ring() != QQ: # slow code over number fields (should eventually use PARI's nfhnf)
1341
O = None
1342
try:
1343
O = A.base_ring().maximal_order()
1344
except AttributeError:
1345
pass
1346
1347
if O:
1348
M = matrix(A.base_ring(), 4, 4, [ x.coefficient_tuple() for x in basis])
1349
v = M.solve_left(V([1,0,0,0]))
1350
1351
if any([ not a in O for a in v]):
1352
raise ValueError, "lattice must contain 1"
1353
1354
# check if multiplicatively closed
1355
Y = matrix(QQ, 16, 4, [ (x*y).coefficient_tuple() for x in basis for y in basis])
1356
X = M.solve_left(Y)
1357
if any([ not a in O for x in X for a in x ]):
1358
raise ValueError, "given lattice must be a ring"
1359
1360
self.__basis = basis
1361
self.__quaternion_algebra = A
1362
ParentWithGens.__init__(self, ZZ, names=None)
1363
1364
def gens(self):
1365
"""
1366
Return generators for self.
1367
1368
EXAMPLES::
1369
1370
sage: QuaternionAlgebra(-1,-7).maximal_order().gens()
1371
(1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
1372
"""
1373
return self.__basis
1374
1375
def ngens(self):
1376
"""
1377
Return the number of generators (which is 4).
1378
1379
EXAMPLES::
1380
1381
sage: QuaternionAlgebra(-1,-7).maximal_order().ngens()
1382
4
1383
"""
1384
return 4
1385
1386
def gen(self, n):
1387
"""
1388
Return the n-th generator.
1389
1390
INPUT:
1391
1392
- ``n`` - an integer between 0 and 3, inclusive.
1393
1394
EXAMPLES::
1395
1396
sage: R = QuaternionAlgebra(-11,-1).maximal_order(); R
1397
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1398
sage: R.gen(0)
1399
1/2 + 1/2*i
1400
sage: R.gen(1)
1401
1/2*j - 1/2*k
1402
sage: R.gen(2)
1403
i
1404
sage: R.gen(3)
1405
-k
1406
"""
1407
return self.__basis[n]
1408
1409
def __cmp__(self, R):
1410
"""
1411
Compare orders self and other. Two orders are equal if they
1412
have the same basis and are in the same quaternion algebra.
1413
1414
EXAMPLES::
1415
1416
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1417
sage: R == R # indirect doctest
1418
True
1419
sage: R == QuaternionAlgebra(-1,-1).maximal_order()
1420
False
1421
sage: R==5
1422
False
1423
"""
1424
if not isinstance(R, QuaternionOrder):
1425
return cmp(type(self), type(R))
1426
c = cmp(self.__quaternion_algebra, R.__quaternion_algebra)
1427
if c: return c
1428
return cmp(self.__basis, R.__basis)
1429
1430
1431
def basis(self):
1432
"""
1433
Return fix choice of basis for this quaternion order.
1434
1435
EXAMPLES::
1436
1437
sage: QuaternionAlgebra(-11,-1).maximal_order().basis()
1438
(1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1439
"""
1440
return self.__basis
1441
1442
def quaternion_algebra(self):
1443
"""
1444
Return ambient quaternion algebra that contains this quaternion order.
1445
1446
EXAMPLES::
1447
1448
sage: QuaternionAlgebra(-11,-1).maximal_order().quaternion_algebra()
1449
Quaternion Algebra (-11, -1) with base ring Rational Field
1450
"""
1451
return self.__quaternion_algebra
1452
1453
def _repr_(self):
1454
"""
1455
Return string representation of this order.
1456
1457
EXAMPLES::
1458
1459
sage: QuaternionAlgebra(-11,-1).maximal_order()._repr_()
1460
'Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)'
1461
sage: QuaternionAlgebra(-11,-1).maximal_order()
1462
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1463
"""
1464
return 'Order of %s with basis %s'%(self.quaternion_algebra(), self.basis())
1465
1466
def random_element(self, *args, **kwds):
1467
"""
1468
Return a random element of this order.
1469
1470
The args and kwds are passed to the random_element method of
1471
the integer ring, and we return an element of the form
1472
1473
.. math::
1474
1475
ae_1 + be_2 + ce_3 + de_4
1476
1477
where `e_1`, ..., `e_4` are the basis of this order and `a`,
1478
`b`, `c`, `d` are random integers.
1479
1480
EXAMPLES::
1481
1482
sage: QuaternionAlgebra(-11,-1).maximal_order().random_element()
1483
-4 - 4*i + j - k
1484
sage: QuaternionAlgebra(-11,-1).maximal_order().random_element(-10,10)
1485
-9/2 - 7/2*i - 7/2*j - 3/2*k
1486
"""
1487
return sum( (ZZ.random_element(*args, **kwds) * b for b in self.basis()) )
1488
1489
def intersection(self, other):
1490
"""
1491
Return the intersection of this order with other.
1492
1493
INPUT:
1494
1495
- ``other`` - a quaternion order in the same ambient quaternion algebra
1496
1497
OUTPUT: a quaternion order
1498
1499
EXAMPLES::
1500
1501
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1502
sage: R.intersection(R)
1503
Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, i, 1/2*j + 1/2*k, k)
1504
1505
We intersect various orders in the quaternion algebra ramified at 11::
1506
1507
sage: B = BrandtModule(11,3)
1508
sage: R = B.maximal_order(); S = B.order_of_level_N()
1509
sage: R.intersection(S)
1510
Order 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)
1511
sage: R.intersection(S) == S
1512
True
1513
sage: B = BrandtModule(11,5)
1514
sage: T = B.order_of_level_N()
1515
sage: S.intersection(T)
1516
Order 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)
1517
"""
1518
if not isinstance(other, QuaternionOrder):
1519
raise TypeError("other must be a QuaternionOrder")
1520
1521
A = self.quaternion_algebra()
1522
if other.quaternion_algebra() != A:
1523
raise ValueError("self and other must be in the same ambient quaternion algebra")
1524
1525
V = A.base_ring()**4
1526
1527
B = V.span([V(list(g)) for g in self.basis()], ZZ)
1528
C = V.span([V(list(g)) for g in other.basis()], ZZ)
1529
1530
# todo -- A(list(e)) could be A(e)
1531
return QuaternionOrder(A, [A(list(e)) for e in B.intersection(C).basis()])
1532
1533
def free_module(self):
1534
"""
1535
Return the free `\\ZZ`-module that corresponds to this order
1536
inside the vector space corresponding to the ambient
1537
quaternion algebra.
1538
1539
OUTPUT: a free `\\ZZ`-module of rank 4
1540
1541
EXAMPLES::
1542
1543
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1544
sage: R.basis()
1545
(1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1546
sage: R.free_module()
1547
Free module of degree 4 and rank 4 over Integer Ring
1548
Echelon basis matrix:
1549
[1/2 1/2 0 0]
1550
[ 0 1 0 0]
1551
[ 0 0 1/2 1/2]
1552
[ 0 0 0 1]
1553
"""
1554
try: return self.__free_module
1555
except AttributeError: pass
1556
V = self.quaternion_algebra().base_ring()**4
1557
M = V.span([V(list(g)) for g in self.basis()], ZZ)
1558
self.__free_module = M
1559
return M
1560
1561
def discriminant(self):
1562
r"""
1563
Return the discriminant of this order, which we define as
1564
`\sqrt{ det ( Tr(e_i \bar{e_j} ) ) }`, where `\{e_i\}` is the
1565
basis of the order.
1566
1567
OUTPUT: rational number
1568
1569
EXAMPLES::
1570
1571
sage: QuaternionAlgebra(-11,-1).maximal_order().discriminant()
1572
11
1573
sage: S = BrandtModule(11,5).order_of_level_N()
1574
sage: S.discriminant()
1575
55
1576
sage: type(S.discriminant())
1577
<type 'sage.rings.rational.Rational'>
1578
"""
1579
L = []
1580
for d in self.basis():
1581
MM = []
1582
for e in self.basis():
1583
MM.append( (d * e.conjugate()).reduced_trace() )
1584
L.append(MM)
1585
1586
return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt()
1587
1588
def left_ideal(self, gens, check=True):
1589
r"""
1590
Return the ideal with given gens over `\ZZ`.
1591
1592
INPUT:
1593
1594
- ``gens`` -- a list of elements of this quaternion order
1595
1596
- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must
1597
4-tuple that forms a Hermite basis for an ideal
1598
1599
EXAMPLES::
1600
1601
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1602
sage: R.left_ideal([2*a for a in R.basis()])
1603
Fractional ideal (1 + i, 2*i, j + k, 2*k)
1604
"""
1605
if self.base_ring() == ZZ:
1606
return QuaternionFractionalIdeal_rational(gens, left_order=self, check=check)
1607
else:
1608
raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
1609
1610
def right_ideal(self, gens, check=True):
1611
r"""
1612
Return the ideal with given gens over `\ZZ`.
1613
1614
INPUT:
1615
1616
- ``gens`` -- a list of elements of this quaternion order
1617
1618
- ``check`` -- bool (default: ``True``); if ``False``, then ``gens`` must
1619
4-tuple that forms a Hermite basis for an ideal
1620
1621
EXAMPLES::
1622
1623
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1624
sage: R.right_ideal([2*a for a in R.basis()])
1625
Fractional ideal (1 + i, 2*i, j + k, 2*k)
1626
"""
1627
if self.base_ring() == ZZ:
1628
return QuaternionFractionalIdeal_rational(gens, right_order=self, check=check)
1629
else:
1630
raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
1631
1632
def unit_ideal(self):
1633
"""
1634
Return the unit ideal in this quaternion order.
1635
1636
EXAMPLES::
1637
1638
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1639
sage: I = R.unit_ideal(); I
1640
Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1641
"""
1642
if self.base_ring() == ZZ:
1643
return QuaternionFractionalIdeal_rational(self.basis(), left_order=self, right_order=self, check=False)
1644
else:
1645
raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
1646
1647
def quadratic_form(self):
1648
"""
1649
Return the normalized quadratic form associated to this quaternion order.
1650
1651
OUTPUT: quadratic form
1652
1653
EXAMPLES::
1654
1655
sage: R = BrandtModule(11,13).order_of_level_N()
1656
sage: Q = R.quadratic_form(); Q
1657
Quadratic form in 4 variables over Rational Field with coefficients:
1658
[ 14 253 55 286 ]
1659
[ * 1455 506 3289 ]
1660
[ * * 55 572 ]
1661
[ * * * 1859 ]
1662
sage: Q.theta_series(10)
1663
1 + 2*q + 2*q^4 + 4*q^6 + 4*q^8 + 2*q^9 + O(q^10)
1664
"""
1665
return self.unit_ideal().quadratic_form()
1666
1667
def ternary_quadratic_form(self, include_basis=False):
1668
"""
1669
Return the ternary quadratic form associated to this order.
1670
1671
INPUT:
1672
1673
- ``include_basis`` -- bool (default: False), if True also
1674
return a basis for the dimension 3 subspace `G`
1675
1676
OUTPUT:
1677
1678
- QuadraticForm
1679
1680
- optional basis for dimension 3 subspace
1681
1682
This function computes the positive definition quadratic form
1683
obtained by letting G be the trace zero subspace of `\ZZ` +
1684
2* ``self``, which has rank 3, and restricting the pairing
1685
1686
(x,y) = (x.conjugate()*y).reduced_trace()
1687
1688
to `G`.
1689
1690
APPLICATIONS: Ternary quadratic forms associated to an order
1691
in a rational quaternion algebra are useful in computing with
1692
Gross points, in decided whether quaternion orders have
1693
embeddings from orders in quadratic imaginary fields, and in
1694
computing elements of the Kohnen plus subspace of modular
1695
forms of weight 3/2.
1696
1697
EXAMPLES::
1698
1699
sage: R = BrandtModule(11,13).order_of_level_N()
1700
sage: Q = R.ternary_quadratic_form(); Q
1701
Quadratic form in 3 variables over Rational Field with coefficients:
1702
[ 5820 1012 13156 ]
1703
[ * 55 1144 ]
1704
[ * * 7436 ]
1705
sage: factor(Q.disc())
1706
2^4 * 11^2 * 13^2
1707
1708
The following theta series is a modular form of weight 3/2 and level 4*11*13::
1709
1710
sage: Q.theta_series(100)
1711
1 + 2*q^23 + 2*q^55 + 2*q^56 + 2*q^75 + 4*q^92 + O(q^100)
1712
"""
1713
if self.base_ring() != ZZ:
1714
raise NotImplementedError("ternary quadratic form of order only implemented for quaternion algebras over QQ")
1715
1716
Q = self.quaternion_algebra()
1717
# 2*R + ZZ
1718
twoR = self.free_module().scale(2)
1719
A = twoR.ambient_module()
1720
Z = twoR.span( [Q(1).coefficient_tuple()], ZZ)
1721
S = twoR + Z
1722
# Now we intersect with the trace 0 submodule
1723
v = [b.reduced_trace() for b in Q.basis()]
1724
M = matrix(QQ,4,1,v)
1725
tr0 = M.kernel()
1726
G = tr0.intersection(S)
1727
B = [Q(a) for a in G.basis()]
1728
m = matrix(QQ,[[x.pair(y) for x in B] for y in B])
1729
from sage.quadratic_forms.quadratic_form import QuadraticForm
1730
Q = QuadraticForm(m)
1731
if include_basis:
1732
return Q, B
1733
else:
1734
return Q
1735
1736
class QuaternionFractionalIdeal(Ideal_fractional):
1737
pass
1738
1739
class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal):
1740
"""
1741
A fractional ideal in a rational quaternion algebra.
1742
"""
1743
def __init__(self, basis, left_order=None, right_order=None, check=True):
1744
"""
1745
INPUT:
1746
1747
- ``left_order`` -- a quaternion order or ``None``
1748
1749
- ``right_order`` -- a quaternion order or ``None``
1750
1751
- ``basis`` -- tuple of length 4 of elements in of ambient
1752
quaternion algebra whose `\\ZZ`-span is an ideal
1753
1754
- ``check`` -- bool (default: ``True``); if ``False``, do no type
1755
checking, and the input basis *must* be in Hermite form.
1756
1757
EXAMPLES::
1758
1759
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1760
sage: R.right_ideal(R.basis())
1761
Fractional ideal (1/2 + 1/2*i, i, 1/2*j + 1/2*k, k)
1762
sage: R.right_ideal(tuple(R.basis()), check=False)
1763
Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
1764
"""
1765
if check:
1766
if left_order is not None and not isinstance(left_order, QuaternionOrder):
1767
raise TypeError("left_order must be a quaternion order or None")
1768
if right_order is not None and not isinstance(right_order, QuaternionOrder):
1769
raise TypeError("right_order must be a quaternion order or None")
1770
if not isinstance(basis, (list, tuple)):
1771
raise TypeError("basis must be a list or tuple")
1772
1773
self.__left_order = left_order
1774
self.__right_order = right_order
1775
1776
if check:
1777
try:
1778
Q = self.quaternion_order().quaternion_algebra()
1779
except RuntimeError:
1780
Q = basis[0].parent()
1781
basis = tuple([Q(v) for v in
1782
(QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()])
1783
self.__basis = basis
1784
1785
def scale(self, alpha, left=False):
1786
r"""
1787
Scale the fractional ideal self by multiplying the basis by alpha.
1788
1789
INPUT:
1790
1791
- `\alpha` -- element of quaternion algebra
1792
1793
- ``left`` -- bool (default: False); if true multiply
1794
`\alpha` on the left, otherwise multiply `\alpha` on the
1795
right.
1796
1797
OUTPUT:
1798
1799
- a new fractional ideal
1800
1801
1802
EXAMPLES::
1803
1804
sage: B = BrandtModule(5,37); I = B.right_ideals()[0]; i,j,k = B.quaternion_algebra().gens(); I
1805
Fractional ideal (2 + 2*j + 106*k, i + 2*j + 105*k, 4*j + 64*k, 148*k)
1806
sage: I.scale(i)
1807
Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j]
1808
sage: I.scale(i, left=True)
1809
Fractional ideal [2*i - 212*j + 2*k, -2 - 210*j + 2*k, -128*j + 4*k, -296*j]
1810
sage: I.scale(i, left=False)
1811
Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j]
1812
sage: i * I.gens()[0]
1813
2*i - 212*j + 2*k
1814
sage: I.gens()[0] * i
1815
2*i + 212*j - 2*k
1816
"""
1817
1818
Q = self.quaternion_algebra()
1819
alpha = Q(alpha)
1820
if left:
1821
gens = [alpha*b for b in self.basis()]
1822
else:
1823
gens = [b*alpha for b in self.basis()]
1824
return Q.ideal(gens, left_order = self.__left_order,
1825
right_order = self.__right_order, check=False)
1826
1827
def quaternion_algebra(self):
1828
"""
1829
Return the ambient quaternion algebra that contains this fractional ideal.
1830
1831
OUTPUT: a quaternion algebra
1832
1833
EXAMPLES::
1834
1835
sage: I = BrandtModule(3,5).right_ideals()[1]; I
1836
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
1837
sage: I.quaternion_algebra()
1838
Quaternion Algebra (-1, -3) with base ring Rational Field
1839
"""
1840
try: return self.__quaternion_algebra
1841
except AttributeError: pass
1842
A = self.__basis[0].parent()
1843
self.__quaternion_algebra = A
1844
return A
1845
1846
def _compute_order(self, side='left'):
1847
r"""
1848
Used internally to compute either the left or right order
1849
associated to an ideal in a quaternion algebra. If
1850
action='right', compute the left order, and if action='left'
1851
compute the right order.
1852
1853
INPUT:
1854
1855
- ``side`` -- 'left' or 'right'
1856
1857
EXAMPLES::
1858
1859
sage: R.<i,j,k> = QuaternionAlgebra(-1,-11)
1860
sage: I = R.ideal([2 + 2*j + 140*k, 2*i + 4*j + 150*k, 8*j + 104*k, 152*k])
1861
sage: Ol = I._compute_order('left'); Ol
1862
Order 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)
1863
sage: Or = I._compute_order('right'); Or
1864
Order 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)
1865
sage: Ol.discriminant()
1866
209
1867
sage: Or.discriminant()
1868
209
1869
sage: I.left_order() == Ol
1870
True
1871
sage: I.right_order() == Or
1872
True
1873
1874
ALGORITHM: Let `b_1, b_2, b_3, b_3` be a basis for this
1875
fractional ideal `I`, and assume we want to compute the left
1876
order of `I` in the quaternion algebra `Q`. Then
1877
multiplication by `b_i` on the right defines a map `B_i:Q \to
1878
Q`. We have
1879
`R = B_1^{-1}(I) \cap B_2^{-1}(I) \cap B_3^{-1}(I)\cap B_4^{-1}(I).`
1880
This is because
1881
`B_n^{-1}(I) = \{\alpha \in Q : \alpha b_n \in I \},`
1882
and
1883
`R = \{\alpha \in Q : \alpha b_n \in I, n=1,2,3,4\}.`
1884
"""
1885
if side == 'left': action = 'right'
1886
elif side == 'right': action = 'left'
1887
else: ValueError, "side must be 'left' or 'right'"
1888
Q = self.quaternion_algebra()
1889
if Q.base_ring() != QQ:
1890
raise NotImplementedError("computation of left and right orders only implemented over QQ")
1891
M = [(~b).matrix(action=action) for b in self.basis()]
1892
B = self.basis_matrix()
1893
invs = [B*m for m in M]
1894
# Now intersect the row spans of each matrix in invs
1895
ISB = [Q(v) for v in intersection_of_row_modules_over_ZZ(invs).row_module(ZZ).basis()]
1896
return Q.quaternion_order(ISB)
1897
1898
def left_order(self):
1899
"""
1900
Return the left order associated to this fractional ideal.
1901
1902
OUTPUT: an order in a quaternion algebra
1903
1904
EXAMPLES::
1905
1906
sage: B = BrandtModule(11)
1907
sage: R = B.maximal_order()
1908
sage: I = R.unit_ideal()
1909
sage: I.left_order()
1910
Order 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)
1911
1912
We do a consistency check::
1913
1914
sage: B = BrandtModule(11,19); R = B.right_ideals()
1915
sage: print [r.left_order().discriminant() for r in R]
1916
[209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209]
1917
"""
1918
if self.__left_order is None:
1919
self.__left_order = self._compute_order(side='left')
1920
return self.__left_order
1921
1922
def right_order(self):
1923
"""
1924
Return the right order associated to this fractional ideal.
1925
1926
OUTPUT: an order in a quaternion algebra
1927
1928
EXAMPLES::
1929
1930
sage: I = BrandtModule(389).right_ideals()[1]; I
1931
Fractional ideal (2 + 6*j + 2*k, i + 2*j + k, 8*j, 8*k)
1932
sage: I.right_order()
1933
Order 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)
1934
sage: I.left_order()
1935
Order 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)
1936
1937
The following is a big consistency check. We take reps for
1938
all the right ideal classes of a certain order, take the
1939
corresponding left orders, then take ideals in the left orders
1940
and from those compute the right order again::
1941
1942
sage: B = BrandtModule(11,19); R = B.right_ideals()
1943
sage: O = [r.left_order() for r in R]
1944
sage: J = [O[i].left_ideal(R[i].basis()) for i in range(len(R))]
1945
sage: len(set(J))
1946
18
1947
sage: len(set([I.right_order() for I in J]))
1948
1
1949
sage: J[0].right_order() == B.order_of_level_N()
1950
True
1951
"""
1952
if self.__right_order is None:
1953
self.__right_order = self._compute_order(side='right')
1954
return self.__right_order
1955
1956
def __repr__(self):
1957
"""
1958
Return string representation of this quaternion fractional ideal.
1959
1960
EXAMPLES::
1961
1962
sage: I = BrandtModule(11).right_ideals()[1]
1963
sage: type(I)
1964
<class 'sage.algebras.quatalg.quaternion_algebra.QuaternionFractionalIdeal_rational'>
1965
sage: I.__repr__()
1966
'Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 2*k, 8*j, 8*k)'
1967
"""
1968
return 'Fractional ideal %s'%(self.gens(),)
1969
1970
def quaternion_order(self):
1971
"""
1972
Return the order for which this ideal is a left or right
1973
fractional ideal. If this ideal has both a left and right
1974
ideal structure, then the left order is returned. If it has
1975
neither structure, then an error is raised.
1976
1977
OUTPUT: QuaternionOrder
1978
1979
EXAMPLES::
1980
1981
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
1982
sage: R.unit_ideal().quaternion_order() is R
1983
True
1984
"""
1985
try: return self.__quaternion_order
1986
except AttributeError: pass
1987
if self.__left_order is not None:
1988
A = self.__left_order
1989
elif self.__right_order is not None:
1990
A = self.__right_order
1991
else:
1992
raise RuntimeError("unable to determine quaternion order of ideal without known order")
1993
self.__quaternion_order = A
1994
return A
1995
1996
def ring(self):
1997
"""
1998
Return ring that this is a fractional ideal for.
1999
2000
EXAMPLES::
2001
2002
sage: R = QuaternionAlgebra(-11,-1).maximal_order()
2003
sage: R.unit_ideal().ring() is R
2004
True
2005
"""
2006
return self.quaternion_order()
2007
2008
def basis(self):
2009
"""
2010
Return basis for this fractional ideal. The basis is in Hermite form.
2011
2012
OUTPUT: tuple
2013
2014
EXAMPLES::
2015
2016
sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis()
2017
(1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2018
"""
2019
return self.__basis
2020
2021
def gens(self):
2022
"""
2023
Return the generators for this ideal, which are the same as
2024
the `\\ZZ`-basis for this ideal.
2025
2026
EXAMPLES::
2027
2028
sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().gens()
2029
(1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2030
"""
2031
return self.__basis
2032
2033
def __cmp__(self, right):
2034
"""
2035
Compare this fractional quaternion ideal to ``right``. If
2036
``right`` is not a fractional quaternion ideal a TypeError is
2037
raised. If the fractional ideals are in different ambient
2038
quaternion algebras, then the quaternion algebras themselves
2039
are compared.
2040
2041
INPUT:
2042
2043
- ``right`` - another fractional quaternion ideal
2044
2045
EXAMPLES::
2046
2047
sage: I = QuaternionAlgebra(-11,-1).maximal_order().unit_ideal()
2048
sage: I == I # indirect doctest
2049
True
2050
sage: I == 5
2051
False
2052
"""
2053
if not isinstance(right, QuaternionFractionalIdeal_rational):
2054
raise TypeError
2055
return cmp(self.__basis, right.__basis)
2056
2057
def basis_matrix(self):
2058
"""
2059
Return basis matrix `M` in Hermite normal form for self as a
2060
matrix with rational entries.
2061
2062
If `Q` is the ambient quaternion algebra, then the `\\ZZ`-span of
2063
the rows of `M` viewed as linear combinations of Q.basis() =
2064
`[1,i,j,k]` is the fractional ideal self. Also, M *
2065
M.denominator() is an integer matrix in Hermite normal form.
2066
2067
OUTPUT: matrix over `\\QQ`
2068
2069
EXAMPLES::
2070
2071
sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()
2072
[ 1/2 1/2 0 0]
2073
[ 0 0 1/2 -1/2]
2074
[ 0 1 0 0]
2075
[ 0 0 0 -1]
2076
"""
2077
try: return self.__hermite_basis_matrix
2078
except AttributeError: pass
2079
B = quaternion_algebra_cython.rational_matrix_from_rational_quaternions(self.__basis)
2080
self.__hermite_basis_matrix = B
2081
return B
2082
2083
def free_module(self):
2084
"""
2085
Return the free module associated to this quaternionic
2086
fractional ideal, viewed as a submodule of
2087
``Q.free_module()``, where ``Q`` is the ambient quaternion
2088
algebra.
2089
2090
OUTPUT: free `\\ZZ`-module of rank 4 embedded in an ambient `\\QQ^4`.
2091
2092
EXAMPLES::
2093
2094
sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()
2095
[ 1/2 1/2 0 0]
2096
[ 0 0 1/2 -1/2]
2097
[ 0 1 0 0]
2098
[ 0 0 0 -1]
2099
2100
This shows that the issue at trac ticket 6760 is fixed::
2101
2102
sage: R.<i,j,k> = QuaternionAlgebra(-1, -13)
2103
sage: I = R.ideal([2+i, 3*i, 5*j, j+k]); I
2104
Fractional ideal (2 + i, 3*i, j + k, 5*k)
2105
sage: I.free_module()
2106
Free module of degree 4 and rank 4 over Integer Ring
2107
Echelon basis matrix:
2108
[2 1 0 0]
2109
[0 3 0 0]
2110
[0 0 1 1]
2111
[0 0 0 5]
2112
"""
2113
try: return self.__free_module
2114
except AttributeError:
2115
M = self.basis_matrix().row_module(ZZ)
2116
self.__free_module = M
2117
return M
2118
2119
def theta_series_vector(self, B):
2120
"""
2121
Return theta series coefficients of self, as a vector of `B` integers.
2122
2123
INPUT:
2124
2125
- ``B`` -- positive integer
2126
2127
OUTPUT: vector over `\\ZZ` with ``B`` entries
2128
2129
EXAMPLES::
2130
2131
sage: I = BrandtModule(37).right_ideals()[1]; I
2132
Fractional ideal (2 + 6*j + 2*k, i + 2*j + k, 8*j, 8*k)
2133
sage: I.theta_series_vector(5)
2134
(1, 0, 2, 2, 6)
2135
sage: I.theta_series_vector(10)
2136
(1, 0, 2, 2, 6, 4, 8, 6, 10, 10)
2137
sage: I.theta_series_vector(5)
2138
(1, 0, 2, 2, 6)
2139
"""
2140
B = Integer(B)
2141
try:
2142
if len(self.__theta_series_vector)>= B: return self.__theta_series_vector[:B]
2143
except AttributeError: pass
2144
V = FreeModule(ZZ, B)
2145
Q = self.quadratic_form()
2146
v = V(Q.representation_number_list(B))
2147
self.__theta_series_vector = v
2148
return v
2149
2150
def quadratic_form(self):
2151
"""
2152
Return the normalized quadratic form associated to this quaternion ideal.
2153
2154
OUTPUT: quadratic form
2155
2156
EXAMPLES::
2157
2158
sage: I = BrandtModule(11).right_ideals()[1]
2159
sage: Q = I.quadratic_form(); Q
2160
Quadratic form in 4 variables over Rational Field with coefficients:
2161
[ 18 22 33 22 ]
2162
[ * 7 22 11 ]
2163
[ * * 22 0 ]
2164
[ * * * 22 ]
2165
sage: Q.theta_series(10)
2166
1 + 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)
2167
sage: I.theta_series(10)
2168
1 + 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)
2169
"""
2170
try: return self.__quadratic_form
2171
except AttributeError: pass
2172
from sage.quadratic_forms.quadratic_form import QuadraticForm
2173
# first get the gram matrix
2174
gram_matrix = self.gram_matrix()
2175
# rescale so that there are no denominators
2176
gram_matrix, _ = gram_matrix._clear_denom()
2177
# Make sure gcd of all entries is 1.
2178
g = gram_matrix.gcd()
2179
if g != 1:
2180
gram_matrix = gram_matrix / g
2181
# now get the quadratic form
2182
Q = QuadraticForm(gram_matrix)
2183
self.__quadratic_form = Q
2184
return Q
2185
2186
def theta_series(self, B, var='q'):
2187
"""
2188
Return normalized theta series of self, as a power series over
2189
`\\ZZ` in the variable ``var``, which is 'q' by default.
2190
2191
The normalized theta series is by definition
2192
2193
.. math::
2194
2195
\\theta_I(q)=\\sum_{x \\in I} q^{\\frac{N(x)}{N(I)}}
2196
2197
INPUT:
2198
2199
- ``B`` -- positive integer
2200
- ``var`` -- string (default: 'q')
2201
2202
OUTPUT: power series
2203
2204
EXAMPLES::
2205
2206
sage: I = BrandtModule(11).right_ideals()[1]; I
2207
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 2*k, 8*j, 8*k)
2208
sage: I.norm()
2209
32
2210
sage: I.theta_series(5)
2211
1 + 12*q^2 + 12*q^3 + 12*q^4 + O(q^5)
2212
sage: I.theta_series(5,'T')
2213
1 + 12*T^2 + 12*T^3 + 12*T^4 + O(T^5)
2214
sage: I.theta_series(3)
2215
1 + 12*q^2 + O(q^3)
2216
"""
2217
try:
2218
if self.__theta_series.prec() >= B:
2219
if var == self.__theta_series.variable():
2220
return self.__theta_series.add_bigoh(B)
2221
else:
2222
ZZ[[var]](self.__theta_series.list()[:B+1])
2223
except AttributeError: pass
2224
v = self.theta_series_vector(B)
2225
theta = ZZ[[var]](v.list()).add_bigoh(B)
2226
self.__theta_series = theta
2227
return theta
2228
2229
def gram_matrix(self):
2230
"""
2231
Return the Gram matrix of this fractional ideal.
2232
2233
OUTPUT: 4x4 matrix over `\\QQ`.
2234
2235
EXAMPLES::
2236
2237
sage: I = BrandtModule(3,5).right_ideals()[1]; I
2238
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
2239
sage: I.gram_matrix()
2240
[ 640 1920 2112 1920]
2241
[ 1920 14080 13440 16320]
2242
[ 2112 13440 13056 15360]
2243
[ 1920 16320 15360 19200]
2244
"""
2245
try: return self.__gram_matrix
2246
except AttributeError: pass
2247
M = []
2248
A = self.__basis
2249
B = [z.conjugate() for z in self.__basis]
2250
two = QQ(2)
2251
m = [two*(a*b).reduced_trace() for b in B for a in A]
2252
M44 = MatrixSpace(QQ, 4)
2253
G = M44(m,coerce=False)
2254
self.__gram_matrix = G
2255
return G
2256
2257
def norm(self):
2258
"""
2259
Return the reduced norm of this fractional ideal.
2260
2261
OUTPUT: rational number
2262
2263
EXAMPLES::
2264
2265
sage: M = BrandtModule(37)
2266
sage: C = M.right_ideals()
2267
sage: [I.norm() for I in C]
2268
[16, 32, 32]
2269
2270
sage: (a,b) = M.quaternion_algebra().invariants() # optional - magma
2271
sage: magma.eval('A<i,j,k> := QuaternionAlgebra<Rationals() | %s, %s>' % (a,b)) # optional - magma
2272
''
2273
sage: magma.eval('O := QuaternionOrder(%s)' % str(list(C[0].right_order().basis()))) # optional - magma
2274
''
2275
sage: [ magma('rideal<O | %s>' % str(list(I.basis()))).Norm() for I in C] # optional - magma
2276
[16, 32, 32]
2277
2278
sage: A.<i,j,k> = QuaternionAlgebra(-1,-1)
2279
sage: R = A.ideal([i,j,k,1/2 + 1/2*i + 1/2*j + 1/2*k]) # this is actually an order, so has reduced norm 1
2280
sage: R.norm()
2281
1
2282
sage: [ J.norm() for J in R.cyclic_right_subideals(3) ] # enumerate maximal right R-ideals of reduced norm 3, verify their norms
2283
[3, 3, 3, 3]
2284
"""
2285
G = self.gram_matrix() / QQ(2)
2286
r = G.det().abs()
2287
assert r.is_square(), "first is bad!"
2288
r = r.sqrt()
2289
# If we know either the left- or the right order, use that one to compute the norm.
2290
# Otherwise quaternion_order() will raise a RuntimeError and we compute the left order
2291
try:
2292
R = self.quaternion_order()
2293
except RuntimeError:
2294
R = self.left_order()
2295
r/= R.discriminant()
2296
assert r.is_square(), "second is bad!"
2297
return r.sqrt()
2298
2299
def conjugate(self):
2300
"""
2301
Return the ideal with generators the conjugates of the generators for self.
2302
2303
OUTPUT: a quaternionic fractional ideal
2304
2305
EXAMPLES::
2306
2307
sage: I = BrandtModule(3,5).right_ideals()[1]; I
2308
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
2309
sage: I.conjugate()
2310
Fractional ideal (2 + 2*j + 28*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
2311
"""
2312
return self.quaternion_algebra().ideal([b.conjugate() for b in self.basis()],
2313
left_order=self.__right_order,
2314
right_order=self.__left_order)
2315
2316
def __mul__(self, right):
2317
"""
2318
Return the product of the fractional ideals ``self`` and ``right``.
2319
2320
.. note::
2321
2322
We do not keep track of left or right order structure.
2323
2324
EXAMPLES::
2325
2326
sage: I = BrandtModule(3,5).right_ideals()[1]; I
2327
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
2328
sage: I*I
2329
Fractional ideal (8 + 24*j + 16*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)
2330
sage: I*I.conjugate()
2331
Fractional ideal (16 + 16*j + 224*k, 8*i + 16*j + 136*k, 32*j + 128*k, 320*k)
2332
sage: I.multiply_by_conjugate(I)
2333
Fractional ideal (16 + 16*j + 224*k, 8*i + 16*j + 136*k, 32*j + 128*k, 320*k)
2334
"""
2335
if not isinstance(right, QuaternionFractionalIdeal_rational):
2336
return self._scale(right, left=False)
2337
gens = [a*b for a in self.basis() for b in right.basis()]
2338
#if self.__right_order == right.__left_order:
2339
# left_order = self.__left_order
2340
# right_order = right.__right_order
2341
basis = tuple(basis_for_quaternion_lattice(gens))
2342
A = self.quaternion_algebra()
2343
return A.ideal(basis, check=False)
2344
2345
@cached_method
2346
def free_module(self):
2347
r"""
2348
Return the underlying free `\ZZ`-module corresponding to this ideal.
2349
2350
EXAMPLES::
2351
2352
sage: X = BrandtModule(3,5).right_ideals()
2353
sage: X[0]
2354
Fractional ideal (2 + 2*j + 8*k, 2*i + 18*k, 4*j + 16*k, 20*k)
2355
sage: X[0].free_module()
2356
Free module of degree 4 and rank 4 over Integer Ring
2357
Echelon basis matrix:
2358
[ 2 0 2 8]
2359
[ 0 2 0 18]
2360
[ 0 0 4 16]
2361
[ 0 0 0 20]
2362
sage: X[0].scale(1/7).free_module()
2363
Free module of degree 4 and rank 4 over Integer Ring
2364
Echelon basis matrix:
2365
[ 2/7 0 2/7 8/7]
2366
[ 0 2/7 0 18/7]
2367
[ 0 0 4/7 16/7]
2368
[ 0 0 0 20/7]
2369
2370
The free module method is also useful since it allows for checking if one ideal
2371
is contained in another, computing quotients I/J, etc.::
2372
2373
sage: X = BrandtModule(3,17).right_ideals()
2374
sage: I = X[0].intersection(X[2]); I
2375
Fractional ideal (2 + 2*j + 164*k, 2*i + 4*j + 46*k, 16*j + 224*k, 272*k)
2376
sage: I.free_module().is_submodule(X[3].free_module())
2377
False
2378
sage: I.free_module().is_submodule(X[1].free_module())
2379
True
2380
sage: X[0].free_module() / I.free_module()
2381
Finitely generated module V/W over Integer Ring with invariants (4, 4)
2382
"""
2383
return self.basis_matrix().row_module(ZZ)
2384
2385
def intersection(self, J):
2386
"""
2387
Return the intersection of the ideals self and `J`.
2388
2389
EXAMPLES::
2390
2391
sage: X = BrandtModule(3,5).right_ideals()
2392
sage: I = X[0].intersection(X[1]); I
2393
Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
2394
2395
"""
2396
V = self.free_module().intersection(J.free_module())
2397
H,d = V.basis_matrix()._clear_denom()
2398
A = self.quaternion_algebra()
2399
gens = quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d)
2400
return A.ideal(gens)
2401
2402
def multiply_by_conjugate(self, J):
2403
"""
2404
Return product of self and the conjugate Jbar of `J`.
2405
2406
INPUT:
2407
2408
- ``J`` -- a quaternion ideal.
2409
2410
OUTPUT: a quaternionic fractional ideal.
2411
2412
EXAMPLES::
2413
2414
sage: R = BrandtModule(3,5).right_ideals()
2415
sage: R[0].multiply_by_conjugate(R[1])
2416
Fractional ideal (8 + 8*j + 112*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)
2417
sage: R[0]*R[1].conjugate()
2418
Fractional ideal (8 + 8*j + 112*k, 8*i + 16*j + 136*k, 32*j + 128*k, 160*k)
2419
"""
2420
Jbar = [b.conjugate() for b in J.basis()]
2421
gens = [a*b for a in self.basis() for b in Jbar]
2422
basis = tuple(basis_for_quaternion_lattice(gens))
2423
R = self.quaternion_algebra()
2424
return R.ideal(basis, check=False)
2425
2426
def is_equivalent(I, J, B=10):
2427
"""
2428
Return ``True`` if ``I`` and ``J`` are equivalent as right ideals.
2429
2430
INPUT:
2431
2432
- ``I`` -- a fractional quaternion ideal (self)
2433
2434
- ``J`` -- a fractional quaternion ideal with same order as ``I``
2435
2436
- ``B`` -- a bound to compute and compare theta series before
2437
doing the full equivalence test
2438
2439
OUTPUT: bool
2440
2441
EXAMPLES::
2442
2443
sage: R = BrandtModule(3,5).right_ideals(); len(R)
2444
2
2445
sage: R[0].is_equivalent(R[1])
2446
False
2447
sage: R[0].is_equivalent(R[0])
2448
True
2449
sage: OO = R[0].quaternion_order()
2450
sage: S = OO.right_ideal([3*a for a in R[0].basis()])
2451
sage: R[0].is_equivalent(S)
2452
True
2453
"""
2454
if not isinstance(I, QuaternionFractionalIdeal_rational):
2455
return False
2456
2457
if I.right_order() != J.right_order():
2458
raise ValueError("I and J must be right ideals")
2459
2460
# Just test theta series first. If the theta series are
2461
# different, the ideals are definitely not equivalent.
2462
if B > 0 and I.theta_series_vector(B) != J.theta_series_vector(B):
2463
return False
2464
2465
# The theta series are the same, so perhaps the ideals are
2466
# equivalent. We use Prop 1.18 of [Pizer, 1980] to decide.
2467
# 1. Compute I * Jbar
2468
# see Prop. 1.17 in Pizer. Note that we use IJbar instead of
2469
# JbarI since we work with right ideals
2470
IJbar = I.multiply_by_conjugate(J)
2471
2472
# 2. Determine if there is alpha in K such
2473
# that N(alpha) = N(I)*N(J) as explained by Pizer.
2474
c = IJbar.theta_series_vector(2)[1]
2475
return c != 0
2476
2477
def __contains__(self, x):
2478
"""
2479
Returns whether x is in self.
2480
2481
EXAMPLES::
2482
sage: R.<i,j,k> = QuaternionAlgebra(-3, -13)
2483
sage: I = R.ideal([2+i, 3*i, 5*j, j+k])
2484
sage: 2+i in I
2485
True
2486
sage: 2+i+j+k in I
2487
True
2488
sage: 1+i in I
2489
False
2490
sage: 101*j + k in I
2491
True
2492
"""
2493
try:
2494
x = self.quaternion_algebra()(x)
2495
return self.basis_matrix().transpose().solve_right(vector(x)) in ZZ**4
2496
except (ValueError, TypeError):
2497
return False
2498
2499
@cached_method
2500
def cyclic_right_subideals(self, p, alpha=None):
2501
r"""
2502
Let `I` = ``self``. This function returns the right subideals
2503
`J` of `I` such that `I/J` is an `\GF{p}`-vector space of
2504
dimension 2.
2505
2506
INPUT:
2507
2508
- `p` -- prime number (see below)
2509
2510
- `alpha` -- (default: None) element of quaternion algebra,
2511
which can be used to parameterize the order of the
2512
ideals `J`. More precisely the `J`'s are the right annihilators
2513
of `(1,0) \alpha^i` for `i=0,1,2,...,p`
2514
2515
OUTPUT:
2516
2517
- list of right ideals
2518
2519
.. note::
2520
2521
Currently, `p` must satisfy a bunch of conditions, or a
2522
NotImplementedError is raised. In particular, `p` must be
2523
odd and unramified in the quaternion algebra, must be
2524
coprime to the index of the right order in the maximal
2525
order, and also coprime to the normal of self. (The Brandt
2526
modules code has a more general algorithm in some cases.)
2527
2528
EXAMPLES::
2529
2530
sage: B = BrandtModule(2,37); I = B.right_ideals()[0]
2531
sage: I.cyclic_right_subideals(3)
2532
[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)]
2533
2534
sage: B = BrandtModule(5,389); I = B.right_ideals()[0]
2535
sage: C = I.cyclic_right_subideals(3); C
2536
[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)]
2537
sage: [(I.free_module()/J.free_module()).invariants() for J in C]
2538
[(3, 3), (3, 3), (3, 3), (3, 3)]
2539
sage: I.scale(3).cyclic_right_subideals(3)
2540
[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)]
2541
sage: C = I.scale(1/9).cyclic_right_subideals(3); C
2542
[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)]
2543
sage: [(I.scale(1/9).free_module()/J.free_module()).invariants() for J in C]
2544
[(3, 3), (3, 3), (3, 3), (3, 3)]
2545
2546
sage: Q.<i,j,k> = QuaternionAlgebra(-2,-5)
2547
sage: I = Q.ideal([Q(1),i,j,k])
2548
sage: I.cyclic_right_subideals(3)
2549
[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)]
2550
2551
The general algorithm is not yet implemented here::
2552
2553
sage: I.cyclic_right_subideals(3)[0].cyclic_right_subideals(3)
2554
Traceback (most recent call last):
2555
...
2556
NotImplementedError: general algorithm not implemented (The given basis vectors must be linearly independent.)
2557
"""
2558
R = self.right_order()
2559
Q = self.quaternion_algebra()
2560
f = Q.modp_splitting_map(p)
2561
if alpha is not None:
2562
alpha = f(alpha)
2563
W = GF(p)**4
2564
try:
2565
A = W.span_of_basis([W(f(a).list()) for a in self.basis()])
2566
scale = 1
2567
IB = self.basis_matrix()
2568
except (ValueError, ZeroDivisionError):
2569
# try rescaling the ideal.
2570
B, d = self.basis_matrix()._clear_denom()
2571
g = gcd(B.list())
2572
IB = B/g
2573
scale = g/d
2574
try:
2575
A = W.span_of_basis([W(f(Q(a.list())).list()) for a in IB.rows()])
2576
except (ValueError, ZeroDivisionError) as msg:
2577
# Here we could replace the ideal by an *equivalent*
2578
# ideal that works. This is always possible.
2579
# However, I haven't implemented that algorithm yet.
2580
raise NotImplementedError("general algorithm not implemented (%s)"%msg)
2581
2582
Ai = A.basis_matrix()**(-1)
2583
AiB = Ai.change_ring(QQ) * IB
2584
2585
# Do not care about the denominator since we're really working in I/p*I.
2586
AiB, _ = AiB._clear_denom()
2587
2588
pB = p*IB
2589
pB, d = pB._clear_denom()
2590
2591
ans = []
2592
Z = matrix(ZZ,2,4)
2593
2594
P1 = P1List(p)
2595
if alpha is None:
2596
lines = P1
2597
else:
2598
x = alpha
2599
lines = []
2600
for i in range(p+1):
2601
lines.append(P1.normalize(x[0,0], x[0,1]))
2602
x *= alpha
2603
2604
for u,v in lines:
2605
# The following does:
2606
# z = matrix(QQ,2,4,[0,-v,0,u, -v,0,u,0],check=False) * AiB
2607
Z[0,1]=-v; Z[0,3]=u; Z[1,0]=-v; Z[1,2]=u
2608
z = Z * AiB
2609
# Now construct submodule of the ideal I spanned by the
2610
# linear combinations given by z of the basis for J along
2611
# with p*I.
2612
G = (d*z).stack(pB) # have to multiply by d since we divide by it below in the "gens = " line.
2613
H = G._hnf_pari(0, include_zero_rows=False)
2614
gens = tuple(quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(Q, H, d))
2615
if scale != 1:
2616
gens = tuple([scale*g for g in gens])
2617
J = R.right_ideal(gens, check=False)
2618
ans.append(J)
2619
return ans
2620
2621
#######################################################################
2622
# Some utility functions that are needed here and are too
2623
# specialized to go elsewhere.
2624
#######################################################################
2625
2626
def basis_for_quaternion_lattice(gens, reverse = False):
2627
"""
2628
Return a basis for the `\\ZZ`-lattice in a quaternion algebra
2629
spanned by the given gens.
2630
2631
INPUT:
2632
2633
- ``gens`` -- list of elements of a single quaternion algebra
2634
2635
- ``reverse`` -- when computing the HNF do it on the basis
2636
(k,j,i,1) instead of (1,i,j,k). This ensures
2637
that if ``gens`` are the generators for an order,
2638
the first returned basis vector is 1.
2639
2640
EXAMPLES::
2641
2642
sage: from sage.algebras.quatalg.quaternion_algebra import basis_for_quaternion_lattice
2643
sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)
2644
sage: basis_for_quaternion_lattice([i+j, i-j, 2*k, A(1/3)])
2645
[1/3, i + j, 2*j, 2*k]
2646
2647
sage: basis_for_quaternion_lattice([A(1),i,j,k])
2648
[1, i, j, k]
2649
2650
"""
2651
if len(gens) == 0: return []
2652
Z, d = quaternion_algebra_cython.integral_matrix_and_denom_from_rational_quaternions(gens, reverse)
2653
H = Z._hnf_pari(0, include_zero_rows=False)
2654
A = gens[0].parent()
2655
return quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d, reverse)
2656
2657
2658
def intersection_of_row_modules_over_ZZ(v):
2659
"""
2660
Intersects the `\ZZ`-modules with basis matrices the full rank 4x4
2661
`\QQ`-matrices in the list v. The returned intersection is
2662
represented by a 4x4 matrix over `\QQ`. This can also be done using
2663
modules and intersection, but that would take over twice as long
2664
because of overhead, hence this function.
2665
2666
EXAMPLES::
2667
2668
sage: a = matrix(QQ,4,[-2, 0, 0, 0, 0, -1, -1, 1, 2, -1/2, 0, 0, 1, 1, -1, 0])
2669
sage: 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])
2670
sage: c = matrix(QQ,4,[0, 1, 0, -1/2, 0, 0, 2, 2, 0, -1/2, 1/2, -1, 1, -1, -1/2, 0])
2671
sage: v = [a,b,c]
2672
sage: from sage.algebras.quatalg.quaternion_algebra import intersection_of_row_modules_over_ZZ
2673
sage: M = intersection_of_row_modules_over_ZZ(v); M
2674
[ -2 0 1 1]
2675
[ -4 1 1 -3]
2676
[ -3 19/2 -1 -4]
2677
[ 2 -3 -8 4]
2678
sage: M2 = a.row_module(ZZ).intersection(b.row_module(ZZ)).intersection(c.row_module(ZZ))
2679
sage: M.row_module(ZZ) == M2
2680
True
2681
"""
2682
if len(v) <= 0:
2683
raise ValueError("v must have positive length")
2684
if len(v) == 1:
2685
return v[0]
2686
elif len(v) == 2:
2687
# real work - the base case
2688
a, b = v
2689
s,_ = a.stack(b)._clear_denom()
2690
s = s.transpose()
2691
K = s.right_kernel_matrix(algorithm='pari', basis='computed')
2692
n = a.nrows()
2693
return K.matrix_from_columns(range(n)) * a
2694
else:
2695
# induct
2696
w = intersection_of_row_modules_over_ZZ(v[:2])
2697
return intersection_of_row_modules_over_ZZ([w] + v[2:])
2698
2699
2700
def normalize_basis_at_p(e, p, B = lambda x,y: (x*y.conjugate()).reduced_trace()):
2701
"""
2702
Computes a (at ``p``) normalized basis from the given basis ``e``
2703
of a `\\ZZ`-module.
2704
2705
The returned basis is (at ``p``) a `\\ZZ_p` basis for the same
2706
module, and has the property that with respect to it the quadratic
2707
form induced by the bilinear form B is represented as a orthogonal
2708
sum of atomic forms multiplied by p-powers.
2709
2710
If `p \\ne 2` this means that the form is diagonal with respect to
2711
this basis.
2712
2713
If `p = 2` there may be additional 2-dimensional subspaces on which
2714
the form is represented as `2^e (ax^2 + bxy + cx^2)` with
2715
`0 = v_2(b) = v_2(a) \\le v_2(c)`.
2716
2717
INPUT:
2718
- ``e`` -- list; basis of a `\\ZZ` module.
2719
WARNING: will be modified!
2720
2721
- ``p`` -- prime for at which the basis should be normalized
2722
2723
- ``B`` -- (default: lambda x,y: ((x*y).conjugate()).reduced_trace())
2724
A bilinear form with respect to which to normalize.
2725
2726
OUTPUT:
2727
2728
- A list containing two-element tuples: The first element of
2729
each tuple is a basis element, the second the valuation of
2730
the orthogonal summand to which it belongs. The list is sorted
2731
by ascending valuation.
2732
2733
EXAMPLES::
2734
2735
sage: from sage.algebras.quatalg.quaternion_algebra import normalize_basis_at_p
2736
sage: A.<i,j,k> = QuaternionAlgebra(-1, -1)
2737
sage: e = [A(1), i, j, k]
2738
sage: normalize_basis_at_p(e, 2)
2739
[(1, 0), (i, 0), (j, 0), (k, 0)]
2740
2741
sage: A.<i,j,k> = QuaternionAlgebra(210)
2742
sage: e = [A(1), i, j, k]
2743
sage: normalize_basis_at_p(e, 2)
2744
[(1, 0), (i, 1), (j, 1), (k, 2)]
2745
2746
sage: A.<i,j,k> = QuaternionAlgebra(286)
2747
sage: e = [A(1), k, 1/2*j + 1/2*k, 1/2 + 1/2*i + 1/2*k]
2748
sage: normalize_basis_at_p(e, 5)
2749
[(1, 0), (1/2*j + 1/2*k, 0), (-5/6*j + 1/6*k, 1), (1/2*i, 1)]
2750
2751
sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)
2752
sage: e = [A(1), k, j, 1/2 + 1/2*i + 1/2*j + 1/2*k]
2753
sage: normalize_basis_at_p(e, 2)
2754
[(1, 0), (1/2 + 1/2*i + 1/2*j + 1/2*k, 0), (-34/105*i - 463/735*j + 71/105*k, 1), (-34/105*i - 463/735*j + 71/105*k, 1)]
2755
"""
2756
2757
N = len(e)
2758
if N == 0:
2759
return []
2760
else:
2761
min_m, min_n, min_v = 0, 0, infinity
2762
2763
# Find two basis vector on which the bilinear form has minimal
2764
# p-valuation. If there is more than one such pair, always
2765
# prefer diagonal entries over any other and (secondary) take
2766
# min_m and then min_n as small as possible
2767
for m in range(N):
2768
for n in range(m, N):
2769
v = B(e[m], e[n]).valuation(p)
2770
if v < min_v or (v == min_v and (min_m != min_n) and (m == n)):
2771
min_m, min_n, min_v = m, n, v
2772
2773
2774
if (min_m == min_n) or p != 2: # In this case we can diagonalize
2775
if min_m == min_n: # Diagonal entry has minimal valuation
2776
f0 = e[min_m]
2777
else:
2778
f0 = e[min_m] + e[min_n] # Only off-diagonal entries have min. val., but p!=2
2779
2780
# Swap with first vector
2781
e[0], e[min_m] = e[min_m], e[0]
2782
2783
# Orthogonalize remaining vectors with respect to f
2784
c = B(f0, f0)
2785
for l in range(1, N):
2786
e[l] = e[l] - B(e[l],f0)/c * f0
2787
2788
# Recursively normalize remaining vectors
2789
f = normalize_basis_at_p(e[1:], p)
2790
f.insert(0, (f0, min_v - valuation(p, 2)))
2791
return f
2792
2793
else: # p = 2 and only off-diagonal entries have min. val., gives 2-dim. block
2794
# first diagonal entry should have smaller valuation
2795
if B(e[min_m],e[min_m]).valuation(p) > B(e[min_n],e[min_n]).valuation(p):
2796
e[min_m], e[min_n] = e[min_n], e[min_m]
2797
2798
f0 = p**min_v / B(e[min_m],e[min_n]) * e[min_m]
2799
f1 = e[min_n]
2800
2801
# Ensures that (B(f0,f0)/2).valuation(p) <= B(f0,f1).valuation(p)
2802
if B(f0,f1).valuation(p) + 1 < B(f0,f0).valuation(p):
2803
f0 = f0 + f1
2804
f1 = f0
2805
2806
# Make remaining vectors orthogonal to span of f0, f1
2807
e[min_m] = e[0]
2808
e[min_n] = e[1]
2809
2810
B00 = B(f0,f0)
2811
B11 = B(f1,f1)
2812
B01 = B(f0,f1)
2813
d = B00*B11 - B01**2
2814
tu = [ (B01 * B(f1,e[l]) - B11 * B(f0,e[l]),
2815
B01 * B(f0,e[l]) - B00 * B(f1,e[l])) for l in range(2,N) ]
2816
2817
e[2:n] = [ e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2,N) ]
2818
2819
# Recursively normalize remaining vectors
2820
f = normalize_basis_at_p(e[2:N], p)
2821
return [(f0, min_v), (f1, min_v)] + f
2822
2823
def maxord_solve_aux_eq(a, b, p):
2824
"""
2825
Given ``a`` and ``b`` and an even prime ideal ``p`` find
2826
(y,z,w) with y a unit mod `p^{2e}` such that
2827
2828
.. MATH::
2829
1 - ay^2 - bz^2 + abw^2 \equiv 0 mod p^{2e},
2830
2831
where `e` is the ramification index of `p`.
2832
2833
Currently only `p=2` is implemented by hardcoding solutions.
2834
2835
INPUT:
2836
2837
- ``a`` -- integer with `v_p(a) = 0`
2838
2839
- ``b`` -- integer with `v_p(b) \in \{0,1\}`
2840
2841
- ``p`` -- even prime ideal (actually only ``p=ZZ(2)`` is implemented)
2842
2843
OUTPUT:
2844
2845
- A tuple (y,z,w)
2846
2847
EXAMPLES::
2848
2849
sage: from sage.algebras.quatalg.quaternion_algebra import maxord_solve_aux_eq
2850
sage: for a in [1,3]:
2851
... for b in [1,2,3]:
2852
... (y,z,w) = maxord_solve_aux_eq(a, b, 2)
2853
... assert mod(y, 4) == 1 or mod(y, 4) == 3
2854
... assert mod(1 - a*y^2 - b*z^2 + a*b*w^2, 4) == 0
2855
"""
2856
if p != ZZ(2):
2857
raise NotImplementedError, "Algorithm only implemented over ZZ at the moment"
2858
2859
v_a = a.valuation(p)
2860
v_b = b.valuation(p)
2861
2862
if v_a != 0:
2863
raise RuntimeError, "a must have v_p(a)=0"
2864
if v_b != 0 and v_b != 1:
2865
raise RuntimeError, "b must have v_p(b) in {0,1}"
2866
2867
R = ZZ.quo(ZZ(4))
2868
lut = {
2869
(R(1), R(1)) : (1,1,1),
2870
(R(1), R(2)) : (1,0,0),
2871
(R(1), R(3)) : (1,0,0),
2872
(R(3), R(1)) : (1,1,1),
2873
(R(3), R(2)) : (1,0,1),
2874
(R(3), R(3)) : (1,1,1), }
2875
2876
return lut[ (R(a), R(b)) ]
2877
2878