Browse Source

general support for aromatic rings

- containing annotated aromatic bonds
- being flat with certain elements
Alexander Rose 5 years ago
parent
commit
b863aaedb3

+ 5 - 33
src/mol-model-props/computed/interactions/charged.ts

@@ -10,17 +10,15 @@
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
 import { FeaturesBuilder, Features } from './features';
-import { ProteinBackboneAtoms, PolymerNames, BaseNames, ElementSymbol } from '../../../mol-model/structure/model/types';
-import { typeSymbol, atomId, eachBondedAtom, compId } from '../chemistry/util';
+import { ProteinBackboneAtoms, PolymerNames, BaseNames } from '../../../mol-model/structure/model/types';
+import { typeSymbol, atomId, eachBondedAtom } from '../chemistry/util';
 import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
 import { ValenceModelProvider } from '../valence-model';
 import { degToRad } from '../../../mol-math/misc';
 import { FeatureType, FeatureGroup, InteractionType } from './common';
 import { ContactProvider } from './contacts';
-import { Segmentation, SortedArray } from '../../../mol-data/int';
+import { Segmentation } from '../../../mol-data/int';
 import { isGuanidine, isAcetamidine, isPhosphate, isSulfonicAcid, isSulfate, isCarboxylate } from '../chemistry/functional-group';
-import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
-import { getPositions } from '../../../mol-model/structure/util';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 
 const IonicParams = {
@@ -189,38 +187,12 @@ function addUnitNegativeCharges(structure: Structure, unit: Unit.Atomic, builder
     }
 }
 
