From 4a6505981845b6ff4b64baf3ddb19704dd0ffa95 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 1 Mar 2024 14:23:08 -0800 Subject: [PATCH 01/22] matrix: Handle column_keys, row_keys --- src/sage/matrix/args.pxd | 12 ++++++++++- src/sage/matrix/args.pyx | 36 +++++++++++++++++++++++++++++++-- src/sage/matrix/constructor.pyx | 29 +++++++++++++++++--------- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index fc26bc35914..519f55e8455 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -47,6 +47,7 @@ cdef class MatrixArgs: cdef public Parent space # parent of matrix cdef public Parent base # parent of entries cdef public long nrows, ncols + cdef public object row_keys, column_keys cdef public object entries cdef entries_type typ cdef public bint sparse @@ -89,7 +90,11 @@ cdef class MatrixArgs: raise ArithmeticError("number of columns must be non-negative") cdef long p = self.ncols if p != -1 and p != n: - raise ValueError(f"inconsistent number of columns: should be {p} but got {n}") + raise ValueError(f"inconsistent number of columns: should be {p} " + f"but got {n}") + if self.column_keys is not None and n != len(self.column_keys): + raise ValueError(f"inconsistent number of columns: should be cardinality of {self.column_keys} " + f"but got {n}") self.ncols = n cdef inline int set_nrows(self, long n) except -1: @@ -102,8 +107,13 @@ cdef class MatrixArgs: cdef long p = self.nrows if p != -1 and p != n: raise ValueError(f"inconsistent number of rows: should be {p} but got {n}") + if self.row_keys is not None and n != len(self.row_keys): + raise ValueError(f"inconsistent number of rows: should be cardinality of {self.row_keys} " + f"but got {n}") self.nrows = n + cpdef int set_column_keys(self, column_keys) except -1 + cpdef int set_row_keys(self, row_keys) except -1 cpdef int set_space(self, space) except -1 cdef int finalize(self) except -1 diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 0284b822ac9..908abc43cf7 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -311,7 +311,8 @@ cdef class MatrixArgs: self.sparse = -1 self.kwds = {} - def __init__(self, *args, ring=None, nrows=None, ncols=None, entries=None, sparse=None, space=None, **kwds): + def __init__(self, *args, ring=None, nrows=None, ncols=None, entries=None, + sparse=None, row_keys=None, column_keys=None, space=None, **kwds): """ Parse arguments for creating a new matrix. @@ -354,6 +355,10 @@ cdef class MatrixArgs: self.entries = entries if sparse is not None: self.sparse = sparse + if row_keys is not None: + self.set_row_keys(row_keys) + if column_keys is not None: + self.set_column_keys(column_keys) if space is not None: self.set_space(space) self.kwds.update(kwds) @@ -380,7 +385,7 @@ cdef class MatrixArgs: # check nrows and ncols argument cdef int k cdef long v - if self.nrows == -1 and self.ncols == -1: + if self.nrows == -1 and self.ncols == -1 and self.row_keys is None and self.column_keys is None: for k in range(2): arg = args[argi] if is_numpy_type(type(arg)): @@ -829,6 +834,33 @@ cdef class MatrixArgs: D[se.i, se.j] = x return D + cpdef int set_column_keys(self, column_keys) except -1: + """ + Set the column keys with consistency checking: if the + value was previously set, it must remain the same. + """ + if self.column_keys is not None and self.column_keys != column_keys: + raise ValueError(f"inconsistent column keys: should be {self.column_keys} " + f"but got {column_keys}") + cdef long p = self.ncols + if p != -1 and p != len(column_keys): + raise ValueError(f"inconsistent column keys: should be of cardinality {self.ncols} " + f"but got {column_keys}") + self.column_keys = column_keys + + cpdef int set_row_keys(self, row_keys) except -1: + """ + Set the row keys with consistency checking: if the + value was previously set, it must remain the same. + """ + if self.row_keys is not None and self.row_keys != row_keys: + raise ValueError(f"inconsistent row keys: should be {self.row_keys} " + f"but got {row_keys}") + if self.nrows != -1 and self.nrows != len(row_keys): + raise ValueError(f"inconsistent row keys: should be of cardinality {self.nrows} " + f"but got {row_keys}") + self.row_keys = row_keys + cpdef int set_space(self, space) except -1: """ Set inputs from a given matrix space. diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index ee5cb2ae16a..09909afa79e 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -62,14 +62,19 @@ def matrix(*args, **kwds): determine this from the given entries, falling back to ``ZZ`` if no entries are given. - - ``nrows`` -- the number of rows in the matrix. + - ``nrows`` or ``row_keys`` -- the number of rows in the matrix, + or a finite or enumerated family of arbitrary objects + that index the rows of the matrix - - ``ncols`` -- the number of columns in the matrix. + - ``ncols`` or ``column_keys`` -- the number of columns in the + matrix, or a finite or enumerated family of arbitrary objects + that index the columns of the matrix - ``entries`` -- see examples below. - If either ``nrows`` or ``ncols`` is given as keyword argument, then - no positional arguments ``nrows`` and ``ncols`` may be given. + If any of ``nrows``, ``ncols``, ``row_keys``, ``column_keys`` is + given as keyword argument, then none of these may be given as + positional arguments. Keyword-only arguments: @@ -78,16 +83,15 @@ def matrix(*args, **kwds): defaults to ``False``. - ``space`` -- matrix space which will be the parent of the output - matrix. This determines ``ring``, ``nrows``, ``ncols`` and - ``sparse``. + matrix. This determines ``ring``, ``nrows``, ``row_keys``, + ``ncols``, ``column_keys``, and ``sparse``. - ``immutable`` -- (boolean) make the matrix immutable. By default, the output matrix is mutable. - OUTPUT: - - a matrix + OUTPUT: a matrix or, more generally, a homomorphism between free + modules. EXAMPLES:: @@ -241,6 +245,13 @@ def matrix(*args, **kwds): ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + Using ``row_keys`` and ``column_keys``:: + + sage: M = matrix([[1,2,3], [4,5,6]], + ....: column_keys=['a','b','c'], row_keys=['u','v']); M + [1 2 3] + [4 5 6] + TESTS: There are many ways to create an empty matrix:: From faf609459d0f3284c0058503c72cc4635be09492 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 1 Mar 2024 19:56:00 -0800 Subject: [PATCH 02/22] matrix: Delegate to new method MatrixArgs.element --- src/sage/matrix/args.pyx | 9 +++++++++ src/sage/matrix/constructor.pyx | 5 +---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 908abc43cf7..0a14e1064cb 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -732,6 +732,15 @@ cdef class MatrixArgs: self.typ = MA_ENTRIES_MATRIX return M + cpdef element(self, bint convert=True, bint immutable=False) noexcept: + r""" + Return the element. + """ + cdef Matrix M = self.matrix(convert) + if immutable: + M.set_immutable() + return M + cpdef list list(self, bint convert=True) noexcept: """ Return the entries of the matrix as a flat list of scalars. diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index 09909afa79e..8820af26fdb 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -656,10 +656,7 @@ def matrix(*args, **kwds): :class:`MatrixArgs`, see :issue:`24742` """ immutable = kwds.pop('immutable', False) - M = MatrixArgs(*args, **kwds).matrix() - if immutable: - M.set_immutable() - return M + return MatrixArgs(*args, **kwds).element(immutable=immutable) Matrix = matrix From c0689fe46de613251c17497a6b3fa6fb36dda283 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 1 Mar 2024 19:56:43 -0800 Subject: [PATCH 03/22] MatrixArgs.set_space: Handle case of homset --- src/sage/matrix/args.pyx | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 0a14e1064cb..a1c99f77609 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -876,7 +876,7 @@ cdef class MatrixArgs: INPUT: - - ``space`` -- a :class:`MatrixSpace` + - ``space`` -- a :class:`MatrixSpace` or a homset of modules with basis EXAMPLES:: @@ -893,12 +893,35 @@ cdef class MatrixArgs: [0 0] sage: M.parent() is S True + + From a homset:: + + sage: C = CombinatorialFreeModule(ZZ, ['a', 'b', 'c']) + sage: R = CombinatorialFreeModule(ZZ, ['u', 'v']) + sage: S = Hom(C, R); S + Set of Morphisms + from Free module generated by {'a', 'b', 'c'} over Integer Ring + to Free module generated by {'u', 'v'} over Integer Ring + in Category of finite dimensional modules with basis over Integer Ring + sage: ma = MatrixArgs() + sage: _ = ma.set_space(S) + sage: ma.finalized() + """ self.space = space - self.set_nrows(space.nrows()) - self.set_ncols(space.ncols()) - self.base = space._base - self.sparse = space.is_sparse() + try: + self.set_nrows(space.nrows()) + self.set_ncols(space.ncols()) + self.base = space._base + self.sparse = space.is_sparse() + except AttributeError: + self.set_row_keys(space.codomain().basis().keys()) + self.set_column_keys(space.domain().basis().keys()) + self.base = space.base_ring() def finalized(self): """ From 5ab473afa5386d700b843d8941a36ced730ba45a Mon Sep 17 00:00:00 2001 From: Luze Xu Date: Mon, 4 Mar 2024 09:04:15 -0800 Subject: [PATCH 04/22] fix MatrixArgs.element --- src/sage/matrix/args.pxd | 1 + src/sage/matrix/args.pyx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index 519f55e8455..fdf6d923e4d 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -55,6 +55,7 @@ cdef class MatrixArgs: cdef bint is_finalized cpdef Matrix matrix(self, bint convert=?) noexcept + cpdef Matrix element(self, bint immutable=?, bint convert=?) noexcept cpdef list list(self, bint convert=?) noexcept cpdef dict dict(self, bint convert=?) noexcept diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index a1c99f77609..19c5ec73bf1 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -732,7 +732,7 @@ cdef class MatrixArgs: self.typ = MA_ENTRIES_MATRIX return M - cpdef element(self, bint convert=True, bint immutable=False) noexcept: + cpdef Matrix element(self, bint immutable=False, bint convert=True) noexcept: r""" Return the element. """ From b00bcde1b5d48f204cbe124befb063782ec52d6c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 12:43:36 -0700 Subject: [PATCH 05/22] WIP --- src/sage/matrix/args.pxd | 2 +- src/sage/matrix/args.pyx | 44 +++++++++++++++++++++-------- src/sage/modules/matrix_morphism.py | 2 +- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index fdf6d923e4d..335c1e901d9 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -55,7 +55,7 @@ cdef class MatrixArgs: cdef bint is_finalized cpdef Matrix matrix(self, bint convert=?) noexcept - cpdef Matrix element(self, bint immutable=?, bint convert=?) noexcept + cpdef element(self, bint immutable=?, bint convert=?) noexcept cpdef list list(self, bint convert=?) noexcept cpdef dict dict(self, bint convert=?) noexcept diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 19c5ec73bf1..e2c7232a7de 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -374,13 +374,15 @@ cdef class MatrixArgs: if self.entries is None and isinstance(arg, (list, tuple, dict)): self.entries = arg argc -= 1 - if argi == argc: return + if argi == argc: + return # check for base ring argument if self.base is None and isinstance(args[argi], Parent): self.base = args[argi] argi += 1 - if argi == argc: return + if argi == argc: + return # check nrows and ncols argument cdef int k @@ -725,21 +727,27 @@ cdef class MatrixArgs: M = M.__copy__() break else: - M = self.space(self, coerce=convert) + space = self.space + if not isinstance(space, MatrixSpace): + space = space.zero().matrix(side='right').parent() + M = space(self, coerce=convert) # Also store the matrix to support multiple calls of matrix() self.entries = M self.typ = MA_ENTRIES_MATRIX return M - cpdef Matrix element(self, bint immutable=False, bint convert=True) noexcept: + cpdef element(self, bint immutable=False, bint convert=True) noexcept: r""" Return the element. """ + self.finalize() cdef Matrix M = self.matrix(convert) if immutable: M.set_immutable() - return M + if isinstance(self.space, MatrixSpace): + return M + return self.space(M) cpdef list list(self, bint convert=True) noexcept: """ @@ -912,6 +920,8 @@ cdef class MatrixArgs: in Category of finite dimensional modules with basis over Integer Ring; typ=ZERO; entries=None> """ + if self.space is not None: + return 0 # TODO: ?????? self.space = space try: self.set_nrows(space.nrows()) @@ -1003,6 +1013,7 @@ cdef class MatrixArgs: # Can we assume a square matrix? if self.typ & MA_FLAG_ASSUME_SQUARE: + # TODO: Handle column_keys/row_keys if self.ncols == -1: if self.nrows != -1: self.ncols = self.nrows @@ -1035,7 +1046,7 @@ cdef class MatrixArgs: # Error if size is required if self.typ & MA_FLAG_DIM_REQUIRED: - if self.nrows == -1 or self.ncols == -1: + if (self.nrows == -1 and self.row_keys is None) or (self.ncols == -1 and self.column_keys is None): raise TypeError("the dimensions of the matrix must be specified") # Determine base in easy cases @@ -1051,7 +1062,9 @@ cdef class MatrixArgs: if self.base is None: raise TypeError(f"unable to determine base of {self.entries!r}") - if self.nrows == -1 or self.ncols == -1 or self.base is None: + if ((self.nrows == -1 and self.row_keys is None) + or (self.ncols == -1 and self.column_keys is None) + or self.base is None): # Determine dimensions or base in the cases where we # really need to look at the entries. if self.typ == MA_ENTRIES_SEQ_SEQ: @@ -1071,9 +1084,9 @@ cdef class MatrixArgs: self.typ = MA_ENTRIES_ZERO except Exception: # "not self.entries" has failed, self.entries cannot be determined to be zero - if self.nrows != self.ncols: + if self.nrows != self.ncols or self.row_keys != self.column_keys: raise TypeError("scalar matrix must be square if the value cannot be determined to be zero") - if self.typ == MA_ENTRIES_SCALAR and self.nrows != self.ncols: + if self.typ == MA_ENTRIES_SCALAR and (self.nrows != self.ncols or self.row_keys != self.column_keys): # self.typ is still SCALAR -> "not self.entries" has successfully evaluated, to False raise TypeError("nonzero scalar matrix must be square") @@ -1084,8 +1097,17 @@ cdef class MatrixArgs: global MatrixSpace if MatrixSpace is None: from sage.matrix.matrix_space import MatrixSpace - self.space = MatrixSpace(self.base, self.nrows, self.ncols, - sparse=self.sparse, **self.kwds) + nrows = self.nrows + if nrows == -1: + nrows = None + ncols = self.ncols + if ncols == -1: + ncols = None + self.space = MatrixSpace(self.base, nrows, ncols, + sparse=self.sparse, + row_keys=self.row_keys, + column_keys=self.column_keys, + **self.kwds) self.is_finalized = True diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 9cd429ef0bc..644519ce0ef 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -1656,7 +1656,7 @@ def matrix(self, side=None): INPUT: - - ``side`` -- (default: ``'None'``) the side of the matrix + - ``side`` -- (default: ``None``) the side of the matrix where a vector is placed to effect the morphism (function) OUTPUT: From fabf5c87afdd8b35eb88725e280aecef234abd09 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 14:45:57 -0700 Subject: [PATCH 06/22] Fixup --- src/sage/matrix/args.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index e2c7232a7de..97fd315a927 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -747,7 +747,7 @@ cdef class MatrixArgs: M.set_immutable() if isinstance(self.space, MatrixSpace): return M - return self.space(M) + return self.space(M, side='right') cpdef list list(self, bint convert=True) noexcept: """ From 6a9ddb873c48b9492a7701dbe698fe25eabd2907 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 20:24:29 -0700 Subject: [PATCH 07/22] src/sage/modules/free_module.py: Allow passing both rank and basis_keys, check consistency --- src/sage/modules/free_module.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 867f42bf93b..b2c6c0fec07 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -403,9 +403,11 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: FreeModule(Integers(7),10) Vector space of dimension 10 over Ring of integers modulo 7 sage: FreeModule(PolynomialRing(QQ,'x'),5) - Ambient free module of rank 5 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field + Ambient free module of rank 5 over the principal ideal domain + Univariate Polynomial Ring in x over Rational Field sage: FreeModule(PolynomialRing(ZZ,'x'),5) - Ambient free module of rank 5 over the integral domain Univariate Polynomial Ring in x over Integer Ring + Ambient free module of rank 5 over the integral domain + Univariate Polynomial Ring in x over Integer Ring Of course we can make rank 0 free modules:: @@ -507,11 +509,18 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: FreeModule(QQ, ['a', 2, 3, 4], with_basis=None) Traceback (most recent call last): ... - NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got ['a', 2, 3, 4] + NotImplementedError: FiniteRankFreeModule only supports integer ranges + as basis_keys, got ['a', 2, 3, 4] sage: FreeModule(QQ, [1, 3, 5], with_basis=None) Traceback (most recent call last): ... - NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got [1, 3, 5] + NotImplementedError: FiniteRankFreeModule only supports integer ranges + as basis_keys, got [1, 3, 5] + + sage: FreeModule(ZZ, rank=3, basis_keys=['c','d']) + Traceback (most recent call last): + ... + ValueError: inconsistent basis keys: should be of cardinality 3, got ['c', 'd'] """ if rank_or_basis_keys is not None: try: @@ -537,12 +546,15 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m return FiniteRankFreeModule(base_ring, rank, start_index=start_index, **args) return FiniteRankFreeModule(base_ring, rank, **args) elif with_basis == 'standard': - if rank is not None: + if rank is not None and basis_keys is None: return FreeModuleFactory_with_standard_basis(base_ring, rank, sparse, inner_product_matrix, **args) else: if inner_product_matrix is not None: raise NotImplementedError + if rank is not None and rank != len(basis_keys): + raise ValueError(f'inconsistent basis_keys: should be of cardinality {rank}, ' + f'got {basis_keys}') from sage.combinat.free_module import CombinatorialFreeModule return CombinatorialFreeModule(base_ring, basis_keys, **args) else: From 4bc6dd9f9977e1b4903b8eb891dd84ff6e14f821 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 20:31:09 -0700 Subject: [PATCH 08/22] src/sage/matrix/args.pyx: Handle initialization when nrows, ncols are not given --- src/sage/matrix/args.pyx | 46 ++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 97fd315a927..619bd70526e 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -473,6 +473,12 @@ cdef class MatrixArgs: """ return self.iter() + def _ensure_nrows_ncols(self): + if self.nrows == -1: + self.nrows = len(self.row_keys) + if self.ncols == -1: + self.ncols = len(self.column_keys) + def iter(self, bint convert=True, bint sparse=False): """ Iteration over the entries in the matrix @@ -551,12 +557,14 @@ cdef class MatrixArgs: if sparse: pass else: + self._ensure_nrows_ncols() zero = self.base.zero() for i in range(self.nrows): for j in range(self.ncols): sig_check() yield zero elif self.typ == MA_ENTRIES_SCALAR: + self._ensure_nrows_ncols() diag = self.entries if convert and self.need_to_convert(diag): diag = self.base(diag) @@ -571,6 +579,7 @@ cdef class MatrixArgs: sig_check() yield diag if (i == j) else zero elif self.typ == MA_ENTRIES_SEQ_SEQ: + self._ensure_nrows_ncols() row_iter = sized_iter(self.entries, self.nrows) for i in range(self.nrows): row = sized_iter(next(row_iter), self.ncols) @@ -584,6 +593,7 @@ cdef class MatrixArgs: else: yield x elif self.typ == MA_ENTRIES_SEQ_FLAT: + self._ensure_nrows_ncols() it = sized_iter(self.entries, self.nrows * self.ncols) for i in range(self.nrows): for j in range(self.ncols): @@ -607,8 +617,14 @@ cdef class MatrixArgs: raise TypeError("dense iteration is not supported for sparse input") elif self.typ == MA_ENTRIES_CALLABLE: f = self.entries - for i in range(self.nrows): - for j in range(self.ncols): + row_keys = self.row_keys + if row_keys is None: + row_keys = range(self.nrows) + column_keys = self.column_keys + if column_keys is None: + column_keys = range(self.ncols) + for i in row_keys: + for j in column_keys: sig_check() x = f(i, j) if convert and self.need_to_convert(x): @@ -641,6 +657,7 @@ cdef class MatrixArgs: 42 """ self.finalize() + self._ensure_nrows_ncols() return self.nrows * self.ncols cpdef Matrix matrix(self, bint convert=True) noexcept: @@ -747,7 +764,7 @@ cdef class MatrixArgs: M.set_immutable() if isinstance(self.space, MatrixSpace): return M - return self.space(M, side='right') + return self.space(matrix=M, side='right') cpdef list list(self, bint convert=True) noexcept: """ @@ -1277,10 +1294,11 @@ cdef class MatrixArgs: e = PySequence_Fast(self.entries, "not a sequence") self.set_nrows(len(e)) if self.nrows == 0: - if self.ncols == -1: self.ncols = 0 + if self.ncols == -1 and self.column_keys is not None: + self.set_ncols(0) self.setdefault_base(ZZ) return 0 - elif self.ncols != -1 and self.base is not None: + elif (self.ncols != -1 or self.column_keys is not None) and self.base is not None: # Everything known => OK return 0 @@ -1326,14 +1344,20 @@ cdef class MatrixArgs: self.nrows = 0 if self.ncols == -1: self.ncols = 0 - elif self.ncols == -1: + elif self.ncols == -1 and self.column_keys is None: if self.nrows == -1: - # Assume row matrix - self.nrows = 1 - self.ncols = N + if self.row_keys is None: + # Assume row matrix + self.nrows = 1 + self.ncols = N + else: + self.nrows = len(self.row_keys) + self.ncols = N // self.nrows else: self.ncols = N // self.nrows - elif self.nrows == -1: + elif self.nrows == -1 and self.row_keys is None: + if self.ncols == -1: + self.ncols = len(self.column_keys) self.nrows = N // self.ncols self.set_seq_flat(entries) @@ -1463,7 +1487,7 @@ cdef class MatrixArgs: return MA_ENTRIES_SEQ_SEQ if type(x) is SparseEntry: return MA_ENTRIES_SEQ_SPARSE - if self.nrows != -1 and self.ncols != -1 and self.ncols != 1: + if self.nrows != -1 and self.ncols != -1: # Determine type from the number of entries. Unfortunately, # this only works if the number of columns is not 1. if len(self.entries) == self.nrows: From c76a6e664e3698609a903cc9070cac03f66996b4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 21:01:25 -0700 Subject: [PATCH 09/22] src/sage/matrix/args.pyx: Fix side of intermediate matrix --- src/sage/matrix/args.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 619bd70526e..7133e8b2c6e 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -746,7 +746,7 @@ cdef class MatrixArgs: else: space = self.space if not isinstance(space, MatrixSpace): - space = space.zero().matrix(side='right').parent() + space = space.zero().matrix(side='left').parent() M = space(self, coerce=convert) # Also store the matrix to support multiple calls of matrix() @@ -764,7 +764,7 @@ cdef class MatrixArgs: M.set_immutable() if isinstance(self.space, MatrixSpace): return M - return self.space(matrix=M, side='right') + return self.space(matrix=M, side='left') cpdef list list(self, bint convert=True) noexcept: """ From 9385b1f6ca335f8723b03c821ebcbd464493258b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 12 Mar 2024 23:23:00 -0700 Subject: [PATCH 10/22] src/sage/matrix/args.pyx, src/sage/matrix/constructor.pyx: Fixups --- src/sage/matrix/args.pyx | 4 ++-- src/sage/matrix/constructor.pyx | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 7133e8b2c6e..90c1f911c79 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -1294,7 +1294,7 @@ cdef class MatrixArgs: e = PySequence_Fast(self.entries, "not a sequence") self.set_nrows(len(e)) if self.nrows == 0: - if self.ncols == -1 and self.column_keys is not None: + if self.ncols == -1 and self.column_keys is None: self.set_ncols(0) self.setdefault_base(ZZ) return 0 @@ -1487,7 +1487,7 @@ cdef class MatrixArgs: return MA_ENTRIES_SEQ_SEQ if type(x) is SparseEntry: return MA_ENTRIES_SEQ_SPARSE - if self.nrows != -1 and self.ncols != -1: + if self.nrows != -1 and self.ncols != -1 and self.ncols != 1: # Determine type from the number of entries. Unfortunately, # this only works if the number of columns is not 1. if len(self.entries) == self.nrows: diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index 8820af26fdb..b92d2a6022c 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -249,8 +249,9 @@ def matrix(*args, **kwds): sage: M = matrix([[1,2,3], [4,5,6]], ....: column_keys=['a','b','c'], row_keys=['u','v']); M - [1 2 3] - [4 5 6] + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'u', 'v'} over Integer Ring TESTS: From a4b65a97fe4fa0eb0c6aadd530c7c7154b9e782e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 10:26:59 -0700 Subject: [PATCH 11/22] src/sage/categories/finite_dimensional_modules_with_basis.py: Add methods _repr_matrix, _ascii_art_matrix, _unicode_art_matrix --- .../finite_dimensional_modules_with_basis.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index ab22cd355d5..b9da9a8997a 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -676,6 +676,96 @@ def matrix(self, base_ring=None, side="left"): m.set_immutable() return m + def _repr_matrix(self): + r""" + Return a string representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._repr_ = M._repr_matrix + sage: M # indirect doctest + a b c + v[1 0 0] + w[0 1 0] + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + return repr(matrix) + + def _ascii_art_matrix(self): + r""" + Return an ASCII art representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._ascii_art_ = M._ascii_art_matrix + sage: ascii_art(M) # indirect doctest + a b c + v[1 0 0] + w[0 1 0] + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(character_art=True, + top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + from sage.typeset.ascii_art import AsciiArt + + return AsciiArt(repr(self).splitlines()) + + def _unicode_art_matrix(self): + r""" + Return a unicode art representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._unicode_art_ = M._unicode_art_matrix + sage: unicode_art(M) # indirect doctest + a b c + v⎛1 0 0⎞ + w⎝0 1 0⎠ + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(unicode=True, character_art=True, + top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + from sage.typeset.unicode_art import UnicodeArt + + return UnicodeArt(repr(self).splitlines()) + def __invert__(self): """ Return the inverse morphism of ``self``. From 7ade84caf2cac58834486ad6262f15465fa3bb5e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 15:32:38 -0700 Subject: [PATCH 12/22] MatrixArgs._ensure_nrows_ncols: inline --- src/sage/matrix/args.pxd | 10 ++++++++++ src/sage/matrix/args.pyx | 6 ------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index 335c1e901d9..2834ca3590e 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -113,6 +113,16 @@ cdef class MatrixArgs: f"but got {n}") self.nrows = n + cdef inline int _ensure_nrows_ncols(self) except -1: + r""" + Make sure that the number of rows and columns is set. + If ``row_keys`` or ``column_keys`` is not finite, this can raise an exception. + """ + if self.nrows == -1: + self.nrows = len(self.row_keys) + if self.ncols == -1: + self.ncols = len(self.column_keys) + cpdef int set_column_keys(self, column_keys) except -1 cpdef int set_row_keys(self, row_keys) except -1 cpdef int set_space(self, space) except -1 diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 90c1f911c79..64377e05b94 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -473,12 +473,6 @@ cdef class MatrixArgs: """ return self.iter() - def _ensure_nrows_ncols(self): - if self.nrows == -1: - self.nrows = len(self.row_keys) - if self.ncols == -1: - self.ncols = len(self.column_keys) - def iter(self, bint convert=True, bint sparse=False): """ Iteration over the entries in the matrix From a8c83037edf890d18f111024de6647e0acd0bbdc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 16:12:29 -0700 Subject: [PATCH 13/22] MatrixArgs.element: Remove parameter 'convert', add doc --- src/sage/matrix/args.pxd | 2 +- src/sage/matrix/args.pyx | 45 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index 2834ca3590e..4a4e6f70280 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -55,7 +55,7 @@ cdef class MatrixArgs: cdef bint is_finalized cpdef Matrix matrix(self, bint convert=?) noexcept - cpdef element(self, bint immutable=?, bint convert=?) noexcept + cpdef element(self, bint immutable=?) noexcept cpdef list list(self, bint convert=?) noexcept cpdef dict dict(self, bint convert=?) noexcept diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 64377e05b94..471a2f3ae0d 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -748,12 +748,51 @@ cdef class MatrixArgs: self.typ = MA_ENTRIES_MATRIX return M - cpdef element(self, bint immutable=False, bint convert=True) noexcept: + cpdef element(self, bint immutable=False) noexcept: r""" - Return the element. + Return the matrix or morphism. + + INPUT: + + - ``immutable`` -- boolean; if ``True``, the result will be immutable. + + OUTPUT: an element of ``self.space``. + + .. NOTE:: + + This may change ``self.entries``, making it unsafe to access the + ``self.entries`` attribute after calling this method. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: M = matrix(2, 3, range(6), sparse=True) + sage: ma = MatrixArgs(M); ma.finalized() + + sage: M2 = ma.element(immutable=True); M2.parent() + Full MatrixSpace of 2 by 3 sparse matrices over Integer Ring + sage: M2.is_immutable() + True + + sage: ma = MatrixArgs(M, row_keys=['u','v'], column_keys=['a','b','c']) + sage: ma.finalized() + + sage: phi = ma.element(); phi + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'u', 'v'} over Integer Ring """ self.finalize() - cdef Matrix M = self.matrix(convert) + cdef Matrix M = self.matrix(convert=True) if immutable: M.set_immutable() if isinstance(self.space, MatrixSpace): From bd2c464a86660b81c138b4c6fdd7754b2aee0656 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 16:43:44 -0700 Subject: [PATCH 14/22] MatrixArgs.set_{row,column}_keys: Add examples --- src/sage/matrix/args.pyx | 52 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 471a2f3ae0d..4984f2de880 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -903,8 +903,30 @@ cdef class MatrixArgs: cpdef int set_column_keys(self, column_keys) except -1: """ - Set the column keys with consistency checking: if the - value was previously set, it must remain the same. + Set the column keys with consistency checking. + + If the value was previously set, it must remain the same. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: ma = MatrixArgs(2, 4) + sage: ma.set_column_keys('xyz') + Traceback (most recent call last): + ... + ValueError: inconsistent column keys: should be of cardinality 4 but got xyz + sage: ma.set_column_keys('abcd') + 0 + sage: ma.finalized() + """ if self.column_keys is not None and self.column_keys != column_keys: raise ValueError(f"inconsistent column keys: should be {self.column_keys} " @@ -917,8 +939,30 @@ cdef class MatrixArgs: cpdef int set_row_keys(self, row_keys) except -1: """ - Set the row keys with consistency checking: if the - value was previously set, it must remain the same. + Set the row keys with consistency checking. + + If the value was previously set, it must remain the same. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: ma = MatrixArgs(2, 4) + sage: ma.set_row_keys('xyz') + Traceback (most recent call last): + ... + ValueError: inconsistent row keys: should be of cardinality 2 but got xyz + sage: ma.set_row_keys(['u', 'v']) + 0 + sage: ma.finalized() + """ if self.row_keys is not None and self.row_keys != row_keys: raise ValueError(f"inconsistent row keys: should be {self.row_keys} " From 4aa9e0ef3aed35a0aeb61232c546ca13f84bc264 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 19:07:44 -0700 Subject: [PATCH 15/22] GenericGraph.adjacency_matrix: If vertices=True, edges=True, construct a morphism --- src/sage/graphs/generic_graph.py | 63 ++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 7c942b4f5df..61da3b97b9d 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2127,15 +2127,23 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None - ``sparse`` -- boolean (default: ``True``); whether to use a sparse or a dense matrix - - ``vertices`` -- list (default: ``None``); when specified, the `i`-th - row of the matrix corresponds to the `i`-th vertex in the ordering of - ``vertices``, otherwise, the `i`-th row of the matrix corresponds to - the `i`-th vertex in the ordering given by method :meth:`vertices`. + - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``); - - ``edges`` -- list (default: ``None``); when specified, the `i`-th - column of the matrix corresponds to the `i`-th edge in the ordering of - ``edges``, otherwise, the `i`-th column of the matrix corresponds to - the `i`-th edge in the ordering given by method :meth:`edge_iterator`. + - when a list, the `i`-th row of the matrix corresponds to the `i`-th + vertex in the ordering of ``vertices``, + - when ``None``, the `i`-th row of the matrix corresponds to + the `i`-th vertex in the ordering given by method :meth:`vertices`, + - when ``True``, construct a morphism of free modules instead of a matrix, + where the codomain's basis is indexed by the vertices. + + - ``edges`` -- list, ``None``, or ``True`` (default: ``None``); + + - when a list, the `i`-th column of the matrix corresponds to the `i`-th + edge in the ordering of ``edges``, + - when ``None``, the `i`-th column of the matrix corresponds to + the `i`-th edge in the ordering given by method :meth:`edge_iterator`, + - when ``True``, construct a morphism of free modules instead of a matrix, + where the domain's basis is indexed by the edges. - ``base_ring`` -- a ring (default: ``ZZ``); the base ring of the matrix space to use. @@ -2259,6 +2267,25 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + Creating a module morphism:: + + sage: # needs sage.modules + sage: D12 = posets.DivisorLattice(12).hasse_diagram() + sage: phi_VE = D12.incidence_matrix(vertices=True, edges=True); phi_VE + Generic morphism: + From: Free module generated by + {(1, 2), (1, 3), (2, 4), (2, 6), (3, 6), (4, 12), (6, 12)} + over Integer Ring + To: Free module generated by {1, 2, 3, 4, 6, 12} over Integer Ring + sage: print(phi_VE._unicode_art_matrix()) + (1, 2) (1, 3) (2, 4) (2, 6) (3, 6) (4, 12) (6, 12) + 1⎛ -1 -1 0 0 0 0 0⎞ + 2⎜ 1 0 -1 -1 0 0 0⎟ + 3⎜ 0 1 0 0 -1 0 0⎟ + 4⎜ 0 0 1 0 0 -1 0⎟ + 6⎜ 0 0 0 1 1 0 -1⎟ + 12⎝ 0 0 0 0 0 1 1⎠ + TESTS:: sage: P5 = graphs.PathGraph(5) @@ -2280,15 +2307,24 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None if oriented is None: oriented = self.is_directed() - if vertices is None: + row_keys = None + if vertices is True: + vertices = self.vertices(sort=False) + row_keys = tuple(vertices) # because a list is not hashable + elif vertices is None: vertices = self.vertices(sort=False) elif (len(vertices) != self.num_verts() or set(vertices) != set(self.vertex_iterator())): raise ValueError("parameter vertices must be a permutation of the vertices") + column_keys = None verts = {v: i for i, v in enumerate(vertices)} - if edges is None: - edges = self.edge_iterator(labels=False) + use_edge_labels = kwds.pop('use_edge_labels', False) + if edges is True: + edges = self.edges(labels=use_edge_labels) + column_keys = tuple(edges) # because an EdgesView is not hashable + elif edges is None: + edges = self.edge_iterator(labels=use_edge_labels) elif len(edges) != self.size(): raise ValueError("parameter edges must be a permutation of the edges") else: @@ -2320,8 +2356,13 @@ def reorder(u, v): m[verts[e[0]], i] += 1 m[verts[e[1]], i] += 1 + if row_keys is not None or column_keys is not None: + m.set_immutable() + return matrix(m, row_keys=row_keys, column_keys=column_keys) + if immutable: m.set_immutable() + return m def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): From ceedc392fcca7c07ed9f5d3edf182b4155932715 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 21:27:44 -0700 Subject: [PATCH 16/22] Matrix_mod2_dense.str: Accept and pass on parameters left_border, ... --- src/sage/matrix/matrix_mod2_dense.pyx | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 7f52c13dd54..1f079ebc001 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -346,7 +346,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None, - *, unicode=False, shape=None, character_art=False): + *, unicode=False, shape=None, character_art=False, + left_border=None, right_border=None, + top_border=None, bottom_border=None): r""" Return a nice string representation of the matrix. @@ -384,6 +386,16 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse :class:`~sage.typeset.unicode_art.UnicodeArt` which support line breaking of wide matrices that exceed the window width + - ``left_border``, ``right_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the rows of the matrix. The labels appear + outside of the parentheses. + + - ``top_border``, ``bottom_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the columns of the matrix. The labels appear + outside of the parentheses. + EXAMPLES:: sage: B = matrix(GF(2), 3, 3, [0, 1, 0, 0, 1, 1, 0, 0, 0]) @@ -416,13 +428,19 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse # Set the mapping based on keyword arguments # We ignore minus_one (it's only there for compatibility with Matrix) if (rep_mapping is not None or zero is not None or plus_one is not None - or unicode or shape is not None or character_art): + or unicode or shape is not None or character_art + or left_border is not None or right_border is not None + or top_border is not None or bottom_border is not None): # Shunt mappings off to the generic code since they might not be # single characters return matrix_dense.Matrix_dense.str(self, rep_mapping=rep_mapping, zero=zero, plus_one=plus_one, unicode=unicode, shape=shape, - character_art=character_art) + character_art=character_art, + left_border=left_border, + right_border=right_border, + top_border=top_border, + bottom_border=bottom_border) if self._nrows == 0 or self._ncols == 0: return "[]" From a88756611c1e996447f863bda9cf03cfdc1c926b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 21:33:25 -0700 Subject: [PATCH 17/22] LinearMatroid.representation: if order=True, return a morphism --- src/sage/matroids/linear_matroid.pyx | 91 ++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 24 deletions(-) diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index bd277d891eb..ba44191df6b 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -496,17 +496,26 @@ cdef class LinearMatroid(BasisExchangeMatroid): - ``B`` -- (default: ``None``) a subset of elements. When provided, the representation is such that a basis `B'` that maximally intersects `B` is an identity matrix. + - ``reduced`` -- (default: ``False``) when ``True``, return a reduced matrix `D` (so `[I\ \ D]` is a representation of the matroid). Otherwise return a full representation matrix. + - ``labels`` -- (default: ``None``) when ``True``, return additionally a list of column labels (if ``reduced=False``) or a list of row labels and a list of column labels (if ``reduced=True``). The default setting, ``None``, will not return the labels for a full matrix, but will return the labels for a reduced matrix. - - ``order`` -- (default: ``None``) an ordering of the groundset - elements. If provided, the columns (and, in case of a reduced - representation, rows) will be presented in the given order. + + - ``order`` -- sequence or ``None`` or ``True`` (default: ``None``); + + - when a sequence, it should be an ordering of the groundset + elements, and the columns (and, in case of a reduced + representation, rows) will be presented in the given order, + - when ``None``, use the same ordering that :meth:`groundset_list` + uses, + - when ``True``, return a morphism of free modules instead of a matrix. + - ``lift_map`` -- (default: ``None``) a dictionary containing the cross ratios of the representing matrix in its domain. If provided, the representation will be transformed by mapping its cross ratios according @@ -579,9 +588,47 @@ cdef class LinearMatroid(BasisExchangeMatroid): [ 1 0 0 1 0 1 1 1] [ 0 1 0 -z + 1 1 0 0 1] [ 0 0 1 0 1 -1 z - 1 0] + + As morphisms:: + + sage: M = matroids.catalog.Fano() + sage: A = M.representation(order=True); A + Generic morphism: + From: Free module generated by {'a', 'b', 'c', 'd', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {0, 1, 2} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + a b c d e f g + 0⎛1 0 0 0 1 1 1⎞ + 1⎜0 1 0 1 0 1 1⎟ + 2⎝0 0 1 1 1 0 1⎠ + sage: A = M.representation(B='efg', order=True); A + Generic morphism: + From: Free module generated by {'a', 'b', 'c', 'd', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {0, 1, 2} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + a b c d e f g + 0⎛1 1 0 1 1 0 0⎞ + 1⎜1 0 1 1 0 1 0⎟ + 2⎝1 1 1 0 0 0 1⎠ + sage: A = M.representation(B='abc', order=True, reduced=True); A + Generic morphism: + From: Free module generated by {'d', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {'a', 'b', 'c'} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + d e f g + a⎛0 1 1 1⎞ + b⎜1 0 1 1⎟ + c⎝1 1 0 1⎠ """ cdef LeanMatrix A - if order is None: + column_keys = None + if order is True: + order = self.groundset_list() + column_keys = tuple(order) + elif order is None: order = self.groundset_list() else: if not frozenset(order) == self.groundset(): @@ -608,16 +655,14 @@ cdef class LinearMatroid(BasisExchangeMatroid): B = self._subset_internal(B) A = self._basic_representation(B) A = A.matrix_from_rows_and_columns(range(A.nrows()), order_idx) - if lift_map is None: - if labels: - return (A._matrix_(), order) - else: - return A._matrix_() - else: - if labels: - return (lift_cross_ratios(A._matrix_(), lift_map), order) - else: - return lift_cross_ratios(A._matrix_(), lift_map) + Am = A._matrix_() + if lift_map is not None: + Am = lift_cross_ratios(Am, lift_map) + if column_keys is not None: + Am = matrix(Am, row_keys=range(A.nrows()), column_keys=column_keys) + if labels: + return Am, order + return Am else: if B is None: B = frozenset(self.basis()) @@ -638,16 +683,14 @@ cdef class LinearMatroid(BasisExchangeMatroid): Ci.append(C.index(e)) Cl.append(e) A = A.matrix_from_rows_and_columns(Ri, Ci) - if lift_map is None: - if labels or labels is None: - return (A._matrix_(), Rl, Cl) - else: - return A._matrix_() - else: - if labels or labels is None: - return (lift_cross_ratios(A._matrix_(), lift_map), Rl, Cl) - else: - return lift_cross_ratios(A._matrix_(), lift_map) + Am = A._matrix_() + if lift_map is not None: + Am = lift_cross_ratios(Am, lift_map) + if column_keys is not None: + Am = matrix(Am, row_keys=tuple(Rl), column_keys=tuple(Cl)) + if labels or (labels is None and column_keys is None): + return Am, Rl, Cl + return Am cpdef _current_rows_cols(self, B=None) noexcept: """ From 0a59764b40f82564d3b2eacc0b057b8b5fc8bd89 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Mar 2024 21:35:37 -0700 Subject: [PATCH 18/22] src/sage/modules/free_module.py: Fix doctest output --- src/sage/modules/free_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index b2c6c0fec07..e2fef76a41e 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -520,7 +520,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: FreeModule(ZZ, rank=3, basis_keys=['c','d']) Traceback (most recent call last): ... - ValueError: inconsistent basis keys: should be of cardinality 3, got ['c', 'd'] + ValueError: inconsistent basis_keys: should be of cardinality 3, got ['c', 'd'] """ if rank_or_basis_keys is not None: try: From 642d5699d6f8e336dc84092015bd73140cdb6ca1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 30 Mar 2024 10:34:02 -0700 Subject: [PATCH 19/22] GenericGraph.incidence_matrix: Remove misleading indentation of doctest output --- src/sage/graphs/generic_graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 61da3b97b9d..a30720b765b 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2278,13 +2278,13 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None over Integer Ring To: Free module generated by {1, 2, 3, 4, 6, 12} over Integer Ring sage: print(phi_VE._unicode_art_matrix()) - (1, 2) (1, 3) (2, 4) (2, 6) (3, 6) (4, 12) (6, 12) - 1⎛ -1 -1 0 0 0 0 0⎞ - 2⎜ 1 0 -1 -1 0 0 0⎟ - 3⎜ 0 1 0 0 -1 0 0⎟ - 4⎜ 0 0 1 0 0 -1 0⎟ - 6⎜ 0 0 0 1 1 0 -1⎟ - 12⎝ 0 0 0 0 0 1 1⎠ + (1, 2) (1, 3) (2, 4) (2, 6) (3, 6) (4, 12) (6, 12) + 1⎛ -1 -1 0 0 0 0 0⎞ + 2⎜ 1 0 -1 -1 0 0 0⎟ + 3⎜ 0 1 0 0 -1 0 0⎟ + 4⎜ 0 0 1 0 0 -1 0⎟ + 6⎜ 0 0 0 1 1 0 -1⎟ + 12⎝ 0 0 0 0 0 1 1⎠ TESTS:: From 85a4a504025e71c2fa6564df0c83b7433a610282 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 30 Mar 2024 10:48:29 -0700 Subject: [PATCH 20/22] src/sage/matrix/constructor.pyx: Expand a doctest --- src/sage/matrix/constructor.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index b92d2a6022c..1be7a177d74 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -252,6 +252,10 @@ def matrix(*args, **kwds): Generic morphism: From: Free module generated by {'a', 'b', 'c'} over Integer Ring To: Free module generated by {'u', 'v'} over Integer Ring + sage: print(M._unicode_art_matrix()) + a b c + u⎛1 2 3⎞ + v⎝4 5 6⎠ TESTS: From 705608accdbdeb8534d538babe1407c244f7ca63 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 30 Mar 2024 11:02:16 -0700 Subject: [PATCH 21/22] GenericGraph.incidence_matrix: Expand doctest --- src/sage/graphs/generic_graph.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index a30720b765b..63903eef635 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2285,6 +2285,13 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None 4⎜ 0 0 1 0 0 -1 0⎟ 6⎜ 0 0 0 1 1 0 -1⎟ 12⎝ 0 0 0 0 0 1 1⎠ + sage: E = phi_VE.domain() + sage: P1 = E.monomial((2, 4)) + E.monomial((4, 12)); P1 + B[(2, 4)] + B[(4, 12)] + sage: P2 = E.monomial((2, 6)) + E.monomial((6, 12)); P2 + B[(2, 6)] + B[(6, 12)] + sage: phi_VE(P1 - P2) + 0 TESTS:: From 219e1885aa27626196465bc3a8ca80fd4ad820e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Wed, 3 Apr 2024 19:41:07 -0700 Subject: [PATCH 22/22] Apply suggestions from code review Co-authored-by: gmou3 <32706872+gmou3@users.noreply.github.com> --- src/sage/matrix/args.pyx | 2 +- src/sage/matrix/constructor.pyx | 2 +- src/sage/matrix/matrix_space.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 4984f2de880..b864c8639b3 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -756,7 +756,7 @@ cdef class MatrixArgs: - ``immutable`` -- boolean; if ``True``, the result will be immutable. - OUTPUT: an element of ``self.space``. + OUTPUT: an element of ``self.space`` .. NOTE:: diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index 1be7a177d74..965f238314d 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -91,7 +91,7 @@ def matrix(*args, **kwds): OUTPUT: a matrix or, more generally, a homomorphism between free - modules. + modules EXAMPLES:: diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index cc3133046e5..12b6bea651c 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -468,7 +468,7 @@ class MatrixSpace(UniqueRepresentation, Parent): - ``'numpy'`` -- for real and complex floating point numbers - OUTPUT: a matrix space or, more generally, a homspace between free modules. + OUTPUT: a matrix space or, more generally, a homspace between free modules This factory function creates instances of various specialized classes depending on the input. Not all combinations of options are