Browse Source

wip, interactions & valence-model label providers

Alexander Rose 5 years ago
parent
commit
f86b46076c

+ 23 - 0
src/mol-model-props/computed/chemistry/geometry.ts

@@ -27,6 +27,29 @@ export const enum AtomGeometry {
     Unknown = 8
 }
 
+export function geometryLabel(geometry: AtomGeometry): string {
+    switch (geometry) {
+        case AtomGeometry.Spherical:
+            return 'Spherical'
+        case AtomGeometry.Terminal:
+            return 'Terminal'
+        case AtomGeometry.Linear:
+            return 'Linear'
+        case AtomGeometry.Trigonal:
+            return 'Trigonal'
+        case AtomGeometry.Tetrahedral:
+            return 'Tetrahedral'
+        case AtomGeometry.TrigonalBiPyramidal:
+            return 'Trigonal Bi-Pyramidal'
+        case AtomGeometry.Octahedral:
+            return 'Octahedral'
+        case AtomGeometry.SquarePlanar:
+            return 'Square Planar'
+        case AtomGeometry.Unknown:
+            return 'Unknown'
+    }
+}
+
 export function assignGeometry (totalCoordination: number): AtomGeometry {
     switch (totalCoordination) {
         case 0: return AtomGeometry.Spherical

+ 85 - 0
src/mol-model-props/computed/interactions/common.ts

@@ -31,6 +31,31 @@ export const enum InteractionType {
     BackboneHydrogenBond = 10
 }
 
+export function interactionTypeLabel(type: InteractionType): string {
+    switch (type) {
+        case InteractionType.HydrogenBond:
+        case InteractionType.WaterHydrogenBond:
+        case InteractionType.BackboneHydrogenBond:
+            return 'Hydrogen Bond'
+        case InteractionType.Hydrophobic:
+            return 'Hydrophobic Contact'
+        case InteractionType.HalogenBond:
+            return 'Halogen Bond'
+        case InteractionType.IonicInteraction:
+            return 'Ionic Interaction'
+        case InteractionType.MetalCoordination:
+            return 'Metal Coordination'
+        case InteractionType.CationPi:
+            return 'Cation-Pi Interaction'
+        case InteractionType.PiStacking:
+            return 'Pi Stacking'
+        case InteractionType.WeakHydrogenBond:
+            return 'Weak Hydrogen Bond'
+        case InteractionType.Unknown:
+            return 'Unknown Interaction'
+    }
+}
+
 export const enum FeatureType {
     None = 0,
     PositiveCharge = 1,
@@ -48,6 +73,39 @@ export const enum FeatureType {
     IonicTypeMetal = 13
 }
 
+export function featureTypeLabel(type: FeatureType): string {
+    switch (type) {
+        case FeatureType.None:
+            return 'None'
+        case FeatureType.PositiveCharge:
+            return 'Positive Charge'
+        case FeatureType.NegativeCharge:
+            return 'Negative Charge'
+        case FeatureType.AromaticRing:
+            return 'Aromatic Ring'
+        case FeatureType.HydrogenDonor:
+            return 'Hydrogen Donor'
+        case FeatureType.HydrogenAcceptor:
+            return 'Hydrogen Acceptor'
+        case FeatureType.HalogenDonor:
+            return 'Halogen Donor'
+        case FeatureType.HalogenAcceptor:
+            return 'Halogen Acceptor'
+        case FeatureType.Hydrophobic:
+            return 'Hydrophobic'
+        case FeatureType.WeakHydrogenDonor:
+            return 'Weak Hydrogen Donor'
+        case FeatureType.IonicTypePartner:
+            return 'Ionic Type Partner'
+        case FeatureType.DativeBondPartner:
+            return 'Dative Bond Partner'
+        case FeatureType.TransitionMetal:
+            return 'Transition Metal'
+        case FeatureType.IonicTypeMetal:
+            return 'Ionic Type Metal'
+    }
+}
+
 export const enum FeatureGroup {
     None = 0,
     QuaternaryAmine = 1,
@@ -60,4 +118,31 @@ export const enum FeatureGroup {
     Guanidine = 8,
     Acetamidine = 9,
     Carboxylate = 10
+}
+
+export function featureGroupLabel(group: FeatureGroup): string {
+    switch (group) {
+        case FeatureGroup.None:
+            return 'None'
+        case FeatureGroup.QuaternaryAmine:
+            return 'Quaternary Amine'
+        case FeatureGroup.TertiaryAmine:
+            return 'Tertiary Amine'
+        case FeatureGroup.Sulfonium:
+            return 'Sulfonium'
+        case FeatureGroup.SulfonicAcid:
+            return 'Sulfonic Acid'
+        case FeatureGroup.Sulfate:
+            return 'Sulfate'
+        case FeatureGroup.Phosphate:
+            return 'Phosphate'
+        case FeatureGroup.Halocarbon:
+            return 'Halocarbon'
+        case FeatureGroup.Guanidine:
+            return 'Guanidine'
+        case FeatureGroup.Acetamidine:
+            return 'Acetamidine'
+        case FeatureGroup.Carboxylate:
+            return 'Carboxylate'
+    }
 }

+ 37 - 9
src/mol-model-props/computed/interactions/features.ts

@@ -13,21 +13,24 @@ import { FeatureGroup, FeatureType } from './common';
 export { Features }
 
 interface Features {
+    /** number of features */
+    readonly count: number
     /** center x coordinate, in invariant coordinate space */
     readonly x: ArrayLike<number>
     /** center y coordinate, in invariant coordinate space */
     readonly y: ArrayLike<number>
     /** center z coordinate, in invariant coordinate space */
     readonly z: ArrayLike<number>
-    /** number of features */
-    readonly count: number
     readonly types: ArrayLike<FeatureType>
     readonly groups: ArrayLike<FeatureGroup>
     readonly offsets: ArrayLike<number>
     /** elements of this feature, range for feature i is offsets[i] to offsets[i + 1] */
     readonly members: ArrayLike<StructureElement.UnitIndex>
+
     /** lookup3d based on center coordinates, in invariant coordinate space */
     readonly lookup3d: GridLookup3D
+    /** maps unit elements to features, range for unit element i is offsets[i] to offsets[i + 1] */
+    readonly elementsIndex: Features.ElementsIndex
 }
 
 namespace Features {
@@ -42,11 +45,22 @@ namespace Features {
         readonly offsets: ArrayLike<number>
     }
 
-    export function createElementsIndex(features: Features, elementsCount: number): ElementsIndex {
+    export type Data = {
+        count: number
+        x: ArrayLike<number>
+        y: ArrayLike<number>
+        z: ArrayLike<number>
+        types: ArrayLike<FeatureType>
+        groups: ArrayLike<FeatureGroup>
+        offsets: ArrayLike<number>
+        members: ArrayLike<StructureElement.UnitIndex>
+    }
+
+    export function createElementsIndex(data: Data, elementsCount: number): ElementsIndex {
         const offsets = new Int32Array(elementsCount + 1)
         const bucketFill = new Int32Array(elementsCount)
         const bucketSizes = new Int32Array(elementsCount)
-        const { members, count, offsets: featureOffsets } = features
+        const { members, count, offsets: featureOffsets } = data
         for (let i = 0; i < count; ++i) ++bucketSizes[members[i]]
 
         let offset = 0
@@ -68,6 +82,21 @@ namespace Features {
 
         return { indices: indices as unknown as ArrayLike<FeatureIndex>, offsets }
     }
+
+    export function create(elementsCount: number, data: Data): Features {
+        let lookup3d: GridLookup3D
+        let elementsIndex: ElementsIndex
+
+        return {
+            ...data,
+            get lookup3d() {
+                return lookup3d || (lookup3d = GridLookup3D({ x: data.x, y: data.y, z: data.z, indices: OrderedSet.ofBounds(0, data.count) }))
+            },
+            get elementsIndex() {
+                return elementsIndex || (elementsIndex = createElementsIndex(data, elementsCount))
+            },
+        }
+    }
 }
 
 export { FeaturesBuilder }
@@ -77,7 +106,7 @@ interface FeaturesBuilder {
     pushMember: (x: number, y: number, z: number, member: StructureElement.UnitIndex) => void
     addState: (type: FeatureType, group: FeatureGroup) => void
     addOne: (type: FeatureType, group: FeatureGroup, x: number, y: number, z: number, member: StructureElement.UnitIndex) => void
-    getFeatures: () => Features
+    getFeatures: (elementsCount: number) => Features
 }
 
 namespace FeaturesBuilder {
@@ -121,20 +150,19 @@ namespace FeaturesBuilder {
                 ChunkedArray.add(offsets, members.elementCount)
                 ChunkedArray.add(members, member)
             },
-            getFeatures: () => {
+            getFeatures: (elementsCount: number) => {
                 ChunkedArray.add(offsets, members.elementCount)
                 const x = ChunkedArray.compact(xCenters, true) as ArrayLike<number>
                 const y = ChunkedArray.compact(yCenters, true) as ArrayLike<number>
                 const z = ChunkedArray.compact(zCenters, true) as ArrayLike<number>
                 const count = xCenters.elementCount
-                return {
+                return Features.create(elementsCount, {
                     x, y, z, count,
                     types: ChunkedArray.compact(types, true) as ArrayLike<FeatureType>,
                     groups: ChunkedArray.compact(groups, true) as ArrayLike<FeatureGroup>,
                     offsets: ChunkedArray.compact(offsets, true) as ArrayLike<number>,
                     members: ChunkedArray.compact(members, true) as ArrayLike<StructureElement.UnitIndex>,
-                    lookup3d: GridLookup3D({ x, y, z, indices: OrderedSet.ofBounds(0, count) }),
-                }
+                })
             }
         }
     }

+ 5 - 30
src/mol-model-props/computed/interactions/interactions.ts

@@ -10,7 +10,7 @@ import { RuntimeContext } from '../../../mol-task';
 import { addUnitHydrogenDonors, addUnitWeakHydrogenDonors, addUnitHydrogenAcceptors, addUnitHydrogenBonds, HydrogenBondsParams, addStructureHydrogenBonds } from './hydrogen-bonds';
 import { Features, FeaturesBuilder } from './features';
 import { ValenceModelProvider } from '../valence-model';
-import { InteractionsIntraLinks, InteractionsInterLinks, InteractionType } from './common';
+import { InteractionsIntraLinks, InteractionsInterLinks } from './common';
 import { IntraLinksBuilder, InterLinksBuilder } from './builder';
 import { IntMap } from '../../../mol-data/int';
 import { Vec3 } from '../../../mol-math/linear-algebra';
@@ -94,31 +94,6 @@ namespace Interactions {
     export function isLociEmpty(loci: Loci) {
         return loci.links.length === 0 ? true : false
     }
-
-    export function typeLabel(type: InteractionType): string {
-        switch (type) {
-            case InteractionType.HydrogenBond:
-            case InteractionType.WaterHydrogenBond:
-            case InteractionType.BackboneHydrogenBond:
-                return 'Hydrogen Bond'
-            case InteractionType.Hydrophobic:
-                return 'Hydrophobic Contact'
-            case InteractionType.HalogenBond:
-                return 'Halogen Bond'
-            case InteractionType.IonicInteraction:
-                return 'Ionic Interaction'
-            case InteractionType.MetalCoordination:
-                return 'Metal Coordination'
-            case InteractionType.CationPi:
-                return 'Cation-Pi Interaction'
-            case InteractionType.PiStacking:
-                return 'Pi Stacking'
-            case InteractionType.WeakHydrogenBond:
-                return 'Weak Hydrogen Bond'
-            case InteractionType.Unknown:
-                return 'Unknown Interaction'
-        }
-    }
 }
 
 export const InteractionsParams = {
@@ -150,16 +125,16 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
 }
 
 function findIntraUnitLinksAndFeatures(structure: Structure, unit: Unit, props: InteractionsProps) {
-
-    const featuresBuilder = FeaturesBuilder.create()
+    const count = unit.elements.length
+    const featuresBuilder = FeaturesBuilder.create(count, count / 2)
     if (Unit.isAtomic(unit)) {
         addUnitHydrogenDonors(structure, unit, featuresBuilder)
         addUnitWeakHydrogenDonors(structure, unit, featuresBuilder)
         addUnitHydrogenAcceptors(structure, unit, featuresBuilder)
     }
-    const features = featuresBuilder.getFeatures()
+    const features = featuresBuilder.getFeatures(count)
 
-    const linksBuilder = IntraLinksBuilder.create(features, unit.elements.length)
+    const linksBuilder = IntraLinksBuilder.create(features, count)
     if (Unit.isAtomic(unit)) {
         addUnitHydrogenBonds(structure, unit, features, linksBuilder, props.hydrogenBonds)
     }

+ 1 - 0
src/mol-plugin/behavior/dynamic/custom-props.ts

@@ -8,6 +8,7 @@
 export { AccessibleSurfaceArea } from './custom-props/computed/accessible-surface-area'
 export { Interactions } from './custom-props/computed/interactions'
 export { SecondaryStructure } from './custom-props/computed/secondary-structure'
+export { ValenceModel } from './custom-props/computed/valence-model'
 
 export { PDBeStructureQualityReport } from './custom-props/pdbe/structure-quality-report'
 export { RCSBAssemblySymmetry } from './custom-props/rcsb/assembly-symmetry'

+ 77 - 4
src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts

@@ -7,32 +7,105 @@
 import { PluginBehavior } from '../../../behavior';
 import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
 import { InteractionsProvider } from '../../../../../mol-model-props/computed/interactions';
+import { Structure } from '../../../../../mol-model/structure';
+import { StateSelection } from '../../../../../mol-state';
+import { PluginStateObject } from '../../../../state/objects';
+import StructureElement from '../../../../../mol-model/structure/structure/element';
+import { OrderedSet } from '../../../../../mol-data/int';
+import { featureGroupLabel, featureTypeLabel } from '../../../../../mol-model-props/computed/interactions/common';
+import { Loci } from '../../../../../mol-model/loci';
 
-export const Interactions = PluginBehavior.create<{ autoAttach: boolean }>({
+export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
     name: 'computed-interactions-prop',
     category: 'custom-props',
     display: { name: 'Interactions' },
-    ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
+    ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
         private provider = InteractionsProvider
 
+        private getStructures(structure: Structure) {
+            const structures: Structure[] = []
+            const root = this.ctx.helpers.substructureParent.get(structure)
+            if (root) {
+                const state = this.ctx.state.dataState
+                const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, root.transform.ref));
+                for (const s of selections) {
+                    if (s.obj) structures.push(s.obj.data)
+                }
+            }
+            return structures
+        }
+
+        private label = (loci: Loci): string | undefined => {
+            if (!this.params.showTooltip) return void 0;
+
+            switch (loci.kind) {
+                case 'element-loci':
+                    if (loci.elements.length === 0) return void 0;
+
+                    const labels: string[] = []
+                    const structures = this.getStructures(loci.structure)
+
+                    for (const s of structures) {
+                        const interactions = this.provider.getValue(s).value
+                        if (!interactions) continue;
+
+                        const l = StructureElement.Loci.remap(loci, s)
+                        if (l.elements.length !== 1) continue
+
+                        const e = l.elements[0]
+                        if (OrderedSet.size(e.indices) !== 1) continue
+
+                        const features = interactions.unitsFeatures.get(e.unit.id)
+                        if (!features) continue;
+
+                        const typeLabels: string[] = []
+                        const groupLabels: string[] = []
+                        const label: string[] = []
+
+                        const idx = OrderedSet.start(e.indices)
+                        const { types, groups, elementsIndex: { indices, offsets } } = features
+                        for (let i = offsets[idx], il = offsets[idx + 1]; i < il; ++i) {
+                            const f = indices[i]
+                            const type = types[f]
+                            const group = groups[f]
+                            if (type) typeLabels.push(featureTypeLabel(type))
+                            if (group) groupLabels.push(featureGroupLabel(group))
+                        }
+
+                        if (typeLabels.length) label.push(`<small>Types</small> ${typeLabels.join(', ')}`)
+                        if (groupLabels.length) label.push(`<small>Groups</small> ${groupLabels.join(', ')}`)
+                        if (label.length) labels.push(`Interaction Feature: ${label.join(' | ')}`)
+                    }
+
+                    return labels.length ? labels.join('<br/>') : undefined;
+
+                default: return void 0;
+            }
+        }
+
         update(p: { autoAttach: boolean, showTooltip: boolean }) {
             let updated = (
-                this.params.autoAttach !== p.autoAttach
+                this.params.autoAttach !== p.autoAttach ||
+                this.params.showTooltip !== p.showTooltip
             )
             this.params.autoAttach = p.autoAttach;
+            this.params.showTooltip = p.showTooltip;
             this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
             return updated;
         }
 
         register(): void {
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
+            this.ctx.lociLabels.addProvider(this.label);
         }
 
         unregister() {
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
+            this.ctx.lociLabels.removeProvider(this.label);
         }
     },
     params: () => ({
-        autoAttach: PD.Boolean(false)
+        autoAttach: PD.Boolean(false),
+        showTooltip: PD.Boolean(true)
     })
 });

+ 100 - 0
src/mol-plugin/behavior/dynamic/custom-props/computed/valence-model.ts

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginBehavior } from '../../../behavior';
+import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
+import { ValenceModelProvider } from '../../../../../mol-model-props/computed/valence-model';
+import { Loci } from '../../../../../mol-model/loci';
+import { PluginStateObject } from '../../../../state/objects';
+import { StateSelection } from '../../../../../mol-state';
+import { Structure, StructureElement } from '../../../../../mol-model/structure';
+import { OrderedSet } from '../../../../../mol-data/int';
+import { geometryLabel } from '../../../../../mol-model-props/computed/chemistry/geometry';
+
+export const ValenceModel = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
+    name: 'computed-valence-model-prop',
+    category: 'custom-props',
+    display: { name: 'Valence Model' },
+    ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
+        private provider = ValenceModelProvider
+
+        private getStructures(structure: Structure) {
+            const structures: Structure[] = []
+            const root = this.ctx.helpers.substructureParent.get(structure)
+            if (root) {
+                const state = this.ctx.state.dataState
+                const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, root.transform.ref));
+                for (const s of selections) {
+                    if (s.obj) structures.push(s.obj.data)
+                }
+            }
+            return structures
+        }
+
+        private label = (loci: Loci): string | undefined => {
+            if (!this.params.showTooltip) return void 0;
+
+            switch (loci.kind) {
+                case 'element-loci':
+                    if (loci.elements.length === 0) return void 0;
+
+                    const labels: string[] = []
+                    const structures = this.getStructures(loci.structure)
+
+                    for (const s of structures) {
+                        const valenceModel = this.provider.getValue(s).value
+                        if (!valenceModel) continue;
+
+                        const l = StructureElement.Loci.remap(loci, s)
+                        if (l.elements.length !== 1) continue
+
+                        const e = l.elements[0]
+                        if (OrderedSet.size(e.indices) !== 1) continue
+
+                        const vm = valenceModel.get(e.unit.id)
+                        if (!vm) continue;
+
+                        const idx = OrderedSet.start(e.indices)
+                        const charge = vm.charge[idx]
+                        const idealGeometry = vm.idealGeometry[idx]
+                        const implicitH = vm.implicitH[idx]
+                        const totalH = vm.totalH[idx]
+
+                        labels.push(`Valence Model: <small>Charge</small> ${charge} | <small>Ideal Geometry</small> ${geometryLabel(idealGeometry)} | <small>Implicit H</small> ${implicitH} | <small>Total H</small> ${totalH}`)
+                    }
+
+                    return labels.length ? labels.join('<br/>') : undefined;
+
+                default: return void 0;
+            }
+        }
+
+        update(p: { autoAttach: boolean, showTooltip: boolean }) {
+            let updated = (
+                this.params.autoAttach !== p.autoAttach ||
+                this.params.showTooltip !== p.showTooltip
+            )
+            this.params.autoAttach = p.autoAttach;
+            this.params.showTooltip = p.showTooltip;
+            this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
+            return updated;
+        }
+
+        register(): void {
+            this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
+            this.ctx.lociLabels.addProvider(this.label);
+        }
+
+        unregister() {
+            this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
+            this.ctx.lociLabels.removeProvider(this.label);
+        }
+    },
+    params: () => ({
+        autoAttach: PD.Boolean(false),
+        showTooltip: PD.Boolean(true)
+    })
+});

