Enable templating for macrocycles (#9203)

* parse templates as smarts

* accept ring templates in SMARTS format

* undo CLAUDE mistake

* rename files

* enable templating for macrocycles

* enable macrocycle templating

* Add test for macrocycle templating

Tests that ring system templates are used only for macrocycles (rings
with size > 8). The test verifies the exact threshold by generating
coordinates with and without templates for rings of size 4-14.

Addresses review feedback on PR #9203.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nic Zonta
2026-03-27 06:25:20 +01:00
committed by GitHub
parent c9dfd5a40e
commit 2096c7fe33
2 changed files with 55 additions and 1 deletions

View File

@@ -629,7 +629,10 @@ void EmbeddedFrag::embedFusedRings(const RDKit::VECT_INT_VECT &fusedRings,
RDKit::INT_VECT funion;
// look for a template that matches the entire fused ring system
if (useRingTemplates && fusedRings.size() > 1) {
// For single rings, only use templates for macrocycles (size > 8)
if (useRingTemplates &&
(fusedRings.size() > 1 ||
(fusedRings.size() == 1 && fusedRings[0].size() > 8))) {
RDKit::Union(fusedRings, funion);
bool found_template = matchToTemplate(funion, fusedRings.size());
if (found_template) {

View File

@@ -2472,4 +2472,55 @@ TEST_CASE("canonical ordering") {
INFO("i " << i << " " << j);
}
}
}
TEST_CASE("macrocycle templating") {
// Helper function to test if templates are used for a ring of size n.
// We generate a ring of that size, generate 2D coordinates with and without
// templates enabled, and compare the results. If the coordinates are the
// same, we assume no template was used. If they differ, a template was used.
auto templates_are_used_for_ring_size_n = [](int ringSize) -> bool {
// Build SMILES for n-membered ring: C1 + (n-2) C's + C1
std::string smiles = "C1";
for (int i = 0; i < ringSize - 2; ++i) {
smiles += "C";
}
smiles += "C1";
auto mol = SmilesToMol(smiles);
if (!mol) {
return false;
}
// Generate coordinates WITHOUT templates
RDDepict::Compute2DCoordParameters params;
params.useRingTemplates = false;
RDDepict::compute2DCoords(*mol, params);
auto withoutTemplates =
mol->getConformer().getAtomPos(0) -
mol->getConformer().getAtomPos(ringSize / 2);
// Generate coordinates WITH templates
params.useRingTemplates = true;
RDDepict::compute2DCoords(*mol, params);
auto withTemplates = mol->getConformer().getAtomPos(0) -
mol->getConformer().getAtomPos(ringSize / 2);
delete mol;
// Return true if coordinates differ (templates were used)
return !RDKit::feq(withoutTemplates.length(), withTemplates.length(), 0.01);
};
SECTION("template usage threshold at ring size 8") {
// Test that templates are used only for rings with size > 8
for (int i = 4; i <= 14; ++i) {
CAPTURE(i);
bool templatesUsed = templates_are_used_for_ring_size_n(i);
bool expectedTemplatesUsed = (i > 8);
CHECK(templatesUsed == expectedTemplatesUsed);
}
}
}