Source code for synkit.Graph.MTG.mtg_explore
from typing import Optional, List
from synkit.Graph.MTG.mtg import MTG
from synkit.Rule.Apply.rule_matcher import RuleMatcher
from synkit.Graph.MTG.mcs_matcher import MCSMatcher
from networkx import Graph
[docs]
def find_mtg(
g1: Graph,
g2: Graph,
ground_truth: str,
node_label_names: Optional[List[str]] = None,
) -> Optional[MTG]:
"""
Attempt to construct a Mapping Transformation Graph (MTG) for two input graphs
by finding maximum common substructure mappings and validating against a ground truth.
:param g1: The first input graph to match.
:type g1: networkx.Graph
:param g2: The second input graph to match.
:type g2: networkx.Graph
:param ground_truth: A string representation of the expected atom-atom mapping (AAM)
used to validate candidate mappings.
:type ground_truth: str
:param node_label_names: List of node attribute names to use for MCS matching.
Defaults to ["element", "charge", "hcount"].
:type node_label_names: list of str, optional
:returns: An MTG instance if a valid mapping satisfying the ground truth is found;
otherwise, None.
:rtype: MTG or None
:raises ValueError: If input graphs are empty or ground_truth is invalid format.
:example:
>>> from networkx import Graph
>>> g1, g2 = Graph(), Graph()
>>> # populate g1 and g2 with nodes/edges
>>> mtg = find_mtg(
... g1,
... g2,
... ground_truth="{0:1, 1:0}",
... node_label_names=["element", "charge", "hcount"]
... )
>>> if mtg:
... print(mtg)
"""
# Validate inputs
if not g1 or not g2:
raise ValueError("Input graphs g1 and g2 must be non-empty.")
if not isinstance(ground_truth, str) or not ground_truth.strip():
raise ValueError("Ground truth mapping must be a non-empty string.")
# Set default node_label_names if not provided
if node_label_names is None:
node_label_names = ["element", "charge", "hcount"]
# Initialize maximum common substructure matcher
mcs = MCSMatcher(node_label_names=node_label_names)
mcs.find_rc_mapping(g1, g2, mcs=False)
mappings = mcs._mappings
for mapping in mappings:
# Construct MTG using current mapping
mtg = MTG([g1, g2], mappings=[mapping])
aam = mtg.get_aam()
try:
# Validate generated AAM against ground truth
RuleMatcher(ground_truth, aam, explicit_h=False)
return mtg
except AssertionError:
# Mapping did not satisfy ground truth, try next
continue
# No valid mapping found
return None