Get substructure matches#

Matching chemical SMARTS while preserving the source atom-index space.

Query complete declared chemistry before restricting results to an atom selection. RDKit is optional and must be installed for this tool. Matches preserve query-atom order; this order can carry geometric meaning in downstream calculations.

Added in version 1.0.0.

Matching multiple chemical patterns#

This alcohol/guanidinium system illustrates oxygen, a double bond and an absent fluorine. Each matrix has one column per query atom, even with zero matches. Matches are unique by atom membership within each query; automorphisms are not expanded. Repeated queries remain separate entries in their supplied order.

import molsysmt as msm
from rdkit import Chem
molsys = msm.convert(Chem.MolFromSmiles('CCO.NC(=[NH2+])N'), to_form='molsysmt.MolSys')
features = msm.topology.get_substructure_matches(molsys, ['[O]', '[C]=[N+]', '[F]'])
print([matrix.tolist() for matrix in features['matches']])
print([matrix.shape for matrix in features['matches']])
print(features['software'])
[[[2]], [[4, 5]], []]
[(1, 1), (1, 2), (0, 1)]
{'molsysmt': '0.21.0+606.ga03eb4bf6', 'rdkit': '2025.09.5'}

Filtering complete matches#

Selections keep only matches whose entire atom membership belongs to the selected set. They do not remove graph atoms before matching. A selected atom’s neighboring chemistry therefore keeps its source meaning. Use an index list or a supported selection string, with the same syntax argument as other MolSysMT tools.

selected = msm.topology.get_substructure_matches(molsys, '[C][C]', selection=[0, 1])
print(selected['matches'][0].tolist())
print(selected['source_atom_indices'].tolist())
[[0, 1]]
[0, 1, 2, 3, 4, 5, 6]

Preserving hydrogen annotations and resonance#

A bracket-declared hydrogen is an atom-level count, not another indexed atom. ChemicalStates keeps it in n_explicit_hydrogens, separately from implicit hydrogens and real H atoms. This matters for valence-sensitive patterns, such as guanidinium resonance. The general RDKit conversion and H5MSM paths preserve the count.

print(msm.get(molsys, element='atom', n_explicit_hydrogens=True))
patterns = ['[+{1-}]', '[NX3]-[C]=[NX3+]']
print([matrix.tolist() for matrix in msm.topology.get_substructure_matches(molsys, patterns)['matches']])
[0, 0, 0, 0, 0, 2, 0]
[[[5]], [[3, 4, 5], [6, 4, 5]]]

Bounding matching without silent truncation#

max_matches is applied per query to the full source before selection. If the query exceeds it, the tool raises; it never silently returns RDKit’s default 1,000 matches. The numerical working budget can impose a lower bound. Use specific patterns and an explicit limit suitable for your system rather than assuming arbitrary SMARTS are cheap.

try:
    msm.topology.get_substructure_matches('smiles:CCCC', '[C]', selection=[0], max_matches=2)
except Exception as error:
    assert type(error).__name__ == "UnsupportedHeavyOperationError"
    print('The full query exceeds the declared limit; no truncated output was returned.')
The full query exceeds the declared limit; no truncated output was returned.

Selecting chemistry and another form#

chemical_state selects the declared state. If you use chemical_state='structure', all requested structures must share one assigned state. Index selection needs no coordinates. Rich spatial selection needs the corresponding structural data.

RDKit inputs retain their atom-level hydrogen declarations. Native matching converts chemistry without a coordinate series and checks that RDKit preserves declared charges and aromaticity. Missing elements/charge/aromatic flags are explicit errors. Ring SMARTS use RDKit’s ring interpretation; they are not a minimum cycle basis.

rdkit_system = msm.convert(molsys, to_form='rdkit.Mol')
other = msm.topology.get_substructure_matches(rdkit_system, '[O]', chemical_state='reference')
print(other['matches'][0].tolist(), other['chemical_state_index'])
[[2]] 0