diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index c04627418a078014a7f89f061b8f0d5bbde55fef..fbc466845b97f16694d61f36f8afffd2462e7219 100644 --- a/src/apps/structure-info/model.ts +++ b/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(', ')}`); diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts index 3a924335ea86458fea0e1d2cb89d2be92310bdee..a6a25607900a4ac9912f878f6ac5ddc9abf9541b 100644 --- a/src/mol-model/structure/structure.ts +++ b/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 } \ No newline at end of file +export { StructureElement, Link, Structure, Unit, StructureSymmetry, StructureProperties } +export * from './structure/unit/rings' \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 62ee0357f2ce9109f1897692b71e81b9c7e42cec..ab8c61fbafc358fb434ea785aa088fa2fc9b41fe 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/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')) { diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 48f429a29dc65e7f1d883669d7ea6d112635a57a..8ec99349308a246bf74320c2b517233d85999e8b 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/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; diff --git a/src/mol-model/structure/structure/unit/links/data.ts b/src/mol-model/structure/structure/unit/links/data.ts index ed046a026da9011b17f43602898b8c2f3e1fdf69..74ff5bd2d5aade0d8f93d0fed3bedf026ea75d63 100644 --- a/src/mol-model/structure/structure/unit/links/data.ts +++ b/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 } diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index 03184c454ff8674d8471d65f76c442721c166f30..cd8e37397544128778502efe23fa2b664c1009a1 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/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) { diff --git a/src/mol-model/structure/structure/unit/rings.ts b/src/mol-model/structure/structure/unit/rings.ts index 83640e22689d6f688704c2bb4866439ba6c1a04c..64f9f857cb0f736216ea46ed887bbd84ba41cbc9 100644 --- a/src/mol-model/structure/structure/unit/rings.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/rings/compute.ts b/src/mol-model/structure/structure/unit/rings/compute.ts index 5485b49809e1c075944a70ecadd08c54ad85f57f..4517fb879f826cd4934682f6ab247d5075084f11 100644 --- a/src/mol-model/structure/structure/unit/rings/compute.ts +++ b/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