Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constructor quaternion algebra over number fields from ramification #37189

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 120 additions & 5 deletions src/sage/algebras/quatalg/quaternion_algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- Lorenz Panny (2022): :meth:`QuaternionOrder.isomorphism_to`,
:meth:`QuaternionFractionalIdeal_rational.minimal_element`

- Eloi Torrents (2024): construct quaternion algebras over number fields from ramification

This code is partly based on Sage code by David Kohel from 2005.

TESTS:
Expand Down Expand Up @@ -48,13 +50,14 @@
from sage.rings.real_mpfr import RR
from sage.rings.integer import Integer
from sage.rings.integer_ring import ZZ
from sage.rings.rational import Rational
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.rings.ideal import Ideal_fractional
from sage.rings.rational_field import RationalField, QQ
from sage.rings.infinity import infinity
from sage.rings.number_field.number_field_base import NumberField
from sage.rings.qqbar import AA
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.polynomial.polynomial_ring import polygen
from sage.rings.power_series_ring import PowerSeriesRing
from sage.structure.category_object import normalize_names
from sage.structure.parent import Parent
Expand All @@ -78,11 +81,14 @@
from sage.modular.modsym.p1list import P1List

from sage.misc.cachefunc import cached_method
from sage.misc.functional import is_odd

from sage.categories.algebras import Algebras
from sage.categories.number_fields import NumberFields

from sage.structure.richcmp import richcmp_method
from sage.libs.pari.all import pari
from sage.combinat.words.word import Word

########################################################
# Constructor
Expand All @@ -95,7 +101,7 @@

INPUT:

There are three input formats:
There are four input formats:

- ``QuaternionAlgebra(a, b)``, where `a` and `b` can be coerced to
units in a common field `K` of characteristic different from 2.
Expand All @@ -108,6 +114,15 @@
`D` over `K = \QQ`. Suitable nonzero rational numbers `a`, `b`
as above are deduced from `D`.

- ``QuaternionAlgebra(K, primes, inv_archimedean)``, where `K` is a
number field or `\QQ`, ``primes`` is a list of prime ideals of `K`
and ``inv_archimedean`` is a list of local invariants (`0` or
`\frac{1}{2}`) specifying the ramification at the (infinite) real
places of `K`. This constructs a quaternion algebra ramified exacly
at the places given by ``primes`` and those (algebraic) real
embeddings of `K` indexed in ``K.embeddings(AA)`` by ``l`` with
``inv_archimedean[l] = 1/2``.

OUTPUT:

The quaternion algebra `(a, b)_K` over `K` generated by `i`, `j`
Expand Down Expand Up @@ -184,6 +199,29 @@
sage: QuaternionAlgebra(2*3*5*7)
Quaternion Algebra (-22, 210) with base ring Rational Field

``QuaternionAlgebra(K, primes, inv_archimedean)`` -- return the
quaternion algebra over `K` with the ramification specified by
``primes`` and ``inv_archimedean``::

sage: QuaternionAlgebra(QQ, [(2), (3)], [0])

Check failure on line 206 in src/sage/algebras/quatalg/quaternion_algebra.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 714, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1144, in compile_and_execute exec(compiled, globs) File "<doctest sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebraFactory[20]>", line 1, in <module> QuaternionAlgebra(QQ, [(Integer(2)), (Integer(3))], [Integer(0)]) File "sage/structure/factory.pyx", line 371, in sage.structure.factory.UniqueFactory.__call__ key, kwds = self.create_key_and_extra_args(*args, **kwds) File "sage/structure/factory.pyx", line 475, in sage.structure.factory.UniqueFactory.create_key_and_extra_args return self.create_key(*args, **kwds), {} File "/sage/src/sage/algebras/quatalg/quaternion_algebra.py", line 337, in create_key if is_RationalField(K): NameError: name 'is_RationalField' is not defined. Did you mean: 'RationalField'?
Quaternion Algebra (-1, 3) with base ring Rational Field
sage: QuaternionAlgebra(QQ, [(2), (3)], [1/2])

