@@ -1900,6 +1900,72 @@ def to_dictionary(self, edge_labels=False, multiple_edges=False):
19001900
19011901 return d
19021902
1903+ def _vertex_indices_and_keys(self, vertices=None, *, sort=None):
1904+ r"""
1905+ Process a ``vertices`` parameter.
1906+
1907+ This is a helper function for :meth:`adjacency_matrix`,
1908+ :meth:`incidence_matrix`, :meth:`weighted_adjacency_matrix`,
1909+ and :meth:`kirchhoff_matrix`.
1910+
1911+ INPUT:
1912+
1913+ - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``)
1914+
1915+ - when a list, the `i`-th row and column of the matrix correspond to
1916+ the `i`-th vertex in the ordering of ``vertices``,
1917+ - when ``None``, the `i`-th row and column of the matrix correspond to
1918+ the `i`-th vertex in the ordering given by
1919+ :meth:`GenericGraph.vertices`
1920+ - when ``True``, construct an endomorphism of a free module instead of
1921+ a matrix, where the module's basis is indexed by the vertices.
1922+
1923+ - ``sort`` -- boolean or ``None`` (default); passed to :meth:`vertices`
1924+ when ``vertices`` is not a list.
1925+
1926+ OUTPUT: pair of:
1927+
1928+ - ``vertex_indices`` -- a dictionary mapping vertices to numerical indices,
1929+ - ``keys`` -- either a tuple of basis keys (when using a
1930+ :class:`CombinatorialFreeModule`) or ``None`` (when using a
1931+ :class:`FreeModule`, :func:`matrix`).
1932+
1933+ EXAMPLES::
1934+
1935+ sage: G = graphs.PathGraph(5)
1936+ sage: G.relabel(['o....', '.o...', '..o..', '...o.', '....o'])
1937+ sage: G._vertex_indices_and_keys(None)
1938+ ({'....o': 0, '...o.': 1, '..o..': 2, '.o...': 3, 'o....': 4},
1939+ None)
1940+ sage: G._vertex_indices_and_keys(None, sort=False)
1941+ ({'....o': 4, '...o.': 3, '..o..': 2, '.o...': 1, 'o....': 0},
1942+ None)
1943+ sage: G._vertex_indices_and_keys(['..o..', '.o...', '...o.', 'o....', '....o'])
1944+ ({'....o': 4, '...o.': 2, '..o..': 0, '.o...': 1, 'o....': 3},
1945+ None)
1946+ sage: G._vertex_indices_and_keys(True)
1947+ ({'....o': 4, '...o.': 3, '..o..': 2, '.o...': 1, 'o....': 0},
1948+ ('o....', '.o...', '..o..', '...o.', '....o'))
1949+ sage: G._vertex_indices_and_keys(True, sort=True)
1950+ ({'....o': 0, '...o.': 1, '..o..': 2, '.o...': 3, 'o....': 4},
1951+ ('....o', '...o.', '..o..', '.o...', 'o....'))
1952+ """
1953+ n = self.order()
1954+ keys = None
1955+ if vertices is True:
1956+ vertices = self.vertices(sort=sort if sort is not None else False)
1957+ keys = tuple(vertices) # tuple to make it hashable
1958+ elif vertices is None:
1959+ try:
1960+ vertices = self.vertices(sort=sort if sort is not None else True)
1961+ except TypeError:
1962+ raise TypeError("Vertex labels are not comparable. You must "
1963+ "specify an ordering using parameter 'vertices'")
1964+ elif (len(vertices) != n or
1965+ set(vertices) != set(self.vertex_iterator())):
1966+ raise ValueError("parameter 'vertices' must be a permutation of the vertices")
1967+ return {v: i for i, v in enumerate(vertices)}, keys
1968+
19031969 def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds):
19041970 r"""
19051971 Return the adjacency matrix of the (di)graph.
@@ -1911,10 +1977,16 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds
19111977 - ``sparse`` -- boolean (default: ``None``); whether to represent with a
19121978 sparse matrix
19131979
1914- - ``vertices`` -- list (default: ``None``); the ordering of
1915- the vertices defining how they should appear in the
1916- matrix. By default, the ordering given by
1917- :meth:`GenericGraph.vertices` with ``sort=True`` is used.
1980+ - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``);
1981+
1982+ - when a list, the `i`-th row and column of the matrix correspond to
1983+ the `i`-th vertex in the ordering of ``vertices``,
1984+ - when ``None``, the `i`-th row and column of the matrix correspond to
1985+ the `i`-th vertex in the ordering given by
1986+ :meth:`GenericGraph.vertices` with ``sort=True``.
1987+ - when ``True``, construct an endomorphism of a free module instead of
1988+ a matrix, where the module's basis is indexed by the vertices.
1989+
19181990 If the vertices are not comparable, the keyword ``vertices`` must be
19191991 used to specify an ordering, or a :class:`TypeError` exception will
19201992 be raised.
@@ -2025,27 +2097,45 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds
20252097 ValueError: matrix is immutable; please change a copy instead
20262098 (i.e., use copy(M) to change a copy of M).
20272099
2100+ Creating a module endomorphism::
2101+
2102+ sage: # needs sage.modules
2103+ sage: D12 = posets.DivisorLattice(12).hasse_diagram()
2104+ sage: phi = D12.adjacency_matrix(vertices=True); phi
2105+ Generic endomorphism of
2106+ Free module generated by {1, 2, 3, 4, 6, 12} over Integer Ring
2107+ sage: print(phi._unicode_art_matrix())
2108+ 1 2 3 4 6 12
2109+ 1⎛ 0 1 1 0 0 0⎞
2110+ 2⎜ 0 0 0 1 1 0⎟
2111+ 3⎜ 0 0 0 0 1 0⎟
2112+ 4⎜ 0 0 0 0 0 1⎟
2113+ 6⎜ 0 0 0 0 0 1⎟
2114+ 12⎝ 0 0 0 0 0 0⎠
2115+
20282116 TESTS::
20292117
2030- sage: graphs.CubeGraph(8).adjacency_matrix().parent() # needs sage.modules
2118+ sage: # needs sage.modules
2119+ sage: graphs.CubeGraph(8).adjacency_matrix().parent()
20312120 Full MatrixSpace of 256 by 256 dense matrices over Integer Ring
2032- sage: graphs.CubeGraph(9).adjacency_matrix().parent() # needs sage.modules
2121+ sage: graphs.CubeGraph(9).adjacency_matrix().parent()
20332122 Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring
2034- sage: Graph([(i, i+1) for i in range(500)] + [(0,1),], # needs sage.modules
2123+ sage: Graph([(i, i+1) for i in range(500)] + [(0,1),],
20352124 ....: multiedges=True).adjacency_matrix().parent()
20362125 Full MatrixSpace of 501 by 501 dense matrices over Integer Ring
2037- sage: graphs.PathGraph(5).adjacency_matrix(vertices=[0,0,0,0,0]) # needs sage.modules
2126+ sage: graphs.PathGraph(5).adjacency_matrix(vertices=[0,0,0,0,0])
20382127 Traceback (most recent call last):
20392128 ...
2040- ValueError: parameter vertices must be a permutation of the vertices
2041- sage: graphs.PathGraph(5).adjacency_matrix(vertices=[1,2,3]) # needs sage.modules
2129+ ValueError: parameter ' vertices' must be a permutation of the vertices
2130+ sage: graphs.PathGraph(5).adjacency_matrix(vertices=[1,2,3])
20422131 Traceback (most recent call last):
20432132 ...
2044- ValueError: parameter vertices must be a permutation of the vertices
2133+ ValueError: parameter 'vertices' must be a permutation of the vertices
2134+
20452135 sage: Graph ([[0, 42, 'John'], [(42, 'John')]]).adjacency_matrix()
20462136 Traceback (most recent call last):
20472137 ...
2048- TypeError: Vertex labels are not comparable. You must specify an ordering using parameter `` vertices``
2138+ TypeError: Vertex labels are not comparable. You must specify an ordering using parameter ' vertices'
20492139 sage: Graph ([[0, 42, 'John'], [(42, 'John')]]).adjacency_matrix(vertices=['John', 42, 0])
20502140 [0 1 0]
20512141 [1 0 0]
@@ -2056,25 +2146,17 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds
20562146 sparse = True
20572147 if self.has_multiple_edges() or n <= 256 or self.density() > 0.05:
20582148 sparse = False
2149+ vertex_indices, keys = self._vertex_indices_and_keys(vertices)
2150+ if keys is not None:
2151+ kwds = copy(kwds)
2152+ kwds['row_keys'] = kwds['column_keys'] = keys
20592153
2060- if vertices is None:
2061- try:
2062- vertices = self.vertices(sort=True)
2063- except TypeError:
2064- raise TypeError("Vertex labels are not comparable. You must "
2065- "specify an ordering using parameter "
2066- "``vertices``")
2067- elif (len(vertices) != n or
2068- set(vertices) != set(self.vertex_iterator())):
2069- raise ValueError("parameter vertices must be a permutation of the vertices")
2070-
2071- new_indices = {v: i for i, v in enumerate(vertices)}
20722154 D = {}
20732155 directed = self._directed
20742156 multiple_edges = self.allows_multiple_edges()
20752157 for u, v, l in self.edge_iterator():
2076- i = new_indices [u]
2077- j = new_indices [v]
2158+ i = vertex_indices [u]
2159+ j = vertex_indices [v]
20782160 if multiple_edges and (i, j) in D:
20792161 D[i, j] += 1
20802162 if not directed and i != j:
@@ -2298,7 +2380,7 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
22982380 sage: P5.incidence_matrix(vertices=[1] * P5.order()) # needs sage.modules
22992381 Traceback (most recent call last):
23002382 ...
2301- ValueError: parameter vertices must be a permutation of the vertices
2383+ ValueError: parameter ' vertices' must be a permutation of the vertices
23022384 sage: P5.incidence_matrix(edges=[(0, 1)] * P5.size()) # needs sage.modules
23032385 Traceback (most recent call last):
23042386 ...
@@ -2313,18 +2395,9 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
23132395 if oriented is None:
23142396 oriented = self.is_directed()
23152397
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:
2321- vertices = self.vertices(sort=False)
2322- elif (len(vertices) != self.num_verts() or
2323- set(vertices) != set(self.vertex_iterator())):
2324- raise ValueError("parameter vertices must be a permutation of the vertices")
2398+ vertex_indices, row_keys = self._vertex_indices_and_keys(vertices, sort=False)
23252399
23262400 column_keys = None
2327- verts = {v: i for i, v in enumerate(vertices)}
23282401 use_edge_labels = kwds.pop('use_edge_labels', False)
23292402 if edges is True:
23302403 edges = self.edges(labels=use_edge_labels)
@@ -2336,13 +2409,13 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None
23362409 else:
23372410 # We check that we have the same set of unlabeled edges
23382411 if oriented:
2339- i_edges = [(verts [e[0]], verts [e[1]]) for e in edges]
2340- s_edges = [(verts [u], verts [v]) for u, v in self.edge_iterator(labels=False)]
2412+ i_edges = [(vertex_indices [e[0]], vertex_indices [e[1]]) for e in edges]
2413+ s_edges = [(vertex_indices [u], vertex_indices [v]) for u, v in self.edge_iterator(labels=False)]
23412414 else:
23422415 def reorder(u, v):
23432416 return (u, v) if u <= v else (v, u)
2344- i_edges = [reorder(verts [e[0]], verts [e[1]]) for e in edges]
2345- s_edges = [reorder(verts [u], verts [v]) for u, v in self.edge_iterator(labels=False)]
2417+ i_edges = [reorder(vertex_indices [e[0]], vertex_indices [e[1]]) for e in edges]
2418+ s_edges = [reorder(vertex_indices [u], vertex_indices [v]) for u, v in self.edge_iterator(labels=False)]
23462419 if sorted(i_edges) != sorted(s_edges):
23472420 raise ValueError("parameter edges must be a permutation of the edges")
23482421
@@ -2355,12 +2428,12 @@ def reorder(u, v):
23552428 if oriented:
23562429 for i, e in enumerate(edges):
23572430 if e[0] != e[1]:
2358- m[verts [e[0]], i] = -1
2359- m[verts [e[1]], i] = +1
2431+ m[vertex_indices [e[0]], i] = -1
2432+ m[vertex_indices [e[1]], i] = +1
23602433 else:
23612434 for i, e in enumerate(edges):
2362- m[verts [e[0]], i] += 1
2363- m[verts [e[1]], i] += 1
2435+ m[vertex_indices [e[0]], i] += 1
2436+ m[vertex_indices [e[1]], i] += 1
23642437
23652438 if row_keys is not None or column_keys is not None:
23662439 m.set_immutable()
@@ -2524,10 +2597,19 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None,
25242597 - ``sparse`` -- boolean (default: ``True``); whether to use a sparse or
25252598 a dense matrix
25262599
2527- - ``vertices`` -- list (default: ``None``); when specified, each vertex
2528- is represented by its position in the list ``vertices``, otherwise
2529- each vertex is represented by its position in the list returned by
2530- method :meth:`vertices`
2600+ - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``);
2601+
2602+ - when a list, the `i`-th row and column of the matrix correspond to
2603+ the `i`-th vertex in the ordering of ``vertices``,
2604+ - when ``None``, the `i`-th row and column of the matrix correspond to
2605+ the `i`-th vertex in the ordering given by
2606+ :meth:`GenericGraph.vertices` with ``sort=True``.
2607+ - when ``True``, construct an endomorphism of a free module instead of
2608+ a matrix, where the module's basis is indexed by the vertices.
2609+
2610+ If the vertices are not comparable, the keyword ``vertices`` must be
2611+ used to specify an ordering, or a :class:`TypeError` exception will
2612+ be raised.
25312613
25322614 - ``default_weight`` -- (default: ``None``); specifies the weight to
25332615 replace any ``None`` edge label. When not specified an error is raised
@@ -2579,6 +2661,21 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None,
25792661 ValueError: matrix is immutable; please change a copy instead
25802662 (i.e., use copy(M) to change a copy of M).
25812663
2664+ Creating a module morphism::
2665+
2666+ sage: # needs sage.modules
2667+ sage: G = Graph(sparse=True, weighted=True)
2668+ sage: G.add_edges([('A', 'B', 1), ('B', 'C', 2), ('A', 'C', 3), ('A', 'D', 4)])
2669+ sage: phi = G.weighted_adjacency_matrix(vertices=True); phi
2670+ Generic endomorphism of
2671+ Free module generated by {'A', 'B', 'C', 'D'} over Integer Ring
2672+ sage: print(phi._unicode_art_matrix())
2673+ A B C D
2674+ A⎛0 1 3 4⎞
2675+ B⎜1 0 2 0⎟
2676+ C⎜3 2 0 0⎟
2677+ D⎝4 0 0 0⎠
2678+
25822679 TESTS:
25832680
25842681 The following doctest verifies that :issue:`4888` is fixed::
@@ -2609,11 +2706,10 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None,
26092706 if self.has_multiple_edges():
26102707 raise NotImplementedError("don't know how to represent weights for a multigraph")
26112708
2612- if vertices is None:
2613- vertices = self.vertices(sort=True)
2614- elif (len(vertices) != self.num_verts() or
2615- set(vertices) != set(self.vertex_iterator())):
2616- raise ValueError("parameter vertices must be a permutation of the vertices")
2709+ vertex_indices, row_column_keys = self._vertex_indices_and_keys(vertices)
2710+ if row_column_keys is not None:
2711+ kwds = copy(kwds)
2712+ kwds['row_keys'] = kwds['column_keys'] = row_column_keys
26172713
26182714 # Method for checking edge weights and setting default weight
26192715 if default_weight is None:
@@ -2628,18 +2724,16 @@ def func(u, v, label):
26282724 return default_weight
26292725 return label
26302726
2631- new_indices = {v: i for i,v in enumerate(vertices)}
2632-
26332727 D = {}
26342728 if self._directed:
26352729 for u, v, label in self.edge_iterator():
2636- i = new_indices [u]
2637- j = new_indices [v]
2730+ i = vertex_indices [u]
2731+ j = vertex_indices [v]
26382732 D[i, j] = func(u, v, label)
26392733 else:
26402734 for u, v, label in self.edge_iterator():
2641- i = new_indices [u]
2642- j = new_indices [v]
2735+ i = vertex_indices [u]
2736+ j = vertex_indices [v]
26432737 label = func(u, v, label)
26442738 D[i, j] = label
26452739 D[j, i] = label
@@ -2707,6 +2801,20 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl
27072801
27082802 - Else, `D-M` is used in calculation of Kirchhoff matrix
27092803
2804+ - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``);
2805+
2806+ - when a list, the `i`-th row and column of the matrix correspond to
2807+ the `i`-th vertex in the ordering of ``vertices``,
2808+ - when ``None``, the `i`-th row and column of the matrix correspond to
2809+ the `i`-th vertex in the ordering given by
2810+ :meth:`GenericGraph.vertices` with ``sort=True``.
2811+ - when ``True``, construct an endomorphism of a free module instead of
2812+ a matrix, where the module's basis is indexed by the vertices.
2813+
2814+ If the vertices are not comparable, the keyword ``vertices`` must be
2815+ used to specify an ordering, or a :class:`TypeError` exception will
2816+ be raised.
2817+
27102818 Note that any additional keywords will be passed on to either the
27112819 :meth:`~GenericGraph.adjacency_matrix` or
27122820 :meth:`~GenericGraph.weighted_adjacency_matrix` method.
@@ -2788,18 +2896,36 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl
27882896 sage: M = G.kirchhoff_matrix(vertices=[0, 1], immutable=True) # needs sage.modules
27892897 sage: M.is_immutable() # needs sage.modules
27902898 True
2899+
2900+ Creating a module morphism::
2901+
2902+ sage: # needs sage.modules
2903+ sage: G = Graph(sparse=True, weighted=True)
2904+ sage: G.add_edges([('A', 'B', 1), ('B', 'C', 2), ('A', 'C', 3), ('A', 'D', 4)])
2905+ sage: phi = G.laplacian_matrix(weighted=True, vertices=True); phi
2906+ Generic endomorphism of
2907+ Free module generated by {'A', 'B', 'C', 'D'} over Integer Ring
2908+ sage: print(phi._unicode_art_matrix())
2909+ A B C D
2910+ A⎛ 8 -1 -3 -4⎞
2911+ B⎜-1 3 -2 0⎟
2912+ C⎜-3 -2 5 0⎟
2913+ D⎝-4 0 0 4⎠
2914+
27912915 """
2792- from sage.matrix.constructor import diagonal_matrix
2916+ from sage.matrix.constructor import diagonal_matrix, matrix
27932917
27942918 set_immutable = kwds.pop('immutable', False)
27952919
2920+ vertex_indices, keys = self._vertex_indices_and_keys(kwds.pop('vertices', None))
2921+
27962922 if weighted is None:
27972923 weighted = self._weighted
27982924
27992925 if weighted:
2800- M = self.weighted_adjacency_matrix(immutable=True, **kwds)
2926+ M = self.weighted_adjacency_matrix(vertices=list(vertex_indices), immutable=True, **kwds)
28012927 else:
2802- M = self.adjacency_matrix(immutable=True, **kwds)
2928+ M = self.adjacency_matrix(vertices=list(vertex_indices), immutable=True, **kwds)
28032929
28042930 D = M.parent(0)
28052931
@@ -2839,6 +2965,8 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl
28392965 else:
28402966 ret = D - M
28412967
2968+ if keys is not None:
2969+ return matrix(ret, row_keys=keys, column_keys=keys)
28422970 if set_immutable:
28432971 ret.set_immutable()
28442972 return ret
0 commit comments