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"""
Constructs an arbitrary multigraph.
Parameters
----------
adj_matrix :
The adjacency matrix of the graph
(any integer Hermitian matrix).
Two input types are accepted:
* Any matrix -- for instance,
* :class:`scipy.sparse.csr_array`,
* :class:`numpy.ndarray`,
* list of lists.
* :class:`networkx.Graph`.
* The adjacency matrix is extracted from the graph.
copy : bool, default=False
If ``True``, a hard copy of ``adj_matrix`` is stored.
Raises
------
TypeError
If ``adj_matrix`` is not a square matrix.
Notes
-----
The ``adj_matrix.data`` attribute is changed for
more efficient manipulation.
If the original matrix is needed,
invoke the constructor with ``copy=True`` or
call :meth:`adjacency_matrix()` after creating the multigraph.
"""
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))
return adj_matrix
[docs]
def laplacian_matrix(self):
raise NotImplementedError()
[docs]
def is_simple(self):
return False