-const AromaticRingElements = [
-    Elements.B, Elements.C, Elements.N, Elements.O,
-    Elements.SI, Elements.P, Elements.S,
-    Elements.GE, Elements.AS,
-    Elements.SN, Elements.SB,
-    Elements.BI
-] as ElementSymbol[]
-const AromaticRingPlanarityThreshold = 0.05
-
-function isRingAromatic(unit: Unit.Atomic, ring: SortedArray<StructureElement.UnitIndex>) {
-    // ignore Proline (can be flat because of bad geometry)
-    if (compId(unit, ring[0]) === 'PRO') return
-    // TODO also check `chem_comp_bond.pdbx_aromatic_flag`
-    let hasAromaticRingElement = false
-    for (let i = 0, il = ring.length; i < il; ++i) {
-        if (AromaticRingElements.includes(typeSymbol(unit, ring[i]))) {
-            hasAromaticRingElement = true
-            break
-        }
-    }
-    if (!hasAromaticRingElement) return
-
-    const ma = PrincipalAxes.calculateMomentsAxes(getPositions(unit, ring))
-    return Vec3.magnitude(ma.dirC) < AromaticRingPlanarityThreshold
-}
-
 function addUnitAromaticRings(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
 
-    for (const ring of unit.rings.all) {
-        if (!isRingAromatic(unit, ring)) continue
+    for (const ringIndex of unit.rings.aromaticRings) {
+        const ring = unit.rings.all[ringIndex]
         builder.startState()
         for (let i = 0, il = ring.length; i < il; ++i) {
             const j = ring[i]

+ 10 - 3
src/mol-model/structure/query/queries/generators.ts

@@ -223,7 +223,7 @@ function getRingStructure(unit: Unit.Atomic, ring: UnitRing, inputStructure: Str
     return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], { parent: inputStructure });
 }
 
-export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery {
+export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>, onlyAromatic?: boolean): StructureQuery {
     return function query_rings(ctx) {
         const { units } = ctx.inputStructure;
         const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
@@ -232,8 +232,14 @@ export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): Structure
             for (const u of units) {
                 if (!Unit.isAtomic(u)) continue;
 
-                for (const r of u.rings.all) {
-                    ret.add(getRingStructure(u, r, ctx.inputStructure));
+                if (onlyAromatic) {
+                    for (const r of u.rings.aromaticRings) {
+                        ret.add(getRingStructure(u, u.rings.all[r], ctx.inputStructure));
+                    }
+                } else {
+                    for (const r of u.rings.all) {
+                        ret.add(getRingStructure(u, r, ctx.inputStructure));
+                    }
                 }
             }
         } else {
@@ -247,6 +253,7 @@ export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): Structure
                 for (const fp of uniqueFps.array) {
                     if (!rings.byFingerprint.has(fp)) continue;
                     for (const r of rings.byFingerprint.get(fp)!) {
+                        if (onlyAromatic && !rings.aromaticRings.includes(r)) continue;
                         ret.add(getRingStructure(u, rings.all[r], ctx.inputStructure));
                     }
                 }

+ 62 - 2
src/mol-model/structure/structure/unit/rings.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { computeRings, getFingerprint, createIndex } from './rings/compute'
@@ -9,7 +10,11 @@ import Unit from '../unit';
 import StructureElement from '../element';
 import { SortedArray } from '../../../../mol-data/int';
 import { ResidueIndex } from '../../model';
-import { ElementSymbol } from '../../model/types';
+import { ElementSymbol, BondType } from '../../model/types';
+import { Elements } from '../../model/properties/atomic/types';
+import { getPositions } from '../../util';
+import { PrincipalAxes } from '../../../../mol-math/linear-algebra/matrix/principal-axes';
+import { Vec3 } from '../../../../mol-math/linear-algebra';
 
 type UnitRing = SortedArray<StructureElement.UnitIndex>
 
@@ -23,6 +28,7 @@ class UnitRings {
         readonly ringComponentIndex: ReadonlyArray<UnitRings.ComponentIndex>,
         readonly ringComponents: ReadonlyArray<ReadonlyArray<UnitRings.Index>>
     };
+    private _aromaticRings?: ReadonlyArray<UnitRings.Index>
 
     private get index() {
         if (this._index) return this._index;
@@ -50,6 +56,12 @@ class UnitRings {
         return this.index.ringComponents;
     }
 
+    get aromaticRings() {
+        if (this._aromaticRings) return this._aromaticRings;
+        this._aromaticRings = getAromaticRings(this.unit, this.all);
+        return this._aromaticRings;
+    }
+
     constructor(all: ReadonlyArray<UnitRing>, public unit: Unit.Atomic) {
         this.all = all;
     }
@@ -70,6 +82,47 @@ namespace UnitRing {
     export function elementFingerprint(elements: ArrayLike<ElementSymbol>) {
         return getFingerprint(elements as ArrayLike<string> as string[]) as Fingerprint;
     }
+
+    const AromaticRingElements = new Set([
+        Elements.B, Elements.C, Elements.N, Elements.O,
+        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: SortedArray<StructureElement.UnitIndex>): boolean {
+        const { elements, bonds: { b, offset, edgeProps: { flags } } } = unit;
+        const { type_symbol } = unit.model.atomicHierarchy.atoms;
+        const { label_comp_id } = unit.model.atomicHierarchy.residues;
+
+        // ignore Proline (can be flat because of bad geometry)
+        if (label_comp_id.value(unit.getResidueIndex(ring[0])) === 'PRO') return false
+
+        let aromaticBondCount = 0
+        let hasAromaticRingElement = false
+
+        for (let i = 0, il = ring.length; i < il; ++i) {
+            const aI = ring[i]
+            if (!hasAromaticRingElement && AromaticRingElements.has(type_symbol.value(elements[aI]))) {
+                hasAromaticRingElement = true
+            }
+
+            for (let j = offset[aI], jl = offset[aI + 1]; j < jl; ++j) {
+                // comes e.g. from `chem_comp_bond.pdbx_aromatic_flag`
+                if (BondType.is(BondType.Flag.Aromatic, flags[j])) {
+                    if (SortedArray.has(ring, b[j])) aromaticBondCount += 1
+
+                }
+            }
+        }
+        if (aromaticBondCount === 2 * ring.length) return true
+        if (!hasAromaticRingElement) return false
+
+        const ma = PrincipalAxes.calculateMomentsAxes(getPositions(unit, ring))
+        return Vec3.magnitude(ma.dirC) < AromaticRingPlanarityThreshold
+    }
 }
 
 namespace UnitRings {
@@ -126,5 +179,12 @@ function addSingleResidueRings(rings: UnitRings, fp: UnitRing.Fingerprint, map:
     }
 }
 
+function getAromaticRings(unit: Unit.Atomic, rings: ReadonlyArray<UnitRing>): ReadonlyArray<UnitRings.Index> {
+    const aromaticRings: UnitRings.Index[] = []
+    for (let i = 0 as UnitRings.Index, il = rings.length; i < il; ++i) {
+        if (UnitRing.isAromatic(unit, rings[i])) aromaticRings.push(i)
+    }
+    return aromaticRings
+}
 
 export { UnitRing, UnitRings }

+ 2 - 1
src/mol-plugin-ui/structure/selection.tsx

@@ -19,7 +19,8 @@ const SSQ = StructureSelectionQueries
 const DefaultQueries: (keyof typeof SSQ)[] = [
     'all', 'polymer', 'trace', 'backbone', 'protein', 'nucleic',
     'helix', 'beta',
-    'water', 'branched', 'ligand', 'nonStandardPolymer', 'ring',
+    'water', 'branched', 'ligand', 'nonStandardPolymer',
+    'ring', 'aromaticRing',
     'surroundings', 'complement', 'bonded'
 ]
 

+ 5 - 0
src/mol-plugin/util/structure-selection-helper.ts

@@ -298,6 +298,10 @@ const ring = StructureSelectionQuery('Rings in Residues', MS.struct.modifier.uni
     MS.struct.generator.rings()
 ]))
 
+const aromaticRing = StructureSelectionQuery('Aromatic Rings in Residues', MS.struct.modifier.union([
+    MS.struct.generator.rings({ 'only-aromatic': true })
+]))
+
 const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of Selection', MS.struct.modifier.union([
     MS.struct.modifier.exceptBy({
         0: MS.struct.modifier.includeSurroundings({
@@ -345,6 +349,7 @@ export const StructureSelectionQueries = {
     nonStandardPolymer,
     coarse,
     ring,
+    aromaticRing,
     surroundings,
     complement,
     bonded,

+ 4 - 1
src/mol-script/language/symbol-table/structure-query.ts

@@ -99,7 +99,10 @@ const generator = {
         // 'group-by': Argument(Type.Any, { isOptional: true, defaultValue: ``, description: 'Group the bonds using the privided value' }),
     }), Types.ElementSelectionQuery, 'Return all pairs of atoms for which the test is satisfied.'),
 
-    rings: symbol(Arguments.List(Types.RingFingerprint), Types.ElementSelectionQuery, 'Return rings with the specified fingerprint(s). If no fingerprints are given, return all rings.'),
+    rings: symbol(Arguments.Dictionary({
+        'fingerprint': Argument(Types.RingFingerprint, { isOptional: true }),
+        'only-aromatic': Argument(Type.Bool, { isOptional: true, defaultValue: false }),
+    }), Types.ElementSelectionQuery, 'Return all rings or those with the specified fingerprint and/or only aromatic rings.'),
 
     queryInSelection: symbol(Arguments.Dictionary({
         0: Argument(Types.ElementSelectionQuery),

+ 1 - 1
src/mol-script/runtime/query/table.ts

@@ -239,7 +239,7 @@ const symbols = [
     D(MolScript.structureQuery.generator.all, function structureQuery_generator_all(ctx) { return Queries.generators.all(ctx) }),
     D(MolScript.structureQuery.generator.empty, function structureQuery_generator_empty(ctx) { return Queries.generators.none(ctx) }),
     D(MolScript.structureQuery.generator.bondedAtomicPairs, function structureQuery_generator_bondedAtomicPairs(ctx, xs) { return Queries.generators.bondedAtomicPairs(xs && xs[0])(ctx) }),
-    D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) { return Queries.generators.rings(getArray(ctx, xs))(ctx) }),
+    D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) { return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx) }),
 
     // ============= MODIFIERS ================