+ 1 - 0
src/mol-plugin/index.ts

@@ -71,6 +71,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Behavior(PluginBehaviors.CustomProps.AccessibleSurfaceArea),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
+        PluginSpec.Behavior(PluginBehaviors.CustomProps.ValenceModel),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }),
         PluginSpec.Behavior(StructureRepresentationInteraction)

+ 1 - 0
src/mol-plugin/util/substructure-parent-helper.ts

@@ -15,6 +15,7 @@ class SubstructureParentHelper {
     private root = new Map<Structure, { ref: string, count: number }>();
     private tracked = new Map<string, Structure>();
 
+    /** Returns the root node of given structure if existing */
     get(s: Structure): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined {
         const r = this.root.get(s);
         if (!r) return;

+ 3 - 2
src/mol-theme/label.ts

@@ -11,6 +11,7 @@ import { OrderedSet } from '../mol-data/int';
 import { capitalize, stripTags } from '../mol-util/string';
 import { Column } from '../mol-data/db';
 import { Interactions } from '../mol-model-props/computed/interactions/interactions';
+import { interactionTypeLabel } from '../mol-model-props/computed/interactions/common';
 
 export type LabelGranularity = 'element' | 'conformation' | 'residue' | 'chain' | 'structure'
 
@@ -132,10 +133,10 @@ export function interactionLabel(location: Interactions.Location): string {
     if (location.unitA === location.unitB) {
         const links = interactions.unitsLinks.get(location.unitA.id)
         const idx = links.getDirectedEdgeIndex(location.indexA, location.indexB)
-        return Interactions.typeLabel(links.edgeProps.type[idx])
+        return interactionTypeLabel(links.edgeProps.type[idx])
     } else {
         const idx = interactions.links.getEdgeIndex(indexA, unitA, indexB, unitB)
-        return Interactions.typeLabel(interactions.links.edges[idx].props.type)
+        return interactionTypeLabel(interactions.links.edges[idx].props.type)
     }
 }