Ver Fonte

added cross-link distance restraints

Alexander Rose há 6 anos atrás
pai
commit
900c8d6e92

+ 4 - 1
src/mol-app/ui/transform/ball-and-stick.tsx

@@ -19,6 +19,7 @@ import { ColorTheme, SizeTheme } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
@@ -46,6 +47,7 @@ interface BallAndStickState {
     linkRadius: number
     radialSegments: number
     detail: number
+    unitKinds: Unit.Kind[]
 }
 
 export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: BallAndStickUpdate, entity: BallAndStickEntity, ctx: StateContext }> {
@@ -65,7 +67,8 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra
         linkSpacing: 1,
         linkRadius: 0.25,
         radialSegments: 16,
-        detail: 1
+        detail: 1,
+        unitKinds: [] as Unit.Kind[]
     }
 
     componentWillMount() {

+ 4 - 1
src/mol-app/ui/transform/spacefill.tsx

@@ -19,6 +19,7 @@ import { ColorTheme, SizeTheme } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
@@ -42,6 +43,7 @@ interface SpacefillState {
     depthMask: boolean
     useFog: boolean
     quality: VisualQuality
+    unitKinds: Unit.Kind[]
 }
 
 export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> {
@@ -57,7 +59,8 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
         alpha: 1,
         depthMask: true,
         useFog: true,
-        quality: 'auto' as VisualQuality
+        quality: 'auto' as VisualQuality,
+        unitKinds: [] as Unit.Kind[]
     }
 
     componentWillMount() {

+ 55 - 0
src/mol-geo/representation/structure/distance-restraint.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation } from '.';
+import { PickingId } from '../../util/picking';
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { SizeTheme } from '../../theme';
+import { CrossLinkRestraintVisual, DefaultCrossLinkRestraintProps } from './visual/cross-link-restraint-cylinder';
+
+export const DefaultDistanceRestraintProps = {
+    ...DefaultCrossLinkRestraintProps,
+
+    sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
+}
+export type DistanceRestraintProps = Partial<typeof DefaultDistanceRestraintProps>
+
+export function DistanceRestraintRepresentation(): StructureRepresentation<DistanceRestraintProps> {
+    const crossLinkRepr = StructureRepresentation(CrossLinkRestraintVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...crossLinkRepr.renderObjects ]
+        },
+        get props() {
+            return { ...crossLinkRepr.props }
+        },
+        create: (structure: Structure, props: DistanceRestraintProps = {} as DistanceRestraintProps) => {
+            const p = Object.assign({}, DefaultDistanceRestraintProps, props)
+            return Task.create('DistanceRestraintRepresentation', async ctx => {
+                await crossLinkRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: DistanceRestraintProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating DistanceRestraintRepresentation', async ctx => {
+                await crossLinkRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            return crossLinkRepr.getLoci(pickingId)
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            crossLinkRepr.mark(loci, action)
+        },
+        destroy() {
+            crossLinkRepr.destroy()
+        }
+    }
+}

+ 175 - 0
src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts

@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Link, Structure } from 'mol-model/structure';
+import { DefaultStructureProps, StructureVisual } from '../index';
+import { RuntimeContext } from 'mol-task'
+import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { createUniformColor } from '../../../util/color-data';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { createIdentityTransform } from './util/common';
+import { updateMeshValues, updateRenderableState, createMeshValues, createRenderableState } from '../../util';
+// import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
+
+async function createCrossLinkRestraintCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+
+    const crossLinks = structure.crossLinkRestraints
+    if (!crossLinks.count) return Mesh.createEmpty(mesh)
+
+    const builderProps = {
+        linkCount: crossLinks.count,
+        referencePosition: (edgeIndex: number) => null,
+        position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
+            const b = crossLinks.pairs[edgeIndex]
+            // console.log(b)
+            const uA = b.unitA, uB = b.unitB
+            uA.conformation.position(uA.elements[b.indexA], posA)
+            uB.conformation.position(uB.elements[b.indexB], posB)
+            // console.log(posA, posB)
+        },
+        order: (edgeIndex: number) => 1,
+        flags: (edgeIndex: number) => 0
+    }
+
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
+}
+
+export const DefaultCrossLinkRestraintProps = {
+    ...DefaultStructureProps,
+    ...DefaultLinkCylinderProps,
+    sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
+    flipSided: false,
+    flatShaded: false,
+}
+export type CrossLinkRestraintProps = Partial<typeof DefaultCrossLinkRestraintProps>
+
+export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintProps> {
+    const renderObjects: RenderObject[] = []
+    let cylinders: MeshRenderObject
+    let currentProps: typeof DefaultCrossLinkRestraintProps
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        renderObjects,
+        async create(ctx: RuntimeContext, structure: Structure, props: CrossLinkRestraintProps = {}) {
+            currentProps = Object.assign({}, DefaultCrossLinkRestraintProps, props)
+
+            renderObjects.length = 0 // clear
+            currentStructure = structure
+
+            const elementCount = structure.crossLinkRestraints.count
+            const instanceCount = 1
+
+            mesh = await createCrossLinkRestraintCylinderMesh(ctx, structure, currentProps)
+
+            if (ctx.shouldUpdate) await ctx.update('Computing link transforms');
+            const transforms = createIdentityTransform()
+
+            if (ctx.shouldUpdate) await ctx.update('Computing link colors');
+            const color = createUniformColor({ value: 0x119911 }) // TODO
+
+            if (ctx.shouldUpdate) await ctx.update('Computing link marks');
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            cylinders = createMeshRenderObject(values, state)
+            console.log(values, instanceCount, elementCount)
+            renderObjects.push(cylinders)
+        },
+        async update(ctx: RuntimeContext, props: CrossLinkRestraintProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!cylinders) return false
+
+            // TODO create in-place
+            if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            updateMeshValues(cylinders.values, newProps)
+            updateRenderableState(cylinders.state, newProps)
+
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getLinkLoci(pickingId, currentStructure, cylinders.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markLink(loci, action, currentStructure, cylinders.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
+
+function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
+    const { objectId, elementId } = pickingId
+    if (id === objectId) {
+        const pair = structure.crossLinkRestraints.pairs[elementId]
+        if (pair) {
+            return Link.Loci([{
+                aUnit: pair.unitA,
+                aIndex: pair.indexA,
+                bUnit: pair.unitB,
+                bIndex: pair.indexB
+            }])
+        }
+    }
+    return EmptyLoci
+}
+
+function markLink(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
+    const tMarker = values.tMarker
+
+    const crossLinks = structure.crossLinkRestraints
+    const elementCount = crossLinks.count
+    const instanceCount = 1
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Link.isLoci(loci)) {
+        for (const b of loci.links) {
+            const indices = crossLinks.getPairIndices(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
+            if (indices) {
+                for (let i = 0, il = indices.length; i < il; ++i) {
+                    const idx = indices[i]
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
+                    }
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}

+ 3 - 3
src/mol-geo/representation/util.ts

@@ -103,15 +103,15 @@ export function getQualityProps(props: Partial<QualityProps>, structure: Structu
 
     switch (quality) {
         case 'highest':
-            detail = 3
+            detail = 2
             radialSegments = 36
             break
         case 'high':
-            detail = 2
+            detail = 1
             radialSegments = 24
             break
         case 'medium':
-            detail = 1
+            detail = 0
             radialSegments = 12
             break
         case 'low':

+ 18 - 0
src/mol-geo/theme/index.ts

@@ -5,6 +5,8 @@
  */
 
 import { Color } from 'mol-util/color';
+// import { Loci } from 'mol-model/loci';
+// import { Structure } from 'mol-model/structure';
 
 export interface UniformColorTheme {
     name: 'uniform'
@@ -16,6 +18,22 @@ export interface ScaleColorTheme {
     domain?: [number, number]
 }
 
+// interface StructureColorProvider {
+//     uniform(): Color
+//     instance(instanceIdx: number): Color
+//     element(elementIdx: number): Color
+//     elementInstance(elementIdx: number, instanceIdx: number): Color
+
+//     lociColor(loci: Loci): Color
+// }
+
+// export namespace ColorProvider {
+//     export function fromLociColor(lociColor: (loci: Loci) => Color) {
+
+//         return
+//     }
+// }
+
 export type ColorTheme = UniformColorTheme | ScaleColorTheme
 
 export interface UniformSizeTheme {

+ 157 - 0
src/mol-model/structure/model/formats/mmcif/pair-restraint.ts

@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// ihm_predicted_contact_restraint: {
+//     id: int,
+//     entity_id_1: str,
+//     entity_id_2: str,
+//     asym_id_1: str,
+//     asym_id_2: str,
+//     comp_id_1: str,
+//     comp_id_2: str,
+//     seq_id_1: int,
+//     seq_id_2: int,
+//     atom_id_1: str,
+//     atom_id_2: str,
+//     distance_upper_limit: float,
+//     probability: float,
+//     restraint_type: Aliased<'lower bound' | 'upper bound' | 'lower and upper bound'>(str),
+//     model_granularity: Aliased<'by-residue' | 'by-feature' | 'by-atom'>(str),
+//     dataset_list_id: int,
+//     software_id: int,
+// },
+// ihm_cross_link_restraint: {
+//     id: int,
+//     group_id: int,
+//     entity_id_1: str,
+//     entity_id_2: str,
+//     asym_id_1: str,
+//     asym_id_2: str,
+//     comp_id_1: str,
+//     comp_id_2: str,
+//     seq_id_1: int,
+//     seq_id_2: int,
+//     atom_id_1: str,
+//     atom_id_2: str,
+//     restraint_type: Aliased<'harmonic' | 'upper bound' | 'lower bound'>(str),
+//     conditional_crosslink_flag: Aliased<'ALL' | 'ANY'>(str),
+//     model_granularity: Aliased<'by-residue' | 'by-feature' | 'by-atom'>(str),
+//     distance_threshold: float,
+//     psi: float,
+//     sigma_1: float,
+//     sigma_2: float,
+// },
+
+import Model from '../../model'
+import { Table } from 'mol-data/db'
+import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif';
+import { findAtomIndexByLabelName } from './util';
+import { Element, Unit } from '../../../structure';
+
+function findAtomIndex(model: Model, entityId: string, asymId: string, compId: string, seqId: number, atomId: string) {
+    if (!model.atomicHierarchy.atoms.auth_atom_id.isDefined) return -1
+    const residueIndex = model.atomicHierarchy.findResidueKey(entityId, compId, asymId, seqId, '')
+    if (residueIndex < 0) return -1
+    return findAtomIndexByLabelName(model, residueIndex, atomId, '') as Element
+}
+
+// function findElementIndex(model: Model, entityId: string, asymId: string, compId: string, seqId: number, atomId: string) {
+
+// }
+
+// function key(entityId: string, asymId: string, compId: string, seqId: number, atomId: string) {
+//     return `${entityId}|${asymId}|${compId}|${seqId}|${atomId}`
+// }
+
+export interface IHMCrossLinkRestraint {
+    getIndicesByElement: (element: Element, kind: Unit.Kind) => number[]
+    data: Table<mmCIF_Schema['ihm_cross_link_restraint']>
+}
+
+export namespace IHMCrossLinkRestraint {
+    export const PropName = '__CrossLinkRestraint__';
+    export function fromModel(model: Model): IHMCrossLinkRestraint | undefined {
+        if (model.properties[PropName]) return model.properties[PropName]
+
+        if (model.sourceData.kind !== 'mmCIF') return
+        const { ihm_cross_link_restraint } = model.sourceData.data;
+        if (!ihm_cross_link_restraint._rowCount) return
+
+        const p1 = {
+            entity_id: ihm_cross_link_restraint.entity_id_1,
+            asym_id: ihm_cross_link_restraint.asym_id_1,
+            comp_id: ihm_cross_link_restraint.comp_id_1,
+            seq_id: ihm_cross_link_restraint.seq_id_1,
+            atom_id: ihm_cross_link_restraint.atom_id_1,
+        }
+
+        const p2: typeof p1 = {
+            entity_id: ihm_cross_link_restraint.entity_id_2,
+            asym_id: ihm_cross_link_restraint.asym_id_2,
+            comp_id: ihm_cross_link_restraint.comp_id_2,
+            seq_id: ihm_cross_link_restraint.seq_id_2,
+            atom_id: ihm_cross_link_restraint.atom_id_2,
+        }
+
+        function _add(map: Map<Element, number[]>, element: Element, row: number) {
+            const indices = map.get(element)
+            if (indices) indices.push(row)
+            else map.set(element, [ row ])
+        }
+
+        function add(row: number, ps: typeof p1) {
+            const entityId = ps.entity_id.value(row)
+            const asymId = ps.asym_id.value(row)
+            const seqId = ps.seq_id.value(row)
+
+            if (ihm_cross_link_restraint.model_granularity.value(row) === 'by-atom') {
+                const atomicElement = findAtomIndex(model, entityId, asymId, ps.comp_id.value(row), seqId, ps.atom_id.value(row))
+                if (atomicElement >= 0) _add(atomicElementMap, atomicElement as Element, row)
+            } else if (model.coarseHierarchy.isDefined) {
+                const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId)
+                if (sphereElement >= 0) {
+                    _add(sphereElementMap, sphereElement as Element, row)
+                } else {
+                    const gaussianElement = model.coarseHierarchy.gaussians.findSequenceKey(entityId, asymId, seqId)
+                    if (gaussianElement >= 0) _add(gaussianElementMap, gaussianElement as Element, row)
+                }
+            }
+        }
+
+        function getMapByKind(kind: Unit.Kind) {
+            switch (kind) {
+                case Unit.Kind.Atomic: return atomicElementMap;
+                case Unit.Kind.Spheres: return sphereElementMap;
+                case Unit.Kind.Gaussians: return gaussianElementMap;
+            }
+        }
+
+        /** map from atomic element to cross link indices */
+        const atomicElementMap: Map<Element, number[]> = new Map()
+        /** map from sphere element to cross link indices */
+        const sphereElementMap: Map<Element, number[]> = new Map()
+        /** map from gaussian element to cross link indices */
+        const gaussianElementMap: Map<Element, number[]> = new Map()
+
+        const emptyIndexArray: number[] = [];
+
+        for (let i = 0; i < ihm_cross_link_restraint._rowCount; ++i) {
+            add(i, p1)
+            add(i, p2)
+        }
+
+        const crossLinkRestraint = {
+            getIndicesByElement: (element: Element, kind: Unit.Kind) => {
+                const map = getMapByKind(kind)
+                const idx = map.get(element)
+                return idx !== undefined ? idx : emptyIndexArray
+            },
+            data: ihm_cross_link_restraint
+        }
+        model.properties[PropName] = crossLinkRestraint
+        return crossLinkRestraint
+    }
+}

+ 9 - 0
src/mol-model/structure/structure/structure.ts

@@ -16,10 +16,12 @@ import { CoarseElements } from '../model/properties/coarse';
 import { StructureSubsetBuilder } from './util/subset-builder';
 import { Queries } from '../query';
 import { InterUnitBonds, computeInterUnitBonds } from './unit/links';
+import { CrossLinkRestraints, extractCrossLinkRestraints } from './unit/pair-restraints';
 
 class Structure {
     readonly unitMap: IntMap<Unit>;
     readonly units: ReadonlyArray<Unit>;
+    /** Count of all elements in the structure, i.e. the sum of the elements in the units */
     readonly elementCount: number;
 
     private _hashCode = 0;
@@ -68,6 +70,13 @@ class Structure {
         return this._links;
     }
 
+    private _crossLinkRestraints?: CrossLinkRestraints = void 0;
+    get crossLinkRestraints() {
+        if (this._crossLinkRestraints) return this._crossLinkRestraints;
+        this._crossLinkRestraints = extractCrossLinkRestraints(this);
+        return this._crossLinkRestraints;
+    }
+
     constructor(units: ArrayLike<Unit>) {
         const map = IntMap.Mutable<Unit>();
         let elementCount = 0;

+ 9 - 0
src/mol-model/structure/structure/unit/pair-restraints.ts

@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export * from './pair-restraints/data'
+export * from './pair-restraints/extract-cross-links'
+// export * from './pair-restraints/extract-predicted_contacts'

+ 60 - 0
src/mol-model/structure/structure/unit/pair-restraints/data.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Unit from '../../unit';
+
+const emptyArray: number[] = []
+
+class CrossLinkRestraints {
+    readonly count: number
+    private readonly pairKeyIndices: Map<string, number[]>
+
+    /** Indices into this.pairs */
+    getPairIndices(indexA: number, unitA: Unit, indexB: number, unitB: Unit): ReadonlyArray<number> {
+        const key = CrossLinkRestraints.getPairKey(indexA, unitA, indexB, unitB)
+        const indices = this.pairKeyIndices.get(key)
+        return indices !== undefined ? indices : emptyArray
+    }
+
+    getPairs(indexA: number, unitA: Unit, indexB: number, unitB: Unit): CrossLinkRestraints.Pair[] | undefined {
+        const indices = this.getPairIndices(indexA, unitA, indexB, unitB)
+        return indices.length ? indices.map(idx => this.pairs[idx]) : undefined
+    }
+
+    constructor(public pairs: ReadonlyArray<CrossLinkRestraints.Pair>) {
+        const pairKeyIndices = new Map<string, number[]>()
+        this.pairs.forEach((p, i) => {
+            const key = CrossLinkRestraints.getPairKey(p.indexA, p.unitA, p.indexB, p.unitB)
+            const indices = pairKeyIndices.get(key)
+            if (indices) indices.push(i)
+            else pairKeyIndices.set(key, [i])
+        })
+
+        this.count = pairs.length
+        this.pairKeyIndices = pairKeyIndices
+    }
+}
+
+namespace CrossLinkRestraints {
+    export interface Pair {
+        readonly unitA: Unit,
+        readonly unitB: Unit,
+        readonly indexA: number,
+        readonly indexB: number,
+
+        readonly restraintType: 'harmonic' | 'upper bound' | 'lower bound',
+        readonly distanceThreshold: number,
+        readonly psi: number,
+        readonly sigma1: number,
+        readonly sigma2: number,
+    }
+
+    export function getPairKey(indexA: number, unitA: Unit, indexB: number, unitB: Unit) {
+        return `${indexA}|${unitA.id}|${indexB}|${unitB.id}`
+    }
+}
+
+export { CrossLinkRestraints }

+ 105 - 0
src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts

@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Unit from '../../unit';
+import Structure from '../../structure';
+import { IHMCrossLinkRestraint } from '../../../model/formats/mmcif/pair-restraint';
+import { CrossLinkRestraints } from './data';
+
+function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) {
+    const { elements } = unit;
+    const elementCount = elements.length;
+    const kind = unit.kind
+
+    for (let i = 0; i < elementCount; i++) {
+        const e = elements[i];
+        restraints.getIndicesByElement(e, kind).forEach(ri => map.set(ri, i))
+    }
+}
+
+function extractInter(pairs: CrossLinkRestraints.Pair[], unitA: Unit, unitB: Unit) {
+    if (unitA.model !== unitB.model) return
+    if (unitA.model.sourceData.kind !== 'mmCIF') return
+
+    const restraints = IHMCrossLinkRestraint.fromModel(unitA.model)
+    if (!restraints) return
+
+    const rA = new Map<number, number>();
+    const rB = new Map<number, number>();
+    _addRestraints(rA, unitA, restraints)
+    _addRestraints(rB, unitB, restraints)
+
+    rA.forEach((indexA, ri) => {
+        const indexB = rB.get(ri)
+        if (indexB !== undefined) {
+            pairs.push(
+                createCrossLinkRestraint(unitA, indexA, unitB, indexB, restraints, ri),
+                createCrossLinkRestraint(unitB, indexB, unitA, indexA, restraints, ri)
+            )
+        }
+    })
+}
+
+function extractIntra(pairs: CrossLinkRestraints.Pair[], unit: Unit) {
+    if (unit.model.sourceData.kind !== 'mmCIF') return
+
+    const restraints = IHMCrossLinkRestraint.fromModel(unit.model)
+    if (!restraints) return
+
+    const { elements } = unit;
+    const elementCount = elements.length;
+    const kind = unit.kind
+
+    const r = new Map<number, number[]>();
+
+    for (let i = 0; i < elementCount; i++) {
+        const e = elements[i];
+        restraints.getIndicesByElement(e, kind).forEach(ri => {
+            const il = r.get(ri)
+            if (il) il.push(i)
+            else r.set(ri, [i])
+        })
+    }
+
+    r.forEach((il, ri) => {
+        if (il.length < 2) return
+        const [ indexA, indexB ] = il
+        pairs.push(
+            createCrossLinkRestraint(unit, indexA, unit, indexB, restraints, ri),
+            createCrossLinkRestraint(unit, indexB, unit, indexA, restraints, ri)
+        )
+    })
+}
+
+function createCrossLinkRestraint(unitA: Unit, indexA: number, unitB: Unit, indexB: number, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraints.Pair {
+    return {
+        unitA, indexA, unitB, indexB,
+
+        restraintType: restraints.data.restraint_type.value(row),
+        distanceThreshold: restraints.data.distance_threshold.value(row),
+        psi: restraints.data.psi.value(row),
+        sigma1: restraints.data.sigma_1.value(row),
+        sigma2: restraints.data.sigma_2.value(row),
+    }
+}
+
+function extractCrossLinkRestraints(structure: Structure): CrossLinkRestraints {
+    const pairs: CrossLinkRestraints.Pair[] = []
+
+    const n = structure.units.length
+    for (let i = 0; i < n; ++i) {
+        const unitA = structure.units[i]
+        extractIntra(pairs, unitA)
+        for (let j = i + 1; j < n; ++j) {
+            const unitB = structure.units[j]
+            extractInter(pairs, unitA, unitB)
+        }
+    }
+
+    return new CrossLinkRestraints(pairs)
+}
+
+export { extractCrossLinkRestraints };

+ 7 - 0
src/mol-model/structure/structure/unit/pair-restraints/extract-predicted-contacts.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// TODO extract from `ihm_predicted_contact_restraint`

+ 13 - 7
src/mol-view/stage.ts

@@ -7,7 +7,7 @@
 import Viewer from 'mol-view/viewer'
 import { StateContext } from './state/context';
 import { Progress } from 'mol-task';
-import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick } from './state/transform';
+import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick, StructureToDistanceRestraint } from './state/transform';
 import { UrlEntity } from './state/entity';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { Context } from 'mol-app/context/context';
@@ -15,15 +15,17 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-sti
 
 const spacefillProps: SpacefillProps = {
     doubleSided: true,
-    colorTheme: { name: 'atom-index' },
-    quality: 'medium'
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
 }
 
 const ballAndStickProps: BallAndStickProps = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     sizeTheme: { name: 'uniform', value: 0.25 },
-    quality: 'medium'
+    quality: 'auto',
+    useFog: false
 }
 
 export class Stage {
@@ -42,10 +44,13 @@ export class Stage {
         // this.loadPdbid('1jj2')
         // this.loadPdbid('4umt') // ligand has bond with order 3
         // this.loadPdbid('1crn') // small
+        // this.loadPdbid('1rb8') // virus
         // this.loadPdbid('1blu') // metal coordination
-        this.loadPdbid('3pqr') // inter unit bonds
+        // this.loadPdbid('3pqr') // inter unit bonds
         // this.loadPdbid('4v5a') // ribosome
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
+
+        this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`)
     }
 
     async loadMmcifUrl (url: string) {
@@ -53,8 +58,9 @@ export class Stage {
         const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity)
         const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
 
-        StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false })
-        StructureToBallAndStick.apply(this.ctx, structureEntity, ballAndStickProps)
+        StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: true })
+        // StructureToBallAndStick.apply(this.ctx, structureEntity, ballAndStickProps)
+        StructureToDistanceRestraint.apply(this.ctx, structureEntity, ballAndStickProps)
 
         this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
     }

+ 8 - 0
src/mol-view/state/entity.ts

@@ -14,6 +14,7 @@ import { Model, Structure } from 'mol-model/structure';
 import { StructureRepresentation } from 'mol-geo/representation/structure';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 
 const getNextId = idFactory(1)
 
@@ -127,4 +128,11 @@ export namespace BallAndStickEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BallAndStickProps>): BallAndStickEntity {
         return StateEntity.create(ctx, 'ballandstick', repr )
     }
+}
+
+export type DistanceRestraintEntity = StateEntity<StructureRepresentation<DistanceRestraintProps>, 'distancerestraint'>
+export namespace DistanceRestraintEntity {
+    export function ofRepr(ctx: StateContext, repr: StructureRepresentation<DistanceRestraintProps>): DistanceRestraintEntity {
+        return StateEntity.create(ctx, 'distancerestraint', repr )
+    }
 }

+ 47 - 24
src/mol-view/state/transform.ts

@@ -5,13 +5,14 @@
  */
 
 import CIF from 'mol-io/reader/cif'
-import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity } from './entity';
+import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity, DistanceRestraintEntity } from './entity';
 import { Model, Structure, Format } from 'mol-model/structure';
 
 import { StateContext } from './context';
 import StructureSymmetry from 'mol-model/structure/structure/symmetry';
 import { SpacefillProps, SpacefillRepresentation } from 'mol-geo/representation/structure/spacefill';
 import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/representation/structure/ball-and-stick';
+import { DistanceRestraintRepresentation, DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
 
@@ -85,11 +86,11 @@ export const ModelToStructure: ModelToStructure = StateTransform.create('model',
     })
 
 export type StructureCenter = StateTransform<StructureEntity, NullEntity, {}>
-    export const StructureCenter: StructureCenter = StateTransform.create('structure', 'null', 'structure-center',
-        async function (ctx: StateContext, structureEntity: StructureEntity) {
-            ctx.viewer.center(structureEntity.value.boundary.sphere.center)
-            return NullEntity
-        })
+export const StructureCenter: StructureCenter = StateTransform.create('structure', 'null', 'structure-center',
+    async function (ctx: StateContext, structureEntity: StructureEntity) {
+        ctx.viewer.center(structureEntity.value.boundary.sphere.center)
+        return NullEntity
+    })
 
 export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps>
 export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill',
@@ -103,15 +104,26 @@ export const StructureToSpacefill: StructureToSpacefill = StateTransform.create(
     })
 
 export type StructureToBallAndStick = StateTransform<StructureEntity, BallAndStickEntity, BallAndStickProps>
-    export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.create('structure', 'ballandstick', 'structure-to-ballandstick',
-        async function (ctx: StateContext, structureEntity: StructureEntity, props: BallAndStickProps = {}) {
-            const ballAndStickRepr = BallAndStickRepresentation()
-            await ballAndStickRepr.create(structureEntity.value, props).run(ctx.log)
-            ctx.viewer.add(ballAndStickRepr)
-            ctx.viewer.requestDraw()
-            console.log('stats', ctx.viewer.stats)
-            return BallAndStickEntity.ofRepr(ctx, ballAndStickRepr)
-        })
+export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.create('structure', 'ballandstick', 'structure-to-ballandstick',
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: BallAndStickProps = {}) {
+        const ballAndStickRepr = BallAndStickRepresentation()
+        await ballAndStickRepr.create(structureEntity.value, props).run(ctx.log)
+        ctx.viewer.add(ballAndStickRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return BallAndStickEntity.ofRepr(ctx, ballAndStickRepr)
+    })
+
+export type StructureToDistanceRestraint = StateTransform<StructureEntity, DistanceRestraintEntity, DistanceRestraintProps>
+export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateTransform.create('structure', 'distancerestraint', 'structure-to-distancerestraint',
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: DistanceRestraintProps = {}) {
+        const distanceRestraintRepr = DistanceRestraintRepresentation()
+        await distanceRestraintRepr.create(structureEntity.value, props).run(ctx.log)
+        ctx.viewer.add(distanceRestraintRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr)
+    })
 
 export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps>
 export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update',
@@ -125,15 +137,26 @@ export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill
     })
 
 export type BallAndStickUpdate = StateTransform<BallAndStickEntity, NullEntity, BallAndStickProps>
-    export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('ballandstick', 'null', 'ballandstick-update',
-        async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: BallAndStickProps = {}) {
-            const ballAndStickRepr = ballAndStickEntity.value
-            await ballAndStickRepr.update(props).run(ctx.log)
-            ctx.viewer.add(ballAndStickRepr)
-            ctx.viewer.requestDraw()
-            console.log('stats', ctx.viewer.stats)
-            return NullEntity
-        })
+export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('ballandstick', 'null', 'ballandstick-update',
+    async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: BallAndStickProps = {}) {
+        const ballAndStickRepr = ballAndStickEntity.value
+        await ballAndStickRepr.update(props).run(ctx.log)
+        ctx.viewer.add(ballAndStickRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return NullEntity
+    })
+
+export type DistanceRestraintUpdate = StateTransform<DistanceRestraintEntity, NullEntity, DistanceRestraintProps>
+export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.create('distancerestraint', 'null', 'distancerestraint-update',
+    async function (ctx: StateContext, distanceRestraintEntity: DistanceRestraintEntity, props: DistanceRestraintProps = {}) {
+        const distanceRestraintRepr = distanceRestraintEntity.value
+        await distanceRestraintRepr.update(props).run(ctx.log)
+        ctx.viewer.add(distanceRestraintRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return NullEntity
+    })
 
 // composed