diff --git a/README.md b/README.md index 0ac60a18d27686a1c9e0550cdd91e84b653c6de9..c7a0e683f1167095371c524669fdecb88be37d88 100644 --- a/README.md +++ b/README.md @@ -51,17 +51,34 @@ This project builds on experience from previous solutions: npm run watch-extra ### Build/watch mol-viewer -Build: +**Build** npm run build npm run build-viewer -Watch: +**Watch** npm run watch npm run watch-extra npm run watch-viewer +**Run** + +If not installed previously: + + npm install -g http-server + +...or a similar solution. + +From the root of the project: + + http-server -p PORT-NUMBER + +and navigate to `build/viewer` + + + + ## Contributing Just open an issue or make a pull request. All contributions are welcome. diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 9c7f0d9a9876f39c78cd5887f9696ef377c1ea4b..c06c66e4eeb80a6655db82a9511f0a6c1c1cdcde 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -72,20 +72,44 @@ export function printSecStructure(model: Model) { } } -export function printBonds(structure: Structure) { - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; +export function printLinks(structure: Structure, showIntra: boolean, showInter: boolean) { + if (showIntra) { + console.log('\nIntra Unit Links\n============='); + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + + const elements = unit.elements; + const { a, b } = unit.links; + const { model } = unit; + + if (!a.length) continue; + + for (let bI = 0, _bI = a.length; bI < _bI; bI++) { + const x = a[bI], y = b[bI]; + if (x >= y) continue; + console.log(`${atomLabel(model, elements[x])} -- ${atomLabel(model, elements[y])}`); + } + } + } - const elements = unit.elements; - const { a, b } = unit.bonds; - const { model } = unit; + if (showInter) { + console.log('\nInter Unit Links\n============='); + const links = structure.links; + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; - if (!a.length) continue; + for (const pairLinks of links.getLinkedUnits(unit)) { + if (!pairLinks.areUnitsOrdered || pairLinks.bondCount === 0) continue; - for (let bI = 0, _bI = a.length; bI < _bI; bI++) { - const x = a[bI], y = b[bI]; - if (x >= y) continue; - console.log(`${atomLabel(model, elements[x])} -- ${atomLabel(model, elements[y])}`); + const { unitA, unitB } = pairLinks; + console.log(`${pairLinks.unitA.id} - ${pairLinks.unitB.id}: ${pairLinks.bondCount} bond(s)`); + + for (const aI of pairLinks.linkedElementIndices) { + for (const link of pairLinks.getBonds(aI)) { + console.log(`${atomLabel(unitA.model, unitA.elements[aI])} -- ${atomLabel(unitB.model, unitB.elements[link.indexB])}`); + } + } + } } } } @@ -101,6 +125,18 @@ export function printSequence(model: Model) { console.log(); } +export function printModRes(model: Model) { + console.log('\nModified Residues\n============='); + const map = model.properties.modifiedResidueNameMap; + const { label_comp_id, _rowCount } = model.atomicHierarchy.residues; + for (let i = 0; i < _rowCount; i++) { + const comp_id = label_comp_id.value(i); + if (!map.has(comp_id)) continue; + console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`); + } + console.log(); +} + export function printRings(structure: Structure) { console.log('\nRings\n============='); for (const unit of structure.units) { @@ -156,11 +192,12 @@ export function printIHMModels(model: Model) { async function run(mmcif: mmCIF_Database) { const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run(); const structure = Structure.ofModel(models[0]); - printSequence(models[0]); + //printSequence(models[0]); //printIHMModels(models[0]); printUnits(structure); - printRings(structure); - //printBonds(structure); + //printRings(structure); + printLinks(structure, true, true); + //printModRes(models[0]); //printSecStructure(models[0]); } @@ -175,13 +212,13 @@ async function runFile(filename: string) { } const parser = new argparse.ArgumentParser({ - addHelp: true, - description: 'Print info about a structure, mainly to test and showcase the mol-model module' + addHelp: true, + description: 'Print info about a structure, mainly to test and showcase the mol-model module' }); -parser.addArgument([ '--download', '-d' ], { +parser.addArgument(['--download', '-d'], { help: 'Pdb entry id' }); -parser.addArgument([ '--file', '-f' ], { +parser.addArgument(['--file', '-f'], { help: 'filename' }); interface Args { diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx index f828e597af544499d74c9c85afc8dea6e4bd7fd7..b4d3823d0bc0d8f2fe473fc73cf2e33b5b4ba2f6 100644 --- a/src/apps/viewer/index.tsx +++ b/src/apps/viewer/index.tsx @@ -22,6 +22,7 @@ import { EntityTree } from 'mol-app/ui/entity/tree'; import { EntityTreeController } from 'mol-app/controller/entity/tree'; import { TransformListController } from 'mol-app/controller/transform/list'; import { TransformList } from 'mol-app/ui/transform/list'; +import { SequenceView } from 'mol-app/ui/visualization/sequence-view'; const elm = document.getElementById('app') if (!elm) throw new Error('Can not find element with id "app".') @@ -45,6 +46,14 @@ targets[LayoutRegion.Bottom].components.push({ isStatic: true }); +targets[LayoutRegion.Top].components.push({ + key: 'molstar-sequence-view', + controller: ctx.components.sequenceView, + region: LayoutRegion.Top, + view: SequenceView, + isStatic: true +}); + targets[LayoutRegion.Main].components.push({ key: 'molstar-background-jobs', controller: new JobsController(ctx, 'Background'), diff --git a/src/helpers.d.ts b/src/helpers.d.ts index 3dbdd2dc79bbbf6f3878bba7ea672785785c6490..69dca25924d4e2fc6dd927672674506e1ad935ff 100644 --- a/src/helpers.d.ts +++ b/src/helpers.d.ts @@ -13,4 +13,5 @@ declare module Helpers { export type NumberArray = TypedArray | number[] export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[] export type ValueOf<T> = T[keyof T] + export type ArrayCtor<T> = { new(size: number): { [i: number]: T, length: number } } } \ No newline at end of file diff --git a/src/mol-app/context/context.ts b/src/mol-app/context/context.ts index 92a317aad2dc583dcf9f1f9051a6295c26536596..5c8fee555e120226afad3fc1ecf5e58c0ea95655 100644 --- a/src/mol-app/context/context.ts +++ b/src/mol-app/context/context.ts @@ -15,6 +15,7 @@ import { Stage } from 'mol-view/stage'; import { AnyTransform } from 'mol-view/state/transform'; import { BehaviorSubject } from 'rxjs'; import { AnyEntity } from 'mol-view/state/entity'; +import { SequenceViewController } from '../controller/visualization/sequence-view'; export class Settings { private settings = new Map<string, any>(); @@ -35,11 +36,16 @@ export class Context { logger = new Logger(this); performance = new PerformanceMonitor(); - stage = new Stage(); + stage = new Stage(this); viewport = new ViewportController(this); layout: LayoutController; settings = new Settings(); + // TODO: this is a temporary solution + components = { + sequenceView: new SequenceViewController(this) + }; + currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined> currentTransforms = new BehaviorSubject([] as AnyTransform[]) diff --git a/src/mol-app/controller/visualization/sequence-view.ts b/src/mol-app/controller/visualization/sequence-view.ts new file mode 100644 index 0000000000000000000000000000000000000000..17423f9806beef2b22a3eb3c4a4117eed3bf1534 --- /dev/null +++ b/src/mol-app/controller/visualization/sequence-view.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { shallowClone } from 'mol-util'; +import { Context } from '../../context/context' +import { Controller } from '../controller'; +import { Structure } from 'mol-model/structure'; + +export const DefaultSequenceViewState = { + structure: void 0 as (Structure | undefined) +} +export type SequenceViewState = typeof DefaultSequenceViewState + +export class SequenceViewController extends Controller<SequenceViewState> { + constructor(context: Context) { + super(context, shallowClone(DefaultSequenceViewState)); + } +} \ No newline at end of file diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts index 99cf366f9c83e66e05af729e3224d74ec7f252de..b87ea5c14eec10f8c2a7ff7161cd569221611bde 100644 --- a/src/mol-app/event/basic.ts +++ b/src/mol-app/event/basic.ts @@ -11,6 +11,7 @@ import { Dispatcher } from '../service/dispatcher' import { LayoutState } from '../controller/layout'; import { ViewportOptions } from '../controller/visualization/viewport'; import { Job } from '../service/job'; +import { Element } from 'mol-model/structure' const Lane = Dispatcher.Lane; @@ -31,3 +32,7 @@ export namespace LayoutEvents { export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow); export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow); } + +export namespace InteractivityEvents { + export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow); +} diff --git a/src/mol-app/skin/components/sequence-view.scss b/src/mol-app/skin/components/sequence-view.scss new file mode 100644 index 0000000000000000000000000000000000000000..b8bf891675cec72eb8cb18bdd57e5fd6d63a4c7f --- /dev/null +++ b/src/mol-app/skin/components/sequence-view.scss @@ -0,0 +1,9 @@ +.molstar-sequence-view-wrap { + position: absolute; + right: 0; + top: 0; + left: 0; + bottom: 0; + overflow: hidden; + overflow-x: scroll; +} \ No newline at end of file diff --git a/src/mol-app/skin/layout/common.scss b/src/mol-app/skin/layout/common.scss index b84b69214b3468b2d165104c9a20c6ff0cf542c3..219de13b1086c2c6ecd7d3ac1d27b5007b1e97c8 100644 --- a/src/mol-app/skin/layout/common.scss +++ b/src/mol-app/skin/layout/common.scss @@ -23,7 +23,7 @@ overflow: hidden; } -.molstar-layout-main, .molstar-layout-bottom { +.molstar-layout-main, .molstar-layout-bottom, .molstar-layout-top { .molstar-layout-static { left: 0; right: 0; diff --git a/src/mol-app/skin/ui.scss b/src/mol-app/skin/ui.scss index 41e78fc7465ee379df02eb3565bcac082c6c4584..cd572232a6843ab134fca6b60e97568d8a4730ee 100644 --- a/src/mol-app/skin/ui.scss +++ b/src/mol-app/skin/ui.scss @@ -35,4 +35,5 @@ @import 'components/misc'; @import 'components/panel'; @import 'components/slider'; -@import 'components/viewport'; \ No newline at end of file +@import 'components/viewport'; +@import 'components/sequence-view'; \ No newline at end of file diff --git a/src/mol-app/ui/visualization/sequence-view.tsx b/src/mol-app/ui/visualization/sequence-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..21bef90b8afe67226ea4cf62b799778664043b73 --- /dev/null +++ b/src/mol-app/ui/visualization/sequence-view.tsx @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as React from 'react' +import { View } from '../view'; +import { SequenceViewController } from '../../controller/visualization/sequence-view'; +import { Structure, StructureSequence, Queries, Selection } from 'mol-model/structure'; +import { Context } from '../../context/context'; +import { InteractivityEvents } from '../../event/basic'; +import { SyncRuntimeContext } from 'mol-task/execution/synchronous'; + +export class SequenceView extends View<SequenceViewController, {}, {}> { + render() { + const s = this.controller.latestState.structure; + if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>; + + const seqs = Structure.getModels(s)[0].sequence.sequences; + return <div className='molstar-sequence-view-wrap'> + {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )} + </div>; + } +} + +function createQuery(entityId: string, label_seq_id: number) { + return Queries.generators.atoms({ + entityTest: l => Queries.props.entity.id(l) === entityId, + residueTest: l => Queries.props.residue.label_seq_id(l) === label_seq_id + }); +} + +// TODO: this is really ineffective and should be done using a canvas. +class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> { + + async raiseInteractityEvent(seqId?: number) { + if (typeof seqId === 'undefined') { + InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); + return; + } + + const query = createQuery(this.props.seq.entityId, seqId); + const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext)); + if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); + else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci); + } + + + render() { + const { ctx, seq } = this.props; + const { offset, sequence } = seq.sequence; + + const elems: JSX.Element[] = []; + for (let i = 0, _i = sequence.length; i < _i; i++) { + elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />; + } + + return <div style={{ wordWrap: 'break-word' }}> + <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset} </span> + {elems} + </div>; + } +} + +class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> { + state = { isHighlighted: false } + + mouseEnter = () => { + this.setState({ isHighlighted: true }); + this.props.parent.raiseInteractityEvent(this.props.seqId); + } + + mouseLeave = () => { + this.setState({ isHighlighted: false }); + this.props.parent.raiseInteractityEvent(); + } + + render() { + return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} + style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}> + {this.props.letter} + </span>; + } +} \ No newline at end of file diff --git a/src/mol-data/int/_spec/sorted-array.spec.ts b/src/mol-data/int/_spec/sorted-array.spec.ts index 2cd82af32ce9046bb8c2b7a3a71779ea68084071..c5ca33cf506892bc90d9da503a241d0fc0f2bfe4 100644 --- a/src/mol-data/int/_spec/sorted-array.spec.ts +++ b/src/mol-data/int/_spec/sorted-array.spec.ts @@ -59,6 +59,10 @@ describe('sortedArray', () => { compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]); }); + it('indicesOf', () => { + compareArrays(SortedArray.indicesOf(SortedArray.ofSortedArray([10, 11, 12]), SortedArray.ofSortedArray([10, 12, 14])), [0, 2]); + }) + // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3))) // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3))) }); \ No newline at end of file diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index 4a5476c16c46de1194639194442ba6b904339a81..09b787726d0f33f11fe4422525c18c56948e2ce2 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/src/mol-data/int/impl/sorted-array.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { sortArray, hash3, hash4 } from '../../util' +import { sortArray, hash3, hash4, createRangeArray } from '../../util' import Interval from '../interval' type Nums = ArrayLike<number> @@ -289,6 +289,39 @@ export function deduplicate(xs: Nums) { return ret; } +export function indicesOf(a: Nums, b: Nums): Nums { + if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1)); + + const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = a.length; + // no common elements + if (!commonCount) return Empty; + // A is subset of B ==> A + if (commonCount === lenA) return ofSortedArray(createRangeArray(0, a.length - 1)); + + const indices = new Int32Array(commonCount); + let offset = 0; + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { indices[offset++] = i; i++; j++; } + } + + return ofSortedArray(indices); +} + const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 }; // for small sets, just gets the whole range, for large sets does a bunch of binary searches function getSuitableIntersectionRange(a: Nums, b: Nums) { diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index 928e383ddf1a083f9a652428131074e4d5a8c735..2c92aa329d3dc16ebd1e1bdfd977b11426d9db09 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/src/mol-data/int/sorted-array.ts @@ -41,7 +41,9 @@ namespace SortedArray { export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any; export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any; - export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any; + export const deduplicate: (array: SortedArray) => SortedArray = Impl.deduplicate as any; + /** Returns indices of xs in the array. E.g. indicesOf([10, 11, 12], [10, 12]) ==> [0, 2] */ + export const indicesOf: (array: SortedArray, xs: SortedArray) => SortedArray = Impl.indicesOf as any; } interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' } diff --git a/src/mol-data/util/array.ts b/src/mol-data/util/array.ts index 93707e7978d657144d2b487a4c665c5f8eaf0198..94311c38c2ad057768411f1c65c9762244c341e5 100644 --- a/src/mol-data/util/array.ts +++ b/src/mol-data/util/array.ts @@ -22,4 +22,28 @@ export function iterableToArray<T>(it: IterableIterator<T>): T[] { ret[ret.length] = value; } return ret; +} + +/** Fills the array so that array[0] = start and array[array.length - 1] = end */ +export function createRangeArray(start: number, end: number, ctor?: Helpers.ArrayCtor<number>) { + const len = end - start + 1; + const array = ctor ? new ctor(len) : new Int32Array(len); + for (let i = 0; i < len; i++) { + array[i] = i + start; + } + return array; +} + +export function arrayPickIndices<T>(array: ArrayLike<T>, indices: ArrayLike<number>) { + const ret = new (arrayGetCtor(array))(indices.length); + for (let i = 0, _i = indices.length; i < _i; i++) { + ret[i] = array[indices[i]]; + } + return ret; +} + +export function arrayGetCtor<T>(data: ArrayLike<T>): Helpers.ArrayCtor<T> { + const ret = (data as any).constructor; + if (!ret) throw new Error('data does not define a constructor and it should'); + return ret; } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts index 28327d08dbf9b90a64c4e2f610196030a56efacd..492731141fe21febd63d55f474b82e5d77985818 100644 --- a/src/mol-geo/representation/structure/bond.ts +++ b/src/mol-geo/representation/structure/bond.ts @@ -10,7 +10,7 @@ import { ValueCell } from 'mol-util/value-cell' import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' -import { Unit, Element, Bond } from 'mol-model/structure'; +import { Unit, Element, Link } from 'mol-model/structure'; import { UnitsRepresentation, DefaultStructureProps } from './index'; import { Task } from 'mol-task' import { createTransforms } from './utils'; @@ -31,7 +31,7 @@ function createBondMesh(unit: Unit, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) const elements = unit.elements; - const bonds = unit.bonds + const bonds = unit.links const { edgeCount, a, b } = bonds if (!edgeCount) return Mesh.createEmpty(mesh) @@ -98,7 +98,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps currentGroup = group const unit = group.units[0] - const elementCount = Unit.isAtomic(unit) ? unit.bonds.edgeCount * 2 : 0 + const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0 const instanceCount = group.units.length mesh = await createBondMesh(unit).runAsChild(ctx, 'Computing bond mesh') @@ -167,11 +167,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps const { objectId, instanceId, elementId } = pickingId const unit = currentGroup.units[instanceId] if (cylinders.id === objectId && Unit.isAtomic(unit)) { - return Bond.Loci([{ + return Link.Loci([{ aUnit: unit, - aIndex: unit.bonds.a[elementId], + aIndex: unit.links.a[elementId], bUnit: unit, - bIndex: unit.bonds.b[elementId] + bIndex: unit.links.b[elementId] }]) } return null @@ -182,7 +182,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps const unit = group.units[0] if (!Unit.isAtomic(unit)) return - const elementCount = unit.bonds.edgeCount * 2 + const elementCount = unit.links.edgeCount * 2 const instanceCount = group.units.length let changed = false @@ -190,11 +190,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps if (isEveryLoci(loci)) { applyMarkerAction(array, 0, elementCount * instanceCount, action) changed = true - } else if (Bond.isLoci(loci)) { - for (const b of loci.bonds) { + } else if (Link.isLoci(loci)) { + for (const b of loci.links) { const unitIdx = Unit.findUnitById(b.aUnit.id, group.units) if (unitIdx !== -1) { - const _idx = unit.bonds.getEdgeIndex(b.aIndex, b.bIndex) + const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex) if (_idx !== -1) { const idx = _idx if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { diff --git a/src/mol-math/graph.ts b/src/mol-math/graph.ts index ce0429662fe04ce07a6a24a56e8a25c188bb4cf1..f1e24c2f39e5cbaef4ae6e7c6f8e5d1ed5dc9f05 100644 --- a/src/mol-math/graph.ts +++ b/src/mol-math/graph.ts @@ -4,4 +4,4 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -export * from './graph/int/graph' \ No newline at end of file +export * from './graph/int-adjacency-graph' \ No newline at end of file diff --git a/src/mol-math/graph/_spec/int-graph.spec.ts b/src/mol-math/graph/_spec/int-graph.spec.ts index 3d75dddb0296d749b60261249cfd666ddb1d8a35..5d0315e658ef3e3662a08149f8d10b18cfe770ba 100644 --- a/src/mol-math/graph/_spec/int-graph.spec.ts +++ b/src/mol-math/graph/_spec/int-graph.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { IntGraph } from '../int/graph'; +import { IntAdjacencyGraph } from '../int-adjacency-graph'; describe('IntGraph', () => { const vc = 3; @@ -12,7 +12,7 @@ describe('IntGraph', () => { const ys = [1, 2, 0]; const _prop = [10, 11, 12]; - const builder = new IntGraph.EdgeBuilder(vc, xs, ys); + const builder = new IntAdjacencyGraph.EdgeBuilder(vc, xs, ys); const prop: number[] = new Array(builder.slotCount); for (let i = 0; i < builder.edgeCount; i++) { builder.addNextEdge(); @@ -28,9 +28,16 @@ describe('IntGraph', () => { }); it('triangle-propAndEdgeIndex', () => { - const prop = graph.prop; + const prop = graph.edgeProps.prop; expect(prop[graph.getEdgeIndex(0, 1)]).toBe(10); expect(prop[graph.getEdgeIndex(1, 2)]).toBe(11); expect(prop[graph.getEdgeIndex(2, 0)]).toBe(12); }); + + it('induce', () => { + const induced = IntAdjacencyGraph.induceByVertices(graph, [1, 2]); + expect(induced.vertexCount).toBe(2); + expect(induced.edgeCount).toBe(1); + expect(induced.edgeProps.prop[induced.getEdgeIndex(0, 1)]).toBe(11); + }) }); \ No newline at end of file diff --git a/src/mol-math/graph/int/graph.ts b/src/mol-math/graph/int-adjacency-graph.ts similarity index 60% rename from src/mol-math/graph/int/graph.ts rename to src/mol-math/graph/int-adjacency-graph.ts index bc2666a2c064bca1f63193ee27709df9019b3e8c..8b5af369dbd1e71e6e166311c67d61bb11036d77 100644 --- a/src/mol-math/graph/int/graph.ts +++ b/src/mol-math/graph/int-adjacency-graph.ts @@ -4,6 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +import { arrayPickIndices } from 'mol-data/util'; + /** * Represent a graph using vertex adjacency list. * @@ -12,12 +14,13 @@ * * Edge properties are indexed same as in the arrays a and b. */ -type IntGraph<EdgeProperties extends object = { }> = { +interface IntAdjacencyGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}> { readonly offset: ArrayLike<number>, readonly a: ArrayLike<number>, readonly b: ArrayLike<number>, readonly vertexCount: number, readonly edgeCount: number, + readonly edgeProps: Readonly<EdgeProps> /** * Get the edge index between i-th and j-th vertex. @@ -28,11 +31,14 @@ type IntGraph<EdgeProperties extends object = { }> = { */ getEdgeIndex(i: number, j: number): number, getVertexEdgeCount(i: number): number -} & EdgeProperties +} -namespace IntGraph { - class Impl implements IntGraph<any> { +namespace IntAdjacencyGraph { + export type EdgePropsBase = { [name: string]: ArrayLike<any> } + + class IntGraphImpl implements IntAdjacencyGraph<any> { readonly vertexCount: number; + readonly edgeProps: object; getEdgeIndex(i: number, j: number): number { let a, b; @@ -48,18 +54,14 @@ namespace IntGraph { return this.offset[i + 1] - this.offset[i]; } - constructor(public offset: ArrayLike<number>, public a: ArrayLike<number>, public b: ArrayLike<number>, public edgeCount: number, props?: any) { + constructor(public offset: ArrayLike<number>, public a: ArrayLike<number>, public b: ArrayLike<number>, public edgeCount: number, edgeProps?: any) { this.vertexCount = offset.length - 1; - if (props) { - for (const p of Object.keys(props)) { - (this as any)[p] = props[p]; - } - } + this.edgeProps = edgeProps || {}; } } - export function create<EdgeProps extends object = { }>(offset: ArrayLike<number>, a: ArrayLike<number>, b: ArrayLike<number>, edgeCount: number, edgeProps?: EdgeProps): IntGraph<EdgeProps> { - return new Impl(offset, a, b, edgeCount, edgeProps) as IntGraph<EdgeProps>; + export function create<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}>(offset: ArrayLike<number>, a: ArrayLike<number>, b: ArrayLike<number>, edgeCount: number, edgeProps?: EdgeProps): IntAdjacencyGraph<EdgeProps> { + return new IntGraphImpl(offset, a, b, edgeCount, edgeProps) as IntAdjacencyGraph<EdgeProps>; } export class EdgeBuilder { @@ -75,7 +77,7 @@ namespace IntGraph { a: Int32Array; b: Int32Array; - createGraph<EdgeProps extends object = { }>(edgeProps?: EdgeProps) { + createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}>(edgeProps?: EdgeProps) { return create(this.offsets, this.a, this.b, this.edgeCount, edgeProps); } @@ -132,6 +134,47 @@ namespace IntGraph { this.b = new Int32Array(offset); } } + + export function induceByVertices<P extends IntAdjacencyGraph.EdgePropsBase>(graph: IntAdjacencyGraph<P>, vertexIndices: ArrayLike<number>): IntAdjacencyGraph<P> { + const { b, offset, vertexCount, edgeProps } = graph; + const vertexMap = new Int32Array(vertexCount); + for (let i = 0, _i = vertexIndices.length; i < _i; i++) vertexMap[vertexIndices[i]] = i + 1; + + let newEdgeCount = 0; + for (let i = 0; i < vertexCount; i++) { + if (vertexMap[i] === 0) continue; + for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) { + if (b[j] > i && vertexMap[b[j]] !== 0) newEdgeCount++; + } + } + + const newOffsets = new Int32Array(vertexIndices.length + 1); + const edgeIndices = new Int32Array(2 * newEdgeCount); + const newA = new Int32Array(2 * newEdgeCount); + const newB = new Int32Array(2 * newEdgeCount); + let eo = 0, vo = 0; + for (let i = 0; i < vertexCount; i++) { + if (vertexMap[i] === 0) continue; + const aa = vertexMap[i] - 1; + for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) { + const bb = vertexMap[b[j]]; + if (bb === 0) continue; + + newA[eo] = aa; + newB[eo] = bb - 1; + edgeIndices[eo] = j; + eo++; + } + newOffsets[++vo] = eo; + } + + const newEdgeProps: P = {} as any; + for (const key of Object.keys(edgeProps)) { + newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices); + } + + return create(newOffsets, newA, newB, newEdgeCount, newEdgeProps); + } } -export { IntGraph } \ No newline at end of file +export { IntAdjacencyGraph } \ No newline at end of file diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index 3d10cb93063593a236d649b6a2911b492c00d300..586ee66d308c5835340cb233bcfa78125f32487b 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -5,7 +5,7 @@ */ import { Element } from './structure' -import { Bond } from './structure/structure/unit/bonds' +import { Link } from './structure/structure/unit/links' /** A Loci that includes every loci */ export const EveryLoci = { kind: 'every-loci' as 'every-loci' } @@ -14,4 +14,4 @@ export function isEveryLoci(x: any): x is EveryLoci { return !!x && x.kind === 'every-loci'; } -export type Loci = Element.Loci | Bond.Loci | EveryLoci \ No newline at end of file +export type Loci = Element.Loci | Link.Loci | EveryLoci \ No newline at end of file diff --git a/src/mol-model/sequence/constants.ts b/src/mol-model/sequence/constants.ts index 192d137f5bbd93804bbcdfe509d8f4cf20aacc02..17796f5996821e804b370738c42af8c7facd73d4 100644 --- a/src/mol-model/sequence/constants.ts +++ b/src/mol-model/sequence/constants.ts @@ -7,10 +7,12 @@ export type AminoAlphabet = | 'H' | 'R' | 'K' | 'I' | 'F' | 'L' | 'W' | 'A' | 'M' | 'P' | 'C' | 'N' | 'V' | 'G' | 'S' | 'Q' | 'Y' | 'D' | 'E' | 'T' | 'U' | 'O' | 'X' /** = Unknown */ + | '-' /** = Gap */ export type NuclecicAlphabet = | 'A' | 'C' | 'G' | 'T' | 'U' | 'X' /** = Unknown */ + | '-' /** = Gap */ // from NGL const ProteinOneLetterCodes: { [name: string]: AminoAlphabet } = { diff --git a/src/mol-model/sequence/sequence.ts b/src/mol-model/sequence/sequence.ts index 7f99ca54ce82ad570fb6f3d76e24d49f730b23f4..9401390e5b74d3efe3887f3959c9bc74864bbbf8 100644 --- a/src/mol-model/sequence/sequence.ts +++ b/src/mol-model/sequence/sequence.ts @@ -29,7 +29,7 @@ namespace Sequence { export interface Protein extends Base<Kind.Protein, AminoAlphabet> { } export interface RNA extends Base<Kind.RNA, NuclecicAlphabet> { } export interface DNA extends Base<Kind.DNA, NuclecicAlphabet> { } - export interface Generic extends Base<Kind.Generic, 'X'> { } + export interface Generic extends Base<Kind.Generic, 'X' | '-'> { } export function create(kind: Kind, sequence: string, offset: number = 0): Sequence { return { kind: kind as any, sequence: sequence as any, offset }; @@ -49,11 +49,21 @@ namespace Sequence { return { kind: Kind.Generic, code: (v: string) => 'X' }; } - export function ofResidueNames(residueName: Column<string>, seqId: Column<number>): Sequence { + function modCode(code: (name: string) => string, map: Map<string, string>): (name: string) => string { + return n => { + const ret = code(n); + if (ret !== 'X' || !map.has(n)) return ret; + return code(map.get(n)!); + } + } + + export function ofResidueNames(residueName: Column<string>, seqId: Column<number>, modifiedMap?: Map<string, string>): Sequence { if (seqId.rowCount === 0) throw new Error('cannot be empty'); const { kind, code } = determineKind(residueName); - return new Impl(kind, residueName, seqId, code) as Sequence; + + if (!modifiedMap || modifiedMap.size === 0) return new Impl(kind, residueName, seqId, code) as Sequence; + return new Impl(kind, residueName, seqId, modCode(code, modifiedMap)) as Sequence; } class Impl implements Base<any, any> { @@ -83,7 +93,7 @@ namespace Sequence { const count = maxSeqId - minSeqId + 1; const sequenceArray = new Array(maxSeqId + 1); for (let i = 0; i < count; i++) { - sequenceArray[i] = 'X'; + sequenceArray[i] = '-'; } for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) { diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts index 1704ab09282eb09ec1e30a442532e52dc80def63..0551a4c2098ebf6cb5ec74367fb37905d55b0500 100644 --- a/src/mol-model/structure/model.ts +++ b/src/mol-model/structure/model.ts @@ -8,5 +8,6 @@ import Model from './model/model' import * as Types from './model/types' import Format from './model/format' import { ModelSymmetry } from './model/properties/symmetry' +import StructureSequence from './model/properties/sequence' -export { Model, Types, Format, ModelSymmetry } \ No newline at end of file +export { Model, Types, Format, ModelSymmetry, StructureSequence } \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index a50d293112e9e2316c7f8f4afa0b344d4652790d..a5c8c500844863e4aef5c76f525e2efb80fbd8ef 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -39,11 +39,12 @@ function findHierarchyOffsets({ data }: mmCIF_Format, bounds: Interval) { const start = Interval.start(bounds), end = Interval.end(bounds); const residues = [start], chains = [start]; - const { label_entity_id, auth_asym_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = data.atom_site; + const { label_entity_id, label_asym_id, label_seq_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = data.atom_site; for (let i = start + 1; i < end; i++) { - const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !auth_asym_id.areValuesEqual(i - 1, i); + const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !label_asym_id.areValuesEqual(i - 1, i); const newResidue = newChain + || !label_seq_id.areValuesEqual(i - 1, i) || !auth_seq_id.areValuesEqual(i - 1, i) || !pdbx_PDB_ins_code.areValuesEqual(i - 1, i) || !label_comp_id.areValuesEqual(i - 1, i); @@ -118,6 +119,19 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) { && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>) } +function modResMap(format: mmCIF_Format) { + const data = format.data.pdbx_struct_mod_residue; + const map = new Map<string, string>(); + const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id; + const parent_id = data.parent_comp_id; + + for (let i = 0; i < data._rowCount; i++) { + map.set(comp_id.value(i), parent_id.value(i)); + } + + return map; +} + function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): Model { const hierarchyOffsets = findHierarchyOffsets(format, bounds); const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets); @@ -146,6 +160,8 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): ? format.data.entry.id.value(0) : format.data._name; + const modifiedResidueNameMap = modResMap(format); + return { id: UUID.create(), label, @@ -153,12 +169,13 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)), entities, atomicHierarchy, - sequence: getSequence(format.data, entities, atomicHierarchy), + sequence: getSequence(format.data, entities, atomicHierarchy, modifiedResidueNameMap), atomicConformation: getConformation(format, bounds), coarseHierarchy: coarse.hierarchy, coarseConformation: coarse.conformation, properties: { - secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy) + secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy), + modifiedResidueNameMap }, symmetry: getSymmetry(format) }; diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts index 89abf70d400bf0da529f3841d875bbfbd83a968f..4eb62528687553ac5ba1ff9afb74d2ea0d31afa2 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds.ts +++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts @@ -6,72 +6,80 @@ */ import Model from '../../model' -import { BondType } from '../../types' +import { LinkType } from '../../types' import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util' import { Column } from 'mol-data/db' -import { IntraUnitBonds } from '../../../structure/unit/bonds'; -export class StructConn implements IntraUnitBonds.StructConn { - private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0; - private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0; +export interface StructConn { + getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> + getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> +} + +export interface ComponentBond { + entries: Map<string, ComponentBond.Entry> +} - private static _resKey(rA: number, rB: number) { +export namespace StructConn { + function _resKey(rA: number, rB: number) { if (rA < rB) return `${rA}-${rB}`; return `${rB}-${rA}`; } - - private getResiduePairIndex() { - if (this._residuePairIndex) return this._residuePairIndex; - this._residuePairIndex = new Map(); - for (const e of this.entries) { - const ps = e.partners; - const l = ps.length; - for (let i = 0; i < l - 1; i++) { - for (let j = i + i; j < l; j++) { - const key = StructConn._resKey(ps[i].residueIndex, ps[j].residueIndex); - if (this._residuePairIndex.has(key)) { - this._residuePairIndex.get(key)!.push(e); - } else { - this._residuePairIndex.set(key, [e]); + const _emptyEntry: Entry[] = []; + + class StructConnImpl implements StructConn { + private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0; + private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0; + + private getResiduePairIndex() { + if (this._residuePairIndex) return this._residuePairIndex; + this._residuePairIndex = new Map(); + for (const e of this.entries) { + const ps = e.partners; + const l = ps.length; + for (let i = 0; i < l - 1; i++) { + for (let j = i + i; j < l; j++) { + const key = _resKey(ps[i].residueIndex, ps[j].residueIndex); + if (this._residuePairIndex.has(key)) { + this._residuePairIndex.get(key)!.push(e); + } else { + this._residuePairIndex.set(key, [e]); + } } } } + return this._residuePairIndex; } - return this._residuePairIndex; - } - private getAtomIndex() { - if (this._atomIndex) return this._atomIndex; - this._atomIndex = new Map(); - for (const e of this.entries) { - for (const p of e.partners) { - const key = p.atomIndex; - if (this._atomIndex.has(key)) { - this._atomIndex.get(key)!.push(e); - } else { - this._atomIndex.set(key, [e]); + private getAtomIndex() { + if (this._atomIndex) return this._atomIndex; + this._atomIndex = new Map(); + for (const e of this.entries) { + for (const p of e.partners) { + const key = p.atomIndex; + if (this._atomIndex.has(key)) { + this._atomIndex.get(key)!.push(e); + } else { + this._atomIndex.set(key, [e]); + } } } + return this._atomIndex; } - return this._atomIndex; - } - private static _emptyEntry = []; - getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> { - return this.getResiduePairIndex().get(StructConn._resKey(residueAIndex, residueBIndex)) || StructConn._emptyEntry; - } + getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> { + return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry; + } - getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> { - return this.getAtomIndex().get(atomIndex) || StructConn._emptyEntry; - } + getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> { + return this.getAtomIndex().get(atomIndex) || _emptyEntry; + } - constructor(public entries: StructConn.Entry[]) { + constructor(public entries: StructConn.Entry[]) { + } } -} -export namespace StructConn { - export interface Entry extends IntraUnitBonds.StructConnEntry { + export interface Entry { distance: number, order: number, flags: number, @@ -90,8 +98,11 @@ export namespace StructConn { | 'modres' | 'saltbr' - export function create(model: Model): StructConn | undefined { - if (model.sourceData.kind !== 'mmCIF') return + export const PropName = '__StructConn__'; + export function fromModel(model: Model): StructConn | undefined { + if (model.properties[PropName]) return model.properties[PropName]; + + if (model.sourceData.kind !== 'mmCIF') return; const { struct_conn } = model.sourceData.data; if (!struct_conn._rowCount) return void 0; @@ -150,7 +161,7 @@ export namespace StructConn { const type = conn_type_id.value(i)! as StructConnType; const orderType = (pdbx_value_order.value(i) || '').toLowerCase(); - let flags = BondType.Flag.None; + let flags = LinkType.Flag.None; let order = 1; switch (orderType) { @@ -166,33 +177,35 @@ export namespace StructConn { case 'covale_phosphate': case 'covale_sugar': case 'modres': - flags = BondType.Flag.Covalent; + flags = LinkType.Flag.Covalent; break; - case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Sulfide; break; - case 'hydrog': flags = BondType.Flag.Hydrogen; break; - case 'metalc': flags = BondType.Flag.MetallicCoordination; break; - case 'saltbr': flags = BondType.Flag.Ion; break; + case 'disulf': flags = LinkType.Flag.Covalent | LinkType.Flag.Sulfide; break; + case 'hydrog': flags = LinkType.Flag.Hydrogen; break; + case 'metalc': flags = LinkType.Flag.MetallicCoordination; break; + case 'saltbr': flags = LinkType.Flag.Ion; break; } entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners }); } - return new StructConn(entries); + const ret = new StructConnImpl(entries); + model.properties[PropName] = ret; + return ret; } } -export class ComponentBondInfo implements IntraUnitBonds.ComponentBondInfo { - entries: Map<string, ComponentBondInfo.Entry> = new Map(); +export namespace ComponentBond { + export class ComponentBondImpl implements ComponentBond { + entries: Map<string, ComponentBond.Entry> = new Map(); - newEntry(id: string) { - let e = new ComponentBondInfo.Entry(id); - this.entries.set(id, e); - return e; + addEntry(id: string) { + let e = new Entry(id); + this.entries.set(id, e); + return e; + } } -} -export namespace ComponentBondInfo { - export class Entry implements IntraUnitBonds.ComponentBondInfoEntry { + export class Entry implements Entry { map: Map<string, Map<string, { order: number, flags: number }>> = new Map(); add(a: string, b: string, order: number, flags: number, swap = true) { @@ -215,16 +228,19 @@ export namespace ComponentBondInfo { } } - export function create(model: Model): ComponentBondInfo | undefined { + export const PropName = '__ComponentBond__'; + export function fromModel(model: Model): ComponentBond | undefined { + if (model.properties[PropName]) return model.properties[PropName]; + if (model.sourceData.kind !== 'mmCIF') return const { chem_comp_bond } = model.sourceData.data; if (!chem_comp_bond._rowCount) return void 0; - let info = new ComponentBondInfo(); + let compBond = new ComponentBondImpl(); const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond; - let entry = info.newEntry(comp_id.value(0)!); + let entry = compBond.addEntry(comp_id.value(0)!); for (let i = 0; i < rowCount; i++) { @@ -235,12 +251,12 @@ export namespace ComponentBondInfo { const aromatic = pdbx_aromatic_flag.value(i) === 'Y'; if (entry.id !== id) { - entry = info.newEntry(id); + entry = compBond.addEntry(id); } - let flags: number = BondType.Flag.Covalent; + let flags: number = LinkType.Flag.Covalent; let ord = 1; - if (aromatic) flags |= BondType.Flag.Aromatic; + if (aromatic) flags |= LinkType.Flag.Aromatic; switch (order.toLowerCase()) { case 'doub': case 'delo': @@ -253,6 +269,7 @@ export namespace ComponentBondInfo { entry.add(nameA, nameB, ord, flags); } - return info; + model.properties[PropName] = compBond; + return compBond; } } \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model/structure/model/formats/mmcif/sequence.ts index 06f0c888d9af880650668dfe69776c0308aae9ce..a279b6122979af54ae375a8be6fd394f400b49c6 100644 --- a/src/mol-model/structure/model/formats/mmcif/sequence.ts +++ b/src/mol-model/structure/model/formats/mmcif/sequence.ts @@ -21,12 +21,13 @@ import { Sequence } from '../../../../sequence'; // corresponding ATOM_SITE entries should reflect this // heterogeneity. -export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): StructureSequence { - if (!cif.entity_poly_seq._rowCount) return StructureSequence.fromAtomicHierarchy(entities, hierarchy); +export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy, modResMap: Map<string, string>): StructureSequence { + if (!cif.entity_poly_seq._rowCount) return StructureSequence.fromAtomicHierarchy(entities, hierarchy, modResMap); const { entity_id, num, mon_id } = cif.entity_poly_seq; const byEntityKey: StructureSequence['byEntityKey'] = {}; + const sequences: StructureSequence.Entity[] = []; const count = entity_id.rowCount; let i = 0; @@ -38,14 +39,17 @@ export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHie const id = entity_id.value(start); const _compId = Column.window(mon_id, start, i); const _num = Column.window(num, start, i); + const entityKey = entities.getEntityIndex(id); - byEntityKey[entities.getEntityIndex(id)] = { + byEntityKey[entityKey] = { entityId: id, compId: _compId, num: _num, - sequence: Sequence.ofResidueNames(_compId, _num) + sequence: Sequence.ofResidueNames(_compId, _num, modResMap) }; + + sequences.push(byEntityKey[entityKey]); } - return { byEntityKey }; + return { byEntityKey, sequences }; } \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index da0037d65dff36c294992e38a77c7d5d0d109b53..799bb664363c9dd7d90d11946a0ba0023e49452f 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -15,7 +15,6 @@ import { SecondaryStructure } from './properties/seconday-structure'; //import from_gro from './formats/gro' import from_mmCIF from './formats/mmcif' - /** * Interface to the "source data" of the molecule. * @@ -36,7 +35,13 @@ interface Model extends Readonly<{ atomicHierarchy: AtomicHierarchy, atomicConformation: AtomicConformation, - properties: { secondaryStructure: SecondaryStructure }, + /** Various parts of the code can "cache" custom properties here */ + properties: { + readonly secondaryStructure: SecondaryStructure, + // maps modified residue name to its parent + readonly modifiedResidueNameMap: Map<string, string>, + [customName: string]: any + }, coarseHierarchy: CoarseHierarchy, coarseConformation: CoarseConformation diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index fe50e990b242ece24021f6f8eee514388e95d6da..4327d22d406451be93e6d9116bb58526cf4fb9d2 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -65,7 +65,9 @@ export interface AtomicKeys { // also index to the Entities table. entityKey: ArrayLike<number>, - findChainKey(entityId: string, label_asym_id: string): number + findChainKey(entityId: string, label_asym_id: string): number, + + /** Unique number for each of the residue. Also the index of the 1st occurence of this residue. */ findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number } diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts index a2f53c56df7c790bbbacadc67e1d28a4f983a6ac..7725eb561352a4c044b1cd410160e545f31aa8e8 100644 --- a/src/mol-model/structure/model/properties/sequence.ts +++ b/src/mol-model/structure/model/properties/sequence.ts @@ -10,6 +10,7 @@ import { Entities } from './common'; import { Sequence } from '../../../sequence'; interface StructureSequence { + readonly sequences: ReadonlyArray<StructureSequence.Entity>, readonly byEntityKey: { [key: number]: StructureSequence.Entity } } @@ -22,11 +23,12 @@ namespace StructureSequence { readonly sequence: Sequence } - export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy): StructureSequence { + export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy, modResMap?: Map<string, string>): StructureSequence { const { label_comp_id, label_seq_id } = hierarchy.residues const { chainSegments, residueSegments } = hierarchy const byEntityKey: StructureSequence['byEntityKey'] = { }; + const sequences: StructureSequence.Entity[] = []; for (let cI = 0, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) { const entityKey = hierarchy.entityKey[cI]; @@ -50,11 +52,13 @@ namespace StructureSequence { entityId: entities.data.id.value(entityKey), compId, num, - sequence: Sequence.ofResidueNames(compId, num) + sequence: Sequence.ofResidueNames(compId, num, modResMap) }; + + sequences.push(byEntityKey[entityKey]); } - return { byEntityKey } + return { byEntityKey, sequences }; } } diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index 36d0cb935f3631e5fd0057526443f753472090f6..16c748b99b1d3d38f4a88614837ae8658acaed94 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -340,9 +340,9 @@ export const VdwRadii = { } export const DefaultVdwRadius = 2.0 -export interface BondType extends BitFlags<BondType.Flag> { } -export namespace BondType { - export const is: (b: BondType, f: Flag) => boolean = BitFlags.has +export interface LinkType extends BitFlags<LinkType.Flag> { } +export namespace LinkType { + export const is: (b: LinkType, f: Flag) => boolean = BitFlags.has export const enum Flag { None = 0x0, Covalent = 0x1, @@ -354,4 +354,8 @@ export namespace BondType { Computed = 0x40 // currently at most 16 flags are supported!! } + + export function isCovalent(flags: LinkType.Flag) { + return (flags & LinkType.Flag.Covalent) !== 0; + } } \ No newline at end of file diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index a1fc34f2a0e6f741a667aa97c2af2fcd7ec7a9c2..0aaa01922dc0f82ca9626e9ba9b0d258125ec742 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -5,8 +5,9 @@ */ import { HashSet } from 'mol-data/generic' -import { Structure } from '../structure' +import { Structure, Element, Unit } from '../structure' import { structureUnion } from './utils/structure'; +import { SortedArray } from 'mol-data/int'; // A selection is a pair of a Structure and a sequence of unique AtomSets type Selection = Selection.Singletons | Selection.Sequence @@ -34,6 +35,16 @@ namespace Selection { return structureUnion(sel.source, sel.structures); } + export function toLoci(sel: Selection): Element.Loci { + const loci: { unit: Unit, indices: SortedArray }[] = []; + + for (const unit of unionStructure(sel).units) { + loci[loci.length] = { unit, indices: SortedArray.indicesOf(sel.source.unitMap.get(unit.id).elements, unit.elements) } + } + + return Element.Loci(loci); + } + export interface Builder { add(structure: Structure): void, getSelection(): Selection diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts index 44a299b2e5c3a9c41148e6298e8fc5488fb8c04d..bed8ae1fd045532d92ac1922e95cfdcd5b5afc04 100644 --- a/src/mol-model/structure/structure.ts +++ b/src/mol-model/structure/structure.ts @@ -8,6 +8,6 @@ import Element from './structure/element' import Structure from './structure/structure' import Unit from './structure/unit' import StructureSymmetry from './structure/symmetry' -import { Bond } from './structure/unit/bonds' +import { Link } from './structure/unit/links' -export { Element, Bond, Structure, Unit, StructureSymmetry } \ No newline at end of file +export { Element, Link, Structure, Unit, StructureSymmetry } \ No newline at end of file diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index cac54e0b559c6f488ad7541915907a1e5a57f061..22666fb137319710faae97b12058c3861003ecaf 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -15,6 +15,7 @@ import { StructureLookup3D } from './util/lookup3d'; import { CoarseElements } from '../model/properties/coarse'; import { StructureSubsetBuilder } from './util/subset-builder'; import { Queries } from '../query'; +import { InterUnitBonds, computeInterUnitBonds } from './unit/links'; class Structure { readonly unitMap: IntMap<Unit>; @@ -60,6 +61,13 @@ class Structure { return this._lookup3d; } + private _links?: InterUnitBonds = void 0; + get links() { + if (this._links) return this._links; + this._links = computeInterUnitBonds(this); + return this._links; + } + constructor(units: ArrayLike<Unit>) { const map = IntMap.Mutable<Unit>(); let elementCount = 0; @@ -90,8 +98,21 @@ namespace Structure { const chains = model.atomicHierarchy.chainSegments; const builder = new StructureBuilder(); + const { residueSegments: { segmentMap: residueIndex } } = model.atomicHierarchy; + for (let c = 0; c < chains.count; c++) { - const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]); + const start = chains.segments[c]; + let end = chains.segments[c + 1]; + + let rStart = residueIndex[start], rEnd = residueIndex[end - 1]; + while (rEnd - rStart <= 1 && c + 1 < chains.count) { + c++; + end = chains.segments[c + 1]; + rStart = rEnd; + rEnd = residueIndex[end - 1]; + } + + const elements = SortedArray.ofBounds(start, end); builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements); } diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index e5ef8d9080ebf11d6b2b5b6e6f2a76353aee9a61..c5488a09bc76f1393318cc1b9c5620998a8daa8f 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -9,7 +9,7 @@ import { Model } from '../model' import { GridLookup3D, Lookup3D } from 'mol-math/geometry' import { SortedArray } from 'mol-data/int'; import { idFactory } from 'mol-util/id-factory'; -import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds' +import { IntraUnitLinks, computeIntraUnitBonds } from './unit/links' import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse'; import { ValueRef } from 'mol-util'; import { UnitRings } from './unit/rings'; @@ -100,10 +100,10 @@ namespace Unit { return this.props.lookup3d.ref; } - get bonds() { - if (this.props.bonds.ref) return this.props.bonds.ref; - this.props.bonds.ref = computeIntraUnitBonds(this); - return this.props.bonds.ref; + get links() { + if (this.props.links.ref) return this.props.links.ref; + this.props.links.ref = computeIntraUnitBonds(this); + return this.props.links.ref; } get rings() { @@ -127,12 +127,12 @@ namespace Unit { interface AtomicProperties { lookup3d: ValueRef<Lookup3D | undefined>, - bonds: ValueRef<IntraUnitBonds | undefined>, + links: ValueRef<IntraUnitLinks | undefined>, rings: ValueRef<UnitRings | undefined> } - function AtomicProperties() { - return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0), rings: ValueRef.create(void 0) }; + function AtomicProperties(): AtomicProperties { + return { lookup3d: ValueRef.create(void 0), links: ValueRef.create(void 0), rings: ValueRef.create(void 0) }; } class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base { diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts deleted file mode 100644 index ba344073c94604ba612a5dfafe4bd100770e30bb..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { BondType, ElementSymbol } from '../../../model/types' -import { IntraUnitBonds } from './intra-data' -import { StructConn, ComponentBondInfo } from '../../../model/formats/mmcif/bonds' -import Unit from '../../unit' -import { IntGraph } from 'mol-math/graph'; - -export interface BondComputationParameters { - maxHbondLength: number, - forceCompute: boolean -} - -// H,D,T are all mapped to H -const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 }; - -const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 }; - -const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 }; - -const __DefaultBondingRadius = 2.001; - -const MetalsSet = (function () { - const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR']; - const set = new Set<number>(); - for (const m of metals) { - set.add(__ElementIndex[m]!); - } - return set; -})(); - -function pair(a: number, b: number) { - if (a < b) return (a + b) * (a + b + 1) / 2 + b; - else return (a + b) * (a + b + 1) / 2 + a; -} - -function idx(e: ElementSymbol) { - const i = __ElementIndex[e as any as string]; - if (i === void 0) return -1; - return i; -} - -function pairThreshold(i: number, j: number) { - if (i < 0 || j < 0) return -1; - const r = __ElementPairThresholds[pair(i, j)]; - if (r === void 0) return -1; - return r; -} - -function threshold(i: number) { - if (i < 0) return __DefaultBondingRadius; - const r = __ElementBondThresholds[i]; - if (r === void 0) return __DefaultBondingRadius; - return r; -} - -const H_ID = __ElementIndex['H']!; -function isHydrogen(i: number) { - return i === H_ID; -} - -function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds { - const builder = new IntGraph.EdgeBuilder(atomCount, atomA, atomB); - const flags = new Uint16Array(builder.slotCount); - const order = new Int8Array(builder.slotCount); - for (let i = 0, _i = builder.edgeCount; i < _i; i++) { - builder.addNextEdge(); - builder.assignProperty(flags, _flags[i]); - builder.assignProperty(order, _order[i]); - } - - return builder.createGraph({ flags, order }); -} - -function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds { - const MAX_RADIUS = 3; - - const { x, y, z } = unit.model.atomicConformation; - const atomCount = unit.elements.length; - const { elements: atoms, residueIndex } = unit; - const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms; - const { label_comp_id } = unit.model.atomicHierarchy.residues; - const query3d = unit.lookup3d; - - const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.create(unit.model) : void 0 - const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(unit.model) : void 0 - - const atomA: number[] = []; - const atomB: number[] = []; - const flags: number[] = []; - const order: number[] = []; - - let lastResidue = -1; - let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0; - - for (let _aI = 0; _aI < atomCount; _aI++) { - const aI = atoms[_aI]; - const raI = residueIndex[aI]; - - if (!params.forceCompute && raI !== lastResidue) { - const resn = label_comp_id.value(raI)!; - if (!!component && component.entries.has(resn)) { - componentMap = component.entries.get(resn)!.map; - } else { - componentMap = void 0; - } - } - lastResidue = raI; - - const componentPairs = componentMap ? componentMap.get(label_atom_id.value(aI)) : void 0; - - const aeI = idx(type_symbol.value(aI)!); - - const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS); - const isHa = isHydrogen(aeI); - const thresholdA = threshold(aeI); - const altA = label_alt_id.value(aI); - const metalA = MetalsSet.has(aeI); - const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); - - for (let ni = 0; ni < count; ni++) { - const _bI = indices[ni]; - const bI = atoms[_bI]; - if (bI <= aI) continue; - - const altB = label_alt_id.value(bI); - if (altA && altB && altA !== altB) continue; - - const beI = idx(type_symbol.value(bI)!); - const isMetal = metalA || MetalsSet.has(beI); - - const rbI = residueIndex[bI]; - // handle "component dictionary" bonds. - if (raI === rbI && componentPairs) { - const e = componentPairs.get(label_atom_id.value(bI)!); - if (e) { - atomA[atomA.length] = _aI; - atomB[atomB.length] = _bI; - order[order.length] = e.order; - let flag = e.flags; - if (isMetal) { - if (flag | BondType.Flag.Covalent) flag ^= BondType.Flag.Covalent; - flag |= BondType.Flag.MetallicCoordination; - } - flags[flags.length] = flag; - } - continue; - } - - const isHb = isHydrogen(beI); - if (isHa && isHb) continue; - - const dist = Math.sqrt(squaredDistances[ni]); - if (dist === 0) continue; - - // handle "struct conn" bonds. - if (structConnEntries && structConnEntries.length) { - let added = false; - for (const se of structConnEntries) { - for (const p of se.partners) { - if (p.atomIndex === bI) { - atomA[atomA.length] = _aI; - atomB[atomB.length] = _bI; - flags[flags.length] = se.flags; - order[order.length] = se.order; - added = true; - break; - } - } - if (added) break; - } - if (added) continue; - } - - if (isHa || isHb) { - if (dist < params.maxHbondLength) { - atomA[atomA.length] = _aI; - atomB[atomB.length] = _bI; - order[order.length] = 1; - flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed; // TODO: check if correct - } - continue; - } - - const thresholdAB = pairThreshold(aeI, beI); - const pairingThreshold = thresholdAB > 0 - ? thresholdAB - : beI < 0 ? thresholdA : Math.max(thresholdA, threshold(beI)); - - - if (dist <= pairingThreshold) { - atomA[atomA.length] = _aI; - atomB[atomB.length] = _bI; - order[order.length] = 1; - flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed; - } - } - } - - return getGraph(atomA, atomB, order, flags, atomCount); -} - -function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<BondComputationParameters>) { - return _computeBonds(unit, { - maxHbondLength: (params && params.maxHbondLength) || 1.15, - forceCompute: !!(params && params.forceCompute), - }); -} - -export { computeIntraUnitBonds } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/bonds/intra-data.ts b/src/mol-model/structure/structure/unit/bonds/intra-data.ts deleted file mode 100644 index 9914145379663e38ce3882ef4634bd57ae3da57e..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/unit/bonds/intra-data.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2017-2018 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 { BondType } from '../../../model/types' -import { IntGraph } from 'mol-math/graph'; - -type IntraUnitBonds = IntGraph<{ readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> }> - -namespace IntraUnitBonds { - export function createEmpty(): IntraUnitBonds { - return IntGraph.create([], [], [], 0, { flags: [], order: [] }); - } - export function isCovalent(flags: number) { - return (flags & BondType.Flag.Covalent) !== 0; - } - export interface StructConnEntry { - flags: BondType.Flag, - order: number, - distance: number, - partners: { residueIndex: number, atomIndex: number, symmetry: string }[] - } - export interface StructConn { - getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConnEntry> - getAtomEntries(atomIndex: number): ReadonlyArray<StructConnEntry> - } - export interface ComponentBondInfoEntry { - map: Map<string, Map<string, { order: number, flags: number }>> - } - export interface ComponentBondInfo { - entries: Map<string, ComponentBondInfoEntry> - } -} - -export { IntraUnitBonds } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/links.ts similarity index 58% rename from src/mol-model/structure/structure/unit/bonds.ts rename to src/mol-model/structure/structure/unit/links.ts index d7268742832c8d7ff04d19199e218655ff48a099..20345c1bfd56e36caa39d01afe9d72ec38bc0aec 100644 --- a/src/mol-model/structure/structure/unit/bonds.ts +++ b/src/mol-model/structure/structure/unit/links.ts @@ -6,10 +6,11 @@ import { Unit } from '../../structure' -export * from './bonds/intra-data' -export * from './bonds/intra-compute' +export * from './links/data' +export * from './links/intra-compute' +export * from './links/inter-compute' -namespace Bond { +namespace Link { export interface Location { readonly aUnit: Unit, /** Index into aUnit.elements */ @@ -20,17 +21,17 @@ namespace Bond { } export interface Loci { - readonly kind: 'bond-loci', - readonly bonds: ReadonlyArray<Location> + readonly kind: 'link-loci', + readonly links: ReadonlyArray<Location> } - export function Loci(bonds: ArrayLike<Location>): Loci { - return { kind: 'bond-loci', bonds: bonds as Loci['bonds'] }; + export function Loci(links: ArrayLike<Location>): Loci { + return { kind: 'link-loci', links: links as Loci['links'] }; } export function isLoci(x: any): x is Loci { - return !!x && x.kind === 'bond-loci'; + return !!x && x.kind === 'link-loci'; } } -export { Bond } \ No newline at end of file +export { Link } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/links/common.ts b/src/mol-model/structure/structure/unit/links/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..6457c86b89ae8bbfd20a33894dc118f271076b6d --- /dev/null +++ b/src/mol-model/structure/structure/unit/links/common.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2017-2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { ElementSymbol } from '../../../model/types'; + +export interface LinkComputationParameters { + maxHbondLength: number, + forceCompute: boolean +} + +// H,D,T are all mapped to H +const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 }; + +const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 }; + +const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 }; + +const __DefaultBondingRadius = 2.001; + +export const MetalsSet = (function () { + const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR']; + const set = new Set<number>(); + for (const m of metals) { + set.add(__ElementIndex[m]!); + } + return set; +})(); + +function pair(a: number, b: number) { + if (a < b) return (a + b) * (a + b + 1) / 2 + b; + else return (a + b) * (a + b + 1) / 2 + a; +} + +export function getElementIdx(e: ElementSymbol) { + const i = __ElementIndex[e as any as string]; + if (i === void 0) return -1; + return i; +} + +export function getElementPairThreshold(i: number, j: number) { + if (i < 0 || j < 0) return -1; + const r = __ElementPairThresholds[pair(i, j)]; + if (r === void 0) return -1; + return r; +} + +export function getElementThreshold(i: number) { + if (i < 0) return __DefaultBondingRadius; + const r = __ElementBondThresholds[i]; + if (r === void 0) return __DefaultBondingRadius; + return r; +} + +const H_ID = __ElementIndex['H']!; +export function isHydrogen(i: number) { + return i === H_ID; +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/links/data.ts b/src/mol-model/structure/structure/unit/links/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..dfd9762c5a6fdc0578f94f56011bcd8605d7cbc0 --- /dev/null +++ b/src/mol-model/structure/structure/unit/links/data.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2017-2018 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 { LinkType } from '../../../model/types' +import { IntAdjacencyGraph } from 'mol-math/graph'; +import Unit from '../../unit'; + +type IntraUnitLinks = IntAdjacencyGraph<{ readonly order: ArrayLike<number>, readonly flags: ArrayLike<LinkType.Flag> }> + +namespace IntraUnitLinks { + export const Empty: IntraUnitLinks = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] }); +} + +class InterUnitBonds { + getLinkedUnits(unit: Unit): ReadonlyArray<InterUnitBonds.UnitPairBonds> { + if (!this.map.has(unit.id)) return emptyArray; + return this.map.get(unit.id)!; + } + + constructor(private map: Map<number, InterUnitBonds.UnitPairBonds[]>) { + } +} + +namespace InterUnitBonds { + export class UnitPairBonds { + hasBonds(indexA: number) { + return this.linkMap.has(indexA); + } + + getBonds(indexA: number): ReadonlyArray<InterUnitBonds.BondInfo> { + if (!this.linkMap.has(indexA)) return emptyArray; + return this.linkMap.get(indexA)!; + } + + get areUnitsOrdered() { + return this.unitA.id < this.unitB.id; + } + + constructor(public unitA: Unit.Atomic, public unitB: Unit.Atomic, + public bondCount: number, public linkedElementIndices: ReadonlyArray<number>, + private linkMap: Map<number, BondInfo[]>) { + } + } + + export interface BondInfo { + /** indexInto */ + readonly indexB: number, + readonly order: number, + readonly flag: LinkType.Flag + } +} + +const emptyArray: any[] = []; + +export { IntraUnitLinks, InterUnitBonds } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts new file mode 100644 index 0000000000000000000000000000000000000000..3eeb8ecc97e0b78ed83e5842f07350aaa326fdac --- /dev/null +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { StructConn } from '../../../model/formats/mmcif/bonds'; +import { LinkType } from '../../../model/types'; +import Structure from '../../structure'; +import Unit from '../../unit'; +import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, LinkComputationParameters, MetalsSet } from './common'; +import { InterUnitBonds } from './data'; +import { UniqueArray } from 'mol-data/generic'; + +const MAX_RADIUS = 4; + +function addMapEntry<A, B>(map: Map<A, B[]>, a: A, b: B) { + if (map.has(a)) map.get(a)!.push(b); + else map.set(a, [b]); +} + +interface PairState { + mapAB: Map<number, InterUnitBonds.BondInfo[]>, + mapBA: Map<number, InterUnitBonds.BondInfo[]>, + bondedA: UniqueArray<number, number>, + bondedB: UniqueArray<number, number> +} + +function addLink(indexA: number, indexB: number, order: number, flag: LinkType.Flag, state: PairState) { + addMapEntry(state.mapAB, indexA, { indexB, order, flag }); + addMapEntry(state.mapBA, indexB, { indexB: indexA, order, flag }); + UniqueArray.add(state.bondedA, indexA, indexA); + UniqueArray.add(state.bondedB, indexB, indexB); +} + +function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkComputationParameters, map: Map<number, InterUnitBonds.UnitPairBonds[]>) { + const state: PairState = { mapAB: new Map(), mapBA: new Map(), bondedA: UniqueArray.create(), bondedB: UniqueArray.create() }; + let bondCount = 0; + + const { elements: atomsA, conformation: { x, y, z } } = unitA; + const { elements: atomsB } = unitB; + const atomCount = unitA.elements.length; + + const { type_symbol: type_symbolA, label_alt_id: label_alt_idA } = unitA.model.atomicHierarchy.atoms; + const { type_symbol: type_symbolB, label_alt_id: label_alt_idB } = unitB.model.atomicHierarchy.atoms; + const { lookup3d } = unitB; + const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unitA.model) : void 0; + + for (let _aI = 0; _aI < atomCount; _aI++) { + const aI = atomsA[_aI]; + + const aeI = getElementIdx(type_symbolA.value(aI)); + const { indices, count, squaredDistances } = lookup3d.find(x(aI), y(aI), z(aI), MAX_RADIUS); + const isHa = isHydrogen(aeI); + const thresholdA = getElementThreshold(aeI); + const altA = label_alt_idA.value(aI); + const metalA = MetalsSet.has(aeI); + const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); + + for (let ni = 0; ni < count; ni++) { + const _bI = indices[ni]; + const bI = atomsB[_bI]; + + const altB = label_alt_idB.value(bI); + // TODO: check if they have the same model? + if (altA && altB && altA !== altB) continue; + + const beI = getElementIdx(type_symbolB.value(bI)!); + const isMetal = metalA || MetalsSet.has(beI); + + const isHb = isHydrogen(beI); + if (isHa && isHb) continue; + + const dist = Math.sqrt(squaredDistances[ni]); + if (dist === 0) continue; + + // handle "struct conn" bonds. + if (structConnEntries && structConnEntries.length) { + let added = false; + for (const se of structConnEntries) { + for (const p of se.partners) { + if (p.atomIndex === bI) { + addLink(_aI, _bI, se.order, se.flags, state); + bondCount++; + added = true; + break; + } + } + if (added) break; + } + if (added) continue; + } + + if (isHa || isHb) { + if (dist < params.maxHbondLength) { + addLink(_aI, _bI, 1, LinkType.Flag.Covalent | LinkType.Flag.Computed, state); // TODO: check if correct + bondCount++; + } + continue; + } + + const thresholdAB = getElementPairThreshold(aeI, beI); + const pairingThreshold = thresholdAB > 0 + ? thresholdAB + : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI)); + + if (dist <= pairingThreshold) { + addLink(_aI, _bI, 1, (isMetal ? LinkType.Flag.MetallicCoordination : LinkType.Flag.Covalent) | LinkType.Flag.Computed, state); + bondCount++; + } + } + } + + addMapEntry(map, unitA.id, new InterUnitBonds.UnitPairBonds(unitA, unitB, bondCount, state.bondedA.array, state.mapAB)); + addMapEntry(map, unitB.id, new InterUnitBonds.UnitPairBonds(unitB, unitA, bondCount, state.bondedB.array, state.mapBA)); + + return bondCount; +} + +function findLinks(structure: Structure, params: LinkComputationParameters) { + const map = new Map<number, InterUnitBonds.UnitPairBonds[]>(); + if (!structure.units.some(u => Unit.isAtomic(u))) return new InterUnitBonds(map); + + const lookup = structure.lookup3d; + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + + const bs = unit.lookup3d.boundary.sphere; + const closeUnits = lookup.findUnitIndices(bs.center[0], bs.center[1], bs.center[2], bs.radius + MAX_RADIUS); + for (let i = 0; i < closeUnits.count; i++) { + const other = structure.units[closeUnits.indices[i]]; + if (!Unit.isAtomic(other) || unit.id >= other.id) continue; + + if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, params, map); + else findPairLinks(other, unit, params, map); + } + } + + return new InterUnitBonds(map); +} + +function computeInterUnitBonds(structure: Structure, params?: Partial<LinkComputationParameters>): InterUnitBonds { + return findLinks(structure, { + maxHbondLength: (params && params.maxHbondLength) || 1.15, + forceCompute: !!(params && params.forceCompute), + }); +} + +export { computeInterUnitBonds }; diff --git a/src/mol-model/structure/structure/unit/links/intra-compute.ts b/src/mol-model/structure/structure/unit/links/intra-compute.ts new file mode 100644 index 0000000000000000000000000000000000000000..28ee7cc7d701f49d3fe91af6f6a96d40bc01a313 --- /dev/null +++ b/src/mol-model/structure/structure/unit/links/intra-compute.ts @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { LinkType } from '../../../model/types' +import { IntraUnitLinks } from './data' +import { StructConn, ComponentBond } from '../../../model/formats/mmcif/bonds' +import Unit from '../../unit' +import { IntAdjacencyGraph } from 'mol-math/graph'; +import { LinkComputationParameters, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold } from './common'; + +function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number): IntraUnitLinks { + const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB); + const flags = new Uint16Array(builder.slotCount); + const order = new Int8Array(builder.slotCount); + for (let i = 0, _i = builder.edgeCount; i < _i; i++) { + builder.addNextEdge(); + builder.assignProperty(flags, _flags[i]); + builder.assignProperty(order, _order[i]); + } + + return builder.createGraph({ flags, order }); +} + +function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): IntraUnitLinks { + const MAX_RADIUS = 4; + + const { x, y, z } = unit.model.atomicConformation; + const atomCount = unit.elements.length; + const { elements: atoms, residueIndex } = unit; + const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms; + const { label_comp_id } = unit.model.atomicHierarchy.residues; + const query3d = unit.lookup3d; + + const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unit.model) : void 0; + const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.fromModel(unit.model) : void 0; + + const atomA: number[] = []; + const atomB: number[] = []; + const flags: number[] = []; + const order: number[] = []; + + let lastResidue = -1; + let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0; + + for (let _aI = 0; _aI < atomCount; _aI++) { + const aI = atoms[_aI]; + const raI = residueIndex[aI]; + + if (!params.forceCompute && raI !== lastResidue) { + const resn = label_comp_id.value(raI)!; + if (!!component && component.entries.has(resn)) { + componentMap = component.entries.get(resn)!.map; + } else { + componentMap = void 0; + } + } + lastResidue = raI; + + const componentPairs = componentMap ? componentMap.get(label_atom_id.value(aI)) : void 0; + + const aeI = getElementIdx(type_symbol.value(aI)!); + + const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS); + const isHa = isHydrogen(aeI); + const thresholdA = getElementThreshold(aeI); + const altA = label_alt_id.value(aI); + const metalA = MetalsSet.has(aeI); + const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); + + for (let ni = 0; ni < count; ni++) { + const _bI = indices[ni]; + const bI = atoms[_bI]; + if (bI <= aI) continue; + + const altB = label_alt_id.value(bI); + if (altA && altB && altA !== altB) continue; + + const beI = getElementIdx(type_symbol.value(bI)!); + const isMetal = metalA || MetalsSet.has(beI); + + const rbI = residueIndex[bI]; + // handle "component dictionary" bonds. + if (raI === rbI && componentPairs) { + const e = componentPairs.get(label_atom_id.value(bI)!); + if (e) { + atomA[atomA.length] = _aI; + atomB[atomB.length] = _bI; + order[order.length] = e.order; + let flag = e.flags; + if (isMetal) { + if (flag | LinkType.Flag.Covalent) flag ^= LinkType.Flag.Covalent; + flag |= LinkType.Flag.MetallicCoordination; + } + flags[flags.length] = flag; + } + continue; + } + + const isHb = isHydrogen(beI); + if (isHa && isHb) continue; + + const dist = Math.sqrt(squaredDistances[ni]); + if (dist === 0) continue; + + // handle "struct conn" bonds. + if (structConnEntries && structConnEntries.length) { + let added = false; + for (const se of structConnEntries) { + for (const p of se.partners) { + if (p.atomIndex === bI) { + atomA[atomA.length] = _aI; + atomB[atomB.length] = _bI; + flags[flags.length] = se.flags; + order[order.length] = se.order; + added = true; + break; + } + } + if (added) break; + } + if (added) continue; + } + + if (isHa || isHb) { + if (dist < params.maxHbondLength) { + atomA[atomA.length] = _aI; + atomB[atomB.length] = _bI; + order[order.length] = 1; + flags[flags.length] = LinkType.Flag.Covalent | LinkType.Flag.Computed; // TODO: check if correct + } + continue; + } + + const thresholdAB = getElementPairThreshold(aeI, beI); + const pairingThreshold = thresholdAB > 0 + ? thresholdAB + : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI)); + + if (dist <= pairingThreshold) { + atomA[atomA.length] = _aI; + atomB[atomB.length] = _bI; + order[order.length] = 1; + flags[flags.length] = (isMetal ? LinkType.Flag.MetallicCoordination : LinkType.Flag.Covalent) | LinkType.Flag.Computed; + } + } + } + + return getGraph(atomA, atomB, order, flags, atomCount); +} + +function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<LinkComputationParameters>) { + return _computeBonds(unit, { + maxHbondLength: (params && params.maxHbondLength) || 1.15, + forceCompute: !!(params && params.forceCompute), + }); +} + +export { computeIntraUnitBonds } \ 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 a76cd6e47c29b67c4d57d2a7565f91a6cd3f2b76..c0bafe3e3abe380d3557ef14edfdd3225299902b 100644 --- a/src/mol-model/structure/structure/unit/rings/compute.ts +++ b/src/mol-model/structure/structure/unit/rings/compute.ts @@ -5,8 +5,9 @@ */ import Unit from '../../unit'; -import { IntraUnitBonds } from '../bonds/intra-data'; +import { IntraUnitLinks } from '../links/data'; import { Segmentation } from 'mol-data/int'; +import { LinkType } from '../../../model/types'; export default function computeRings(unit: Unit.Atomic) { const size = largestResidue(unit); @@ -40,7 +41,7 @@ interface State { currentColor: number, rings: number[][], - bonds: IntraUnitBonds, + bonds: IntraUnitLinks, unit: Unit.Atomic } @@ -58,7 +59,7 @@ function State(unit: Unit.Atomic, capacity: number): State { currentColor: 0, rings: [], unit, - bonds: unit.bonds + bonds: unit.links }; } @@ -148,7 +149,7 @@ function addRing(state: State, a: number, b: number) { function findRings(state: State, from: number) { const { bonds, startVertex, endVertex, visited, queue, pred } = state; - const { b: neighbor, flags: bondFlags, offset } = bonds; + const { b: neighbor, edgeProps: { flags: bondFlags }, offset } = bonds; visited[from] = 1; queue[0] = from; let head = 0, size = 1; @@ -160,7 +161,7 @@ function findRings(state: State, from: number) { for (let i = start; i < end; i++) { const b = neighbor[i]; - if (b < startVertex || b >= endVertex || !IntraUnitBonds.isCovalent(bondFlags[i])) continue; + if (b < startVertex || b >= endVertex || !LinkType.isCovalent(bondFlags[i])) continue; const other = b - startVertex; diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index 2ea308e3ec11624d1528fa28d8d9f38e49f96fd0..3c7b8b6ac24690292f4eacc144fde5fbf27368e8 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/src/mol-model/structure/structure/util/lookup3d.ts @@ -17,6 +17,10 @@ export class StructureLookup3D implements Lookup3D<Element> { private result = Result.create<Element>(); private pivot = Vec3.zero(); + findUnitIndices(x: number, y: number, z: number, radius: number): Result<number> { + return this.unitLookup.find(x, y, z, radius); + } + find(x: number, y: number, z: number, radius: number): Result<Element> { Result.reset(this.result); const { units } = this.structure; diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts index 578a4a7a53260c5c682edc6a7d648edf692a0031..0ea82df1ba0a07c339d76453580f942cf19a85cd 100644 --- a/src/mol-view/label.ts +++ b/src/mol-view/label.ts @@ -6,7 +6,7 @@ */ import { Unit, Element, Queries } from 'mol-model/structure'; -import { Bond } from 'mol-model/structure/structure/unit/bonds'; +import { Link } from 'mol-model/structure/structure/unit/links'; import { Loci } from 'mol-model/loci'; const elementLocA = Element.Location() @@ -23,8 +23,8 @@ export function labelFirst(loci: Loci) { if (e && e.indices[0] !== undefined) { return elementLabel(Element.Location(e.unit, e.indices[0])) } - } else if (Bond.isLoci(loci)) { - const bond = loci.bonds[0] + } else if (Link.isLoci(loci)) { + const bond = loci.links[0] if (bond) { setElementLocation(elementLocA, bond.aUnit, bond.aIndex) setElementLocation(elementLocB, bond.bUnit, bond.bIndex) diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index af44bda4bf3e7f202db99f95f6f971f8ef7b347b..1f46148e836e57db7654686a47d4438b8e1b26bd 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -10,6 +10,7 @@ import { Progress } from 'mol-task'; import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBond } from './state/transform'; import { UrlEntity } from './state/entity'; import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; +import { Context } from 'mol-app/context/context'; // export const ColorTheme = { // 'atom-index': {}, @@ -30,7 +31,7 @@ export class Stage { viewer: Viewer ctx = new StateContext(Progress.format) - constructor() { + constructor(public globalContext: Context) { } @@ -38,7 +39,7 @@ export class Stage { this.viewer = Viewer.create(canvas, container) this.viewer.animate() this.ctx.viewer = this.viewer - // this.loadPdbid('1crn') + //this.loadPdbid('1jj2') this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) } @@ -48,6 +49,8 @@ export class Stage { const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity) StructureToSpacefill.apply(this.ctx, structureEntity, spacefillProps) StructureToBond.apply(this.ctx, structureEntity, spacefillProps) // TODO props + + this.globalContext.components.sequenceView.setState({ structure: structureEntity.value }); } loadPdbid (pdbid: string) {