Fix aromatic ring detection not accounting for hybridization

This commit is contained in:
Alexander Rose
2026-05-17 22:17:55 -07:00
parent 8d2a44983e
commit fd50a8f8e0
2 changed files with 33 additions and 1 deletions

View File

@@ -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

View File

@@ -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;