|
@@ -4,105 +4,127 @@
|
|
|
* @author David Sehnal <david.sehnal@gmail.com>
|
|
|
*/
|
|
|
|
|
|
-import computeRings from './rings/compute'
|
|
|
+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';
|
|
|
|
|
|
-interface UnitRings {
|
|
|
- /** Each ring is specified as an array of indices in Unit.elements. */
|
|
|
- readonly all: ReadonlyArray<ReadonlyArray<StructureElement.UnitIndex>>,
|
|
|
- readonly byFingerprint: ReadonlyMap<string, ReadonlyArray<number>>
|
|
|
-}
|
|
|
+type UnitRing = SortedArray<StructureElement.UnitIndex>
|
|
|
|
|
|
-namespace UnitRings {
|
|
|
- export function getRingFingerprint(unit: Unit.Atomic, ring: ArrayLike<number>) {
|
|
|
- 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);
|
|
|
+class UnitRings {
|
|
|
+ /** Each ring is specified as an array of indices in Unit.elements. */
|
|
|
+ readonly all: ReadonlyArray<UnitRing>;
|
|
|
+
|
|
|
+ private _byFingerprint?: ReadonlyMap<UnitRing.Fingerprint, ReadonlyArray<UnitRings.Index>>;
|
|
|
+ private _index?: {
|
|
|
+ readonly elementRingIndices: ReadonlyMap<StructureElement.UnitIndex, UnitRings.Index[]>,
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
- export function create(unit: Unit.Atomic): UnitRings {
|
|
|
- const rings = computeRings(unit);
|
|
|
- const byFingerprint = new Map<string, number[]>();
|
|
|
-
|
|
|
- let idx = 0;
|
|
|
- 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++;
|
|
|
- }
|
|
|
+ get byFingerprint() {
|
|
|
+ if (this._byFingerprint) return this._byFingerprint;
|
|
|
+ this._byFingerprint = createByFingerprint(this.unit, this.all);
|
|
|
+ return this._byFingerprint;
|
|
|
+ }
|
|
|
|
|
|
- return { all: rings, 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;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-export { UnitRings }
|
|
|
+ /** Maps UnitRings.Index to index to ringComponents */
|
|
|
+ get ringComponentIndex() {
|
|
|
+ return this.index.ringComponentIndex;
|
|
|
+ }
|
|
|
|
|
|
-function getFingerprint(elements: string[]) {
|
|
|
- const len = elements.length;
|
|
|
- const reversed: string[] = new Array(len);
|
|
|
+ get ringComponents() {
|
|
|
+ return this.index.ringComponents;
|
|
|
+ }
|
|
|
|
|
|
- for (let i = 0; i < len; i++) reversed[i] = elements[len - i - 1];
|
|
|
+ constructor(all: ReadonlyArray<UnitRing>, public unit: Unit.Atomic) {
|
|
|
+ this.all = all;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- const rotNormal = getMinimalRotation(elements);
|
|
|
- const rotReversed = getMinimalRotation(reversed);
|
|
|
+namespace UnitRing {
|
|
|
+ export type Fingerprint = { readonly '@type': 'unit-ring-fingerprint' } & string
|
|
|
|
|
|
- let isNormalSmaller = false;
|
|
|
+ export function fingerprint(unit: Unit.Atomic, ring: UnitRing): Fingerprint {
|
|
|
+ const { elements } = unit;
|
|
|
+ const { type_symbol } = unit.model.atomicHierarchy.atoms;
|
|
|
|
|
|
- for (let i = 0; i < len; i++) {
|
|
|
- const u = elements[(i + rotNormal) % len], v = reversed[(i + rotReversed) % len];
|
|
|
- if (u !== v) {
|
|
|
- isNormalSmaller = u < v;
|
|
|
- break;
|
|
|
- }
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
- if (isNormalSmaller) return buildFinderprint(elements, rotNormal);
|
|
|
- return buildFinderprint(reversed, rotReversed);
|
|
|
+ export function elementFingerprint(elements: ArrayLike<ElementSymbol>) {
|
|
|
+ return getFingerprint(elements as ArrayLike<String> as string[]) as Fingerprint;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function getMinimalRotation(elements: string[]) {
|
|
|
- // adapted from http://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation
|
|
|
-
|
|
|
- const len = elements.length;
|
|
|
- const f = new Int32Array(len * 2);
|
|
|
- for (let i = 0; i < f.length; i++) f[i] = -1;
|
|
|
+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
|
|
|
|
|
|
- let u = '', v = '', k = 0;
|
|
|
+ export function create(unit: Unit.Atomic): UnitRings {
|
|
|
+ const rings = computeRings(unit);
|
|
|
+ return new UnitRings(rings, unit);
|
|
|
+ }
|
|
|
|
|
|
- for (let j = 1; j < f.length; j++) {
|
|
|
- let i = f[j - k - 1];
|
|
|
- while (i !== -1) {
|
|
|
- u = elements[j % len]; v = elements[(k + i + 1) % len];
|
|
|
- if (u === v) break;
|
|
|
- if (u < v) k = j - i - 1;
|
|
|
- i = f[i];
|
|
|
+ /** 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;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- if (i === -1) {
|
|
|
- u = elements[j % len]; v = elements[(k + i + 1) % len];
|
|
|
- if (u !== v) {
|
|
|
- if (u < v) k = j;
|
|
|
- f[j - k] = -1;
|
|
|
- } else f[j - k] = i + 1;
|
|
|
- } else f[j - k] = i + 1;
|
|
|
+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;
|
|
|
+}
|
|
|
|
|
|
- return k;
|
|
|
+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 buildFinderprint(elements: string[], offset: number) {
|
|
|
- const len = elements.length;
|
|
|
- const ret: string[] = [];
|
|
|
- let i;
|
|
|
- for (i = 0; i < len - 1; i++) {
|
|
|
- ret.push(elements[(i + offset) % len]);
|
|
|
- ret.push('-');
|
|
|
+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]);
|
|
|
+ }
|
|
|
}
|
|
|
- ret.push(elements[(i + offset) % len]);
|
|
|
- return ret.join('');
|
|
|
-}
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+export { UnitRing, UnitRings }
|