Browse Source

wip, interactions refinement

- more links to contacts renaming
- line-of-sight check
- hydrophobic contact refinement
Alexander Rose 5 years ago
parent
commit
6dfe975fc7

+ 15 - 4
src/mol-model-props/computed/interactions/common.ts

@@ -7,14 +7,25 @@
 import { IntAdjacencyGraph } from '../../../mol-math/graph'
 import { InterUnitGraph } from '../../../mol-math/graph/inter-unit-graph'
 import { Unit } from '../../../mol-model/structure'
+import { AssignableArrayLike } from '../../../mol-util/type-helpers'
 
-export type InteractionsIntraContacts = IntAdjacencyGraph<{ readonly type: ArrayLike<InteractionType> }>
+type IntraProps = {
+    readonly type: ArrayLike<InteractionType>
+    readonly flag: AssignableArrayLike<InteractionFlag>
+}
+export type InteractionsIntraContacts = IntAdjacencyGraph<IntraProps>
 
 export { InteractionsInterContacts }
-type InteractionsInterContacts = InterUnitGraph<Unit, number, { type: InteractionType }>
+type InterProps = { type: InteractionType, flag: InteractionFlag }
+type InteractionsInterContacts = InterUnitGraph<Unit, number, InterProps>
 namespace InteractionsInterContacts {
-    export class Pair extends InterUnitGraph.UnitPairEdges<Unit, number, { type: InteractionType }> {}
-    export type Info = InterUnitGraph.EdgeInfo<number, { type: InteractionType }>
+    export class Pair extends InterUnitGraph.UnitPairEdges<Unit, number, InterProps> {}
+    export type Info = InterUnitGraph.EdgeInfo<number, InterProps>
+}
+
+export const enum InteractionFlag {
+    None = 0,
+    Filtered = 1,
 }
 
 export const enum InteractionType {

+ 10 - 8
src/mol-model-props/computed/interactions/contacts-builder.ts

@@ -6,16 +6,17 @@
 
 import { Features } from './features';
 import { IntAdjacencyGraph } from '../../../mol-math/graph';
-import { InteractionType, InteractionsIntraContacts, InteractionsInterContacts } from './common';
+import { InteractionType, InteractionsIntraContacts, InteractionsInterContacts, InteractionFlag } from './common';
 import { Unit, StructureElement } from '../../../mol-model/structure/structure';
 import { InterUnitGraph } from '../../../mol-math/graph/inter-unit-graph';
 import { UniqueArray } from '../../../mol-data/generic';
+import { NumberArray } from '../../../mol-util/type-helpers';
 
 export { IntraContactsBuilder }
 
 interface IntraContactsBuilder {
     add: (indexA: number, indexB: number, type: InteractionType) => void
-    getLinks: () => InteractionsIntraContacts
+    getContacts: () => InteractionsIntraContacts
 }
 
 namespace IntraContactsBuilder {
@@ -30,14 +31,15 @@ namespace IntraContactsBuilder {
                 bIndices[bIndices.length] = indexB
                 types[types.length] = type
             },
-            getLinks() {
+            getContacts() {
                 const builder = new IntAdjacencyGraph.EdgeBuilder(features.count, aIndices, bIndices)
                 const type = new Int8Array(builder.slotCount) as ArrayLike<InteractionType>
+                const flag = new Int8Array(builder.slotCount) as NumberArray
                 for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
                     builder.addNextEdge()
                     builder.assignProperty(type, types[i])
                 }
-                return builder.createGraph({ type })
+                return builder.createGraph({ type, flag })
             }
         }
     }
