Skip to content

Commit

Permalink
sagemathgh-37692: matrix, Graph.incidence_matrix, `LinearMatroid.…
Browse files Browse the repository at this point in the history
…representation`: Support constructing `Hom(CombinatorialFreeModule)` elements

    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

We use morphisms of `CombinatorialFreeModule`s (each of which has a
distinguished finite or enumerated basis indexed by arbitrary objects)
as matrices whose rows and columns are indexed by arbitrary objects
(`row_keys`, `column_keys`).

Example:
```
        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
```

Example application done here on the PR: The incidence matrix of a graph
or digraph. Returning it as a morphism instead of a matrix has the
benefit of keeping the vertices and edges with the result. This new
behavior is activated by special values for the existing parameters
`vertices` and `edges`.
```
            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⎠
```



### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [ ] I have linked a relevant issue or discussion.
- [ ] I have created tests covering the changes.
- [ ] I have updated the documentation accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
- Depends on sagemath#37607
- Depends on sagemath#37514
- Depends on sagemath#37606
- Depends on sagemath#37646
    
URL: sagemath#37692
Reported by: Matthias Köppe
Reviewer(s): gmou3
  • Loading branch information
Release Manager committed Apr 28, 2024
2 parents 2d3f082 + 3e71e20 commit b048320
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 79 deletions.
90 changes: 90 additions & 0 deletions src/sage/categories/finite_dimensional_modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down
70 changes: 59 additions & 11 deletions src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
23 changes: 22 additions & 1 deletion src/sage/matrix/args.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ 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
cdef public dict kwds # **kwds for MatrixSpace()
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=?)

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
Loading

0 comments on commit b048320

Please sign in to comment.