Bladeren bron

Refactoring UnitRings, carbs computation

David Sehnal 6 jaren geleden
bovenliggende
commit
a8a746142d

+ 2 - 3
src/apps/structure-info/model.ts

@@ -9,11 +9,10 @@ import * as argparse from 'argparse'
 require('util.promisify').shim();
 
 import { CifFrame } from 'mol-io/reader/cif'
-import { Model, Structure, StructureElement, Unit, Format, StructureProperties } from 'mol-model/structure'
+import { Model, Structure, StructureElement, Unit, Format, StructureProperties, UnitRing } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { openCif, downloadCif } from './helpers';
-import { UnitRings } from 'mol-model/structure/structure/unit/rings';
 import { Vec3 } from 'mol-math/linear-algebra';
 
 
@@ -136,7 +135,7 @@ export function printRings(structure: Structure) {
         const { all, byFingerprint } = unit.rings;
         const fps: string[] = [];
         for (let i = 0, _i = Math.min(5, all.length); i < _i; i++) {
-            fps[fps.length] = UnitRings.getRingFingerprint(unit, all[i]);
+            fps[fps.length] = UnitRing.fingerprint(unit, all[i]);
         }
         if (all.length > 5) fps.push('...')
         console.log(`Unit ${unit.id}, ${all.length} ring(s), ${byFingerprint.size} different fingerprint(s).\n  ${fps.join(', ')}`);

+ 2 - 1
src/mol-model/structure/structure.ts

@@ -11,4 +11,5 @@ import StructureSymmetry from './structure/symmetry'
 import { Link } from './structure/unit/links'
 import StructureProperties from './structure/properties'
 
-export { StructureElement, Link, Structure, Unit, StructureSymmetry, StructureProperties }
+export { StructureElement, Link, Structure, Unit, StructureSymmetry, StructureProperties }
+export * from './structure/unit/rings'

+ 8 - 38
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -18,42 +18,11 @@ import Structure from '../structure';
 import Unit from '../unit';
 import { SaccharideNameMap, UnknownSaccharideComponent } from './constants';
 import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink } from './data';
+import { UnitRings, UnitRing } from '../unit/rings';
 
-function getResidueIndex(elementIndex: number, unit: Unit.Atomic) {
-    return unit.model.atomicHierarchy.residueAtomSegments.index[unit.elements[elementIndex]]
-}
-
-function sugarResidueIdx(unit: Unit.Atomic, ring: ArrayLike<StructureElement.UnitIndex>): ResidueIndex {
-    const { elements } = unit;
-    const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
-    const idx = residueIndex[elements[ring[0]]];
-    for (let rI = 1, _rI = ring.length; rI < _rI; rI++) {
-        if (idx !== residueIndex[elements[ring[rI]]]) return -1 as ResidueIndex;
-    }
-    return idx;
-}
+const C = ElementSymbol('C'), O = ElementSymbol('O');
+const SugarRingFps = [UnitRing.elementFingerprint([C, C, C, C, C, O]), UnitRing.elementFingerprint([C, C, C, C, O])]
 
