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/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..ed27f7a41bb 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 @@ -2024,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..f5eb173479c 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 + + :: + + 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)(ring.one()) @matrix_method 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): 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..12761978673 --- /dev/null +++ b/src/sage/rings/semirings/tropical_matrix.py @@ -0,0 +1,199 @@ +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): + 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()]) + 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): + 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: + 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): + 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() + [ 0 -2 -2] + [ 2 0 0] + [ 2 0 0] + + :: + + 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