"""Vertex orbit counts by ORCA, through the _orca_cpp extension."""
import networkx as nx
import numpy as np
from _orca_cpp import orca_count
from .orbit_converter import OrbitMatrixConverter
[docs]
class ORCAOrbitCounter:
"""Vertex orbit counts of a graph by ORCA (graphlets of 4 vertices only).
Parameters
----------
G : networkx.Graph
The graph; self-loops are ignored.
size : {4}, default 4
Graphlet size (15 orbits).
"""
def __init__(self, G: nx.Graph, size: int = 4):
if size != 4:
raise ValueError("The ORCA backend supports graphlet size 4 only; use EVOKE for size 5.")
self.G = G
self.size = size
self.counts = None
self.converter = OrbitMatrixConverter()
def _nx_to_cpp_adjlist(self):
"""Adjacency lists over positions in sorted node order, without self-loops, and the sorted nodes."""
nodes = sorted(self.G.nodes())
index = {node: i for i, node in enumerate(nodes)}
adj = [[index[nbr] for nbr in self.G.neighbors(node) if nbr != node] for node in nodes]
return adj, nodes
[docs]
def count_orbits(self) -> dict:
"""Count the induced orbits: ``{node: list of counts}``, also kept in ``self.counts``."""
adj, nodes = self._nx_to_cpp_adjlist()
rows = orca_count(adj, self.size)
self.counts = {node: row for node, row in zip(nodes, rows)}
return self.counts
[docs]
def get_orbits(self, induced: bool = False) -> np.ndarray:
"""The orbit counts, one row per node in sorted node order.
Parameters
----------
induced : bool, default False
Induced counts (ORCA's own) instead of non-induced ones.
Returns
-------
numpy.ndarray
An ``n x 15`` int64 array.
"""
if self.counts is None:
self.count_orbits()
orbit_matrix = np.array([self.counts[node] for node in sorted(self.counts)], dtype=np.int64)
orbit_matrix = orbit_matrix.reshape(len(self.counts), 15)
return orbit_matrix if induced else self.converter.induced_to_noninduced(orbit_matrix)
[docs]
@staticmethod
def count_csr(offsets, nbors, size=4, orbits=None):
"""Non-induced orbit counts of a graph in CSR form.
Parameters
----------
offsets, nbors : array_like of int
The graph over vertices 0..n-1: the neighbours of vertex ``i`` are
``nbors[offsets[i]:offsets[i + 1]]``.
size : {4}, default 4
Graphlet size.
orbits : iterable of int, optional
Accepted for :meth:`EVOKEOrbitCounter.count_csr`'s interface; all orbits are counted.
Returns
-------
numpy.ndarray
An ``n x 15`` int64 array.
"""
if size != 4:
raise ValueError("The ORCA backend supports graphlet size 4 only; use EVOKE for size 5.")
offsets = np.asarray(offsets, dtype=np.int64)
adj = [a.tolist() for a in np.split(np.asarray(nbors, dtype=np.int64), offsets[1:-1])] if len(offsets) > 1 else []
induced = np.array(orca_count(adj, 4), dtype=np.int64).reshape(len(adj), 15)
return OrbitMatrixConverter().induced_to_noninduced(induced)