From fd50a8f8e08d3aa13fb718dd7b6c8761719b2679 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 17 May 2026 22:17:55 -0700 Subject: [PATCH 1/2] Fix aromatic ring detection not accounting for hybridization --- CHANGELOG.md | 1 + .../structure/structure/unit/rings.ts | 33 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca223246..e9bde57dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Note that since we don't clearly distinguish between a public and private interf - Fix memory leak in `State.dispose()` not invoking transformer `dispose` callbacks for live cells - Fix bugs in ModelServer surroundingLigands endpoint, resulting in omitWater not honored - Fix `Volume` and `Isosurface` getBoundingSphere ignoring instances +- Fix aromatic ring detection not accounting for hybridization ## [v5.9.0] - 2026-05-03 - Fix edge case when `PluginSpec.animations` is empty diff --git a/src/mol-model/structure/structure/unit/rings.ts b/src/mol-model/structure/structure/unit/rings.ts index 21f095fa5..3549906e5 100644 --- a/src/mol-model/structure/structure/unit/rings.ts +++ b/src/mol-model/structure/structure/unit/rings.ts @@ -95,10 +95,22 @@ namespace UnitRing { Elements.SN, Elements.SB, Elements.BI ] as ElementSymbol[]); + /** + * Elements that are sp3 (and therefore non-aromatic) when degree >= 4 with no pi bonds. + * Excludes O (never realistically reaches degree 4) and N (quaternary N can be aromatic, + * but is guarded by the hasPiBond check below). + */ + const Sp3RingCheckElements = new Set([ + Elements.B, Elements.C, Elements.N, + Elements.SI, Elements.P, Elements.S, + Elements.GE, Elements.AS, + Elements.SN, Elements.SB, + Elements.BI + ] as ElementSymbol[]); const AromaticRingPlanarityThreshold = 0.05; export function isAromatic(unit: Unit.Atomic, ring: UnitRing): boolean { - const { elements, bonds: { b, offset, edgeProps: { flags } } } = unit; + const { elements, bonds: { b, offset, edgeProps: { flags, order } } } = unit; const { type_symbol, label_comp_id } = unit.model.atomicHierarchy.atoms; // ignore Proline (can be flat because of bad geometry) @@ -120,6 +132,25 @@ namespace UnitRing { } } } + + for (let i = 0, il = ring.length; i < il; ++i) { + const aI = ring[i]; + const elem = type_symbol.value(elements[aI]); + if (!Sp3RingCheckElements.has(elem)) continue; + + let degree = 0; + let hasPiBond = false; + for (let j = offset[aI], jl = offset[aI + 1]; j < jl; ++j) { + degree += 1; + const f = flags[j]; + const o = order[j]; + if (BondType.is(BondType.Flag.Aromatic, f) || o === 2 || o === 3) { + hasPiBond = true; + } + } + if (degree >= 4 && !hasPiBond) return false; + } + if (aromaticBondCount === 2 * ring.length) return true; if (!hasAromaticRingElement) return false; if (ring.length < 5) return false; From bcd304d058b7ebc1ef4f8222d1c498d71e77d2c4 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 17 May 2026 22:19:04 -0700 Subject: [PATCH 2/2] header --- src/mol-model/structure/structure/unit/rings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mol-model/structure/structure/unit/rings.ts b/src/mol-model/structure/structure/unit/rings.ts index 3549906e5..40f323515 100644 --- a/src/mol-model/structure/structure/unit/rings.ts +++ b/src/mol-model/structure/structure/unit/rings.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal * @author Alexander Rose