Generate and read scaffold networks - #56
Draft
cpetersen wants to merge 2 commits into
Draft
Conversation
The scaffold_network bridge could build ScaffoldNetworkParams but had no way to run RDKit's generator or read a network back, so a network could not be produced from Rust at all. rdkit-sys gains create_scaffold_network/update_scaffold_network plus accessors that return the nodes, counts, molCounts and edges as plain data. RDKit's createScaffoldNetwork is a template and only std::vector<ROMOL_SPTR> (which is boost::shared_ptr) and std::vector<std::shared_ptr<ROMol>> are explicitly instantiated, so the wrapper takes one molecule at a time and wraps it in a one element vector rather than fighting cxx over Vec<SharedPtr<ROMol>> (BACKLOG.md). Callers wanting a network across several molecules build it up with update_scaffold_network. rdkit gains a safe ScaffoldNetworkParams builder and scaffold_network_for_mol/scaffold_network_for_mols, returning a ScaffoldNetwork of nodes and edges. Edges carry child_idx/parent_idx accessors because RDKit's beginIdx is the more specific scaffold and endIdx the more general one, which is easy to get backwards. Worth knowing: new_scaffold_network_params(&vec![]) leaves RDKit with no bond breaker reactions, so nothing ever fragments and every network is a single node. The safe wrapper defaults to default_scaffold_network_params instead, and tests pin both behaviours.
Credits Wilkens/Janes/Su (J. Med. Chem. 2005) as the prior art that established hierarchical scaffold clustering, while being explicit that it is not the algorithm RDKit implements: HierS enumerates ring-delimited substructures directly, where RDKit applies a bond breaking reaction and additionally emits generic and attachment point scaffolds. RDKit's own sources cite no paper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The
scaffold_networkbridge could build aScaffoldNetworkParamsand hand back an emptyScaffoldNetwork, but there was no binding for RDKit's generator and no way to read a network'scontents. A scaffold network could not actually be produced from Rust. This adds both layers.
rdkit-sys
wrapper/{include,src}/scaffold_network.{h,cc}+src/bridge/scaffold_network.rs:create_scaffold_network(mol, params) -> Result<SharedPtr<ScaffoldNetworkClass>>update_scaffold_network(network, mol, params) -> Result<()>ScaffoldNetworkmember:scaffold_network_nodes(Vec<String>),scaffold_network_counts/scaffold_network_mol_counts(
Vec<u32>),scaffold_network_edge_begin_indices/..._end_indices(Vec<usize>) andscaffold_network_edge_types(Vec<String>).Two things shaped this:
RDKit::ScaffoldNetwork::createScaffoldNetworkis a template, and onlystd::vector<ROMOL_SPTR>(which is
boost::shared_ptr<ROMol>) andstd::vector<std::shared_ptr<ROMol>>are explicitlyinstantiated. cxx hands us one
std::shared_ptr<ROMol>at a time, so the wrapper takes a singlemolecule and wraps it in a one element vector rather than fighting cxx over
Vec<SharedPtr<ROMol>>— the pain point already written up inBACKLOG.md. Callers who want anetwork spanning several molecules accumulate with
update_scaffold_network.bridge, so there is no
Vec<SharedPtr<T>>anywhere. Edge type strings come from RDKit's ownoperator<<forEdgeTypeso they cannot drift from upstream naming.Both generator functions are declared
Result<...>, so RDKit'sValueErrorException(bad params,null molecule) surfaces as an
Errinstead of unwinding through Rust.build.rsneeded no change, as expected — it discovers bridges by scanningsrc/bridgeand alreadylinks
RDKitScaffoldNetwork.rdkit
New
src/scaffold_network.rs, re-exported fromsrc/lib.rs, mirroringsrc/substruct_match/:ScaffoldNetworkParamswithnew()/with_bond_breakers()and aset_*per RDKit fieldscaffold_network_for_mol(&ROMol, &ScaffoldNetworkParams) -> Result<ScaffoldNetwork, ScaffoldNetworkError>scaffold_network_for_mols(&[ROMol], ...)for one network spanning several moleculesScaffoldNetwork { nodes: Vec<ScaffoldNetworkNode>, edges: Vec<ScaffoldNetworkEdge> }, withScaffoldNetworkEdgeTypeas an enum rather than a bare stringScaffoldNetworkEdgeexposeschild_idx()/parent_idx()on top ofbegin_idx/end_idx:RDKit's
beginIdxis the more specific scaffold andendIdxthe more general one, which iseasy to get backwards and matters a lot to anything building a hierarchy out of this.
mol_countisOption<u32>because RDKit leavesmolCountsempty rather than zero filled whencollectMolCountsis off, so the vector does not always parallelnodes.Two behaviours worth knowing about
Both are pinned by tests so they cannot regress silently.
new_scaffold_network_params(&vec![])disables fragmentation entirely. The argument goesstraight to
ScaffoldNetworkParams(const std::vector<std::string> &bondBreakersSmarts), so anempty vec means no bond breaker reactions and every network comes back as a single node. The
pre-existing test only ever called it with an empty vec, which is part of why this went unnoticed.
ScaffoldNetworkParams::new()usesdefault_scaffold_network_params()instead, which carriesRDKit's default
[!#0;R:1]-!@[!#0:2]>>[*:1]-[#0].[#0]-[*:2].Initializeedge — e.g.
CCOgives nodes["CCO", ""]. Consumers deduping scaffolds by SMILES will want todecide deliberately what to do with that node.
Corrections to what we thought was there
NetworkEdge::beginIdx/endIdxaresize_t, notunsigned.ScaffoldNetworkParamshas nobondBreakersSmartsmember; it hasbondBreakersRxns(std::vector<std::shared_ptr<ChemicalReaction>>) and the SMARTS vector is aconstructor argument only. The existing bridge already had this right.
counts/molCountsarestd::vector<unsigned>.Versions
rdkitandrdkit-sysboth0.4.12->0.4.13.rdkit's dependency onrdkit-sysalso moves fromversion = "0.4.9"to"0.4.13". That isload-bearing, not cosmetic: a published
rdkit0.4.13 asking forrdkit-sys ^0.4.9could resolveto 0.4.12, which has none of these bindings, and fail to compile.
Downstream should pin
rdkit = "0.4.13"once this is released.Test evidence
Run against RDKit 2024_09_1 on Ubuntu 22.04 (same tarball CI installs), since RDKit is not available
on the dev host.
The new rdkit-sys tests build a real network for 2-phenylquinoline and assert exact node SMILES,
edge indices and edge types; plus single-ring, ring-free, multi-molecule, empty-bond-breaker and
bad-params cases. The rdkit tests cover the same ground through the safe API and assert the
parent/child direction of every edge by SMILES rather than by index.
Prior art
This binds RDKit's
rdScaffoldNetwork; it is not an implementation of HierS. The two are in thesame family but differ: HierS (Wilkens, Janes & Su, "HierS: hierarchical scaffold clustering using
topological chemical graphs", J. Med. Chem. 2005, 48(9), 3182-93,
doi:10.1021/jm049032d, PMID 15857124) recursively enumerates
ring-delimited substructures, where RDKit applies a bond breaking reaction and additionally emits
Generic,GenericBondandRemoveAttachmentscaffolds that HierS has no equivalent of.HierS is cited in the module docs as the prior art that established this class of hierarchy, and
labelled as such rather than as the implemented algorithm. Worth noting that RDKit's
Code/GraphMol/ScaffoldNetwork/sources carry no citation of their own, so there is no primaryreference to point at for the exact algorithm.