Check failure on line 208 in src/sage/algebras/quatalg/quaternion_algebra.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 714, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1144, in compile_and_execute exec(compiled, globs) File "<doctest sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebraFactory[21]>", line 1, in <module> QuaternionAlgebra(QQ, [(Integer(2)), (Integer(3))], [Integer(1)/Integer(2)]) File "sage/structure/factory.pyx", line 371, in sage.structure.factory.UniqueFactory.__call__ key, kwds = self.create_key_and_extra_args(*args, **kwds) File "sage/structure/factory.pyx", line 475, in sage.structure.factory.UniqueFactory.create_key_and_extra_args return self.create_key(*args, **kwds), {} File "/sage/src/sage/algebras/quatalg/quaternion_algebra.py", line 337, in create_key if is_RationalField(K): NameError: name 'is_RationalField' is not defined. Did you mean: 'RationalField'?
Traceback (most recent call last):
...
ValueError: quaternion algebra over the rationals must have an even number of ramified places

sage: x = polygen(ZZ, 'x')
sage: K.<w> = NumberField(x^2-x-1)
sage: P = K.prime_above(2)
sage: Q = K.prime_above(3)
sage: A = QuaternionAlgebra(K, [P,Q], [0,0])

Check failure on line 217 in src/sage/algebras/quatalg/quaternion_algebra.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 714, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1144, in compile_and_execute exec(compiled, globs) File "<doctest sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebraFactory[26]>", line 1, in <module> A = QuaternionAlgebra(K, [P,Q], [Integer(0),Integer(0)]) File "sage/structure/factory.pyx", line 371, in sage.structure.factory.UniqueFactory.__call__ key, kwds = self.create_key_and_extra_args(*args, **kwds) File "sage/structure/factory.pyx", line 475, in sage.structure.factory.UniqueFactory.create_key_and_extra_args return self.create_key(*args, **kwds), {} File "/sage/src/sage/algebras/quatalg/quaternion_algebra.py", line 337, in create_key if is_RationalField(K): NameError: name 'is_RationalField' is not defined. Did you mean: 'RationalField'?
sage: A.discriminant()

Check failure on line 218 in src/sage/algebras/quatalg/quaternion_algebra.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "sage/structure/category_object.pyx", line 857, in sage.structure.category_object.CategoryObject.getattr_from_category return self._cached_methods[name] KeyError: 'discriminant' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 714, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1144, in compile_and_execute exec(compiled, globs) File "<doctest sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebraFactory[27]>", line 1, in <module> A.discriminant() File "sage/structure/category_object.pyx", line 851, in sage.structure.category_object.CategoryObject.__getattr__ return self.getattr_from_category(name) File "sage/structure/category_object.pyx", line 866, in sage.structure.category_object.CategoryObject.getattr_from_category attr = getattr_from_other_class(self, cls, name) File "sage/cpython/getattr.pyx", line 358, in sage.cpython.getattr.getattr_from_other_class raise AttributeError(dummy_error_message) AttributeError: 'PolynomialRing_integral_domain_with_category' object has no attribute 'discriminant'
Fractional ideal (6)
sage: A = QuaternionAlgebra(K, [P,Q], [1/2,0])

Check failure on line 220 in src/sage/algebras/quatalg/quaternion_algebra.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 714, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1144, in compile_and_execute exec(compiled, globs) File "<doctest sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebraFactory[28]>", line 1, in <module> A = QuaternionAlgebra(K, [P,Q], [Integer(1)/Integer(2),Integer(0)]) File "sage/structure/factory.pyx", line 371, in sage.structure.factory.UniqueFactory.__call__ key, kwds = self.create_key_and_extra_args(*args, **kwds) File "sage/structure/factory.pyx", line 475, in sage.structure.factory.UniqueFactory.create_key_and_extra_args return self.create_key(*args, **kwds), {} File "/sage/src/sage/algebras/quatalg/quaternion_algebra.py", line 337, in create_key if is_RationalField(K): NameError: name 'is_RationalField' is not defined. Did you mean: 'RationalField'?
Traceback (most recent call last):
...
ValueError: quaternion algebra over a number field must have an even number of ramified places

If the coefficients `a` and `b` in the definition of the quaternion
algebra are not integral, then a slower generic type is used for
arithmetic::
Expand Down Expand Up @@ -225,6 +263,29 @@
Rational Field
sage: parent(Q._b)
Rational Field

Check that construction via ramification yields the correct algebra, i.e.
that the differences between Sage and PARI are incorporated correctly::

