Source code for orbitsi.orbit.orbit_converter

"""Exact conversion of vertex orbit counts between induced and non-induced."""
import numpy as np
import os


[docs] class OrbitMatrixConverter: """Converts vertex orbit counts between induced and non-induced. Orbits are grouped by graphlet size: orbit 0 (an edge) is the same either way; orbits 1-3, 4-14 and 15-72 are related by the matrices for 3-, 4- and 5-vertex graphlets (non-induced = M @ induced). Each M is unit upper-triangular with integer entries, so its inverse is an integer matrix too, and both directions are exact in int64. """ BLOCKS = ((3, slice(1, 4)), (4, slice(4, 15)), (5, slice(15, 73))) _loaded = None def __init__(self): if OrbitMatrixConverter._loaded is None: this_dir = os.path.dirname(__file__) path = os.path.join(this_dir, 'orbit_conversion_matrices.npz') with np.load(path) as f: matrices = {int(k.split('_')[1]): np.rint(f[k]).astype(np.int64) for k in f.files} inverses = {k: np.rint(np.linalg.inv(m.astype(np.float64))).astype(np.int64) for k, m in matrices.items()} for k, m in matrices.items(): assert np.array_equal(m @ inverses[k], np.eye(len(m), dtype=np.int64)), k OrbitMatrixConverter._loaded = (matrices, inverses) self.matrices, self.inverses = OrbitMatrixConverter._loaded def _convert(self, matrix: np.ndarray, mats: dict) -> np.ndarray: """Apply each graphlet size's matrix in mats to its block of orbit columns.""" matrix = np.asarray(matrix, dtype=np.int64) if matrix.ndim != 2 or matrix.shape[1] not in (15, 73): raise ValueError("Unsupported orbit count size for conversion (expected 15 or 73 orbits per vertex).") out = matrix.copy() for size, cols in self.BLOCKS: if cols.stop <= matrix.shape[1]: out[:, cols] = matrix[:, cols] @ mats[size].T return out
[docs] def noninduced_to_induced(self, noninduced_matrix: np.ndarray) -> np.ndarray: """Convert non-induced orbit counts (one row per vertex) to induced counts.""" return self._convert(noninduced_matrix, self.inverses)
[docs] def induced_to_noninduced(self, induced_matrix: np.ndarray) -> np.ndarray: """Convert induced orbit counts (one row per vertex) to non-induced counts.""" return self._convert(induced_matrix, self.matrices)