Source code for hiperwalk.graph.multigraph

import numpy as np
from .graph import Graph
from scipy.sparse import issparse, csr_array, diags

[docs] class Multigraph(Graph): r""" Construct an arbitrary multigraph. This class facilitates the creation of a multigraph, in which multiple edges between the same pair of vertices are allowed. The graph's structure is determined by a Hermitian adjacency matrix, the entries of which are non-negative integers that represent the number of multiple edges between vertices. The multigraph also supports loops, which are considered arcs. Parameters ---------- adj_matrix : various types accepted The adjacency matrix of the graph, which must be a Hermitian matrix with non-negative integer entries. Acceptable input types include: * Direct matrix types such as: * :class:`scipy.sparse.csr_array`, * :class:`numpy.ndarray`, * List of lists. * :class:`networkx.Graph`: The adjacency matrix is automatically extracted from the specified networkx graph. copy : bool, default=False Specifies whether to store a hard copy of ``adj_matrix``: * If ``True``, a deep copy of the adjacency matrix is created and stored. * If ``False``, a reference to the original ``adj_matrix`` is stored. Raises ------ TypeError If ``adj_matrix`` is not a square matrix. Notes ----- The ``adj_matrix.data`` attribute may be modified for more efficient manipulation. If the original matrix data is required, it is recommended to initialize the constructor with ``copy=True``. Alternatively, the original matrix can be retrieved anytime by calling :meth:`adjacency_matrix()` after the multigraph has been created. When defining an instance of the Coined class on a multigraph, the number of multiple edges impacts the dimension of the coin. When defining an instance of the ContinuousTime class on a multigraph, the number of multiple edges is treated as an edge weight. """ # def _default_dtype(self): # return np.int32 def _count_loops(self, adj_matrix): loops = [adj_matrix[v, v] for v in range(adj_matrix.shape[0])] self._num_loops = np.sum(loops) def _set_adj_matrix(self, adj_matrix): # if not np.issubdtype(adj_matrix.dtype, self._default_dtype()): # adj_matrix.data = adj_matrix.data.astype( # self._default_dtype(), # copy=False) data = adj_matrix.data for i in range(1, len(data)): data[i] += data[i - 1] self._adj_matrix = adj_matrix
[docs] def __init__(self, adj_matrix, copy=False): super().__init__(adj_matrix, copy)
# TODO: is it useful to store the underlying simple graph? def _entry(self, row, col): return self._adj_matrix[row, col] def _find_entry(self, entry): adj_matrix = self._adj_matrix index = _interval_binary_search(adj_matrix.data, entry) + 1 col = adj_matrix.indices[index] row = _interval_binary_search(adj_matrix.indptr, index) return (row, col)
[docs] def number_of_edges(self, u=None, v=None): r""" Return number of edges. Return number of edges in the multigraph if ``u is None`` and ``v is None``. Otherwise, return the number of edges incident to both ``u`` and ``v``. Parameters ---------- u, v : default=None Vertices of the Graph. Returns ------- Number of edges in the graph """ if u is None and v is None: non_loops = self._adj_matrix.data[-1] - self._num_loops num_edges = non_loops >> 1 return num_edges + self._num_loops # number of edges incident to both u and v indptr = self._adj_matrix.indptr data = self._adj_matrix.data index = indptr[u] try: index += self._neighbor_index(u, v) except ValueError: return 0 out_degree = (data[index] - data[index - 1] if index > 0 else data[index]) return out_degree
[docs] def degree(self, vertex): vertex = self.vertex_number(vertex) adj_matrix = self._adj_matrix start = adj_matrix.indptr[vertex] - 1 end = adj_matrix.indptr[vertex + 1] - 1 if start < 0: return adj_matrix.data[end] return adj_matrix.data[end] - adj_matrix.data[start]
# TODO: add functions to manage multiedges
[docs] def adjacency_matrix(self): data = np.copy(self._adj_matrix.data) for i in range(len(data) - 1, 0, -1): data[i] -= data[i - 1] indices = self._adj_matrix.indices indptr = self._adj_matrix.indptr adj_matrix = csr_array((data, indices, indptr), copy=True) return adj_matrix
[docs] def laplacian_matrix(self): raise NotImplementedError()
[docs] def is_simple(self): r""" Return False. """ return False