sage: x = polygen(ZZ, 'x')
sage: K.<v> = NumberField(-3*x^5 - 11*x^4 - 4*x^3 + 1)
sage: QuaternionAlgebra(K, [], [1/2, 0, 1/2]) # not tested
Quaternion Algebra (-87*v^4 - 412*v^3 - 472*v^2 - 173*v - 7, 6*v^4 + 16*v^3 - 26*v^2 - 40*v - 1)
with base ring Number Field in v with defining polynomial -3*x^5 - 11*x^4 - 4*x^3 + 1

Also check that the Sage-PARI permutation is the correct way around, i.e.
it does not need to be replaced by its inverse (computation is not deterministic,
this will be checked properly once the required code has been merged)::

sage: x = polygen(ZZ, 'x')
sage: K.<j> = NumberField(5*x^4 - 50*x^2 + 5)
sage: P = K.prime_above(2)
sage: Q = K.prime_above(5)
sage: inv_arch = [1/2, 1/2, 0, 0]
sage: QuaternionAlgebra(K, [P,Q], inv_arch) # not tested
Quaternion Algebra (-51/4*j^3 - 11/4*j^2 + 523/4*j + 47/4, 149/2*j^3 + 85*j^2 - 1073/2*j - 900)
with base ring Number Field in j with defining polynomial 5*x^4 - 50*x^2 + 5

"""
def create_key(self, arg0, arg1=None, arg2=None, names='i,j,k'):
"""
Expand All @@ -240,8 +301,8 @@
K = QQ
D = Integer(arg0)
a, b = hilbert_conductor_inverse(D)
a = Rational(a)
b = Rational(b)
a = QQ(a)
b = QQ(b)

elif arg2 is None:
# If arg0 or arg1 are Python data types, coerce them
Expand All @@ -263,8 +324,62 @@
a = K(v[0])
b = K(v[1])

# QuaternionAlgebra(K, a, b)
elif isinstance(arg1, list) and isinstance(arg2, list):
# QuaternionAlgebra(K, primes, inv_archimedean)
K = arg0
if K not in NumberFields():
raise ValueError("quaternion algebra must be defined over a number field")
if not set(arg2).issubset(set([0, QQ(1/2)])):
raise ValueError("list of local invariants specifying ramification should contain only 0 and 1/2")
primes = set(arg1)
if not all([p.is_prime() for p in primes]):
raise ValueError("quaternion algebra constructor requires a list of primes specifying the ramification")
if is_RationalField(K):
if len(arg2) > 1 or (len(arg2) == 1 and is_odd(len(primes) + 2*arg2[0])):
raise ValueError("quaternion algebra over the rationals must have an even number of ramified places")
D = ZZ.ideal_monoid().prod(primes).gen()
a, b = hilbert_conductor_inverse(D)
a = QQ(a)
b = QQ(b)
else:
if len(arg2) != len(K.real_places()):
raise ValueError("must specify ramification at the real places of %s" % K)
if is_odd(len(primes) + 2 * sum(arg2)):
raise ValueError("quaternion algebra over a number field must have an even number of ramified places")

# We want to compute the correct quaternion algebra over K with PARI
# As PARI optimizes the polynomial used to define the number field, we need to precompute
# this optimization in Sage and then permute the local invariants correctly
x = polygen(QQ, 'x')
g = K.pari_polynomial().sage({'x': x})
alpha = g.roots(ring=K, multiplicities=False)[0]

# Compute the number field PARI actually uses, together with isomorphisms to and from K
L, to_K, from_K = K.change_generator(alpha)

# This computation of the permutation relies on the fact that both
# Sage and PARI sort real roots of polynomials in increasing order
v = K.gen()
vals_embed = [sigma(from_K(v)) for sigma in L.embeddings(AA)]
perm = Word(vals_embed).standard_permutation()

# Transfer the primes to PARI and permute the local invariants
fin_places_pari = [I.pari_prime() for I in primes]
inv_arch_pari = [arg2[i-1] for i in perm]

# Compute the correct quaternion algebra over L in PARI
A = L.__pari__().alginit([2, [fin_places_pari, [QQ(1/2)] * len(fin_places_pari)],
inv_arch_pari], maxord=0)

# Obtain representation of A in terms of invariants in L
a_L = L(A.algsplittingfield().disc()[1])
b_L = L(A.algb())

# Finally, transfer the result to K
a = to_K(a_L)
b = to_K(b_L)
else:
# QuaternionAlgebra(K, a, b)
K = arg0
a = K(arg1)
b = K(arg2)
Expand Down
Loading