From 41f217c951ea29ba10293c8cdb2f7393d2a906ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 21 Oct 2025 16:47:23 +0200 Subject: [PATCH 1/6] new proposal for Jordan algebras and python 3.14 --- src/sage/algebras/jordan_algebra.py | 173 +++++++++++++++------------- 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index 162b0e67303..d378bd926db 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -9,12 +9,12 @@ Jordan algebra """ -#***************************************************************************** +# *************************************************************************** # Copyright (C) 2014, 2023 Travis Scrimshaw # # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ -#***************************************************************************** +# *************************************************************************** from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -165,10 +165,16 @@ def __classcall_private__(self, arg0, arg1=None, names=None): sage: cat = Algebras(QQ).WithBasis().FiniteDimensional() sage: C = CombinatorialFreeModule(QQ, ['x','y','z'], category=cat) - sage: J1 = JordanAlgebra(C, names=['a','b','c']) + sage: J1 = JordanAlgebra(C, names=['a','b','c']); J1 + Jordan algebra of Free module generated by {'x', 'y', 'z'} + over Rational Field + + In this case, there following syntactic sugar no longer works:: + sage: J2. = JordanAlgebra(C) - sage: J1 is J2 - True + Traceback (most recent call last): + ... + NotImplementedError: infinite set We check with passing in a symmetric bilinear form:: @@ -209,7 +215,7 @@ def __classcall_private__(self, arg0, arg1=None, names=None): if not arg1.is_symmetric(): raise ValueError("the bilinear form is not symmetric") - arg1 = arg1.change_ring(arg0) # This makes a copy + arg1 = arg1.change_ring(arg0) # This makes a copy arg1.set_immutable() return JordanAlgebraSymmetricBilinear(arg0, arg1, names=names) @@ -240,7 +246,7 @@ class SpecialJordanAlgebra(JordanAlgebra): r""" A (special) Jordan algebra `A^+` from an associative algebra `A`. """ - def __init__(self, A, names=None): + def __init__(self, A, names=None) -> None: """ Initialize ``self``. @@ -250,7 +256,8 @@ def __init__(self, A, names=None): sage: J = JordanAlgebra(F) sage: TestSuite(J).run() sage: J.category() - Category of commutative unital algebras with basis over Rational Field + Category of commutative unital algebras with basis + over Rational Field """ R = A.base_ring() C = MagmaticAlgebras(R) @@ -276,9 +283,10 @@ def _repr_(self) -> str: sage: F. = FreeAlgebra(QQ) sage: JordanAlgebra(F) - Jordan algebra of Free Algebra on 3 generators (x, y, z) over Rational Field + Jordan algebra of Free Algebra on 3 generators (x, y, z) + over Rational Field """ - return "Jordan algebra of {}".format(self._A) + return f"Jordan algebra of {self._A}" def _element_constructor_(self, x): """ @@ -311,7 +319,7 @@ def _an_element_(self): return self.element_class(self, self._A.an_element()) @cached_method - def basis(self): + def basis(self) -> Family: """ Return the basis of ``self``. @@ -323,21 +331,25 @@ def basis(self): Lazy family (Term map(i))_{i in Free monoid on 3 generators (x, y, z)} """ B = self._A.basis() - return Family(B.keys(), lambda x: self.element_class(self, B[x]), name="Term map") + return Family(B.keys(), lambda x: self.element_class(self, B[x]), + name="Term map") algebra_generators = basis # TODO: Keep this until we can better handle R.<...> shorthand def gens(self) -> tuple: """ - Return the generators of ``self``. + Return the generators of ``self`` as a tuple. + + This fails with :exc:`NotImplementedError` + when there are infinitely many. EXAMPLES:: sage: cat = Algebras(QQ).WithBasis().FiniteDimensional() sage: C = CombinatorialFreeModule(QQ, ['x','y','z'], category=cat) sage: J = JordanAlgebra(C) - sage: J.gens() + sage: tuple(map(J, C.gens())) (B['x'], B['y'], B['z']) sage: F. = FreeAlgebra(QQ) @@ -347,7 +359,10 @@ def gens(self) -> tuple: ... NotImplementedError: infinite set """ - return tuple(self.algebra_generators()) + F = self.algebra_generators() + if self.is_finite(): + return tuple(F) + raise NotImplementedError('infinite set') @cached_method def zero(self): @@ -381,7 +396,7 @@ class Element(AlgebraElement): """ An element of a special Jordan algebra. """ - def __init__(self, parent, x): + def __init__(self, parent, x) -> None: """ Initialize ``self``. @@ -438,7 +453,7 @@ def __bool__(self) -> bool: """ return bool(self._x) - def __eq__(self, other): + def __eq__(self, other) -> bool: """ Check equality. @@ -461,7 +476,7 @@ def __eq__(self, other): return False return self._x == other._x - def __ne__(self, other): + def __ne__(self, other) -> bool: """ Check inequality. @@ -575,7 +590,7 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._x) - def monomial_coefficients(self, copy=True): + def monomial_coefficients(self, copy=True) -> dict: """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding @@ -603,8 +618,15 @@ def monomial_coefficients(self, copy=True): class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. + + TESTS:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J = JordanAlgebra(m) + sage: J.gens() + (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ - def __init__(self, R, form, names=None): + def __init__(self, R, form, names=None) -> None: """ Initialize ``self``. @@ -707,7 +729,8 @@ def _element_constructor_(self, *args): return self.element_class(self, R(s), self._M.zero()) - if len(args) == 2 and (isinstance(args[1], (list, tuple)) or args[1] in self._M): + if len(args) == 2 and (isinstance(args[1], (list, tuple)) + or args[1] in self._M): return self.element_class(self, R(args[0]), self._M(args[1])) if len(args) == self._form.ncols() + 1: @@ -741,7 +764,7 @@ def _coerce_map_from_base_ring(self): return self._generic_coerce_map(self.base_ring()) @cached_method - def basis(self): + def basis(self) -> Family: """ Return a basis of ``self``. @@ -763,19 +786,6 @@ def basis(self): algebra_generators = basis - def gens(self) -> tuple: - """ - Return the generators of ``self``. - - EXAMPLES:: - - sage: m = matrix([[0,1],[1,1]]) - sage: J = JordanAlgebra(m) - sage: J.gens() - (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) - """ - return tuple(self.algebra_generators()) - @cached_method def zero(self): """ @@ -808,7 +818,7 @@ class Element(AlgebraElement): """ An element of a Jordan algebra defined by a symmetric bilinear form. """ - def __init__(self, parent, s, v): + def __init__(self, parent, s, v) -> None: """ Initialize ``self``. @@ -866,7 +876,7 @@ def __bool__(self) -> bool: """ return bool(self._s) or bool(self._v) - def __eq__(self, other): + def __eq__(self, other) -> bool: """ Check equality. @@ -891,7 +901,7 @@ def __eq__(self, other): return False return self._s == other._s and self._v == other._v - def __ne__(self, other): + def __ne__(self, other) -> bool: """ Check inequality. @@ -974,7 +984,7 @@ def _mul_(self, other): P = self.parent() return self.__class__(P, self._s * other._s - + (self._v * P._form * other._v.column())[0], + + (self._v * P._form * other._v.column())[0], other._s * self._v + self._s * other._v) def _lmul_(self, other): @@ -1004,7 +1014,7 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._s, other * self._v) - def monomial_coefficients(self, copy=True): + def monomial_coefficients(self, copy=True) -> dict: """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding @@ -1023,7 +1033,7 @@ def monomial_coefficients(self, copy=True): {0: 1, 1: 2, 2: -1} """ d = {0: self._s} - for i,c in enumerate(self._v): + for i, c in enumerate(self._v): d[i+1] = c return d @@ -1150,7 +1160,7 @@ class ExceptionalJordanAlgebra(JordanAlgebra): - :wikipedia:`Hurwitz's_theorem_(composition_algebras)#Applications_to_Jordan_algebras` - ``_ """ - def __init__(self, O): + def __init__(self, Octo) -> None: r""" Initialize ``self``. @@ -1174,16 +1184,34 @@ def __init__(self, O): Traceback (most recent call last): ... ValueError: 2 must be invertible + + TESTS:: + + sage: O = OctonionAlgebra(QQ) + sage: J = JordanAlgebra(O) + sage: G = J.gens() + sage: G[0] + [1 0 0] + [0 0 0] + [0 0 0] + sage: G[5] + [ 0 j 0] + [-j 0 0] + [ 0 0 0] + sage: G[22] + [ 0 0 0] + [ 0 0 k] + [ 0 -k 0] """ - self._O = O - R = O.base_ring() + self._O = Octo + R = Octo.base_ring() if not R(2).is_unit(): raise ValueError("2 must be invertible") self._half = R(2).inverse_of_unit() from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - Onames = list(O.variable_names()) + Onames = list(Octo.variable_names()) Onames.extend(Onames[3] + Onames[i] for i in range(3)) self._repr_poly_ring = PolynomialRing(R, Onames) @@ -1258,8 +1286,8 @@ def _test_multiplication_self_adjoint(self, **options): [SD[3].conjugate(), SD[1], SD[5]], [SD[4].conjugate(), SD[5].conjugate(), SD[2]]] Y = [[OD[0], OD[3], OD[4]], - [OD[3].conjugate(), OD[1], OD[5]], - [OD[4].conjugate(), OD[5].conjugate(), OD[2]]] + [OD[3].conjugate(), OD[1], OD[5]], + [OD[4].conjugate(), OD[5].conjugate(), OD[2]]] for r, c in data_pairs: if r != c: val = sum(X[r][i] * Y[i][c] + Y[r][i] * X[i][c] for i in range(3)) * self._half @@ -1270,7 +1298,7 @@ def _test_multiplication_self_adjoint(self, **options): tester.assertEqual(val.imag_part(), zerO) @cached_method - def basis(self): + def basis(self) -> Family: r""" Return a basis of ``self``. @@ -1315,30 +1343,6 @@ def basis(self): algebra_generators = basis - def gens(self) -> tuple: - """ - Return the generators of ``self``. - - EXAMPLES:: - - sage: O = OctonionAlgebra(QQ) - sage: J = JordanAlgebra(O) - sage: G = J.gens() - sage: G[0] - [1 0 0] - [0 0 0] - [0 0 0] - sage: G[5] - [ 0 j 0] - [-j 0 0] - [ 0 0 0] - sage: G[22] - [ 0 0 0] - [ 0 0 k] - [ 0 -k 0] - """ - return tuple(self.algebra_generators()) - @cached_method def zero(self): r""" @@ -1377,7 +1381,7 @@ def one(self): zero = self._O.zero() return self.element_class(self, (one, one, one, zero, zero, zero)) - def some_elements(self): + def some_elements(self) -> list: r""" Return some elements of ``self``. @@ -1456,14 +1460,15 @@ def some_elements(self): B[1], B[5], B[17], B[1] + self._half*B[25], self.one() + B[13] + 2*B[16]] S.append(sum(B[::5])) - S.append(sum(self._half * ind * b for ind, b in enumerate(B[::7], start=2))) + S.append(sum(self._half * ind * b + for ind, b in enumerate(B[::7], start=2))) return S class Element(AlgebraElement): r""" An element of an exceptional Jordan algebra. """ - def __init__(self, parent, data): + def __init__(self, parent, data) -> None: """ Initialize ``self``. @@ -1494,14 +1499,18 @@ def _to_print_matrix(self): PR = self.parent()._repr_poly_ring gens = [PR.one()] + list(PR.gens()) data = [PR(self._data[i]) for i in range(3)] - data.extend(PR.sum(c * g for c, g in zip(self._data[3+i].vector(), gens)) + data.extend(PR.sum(c * g + for c, g in zip(self._data[3+i].vector(), gens)) for i in range(3)) # add the conjugates for i in range(1, 8): gens[i] = -gens[i] - data.extend(PR.sum(c * g for c, g in zip(self._data[3+i].vector(), gens)) + data.extend(PR.sum(c * g + for c, g in zip(self._data[3+i].vector(), gens)) for i in range(3)) - return matrix(PR, [[data[0], data[3], data[4]], [data[6], data[1], data[5]], [data[7], data[8], data[2]]]) + return matrix(PR, [[data[0], data[3], data[4]], + [data[6], data[1], data[5]], + [data[7], data[8], data[2]]]) def _repr_(self) -> str: r""" @@ -1583,7 +1592,7 @@ def __bool__(self) -> bool: """ return any(d for d in self._data) - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: r""" Rich comparison of ``self`` with ``other`` by ``op``. @@ -1684,8 +1693,8 @@ def _mul_(self, other): [SD[3].conjugate(), SD[1], SD[5]], [SD[4].conjugate(), SD[5].conjugate(), SD[2]]] Y = [[OD[0], OD[3], OD[4]], - [OD[3].conjugate(), OD[1], OD[5]], - [OD[4].conjugate(), OD[5].conjugate(), OD[2]]] + [OD[3].conjugate(), OD[1], OD[5]], + [OD[4].conjugate(), OD[5].conjugate(), OD[2]]] # we do a simplified multiplication for the diagonal entries since # we have, e.g., \alpha * \alpha' + (x (x')^* + x' x^* + y (y')^* + y' y^*) / 2 ret = [X[0][0] * Y[0][0] + (X[0][1] * Y[1][0]).real_part() + (X[0][2] * Y[2][0]).real_part(), @@ -1734,7 +1743,7 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), [other * c for c in self._data]) - def monomial_coefficients(self, copy=True): + def monomial_coefficients(self, copy=True) -> dict: r""" Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding From 8d7418dbdd4fe289628c59a36647e1f3a5b81db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 23 Oct 2025 08:05:31 +0200 Subject: [PATCH 2/6] fixing mistake --- src/sage/algebras/jordan_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index d378bd926db..cd75f808064 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -360,7 +360,7 @@ def gens(self) -> tuple: NotImplementedError: infinite set """ F = self.algebra_generators() - if self.is_finite(): + if F.is_finite(): return tuple(F) raise NotImplementedError('infinite set') From 558146c6a52ac95f1e5d0ec54c4a5eb3a3d1da66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 23 Oct 2025 12:05:33 +0200 Subject: [PATCH 3/6] refine category of free monoids --- src/sage/monoids/free_monoid.py | 15 ++++++++------- src/sage/monoids/monoid.py | 9 ++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 32196e004dc..0bb588f5188 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -24,16 +24,15 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.integer import Integer -from sage.structure.category_object import normalize_names from .free_monoid_element import FreeMonoidElement - from .monoid import Monoid_class +from sage.categories.monoids import Monoids from sage.combinat.words.finite_word import FiniteWord_class - -from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ +from sage.structure.category_object import normalize_names +from sage.structure.unique_representation import UniqueRepresentation def is_FreeMonoid(x): @@ -197,7 +196,9 @@ def __init__(self, n, names=None): if n < 0: raise ValueError("n (=%s) must be nonnegative" % n) self.__ngens = int(n) - Monoid_class.__init__(self, names) + if names: + cat = Monoids().Infinite() + Monoid_class.__init__(self, names, category=cat) def _repr_(self): return f"Free monoid on {self.__ngens} generators {self.gens()}" @@ -329,6 +330,6 @@ def cardinality(self): 1 """ if self.__ngens == 0: - return Integer(1) + return ZZ.one() from sage.rings.infinity import infinity return infinity diff --git a/src/sage/monoids/monoid.py b/src/sage/monoids/monoid.py index f6862e2615e..ff80fbc0a54 100644 --- a/src/sage/monoids/monoid.py +++ b/src/sage/monoids/monoid.py @@ -37,7 +37,7 @@ def is_Monoid(x) -> bool: class Monoid_class(Parent): - def __init__(self, names): + def __init__(self, names, category=None): r""" EXAMPLES:: @@ -51,8 +51,11 @@ def __init__(self, names): sage: TestSuite(F).run() """ from sage.categories.monoids import Monoids - category = Monoids().FinitelyGeneratedAsMagma() - Parent.__init__(self, base=self, names=names, category=category) + if category is None: + cat = Monoids().FinitelyGeneratedAsMagma() + else: + cat = category & Monoids().FinitelyGeneratedAsMagma() + Parent.__init__(self, base=self, names=names, category=cat) @cached_method def gens(self) -> tuple: From fe4a95fbfe008faf7462d4c5954c634e70d73415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 23 Oct 2025 13:35:53 +0200 Subject: [PATCH 4/6] fix category --- src/sage/monoids/free_monoid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 0bb588f5188..4d6afef8e9f 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -196,8 +196,7 @@ def __init__(self, n, names=None): if n < 0: raise ValueError("n (=%s) must be nonnegative" % n) self.__ngens = int(n) - if names: - cat = Monoids().Infinite() + cat = Monoids().Infinite() if names else None Monoid_class.__init__(self, names, category=cat) def _repr_(self): From 802dca9dc0db6292afe87de6e8b97572d44e95b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 23 Oct 2025 14:03:50 +0200 Subject: [PATCH 5/6] fix doctest --- src/sage/algebras/jordan_algebra.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index cd75f808064..a8ff7f1e724 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -169,12 +169,9 @@ def __classcall_private__(self, arg0, arg1=None, names=None): Jordan algebra of Free module generated by {'x', 'y', 'z'} over Rational Field - In this case, there following syntactic sugar no longer works:: + In this case, the following syntactic sugar works:: sage: J2. = JordanAlgebra(C) - Traceback (most recent call last): - ... - NotImplementedError: infinite set We check with passing in a symmetric bilinear form:: From 5e9e980ebf2764628c778c3039e60e34f0a61c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 23 Oct 2025 14:17:20 +0200 Subject: [PATCH 6/6] add back doctest --- src/sage/algebras/jordan_algebra.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index a8ff7f1e724..94f347acc02 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -172,6 +172,8 @@ def __classcall_private__(self, arg0, arg1=None, names=None): In this case, the following syntactic sugar works:: sage: J2. = JordanAlgebra(C) + sage: J1 is J2 + True We check with passing in a symmetric bilinear form::