diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index 5208522a11a..58916af2aaf 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -679,6 +679,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``. diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 4d147efcd2f..c6d429863d3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2126,15 +2126,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. @@ -2258,6 +2266,32 @@ 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⎠ + 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:: sage: P5 = graphs.PathGraph(5) @@ -2279,15 +2313,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: @@ -2319,8 +2362,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): diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index 9ab004e1887..8deb4d434c9 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 @@ -54,6 +55,7 @@ cdef class MatrixArgs: cdef bint is_finalized cpdef Matrix matrix(self, bint convert=?) + cpdef element(self, bint immutable=?) cpdef list list(self, bint convert=?) cpdef dict dict(self, bint convert=?) @@ -89,7 +91,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 +108,23 @@ 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 + 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 cdef int finalize(self) except -1 diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 5613a63dfd5..b01f3466998 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -312,7 +312,8 @@ cdef class MatrixArgs: self.sparse = -1 self.kwds = {} - def __init__(self, *args, base_ring=None, nrows=None, ncols=None, entries=None, sparse=None, space=None, **kwds): + def __init__(self, *args, base_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. @@ -360,6 +361,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) @@ -375,18 +380,20 @@ 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 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)): @@ -550,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) @@ -570,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) @@ -583,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): @@ -606,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): @@ -640,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): @@ -726,13 +744,67 @@ 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='left').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 element(self, bint immutable=False): + r""" + 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=True) + if immutable: + M.set_immutable() + if isinstance(self.space, MatrixSpace): + return M + return self.space(matrix=M, side='left') + cpdef list list(self, bint convert=True): """ Return the entries of the matrix as a flat list of scalars. @@ -835,13 +907,84 @@ 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. + + 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} " + 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. + + 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} " + 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. INPUT: - - ``space`` -- a :class:`MatrixSpace` + - ``space`` -- a :class:`MatrixSpace` or a homset of modules with basis EXAMPLES:: @@ -858,12 +1001,37 @@ 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() + """ + if self.space is not None: + return 0 # TODO: ?????? 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): """ @@ -945,6 +1113,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 @@ -977,7 +1146,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 @@ -993,7 +1162,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: @@ -1013,9 +1184,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") @@ -1026,8 +1197,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 @@ -1197,10 +1377,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 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 @@ -1246,14 +1427,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) diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index 6e2e4f7dda8..3e8235cb94f 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 ``base_ring``, ``nrows``, ``ncols`` and - ``sparse``. + matrix. This determines ``base_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,18 @@ 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 + 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: There are many ways to create an empty matrix:: @@ -645,10 +661,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 diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index f7efb5432c8..6683003b6f2 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 diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index 19b87336033..0027d85073f 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -495,17 +495,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 @@ -578,9 +587,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(): @@ -607,16 +654,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()) @@ -637,16 +682,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): """ diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 0cb8a0be8fd..f901b28ba3c 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -509,12 +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 rank: should be cardinality of ['c', 'd'] but got 3 sage: FreeModule(QQ, ['a', 'b'], rank=2) Free module generated by {'a', 'b'} over Rational Field sage: FreeModule(QQ, 2, basis_keys=['a', 'b']) @@ -559,12 +565,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 basis_keys is 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: diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 4b1f2af7e0d..ce2b88ede82 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -1607,7 +1607,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: