Source code for synkit.Graph.Feature.graph_fps

import networkx as nx
import hashlib
import numpy as np


[docs] class GraphFP: def __init__( self, graph: nx.Graph, nBits: int = 1024, hash_alg: str = "sha256" ) -> None: """Initialize the GraphFP class to create binary fingerprints based on various graph characteristics. Parameters: - graph (nx.Graph): Graph on which to perform analysis. - nBits (int): Size of the binary fingerprint in bits. - hash_alg (str): Cryptographic hash function used for hashing. """ self.graph = graph self.nBits = nBits self.hash_alg = hash_alg self.hash_function = getattr(hashlib, self.hash_alg)
[docs] def fingerprint(self, method: str) -> str: """Generate a binary string fingerprint of the graph using the specified method. Parameters: - method (str): The method to use for fingerprinting ('spectrum', 'adjacency', 'degree', 'motif') Returns: - str: A binary string of length `nBits` that represents the fingerprint of the graph. """ if method == "spectrum": fp = self._spectrum_fp() elif method == "adjacency": fp = self._adjacency_fp() elif method == "degree": fp = self._degree_sequence_fp() elif method == "motif": fp = self._motif_count_fp() else: raise ValueError("Unsupported fingerprinting method.") # If the fingerprint is shorter than nBits, use iterative deepening if len(fp) < self.nBits: fp += self.iterative_deepening(self.nBits - len(fp)) return fp[: self.nBits]
def _spectrum_fp(self) -> str: # Graph spectrum (eigenvalues of the adjacency matrix) eigenvalues = np.linalg.eigvals(nx.adjacency_matrix(self.graph).todense()) sorted_eigenvalues = np.sort(eigenvalues)[: self.nBits] eigen_str = "".join( bin(int(abs(eig)))[2:].zfill(8) for eig in sorted_eigenvalues ) return eigen_str[: self.nBits] def _adjacency_fp(self) -> str: # Adjacency matrix flattened adj_matrix = nx.adjacency_matrix(self.graph).todense().flatten() adj_str = "".join(str(int(x)) for x in adj_matrix) return adj_str[: self.nBits] def _degree_sequence_fp(self) -> str: # Degree sequence degrees = sorted([d for n, d in self.graph.degree()], reverse=True) degree_str = "".join(bin(d)[2:].zfill(8) for d in degrees) return degree_str[: self.nBits] def _motif_count_fp(self) -> str: # Motif counts (e.g., number of triangles) triangles = sum(nx.triangles(self.graph).values()) // 3 triangle_str = bin(triangles)[2:].zfill(self.nBits) return triangle_str[: self.nBits]
[docs] def iterative_deepening(self, remaining_bits: int) -> str: """Extend the hash length using iterative hashing until the desired bit length is achieved. Parameters: - remaining_bits (int): Number of bits needed to complete the fingerprint to `nBits`. Returns: - str: Additional binary data to achieve the desired hash length. """ additional_data = "" hash_obj = self.hash_function() while len(additional_data) * 4 < remaining_bits: hash_obj.update(additional_data.encode()) additional_data += hash_obj.hexdigest() return bin(int(additional_data, 16))[2:][:remaining_bits]