Skip to content

Commit f0a2850

Browse files
author
Release Manager
committed
gh-37692: `matrix`, `Graph.incidence_matrix`, `LinearMatroid.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 #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 #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, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> - Depends on #37607 - Depends on #37514 - Depends on #37606 - Depends on #37646 URL: #37692 Reported by: Matthias Köppe Reviewer(s): gmou3
2 parents 9783f96 + 3e71e20 commit f0a2850

File tree

9 files changed

+490
-79
lines changed

9 files changed

+490
-79
lines changed

src/sage/categories/finite_dimensional_modules_with_basis.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,96 @@ def matrix(self, base_ring=None, side="left"):
679679
m.set_immutable()
680680
return m
681681

682+
def _repr_matrix(self):
683+
r"""
684+
Return a string representation of this morphism (as a matrix).
685+
686+
EXAMPLES::
687+
688+
sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]],
689+
....: column_keys=['a', 'b', 'c'],
690+
....: row_keys=['v', 'w']); M
691+
Generic morphism:
692+
From: Free module generated by {'a', 'b', 'c'} over Integer Ring
693+
To: Free module generated by {'v', 'w'} over Integer Ring
694+
sage: M._repr_ = M._repr_matrix
695+
sage: M # indirect doctest
696+
a b c
697+
v[1 0 0]
698+
w[0 1 0]
699+
"""
700+
matrix = self.matrix()
701+
702+
from sage.matrix.constructor import options
703+
704+
if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols():
705+
return matrix.str(top_border=self.domain().basis().keys(),
706+
left_border=self.codomain().basis().keys())
707+
708+
return repr(matrix)
709+
710+
def _ascii_art_matrix(self):
711+
r"""
712+
Return an ASCII art representation of this morphism (as a matrix).
713+
714+
EXAMPLES::
715+
716+
sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]],
717+
....: column_keys=['a', 'b', 'c'],
718+
....: row_keys=['v', 'w']); M
719+
Generic morphism:
720+
From: Free module generated by {'a', 'b', 'c'} over Integer Ring
721+
To: Free module generated by {'v', 'w'} over Integer Ring
722+
sage: M._ascii_art_ = M._ascii_art_matrix
723+
sage: ascii_art(M) # indirect doctest
724+
a b c
725+
v[1 0 0]
726+
w[0 1 0]
727+
"""
728+
matrix = self.matrix()
729+
730+
from sage.matrix.constructor import options
731+
732+
if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols():
733+
return matrix.str(character_art=True,
734+
top_border=self.domain().basis().keys(),
735+
left_border=self.codomain().basis().keys())
736+
737+
from sage.typeset.ascii_art import AsciiArt
738+
739+
return AsciiArt(repr(self).splitlines())
740+
741+
def _unicode_art_matrix(self):
742+
r"""
743+
Return a unicode art representation of this morphism (as a matrix).
744+
745+
EXAMPLES::
746+
747+
sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]],
748+
....: column_keys=['a', 'b', 'c'],
749+
....: row_keys=['v', 'w']); M
750+
Generic morphism:
751+
From: Free module generated by {'a', 'b', 'c'} over Integer Ring
752+
To: Free module generated by {'v', 'w'} over Integer Ring
753+
sage: M._unicode_art_ = M._unicode_art_matrix
754+
sage: unicode_art(M) # indirect doctest
755+
a b c
756+
v⎛1 0 0⎞
757+
w⎝0 1 0⎠
758+
"""
759+
matrix = self.matrix()
760+
761+
from sage.matrix.constructor import options
762+
763+
if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols():
764+
return matrix.str(unicode=True, character_art=True,
765+
top_border=self.domain().basis().keys(),
766+
left_border=self.codomain().basis().keys())
767+
768+
from sage.typeset.unicode_art import UnicodeArt
769+
770+
return UnicodeArt(repr(self).splitlines())
771+
682772
def __invert__(self):
683773
"""
684774
Return the inverse morphism of ``self``.

src/sage/graphs/generic_graph.py

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,15 +2126,23 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
21262126
- ``sparse`` -- boolean (default: ``True``); whether to use a sparse or
21272127
a dense matrix
21282128

2129-
- ``vertices`` -- list (default: ``None``); when specified, the `i`-th
2130-
row of the matrix corresponds to the `i`-th vertex in the ordering of
2131-
``vertices``, otherwise, the `i`-th row of the matrix corresponds to
2132-
the `i`-th vertex in the ordering given by method :meth:`vertices`.
2129+
- ``vertices`` -- list, ``None``, or ``True`` (default: ``None``);
21332130

2134-
- ``edges`` -- list (default: ``None``); when specified, the `i`-th
2135-
column of the matrix corresponds to the `i`-th edge in the ordering of
2136-
``edges``, otherwise, the `i`-th column of the matrix corresponds to
2137-
the `i`-th edge in the ordering given by method :meth:`edge_iterator`.
2131+
- when a list, the `i`-th row of the matrix corresponds to the `i`-th
2132+
vertex in the ordering of ``vertices``,
2133+
- when ``None``, the `i`-th row of the matrix corresponds to
2134+
the `i`-th vertex in the ordering given by method :meth:`vertices`,
2135+
- when ``True``, construct a morphism of free modules instead of a matrix,
2136+
where the codomain's basis is indexed by the vertices.
2137+
2138+
- ``edges`` -- list, ``None``, or ``True`` (default: ``None``);
2139+
2140+
- when a list, the `i`-th column of the matrix corresponds to the `i`-th
2141+
edge in the ordering of ``edges``,
2142+
- when ``None``, the `i`-th column of the matrix corresponds to
2143+
the `i`-th edge in the ordering given by method :meth:`edge_iterator`,
2144+
- when ``True``, construct a morphism of free modules instead of a matrix,
2145+
where the domain's basis is indexed by the edges.
21382146

21392147
- ``base_ring`` -- a ring (default: ``ZZ``); the base ring of the matrix
21402148
space to use.
@@ -2258,6 +2266,32 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
22582266
ValueError: matrix is immutable; please change a copy instead
22592267
(i.e., use copy(M) to change a copy of M).
22602268

2269+
Creating a module morphism::
2270+
2271+
sage: # needs sage.modules
2272+
sage: D12 = posets.DivisorLattice(12).hasse_diagram()
2273+
sage: phi_VE = D12.incidence_matrix(vertices=True, edges=True); phi_VE
2274+
Generic morphism:
2275+
From: Free module generated by
2276+
{(1, 2), (1, 3), (2, 4), (2, 6), (3, 6), (4, 12), (6, 12)}
2277+
over Integer Ring
2278+
To: Free module generated by {1, 2, 3, 4, 6, 12} over Integer Ring
2279+
sage: print(phi_VE._unicode_art_matrix())
2280+
(1, 2) (1, 3) (2, 4) (2, 6) (3, 6) (4, 12) (6, 12)
2281+
1⎛ -1 -1 0 0 0 0 0⎞
2282+
2⎜ 1 0 -1 -1 0 0 0⎟
2283+
3⎜ 0 1 0 0 -1 0 0⎟
2284+
4⎜ 0 0 1 0 0 -1 0⎟
2285+
6⎜ 0 0 0 1 1 0 -1⎟
2286+
12⎝ 0 0 0 0 0 1 1⎠
2287+
sage: E = phi_VE.domain()
2288+
sage: P1 = E.monomial((2, 4)) + E.monomial((4, 12)); P1
2289+
B[(2, 4)] + B[(4, 12)]
2290+
sage: P2 = E.monomial((2, 6)) + E.monomial((6, 12)); P2
2291+
B[(2, 6)] + B[(6, 12)]
2292+
sage: phi_VE(P1 - P2)
2293+
0
2294+
22612295
TESTS::
22622296

22632297
sage: P5 = graphs.PathGraph(5)
@@ -2279,15 +2313,24 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
22792313
if oriented is None:
22802314
oriented = self.is_directed()
22812315

2282-
if vertices is None:
2316+
row_keys = None
2317+
if vertices is True:
2318+
vertices = self.vertices(sort=False)
2319+
row_keys = tuple(vertices) # because a list is not hashable
2320+
elif vertices is None:
22832321
vertices = self.vertices(sort=False)
22842322
elif (len(vertices) != self.num_verts() or
22852323
set(vertices) != set(self.vertex_iterator())):
22862324
raise ValueError("parameter vertices must be a permutation of the vertices")
22872325

2326+
column_keys = None
22882327
verts = {v: i for i, v in enumerate(vertices)}
2289-
if edges is None:
2290-
edges = self.edge_iterator(labels=False)
2328+
use_edge_labels = kwds.pop('use_edge_labels', False)
2329+
if edges is True:
2330+
edges = self.edges(labels=use_edge_labels)
2331+
column_keys = tuple(edges) # because an EdgesView is not hashable
2332+
elif edges is None:
2333+
edges = self.edge_iterator(labels=use_edge_labels)
22912334
elif len(edges) != self.size():
22922335
raise ValueError("parameter edges must be a permutation of the edges")
22932336
else:
@@ -2319,8 +2362,13 @@ def reorder(u, v):
23192362
m[verts[e[0]], i] += 1
23202363
m[verts[e[1]], i] += 1
23212364

2365+
if row_keys is not None or column_keys is not None:
2366+
m.set_immutable()
2367+
return matrix(m, row_keys=row_keys, column_keys=column_keys)
2368+
23222369
if immutable:
23232370
m.set_immutable()
2371+
23242372
return m
23252373

23262374
def distance_matrix(self, vertices=None, *, base_ring=None, **kwds):

src/sage/matrix/args.pxd

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ cdef class MatrixArgs:
4747
cdef public Parent space # parent of matrix
4848
cdef public Parent base # parent of entries
4949
cdef public long nrows, ncols
50+
cdef public object row_keys, column_keys
5051
cdef public object entries
5152
cdef entries_type typ
5253
cdef public bint sparse
5354
cdef public dict kwds # **kwds for MatrixSpace()
5455
cdef bint is_finalized
5556

5657
cpdef Matrix matrix(self, bint convert=?)
58+
cpdef element(self, bint immutable=?)
5759
cpdef list list(self, bint convert=?)
5860
cpdef dict dict(self, bint convert=?)
5961

@@ -89,7 +91,11 @@ cdef class MatrixArgs:
8991
raise ArithmeticError("number of columns must be non-negative")
9092
cdef long p = self.ncols
9193
if p != -1 and p != n:
92-
raise ValueError(f"inconsistent number of columns: should be {p} but got {n}")
94+
raise ValueError(f"inconsistent number of columns: should be {p} "
95+
f"but got {n}")
96+
if self.column_keys is not None and n != len(self.column_keys):
97+
raise ValueError(f"inconsistent number of columns: should be cardinality of {self.column_keys} "
98+
f"but got {n}")
9399
self.ncols = n
94100

95101
cdef inline int set_nrows(self, long n) except -1:
@@ -102,8 +108,23 @@ cdef class MatrixArgs:
102108
cdef long p = self.nrows
103109
if p != -1 and p != n:
104110
raise ValueError(f"inconsistent number of rows: should be {p} but got {n}")
111+
if self.row_keys is not None and n != len(self.row_keys):
112+
raise ValueError(f"inconsistent number of rows: should be cardinality of {self.row_keys} "
113+
f"but got {n}")
105114
self.nrows = n
106115

116+
cdef inline int _ensure_nrows_ncols(self) except -1:
117+
r"""
118+
Make sure that the number of rows and columns is set.
119+
If ``row_keys`` or ``column_keys`` is not finite, this can raise an exception.
120+
"""
121+
if self.nrows == -1:
122+
self.nrows = len(self.row_keys)
123+
if self.ncols == -1:
124+
self.ncols = len(self.column_keys)
125+
126+
cpdef int set_column_keys(self, column_keys) except -1
127+
cpdef int set_row_keys(self, row_keys) except -1
107128
cpdef int set_space(self, space) except -1
108129

109130
cdef int finalize(self) except -1

0 commit comments

Comments
 (0)