Source code for synkit.Graph.syn_graph
"""synkit.Graph.syn_graph
======================
Wrapper around `networkx.Graph` providing both original and canonical forms,
plus a SHA‑256 signature for fast isomorphism checks.
Key features
------------
* **Value‑object semantics** – `__eq__` and `__hash__` use the canonical signature,
so graphs can be used in sets/dicts.
* **Lazy canonicalisation** – canonical graph & signature are computed once on demand
(cached internally) to avoid upfront cost when not needed.
* **Transparent delegation** – any unknown attribute/method is forwarded to the raw graph.
Example
-------
>>> G = nx.Graph(); G.add_node(1, element='C')
>>> SG = SynGraph(G)
>>> SG.signature # 32‑hex SHA‑256 digest
'8dc1f7b843e447ff4b67bf0ccc175f63'
>>> SG.canonical # relabelled, sorted graph
<networkx.Graph with 1 nodes>
>>>
"""
from __future__ import annotations
from typing import Any, Dict, Iterable, Optional, Tuple, Union
import networkx as nx
from synkit.Graph.canon_graph import GraphCanonicaliser
__all__ = ["SynGraph"]
[docs]
class SynGraph:
"""Wrapper around networkx.Graph providing both its original and
(optionally) canonicalized form, plus a SHA-256 signature.
Parameters:
- graph (nx.Graph): The NetworkX graph to wrap.
- canonicaliser (Optional[GraphCanonicaliser]): If provided, used to
produce the canonical form; otherwise a default is constructed.
- canon (bool): If True (default), computes and stores both
`.canonical` and `.signature`. Otherwise they remain None.
Public Properties:
- raw nx.Graph The original graph.
- canonical Optional[nx.Graph] The canonicalized graph (or None).
- signature Optional[str] The SHA-256 hex digest (or None).
Methods:
- get_nodes(data: bool = True) -> Iterable[…]
- get_edges(data: bool = True) -> Iterable[…]
- help() Print this API summary.
"""
def __init__(
self,
graph: nx.Graph,
canonicaliser: Optional[GraphCanonicaliser] = None,
canon: bool = True,
) -> None:
"""Initialize a SynGraph wrapper.
Parameters:
- graph (nx.Graph): Input graph.
- canonicaliser (Optional[GraphCanonicaliser]): Canonicaliser instance.
- canon (bool): Whether to compute canonical form/signature.
"""
self._raw: nx.Graph = graph
self._canonicaliser: GraphCanonicaliser = canonicaliser or GraphCanonicaliser()
self._do_canon: bool = canon
if self._do_canon:
# build & store canonical graph
self._canonical: nx.Graph = self._canonicaliser.make_canonical_graph(graph)
else:
# skip canonicalisation
self._canonical = None
def __getattr__(self, name: str) -> Any:
"""Delegate any unknown attribute lookup to the underlying ._raw
graph."""
return getattr(self._raw, name)
def __eq__(self, other: object) -> bool:
"""Two SynGraph instances are equal iff their signatures match."""
if not isinstance(other, SynGraph):
return False
return self.signature == other.signature
def __hash__(self) -> int:
"""Hash on the signature, allowing use in sets and as dict keys."""
return hash(self.signature)
@property
def raw(self) -> nx.Graph:
"""The original NetworkX graph."""
return self._raw
@property
def canonical(self) -> Optional[nx.Graph]:
"""The canonicalized graph, or None if canon=False."""
return self._canonical
@property
def signature(self) -> Optional[str]:
"""SHA-256 hex digest of the canonical form, or None."""
return self._canonicaliser.canonical_signature(self._raw)
[docs]
def get_nodes(
self, data: bool = True
) -> Iterable[Union[Any, Tuple[Any, Dict[str, Any]]]]:
"""Yield nodes from the original graph.
Parameters
----------
data : bool, default True
If True, yield (node, data_dict), else just node IDs.
"""
return self._raw.nodes(data=data)
[docs]
def get_edges(
self, data: bool = True
) -> Iterable[Union[Tuple[Any, Any], Tuple[Any, Any, Dict[str, Any]]]]:
"""Yield edges from the original graph.
Parameters
----------
data : bool, default True
If True, yield (u, v, data_dict), else just (u, v).
"""
return self._raw.edges(data=data)
def __repr__(self) -> str:
try:
v = self._raw.number_of_nodes()
e = self._raw.number_of_edges()
except Exception:
v = e = 0
return f"<SynGraph |V|={v} |E|={e} sig={self.signature[:8]}>"
[docs]
def help(self) -> None:
"""Print a summary of the SynGraph API."""
print(
"SynGraph Help\n"
"----------\n"
"raw original networkx.Graph\n"
"canonical canonical networkx.Graph\n"
"signature SHA-256 hex digest\n"
"get_nodes() nodes (with data)\n"
"get_edges() edges (with data)\n"
"__eq__/__hash__ use signature for comparisons"
)