@@ -49,7 +51,7 @@ interface InterContactsBuilder {
     startUnitPair: (unitA: Unit, unitB: Unit) => void
     finishUnitPair: () => void
     add: (indexA: number, indexB: number, type: InteractionType) => void
-    getLinks: () => InteractionsInterContacts
+    getContacts: () => InteractionsInterContacts
 }
 
 namespace InterContactsBuilder {
@@ -85,13 +87,13 @@ namespace InterContactsBuilder {
                 addMapEntry(map, uB.id, new InteractionsInterContacts.Pair(uB, uA, bondCount, bondedB.array, mapBA))
             },
             add(indexA: number, indexB: number, type: InteractionType) {
-                addMapEntry(mapAB, indexA, { indexB, props: { type } })
-                addMapEntry(mapBA, indexB, { indexB: indexA, props: { type } })
+                addMapEntry(mapAB, indexA, { indexB, props: { type, flag: InteractionFlag.None } })
+                addMapEntry(mapBA, indexB, { indexB: indexA, props: { type, flag: InteractionFlag.None } })
                 UniqueArray.add(bondedA, indexA, indexA)
                 UniqueArray.add(bondedB, indexB, indexB)
                 bondCount += 1
             },
-            getLinks() {
+            getContacts() {
                 return new InterUnitGraph(map);
             }
         }

+ 101 - 11
src/mol-model-props/computed/interactions/contacts.ts

@@ -5,15 +5,23 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { Structure, Unit } from '../../../mol-model/structure';
+import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
 import { Features } from './features';
 import { InteractionType, FeatureType } from './common';
 import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
 import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
-import { altLoc, connectedTo } from '../chemistry/util';
+import { altLoc, connectedTo, typeSymbol } from '../chemistry/util';
 import { OrderedSet } from '../../../mol-data/int';
+import { VdwRadius } from '../../../mol-model/structure/model/properties/atomic';
+import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
 
-const MAX_DISTANCE = 5
+export const ContactsParams = {
+    lineOfSightDistFactor: PD.Numeric(1.0, { min: 0, max: 3, step: 0.1 }),
+}
+export type ContactsParams = typeof ContactsParams
+export type ContactsProps = PD.Values<ContactsParams>
+
+const MAX_LINE_OF_SIGHT_DISTANCE = 3
 
 export interface ContactProvider<P extends PD.Params> {
     readonly name: string
@@ -41,23 +49,101 @@ function validPair(structure: Structure, infoA: Features.Info, infoB: Features.I
     return true
 }
 
+//
+
+function invalidAltLoc (unitA: Unit.Atomic, indexA: StructureElement.UnitIndex, unitB: Unit.Atomic, indexB: StructureElement.UnitIndex) {
+    const altA = altLoc(unitA, indexA)
+    const altB = altLoc(unitB, indexB)
+    return altA && altB && altA !== altB
+}
+
+function isMember(element: StructureElement.UnitIndex, info: Features.Info) {
+    const { feature, offsets, members } = info
+    for (let i = offsets[feature], il = offsets[feature + 1]; i < il; ++i) {
+        if (members[i] === element) return true
+    }
+    return false
+}
+
+const tmpVec = Vec3()
+const tmpVecA = Vec3()
+const tmpVecB = Vec3()
+
+function _checkLineOfSight(infoA: Features.Info, infoB: Features.Info, distFactor: number) {
+    const unit = infoA.unit
+    const featureA = infoA.feature
+    const featureB = infoB.feature
+    const indexA = infoA.members[infoA.offsets[featureA]]
+    const indexB = infoB.members[infoB.offsets[featureB]]
+
+    Vec3.set(tmpVecA, infoA.x[featureA], infoA.y[featureA], infoA.z[featureA])
+    Vec3.transformMat4(tmpVecA, tmpVecA, infoA.unit.conformation.operator.matrix)
+    Vec3.set(tmpVecB, infoB.x[featureB], infoB.y[featureB], infoB.z[featureB])
+    Vec3.transformMat4(tmpVecB, tmpVecB, infoB.unit.conformation.operator.matrix)
+    Vec3.scale(tmpVec, Vec3.add(tmpVec, tmpVecA, tmpVecB), 0.5)
+
+    const pos = unit.conformation.position
+    const distMax = distFactor * MAX_LINE_OF_SIGHT_DISTANCE
+
+    const { count, indices, squaredDistances } = unit.lookup3d.find(tmpVec[0], tmpVec[1], tmpVec[2], distMax)
+    if (count === 0) true
+
+    for (let r = 0; r < count; ++r) {
+        const i = indices[r] as StructureElement.UnitIndex
+
+        const element = typeSymbol(unit, i)
+        // allow hydrogens
+        if (element === Elements.H) continue
+
+        const vdw = VdwRadius(element)
+        // check distance
+        if (vdw * vdw * distFactor * distFactor <= squaredDistances[r]) continue
+
+        // allow different altlocs
+        if (invalidAltLoc(unit, i, infoA.unit, indexA) || invalidAltLoc(unit, i, infoB.unit, indexB)) continue
+
+        // allow member atoms
+        if (isMember(i, infoA) || (infoB.unit === unit && isMember(i, infoB))) continue
+
+        pos(unit.elements[i], tmpVec)
+        // allow atoms at the center of functional groups
+        if (Vec3.squaredDistance(tmpVec, tmpVecA) < 1 || Vec3.squaredDistance(tmpVec, tmpVecB) < 1) continue
+
+        return false
+    }
+
+    return true
+}
+
+function checkLineOfSight(infoA: Features.Info, infoB: Features.Info, distFactor: number) {
+    if (infoA.unit === infoB.unit) {
+        return _checkLineOfSight(infoA, infoB, distFactor)
+    } else {
+        return (
+            _checkLineOfSight(infoA, infoB, distFactor) &&
+            _checkLineOfSight(infoB, infoA, distFactor)
+        )
+    }
+}
+
 /**
  * Add all intra-unit contacts, i.e. pairs of features
  */
-export function addUnitContacts(structure: Structure, unit: Unit.Atomic, features: Features, builder: IntraContactsBuilder, testers: ReadonlyArray<ContactTester>) {
-
+export function addUnitContacts(structure: Structure, unit: Unit.Atomic, features: Features, builder: IntraContactsBuilder, testers: ReadonlyArray<ContactTester>, props: ContactsProps) {
     for (const tester of testers) {
-        _addUnitContacts(structure, unit, features, builder, tester)
+        _addUnitContacts(structure, unit, features, builder, tester, props)
     }
 }
 
-function _addUnitContacts(structure: Structure, unit: Unit.Atomic, features: Features, builder: IntraContactsBuilder, tester: ContactTester) {
+function _addUnitContacts(structure: Structure, unit: Unit.Atomic, features: Features, builder: IntraContactsBuilder, tester: ContactTester, props: ContactsProps) {
     const { x, y, z } = features
     const { lookup3d, indices: subsetIndices } = features.subset(tester.requiredFeatures)
 
     const infoA = Features.Info(structure, unit, features)
     const infoB = { ...infoA }
 
+    const distFactor = props.lineOfSightDistFactor
+
     for (let t = 0, tl = OrderedSet.size(subsetIndices); t < tl; ++t) {
         const i = OrderedSet.getAt(subsetIndices, t)
         const { count, indices, squaredDistances } = lookup3d.find(x[i], y[i], z[i], tester.maxDistance)
@@ -73,7 +159,9 @@ function _addUnitContacts(structure: Structure, unit: Unit.Atomic, features: Fea
             if (!validPair(structure, infoA, infoB)) continue
 
             const type = tester.getType(structure, infoA, infoB, squaredDistances[r])
-            if (type) builder.add(i, j, type)
+            if (type && checkLineOfSight(infoA, infoB, distFactor)) {
+                builder.add(i, j, type)
+            }
         }
     }
 }
@@ -83,7 +171,7 @@ const _imageTransform = Mat4()
 /**
  * Add all inter-unit contacts, i.e. pairs of features
  */
-export function addStructureContacts(structure: Structure, unitA: Unit.Atomic, featuresA: Features, unitB: Unit.Atomic, featuresB: Features, builder: InterContactsBuilder, testers: ReadonlyArray<ContactTester>) {
+export function addStructureContacts(structure: Structure, unitA: Unit.Atomic, featuresA: Features, unitB: Unit.Atomic, featuresB: Features, builder: InterContactsBuilder, testers: ReadonlyArray<ContactTester>, props: ContactsProps) {
     const { count, x: xA, y: yA, z: zA } = featuresA;
     const { lookup3d } = featuresB;
 
@@ -97,6 +185,8 @@ export function addStructureContacts(structure: Structure, unitA: Unit.Atomic, f
     const { center, radius } = lookup3d.boundary.sphere;
     const testDistanceSq = (radius + maxDistance) * (radius + maxDistance);
 
+    const distFactor = props.lineOfSightDistFactor
+
     const infoA = Features.Info(structure, unitA, featuresA)
     const infoB = Features.Info(structure, unitB, featuresB)
 
@@ -107,7 +197,7 @@ export function addStructureContacts(structure: Structure, unitA: Unit.Atomic, f
         if (isNotIdentity) Vec3.transformMat4(imageA, imageA, imageTransform)
         if (Vec3.squaredDistance(imageA, center) > testDistanceSq) continue
 
-        const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_DISTANCE)
+        const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], maxDistance)
         if (count === 0) continue
 
         infoA.feature = i
@@ -121,7 +211,7 @@ export function addStructureContacts(structure: Structure, unitA: Unit.Atomic, f
             for (const tester of testers) {
                 if (distanceSq < tester.maxDistance * tester.maxDistance) {
                     const type = tester.getType(structure, infoA, infoB, distanceSq)
-                    if (type) {
+                    if (type && checkLineOfSight(infoA, infoB, distFactor)) {
                         builder.add(i, j, type)
                         break
                     }

+ 21 - 17
src/mol-model-props/computed/interactions/interactions.ts

@@ -13,13 +13,14 @@ import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType } fro
 import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
 import { IntMap } from '../../../mol-data/int';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { addUnitContacts, ContactTester, addStructureContacts, ContactProvider } from './contacts';
+import { addUnitContacts, ContactTester, addStructureContacts, ContactProvider, ContactsParams, ContactsProps } from './contacts';
 import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds';
 import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds';
 import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
 import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic';
 import { SetUtils } from '../../../mol-util/set';
 import { MetalCoordinationProvider, MetalProvider, MetalBindingProvider } from './metal';
+import { refineInteractions } from './refine';
 
 export { Interactions }
 
@@ -27,9 +28,9 @@ interface Interactions {
     /** Features of each unit */
     unitsFeatures: IntMap<Features>
     /** Interactions of each unit */
-    unitsLinks: IntMap<InteractionsIntraContacts>
+    unitsContacts: IntMap<InteractionsIntraContacts>
     /** Interactions between units */
-    links: InteractionsInterContacts
+    contacts: InteractionsInterContacts
 }
 
 namespace Interactions {
@@ -140,12 +141,13 @@ export const InteractionsParams = {
         'metal-coordination',
         // 'weak-hydrogen-bonds',
     ], PD.objectToOptions(LinkProviders)),
+    contacts: PD.Group(ContactsParams, { isFlat: true }),
     ...getProvidersParams()
 }
 export type InteractionsParams = typeof InteractionsParams
 export type InteractionsProps = PD.Values<InteractionsParams>
 
-export async function computeInteractions(runtime: RuntimeContext, structure: Structure, props: Partial<InteractionsProps>) {
+export async function computeInteractions(runtime: RuntimeContext, structure: Structure, props: Partial<InteractionsProps>): Promise<Interactions> {
     const p = { ...PD.getDefaultValues(InteractionsParams), ...props }
     await ValenceModelProvider.attach(structure).runInContext(runtime)
 
@@ -160,7 +162,7 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
     const featureProviders = FeatureProviders.filter(f => SetUtils.areIntersecting(requiredFeatures, f.types))
 
     const unitsFeatures = IntMap.Mutable<Features>()
-    const unitsLinks = IntMap.Mutable<InteractionsIntraContacts>()
+    const unitsContacts = IntMap.Mutable<InteractionsIntraContacts>()
 
     for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
         const group = structure.unitSymmetryGroups[i]
@@ -168,17 +170,19 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
             await runtime.update({ message: 'computing interactions', current: i, max: il })
         }
         const features = findUnitFeatures(structure, group.units[0], featureProviders)
-        const intraUnitLinks = findIntraUnitLinks(structure, group.units[0], features, linkTesters)
+        const intraUnitLinks = findIntraUnitContacts(structure, group.units[0], features, linkTesters, p.contacts)
         for (let j = 0, jl = group.units.length; j < jl; ++j) {
             const u = group.units[j]
             unitsFeatures.set(u.id, features)
-            unitsLinks.set(u.id, intraUnitLinks)
+            unitsContacts.set(u.id, intraUnitLinks)
         }
     }
 
-    const links = findInterUnitLinks(structure, unitsFeatures, linkTesters)
+    const contacts = findInterUnitContacts(structure, unitsFeatures, linkTesters, p.contacts)
 
-    return { unitsFeatures, unitsLinks, links }
+    const interactions = { unitsFeatures, unitsContacts, contacts }
+    refineInteractions(structure, interactions)
+    return interactions
 }
 
 function findUnitFeatures(structure: Structure, unit: Unit, featureProviders: Features.Provider[]) {
@@ -192,18 +196,18 @@ function findUnitFeatures(structure: Structure, unit: Unit, featureProviders: Fe
     return featuresBuilder.getFeatures(count)
 }
 
-function findIntraUnitLinks(structure: Structure, unit: Unit, features: Features, linkTesters: ReadonlyArray<ContactTester>) {
+function findIntraUnitContacts(structure: Structure, unit: Unit, features: Features, contactTesters: ReadonlyArray<ContactTester>, props: ContactsProps) {
     const linksBuilder = IntraContactsBuilder.create(features, unit.elements.length)
     if (Unit.isAtomic(unit)) {
-        addUnitContacts(structure, unit, features, linksBuilder, linkTesters)
+        addUnitContacts(structure, unit, features, linksBuilder, contactTesters, props)
     }
-    return linksBuilder.getLinks()
+    return linksBuilder.getContacts()
 }
 
-function findInterUnitLinks(structure: Structure, unitsFeatures: IntMap<Features>, linkTesters: ReadonlyArray<ContactTester>) {
+function findInterUnitContacts(structure: Structure, unitsFeatures: IntMap<Features>, contactTesters: ReadonlyArray<ContactTester>, props: ContactsProps) {
     const builder = InterContactsBuilder.create()
 
-    const maxDistance = Math.max(...linkTesters.map(t => t.maxDistance))
+    const maxDistance = Math.max(...contactTesters.map(t => t.maxDistance))
 
     const lookup = structure.lookup3d;
     const imageCenter = Vec3.zero();
@@ -224,12 +228,12 @@ function findInterUnitLinks(structure: Structure, unitsFeatures: IntMap<Features
             const featuresB = unitsFeatures.get(unitB.id)
 
             if (unitB.elements.length >= unitA.elements.length) {
-                addStructureContacts(structure, unitA, featuresA, unitB, featuresB, builder, linkTesters)
+                addStructureContacts(structure, unitA, featuresA, unitB, featuresB, builder, contactTesters, props)
             } else {
-                addStructureContacts(structure, unitB, featuresB, unitA, featuresA, builder, linkTesters)
+                addStructureContacts(structure, unitB, featuresB, unitA, featuresA, builder, contactTesters, props)
             }
         }
     }
 
-    return builder.getLinks()
+    return builder.getContacts()
 }

+ 106 - 0
src/mol-model-props/computed/interactions/refine.ts

@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ *
+ * based in part on NGL (https://github.com/arose/ngl)
+ */
+
+import { Interactions } from './interactions';
+import { InteractionType, InteractionFlag } from './common';
+import { Vec3 } from '../../../mol-math/linear-algebra';
+import { Unit, Structure } from '../../../mol-model/structure';
+
+export function refineInteractions(structure: Structure, interactions: Interactions) {
+    refineHydrophobicContacts(structure, interactions)
+}
+
+/**
+ * For atoms interacting with several atoms in the same residue
+ * only the one with the closest distance is kept.
+ */
+function refineHydrophobicContacts(structure: Structure, interactions: Interactions) {
+    const { contacts, unitsContacts, unitsFeatures } = interactions
+
+    /* keep only closest contact between residues */
+    const handleResidueContact = function (dist: number, i: number, key: string, map: Map<string, [number, number]>, set: (i: number) => void) {
+        const [minDist, minIndex] = map.get(key) || [Infinity, -1]
+        if (dist < minDist) {
+            if (minIndex !== -1) set(i)
+            map.set(key, [dist, i])
+        } else {
+            set(i)
+        }
+    }
+
+    const pA = Vec3()
+    const pB = Vec3()
+
+    //
+
+    const residueInterMap = new Map<string, [number, number]>()
+    const setInterFiltered = (i: number) => contacts.edges[i].props.flag = InteractionFlag.Filtered
+
+    for (let i = 0, il = contacts.edgeCount; i < il; ++i) {
+        const e = contacts.edges[i]
+        if (e.props.type !== InteractionType.Hydrophobic) continue
+
+        const featureA = e.indexA
+        const featureB = e.indexB
+
+        const { offsets: offsetsA, members: membersA } = unitsFeatures.get(e.unitA.id)
+        const { offsets: offsetsB, members: membersB } = unitsFeatures.get(e.unitB.id)
+
+        const elementA = membersA[offsetsA[featureA]]
+        const elementB = membersB[offsetsB[featureB]]
+        const residueA = (e.unitA as Unit.Atomic).getResidueIndex(elementA)
+        const residueB = (e.unitB as Unit.Atomic).getResidueIndex(elementB)
+
+        e.unitA.conformation.position(e.unitA.elements[elementA], pA)
+        e.unitB.conformation.position(e.unitB.elements[elementB], pB)
+        const dist = Vec3.distance(pA, pB)
+
+        const keyA = `${elementA}|${e.unitA.id}|${residueB}|${e.unitB.id}`
+        const keyB = `${elementB}|${e.unitB.id}|${residueA}|${e.unitA.id}`
+
+        handleResidueContact(dist, i, keyA, residueInterMap, setInterFiltered)
+        handleResidueContact(dist, i, keyB, residueInterMap, setInterFiltered)
+    }
+
+    //
+
+    const uc = unitsContacts.keys()
+
+    while (true) {
+        const { done, value } = uc.next();
+        if (done) break;
+
+        const contacts = unitsContacts.get(value)
+        const { offsets, members } = unitsFeatures.get(value)
+        const unit = structure.unitMap.get(value)
+
+        const residueIntraMap = new Map<string, [number, number]>()
+        const setIntraFiltered = (i: number) => contacts.edgeProps.flag[i] = InteractionFlag.Filtered
+
+        for (let i = 0, il = contacts.edgeCount * 2; i < il; ++i) {
+            if (contacts.edgeProps.type[i] !== InteractionType.Hydrophobic) continue
+
+            const featureA = contacts.a[i]
+            const featureB = contacts.b[i]
+            const elementA = members[offsets[featureA]]
+            const elementB = members[offsets[featureB]]
+            const residueA = (unit as Unit.Atomic).getResidueIndex(elementA)
+            const residueB = (unit as Unit.Atomic).getResidueIndex(elementB)
+
+            unit.conformation.position(unit.elements[elementA], pA)
+            unit.conformation.position(unit.elements[elementB], pB)
+            const dist = Vec3.distance(pA, pB)
+
+            const keyA = `${elementA}|${residueB}`
+            const keyB = `${elementB}|${residueA}}`
+
+            handleResidueContact(dist, i, keyA, residueIntraMap, setIntraFiltered)
+            handleResidueContact(dist, i, keyB, residueIntraMap, setIntraFiltered)
+        }
+    }
+}

+ 16 - 15
src/mol-repr/structure/visual/interactions-inter-unit-cylinder.ts

@@ -20,14 +20,15 @@ import { BondType } from '../../../mol-model/structure/model/types';
 import { Interactions } from '../../../mol-model-props/computed/interactions/interactions';
 import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
+import { InteractionFlag } from '../../../mol-model-props/computed/interactions/common';
 
 function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
     if (!structure.hasAtomic) return Mesh.createEmpty(mesh)
 
     const interactions = InteractionsProvider.getValue(structure).value!
-    const { links, unitsFeatures } = interactions
+    const { contacts, unitsFeatures } = interactions
 
-    const { edgeCount, edges } = links
+    const { edgeCount, edges } = contacts
     const { sizeFactor } = props
 
     if (!edgeCount) return Mesh.createEmpty(mesh)
@@ -47,7 +48,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
         order: (edgeIndex: number) => 1,
         flags: (edgeIndex: number) => BondType.Flag.MetallicCoordination, // TODO
         radius: (edgeIndex: number) => sizeFactor,
-        ignore: () => false
+        ignore: (edgeIndex: number) => edges[edgeIndex].props.flag === InteractionFlag.Filtered
     }
 
     return createBondCylinderMesh(ctx, builderProps, props, mesh)
@@ -87,10 +88,10 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
     const { objectId, groupId } = pickingId
     if (id === objectId) {
         const interactions = InteractionsProvider.getValue(structure).value!
-        const l = interactions.links.edges[groupId]
+        const c = interactions.contacts.edges[groupId]
         return Interactions.Loci(structure, interactions, [
-            { unitA: l.unitA, indexA: l.indexA, unitB: l.unitB, indexB: l.indexB },
-            { unitA: l.unitB, indexA: l.indexB, unitB: l.unitA, indexB: l.indexA },
+            { unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB },
+            { unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA },
         ])
     }
     return EmptyLoci
@@ -102,10 +103,10 @@ function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Int
         if (!Structure.areEquivalent(loci.structure, structure)) return false
         const interactions = InteractionsProvider.getValue(structure).value!
         if (loci.interactions !== interactions) return false
-        const { links } = interactions
+        const { contacts } = interactions
 
         for (const l of loci.links) {
-            const idx = links.getEdgeIndex(l.indexA, l.unitA, l.indexB, l.unitB)
+            const idx = contacts.getEdgeIndex(l.indexA, l.unitA, l.indexB, l.unitB)
             if (idx !== -1) {
                 if (apply(Interval.ofSingleton(idx))) changed = true
             }
@@ -116,16 +117,16 @@ function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Int
 
 function createInteractionsIterator(structure: Structure): LocationIterator {
     const interactions = InteractionsProvider.getValue(structure).value!
-    const { links } = interactions
-    const groupCount = links.edgeCount
+    const { contacts } = interactions
+    const groupCount = contacts.edgeCount
     const instanceCount = 1
     const location = Interactions.Location(interactions)
     const getLocation = (groupIndex: number) => {
-        const link = links.edges[groupIndex]
-        location.unitA = link.unitA
-        location.indexA = link.indexA
-        location.unitB = link.unitB
-        location.indexB = link.indexB
+        const c = contacts.edges[groupIndex]
+        location.unitA = c.unitA
+        location.indexA = c.indexA
+        location.unitB = c.unitB
+        location.indexB = c.indexB
         return location
     }
     return LocationIterator(groupCount, instanceCount, getLocation, true)

+ 14 - 13
src/mol-repr/structure/visual/interactions-intra-unit-cylinder.ts

@@ -20,16 +20,17 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '.
 import { VisualUpdateState } from '../../util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
 import { Interactions } from '../../../mol-model-props/computed/interactions/interactions';
+import { InteractionFlag } from '../../../mol-model-props/computed/interactions/common';
 
 async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
     const interactions = InteractionsProvider.getValue(structure).value!
     const features = interactions.unitsFeatures.get(unit.id)
-    const links = interactions.unitsLinks.get(unit.id)
+    const contacts = interactions.unitsContacts.get(unit.id)
 
     const { x, y, z } = features
-    const { edgeCount, a, b } = links
+    const { edgeCount, a, b, edgeProps: { flag } } = contacts
     const { sizeFactor } = props
     const { matrix } = unit.conformation.operator
 
@@ -47,7 +48,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
         order: (edgeIndex: number) => 1,
         flags: (edgeIndex: number) => BondType.Flag.MetallicCoordination, // TODO
         radius: (edgeIndex: number) => sizeFactor,
-        ignore: () => false
+        ignore: (edgeIndex: number) => flag[edgeIndex] === InteractionFlag.Filtered
     }
 
     return createBondCylinderMesh(ctx, builderProps, props, mesh)
@@ -89,10 +90,10 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
         const { structure, group } = structureGroup
         const unit = structure.unitMap.get(group.units[instanceId].id)
         const interactions = InteractionsProvider.getValue(structure).value!
-        const links = interactions.unitsLinks.get(unit.id)
+        const contacts = interactions.unitsContacts.get(unit.id)
         return Interactions.Loci(structure, interactions, [
-            { unitA: unit, indexA: links.a[groupId], unitB: unit, indexB: links.b[groupId] },
-            { unitA: unit, indexA: links.b[groupId], unitB: unit, indexB: links.a[groupId] },
+            { unitA: unit, indexA: contacts.a[groupId], unitB: unit, indexB: contacts.b[groupId] },
+            { unitA: unit, indexA: contacts.b[groupId], unitB: unit, indexB: contacts.a[groupId] },
         ])
     }
     return EmptyLoci
@@ -106,12 +107,12 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
         const interactions = InteractionsProvider.getValue(structure).value!
         if (loci.interactions !== interactions) return false
         const unit = group.units[0]
-        const links = interactions.unitsLinks.get(unit.id)
-        const groupCount = links.edgeCount * 2
+        const contacts = interactions.unitsContacts.get(unit.id)
+        const groupCount = contacts.edgeCount * 2
         for (const l of loci.links) {
             const unitIdx = group.unitIndexMap.get(l.unitA.id)
             if (unitIdx !== undefined) {
-                const idx = links.getDirectedEdgeIndex(l.indexA, l.indexB)
+                const idx = contacts.getDirectedEdgeIndex(l.indexA, l.indexB)
                 if (idx !== -1) {
                     if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
                 }
@@ -125,16 +126,16 @@ function createInteractionsIterator(structureGroup: StructureGroup): LocationIte
     const { structure, group } = structureGroup
     const unit = group.units[0]
     const interactions = InteractionsProvider.getValue(structure).value!
-    const links = interactions.unitsLinks.get(unit.id)
-    const groupCount = links.edgeCount * 2
+    const contacts = interactions.unitsContacts.get(unit.id)
+    const groupCount = contacts.edgeCount * 2
     const instanceCount = group.units.length
     const location = Interactions.Location(interactions)
     const getLocation = (groupIndex: number, instanceIndex: number) => {
         const instanceUnit = group.units[instanceIndex]
         location.unitA = instanceUnit
-        location.indexA = links.a[groupIndex]
+        location.indexA = contacts.a[groupIndex]
         location.unitB = instanceUnit
-        location.indexB = links.b[groupIndex]
+        location.indexB = contacts.b[groupIndex]
         return location
     }
     return LocationIterator(groupCount, instanceCount, getLocation)

+ 3 - 3
src/mol-theme/color/interaction-type.ts

@@ -80,12 +80,12 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
             if (Interactions.isLocation(location)) {
                 const { interactions, unitA, indexA, unitB, indexB } = location
                 if (location.unitA === location.unitB) {
-                    const links = interactions.unitsLinks.get(location.unitA.id)
+                    const links = interactions.unitsContacts.get(location.unitA.id)
                     const idx = links.getDirectedEdgeIndex(location.indexA, location.indexB)
                     return typeColor(links.edgeProps.type[idx])
                 } else {
-                    const idx = interactions.links.getEdgeIndex(indexA, unitA, indexB, unitB)
-                    return typeColor(interactions.links.edges[idx].props.type)
+                    const idx = interactions.contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
+                    return typeColor(interactions.contacts.edges[idx].props.type)
                 }
             }
             return DefaultColor

+ 5 - 5
src/mol-theme/label.ts

@@ -131,12 +131,12 @@ export function bondLabel(bond: Bond.Location): string {
 export function interactionLabel(location: Interactions.Location): string {
     const { interactions, unitA, indexA, unitB, indexB } = location
     if (location.unitA === location.unitB) {
-        const links = interactions.unitsLinks.get(location.unitA.id)
-        const idx = links.getDirectedEdgeIndex(location.indexA, location.indexB)
-        return interactionTypeLabel(links.edgeProps.type[idx])
+        const contacts = interactions.unitsContacts.get(location.unitA.id)
+        const idx = contacts.getDirectedEdgeIndex(location.indexA, location.indexB)
+        return interactionTypeLabel(contacts.edgeProps.type[idx])
     } else {
-        const idx = interactions.links.getEdgeIndex(indexA, unitA, indexB, unitB)
-        return interactionTypeLabel(interactions.links.edges[idx].props.type)
+        const idx = interactions.contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
+        return interactionTypeLabel(interactions.contacts.edges[idx].props.type)
     }
 }