-function addSugarRings(unit: Unit.Atomic, fp: string, sugarResidues: Map<ResidueIndex, number[]>) {
-    const rings = unit.rings;
-    const byFp = rings.byFingerprint.get(fp);
-    if (!byFp) return;
-    for (const r of byFp) {
-        const idx = sugarResidueIdx(unit, rings.all[r]);
-        if (idx >= 0) {
-            if (sugarResidues.has(idx)) sugarResidues.get(idx)!.push(r);
-            else sugarResidues.set(idx, [r]);
-        }
-    }
-}
-
-function getSugarRingIndices(unit: Unit.Atomic) {
-    const sugarResidues = new Map<ResidueIndex, number[]>();
-    addSugarRings(unit, 'C-C-C-C-C-O', sugarResidues);
-    addSugarRings(unit, 'C-C-C-C-O', sugarResidues);
-    return sugarResidues;
-}
-
-const C = ElementSymbol('C')
 function getDirection(direction: Vec3, unit: Unit.Atomic, indices: ArrayLike<StructureElement.UnitIndex>, center: Vec3) {
     let indexC1 = -1, indexC1X = -1, indexC = -1
     const { elements } = unit
@@ -85,6 +54,7 @@ function getAtomId(unit: Unit.Atomic, index: number) {
     return label_atom_id.value(elements[index])
 }
 
+
 export function computeCarbohydrates(structure: Structure): Carbohydrates {
     const links: CarbohydrateLink[] = []
     const terminalLinks: CarbohydrateTerminalLink[] = []
@@ -120,7 +90,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
         const chainIt = Segmentation.transientSegments(chainAtomSegments, unit.elements)
         const residueIt = Segmentation.transientSegments(residueAtomSegments, unit.elements)
 
-        let sugarResidueMap: Map<ResidueIndex, number[]> | undefined = void 0;
+        let sugarResidueMap: Map<ResidueIndex, UnitRings.Index[]> | undefined = void 0;
 
         while (chainIt.hasNext) {
             residueIt.setSegment(chainIt.move());
@@ -134,7 +104,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                 }
 
                 if (!sugarResidueMap) {
-                    sugarResidueMap = getSugarRingIndices(unit);
+                    sugarResidueMap = UnitRings.byFingerprintAndResidue(unit.rings, SugarRingFps);
                 }
 
                 const sugarRings = sugarResidueMap.get(residueIndex);
@@ -202,8 +172,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                 pairBonds.getBonds(indexA).forEach(bondInfo => {
                     const { unitA, unitB } = pairBonds
                     const indexB = bondInfo.indexB
-                    const elementIndexA = elementsWithRingMap.get(elementKey(getResidueIndex(indexA, unitA), unitA.id))
-                    const elementIndexB = elementsWithRingMap.get(elementKey(getResidueIndex(indexB, unitB), unitB.id))
+                    const elementIndexA = elementsWithRingMap.get(elementKey(unitA.getResidueIndex(indexA), unitA.id))
+                    const elementIndexB = elementsWithRingMap.get(elementKey(unitB.getResidueIndex(indexB), unitB.id))
 
                     if (elementIndexA !== undefined && elementIndexB !== undefined) {
                         if (getAtomId(unitA, indexA).startsWith('C1')) {

+ 4 - 0
src/mol-model/structure/structure/unit.ts

@@ -117,6 +117,10 @@ namespace Unit {
             return this.props.rings.ref;
         }
 
+        getResidueIndex(elementIndex: StructureElement.UnitIndex) {
+            return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]];
+        }
+
         constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
             this.id = id;
             this.invariantId = invariantId;

+ 3 - 2
src/mol-model/structure/structure/unit/links/data.ts

@@ -8,6 +8,7 @@
 import { LinkType } from '../../../model/types'
 import { IntAdjacencyGraph } from 'mol-math/graph';
 import Unit from '../../unit';
+import StructureElement from '../../element';
 
 type IntraUnitLinks = IntAdjacencyGraph<{ readonly order: ArrayLike<number>, readonly flags: ArrayLike<LinkType.Flag> }>
 
@@ -76,14 +77,14 @@ namespace InterUnitBonds {
         }
 
         constructor(public unitA: Unit.Atomic, public unitB: Unit.Atomic,
-            public bondCount: number, public linkedElementIndices: ReadonlyArray<number>,
+            public bondCount: number, public linkedElementIndices: ReadonlyArray<StructureElement.UnitIndex>,
             private linkMap: Map<number, BondInfo[]>) {
         }
     }
 
     export interface BondInfo {
         /** indexInto */
-        readonly indexB: number,
+        readonly indexB: StructureElement.UnitIndex,
         readonly order: number,
         readonly flag: LinkType.Flag
     }

+ 3 - 2
src/mol-model/structure/structure/unit/links/inter-compute.ts

@@ -13,6 +13,7 @@ import { InterUnitBonds } from './data';
 import { UniqueArray } from 'mol-data/generic';
 import { SortedArray } from 'mol-data/int';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import StructureElement from '../../element';
 
 const MAX_RADIUS = 4;
 
@@ -24,8 +25,8 @@ function addMapEntry<A, B>(map: Map<A, B[]>, a: A, b: B) {
 interface PairState {
     mapAB: Map<number, InterUnitBonds.BondInfo[]>,
     mapBA: Map<number, InterUnitBonds.BondInfo[]>,
-    bondedA: UniqueArray<number, number>,
-    bondedB: UniqueArray<number, number>
+    bondedA: UniqueArray<StructureElement.UnitIndex, StructureElement.UnitIndex>,
+    bondedB: UniqueArray<StructureElement.UnitIndex, StructureElement.UnitIndex>
 }
 
 function addLink(indexA: number, indexB: number, order: number, flag: LinkType.Flag, state: PairState) {

+ 96 - 37
src/mol-model/structure/structure/unit/rings.ts

@@ -8,64 +8,123 @@ import { computeRings, getFingerprint, createIndex } from './rings/compute'
 import Unit from '../unit';
 import StructureElement from '../element';
 import { SortedArray } from 'mol-data/int';
+import { ResidueIndex } from '../../model';
+import { ElementSymbol } from '../../model/types';
 
 type UnitRing = SortedArray<StructureElement.UnitIndex>
 
-interface UnitRings {
+class UnitRings {
     /** Each ring is specified as an array of indices in Unit.elements. */
-    readonly all: ReadonlyArray<UnitRing>,
-    readonly byFingerprint: ReadonlyMap<string, ReadonlyArray<UnitRings.Index>>,
+    readonly all: ReadonlyArray<UnitRing>;
 
-    readonly index: {
-        /** Maps atom index inside a Unit to the smallest ring index (an atom can be part of more than one ring) */
+    private _byFingerprint?: ReadonlyMap<UnitRing.Fingerprint, ReadonlyArray<UnitRings.Index>>;
+    private _index?: {
         readonly elementRingIndices: ReadonlyMap<StructureElement.UnitIndex, UnitRings.Index[]>,
-
-        /** Maps UnitRings.Index to index to ringComponents */
         readonly ringComponentIndex: ReadonlyArray<UnitRings.ComponentIndex>,
         readonly ringComponents: ReadonlyArray<ReadonlyArray<UnitRings.Index>>
+    };
+
+    private get index() {
+        if (this._index) return this._index;
+        this._index = createIndex(this.all);
+        return this._index;
+    }
+
+    get byFingerprint() {
+        if (this._byFingerprint) return this._byFingerprint;
+        this._byFingerprint = createByFingerprint(this.unit, this.all);
+        return this._byFingerprint;
+    }
+
+    /** Maps atom index inside a Unit to the smallest ring index (an atom can be part of more than one ring) */
+    get elementRingIndices() {
+        return this.index.elementRingIndices;
+    }
+
+    /** Maps UnitRings.Index to index to ringComponents */
+    get ringComponentIndex() {
+        return this.index.ringComponentIndex;
+    }
+
+    get ringComponents() {
+        return this.index.ringComponents;
+    }
+
+    constructor(all: ReadonlyArray<UnitRing>, public unit: Unit.Atomic) {
+        this.all = all;
     }
 }
 
-namespace UnitRings {
-    /** Index into UnitRings.all */
-    export type Index = { readonly '@type': 'unit-ring-index' } & number
-    export type ComponentIndex = { readonly '@type': 'unit-ring-component-index' } & number
+namespace UnitRing {
+    export type Fingerprint = { readonly '@type': 'unit-ring-fingerprint' } & string
 
-    export function getRingFingerprint(unit: Unit.Atomic, ring: UnitRing) {
+    export function fingerprint(unit: Unit.Atomic, ring: UnitRing): Fingerprint {
         const { elements } = unit;
         const { type_symbol } = unit.model.atomicHierarchy.atoms;
 
-        const symbols: string[] = [];
-        for (let i = 0, _i = ring.length; i < _i; i++) symbols[symbols.length] = type_symbol.value(elements[ring[i]]) as String as string;
-        return getFingerprint(symbols);
+        const symbols: ElementSymbol[] = [];
+        for (let i = 0, _i = ring.length; i < _i; i++) symbols[symbols.length] = type_symbol.value(elements[ring[i]]);
+        return elementFingerprint(symbols);
     }
 
+    export function elementFingerprint(elements: ArrayLike<ElementSymbol>) {
+        return getFingerprint(elements as ArrayLike<String> as string[]) as Fingerprint;
+    }
+}
+
+namespace UnitRings {
+    /** Index into UnitRings.all */
+    export type Index = { readonly '@type': 'unit-ring-index' } & number
+    export type ComponentIndex = { readonly '@type': 'unit-ring-component-index' } & number
+
     export function create(unit: Unit.Atomic): UnitRings {
         const rings = computeRings(unit);
+        return new UnitRings(rings, unit);
+    }
 
-        let _byFingerprint: Map<string, Index[]> | undefined = void 0;
-        let _index: UnitRings['index'] | undefined = void 0;
-        return {
-            all: rings,
-            get byFingerprint() {
-                if (_byFingerprint) return _byFingerprint;
-                _byFingerprint = new Map();
-                let idx = 0 as Index;
-                for (const r of rings) {
-                    const fp = getRingFingerprint(unit, r);
-                    if (_byFingerprint.has(fp)) _byFingerprint.get(fp)!.push(idx);
-                    else _byFingerprint.set(fp, [idx]);
-                    idx++;
-                }
-                return _byFingerprint;
-            },
-            get index() {
-                if (_index) return _index;
-                _index = createIndex(rings);
-                return _index;
-            }
-        };
+    /** Creates a mapping ResidueIndex -> list or rings that are on that residue and have one of the specified fingerprints. */
+    export function byFingerprintAndResidue(rings: UnitRings, fingerprints: ReadonlyArray<UnitRing.Fingerprint>) {
+        const map = new Map<ResidueIndex, Index[]>();
+        for (const fp of fingerprints) {
+            addSingleResidueRings(rings, fp, map);
+        }
+        return map;
     }
 }
 
+function createByFingerprint(unit: Unit.Atomic, rings: ReadonlyArray<UnitRing>) {
+    const byFingerprint = new Map<UnitRing.Fingerprint, UnitRings.Index[]>();
+    let idx = 0 as UnitRings.Index;
+    for (const r of rings) {
+        const fp = UnitRing.fingerprint(unit, r);
+        if (byFingerprint.has(fp)) byFingerprint.get(fp)!.push(idx);
+        else byFingerprint.set(fp, [idx]);
+        idx++;
+    }
+    return byFingerprint;
+}
+
+function ringResidueIdx(unit: Unit.Atomic, ring: ArrayLike<StructureElement.UnitIndex>): ResidueIndex {
+    const { elements } = unit;
+    const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
+    const idx = residueIndex[elements[ring[0]]];
+    for (let rI = 1, _rI = ring.length; rI < _rI; rI++) {
+        if (idx !== residueIndex[elements[ring[rI]]]) return -1 as ResidueIndex;
+    }
+    return idx;
+}
+
+function addSingleResidueRings(rings: UnitRings, fp: UnitRing.Fingerprint, map: Map<ResidueIndex, UnitRings.Index[]>) {
+    const byFp = rings.byFingerprint.get(fp);
+    if (!byFp) return;
+    for (const r of byFp) {
+        const idx = ringResidueIdx(rings.unit, rings.all[r]);
+        if (idx >= 0) {
+            if (map.has(idx)) map.get(idx)!.push(r);
+            else map.set(idx, [r]);
+        }
+    }
+}
+
+
 export { UnitRing, UnitRings }

+ 1 - 1
src/mol-model/structure/structure/unit/rings/compute.ts

@@ -249,7 +249,7 @@ function buildFinderprint(elements: string[], offset: number) {
 type RingIndex = import('../rings').UnitRings.Index
 type RingComponentIndex = import('../rings').UnitRings.ComponentIndex
 
-export function createIndex(rings: SortedArray<StructureElement.UnitIndex>[]) {
+export function createIndex(rings: ArrayLike<SortedArray<StructureElement.UnitIndex>>) {
     const elementRingIndices: Map<StructureElement.UnitIndex, RingIndex[]> = new Map();
 
     // for each ring atom, assign all rings that it is present in