SMSD Pro Python guide¶
Reference for the smsd Python package, version 8.1.0. The
Python examples cover fingerprints, drawing, layout, stereo,
files, R-groups and scaffolds.
Installation¶
The CPU/OpenMP wheels cover CPython 3.12–3.15; the supported-platforms
table lists the operating systems and
architectures. smsd.__version__ gives the installed version. Core matching
needs no other toolkit. RDKit is needed only for RDKit molecules, to_rdkit,
to_svg, depict_mcs and depict_substructure (python -m pip install
"rdkit<2027"); Open Babel only for from_openbabel and to_openbabel.
Deprecated compatibility aliases remain available in 8.1. Prefer their current replacement names in new code.
Build from source¶
With Python 3.12 or later, a C++17 compiler and CMake 3.18 or later, from the repository root:
This builds the CPU/OpenMP package. Metal and CUDA are left out unless
-Ccmake.define.SMSD_BUILD_METAL=ON or -Ccmake.define.SMSD_BUILD_CUDA=ON is
added (AUTO includes each one when it is available); Python screening and
search do not run on them, so they change only what gpu_is_available() and
gpu_device_info() report. Without OpenMP, batch calls run sequentially.
Optional CUDA screening¶
smsd-cuda provides optional NVIDIA CUDA screening on Linux x86_64 and needs
the same version of smsd:
The GPU computes integer overlaps; scores use the CPU expressions and hits keep target order. Matching runs on the CPU. The backend keeps prepared corpus fingerprints on the device for reuse. ECFP4 Tanimoto screening and the atom-count MCS similarity bound are distinct operations.
Automatic ECFP4 screening of a prepared TargetCorpus uses CUDA on a Tesla T4
with compute capability 7.5 when the corpus has at least 100,000 targets.
Smaller corpora and other devices use the CPU. Setting SMSD_CUDA_MIN_TARGETS
to a positive integer N makes this automatic screening use any NVIDIA GPU with
compute capability 7.5 or later for corpora of at least N targets; on a Tesla
T4, N replaces 100,000. Other values are ignored, and SMSD_DISABLE_GPU still
overrides the setting. These rules do not cover the packed FingerprintCorpus
backend "auto", described under
CPU and CUDA backends. A device
below compute capability 7.5 is reported as unavailable, with the reason given
by smsd_cuda.unavailable_reason(); automatic screening and the packed backend
"auto" then use the CPU, and an explicit packed backend="cuda" call raises
an error that gives the reason. Automatic similarity-bound screening
(screen_targets) and substructure pre-screening use the CPU; the explicit
match_corpus(..., gpu_prefilter=True) methods can ask an optional GPU
provider to run their pre-screen. gpu_is_available() and gpu_device_info()
report backend availability, not whether a call used the device. Missing
devices, allocation failure and device loss use the CPU.
Setting SMSD_DISABLE_GPU to anything but an empty string or 0 disables the
GPU at run time; an explicit packed backend="cuda" call then raises
RuntimeError. macOS uses the CPU; the
supported-platforms table gives the
CUDA package requirements.
Additions in 8.1.0¶
- Python adapters and MCP: NumPy/PyTorch arrays, scikit-learn transformers, bounded search tools and MCS SVG highlighting. Known bundled OpenMP conflicts are rejected before the PyTorch adapter runs; use a shared runtime or an OpenMP-free SMSD build.
- Packed corpora: streamed construction, memory-mapped binary fingerprints, chunked threshold/top-k scans, Butina and MaxMin. All-against-all comparisons remain quadratic, without a dense result matrix.
- SMARTS export:
Match.to_smarts(query, chemistry)andMCS.match_with_smarts(target)retain selected common bonds. The latter uses the prepared chemistry and returns aSmartsResultwithresultandsmarts. Stereo, tautomer-aware, complete-ring and strict ring-fusion export are rejected, together with unsupported conditional resonance cases. The pattern does not encode induced MCIS or establish that a search completed. stream_screen_smilesconsumes target chunks through the public native corpus screen.warm_start(corpus, query, threshold=...)prepares and screens a retained corpus. Both keep ordinary device crossovers and CPU fallback.
The macOS wheels need macOS 14 or later on Apple silicon and include their OpenMP runtime.
Searches¶
A molecule is a SMILES string, MolGraph or RDKit molecule; a graph is a
MolGraph (parse SMILES with parse_smiles). Prepare a query once:
Substructure.find(query): a molecule to embed whole in each target.Substructure.smarts(query): a SMARTS pattern; an empty or non-string pattern raisesValueError.MCS.find(query): a molecule for a maximum common substructure (MCS) with each target.
A prepared query provides:
| Method | Returns |
|---|---|
match(target) |
A SearchResult with at most one match |
match_all(target, limit=1000) |
Up to limit matches: all optimal MCS mappings or all Substructure embeddings, symmetry-related ones included |
match_many(targets, *, num_threads=0) |
One SearchResult per target, in order, each with its own budget; 0 uses the OpenMP default (OMP_NUM_THREADS) |
with_options(options), with_timeout(milliseconds) |
A new query with a copy of the options |
MCS.with_objective(objective), MCS.with_fragments(*, min_atoms=1, max_fragments=2147483647), MCS.with_atom_weights(weights) |
A new MCS query with that setting |
Substructure.with_induced(enabled=True) |
A new Substructure query with induced matching |
limit must be a positive integer and num_threads a non-negative integer,
or ValueError is raised. Searches release the global interpreter lock, so a
prepared query can serve several threads; do not modify a target during a call.
import smsd
query = smsd.Substructure.find("CO")
assert query.match("CCO").found
assert not query.match("CCN").found
embeddings = query.match_all("OCO", limit=10)
assert len(embeddings.matches) == 2 and embeddings.complete
result = smsd.MCS.find("c1ccc(O)cc1").with_timeout(1000).match("c1ccc(N)cc1")
assert result.best.atom_count == 6 and result.optimal
print(result.best.atom_mapping)
Results¶
SearchResult field |
Meaning |
|---|---|
matches, best, found |
Tuple of Match objects; the first or None; whether any exists |
status |
SearchStatus.COMPLETED, TIMED_OUT, CANCELLED or LIMIT_REACHED |
optimal |
The MCS optimum, or the substructure answer, is proved |
complete |
The search or enumeration finished |
score |
Objective value, in weight units with atom weights |
nodes_visited, elapsed_ms |
Search effort and elapsed milliseconds |
COMPLETED means the search ran to its end; TIMED_OUT and CANCELLED mean
the time limit or the cancellation token stopped it. LIMIT_REACHED means the
node limit stopped it, an enumeration reached its match limit, or an MCES pair
beyond the size limits was declined before any search; a
declined pair returns no match and proves nothing.
With complete_rings_only or RingFusionMode.STRICT, truncated ring perception
in either input leaves the mappings selected using the perceived rings, but
cannot prove the result. The status is LIMIT_REACHED, with optimal and
complete both False; TIMED_OUT and CANCELLED take precedence.
A stopped search can return a valid but unproved match, and an empty
incomplete result does not prove absence, so check status, optimal and
complete. An enumeration limit can leave optimal=True with
complete=False. Tied matches are not ranked chemically. A stopped MCIS search
can return a result or atom mapping that differs from 8.0.3 or Java; see the
upgrade notes.
import smsd
result = smsd.Substructure.find("CC").match_all("CCC", limit=1)
assert len(result.matches) == 1 and result.optimal and not result.complete
assert result.status == smsd.SearchStatus.LIMIT_REACHED
Each Match has:
atom_mapping: a read-only, one-to-one map from query to target atom index;bond_mapping:(query_first, query_second, target_first, target_second)for each selected bond;fragments: the components of the selected bonds, as query atom indices;atom_countandbond_countof the selected graph;to_smiles(query)andto_graph(query): the selection as SMILES with cut valences capped, or as aMolGraphwith the original chemistry and hydrogen counts.
Size limits¶
An MCES search declines a pair before searching it, with LIMIT_REACHED, no
match, and optimal and complete both False, when:
- query atoms × target atoms exceed 2³⁰ (1,073,741,824), as for two molecules of more than 32,768 atoms each;
- the two molecules are too large in bonds, as for two molecules of more than 16,384 bonds each;
- rarely, a molecule has an atom with 512 or more bonds and the two molecules share many kinds of bond.
MCIS searches are never declined; the time and node limits bound them. C++ and Java use the same limits.
MCIS, MCES and fragments¶
import smsd
query, target = "C1CC1", "CC(C)C" # cyclopropane, isobutane
induced = smsd.MCS.find(query).match(target)
edges = smsd.MCS.find(query).with_objective(smsd.MCS.Objective.MCES).match(target)
assert (induced.best.atom_count, induced.best.bond_count) == (2, 1)
assert (edges.best.atom_count, edges.best.bond_count) == (3, 2)
assert len(smsd.parse_smiles(edges.best.to_smiles(query)).bonds()) == 2
fragments = smsd.MCS.find("CCOCC").with_fragments().match("CCNCC")
assert fragments.best.atom_count == 4 and len(fragments.best.fragments) == 2
all_matches = smsd.MCS.find(query).match_all(target, limit=100)
assert all_matches.optimal and all_matches.complete
MCSdefaults to a connected maximum common induced subgraph (MCIS): most atoms, then most bonds, keeping bonds and non-bonds between mapped atoms.MCS.Objective.MCES, the maximum common edge subgraph, maximises common bonds, then their atoms; it may omit input bonds and adds no isolated atoms.with_fragments()allows several components of at leastmin_atomsatoms each, so MCIS may include isolated atoms whenmin_atoms=1. Limits below one raiseValueError.Substructureis non-induced unlesswith_induced()orSubstructure.Options.inducedis set.
Chemistry options¶
import smsd
options = smsd.MCS.Options()
options.chemistry.match_formal_charge = True
options.chemistry.use_chirality = True
options.chemistry.use_bond_stereo = True
options.chemistry.complete_rings_only = True
options.timeout_ms = 1000
search = smsd.MCS.find("c1ccccc1").with_options(options)
assert search.match("Cc1ccccc1").best.atom_count == 6
sub_options = smsd.Substructure.Options()
sub_options.induced = True
assert not smsd.Substructure.find("CCC").with_options(sub_options).match("C1CC1").found
ChemOptions, as options.chemistry, decides which atoms and bonds match:
| Attribute | Default | Effect |
|---|---|---|
match_atom_type |
True |
Same element |
match_bond_order |
BondOrderMode.STRICT |
Equal orders, subject to aromaticity_mode; LOOSE and ANY relax this |
aromaticity_mode |
AromaticityMode.FLEXIBLE |
Aromatic bonds also match single or double bonds; STRICT needs agreement |
aromaticity_model |
AromaticityModel.DAYLIGHT_LIKE |
Deprecated; no search reads it |
match_formal_charge |
False |
Same formal charge |
match_isotope |
False |
Same mass number where both atoms give one |
use_chirality, use_bond_stereo |
False |
Mapped tetrahedral centres or double bonds agree in configuration, by the parity of their mapped neighbours, not CIP labels |
ring_matches_ring_only |
False |
Ring atoms and bonds match only ring atoms and bonds |
complete_rings_only |
False |
Retained ring atoms and bonds lie in fully selected relevant cycles of each input |
ring_fusion_mode |
RingFusionMode.IGNORE |
STRICT: matched ring atoms have the same per-atom ring count; PERMISSIVE adds no constraint, like IGNORE |
tautomer_aware |
False |
Allow recognised tautomeric relaxations; match_bond_order is unchanged |
pH, solvent |
7.4, Solvent.AQUEOUS |
Context for MCS.compute_tautomer_confidence only |
Stereo is checked only where the mapping determines it. In Substructure, a
tagged query centre or double bond needs the same configuration in the target,
and an untagged one matches any; in MCS, an unspecified target atom or bond
matches either configuration. The comparison uses the parity of the mapped
neighbours, not CIP labels, whose priorities depend on atoms outside the
mapping. In MCS it is a hard constraint: an MCIS is the largest common
subgraph whose mapped stereo agrees, so it can be smaller than the MCIS found
without these options, and an MCES leaves out the fewest common bonds that
resolve each disagreement.
Tautomer matching usually also needs a relaxed bond order:
import smsd
options = smsd.MCS.Options()
options.chemistry.tautomer_aware = True
options.chemistry.match_bond_order = smsd.BondOrderMode.ANY
options.timeout_ms = 1000
result = smsd.MCS.find("CC(=O)C").with_options(options).match("CC(O)=C")
assert result.best.atom_count == 4
options.chemistry.match_bond_order = smsd.BondOrderMode.STRICT
strict = smsd.MCS.find("CC(=O)C").with_options(options).match("CC(O)=C")
assert strict.best.atom_count == 2
ChemOptions.profile(name="default") returns a preset; names are not
case-sensitive, and an unknown name raises ValueError:
| Preset | Differs from the defaults |
|---|---|
default |
No differences |
strict |
aromaticity_mode=STRICT |
pharma |
match_formal_charge, ring_matches_ring_only, complete_rings_only |
reaction, compat-fmcs, compat-substruct |
match_bond_order=LOOSE |
ChemOptions.tautomer_profile() |
tautomer_aware=True, match_bond_order=LOOSE |
ChemOptions(options), copy.copy and copy.deepcopy copy options. The
setters with_bond_order_mode, with_aromaticity_mode,
with_complete_rings_only, with_match_isotope, with_ring_fusion_mode,
with_tautomer_aware, with_solvent and with_ph set one field and return the
same object; with_ph clamps a finite pH to [0, 14].
Timeouts, node limits and cancellation¶
timeout_ms, default-1: a negative value selects 10 s forSubstructureand 500 + 2 × (query atoms × target atoms) ms, at most 30 s, forMCS: 1.3 s for two 20-atom molecules, 5.5 s for two 50-atom molecules and 30 s from 122 atoms each;0stops at once; a positive value applies to each query–target pair.node_limit, default0(none): reaching it reportsLIMIT_REACHED.cancellation, defaultNone: aCancellationToken;cancel()from any thread stops the search withCANCELLED. Tokens stay cancelled, so use a new one per search.
Parsing and preparation can take the total time past the deadline. Converting
matches to Python objects counts towards it in match and match_all, which
then return the matches converted so far with TIMED_OUT or CANCELLED and
complete=False; match_many stops conversion only on cancellation.
Atom weights¶
with_atom_weights(weights), for MCIS only, ranks results by the sum of query
atom weights before atom and bond counts; negative weights can favour a smaller
result. Give one finite weight per query atom, in input order. Weights are
compared to 0.001; result.score is in the supplied units. Non-finite weights,
totals beyond about ±2.1 million, a list missing a query atom, and use with
MCES raise ValueError.
Tautomer confidence¶
MCS.compute_tautomer_confidence(query, target, match, chemistry) scores the
tautomeric relaxations a Match uses, in the pH and solvent of
chemistry; identical selected chemistry scores 1. The score is a heuristic,
not a probability, and changes neither molecules nor search. A plain atom
mapping raises TypeError.
import smsd
query, target = smsd.parse_smiles("ONC(C)=O"), smsd.parse_smiles("ON=C(C)O")
options = smsd.MCS.Options()
options.chemistry = smsd.ChemOptions.tautomer_profile()
match = smsd.MCS.find(query).with_options(options).match(target).best
for pH in (2.0, 7.4, 9.0, 12.0):
chemistry = smsd.ChemOptions.tautomer_profile().with_ph(pH)
print(pH, round(smsd.MCS.compute_tautomer_confidence(query, target, match, chemistry), 3))
MCS.validate_tautomer_consistency(query, target, match) reports whether a
Match or atom mapping conserves the mobile N–H, O–H and S–H protons of each
mapped tautomeric group.
SMARTS and target collections¶
import smsd
query = smsd.Substructure.smarts("[#6]-[#8]")
assert [result.found for result in query.match_many(["CCO", "CCN"])] == [True, False]
corpus = smsd.TargetCorpus.from_smiles(["CCO", "CCN"])
corpus.prewarm(num_threads=1)
assert [result.found for result in query.match_many(corpus, num_threads=2)] == [True, False]
SMARTS queries follow the result, timeout and enumeration rules of molecule
queries. TargetCorpus holds targets for repeated queries:
TargetCorpus(),TargetCorpus.from_smiles(targets),add_target(target),add_targets(targets): capture molecules and their atom numbering, unaffected by later changes to the inputs;len(corpus)counts them.prewarm(*, num_threads=0)computes invariants and ECFP4 fingerprints;is_prewarmedsays whether they cover every target.screen(query, threshold, *, num_threads=0): indices of targets with ECFP4 Tanimoto of at leastthreshold, empty untilprewarm()follows the last addition. ECFP similarity is not a substructure test.screen_many(queries, threshold, *, num_threads=0): the same ECFP4 screen for each query, returning a list of hit-index lists in query order. Each hit list keeps target order and needs the sameprewarm().- Errors in
prewarm()and in CPU screening reach the caller; when several targets fail, the error of the first in target order is raised, whatever the thread count. screen_targets_many(queries, threshold, chemistry=None, *, num_threads=0): the atom-count MCS similarity-bound screen for each query, with one chemistry setting for the batch. Noprewarm()is needed. Each hit list keeps target order, asscreen_targets(query, corpus, threshold, chemistry)does.
import smsd
corpus = smsd.TargetCorpus.from_smiles(["CCO", "CCN", "CCC"])
corpus.prewarm(num_threads=1)
assert corpus.screen_many(["CCO", "CCN"], 1.0, num_threads=1) == [[0], [1]]
assert corpus.screen_targets_many(["CCO", "CCN"], 1.0, num_threads=1) == [[0], [1]]
Explicit corpus prefilters¶
The additive Substructure.match_corpus(corpus, *, gpu_prefilter=False,
num_threads=0) and MCS.match_corpus(corpus, minimum_atoms, *,
gpu_prefilter=False, num_threads=0) return CorpusSearchResult objects in
original target order, preserving each target's input atom numbers.
import smsd
corpus = smsd.TargetCorpus.from_smiles(["CCO", "N"])
embeddings = smsd.Substructure.find("CO").match_corpus(corpus, num_threads=1)
assert embeddings[0].threshold_reached and embeddings[0].result.found
decisions = smsd.MCS.find("CC").match_corpus(corpus, 2, num_threads=1)
assert decisions[1].atom_upper_bound == 0
assert decisions[1].proven_below_threshold and decisions[1].result is None
result is an actual CPU search result, or None only for a proved filter
rejection. threshold_reached requires a real witness. Both decision flags
being false means unknown, including stopped searches without a sufficient
witness. For eligible unweighted MCIS, atom_upper_bound is an upper bound on
the MCS atom count; a target whose bound equals minimum_atoms is searched.
Substructure decisions report no bound, so the field is None.
The default runs the same pre-screen on the CPU. gpu_prefilter=True
explicitly asks the optional GPU provider to run it; unavailable or failed GPU
work falls back to the CPU. Availability does not prove device use. No prewarm
is required. With SMARTS, unsupported queries, node caps, immediate timeouts,
cancellation tokens, MCS weights, MCES or excluded atoms, every target is
searched on the CPU. MCES or weighted scores cannot prove an atom-count
negative. match_many remains unchanged. Each target's search budget excludes
the pre-screen; there is no whole-corpus timeout. Java retains its ordinary CPU
batch API.
Corpus substructure searches can rule out targets that cannot contain the query before CPU matching. With SMARTS queries, untyped queries or a finite node limit, every target is searched. Target results keep their original slots, atom numbering and search status.
Constrained and consensus searches¶
import smsd
results = smsd.MCS.match_constrained(["CO", "CN"], "CO.CN")
assert [result.best.atom_count for result in results] == [2, 2]
assert set(results[0].best.atom_mapping.values()).isdisjoint(
results[1].best.atom_mapping.values()
)
consensus = smsd.MCS.consensus(["CCO", "CCN", "O"], threshold=2 / 3)
assert consensus.found and consensus.support_count == 2
assert consensus.graph.n == 2 and not consensus.optimal
assert consensus.to_smiles() == "CC"
MCS.match_constrained(queries, target, options=None): oneSearchResultper query, in order, each excluding target atoms claimed earlier; the joint mapping is not proved optimal.MCS.Options.excluded_target_atomsreserves further target atoms without changing the target.MCS.consensus(molecules, options=None, threshold=1.0): a common graph in at leastceil(threshold * len(molecules))inputs,thresholdin (0, 1], under one budget; alwaysoptimal=False, including empty and single-input calls.completereports whether the progressive pass finished. Neither it norCOMPLETEDproves a global optimum or absence of a qualifying common graph. Atom weights and exclusions raiseValueError. With complete rings or strict ring fusion, truncated ring perception in any input makes itLIMIT_REACHEDand incomplete, unless it is alreadyTIMED_OUTorCANCELLED; its fragment and supporting mappings are retained.ConsensusResulthasgraph,to_smiles(),support_count,mappings(supporting input index to aMatchfromgraph),fragmentandsource_index(the graph in its source),found,status,optimal,complete,nodes_visitedandelapsed_ms.
Validation and symmetry¶
import smsd
query, target = "c1ccc(O)cc1", "c1ccc(N)cc1"
result = smsd.MCS.find(query).match(target)
assert result.best.atom_count == 6
assert result.best.bond_count == 6
assert smsd.MCS.validate_mapping(query, target, result.best) == []
fragment = result.best.to_smiles(query)
assert smsd.parse_smiles(fragment).n == 6
MCS.validate_mapping(query, target, match, options=None): a list of errors, empty when valid. A plain atom mapping selects every compatible mapped bond, so pass aMatchfor MCES.MCS.is_mapping_maximal(query, target, match, options=None): whether no compatible extension exists, which is weaker than optimality. An invalid mapping givesFalse; a valid mapping raisesRuntimeErrorif the proof cannot finish or required ring perception was truncated.MCS.canonicalize_mapping(query, target, atom_mapping): the lexicographically smallest equivalent mapping under both molecules' symmetries; raises if it cannot finish.
R-group decomposition¶
Substructure.decompose_r_groups(core, targets, options=None) returns, per
target, a dictionary with core (a MolGraph), rgroups (labels such as
"R1" mapped to graphs) and search (the core's SearchResult, to check
first); see the R-group example.
Input atom numbering¶
SMILES use parser order, MolGraph objects graph order and RDKit molecules
their own order, separately on each side; search results and atom weights use
these indices. Other functions number an RDKit molecule as from_rdkit does;
get_index_map(from_rdkit(mol)) gives the RDKit index of each atom. Explicit
hydrogen atoms are vertices; bracket hydrogen counts are not.
from rdkit import Chem
import smsd
query = Chem.RenumberAtoms(Chem.MolFromSmiles("NCCO"), [3, 1, 0, 2])
target = Chem.MolFromSmiles("CC(O)CN")
result = smsd.MCS.find(query).match(target)
assert result.best.atom_count == 4
for first, second in result.best.atom_mapping.items():
assert query.GetAtomWithIdx(first).GetAtomicNum() == target.GetAtomWithIdx(second).GetAtomicNum()
Conversion passes through SMILES, so coordinates, data fields and query predicates are lost. For C++, see the optional native adapter.
from_rdkit(mol, use_cache=True): a graph with the atom correspondence, reused while the molecule is unchanged; empty or hydrogen-only molecules raiseValueError.to_rdkit(mol_or_smiles): an RDKit molecule in canonical SMILES order.get_index_map(g): a copy ofresult[smsd_index] = rdkit_indexfor afrom_rdkitgraph, elseNone.translate_mapping(mapping, g_query=None, g_target=None): a mapping betweenfrom_rdkitgraphs in RDKit indices; an out-of-range index raisesIndexError.clear_cache(): clears reusable conversions; live graphs keep their correspondence.from_openbabel(mol),to_openbabel(mol): Pybel molecule orOBMolto graph, and back to Pybel, without the Open Babel numbering.
API reference¶
Every name in smsd.__all__ appears in this guide.
Enumerations and search options¶
| Name | Members |
|---|---|
MCS.Objective, MCS.Connectivity |
MCIS (default), MCES; CONNECTED (default), FRAGMENTS |
SearchStatus |
COMPLETED, TIMED_OUT, CANCELLED, LIMIT_REACHED |
BondOrderMode, AromaticityMode, AromaticityModel |
STRICT, LOOSE, ANY; STRICT, FLEXIBLE; DAYLIGHT_LIKE |
RingFusionMode |
IGNORE, PERMISSIVE, STRICT |
Solvent |
AQUEOUS, DMSO, METHANOL, CHLOROFORM, ACETONITRILE, DIETHYL_ETHER |
CancellationToken |
cancel(), is_cancelled |
MCS.Options and Substructure.Options (copied by MCS.Options(other) and
Substructure.Options(other)):
| Field | Default | Applies to |
|---|---|---|
chemistry, timeout_ms, node_limit, cancellation |
ChemOptions(), -1, 0, None |
Both |
induced |
False |
Substructure |
objective, connectivity |
MCS.Objective.MCIS, MCS.Connectivity.CONNECTED |
MCS |
min_fragment_atoms, max_fragments |
1, 2147483647 |
MCS |
atom_weights, excluded_target_atoms |
[], [] |
MCS; weights with MCIS only |
Molecules and SMILES¶
parse_smiles(smiles, *, lenient=False): a graph;""gives an empty one. Malformed text raisesValueErrorunlesslenient=Truerecovers it; bracket atoms beyond the OpenSMILES limits (one-digit hydrogen count, charge within ±15, three-digit isotope) always raise it.parse_mapped_smiles(smiles): parse without atom maps;strip_atom_maps(smiles)removes them from the text, keeping ring closures and aromatic bonds.to_smiles(mol): canonical SMILES, whose order may differ between languages;canonical_smilesis a deprecated alias./and\go on single bonds wherever a configured double bond has one at an end; an end with only aromatic ring bonds carries them on an aromatic bond, as RDKit writes it, which strict OpenSMILES readers take as a single bond. For a molecule of more than 5,000 atoms, or when canonical labelling reaches its search limit, the output can depend on the input atom order; the graph'sautomorphism_generators_truncated()is thenTrue. A graph that needs more than 99 ring closures open at once is rejected.to_smarts(mol, include_aromaticity=True, include_charge=True, include_ring_member=False, include_h_count=False, include_isotope=False): SMARTS for a molecule with the selected predicates; a graph that needs more than 99 ring closures open at once is rejected.canonical_hash(mol): a hash of connectivity, bond orders, aromaticity, rings, charges, mass numbers and stereo; a screening key, not proof of identity.same_canonical_graph(mol1, mol2)compares atom count, canonical hash and canonical SMILES.prewarm(mol): caches invariants and returns the graph;prewarm_graphis a deprecated alias returningNone.extract_subgraph(mol, atom_indices): the induced subgraph, numbered in the supplied order, keeping a stereocentre whose neighbours all stay and the E/Z configuration of a double bond whose ends keep the neighbours that define it.murcko_scaffold(mol): the Bemis–Murcko scaffold (ring systems, linkers and atoms double- or triple-bonded to them), other substituents replaced by hydrogen and stereo kept where defined; acyclic graphs are returned unchanged.count_components(mol),split_components(mol): the number of connected components, or one graph for each.translate_to_atom_ids(mapping, g1, g2): a mapping in the IDs set byMolGraphBuilder.atom_ids; unchanged without IDs.
MolGraph members:
n,len(graph): atom count.atomic_num,formal_charge,mass_number,hydrogen_count,atom_class,aromatic,ring,degree,neighbors,label: per-atom values, adjacency lists and matching labels.bonds()as(i, j, order)tuples;bond_order(i, j),has_bond(i, j),bond_in_ring(i, j),bond_aromatic(i, j), raisingIndexErrorout of range.name,program_line,comment,properties: writable MOL header lines and SDF data fields.num_rings(),ensure_ring_counts(),ring_system_of(atom),ring_system_count(),ring_system_sig(rs_id): rings and ring systems.get_canonical_hash(),ensure_canonical(),morgan_rank,canonical_label,orbit: canonical ranks, labels and symmetry classes, computed on first access.get_automorphism_generators(),automorphism_generators_truncated(): automorphism generators, and whether they were capped.tautomer_class: tautomer class per atom from 0, or −1; independent of atom order except between symmetric classes.prewarm()caches invariants;perceive_aromaticity(model=AromaticityModel.DAYLIGHT_LIKE),kekulize()anddearomatize()change the graph in place.
MolGraphBuilder builds a MolGraph through chainable setters:
atom_count(n), atomic_numbers(nums), formal_charges(charges),
mass_numbers(masses), hydrogen_counts(counts), ring_flags(flags),
aromatic_flags(flags), neighbors(adj), bond_orders(orders) (parallel to
neighbors), bond_ring_flags(flags), bond_aromatic_flags(flags),
atom_ids(ids), name(value), program_line(value), comment(value),
properties(values), then build(perceive_aromaticity=True,
aromaticity_model=AromaticityModel.DAYLIGHT_LIKE).
Stereo, rings and atom chemistry¶
assign_rs(mol),assign_ez(mol):{atom: "R" or "S"}and{(atom, atom): "E" or "Z"}where priorities resolve.assign_cip(mol)returnsCIPDescriptorswithrs_labels, oneRSLabelper atom (NONE,R,S, pseudoasymmetricr,s), andez_bonds,(atom, atom, EZLabel)tuples (NONE,E,Z).compute_sssr(mol): the smallest set of smallest rings, as atom lists by ascending size.rings_truncated(mol): whether the ring search behindcompute_sssrstopped before finding a complete set of rings, as for a very large ring system; its 8.0.x meaning is retained.relevant_cycles_truncated(mol): whether full relevant-cycle enumeration was incomplete. It can be true with a complete minimum basis. Complete-ring matching checks this perception; strict ring fusion checks the perception that produced its per-atom ring counts.compute_relevant_cycle_families(mol): the relevant cycles grouped into families, asRelevantCycleFamilieswithfamilies,relevant_cycle_countandtruncated. EachRelevantCycleFamilygives the ringsize, theroot(the highest-numbered atom of every member),first,secondandopposite, which locate the bond or atom opposite the root, one member asprototypeand the exactmember_count, so the relevant cycles can be counted without listing them; counts are Python integers of any size.truncatedreports an incomplete enumeration, whose families are relevant but may not be all of them. When the enumeration is complete, the relevant cycles and their number do not depend on the input atom order; the grouping into families can.relevant_cycle_family_of(mol, families, ring): the index infamilies.familiesof the family that hasring, a list of atom indices in ring order, as a member, or -1.relevant_cycle_family_members(mol, family, limit): up tolimitmembers offamilyas atom-index lists, starting with its prototype.perceive_aromaticity(mol, *, model=AromaticityModel.DAYLIGHT_LIKE): recompute ring membership and aromaticity.kekulize(mol): replace aromatic bonds with Kekulé bonds, or raiseValueError;dearomatize(mol)is a deprecated alias issuing aDeprecationWarning.classify_pharmacophore(mol, atom_index): bit mask of a graph atom: 0 donor, 1 acceptor, 2 positive and 3 negative ionisable, 4 aromatic, 5 hydrophobic.implicit_h(atomic_num, bond_order_sum, formal_charge, aromatic=False): implicit hydrogens by SMILES valence; 0 when none fits.
Positive numeric SMARTS R<n> (n > 0) uses one deterministic minimum cycle
basis, the same as C++ and Java. If ring perception cannot complete it, a
search that cannot be decided without it returns LIMIT_REACHED without
matches, including for negated, combined and recursive predicates; found is
then false, which does not prove absence. Literal R and R0 remain
independent of that basis. Other functions that need the basis raise
smsd.RingBasisUnavailable, a RuntimeError, naming the cause;
compute_sssr may return incomplete rings, flagged by rings_truncated.
relevant_cycles_truncated alone does not make the basis incomplete. Ring
perception works within one fixed, deterministic work limit; see
Results and budgets.
The other functions here accept a molecule; perceive_aromaticity,
kekulize and dearomatize change a MolGraph in place and return it. CIP
labels follow IUPAC 2013: at phosphorus, oxo outranks hydroxy and alkoxy
oxygen, so phosphinates and phosphonates can differ from toolkits that rank it
lower.
Fingerprints¶
Binary fingerprints are sorted set positions; count fingerprints are
(position, count) pairs.
fingerprint(mol, *, kind="mcs", path_length=7, fp_size=2048, radius=2, options=None):kindis"mcs","path","ecfp"or"circular"(ECFP4),"fcfp"(FCFP4) or"torsion";optionsoverrides it.fingerprint_from_smiles(smiles, radius=2, fp_size=2048, mode="ecfp", *, path_length=7, options=None):modetakes thekindvalues above;optionsoverrides it.fingerprints(mols, *, kind="mcs", path_length=7, fp_size=2048, radius=2, options=None, num_threads=0): the fingerprint of every molecule, each asfingerprintgives it, in input order. SMILES strings are parsed and the fingerprints computed natively onnum_threadsthreads (0 uses the OpenMP default); the first molecule that fails raises the errorfingerprintgives for it. Ansmsd.RingBasisUnavailableerror, or the error of a key left undecided by its time or embedding limit, keeps its type and adds the zero-based input index to its message.circular_fingerprint(mol, radius=2, fp_size=2048, mode="ecfp"),circular_fingerprint_counts(mol, radius=2, fp_size=2048, mode="ecfp"): ECFP or FCFP of a graph; radius 2 is ECFP4.path_fingerprint(mol, path_length=7, fp_size=2048): heavy-atom paths of up topath_lengthbonds in a graph.mcs_fingerprint(mol, path_length=7, fp_size=2048, tautomer_aware=False): an MCS-aware path fingerprint of a graph.topological_torsion(mol, fp_size=2048),topological_torsion_counts(mol, fp_size=2048): four-atom torsions (Nilakantan et al., 1987) of a molecule, or counts for a graph.fingerprint_subset(fp1, fp2): whether every position offp1is infp2; both sorted.analyze_fp_quality(fp, fp_size=2048):set_bits(distinct positions),fp_size,density(set bits overfp_size),expected_collision_rateandis_saturated(density over 0.75); a position beyondfp_sizeraisesValueError. With s of N bits set, the fingerprint holds about n = ln(1 − s/N) / ln(1 − 1/N) uniformly hashed features, and the collision rate is the share 1 − s/n of them that fell on a bit already set: 0 for an empty fingerprint and 1 for a full one. Path fingerprints of a power-of-two size collide more often than uniform hashing predicts, so the rate understates their collisions. JavaanalyzeFpQualityuses the same definitions.
Configurable fingerprints¶
FingerprintOptions(family="ecfp", **switches) gives the same bits in Python,
C++ and Java. Each family starts from the classic configuration that
reproduces circular_fingerprint, path_fingerprint and topological_torsion;
FingerprintOptions() is ECFP4. The class methods ecfp(radius=2, size=2048),
fcfp(radius=2, size=2048), path(path_length=7, size=2048),
torsion(size=2048) and maccs() build the same configurations. Pass the
value as options to fingerprint, fingerprints or
fingerprint_from_smiles.
import smsd
chiral = smsd.FingerprintOptions(family="ecfp", radius=2, size=2048, cip_atoms=True, ring_size=True)
left = smsd.fingerprint("C[C@H](N)C(=O)O", options=chiral)
right = smsd.fingerprint("C[C@@H](N)C(=O)O", options=chiral)
assert left != right
assert smsd.fingerprint("C[C@H](N)C(=O)O", options=smsd.FingerprintOptions()) == smsd.fingerprint(
"C[C@@H](N)C(=O)O", options=smsd.FingerprintOptions())
framework = smsd.FingerprintOptions(scaffold="murcko")
assert smsd.fingerprint("Cc1ccccc1", options=framework) == smsd.fingerprint("c1ccccc1", options=smsd.FingerprintOptions())
keys = smsd.fingerprint_from_smiles("CC(=O)Oc1ccccc1C(=O)O", options=smsd.FingerprintOptions.maccs())
assert 162 in keys # MACCS key 163, a six-membered ring, is position 162
counts = smsd.fingerprint("CC(=O)Oc1ccccc1C(=O)O", options=smsd.FingerprintOptions("keys", keys=["[#8]", ("c1ccccc1", 1)], counts=True))
assert counts == [(0, 4), (1, 1)]
Keywords, with their defaults for ecfp, fcfp, path and torsion:
family:"ecfp"(default),"fcfp","path","torsion","keys", or aFingerprintFamily(ECFP,FCFP,PATH,TORSION,KEYS).radius(2, 2, –, –; −1 for the whole component),path_lengthin bonds (–, –, 7, –),sizein bits or bins (2048), andcountsto return(position, count)pairs (False).- Atom terms:
element(on, off, on, on);degree, explicit neighbours (on, on, off, on);valence, the bond order sum or pi electrons for torsion (on, off, off, on);formal_charge,isotope,hydrogen_count,tautomer_classandaromaticity(on, off, off, off);ring_membership(on, on, off, on);ring_size, the smallest ring through the atom, with rings at or abovering_size_cap(8) sharing a class (off). - Bond terms:
bond_orderof neighbour or path bonds (on, on, off, off);aromatic_bondandring_bond(off). cip_atoms,cip_bonds: R/S (and r/s) and E/Z as fromassign_rsandassign_ez(off).fragments(ecfp,"whole"):"whole","largest"(most heavy atoms, independent of atom order),"per_fragment"(terms carry the component), or aFragmentMode(WHOLE,LARGEST,PER_FRAGMENT).scaffold(ecfp,"none"):"none","murcko"(the Bemis–Murcko framework, taken beforefragments; ring-free molecules stay whole), or aScaffoldMode(NONE,MURCKO).keys, for"keys": SMARTS strings,(smarts, minimum)pairs orFingerprintKey(smarts, minimum=1); a bit is set when at leastminimumdistinct atom sets match.
FCFP always includes the pharmacophore class. Stereo terms use whole-molecule
CIP labels; radius 0 does not separate stereoisomers, and double bonds in rings
under eight atoms carry no E/Z term. maccs() gives the 166 MACCS keys, key k
at position k − 1; maccs_keys() lists them as FingerprintKey values with
smarts, minimum and kind (what the key counts). Attributes are readable
and writable, and copy() returns an independent copy. An unknown keyword
raises TypeError; an unknown family, fragment or scaffold name raises
ValueError, as do inconsistent options and unparsable keys when computing. A
key search that runs out of time, or a key count that the embedding limit
leaves undecided, raises RuntimeError; a key whose minimum number of matches
is found before that limit is set. Ring-size terms, double-bond stereo terms,
aromatic-ring keys and SMARTS keys whose search needs the minimum cycle basis
raise smsd.RingBasisUnavailable naming the cause if ring perception cannot
complete that basis; a separate relevant-cycle truncation does not prevent
their use. The ring refusals replace values that 8.0.3 could compute from incomplete rings.
mcs_fingerprint ignores these options.
Similarity coefficients and encodings¶
tanimoto_coefficient(fp1, fp2, fp_size=2048): |A ∩ B| / |A ∪ B|; 1.0 for two empty fingerprints.overlap_coefficient(fp1, fp2, fp_size=2048): |A ∩ B| / min(|A|, |B|); 1.0 for two empty, 0.0 for one.overlapCoefficientis a deprecated alias.dice(fp1, fp2, fp_size=2048),cosine(fp1, fp2, fp_size=2048): 2|A ∩ B| / (|A| + |B|) and |A ∩ B| / √(|A| |B|).soergel(fp1, fp2, fp_size=2048): 1 − Tanimoto.count_tanimoto_coefficient(fp1, fp2): Σ min(a, b) / Σ max(a, b).count_overlap_coefficient(fp1, fp2): Σ min(a, b) / min(Σ a, Σ b);count_overlapCoefficientis a deprecated alias.count_dice(fp1, fp2),count_cosine(fp1, fp2): Dice and cosine of dense count vectors of equal length,(position, count)pairs or dictionaries.counts_to_array(counts_dict, fp_size): a dense list of lengthfp_sizefrom a dictionary or pairs, ignoring positions outside it.to_hex(fp, fp_size=2048),from_hex(hex_str): hexadecimal text and back; a non-hexadecimal character raisesValueError.to_binary_string(fp, fp_size=2048):fp_sizecharacters of0and1.
tanimoto_coefficient and overlap_coefficient compare positions only, even
for counts, and ignore fp_size. The two sparse count coefficients take pairs
or position lists and return 0.0 when both are empty.
Screening¶
similarity(mol1, mol2): an upper bound in [0, 1] on MCS similarity, for screening; not a fingerprint metric.similarity_upper_bound(g1, g2, chemistry=None): the same bound underchemistry;screen_targets(query, targets, threshold, chemistry=None, *, num_threads=0): indices of targets whose bound reachesthreshold, in target order;0uses the OpenMP default (OMP_NUM_THREADS), as inmatch_many. For both,chemis a deprecated keyword. To screen the same targets repeatedly, pass a preparedTargetCorpusastargets, so that they are not converted again on each call.batch_similarity(query, targets, fp_size=2048, radius=2):(index, Tanimoto)pairs on ECFP, most similar first.gpu_is_available(),gpu_device_info(): whether a GPU backend is available, and a description of the run-time compute backend (the CPU when there is no GPU), not of the backend a call used.
MOL and SDF¶
read_mol_block(mol_block),read_mol_file(filename): V2000 or V3000 text or file; an unopenable file or a block that cannot be read, such as a V3000 block with more than 10,000 atoms, raisesValueError; V2000 numeric fields are read leniently.read_molfileis a deprecated alias.read_sdf(filename): every record at once; malformed records become empty graphs.write_mol_block(mol)(V2000, or V3000 above 999 atoms or bonds),write_mol_block_v3000(mol)andwrite_sdf_record(mol): MOL text or one SDF record.write_molfile(mol, filename, *, v3000=False, sdf=False): write a MOL block, V3000 block or SDF record.write_sdf(molecules, filename): write SDF records;export_sdfis a deprecated alias.
Coordinates and layout¶
Coordinates are lists of (x, y) or (x, y, z) tuples, one per atom; [x, y]
lists are accepted, and new lists are returned. Every function taking mol
accepts a molecule.
generate_coords_2d(mol, target_bond_length=1.5),generate_coords_3d(mol, target_bond_length=1.5): 2D coordinates, or a 3D starting geometry that is not a validated conformer.layout_quality(mol, coords, target_bond_length=1.5): a bond-length, overlap and crossing score; 0.0 is ideal.count_crossings(mol, coords),is_degenerate_layout(coords): crossing bond pairs; whether width or height is zero.layout_sssr(mol): the smallest set of smallest rings, ordered for layout.match_template(mol, target_bond_length=1.5): coordinates for benzene, naphthalene, indole, purine, steroid, morphinan, quinoline, biphenyl, cyclohexane or piperidine, else[].force_directed_layout(mol, coords, max_iter=500, target_bond_length=1.5),stress_majorisation(mol, coords, max_iter=300, target_bond_length=1.5):(stress, coords).reduce_crossings(mol, coords, max_iter=1000):(crossings, coords)after flipping ring systems;resolve_overlaps(coords, threshold=0.3, max_iter=100):(remaining_overlaps, coords).translate_2d(coords, dx, dy)translates;rotate_2d(coords, angle), in radians, andscale_2d(coords, factor)act about the centroid.mirror_x(coords),mirror_y(coords): reflect in the x or y axis;center_2d(coords)centres on the origin.align_2d(coords, reference):(rmsd, aligned);bounding_box_2d(coords):(min_x, min_y, max_x, max_y).normalise_bond_length(mol, coords, target=1.5)scales to the bond length;canonical_orientation(mol, coords)rotates onto a 30-degree grid.Point2D(x, y),Point3D(x, y, z): value types with writable coordinates.
Depiction¶
depict_svg(mol_or_smiles, opts=None, **kwargs): SVG text.depict_mapping(mol_or_smiles, mapping, opts=None, **kwargs): SVG highlighting and numbering mapped atoms, keyed by drawn-atom index.depict_pair(mol1, mol2, mapping, opts=None, **kwargs): a side-by-side SVG of a mapping.save_svg(svg_str, path): write SVG text to a file.depict_mcs(mol1, mol2, *, tautomer_aware=False, timeout_ms=10000, size=(600, 300), highlight_color=(0.5, 0.8, 1.0)): an RDKit image of the MCS;highlight_coloris deprecated, ignored, and warns when changed.depict_substructure(query, target, *, timeout_ms=10000, size=(600, 300)): an RDKit image of a substructure match.to_svg(mol_or_smiles, *, size=(300, 200)): deprecated RDKit SVG; usedepict_svg.
The native SVG functions need no RDKit; keyword arguments set DepictOptions
attributes, on opts when given. Defaults: bond_length and padding 30 px;
line_width, bold_width, font_size and match_width 0 (automatic);
width and height 0 (fitted to the molecule); bond_spacing and
highlight_radius 0 (18% and 40% of bond_length); highlight_opacity 0.30;
subscript_scale 0.70; show_carbon_labels and show_atom_indices False;
show_map_numbers True; font_family "Arial, Helvetica, sans-serif".
Further reading¶
The user guide, Python package README, installation guide, C++ guide, Java guide and changelog.