From 1f53e212ad523039f780097ce3b18bbd656f784e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 19 Nov 2025 14:04:03 +0100 Subject: [PATCH 1/5] mean weight and transitive closure --- src/sage/matrix/action.pyx | 2 +- src/sage/matrix/matrix_space.py | 31 +++++++++---- src/sage/rings/semirings/meson.build | 1 + src/sage/rings/semirings/tropical_matrix.py | 50 +++++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 src/sage/rings/semirings/tropical_matrix.py diff --git a/src/sage/matrix/action.pyx b/src/sage/matrix/action.pyx index 732a1312ca1..f40cf212066 100644 --- a/src/sage/matrix/action.pyx +++ b/src/sage/matrix/action.pyx @@ -279,7 +279,7 @@ cdef class MatrixMatrixAction(MatrixMulAction): B = B.dense_matrix() else: A = A.dense_matrix() - assert type(A) is type(B), (type(A), type(B)) + # assert type(A) is type(B), (type(A), type(B)) prod = A._matrix_times_matrix_(B) if A._subdivisions is not None or B._subdivisions is not None: Asubs = A.subdivisions() diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index eafab99b0e9..32f4162c85c 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -50,6 +50,8 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.superseded import deprecated_function_alias from sage.misc.persist import register_unpickle_override +from sage.categories.sets_cat import Sets +from sage.categories.semirings import Semirings from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.categories.enumerated_sets import EnumeratedSets @@ -60,7 +62,6 @@ feature=Meataxe()) lazy_import('sage.groups.matrix_gps.matrix_group', ['MatrixGroup_base']) -_Rings = Rings() _Fields = Fields() @@ -320,6 +321,15 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): else: return matrix_laurent_mpolynomial_dense.Matrix_laurent_mpolynomial_dense + try: + from sage.rings.semirings.tropical_semiring import TropicalSemiring + except ImportError: + pass + else: + if isinstance(R, TropicalSemiring): + from sage.rings.semirings import tropical_matrix + return tropical_matrix.Matrix_tropical_dense + # The fallback from sage.matrix.matrix_generic_dense import Matrix_generic_dense return Matrix_generic_dense @@ -725,8 +735,8 @@ def __classcall__(cls, base_ring, sage: MS2._my_option False """ - if base_ring not in _Rings: - raise TypeError("base_ring (=%s) must be a ring" % base_ring) + if base_ring not in Semirings(): + raise TypeError("base_ring (=%s) must be a ring or a semiring" % base_ring) if ncols_or_column_keys is not None: try: @@ -898,12 +908,17 @@ def __init__(self, base_ring, nrows, ncols, sparse, implementation): from sage.categories.modules import Modules from sage.categories.algebras import Algebras - if nrows == ncols: - category = Algebras(base_ring.category()) + if base_ring in Rings(): + if nrows == ncols: + category = Algebras(base_ring.category()) + else: + category = Modules(base_ring.category()) + category = category.WithBasis().FiniteDimensional() else: - category = Modules(base_ring.category()) - - category = category.WithBasis().FiniteDimensional() + if nrows == ncols: + category = Semirings() + else: + category = Sets() if not self.__nrows or not self.__ncols: is_finite = True diff --git a/src/sage/rings/semirings/meson.build b/src/sage/rings/semirings/meson.build index b92ea837850..0a9762b756d 100644 --- a/src/sage/rings/semirings/meson.build +++ b/src/sage/rings/semirings/meson.build @@ -2,6 +2,7 @@ py.install_sources( '__init__.py', 'all.py', 'non_negative_integer_semiring.py', + 'tropical_matrix.py', 'tropical_mpolynomial.py', 'tropical_polynomial.py', 'tropical_semiring.pyx', diff --git a/src/sage/rings/semirings/tropical_matrix.py b/src/sage/rings/semirings/tropical_matrix.py new file mode 100644 index 00000000000..3a5061ccaec --- /dev/null +++ b/src/sage/rings/semirings/tropical_matrix.py @@ -0,0 +1,50 @@ +from sage.rings.infinity import infinity +from sage.matrix.constructor import matrix +from sage.matrix.matrix_generic_dense import Matrix_generic_dense + +class Matrix_tropical_dense(Matrix_generic_dense): + def extremum_mean_weight(self): + # Karp algorithm + T = self.base_ring() + n = self.ncols() + if self.nrows() != n: + raise TypeError("matrix must be square") + v = matrix(1, n, n*[T.one()]) # ??? + vs = [v] + for _ in range(n): + v = v * self + vs.append(v) + w = [vs[n][0,j].lift() for j in range(n)] + if T._use_min: + return min(max((w[j] - vs[k][0,j].lift()) / (n-k) for k in range(n)) + for j in range(n) if w[j] is not infinity) + else: + return max(min((w[j] - vs[k][0,j].lift()) / (n-k) for k in range(n)) + for j in range(n) if w[j] is not infinity) + + def weak_transitive_closure(self): + # Floyd-Warshall algorithm + T = self.base_ring() + n = self.ncols() + if self.nrows() != n: + raise TypeError("matrix must be square") + G = self.__copy__() + for p in range(n): + for i in range(n): + if i == p: + continue + for j in range(n): + if j == p: + continue + G[i,j] += G[i,p] * G[p,j] + if i == j: + if T._use_min and G[i,i].lift() < 0: + raise ValueError("negative cycle exists") + if not T._use_min and G[i,i].lift() > 0: + raise ValueError("positive cycle exists") + return G + + def strong_transitive_closure(self): + return self.parent().identity_matrix() + self.weak_transitive_closure() + + kleene_star = strong_transitive_closure From e4df0397357040697d5d28ddfc2a7e9edbd0205b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 19 Nov 2025 17:16:15 +0100 Subject: [PATCH 2/5] extremum cycle mean, documentation --- src/doc/en/reference/references/index.rst | 3 + src/doc/en/reference/semirings/index.rst | 4 + src/sage/rings/semirings/tropical_matrix.py | 159 +++++++++++++++++++- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1f1cd007290..1e687b6b192 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1432,6 +1432,9 @@ REFERENCES: An Algorithmic Approach, Algorithms and Computation in Mathematics, Volume 20, Springer (2007) +.. [But2010] Peter Butkovič, *Max-linear systems. Theory and algorithms.* + Springer Monographs in Mathematics. London: Springer. xvii, 272 p. (2010). + .. [Buell89] Duncan A. Buell. *Binary Quadratic Forms: Classical Theory and Modern Computations.* Springer, 1989. diff --git a/src/doc/en/reference/semirings/index.rst b/src/doc/en/reference/semirings/index.rst index b40e71c54e1..3bbb10edd0c 100644 --- a/src/doc/en/reference/semirings/index.rst +++ b/src/doc/en/reference/semirings/index.rst @@ -6,5 +6,9 @@ Standard Semirings sage/rings/semirings/non_negative_integer_semiring sage/rings/semirings/tropical_semiring + sage/rings/semirings/tropical_polynomial + sage/rings/semirings/tropical_mpolynomial + sage/rings/semirings/tropical_matrix + sage/rings/semirings/tropical_variety .. include:: ../footer.txt diff --git a/src/sage/rings/semirings/tropical_matrix.py b/src/sage/rings/semirings/tropical_matrix.py index 3a5061ccaec..838956589e5 100644 --- a/src/sage/rings/semirings/tropical_matrix.py +++ b/src/sage/rings/semirings/tropical_matrix.py @@ -1,15 +1,73 @@ -from sage.rings.infinity import infinity +r""" +Matrices over tropical semirings + +AUTHORS: + +- Xavier Caruso (2025-11): initial version +""" + +# **************************************************************************** +# Copyright (C) 2025 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + from sage.matrix.constructor import matrix from sage.matrix.matrix_generic_dense import Matrix_generic_dense +from sage.rings.infinity import infinity + class Matrix_tropical_dense(Matrix_generic_dense): - def extremum_mean_weight(self): - # Karp algorithm + r""" + A class for dense matrices over a tropical semiring. + + EXAMPLES:: + + sage: from sage.rings.semirings.tropical_matrix import Matrix_tropical_dense + sage: T = TropicalSemiring(QQ) + sage: M = matrix(T, [[1, 2], [3, 4]]) + sage: isinstance(M, Matrix_tropical_dense) + True + """ + def extremum_cycle_mean(self): + r""" + Return the extremal (that is, minimal if the addition is max + and maximum is the addition is min) mean weight of this matrix + It is also the smallest/largest eigenvalue of this matrix. + + ALGORITHM: + + We implement Karp's algorithm described in []_, Section 1.6.1. + + EXAMPLES:: + + sage: T = TropicalSemiring(QQ, use_min=False) + sage: M = matrix(T, [[-2, 1, -3], + ....: [ 3, 0, 3], + ....: [ 5, 2, 1]]) + sage: M.extremum_cycle_mean() + 3 + + :: + + sage: T = TropicalSemiring(QQ) + sage: z = T.zero() + sage: M = matrix(T, [[z, 1, 10, z], + ....: [z, z, 3, z], + ....: [z, z, z, 2], + ....: [8, 0, z, z]]) + sage: M.extremum_cycle_mean() + 5/3 + """ T = self.base_ring() n = self.ncols() if self.nrows() != n: raise TypeError("matrix must be square") - v = matrix(1, n, n*[T.one()]) # ??? + v = matrix(1, n, n*[T.one()]) vs = [v] for _ in range(n): v = v * self @@ -23,7 +81,57 @@ def extremum_mean_weight(self): for j in range(n) if w[j] is not infinity) def weak_transitive_closure(self): - # Floyd-Warshall algorithm + r""" + Return the weak transitive closure of this matrix `M`, + that is, by definition + + .. MATH:: + + A \oplus A^2 \oplus A^3 \oplus A^4 \oplus \cdots + + or raise an error if this sum does not converge. + + ALGORITHM: + + We implement the Floyd-Warshall algorithm described in + [But2010]_, Algorithm 1.6.21. + + EXAMPLES:: + + sage: T = TropicalSemiring(QQ) + sage: z = T.zero() + sage: M = matrix(T, [[z, 1, 10, z], + ....: [z, z, 3, z], + ....: [z, z, z, 2], + ....: [8, 0, z, z]]) + sage: M.weak_transitive_closure() + [14 1 4 6] + [13 5 3 5] + [10 2 5 2] + [ 8 0 3 5] + + We check that the minimal cycle mean of `M` is the largest + value `a` such that `(-a) \otimes M` has a weak transitive + closure:: + + sage: M.extremum_cycle_mean() + 5/3 + sage: aM = T(-5/3) * M + sage: aM.weak_transitive_closure() + [22/3 -2/3 2/3 1] + [ 8 0 4/3 5/3] + [20/3 -4/3 0 1/3] + [19/3 -5/3 -1/3 0] + sage: bM = T(-2) * M + sage: bM.weak_transitive_closure() + Traceback (most recent call last): + ... + ValueError: negative cycle exists + + .. SEEALSO:: + + :meth:`strong_transitive_closure` + """ T = self.base_ring() n = self.ncols() if self.nrows() != n: @@ -45,6 +153,47 @@ def weak_transitive_closure(self): return G def strong_transitive_closure(self): + r""" + Return the string transitive closure of this matrix `M`, + that is, by definition + + .. MATH:: + + I \oplus A \oplus A^2 \oplus A^3 \oplus A^4 \oplus \cdots + + or raise an error if this sum does not converge. + + ALGORITHM: + + We implement the Floyd-Warshall algorithm described in + [But2010]_, Algorithm 1.6.21. + + EXAMPLES:: + + sage: T = TropicalSemiring(QQ, use_min=False) + sage: M = matrix(T, [[-5, -2, -6], + ....: [ 0, -3, 0], + ....: [ 2, -1, -2]]) + sage: M.strong_transitive_closure() + [ 1 -2 -2] + [ 2 1 0] + [ 2 0 1] + + :: + + sage: T = TropicalSemiring(QQ) + sage: M = matrix(T, [[-5, -2, -6], + ....: [ 0, -3, 0], + ....: [ 2, -1, -2]]) + sage: M.strong_transitive_closure() + Traceback (most recent call last): + ... + ValueError: negative cycle exists + + .. SEEALSO:: + + :meth:`weak_transitive_closure` + """ return self.parent().identity_matrix() + self.weak_transitive_closure() kleene_star = strong_transitive_closure From c28ee4ad8d1b87afd2c03264ae5dd5c8480c2661 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 19 Nov 2025 22:23:58 +0100 Subject: [PATCH 3/5] identity matrix --- src/sage/matrix/matrix_space.py | 3 ++- src/sage/matrix/special.py | 10 +++++++++- src/sage/rings/semirings/tropical_matrix.py | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 32f4162c85c..ed27f7a41bb 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -2039,8 +2039,9 @@ def identity_matrix(self): if self.__nrows != self.__ncols: raise TypeError("identity matrix must be square") A = self.zero_matrix().__copy__() + one = self.base_ring().one() for i in range(self.__nrows): - A[i, i] = 1 + A[i, i] = one A.set_immutable() return A diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index 7182c27662f..b0b5484a655 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -936,11 +936,19 @@ def identity_matrix(ring, n=0, sparse=False): Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring sage: M.is_mutable() True + + TESTS:: + + sage: T = TropicalSemiring(QQ) + sage: identity_matrix(T, 3) + [ 0 +infinity +infinity] + [+infinity 0 +infinity] + [+infinity +infinity 0] """ if isinstance(ring, (Integer, int)): n = ring ring = ZZ - return matrix_space.MatrixSpace(ring, n, n, sparse)(1) + return matrix_space.MatrixSpace(ring, n, n, sparse).identity_matrix() @matrix_method diff --git a/src/sage/rings/semirings/tropical_matrix.py b/src/sage/rings/semirings/tropical_matrix.py index 838956589e5..12761978673 100644 --- a/src/sage/rings/semirings/tropical_matrix.py +++ b/src/sage/rings/semirings/tropical_matrix.py @@ -175,9 +175,9 @@ def strong_transitive_closure(self): ....: [ 0, -3, 0], ....: [ 2, -1, -2]]) sage: M.strong_transitive_closure() - [ 1 -2 -2] - [ 2 1 0] - [ 2 0 1] + [ 0 -2 -2] + [ 2 0 0] + [ 2 0 0] :: From 264796ec859b7a45cdf1eb10c8da158921ba55d1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 20 Nov 2025 18:11:42 +0100 Subject: [PATCH 4/5] identity_matrix should return a mutable matrix --- src/sage/matrix/special.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index b0b5484a655..52b3e865ed0 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -948,7 +948,7 @@ def identity_matrix(ring, n=0, sparse=False): if isinstance(ring, (Integer, int)): n = ring ring = ZZ - return matrix_space.MatrixSpace(ring, n, n, sparse).identity_matrix() + return matrix_space.MatrixSpace(ring, n, n, sparse)(ring.one()) @matrix_method From f1bc7ecb89e5fec369c9dbc462520da7e660834b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 20 Nov 2025 21:09:45 +0100 Subject: [PATCH 5/5] doctest is sagedoc.py --- src/sage/matrix/special.py | 2 +- src/sage/misc/sagedoc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index 52b3e865ed0..f5eb173479c 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -937,7 +937,7 @@ def identity_matrix(ring, n=0, sparse=False): sage: M.is_mutable() True - TESTS:: + :: sage: T = TropicalSemiring(QQ) sage: identity_matrix(T, 3) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index ab5ff639dd6..7ff39a8a7ee 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -1454,7 +1454,7 @@ class _sage_doc: sage: browse_sage_doc._open("reference", testing=True)[0] # needs sagemath_doc_html 'http://localhost:8000/doc/live/reference/index.html' - sage: browse_sage_doc(identity_matrix, 'rst')[-107:-47] # needs sage.modules + sage: browse_sage_doc(identity_matrix, 'rst')[-311:-251] # needs sage.modules '...Full MatrixSpace of 3 by 3 sparse matrices...' """ def __init__(self):