From 32bcda310ba8a6ef93c1950245ca19288f5a6c5b Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 27 Jan 2024 02:52:48 +0100 Subject: [PATCH 01/21] Implemented `.ramified_places` to extend quaternion algebra functionality to number fields - Implemented method `.ramified_places` for quaternion algebras over number fields. Integrated `.ramified_primes()` into it in the process - Rerouted `.discriminant()` through `.ramified_places` - Modified `.is_division_algebra()`, `.is_matrix_ring()` and `.is_isomorphic` to use `.ramified_places` instead of `.discriminant()`, extending them to base number fields - Added `.is_definite()` and `.is_totally_definite()` methods - Added Voight's book "Quaternion Algebras" to the list of references --- src/doc/en/reference/references/index.rst | 3 + .../algebras/quatalg/quaternion_algebra.py | 336 +++++++++++++++--- 2 files changed, 282 insertions(+), 57 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1c075a474d6..4535380e922 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6342,6 +6342,9 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. +.. [Voi2021] \J. Voight. Quaternion Algebras. Graduate Texts in + Mathematics 288. Springer Cham, 2021. + .. [VS06] \G.D. Villa Salvador. Topics in the Theory of Algebraic Function Fields. Birkh\"auser, 2006. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 26b02b5d3e8..d479b69a4da 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -401,9 +401,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Return ``True`` if the quaternion algebra is a division algebra (i.e. - every nonzero element in ``self`` is invertible), and ``False`` if the - quaternion algebra is isomorphic to the 2x2 matrix algebra. + Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether + every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -411,40 +410,52 @@ def is_division_algebra(self) -> bool: True sage: QuaternionAlgebra(1).is_division_algebra() False - sage: QuaternionAlgebra(2,9).is_division_algebra() - False + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).is_division_algebra() + True + sage: QuaternionAlgebra(L, -1, -1).is_division_algebra() + True + sage: QuaternionAlgebra(RR(2.),1).is_division_algebra() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers + NotImplementedError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise NotImplementedError("base field must be rational numbers") - return self.discriminant() != 1 + try: + return self.ramified_places(inf=True) != ([], []) + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def is_matrix_ring(self) -> bool: """ - Return ``True`` if the quaternion algebra is isomorphic to the 2x2 - matrix ring, and ``False`` if ``self`` is a division algebra (i.e. - every nonzero element in ``self`` is invertible). + Checks whether the quaternion algebra ``self`` is isomorphic to the 2x2 matrix + ring over the base field. EXAMPLES:: sage: QuaternionAlgebra(QQ,-5,-2).is_matrix_ring() False - sage: QuaternionAlgebra(1).is_matrix_ring() - True sage: QuaternionAlgebra(2,9).is_matrix_ring() True + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).is_matrix_ring() + False + sage: QuaternionAlgebra(L, -1, -1).is_matrix_ring() + False + sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers - + NotImplementedError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise NotImplementedError("base field must be rational numbers") - return self.discriminant() == 1 + try: + return self.ramified_places(inf=True) == ([], []) + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def is_exact(self) -> bool: """ @@ -1029,86 +1040,297 @@ def inner_product_matrix(self): a, b = self._a, self._b return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b]) - @cached_method - def discriminant(self): + def is_definite(self): """ - Return the discriminant of this quaternion algebra, i.e. the product of the finite - primes it ramifies at. + Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the + unique Archimedean place of its base field QQ. This is the case if and only if both + invariants of ``self`` are negative, see [Voi2021, Exercise 2.4(c)]. EXAMPLES:: - sage: QuaternionAlgebra(210,-22).discriminant() - 210 - sage: QuaternionAlgebra(19).discriminant() - 19 + sage: QuaternionAlgebra(QQ,-5,-2).is_definite() + True + sage: QuaternionAlgebra(1).is_definite() + False + + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers + """ + if not is_RationalField(self.base_ring()): + raise ValueError("base field must be rational numbers") + a, b = self.invariants() + return a < 0 and b < 0 + + def is_totally_definite(self): + """ + Checks whether the quaternion algebra ``self`` is totally definite, i.e. whether it ramifies + at all real Archimedean places of its base number field. + + EXAMPLES:: + + sage: QuaternionAlgebra(QQ, -5, -2).is_totally_definite() + True + + sage: K = QuadraticField(3) + sage: QuaternionAlgebra(K, -1, -1).is_totally_definite() + True + + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(L, -1, -1).is_totally_definite() + True sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) - sage: B. = QuaternionAlgebra(F, 2*a, F(-1)) - sage: B.discriminant() - Fractional ideal (2) + sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() + False - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic - Fractional ideal (1) + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - try: - F = self.base_ring() - return F.hilbert_conductor(self._a, self._b) - except NotImplementedError: - raise ValueError("base field must be rational numbers or number field") - else: - return ZZ.prod(self.ramified_primes()) + F = self.base_ring() + if is_RationalField(F): + return self.is_definite() + + try: + E = F.real_embeddings() + return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + except (AttributeError, NotImplementedError): + raise ValueError("base field must be rational numbers or a number field") + + @cached_method + def ramified_places(self, inf=True): + """ + Return the places of the base number field at which the quaternion algebra ``self`` ramifies. + + Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the + number field case) to check ramification for is motivated by [Voi2021, 12.4.12(a)]. The + restriction to real Archimedean embeddings is due to [Voi2021, 14.5.8]. + + INPUT: + + - ``inf`` -- (default: ``True``) + + OUTPUT: + + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if + ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number + field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean + (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). + + EXAMPLES:: + + sage: QuaternionAlgebra(210,-22).ramified_places() + ([2, 3, 5, 7], []) + + sage: QuaternionAlgebra(-1, -1).ramified_places() + ([2], + [Ring morphism: + From: Rational Field + To: Real Field with 53 bits of precision + Defn: 1 |--> 1.00000000000000]) + + sage: K = QuadraticField(3) + sage: QuaternionAlgebra(K, -1, -1).ramified_places() + ([], + [Ring morphism: + From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? + To: Real Field with 53 bits of precision + Defn: a |--> -1.73205080756888, + Ring morphism: + From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? + To: Real Field with 53 bits of precision + Defn: a |--> 1.73205080756888]) + + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(L, -1, -1).ramified_places() + ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) + + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_places() + ([Fractional ideal (2)], + [Ring morphism: + From: Number Field in a with defining polynomial x^2 - x - 1 + To: Real Field with 53 bits of precision + Defn: a |--> -0.618033988749895]) + + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic + ([], []) + sage: QuaternionAlgebra(RR(2.),1).ramified_places() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field + """ + if not isinstace(inf, bool): + raise ValueError("inf must be a truth value") + + F = self.base_ring() + + # For efficiency (and to not convert QQ into a number field manually), we handle F = QQ first + if is_RationalField(F): + ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), + prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), + prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + + if not inf: + return ram_fin + + # The given quaternion algebra ramifies at the unique infinite place of QQ, by definition, + # if and only if it is definite + if self.is_definite(): + return ram_fin, QQ.places() + + return ram_fin, [] + + try: + # Over the number field F, first compute the finite ramified places + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), + F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + + if not inf: + return ram_fin + + # At this point the infinite ramified places also need to be computed + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + except (AttributeError, NotImplementedError): + raise ValueError("base field must be rational numbers or a number field") + @cached_method def ramified_primes(self): """ - Return the (finite) primes that ramify in this rational quaternion algebra. + Return the (finite) primes of the base number field at which the quaternion algebra ``self`` ramifies. OUTPUT: - The list of prime numbers at which ``self`` ramifies (given as integers), sorted by their - magnitude (small to large). + The list of finite primes at which ``self`` ramifies; given as integers, sorted + small-to-large, if ``self`` is defined over QQ, and as fractional ideals in the + ring of integers of the base number field otherwise. EXAMPLES:: - sage: QuaternionAlgebra(QQ, -1, -1).ramified_primes() + sage: QuaternionAlgebra(-58, -69).ramified_primes() + [3, 23, 29] + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(-1, -1).ramified_primes() [2] + sage: QuaternionAlgebra(K, -1, -1).ramified_primes() + [] + sage: QuaternionAlgebra(L, -1, -1).ramified_primes() + [Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)] - sage: QuaternionAlgebra(QQ, -58, -69).ramified_primes() - [3, 23, 29] + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_primes() + [Fractional ideal (2)] + + sage: QuaternionAlgebra(RR(2.),1).ramified_primes() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise ValueError("base field must be the rational numbers") + return self.ramified_places(inf=False) + + @cached_method + def discriminant(self): + """ + Return the discriminant of the quaternion algebra ``self``, i.e. the product of the + finite places it ramifies at. - return sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), - prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), - prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + OUTPUT: + + The discriminant of this quaternion algebra (which has to be defined over a number field), + as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the + ring of integers of the base number field otherwise. + + EXAMPLES:: + + sage: QuaternionAlgebra(210, -22).discriminant() + 210 + sage: QuaternionAlgebra(19).discriminant() + 19 + sage: QuaternionAlgebra(-1, -1).discriminant() + 2 + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).discriminant() + Fractional ideal (1) + sage: QuaternionAlgebra(L, -1, -1).discriminant() + Fractional ideal (2) + + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).discriminant() + Fractional ideal (2) + + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic + Fractional ideal (1) + + sage: QuaternionAlgebra(RR(2.),1).discriminant() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field + """ + F = self.base_ring() + if is_RationalField(F): + return ZZ.prod(self.ramified_places(inf=False)) + + try: + return F.ideal(F.prod(self.ramified_places(inf=False))) + except NotImplementedError: + raise ValueError("base field must be rational numbers or a number field") def is_isomorphic(self, A) -> bool: """ - Return ``True`` if (and only if) ``self`` and ``A`` are isomorphic quaternion algebras over Q. + Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. + + Currently only implemented over a number field; motivated by + [Voi2021, Main Theorem 14.6.1], noting that QQ has a unique infinite place. INPUT: - - ``A`` -- a quaternion algebra defined over the rationals Q + - ``A`` -- a quaternion algebra defined over a number field EXAMPLES:: sage: B = QuaternionAlgebra(-46, -87) sage: A = QuaternionAlgebra(-58, -69) + sage: A == B + False sage: B.is_isomorphic(A) True - sage: A == B + + sage: K = QuadraticField(3) + sage: A = QuaternionAlgebra(K, -1, -1) + sage: B = QuaternionAlgebra(K, 1, -1) + sage: A.discriminant() == B.discriminant() + True + sage: B.is_isomorphic(A) False """ if not isinstance(A, QuaternionAlgebra_ab): raise TypeError("A must be a quaternion algebra of the form (a,b)_K") - if self.base_ring() != QQ or A.base_ring() != QQ: - raise NotImplementedError("isomorphism check only implemented for rational quaternion algebras") + F = self.base_ring() + if F != A.base_ring(): + raise ValueError("both quaternion algebras must be defined over the same base ring") - return self.ramified_primes() == A.ramified_primes() + try: + if is_RationalField(F): + return self.ramified_places(inf=False) == A.ramified_places(inf=False) + + ram_self = self.ramified_places(inf=True) + ram_A = A.ramified_places(inf=True) + return set(ram_self[0]) == set(ram_A[0]) and ram_self[1] == ram_A[1] + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def _magma_init_(self, magma): """ From 97f1257b5d51bc4ea76c092ee66b240f39af2b32 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 27 Jan 2024 03:55:59 +0100 Subject: [PATCH 02/21] Fixed lint issues and refernce formatting --- .../algebras/quatalg/quaternion_algebra.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index d479b69a4da..01c84d4e821 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -401,7 +401,7 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether + Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -1044,7 +1044,7 @@ def is_definite(self): """ Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative, see [Voi2021, Exercise 2.4(c)]. + invariants of ``self`` are negative, see Exercise 2.4(c) in [Voi2021]_. EXAMPLES:: @@ -1107,8 +1107,8 @@ def ramified_places(self, inf=True): Return the places of the base number field at which the quaternion algebra ``self`` ramifies. Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the - number field case) to check ramification for is motivated by [Voi2021, 12.4.12(a)]. The - restriction to real Archimedean embeddings is due to [Voi2021, 14.5.8]. + number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real Archimedean embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1116,9 +1116,9 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if - ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number - field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if + ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number + field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1175,7 +1175,7 @@ def ramified_places(self, inf=True): ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) - + if not inf: return ram_fin @@ -1199,7 +1199,6 @@ def ramified_places(self, inf=True): except (AttributeError, NotImplementedError): raise ValueError("base field must be rational numbers or a number field") - @cached_method def ramified_primes(self): """ @@ -1241,14 +1240,14 @@ def ramified_primes(self): def discriminant(self): """ Return the discriminant of the quaternion algebra ``self``, i.e. the product of the - finite places it ramifies at. + finite places it ramifies at. OUTPUT: The discriminant of this quaternion algebra (which has to be defined over a number field), as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the ring of integers of the base number field otherwise. - + EXAMPLES:: sage: QuaternionAlgebra(210, -22).discriminant() @@ -1261,7 +1260,7 @@ def discriminant(self): sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(K, -1, -1).discriminant() - Fractional ideal (1) + Fractional ideal (1) sage: QuaternionAlgebra(L, -1, -1).discriminant() Fractional ideal (2) @@ -1291,8 +1290,8 @@ def is_isomorphic(self, A) -> bool: """ Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. - Currently only implemented over a number field; motivated by - [Voi2021, Main Theorem 14.6.1], noting that QQ has a unique infinite place. + Currently only implemented over a number field; motivated by Main Theorem 14.6.1 + in [Voi2021]_, noting that QQ has a unique infinite place. INPUT: From 0bb5cfe4539e6bb45dc84ac0c0a7cd52b97cafac Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sun, 28 Jan 2024 00:16:14 +0100 Subject: [PATCH 03/21] Fixed typo and corrected/modified docstrings --- .../algebras/quatalg/quaternion_algebra.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 01c84d4e821..06083f287e0 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1086,7 +1086,7 @@ def is_totally_definite(self): sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() False - sage: QuaternionAlgebra(RR(2.),1).is_definite() + sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field @@ -1116,10 +1116,11 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if - ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number - field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean - (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ, + sorted small to large, if ``self`` is defined over the rational field QQ, respectively as + fractional ideals of the number field's ring of integers, otherwise) and, if ``inf`` is set + to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given + by real embeddings of the base field). EXAMPLES:: @@ -1161,11 +1162,11 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic ([], []) sage: QuaternionAlgebra(RR(2.),1).ramified_places() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ - if not isinstace(inf, bool): + if not isinstance(inf, bool): raise ValueError("inf must be a truth value") F = self.base_ring() @@ -1207,7 +1208,7 @@ def ramified_primes(self): OUTPUT: The list of finite primes at which ``self`` ramifies; given as integers, sorted - small-to-large, if ``self`` is defined over QQ, and as fractional ideals in the + small to large, if ``self`` is defined over QQ, and as fractional ideals in the ring of integers of the base number field otherwise. EXAMPLES:: @@ -1230,7 +1231,7 @@ def ramified_primes(self): [Fractional ideal (2)] sage: QuaternionAlgebra(RR(2.),1).ramified_primes() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ @@ -1273,7 +1274,7 @@ def discriminant(self): Fractional ideal (1) sage: QuaternionAlgebra(RR(2.),1).discriminant() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ From 6e95539541983ff74871d87400b0b5de5254c5fb Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 2 Feb 2024 22:52:08 +0100 Subject: [PATCH 04/21] Moved code in `.is_isomorphic`, edited some docstrings --- .../algebras/quatalg/quaternion_algebra.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 04fd2e866d7..71ee1022336 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1045,7 +1045,7 @@ def is_definite(self): """ Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative, see Exercise 2.4(c) in [Voi2021]_. + invariants of ``self`` are negative; see Exercise 2.4(c) in [Voi2021]_. EXAMPLES:: @@ -1323,10 +1323,10 @@ def is_isomorphic(self, A) -> bool: if F != A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same base ring") - try: - if is_RationalField(F): - return self.ramified_places(inf=False) == A.ramified_places(inf=False) + if is_RationalField(F): + return self.ramified_places(inf=False) == A.ramified_places(inf=False) + try: ram_self = self.ramified_places(inf=True) ram_A = A.ramified_places(inf=True) return set(ram_self[0]) == set(ram_A[0]) and ram_self[1] == ram_A[1] @@ -2021,7 +2021,8 @@ def is_maximal(self): r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. - Only works in quaternion algebras over number fields + Only implemented for quaternion algebras over number fields; for reference, + see Theorem 15.5.5 in [Voi2021]_. OUTPUT: Boolean @@ -3268,12 +3269,12 @@ def cyclic_right_subideals(self, p, alpha=None): def is_integral(self): r""" - Check if a quaternion fractional ideal is integral. An ideal in a quaternion algebra is - said integral if it is contained in its left order. If the left order is already defined it just - check the definition, otherwise it uses one of the alternative definition of Lemma 16.2.8 of - [Voi2021]_. + Checks whether a quaternion fractional ideal is integral. An ideal in a quaternion algebra + is integral if and only if it is contained in its left order. If the left order is already + defined this method just checks this definition, otherwise it uses one of the alternative + definitions from Lemma 16.2.8 of [Voi2021]_. - OUTPUT: a boolean. + OUTPUT: A boolean. EXAMPLES:: From 4032e432fa6cb771f65abcbdc21a9ca86c380221 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 17:42:34 +0100 Subject: [PATCH 05/21] Adapted `.ramified_places` slightly, removed docstring modifications --- src/doc/en/reference/references/index.rst | 3 +- .../algebras/quatalg/quaternion_algebra.py | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index b0194d1eef7..09b78514e1c 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6360,8 +6360,7 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. -.. [Voi2021] \J. Voight. Quaternion Algebras. Graduate Texts in - Mathematics 288. Springer Cham, 2021. +.. [Voi2021] \J. Voight. Quaternion algebras, Springer Nature (2021). .. [VS06] \G.D. Villa Salvador. Topics in the Theory of Algebraic Function Fields. Birkh\"auser, 2006. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index e655004fa6f..97ffc7c21bc 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1202,18 +1202,21 @@ def ramified_places(self, inf=True): return ram_fin, [] - try: - # Over the number field F, first compute the finite ramified places - ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), - F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + # At this point F needs to be a number field + # Note: Support for global function fields will be added in a future update + if not F is in NumberFields(): + raise NotImplementedError("base field must be rational numbers or a number field") + + # Over the number field F, first compute the finite ramified places + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), + F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] - if not inf: - return ram_fin + if not inf: + return ram_fin - # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] - except (AttributeError, NotImplementedError): - raise ValueError("base field must be rational numbers or a number field") + # At this point the infinite ramified places also need to be computed + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + @cached_method def ramified_primes(self): @@ -2035,8 +2038,7 @@ def is_maximal(self): r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. - Only implemented for quaternion algebras over number fields; for reference, - see Theorem 15.5.5 in [Voi2021]_. + Only works in quaternion algebras over number fields OUTPUT: Boolean @@ -3502,12 +3504,12 @@ def cyclic_right_subideals(self, p, alpha=None): def is_integral(self): r""" - Checks whether a quaternion fractional ideal is integral. An ideal in a quaternion algebra - is integral if and only if it is contained in its left order. If the left order is already - defined this method just checks this definition, otherwise it uses one of the alternative - definitions from Lemma 16.2.8 of [Voi2021]_. + Check if a quaternion fractional ideal is integral. An ideal in a quaternion algebra is + said integral if it is contained in its left order. If the left order is already defined it just + check the definition, otherwise it uses one of the alternative definition of Lemma 16.2.8 of + [Voi2021]_. - OUTPUT: A boolean. + OUTPUT: a boolean. EXAMPLES:: From 89dae3f4289aee0b8bfcb97ce2001e2f8b577a6a Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 18:02:52 +0100 Subject: [PATCH 06/21] Fixed LINT --- src/sage/algebras/quatalg/quaternion_algebra.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 97ffc7c21bc..1e7ff5184ca 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1204,9 +1204,9 @@ def ramified_places(self, inf=True): # At this point F needs to be a number field # Note: Support for global function fields will be added in a future update - if not F is in NumberFields(): - raise NotImplementedError("base field must be rational numbers or a number field") - + if F not in NumberFields(): + raise ValueError("base field must be rational numbers or a number field") + # Over the number field F, first compute the finite ramified places ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] @@ -1216,7 +1216,6 @@ def ramified_places(self, inf=True): # At this point the infinite ramified places also need to be computed return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] - @cached_method def ramified_primes(self): @@ -1300,10 +1299,7 @@ def discriminant(self): if is_RationalField(F): return ZZ.prod(self.ramified_places(inf=False)) - try: - return F.ideal(F.prod(self.ramified_places(inf=False))) - except NotImplementedError: - raise ValueError("base field must be rational numbers or a number field") + return F.ideal(F.prod(self.ramified_places(inf=False))) def is_isomorphic(self, A) -> bool: """ From deb764e284b0b401012ee871daea7fecffe7a4bb Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 21:26:42 +0100 Subject: [PATCH 07/21] Small rewrite of `.is_totally_definite()` --- src/sage/algebras/quatalg/quaternion_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 1e7ff5184ca..0880281edab 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1110,12 +1110,12 @@ def is_totally_definite(self): if is_RationalField(F): return self.is_definite() - try: - E = F.real_embeddings() - return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E - except (AttributeError, NotImplementedError): + if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") + E = F.real_embeddings() + return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + @cached_method def ramified_places(self, inf=True): """ From ad7d628d43e78e566077151e8838a13fb96a0e9a Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 9 Mar 2024 00:10:17 +0100 Subject: [PATCH 08/21] Cleaned up some code --- .../algebras/quatalg/quaternion_algebra.py | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 0880281edab..92c7efc852d 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,8 +403,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether - every nonzero element in ``self`` is invertible. + Check whether the quaternion algebra ``self`` is a division algebra, + i.e. whether every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -432,8 +432,8 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is isomorphic to the 2x2 matrix - ring over the base field. + Check whether the quaternion algebra ``self`` is isomorphic to the + 2x2 matrix ring over the base field. EXAMPLES:: @@ -1057,9 +1057,11 @@ def inner_product_matrix(self): def is_definite(self): """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative; see Exercise 2.4(c) in [Voi2021]_. + Check whether the quaternion algebra ``self`` is definite, i.e. whether + it ramifies at the unique Archimedean place of its base field `QQ`. + + A quaternion algebra over `QQ` is definite if and only if both of + its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1080,8 +1082,8 @@ def is_definite(self): def is_totally_definite(self): """ - Checks whether the quaternion algebra ``self`` is totally definite, i.e. whether it ramifies - at all real Archimedean places of its base number field. + Check whether the quaternion algebra ``self`` is totally definite, i.e. + whether it ramifies at all real Archimedean places of its base number field. EXAMPLES:: @@ -1119,11 +1121,13 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): """ - Return the places of the base number field at which the quaternion algebra ``self`` ramifies. + Return the places of the base number field at which the quaternion + algebra``self`` ramifies. - Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the - number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The - restriction to real Archimedean embeddings is due to 14.5.8 in [Voi2021]_. + Note: The initial choice of primes (in the case F = QQ) respectively + of prime ideals (in the number field case) to check ramification for + is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real + Archimedean embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1131,11 +1135,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ, - sorted small to large, if ``self`` is defined over the rational field QQ, respectively as - fractional ideals of the number field's ring of integers, otherwise) and, if ``inf`` is set - to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given - by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given + as elements of ZZ, sorted small to large, if ``self`` is defined over the + rational field QQ, respectively as fractional ideals of the number field's + ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the + Archimedean (AKA infinite) places at which ``self`` ramifies (given by + real embeddings of the base field). EXAMPLES:: @@ -1185,18 +1190,22 @@ def ramified_places(self, inf=True): raise ValueError("inf must be a truth value") F = self.base_ring() + a = self._a + b = self._b - # For efficiency (and to not convert QQ into a number field manually), we handle F = QQ first + # For efficiency (and to not convert QQ into a number field manually), + # we handle the case F = QQ first if is_RationalField(F): - ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), - prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), - prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + ram_fin = sorted([p for p in set([2]).union( + prime_divisors(a.numerator()), prime_divisors(a.denominator()), + prime_divisors(b.numerator()), prime_divisors(b.denominator())) + if hilbert_symbol(a, b, p) == -1]) if not inf: return ram_fin - # The given quaternion algebra ramifies at the unique infinite place of QQ, by definition, - # if and only if it is definite + # The given quaternion algebra ramifies at the unique infinite place + # of QQ, by definition, if and only if it is definite if self.is_definite(): return ram_fin, QQ.places() @@ -1208,14 +1217,14 @@ def ramified_places(self, inf=True): raise ValueError("base field must be rational numbers or a number field") # Over the number field F, first compute the finite ramified places - ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), - F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(a), + F.primes_above(b)) if F.hilbert_symbol(a, b, p) == -1] if not inf: return ram_fin # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(a, b, e) == -1] @cached_method def ramified_primes(self): @@ -1303,10 +1312,10 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. + Check whether ``self`` and ``A`` are isomorphic quaternion algebras. - Currently only implemented over a number field; motivated by Main Theorem 14.6.1 - in [Voi2021]_, noting that QQ has a unique infinite place. + Currently only implemented over a number field; motivated by Main + Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique infinite place. INPUT: @@ -1334,7 +1343,7 @@ def is_isomorphic(self, A) -> bool: F = self.base_ring() if F != A.base_ring(): - raise ValueError("both quaternion algebras must be defined over the same base ring") + raise ValueError("both quaternion algebras must be defined over the same ring") if is_RationalField(F): return self.ramified_places(inf=False) == A.ramified_places(inf=False) From a2c85642611ab051d574f031c160c6a9ec07335f Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Mon, 25 Mar 2024 18:00:41 +0100 Subject: [PATCH 09/21] Small doctest fix --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 7689248b5b5..59b8901b93e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1179,7 +1179,7 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> -0.618033988749895]) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic ([], []) sage: QuaternionAlgebra(RR(2.),1).ramified_places() Traceback (most recent call last): From 914d1b883d2ac5fbd8c866fed154bda4a82e128c Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Mon, 25 Mar 2024 18:18:05 +0100 Subject: [PATCH 10/21] Error type correction --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 59b8901b93e..ccab3266648 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1187,7 +1187,7 @@ def ramified_places(self, inf=True): ValueError: base field must be rational numbers or a number field """ if not isinstance(inf, bool): - raise ValueError("inf must be a truth value") + raise TypeError("inf must be a truth value") F = self.base_ring() a = self._a From 9c324bf9a5a351e3279c91180c68c37fe3f430f3 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:40:02 +0100 Subject: [PATCH 11/21] Remove redundant copy of `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 1b4972c373c..658f8c22869 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -930,29 +930,6 @@ def invariants(self): """ return self._a, self._b - def is_definite(self): - """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative. - - EXAMPLES:: - - sage: QuaternionAlgebra(QQ,-5,-2).is_definite() - True - sage: QuaternionAlgebra(1).is_definite() - False - - sage: QuaternionAlgebra(RR(2.),1).is_definite() - Traceback (most recent call last): - ... - ValueError: base field must be rational numbers - """ - if not is_RationalField(self.base_ring()): - raise ValueError("base field must be rational numbers") - a, b = self.invariants() - return a < 0 and b < 0 - def __eq__(self, other): """ Compare self and other. From 15f8fa5ccadffbaba5f5c7c35cf851c3523cf822 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:51:25 +0100 Subject: [PATCH 12/21] Some doctest refactoring Amend: Refactored `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 658f8c22869..82401e1224e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,8 +403,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Check whether the quaternion algebra ``self`` is a division algebra, - i.e. whether every nonzero element in ``self`` is invertible. + Check whether the quaternion algebra is a division algebra, + i.e. whether every nonzero element in it is invertible. EXAMPLES:: @@ -432,7 +432,7 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Check whether the quaternion algebra ``self`` is isomorphic to the + Check whether the quaternion algebra is isomorphic to the 2x2 matrix ring over the base field. EXAMPLES:: @@ -1056,11 +1056,11 @@ def inner_product_matrix(self): return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b]) def is_definite(self): - """ - Check whether the quaternion algebra ``self`` is definite, i.e. whether - it ramifies at the unique Archimedean place of its base field `QQ`. + r""" + Check whether the given quaternion algebra is definite, i.e. whether + it ramifies at the unique Archimedean place of its base field `\QQ`. - A quaternion algebra over `QQ` is definite if and only if both of + A quaternion algebra over `\QQ` is definite if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1121,13 +1121,13 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): """ - Return the places of the base number field at which the quaternion - algebra``self`` ramifies. + Return the places of the base number field at which the given + quaternion algebra ramifies. - Note: The initial choice of primes (in the case F = QQ) respectively - of prime ideals (in the number field case) to check ramification for - is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real - Archimedean embeddings is due to 14.5.8 in [Voi2021]_. + Note: The initial choice of primes (for the base field ``\\QQ``) + respectively of prime ideals (in the number field case) to check + ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1135,12 +1135,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given - as elements of ZZ, sorted small to large, if ``self`` is defined over the - rational field QQ, respectively as fractional ideals of the number field's - ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the - Archimedean (AKA infinite) places at which ``self`` ramifies (given by - real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies + (given as elements of ZZ, sorted small to large, if ``self`` is + defined over the rational field QQ, respectively as fractional ideals + of the number field's ring of integers, otherwise) and, if ``inf`` is + set to ``True``, also the Archimedean (AKA infinite) places at which + ``self`` ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1229,13 +1229,15 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): """ - Return the (finite) primes of the base number field at which the quaternion algebra ``self`` ramifies. + Return the (finite) primes of the base number field at which + the given quaternion algebra ramifies. OUTPUT: - The list of finite primes at which ``self`` ramifies; given as integers, sorted - small to large, if ``self`` is defined over QQ, and as fractional ideals in the - ring of integers of the base number field otherwise. + The list of finite primes at which ``self`` ramifies; given as + integers, sorted small to large, if ``self`` is defined over `\\QQ`, + and as fractional ideals in the ring of integers of the base number + field otherwise. EXAMPLES:: @@ -1265,15 +1267,16 @@ def ramified_primes(self): @cached_method def discriminant(self): - """ - Return the discriminant of the quaternion algebra ``self``, i.e. the product of the - finite places it ramifies at. + r""" + Return the discriminant of the given quaternion algebra, i.e. the + product of the finite places it ramifies at. OUTPUT: - The discriminant of this quaternion algebra (which has to be defined over a number field), - as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the - ring of integers of the base number field otherwise. + The discriminant of this quaternion algebra (which has to be defined + over a number field), as an element of `\ZZ` if the quaternion algebra + is defined over `\QQ`, and as a fractional ideal in the ring of + integers of the base number field otherwise. EXAMPLES:: @@ -1312,10 +1315,10 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Check whether ``self`` and ``A`` are isomorphic quaternion algebras. + Check whether the given quaternion algebra is isomorphic to ``A``. Currently only implemented over a number field; motivated by Main - Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique infinite place. + Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique real place. INPUT: From d09005c52d4933dbd1b4662e90b5e358ab7edfa1 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 14:17:53 +0100 Subject: [PATCH 13/21] More small refactoring Amend: Even more refactoring of `.is_definite()` and `.is_totally_definite()` Amend 2: Corrected backtick error --- .../algebras/quatalg/quaternion_algebra.py | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 82401e1224e..fd287b0f490 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,7 +403,7 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Check whether the quaternion algebra is a division algebra, + Check whether this quaternion algebra is a division algebra, i.e. whether every nonzero element in it is invertible. EXAMPLES:: @@ -432,7 +432,7 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Check whether the quaternion algebra is isomorphic to the + Check whether this quaternion algebra is isomorphic to the 2x2 matrix ring over the base field. EXAMPLES:: @@ -1057,8 +1057,8 @@ def inner_product_matrix(self): def is_definite(self): r""" - Check whether the given quaternion algebra is definite, i.e. whether - it ramifies at the unique Archimedean place of its base field `\QQ`. + Check whether this quaternion algebra is definite, i.e. whether + it ramifies at the unique real place of its base field `\QQ`. A quaternion algebra over `\QQ` is definite if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). @@ -1082,8 +1082,10 @@ def is_definite(self): def is_totally_definite(self): """ - Check whether the quaternion algebra ``self`` is totally definite, i.e. - whether it ramifies at all real Archimedean places of its base number field. + Check whether this quaternion algebra is totally definite. + + A quaternion algebra defined over a number field is totally definite + if it ramifies at all real places of its base field. EXAMPLES:: @@ -1120,11 +1122,11 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): - """ - Return the places of the base number field at which the given + r""" + Return the places of the base number field at which this quaternion algebra ramifies. - Note: The initial choice of primes (for the base field ``\\QQ``) + Note: The initial choice of primes (for the base field `\QQ`) respectively of prime ideals (in the number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real embeddings is due to 14.5.8 in [Voi2021]_. @@ -1135,12 +1137,13 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies - (given as elements of ZZ, sorted small to large, if ``self`` is - defined over the rational field QQ, respectively as fractional ideals - of the number field's ring of integers, otherwise) and, if ``inf`` is - set to ``True``, also the Archimedean (AKA infinite) places at which - ``self`` ramifies (given by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which the quaternion + algebra ramifies (given as elements of `\ZZ`, sorted small to + large, if the algebra is defined over the rational field `\QQ`, + respectively as fractional ideals of the number field's ring of + integers, otherwise) and, if ``inf`` is set to ``True``, also the + Archimedean (AKA infinite) places at which the quaternion algebra + ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1229,15 +1232,15 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): """ - Return the (finite) primes of the base number field at which - the given quaternion algebra ramifies. + Return the (finite) primes of the base number field at + which this quaternion algebra ramifies. OUTPUT: The list of finite primes at which ``self`` ramifies; given as - integers, sorted small to large, if ``self`` is defined over `\\QQ`, - and as fractional ideals in the ring of integers of the base number - field otherwise. + integers, sorted small to large, if ``self`` is defined over + `\\QQ`, and as fractional ideals in the ring of integers of the + base number field otherwise. EXAMPLES:: @@ -1268,7 +1271,7 @@ def ramified_primes(self): @cached_method def discriminant(self): r""" - Return the discriminant of the given quaternion algebra, i.e. the + Return the discriminant of this quaternion algebra, i.e. the product of the finite places it ramifies at. OUTPUT: @@ -1315,10 +1318,11 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Check whether the given quaternion algebra is isomorphic to ``A``. + Check whether this quaternion algebra is isomorphic to ``A``. - Currently only implemented over a number field; motivated by Main - Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique real place. + Currently only implemented over a number field; motivated by + Main Theorem 14.6.1 in [Voi2021]_, noting that `\\QQ` has a + unique infinite place. INPUT: From 12f44a34de1799e11248b2eb7ffe6b61c7393899 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 05:16:45 +0100 Subject: [PATCH 14/21] Implemented reviewer feedback - Rewrote some docstring descriptions - Broke apart examples into multiple blocks, with added flavor text - Modified docstring examples to emphasize certain features more - Refactored some larger descriptions to use bullet point lists Amend 1: Fixed missing space before NOTE Amend 2: Fixed indent of second line in bullet point list in `.discriminant()` --- .../algebras/quatalg/quaternion_algebra.py | 167 ++++++++++++------ 1 file changed, 116 insertions(+), 51 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index fd287b0f490..f2debb672cc 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -406,34 +406,46 @@ def is_division_algebra(self) -> bool: Check whether this quaternion algebra is a division algebra, i.e. whether every nonzero element in it is invertible. + Currently only implemented for quaternion algebras + defined over a number field. + EXAMPLES:: sage: QuaternionAlgebra(QQ,-5,-2).is_division_algebra() True - sage: QuaternionAlgebra(1).is_division_algebra() + sage: QuaternionAlgebra(2,9).is_division_algebra() False + By checking ramification, the methods correctly recognizes division + quaternion algebras over a number field even if they have trivial + discriminant:: + sage: K = QuadraticField(3) - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(K, -1, -1).is_division_algebra() - True - sage: QuaternionAlgebra(L, -1, -1).is_division_algebra() + sage: A = QuaternionAlgebra(K, -1, -1) + sage: A.discriminant() + Fractional ideal (1) + sage: A.is_division_algebra() True + The method is not implemented over arbitrary base rings yet:: + sage: QuaternionAlgebra(RR(2.),1).is_division_algebra() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers or a number field + NotImplementedError: base ring must be rational numbers or a number field """ try: return self.ramified_places(inf=True) != ([], []) except ValueError: - raise NotImplementedError("base field must be rational numbers or a number field") + raise NotImplementedError("base ring must be rational numbers or a number field") def is_matrix_ring(self) -> bool: """ Check whether this quaternion algebra is isomorphic to the - 2x2 matrix ring over the base field. + 2x2 matrix ring over the base ring. + + Currently only implemented for quaternion algebras + defined over a number field. EXAMPLES:: @@ -442,22 +454,28 @@ def is_matrix_ring(self) -> bool: sage: QuaternionAlgebra(2,9).is_matrix_ring() True + By checking ramification, the method is able to recognize that + quaternion algebras (defined over a number field) with trivial + discriminant need not be matrix rings:: + sage: K = QuadraticField(3) - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(K, -1, -1).is_matrix_ring() - False - sage: QuaternionAlgebra(L, -1, -1).is_matrix_ring() + sage: A = QuaternionAlgebra(K, -1, -1) + sage: A.discriminant() + Fractional ideal (1) + sage: A.is_matrix_ring() False + The method is not implemented over arbitrary base rings yet:: + sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers or a number field + NotImplementedError: base ring must be rational numbers or a number field """ try: return self.ramified_places(inf=True) == ([], []) except ValueError: - raise NotImplementedError("base field must be rational numbers or a number field") + raise NotImplementedError("base ring must be rational numbers or a number field") def is_exact(self) -> bool: """ @@ -1057,10 +1075,10 @@ def inner_product_matrix(self): def is_definite(self): r""" - Check whether this quaternion algebra is definite, i.e. whether - it ramifies at the unique real place of its base field `\QQ`. + Check whether this quaternion algebra is definite. - A quaternion algebra over `\QQ` is definite if and only if both of + A quaternion algebra over `\QQ` is definite if it ramifies at the + unique real place of `\QQ`, which happens if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1070,6 +1088,8 @@ def is_definite(self): sage: QuaternionAlgebra(1).is_definite() False + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).is_definite() Traceback (most recent call last): ... @@ -1096,15 +1116,15 @@ def is_totally_definite(self): sage: QuaternionAlgebra(K, -1, -1).is_totally_definite() True - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(L, -1, -1).is_totally_definite() - True + We can also use number field elements as invariants:: sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() False + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() Traceback (most recent call last): ... @@ -1117,8 +1137,8 @@ def is_totally_definite(self): if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") - E = F.real_embeddings() - return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + return all(F.hilbert_symbol(self._a, self._b, e) == -1 + for e in F.real_embeddings()) @cached_method def ramified_places(self, inf=True): @@ -1126,10 +1146,12 @@ def ramified_places(self, inf=True): Return the places of the base number field at which this quaternion algebra ramifies. - Note: The initial choice of primes (for the base field `\QQ`) - respectively of prime ideals (in the number field case) to check - ramification for is motivated by 12.4.12(a) in [Voi2021]_. The - restriction to real embeddings is due to 14.5.8 in [Voi2021]_. + .. NOTE:: + + The initial choice of primes (for the base field `\QQ`) + respectively of prime ideals (in the number field case) to check + ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1138,18 +1160,26 @@ def ramified_places(self, inf=True): OUTPUT: The non-Archimedean (AKA finite) places at which the quaternion - algebra ramifies (given as elements of `\ZZ`, sorted small to - large, if the algebra is defined over the rational field `\QQ`, - respectively as fractional ideals of the number field's ring of - integers, otherwise) and, if ``inf`` is set to ``True``, also the - Archimedean (AKA infinite) places at which the quaternion algebra - ramifies (given by real embeddings of the base field). + algebra ramifies, given as + + - elements of `\ZZ` (sorted small to large) if the algebra is + defined over `\QQ`, + + - fractional ideals in the ring of integers of the base number field, + otherwise. + + Additionally, if ``inf`` is set to ``True``, then the Archimedean + (AKA infinite) places at which the quaternion algebra ramifies are + also returned, given by real embeddings of the base field. EXAMPLES:: sage: QuaternionAlgebra(210,-22).ramified_places() ([2, 3, 5, 7], []) + For a definite quaternion algebra we get ramification at the + unique infinite place of `\QQ`:: + sage: QuaternionAlgebra(-1, -1).ramified_places() ([2], [Ring morphism: @@ -1157,6 +1187,15 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: 1 |--> 1.00000000000000]) + Extending the base field can resolve all ramification:: + + sage: F = QuadraticField(-1) + sage: QuaternionAlgebra(F, -1, -1).ramified_places() + ([], []) + + Extending the base field can resolve all ramification at finite places + while still leaving some ramification at infinite places:: + sage: K = QuadraticField(3) sage: QuaternionAlgebra(K, -1, -1).ramified_places() ([], @@ -1169,10 +1208,15 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> 1.73205080756888]) + Extending the base field can also get rid of ramification at infinite + places while still leaving some ramification at finite places:: + sage: L = QuadraticField(-15) sage: QuaternionAlgebra(L, -1, -1).ramified_places() ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_places() @@ -1182,8 +1226,8 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> -0.618033988749895]) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic - ([], []) + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).ramified_places() Traceback (most recent call last): ... @@ -1231,22 +1275,28 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): - """ + r""" Return the (finite) primes of the base number field at which this quaternion algebra ramifies. OUTPUT: - The list of finite primes at which ``self`` ramifies; given as - integers, sorted small to large, if ``self`` is defined over - `\\QQ`, and as fractional ideals in the ring of integers of the - base number field otherwise. + The list of finite primes at which ``self`` ramifies, given as + + - elements of `\ZZ` (sorted small to large) if the algebra is + defined over `\QQ`, + + - fractional ideals in the ring of integers of the base number field, + otherwise. EXAMPLES:: sage: QuaternionAlgebra(-58, -69).ramified_primes() [3, 23, 29] + Under field extensions, the number of ramified primes can increase + or decrease:: + sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(-1, -1).ramified_primes() @@ -1256,11 +1306,15 @@ def ramified_primes(self): sage: QuaternionAlgebra(L, -1, -1).ramified_primes() [Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)] + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_primes() [Fractional ideal (2)] + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).ramified_primes() Traceback (most recent call last): ... @@ -1271,15 +1325,19 @@ def ramified_primes(self): @cached_method def discriminant(self): r""" - Return the discriminant of this quaternion algebra, i.e. the - product of the finite places it ramifies at. + Return the discriminant of this quaternion algebra. + + The discriminant of a quaternion algebra over a number field is the + product of the finite places at which the algebra ramifies. OUTPUT: - The discriminant of this quaternion algebra (which has to be defined - over a number field), as an element of `\ZZ` if the quaternion algebra - is defined over `\QQ`, and as a fractional ideal in the ring of - integers of the base number field otherwise. + The discriminant of this quaternion algebra, as + + - an element of `\ZZ` if the algebra is defined over `\QQ`, + + - a fractional ideal in the ring of integers of the base number + field, otherwise. EXAMPLES:: @@ -1290,6 +1348,8 @@ def discriminant(self): sage: QuaternionAlgebra(-1, -1).discriminant() 2 + Some examples over number fields:: + sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(K, -1, -1).discriminant() @@ -1297,13 +1357,14 @@ def discriminant(self): sage: QuaternionAlgebra(L, -1, -1).discriminant() Fractional ideal (2) + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).discriminant() Fractional ideal (2) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic - Fractional ideal (1) + The method does not make sense over an arbitrary base ring:: sage: QuaternionAlgebra(RR(2.),1).discriminant() Traceback (most recent call last): @@ -1320,9 +1381,9 @@ def is_isomorphic(self, A) -> bool: """ Check whether this quaternion algebra is isomorphic to ``A``. - Currently only implemented over a number field; motivated by - Main Theorem 14.6.1 in [Voi2021]_, noting that `\\QQ` has a - unique infinite place. + Currently only implemented for quaternion algebras defined over + a number field; based on Main Theorem 14.6.1 in [Voi2021]_, + noting that `\\QQ` has a unique infinite place. INPUT: @@ -1337,6 +1398,10 @@ def is_isomorphic(self, A) -> bool: sage: B.is_isomorphic(A) True + Checking ramification at both finite and infinite places, the method + correctly distinguishes isomorphism classes of quaternion algebras + that the discriminant can not distinguish:: + sage: K = QuadraticField(3) sage: A = QuaternionAlgebra(K, -1, -1) sage: B = QuaternionAlgebra(K, 1, -1) @@ -1349,7 +1414,7 @@ def is_isomorphic(self, A) -> bool: raise TypeError("A must be a quaternion algebra of the form (a,b)_K") F = self.base_ring() - if F != A.base_ring(): + if F is not A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same ring") if is_RationalField(F): From aec353447f0fefd3efc15e9bf33f7350f292b8bd Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 14:45:15 +0100 Subject: [PATCH 15/21] Small docstring modifications --- .../algebras/quatalg/quaternion_algebra.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f2debb672cc..17d5d5e7475 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -416,7 +416,7 @@ def is_division_algebra(self) -> bool: sage: QuaternionAlgebra(2,9).is_division_algebra() False - By checking ramification, the methods correctly recognizes division + By checking ramification, the method correctly recognizes division quaternion algebras over a number field even if they have trivial discriminant:: @@ -1150,7 +1150,7 @@ def ramified_places(self, inf=True): The initial choice of primes (for the base field `\QQ`) respectively of prime ideals (in the number field case) to check - ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + ramification for is based on 12.4.12(a) in [Voi2021]_. The restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1159,14 +1159,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which the quaternion + The non-Archimedean (AKA finite) places at which this quaternion algebra ramifies, given as - - elements of `\ZZ` (sorted small to large) if the algebra is - defined over `\QQ`, + - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`, - - fractional ideals in the ring of integers of the base number field, - otherwise. + - integral fractional ideals of the base number field, otherwise. Additionally, if ``inf`` is set to ``True``, then the Archimedean (AKA infinite) places at which the quaternion algebra ramifies are @@ -1193,8 +1191,8 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(F, -1, -1).ramified_places() ([], []) - Extending the base field can resolve all ramification at finite places - while still leaving some ramification at infinite places:: + Extending the base field can also resolve all ramification at finite + places while still leaving some ramification at infinite places:: sage: K = QuadraticField(3) sage: QuaternionAlgebra(K, -1, -1).ramified_places() @@ -1215,7 +1213,7 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(L, -1, -1).ramified_places() ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) - We can also use number field elements as invariants:: + We can use number field elements as invariants as well:: sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) @@ -1281,13 +1279,12 @@ def ramified_primes(self): OUTPUT: - The list of finite primes at which ``self`` ramifies, given as + The list of finite primes at which this quaternion algebra ramifies, + given as - - elements of `\ZZ` (sorted small to large) if the algebra is - defined over `\QQ`, + - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`, - - fractional ideals in the ring of integers of the base number field, - otherwise. + - integral fractional ideals of the base number field, otherwise. EXAMPLES:: @@ -1332,12 +1329,11 @@ def discriminant(self): OUTPUT: - The discriminant of this quaternion algebra, as + The discriminant of this quaternion algebra, given as - an element of `\ZZ` if the algebra is defined over `\QQ`, - - a fractional ideal in the ring of integers of the base number - field, otherwise. + - an integral fractional ideal of the base number field, otherwise. EXAMPLES:: From c0b8a6572ade213efa3646d53dcbcc4d65288cd5 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 29 Mar 2024 00:59:55 +0100 Subject: [PATCH 16/21] Corrected `.is_totally_definite()`, moved .. NOTE - Added missing check that the base field of a totally definite algebra needs to be totally real - Moved note in `.ramified_places` below input and moved the interal info on initial choice of finite primes to code comments --- .../algebras/quatalg/quaternion_algebra.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 17d5d5e7475..b89110144d7 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1104,8 +1104,10 @@ def is_totally_definite(self): """ Check whether this quaternion algebra is totally definite. - A quaternion algebra defined over a number field is totally definite - if it ramifies at all real places of its base field. + A quaternion algebra defined over a number field is + totally definite if it ramifies at all Archimedean + places of its base field. In particular, the base number + field has to be totally real (see 14.5.8 in [Voi2021]_). EXAMPLES:: @@ -1137,8 +1139,12 @@ def is_totally_definite(self): if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") - return all(F.hilbert_symbol(self._a, self._b, e) == -1 - for e in F.real_embeddings()) + # Since we need the list of real embeddings of the number field (instead + # of just the number of them), we avoid a call of the `is_totally_real()`- + # method by directly comparing the embedding list's length to the degree + E = F.real_embeddings() + return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 + for e in E) @cached_method def ramified_places(self, inf=True): @@ -1146,13 +1152,6 @@ def ramified_places(self, inf=True): Return the places of the base number field at which this quaternion algebra ramifies. - .. NOTE:: - - The initial choice of primes (for the base field `\QQ`) - respectively of prime ideals (in the number field case) to check - ramification for is based on 12.4.12(a) in [Voi2021]_. The - restriction to real embeddings is due to 14.5.8 in [Voi2021]_. - INPUT: - ``inf`` -- (default: ``True``) @@ -1170,6 +1169,11 @@ def ramified_places(self, inf=True): (AKA infinite) places at which the quaternion algebra ramifies are also returned, given by real embeddings of the base field. + .. NOTE:: + + Any Archimedean place at which a quaternion algebra ramifies + has to be real (see 14.5.8 in [Voi2021]_). + EXAMPLES:: sage: QuaternionAlgebra(210,-22).ramified_places() @@ -1238,6 +1242,10 @@ def ramified_places(self, inf=True): a = self._a b = self._b + # The initial choice of primes (for the base field QQ) respectively + # of prime ideals (in the number field case) to check ramification + # for is based on 12.4.12(a) in [Voi2021]_. + # For efficiency (and to not convert QQ into a number field manually), # we handle the case F = QQ first if is_RationalField(F): From da2a8050e4477f09c16b1de86065c2cce0b7af3d Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 29 Mar 2024 01:43:00 +0100 Subject: [PATCH 17/21] Added missing type descriptor for `inf` argument --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index b89110144d7..d6d245c2b89 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1154,7 +1154,7 @@ def ramified_places(self, inf=True): INPUT: - - ``inf`` -- (default: ``True``) + - ``inf`` -- bool (default: ``True``) OUTPUT: From 74c1c4ddea47af6cd010a455b53a8f51b28c9a57 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 6 Apr 2024 18:23:15 +0200 Subject: [PATCH 18/21] Implemented reviewer feedback --- src/sage/algebras/quatalg/quaternion_algebra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index d6d245c2b89..be1a2a622b8 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1090,7 +1090,7 @@ def is_definite(self): The method does not make sense over an arbitrary base ring:: - sage: QuaternionAlgebra(RR(2.),1).is_definite() + sage: QuaternionAlgebra(RR(2.), 1).is_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers @@ -1127,7 +1127,7 @@ def is_totally_definite(self): The method does not make sense over an arbitrary base ring:: - sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() + sage: QuaternionAlgebra(RR(2.), 1).is_totally_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field @@ -1144,7 +1144,7 @@ def is_totally_definite(self): # method by directly comparing the embedding list's length to the degree E = F.real_embeddings() return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 - for e in E) + for e in E) @cached_method def ramified_places(self, inf=True): @@ -1382,12 +1382,12 @@ def discriminant(self): return F.ideal(F.prod(self.ramified_places(inf=False))) def is_isomorphic(self, A) -> bool: - """ + r""" Check whether this quaternion algebra is isomorphic to ``A``. Currently only implemented for quaternion algebras defined over a number field; based on Main Theorem 14.6.1 in [Voi2021]_, - noting that `\\QQ` has a unique infinite place. + noting that `\QQ` has a unique infinite place. INPUT: From 642629675a579712d36a51e2c54ef4418e035c3e Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sun, 14 Apr 2024 01:03:07 +0200 Subject: [PATCH 19/21] Changed number field embedding target to `AA` - Avoids possible future issues caused by rounding precision - Seems to be more efficient Amend: Docstring fix --- .../algebras/quatalg/quaternion_algebra.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index bff96a9d8d9..caf8e1e28e3 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -55,6 +55,7 @@ from sage.rings.rational_field import is_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.power_series_ring import PowerSeriesRing from sage.structure.category_object import normalize_names @@ -1235,7 +1236,7 @@ def is_totally_definite(self): # Since we need the list of real embeddings of the number field (instead # of just the number of them), we avoid a call of the `is_totally_real()`- # method by directly comparing the embedding list's length to the degree - E = F.real_embeddings() + E = F.embeddings(AA) return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 for e in E) @@ -1260,7 +1261,11 @@ def ramified_places(self, inf=True): Additionally, if ``inf`` is set to ``True``, then the Archimedean (AKA infinite) places at which the quaternion algebra ramifies are - also returned, given by real embeddings of the base field. + also returned, given as + + - the embeddings of `\QQ` into `\RR` if the base field is `\QQ`, or + + - the embeddings of the base number field into the Algebraic Real Field. .. NOTE:: @@ -1296,12 +1301,12 @@ def ramified_places(self, inf=True): ([], [Ring morphism: From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? - To: Real Field with 53 bits of precision - Defn: a |--> -1.73205080756888, + To: Algebraic Real Field + Defn: a |--> -1.732050807568878?, Ring morphism: From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? - To: Real Field with 53 bits of precision - Defn: a |--> 1.73205080756888]) + To: Algebraic Real Field + Defn: a |--> 1.732050807568878?]) Extending the base field can also get rid of ramification at infinite places while still leaving some ramification at finite places:: @@ -1318,8 +1323,8 @@ def ramified_places(self, inf=True): ([Fractional ideal (2)], [Ring morphism: From: Number Field in a with defining polynomial x^2 - x - 1 - To: Real Field with 53 bits of precision - Defn: a |--> -0.618033988749895]) + To: Algebraic Real Field + Defn: a |--> -0.618033988749895?]) The method does not make sense over an arbitrary base ring:: @@ -1370,7 +1375,7 @@ def ramified_places(self, inf=True): return ram_fin # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(a, b, e) == -1] + return ram_fin, [e for e in F.embeddings(AA) if F.hilbert_symbol(a, b, e) == -1] @cached_method def ramified_primes(self): From 187988a4dd20b82bf294a9bc86cc9818879b2056 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 25 Jun 2024 10:50:48 +0200 Subject: [PATCH 20/21] Removed duplicate of `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f3394d8b731..3c2e3ee5b8c 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1078,29 +1078,6 @@ def invariants(self): """ return self._a, self._b - def is_definite(self): - """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative. - - EXAMPLES:: - - sage: QuaternionAlgebra(QQ,-5,-2).is_definite() - True - sage: QuaternionAlgebra(1).is_definite() - False - - sage: QuaternionAlgebra(RR(2.),1).is_definite() - Traceback (most recent call last): - ... - ValueError: base field must be rational numbers - """ - if not isinstance(self.base_ring(), RationalField): - raise ValueError("base field must be rational numbers") - a, b = self.invariants() - return a < 0 and b < 0 - def __eq__(self, other): """ Compare self and other. @@ -1248,7 +1225,7 @@ def is_definite(self): ... ValueError: base field must be rational numbers """ - if not is_RationalField(self.base_ring()): + if not isinstance(self.base_ring(), RationalField): raise ValueError("base field must be rational numbers") a, b = self.invariants() return a < 0 and b < 0 From e783d8bdf01b5e3b0385fac7d48cb326c72347e1 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 25 Jun 2024 17:13:21 +0200 Subject: [PATCH 21/21] Replace instances of `is_RationalField` with according `isinstance`-calls --- src/sage/algebras/quatalg/quaternion_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3c2e3ee5b8c..6be502c63ec 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1263,7 +1263,7 @@ def is_totally_definite(self): ValueError: base field must be rational numbers or a number field """ F = self.base_ring() - if is_RationalField(F): + if isinstance(F, RationalField): return self.is_definite() if F not in NumberFields(): @@ -1382,7 +1382,7 @@ def ramified_places(self, inf=True): # For efficiency (and to not convert QQ into a number field manually), # we handle the case F = QQ first - if is_RationalField(F): + if isinstance(F, RationalField): ram_fin = sorted([p for p in set([2]).union( prime_divisors(a.numerator()), prime_divisors(a.denominator()), prime_divisors(b.numerator()), prime_divisors(b.denominator())) @@ -1510,7 +1510,7 @@ def discriminant(self): ValueError: base field must be rational numbers or a number field """ F = self.base_ring() - if is_RationalField(F): + if isinstance(F, RationalField): return ZZ.prod(self.ramified_places(inf=False)) return F.ideal(F.prod(self.ramified_places(inf=False))) @@ -1555,7 +1555,7 @@ def is_isomorphic(self, A) -> bool: if F is not A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same ring") - if is_RationalField(F): + if isinstance(F, RationalField): return self.ramified_places(inf=False) == A.ramified_places(inf=False) try: