Graph#
The synkit.Graph package provides the core graph-based infrastructure used across SynKit.
It supports graph construction, matching, canonicalization, and reaction-specific graph formalisms.
Most workflows in rule application, mapping validation, and CRN exploration rely on these utilities.
Key submodules include:
Matcher — graph isomorphism and subgraph search engines
ITS — Internal Transition State (ITS) graph construction and decomposition
MTG — Mechanistic Transition Graph generation and exploration
FG — graph-native functional-group detection and audit tooling
Context — reaction-center expansion for context-aware matching and analysis
Isomorphism, subgraph search, and match enumeration for labeled molecular graphs. Powers rule application and equivalence checks.
Construct and decompose Internal Transition State graphs to isolate reaction centers and represent bond-order changes explicitly.
Build Mechanistic Transition Graphs from reaction-center ITS graphs to represent stepwise mechanisms and compare pathways.
Detect functional groups directly on SynKit molecular graphs, with hierarchical labels and aromatic ring-system reporting.
Graph Canonicalization#
The class GraphCanonicaliser canonicalises a graph by
computing a deterministic relabeling of node indices. By default it employs a Weisfeiler–Lehman
(WL) colour-refinement backend (wl_iterations=3) to obtain a consistent canonical form across
isomorphic graphs [1].
1from synkit.IO import rsmi_to_its
2from synkit.Graph.canon_graph import GraphCanonicaliser
3from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
4
5canon = GraphCanonicaliser(backend='wl', wl_iterations=3)
6
7rsmi = (
8 '[CH3:1][CH:2]=[O:3].'
9 '[CH:4]([H:7])([H:8])[CH:5]=[O:6]>>'
10 '[CH3:1][CH:2]=[CH:4][CH:5]=[O:6].'
11 '[O:3]([H:7])([H:8])'
12)
13
14its_graph = rsmi_to_its(rsmi)
15canon_graph = canon.canonicalise_graph(its_graph).canonical_graph
16
17print(its_graph == canon_graph) # structural relabeling differs
18
19gm = GraphMatcherEngine(backend='nx')
20print(gm.isomorphic(its_graph, canon_graph)) # graph structure is preserved
Example output
False
True
Matcher#
The synkit.Graph.Matcher submodule provides matching engines for labeled graphs:
GraphMatcherEngine— generic graph isomorphism / subgraph checksSubgraphMatch— subgraph search and containment tests
Example: Graph Isomorphism#
Check whether two ITS graphs—derived from reaction SMILES differing only by atom-map ordering—are isomorphic.
1from synkit.IO import rsmi_to_its
2from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
3
4rsmi_1 = (
5 '[CH3:1][C:2](=[O:3])[OH:4].[CH3:5][OH:6]'
6 '>>'
7 '[CH3:1][C:2](=[O:3])[O:6][CH3:5].[OH2:4]'
8)
9rsmi_2 = (
10 '[CH3:5][C:1](=[O:2])[OH:3].[CH3:6][OH:4]'
11 '>>'
12 '[CH3:5][C:1](=[O:2])[O:4][CH3:6].[OH2:3]'
13)
14
15its_1 = rsmi_to_its(rsmi_1)
16its_2 = rsmi_to_its(rsmi_2)
17
18gm = GraphMatcherEngine(
19 backend='nx',
20 node_attrs=['element', 'charge'],
21 edge_attrs=['order'],
22)
23
24are_isomorphic = gm.isomorphic(its_1, its_2)
25print(are_isomorphic)
Example output
True
Example: Subgraph Search#
Locate a smaller “reaction-center” ITS graph as a subgraph within a larger ITS graph.
1from synkit.IO import rsmi_to_its
2from synkit.Graph.Matcher.subgraph_matcher import SubgraphMatch
3
4core_its = rsmi_to_its(
5 '[CH3:1][C:2](=[O:3])[OH:4]>>[CH3:1][C:2](=[O:3])[O:6][CH3:5]',
6 core=True
7)
8
9full_its = rsmi_to_its(
10 '[CH3:5][C:1](=[O:2])[OH:3]>>[CH3:5][C:1](=[O:2])[O:4][CH3:6]'
11)
12
13sub_search = SubgraphMatch()
14found = sub_search.subgraph_isomorphism(core_its, full_its)
15print(found)
Example output
True
ITS#
The synkit.Graph.ITS package supports the construction and decomposition of
Internal Transition State (ITS) graphs:
ITSConstruction— build ITS graphs from reactant/product graphsget_rc()— extract the minimal reaction-center subgraphits_decompose()— split an ITS graph into reactant/product graphs
Lewis State Graph fields#
SynKit 1.4 introduces the Lewis State Graph (LSG) framework for the
pure-Python reactor and new mechanistic work. Legacy ITS remains available,
but LSG is the preferred representation when valence-state information must be
explicit. In the current API this representation is requested with
format="tuple".
Important LSG fields:
Field |
Meaning |
|---|---|
|
Authoritative bond components for Lewis-state rewriting. |
|
Integer-like bond order used for product reconstruction; normally
|
|
Valence-state fields used by LSG matching and product accounting. |
|
Element valence-shell reference used when recomputing charge. |
|
Legacy or presentation order. Aromatic |
1from synkit.IO import rsmi_to_its
2
3rsmi = "[CH3:1][Cl:2].[NH3:3]>>[CH3:1][NH3+:3].[Cl-:2]"
4its = rsmi_to_its(rsmi, format="tuple", core=False)
5
6print(its.nodes[2]["lone_pairs"])
7print(its.edges[1, 2]["sigma_order"])
Note
Aromatic LSG matching is intentionally conservative. Aromaticity is still useful for presentation and pruning, but full aromatic-system relabeling is tracked as ongoing work.
Example: Construct and Visualize an ITS#
1from synkit.IO.chem_converter import rsmi_to_graph
2from synkit.Graph.ITS.its_construction import ITSConstruction
3from synkit.Graph.ITS.its_decompose import get_rc
4from synkit.Vis import GraphVisualizer
5import matplotlib.pyplot as plt
6
7rsmi = (
8 '[CH3:1][CH:2]=[O:3].'
9 '[CH:4]([H:7])([H:8])[CH:5]=[O:6]'
10 '>>'
11 '[CH3:1][CH:2]=[CH:4][CH:5]=[O:6].'
12 '[O:3]([H:7])([H:8])'
13)
14
15react_graph, prod_graph = rsmi_to_graph(rsmi)
16
17its_graph = ITSConstruction().ITSGraph(react_graph, prod_graph)
18rc_graph = get_rc(its_graph)
19
20vis = GraphVisualizer()
21fig, axes = plt.subplots(1, 2, figsize=(14, 6))
22vis.plot_its(its_graph, axes[0], use_edge_color=True, title='A. Full ITS Graph')
23vis.plot_its(rc_graph, axes[1], use_edge_color=True, title='B. Reaction Center')
24plt.show()
MTG Submodule#
The synkit.Graph.MTG package provides tools for constructing and analyzing
Mechanistic Transition Graphs (MTGs) from reaction-center ITS graphs:
MCSMatcher— maximum common substructure mappingsMTG— MTG construction from ITS graphs and MCS mapping
The current MTG direction is aligned with LSG/ITS. Invariant atom data such
as element and atom_map should be stored once, while temporal fields
such as charge, hcount, lone_pairs, radical,
sigma_order, and pi_order store compact histories across snapshots.
This avoids redundant *_step_history attributes and makes MTG-to-ITS
round trips easier to inspect.
1from synkit.Graph.MTG.mtg import MTG
2
3mtg = MTG([step_1_its, step_2_its])
4step_its = mtg.get_its_steps()
5composed = mtg.get_compose_its()
When an MTG is built from RSMI strings, SynKit 1.4.0 converts those strings to Lewis State Graph ITS by default:
1mtg = MTG(step_rsmis, mcs_mol=True)
Legacy string conversion is still available for compatibility:
1mtg = MTG(step_rsmis, mcs_mol=True, its_format="typesGH")
Compact MTG data model#
An LSG-backed MTG is a normal networkx.Graph. Node attributes split into
two categories:
Attribute type |
Examples |
Meaning |
|---|---|---|
Invariant atom fields |
|
Stored once because the atom identity does not change across the mechanism. |
State timelines |
|
Tuples with one value per mechanism state. For |
Bond timelines |
|
Tuples with one bond state per mechanism state. |
This compact form intentionally avoids legacy typesGH and redundant
*_step_history attributes in the new Lewis State Graph path.
Example: LSG MTG changed core#
This example reads a stepwise aldol mechanism, constructs an LSG-backed MTG
directly from the RSMI strings, and visualizes the changed core. The default
MTG string conversion uses format="tuple" internally, so the result stores
Lewis-state timelines rather than legacy typesGH fields.
1from synkit.IO import load_database
2from synkit.Graph.MTG.mtg import MTG
3from synkit.Vis import draw_mtg_graph
4
5data = load_database("Data/Testcase/mech.json.gz")[0]
6neutral = data["mechanisms"][1]
7steps = [step["smart_string"] for step in neutral["steps"]]
8
9mtg = MTG(steps, mcs_mol=True)
10graph = mtg.get_mtg()
11
12assert mtg._tuple_its
13assert not any("typesGH" in attrs for _, attrs in graph.nodes(data=True))
14
15fig, ax = draw_mtg_graph(
16 mtg,
17 title=f"{neutral['mech_name']} - changed core",
18 changed_only=True,
19 show_edge_labels=True,
20 compress=True,
21)
compress=True labels only the first and final state of each changed edge.
Use compress=False when debugging the full mechanism-state sequence.
Round-trip helpers#
MTGs can be projected back to their ordered ITS steps or to a composed outer-state ITS:
1step_its = mtg.get_its_steps()
2step_rsmi = mtg.get_rsmi_steps()
3composed = mtg.get_compose_its()
Use get_its_steps() when validating temporal history. Use
get_compose_its() when you need the net start/end reaction encoded as a
single ITS graph.
Functional Groups#
The synkit.Graph.FG package detects functional groups directly on SynKit
molecular networkx graphs. It avoids an external FG representation and
returns labels in graph/node-index space.
Core APIs:
1from synkit.Graph.FG import smiles_to_graph_and_functional_groups
2
3graph, groups = smiles_to_graph_and_functional_groups(
4 "CC(=O)OC1=CC=CC=C1C(=O)O"
5)
6
7print(groups)
Example output
[('ester', (2, 3, 4)), ('carboxylic_acid', (11, 12, 13))]
Detection is hierarchical: specific labels such as carboxylic_acid can
suppress generic nested labels such as carbonyl when the broader label
would be less useful. Public labels cover common carbonyl/acyl, oxygen,
nitrogen/C=N, sulfur, boron, silicon, phosphorus, and heteroaromatic families.
Context graph#
The synkit.Graph.Context submodule expands reaction centers to include local neighborhoods,
enabling context-aware matching and analysis.
1from synkit.IO import rsmi_to_its
2from synkit.Graph.Context.radius_expand import RadiusExpand
3from synkit.Vis.graph_visualizer import GraphVisualizer
4
5smart = (
6 '[CH3:1][O:2][C:3](=[O:4])[CH:5]([CH2:6][CH2:7][CH2:8][CH2:9]'
7 '[NH:10][C:11](=[O:12])[O:13][CH2:14][c:15]1[cH:16][cH:17]'
8 '[cH:18][cH:19][cH:20]1)[NH:21][C:22](=[O:23])[NH:24][c:25]1'
9 '[cH:26][c:27]([O:28][CH3:29])[cH:30][c:31]([C:32]([CH3:33])'
10 '([CH3:34])[CH3:35])[c:36]1[OH:37].[OH:38][H:39]>>'
11 '[C:11](=[O:12])([O:13][CH2:14][c:15]1[cH:16][cH:17][cH:18]'
12 '[cH:19][cH:20]1)[OH:38].[CH3:1][O:2][C:3](=[O:4])[CH:5]'
13 '([CH2:6][CH2:7][CH2:8][CH2:9][NH:10][H:39])[NH:21][C:22]'
14 '(=[O:23])[NH:24][c:25]1[cH:26][c:27]([O:28][CH3:29])[cH:30]'
15 '[c:31]([C:32]([CH3:33])([CH3:34])[CH3:35])[c:36]1[OH:37]'
16)
17
18its = rsmi_to_its(smart)
19rc = rsmi_to_its(smart, core=True)
20
21exp = RadiusExpand()
22k1 = exp.extract_k(its, n_knn=1)
23
24gv = GraphVisualizer()
25gv.visualize_its_grid([rc, k1])
See Also#
synkit.IO— format conversion utilitiessynkit.Synthesis— reaction prediction and network exploration