Browse Source

wip, interactions

- hydrophobic
- allow computing of a subset of types
Alexander Rose 5 years ago
parent
commit
e2d595394a

+ 116 - 55
src/mol-model-props/computed/interactions/charged.ts

@@ -23,16 +23,26 @@ import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal
 import { getPositions } from '../../../mol-model/structure/util';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 
-export const ChargedParams = {
-    piStackingDistanceMax: PD.Numeric(5.5, { min: 1, max: 8, step: 0.1 }),
-    piStackingOffsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }),
-    piStackingAngleDevMax: PD.Numeric(30, { min: 0, max: 180, step: 1 }),
-    cationPiDistanceMax: PD.Numeric(6.0, { min: 1, max: 8, step: 0.1 }),
-    cationPiOffsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }),
-    ionicDistanceMax: PD.Numeric(5.0, { min: 0, max: 8, step: 0.1 }),
+const IonicParams = {
+    distanceMax: PD.Numeric(5.0, { min: 0, max: 8, step: 0.1 }),
 }
-export type ChargedParams = typeof ChargedParams
-export type ChargedProps = PD.Values<ChargedParams>
+type IonicParams = typeof IonicParams
+type IonicProps = PD.Values<IonicParams>
+
+const PiStackingParams = {
+    distanceMax: PD.Numeric(5.5, { min: 1, max: 8, step: 0.1 }),
+    offsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }),
+    angleDevMax: PD.Numeric(30, { min: 0, max: 180, step: 1 }),
+}
+type PiStackingParams = typeof PiStackingParams
+type PiStackingProps = PD.Values<PiStackingParams>
+
+const CationPiParams = {
+    distanceMax: PD.Numeric(6.0, { min: 1, max: 8, step: 0.1 }),
+    offsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }),
+}
+type CationPiParams = typeof CationPiParams
+type CationPiProps = PD.Values<CationPiParams>
 
 //
 
@@ -47,7 +57,7 @@ function getUnitValenceModel(structure: Structure, unit: Unit.Atomic) {
     return unitValenceModel
 }
 
-export function addUnitPositiveCharges(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitPositiveCharges(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { charge } = getUnitValenceModel(structure, unit)
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
@@ -104,7 +114,7 @@ export function addUnitPositiveCharges(structure: Structure, unit: Unit.Atomic,
     }
 }
 
-export function addUnitNegativeCharges(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitNegativeCharges(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { charge } = getUnitValenceModel(structure, unit)
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
@@ -203,7 +213,7 @@ function isRingAromatic(unit: Unit.Atomic, ring: SortedArray<StructureElement.Un
     return Vec3.magnitude(ma.dirC) < AromaticRingPlanarityThreshold
 }
 
-export function addUnitAromaticRings(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitAromaticRings(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
 
@@ -218,7 +228,7 @@ export function addUnitAromaticRings(structure: Structure, unit: Unit.Atomic, bu
     }
 }
 
-function isIonicInteraction(ti: FeatureType, tj: FeatureType) {
+function isIonic(ti: FeatureType, tj: FeatureType) {
     return (
         (ti === FeatureType.NegativeCharge && tj === FeatureType.PositiveCharge) ||
         (ti === FeatureType.PositiveCharge && tj === FeatureType.NegativeCharge)
@@ -285,17 +295,27 @@ const getOffset = function (infoA: Features.Info, infoB: Features.Info, normal:
     return Vec3.distance(tmpVecD, tmpVecB)
 }
 
-function getOptions(props: ChargedProps) {
+function getIonicOptions(props: IonicProps) {
+    return {
+        distanceMaxSq: props.distanceMax * props.distanceMax,
+    }
+}
+type IonicOptions = ReturnType<typeof getIonicOptions>
+
+function getPiStackingOptions(props: PiStackingProps) {
+    return {
+        offsetMax: props.offsetMax,
+        angleDevMax: degToRad(props.angleDevMax),
+    }
+}
+type PiStackingOptions = ReturnType<typeof getPiStackingOptions>
+
+function getCationPiOptions(props: CationPiProps) {
     return {
-        piStackingDistanceMaxSq: props.piStackingDistanceMax * props.piStackingDistanceMax,
-        piStackingOffsetMax: props.piStackingOffsetMax,
-        piStackingAngleDevMax: degToRad(props.piStackingAngleDevMax),
-        cationPiDistanceMaxSq: props.cationPiDistanceMax * props.cationPiDistanceMax,
-        cationPiOffsetMax: props.cationPiOffsetMax,
-        ionicDistanceMaxSq: props.ionicDistanceMax * props.ionicDistanceMax,
+        offsetMax: props.offsetMax
     }
 }
-type Options = ReturnType<typeof getOptions>
+type CationPiOptions = ReturnType<typeof getCationPiOptions>
 
 const deg180InRad = degToRad(180)
 const deg90InRad = degToRad(90)
@@ -303,52 +323,93 @@ const deg90InRad = degToRad(90)
 const tmpNormalA = Vec3()
 const tmpNormalB = Vec3()
 
-function testCharged(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: Options): InteractionType | undefined {
+function testIonic(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: IonicOptions): InteractionType | undefined {
     const typeA = infoA.types[infoA.feature]
     const typeB = infoB.types[infoB.feature]
 
-    if (isIonicInteraction(typeA, typeB)) {
-        if (areFeaturesWithinDistanceSq(infoA, infoB, opts.ionicDistanceMaxSq)) {
-            return InteractionType.IonicInteraction
+    if (isIonic(typeA, typeB)) {
+        if (areFeaturesWithinDistanceSq(infoA, infoB, opts.distanceMaxSq)) {
+            return InteractionType.Ionic
         }
-    } else if (isPiStacking(typeA, typeB)) {
-        if (distanceSq <= opts.piStackingDistanceMaxSq) {
-            getNormal(tmpNormalA, infoA)
-            getNormal(tmpNormalB, infoB)
-
-            const angle = Vec3.angle(tmpNormalA, tmpNormalB)
-            const offset = Math.min(getOffset(infoA, infoB, tmpNormalB), getOffset(infoB, infoA, tmpNormalA))
-            if (offset <= opts.piStackingOffsetMax) {
-                if (angle <= opts.piStackingAngleDevMax || angle >= deg180InRad - opts.piStackingAngleDevMax) {
-                    return InteractionType.PiStacking  // parallel
-                } else if (angle <= opts.piStackingAngleDevMax + deg90InRad && angle >= deg90InRad - opts.piStackingAngleDevMax) {
-                    return InteractionType.PiStacking  // t-shaped
-                }
+    }
+}
+
+function testPiStacking(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: PiStackingOptions): InteractionType | undefined {
+    const typeA = infoA.types[infoA.feature]
+    const typeB = infoB.types[infoB.feature]
+
+    if (isPiStacking(typeA, typeB)) {
+        getNormal(tmpNormalA, infoA)
+        getNormal(tmpNormalB, infoB)
+
+        const angle = Vec3.angle(tmpNormalA, tmpNormalB)
+        const offset = Math.min(getOffset(infoA, infoB, tmpNormalB), getOffset(infoB, infoA, tmpNormalA))
+        if (offset <= opts.offsetMax) {
+            if (angle <= opts.angleDevMax || angle >= deg180InRad - opts.angleDevMax) {
+                return InteractionType.PiStacking  // parallel
+            } else if (angle <= opts.angleDevMax + deg90InRad && angle >= deg90InRad - opts.angleDevMax) {
+                return InteractionType.PiStacking  // t-shaped
             }
         }
-    } else if (isCationPi(typeA, typeB)) {
-        if (distanceSq <= opts.cationPiDistanceMaxSq) {
-            const [infoR, infoC] = typeA === FeatureType.AromaticRing ? [infoA, infoB] : [infoB, infoA]
-
-            getNormal(tmpNormalA, infoR)
-            const offset = getOffset(infoC, infoR, tmpNormalA)
-            if (offset <= opts.cationPiOffsetMax) {
-                return InteractionType.CationPi
-            }
+    }
+}
+
+function testCationPi(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: CationPiOptions): InteractionType | undefined {
+    const typeA = infoA.types[infoA.feature]
+    const typeB = infoB.types[infoB.feature]
+
+    if (isCationPi(typeA, typeB)) {
+        const [infoR, infoC] = typeA === FeatureType.AromaticRing ? [infoA, infoB] : [infoB, infoA]
+
+        getNormal(tmpNormalA, infoR)
+        const offset = getOffset(infoC, infoR, tmpNormalA)
+        if (offset <= opts.offsetMax) {
+            return InteractionType.CationPi
         }
     }
+}
+
+//
 
+export const NegativChargeProvider = { type: FeatureType.NegativeCharge, add: addUnitNegativeCharges }
+export const PositiveChargeProvider = { type: FeatureType.PositiveCharge, add: addUnitPositiveCharges }
+export const AromaticRingProvider = { type: FeatureType.AromaticRing, add: addUnitAromaticRings }
+
+export const IonicProvider: LinkProvider<IonicParams> = {
+    name: 'ionic',
+    params: IonicParams,
+    requiredFeatures: [FeatureType.NegativeCharge, FeatureType.PositiveCharge],
+    createTester: (props: IonicProps) => {
+        const opts = getIonicOptions(props)
+        return {
+            maxDistanceSq: opts.distanceMaxSq,
+            getType: (structure, infoA, infoB, distanceSq) => testIonic(structure, infoA, infoB, distanceSq, opts)
+        }
+    }
+}
+
+export const PiStackingProvider: LinkProvider<PiStackingParams> = {
+    name: 'pi-stacking',
+    params: PiStackingParams,
+    requiredFeatures: [FeatureType.AromaticRing],
+    createTester: (props: PiStackingProps) => {
+        const opts = getPiStackingOptions(props)
+        return {
+            maxDistanceSq: props.distanceMax * props.distanceMax,
+            getType: (structure, infoA, infoB, distanceSq) => testPiStacking(structure, infoA, infoB, distanceSq, opts)
+        }
+    }
 }
 
-export const ChargedProvider: LinkProvider<ChargedParams> = {
-    name: 'charged',
-    params: ChargedParams,
-    createTester: (props: ChargedProps) => {
-        const maxDistance = Math.max(props.ionicDistanceMax + 2, props.piStackingDistanceMax, props.cationPiDistanceMax)
-        const opts = getOptions(props)
+export const CationPiProvider: LinkProvider<CationPiParams> = {
+    name: 'cation-pi',
+    params: CationPiParams,
+    requiredFeatures: [FeatureType.AromaticRing, FeatureType.PositiveCharge],
+    createTester: (props: CationPiProps) => {
+        const opts = getCationPiOptions(props)
         return {
-            maxDistanceSq: maxDistance * maxDistance,
-            getType: (structure, infoA, infoB, distanceSq) => testCharged(structure, infoA, infoB, distanceSq, opts)
+            maxDistanceSq: props.distanceMax * props.distanceMax,
+            getType: (structure, infoA, infoB, distanceSq) => testCationPi(structure, infoA, infoB, distanceSq, opts)
         }
     }
 }

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

@@ -19,7 +19,7 @@ namespace InteractionsInterLinks {
 
 export const enum InteractionType {
     Unknown = 0,
-    IonicInteraction = 1,
+    Ionic = 1,
     CationPi = 2,
     PiStacking = 3,
     HydrogenBond = 4,
@@ -27,21 +27,17 @@ export const enum InteractionType {
     Hydrophobic = 6,
     MetalCoordination = 7,
     WeakHydrogenBond = 8,
-    WaterHydrogenBond = 9,
-    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:
+        case InteractionType.Ionic:
             return 'Ionic Interaction'
         case InteractionType.MetalCoordination:
             return 'Metal Coordination'
@@ -65,7 +61,7 @@ export const enum FeatureType {
     HydrogenAcceptor = 5,
     HalogenDonor = 6,
     HalogenAcceptor = 7,
-    Hydrophobic = 8,
+    HydrophobicAtom = 8,
     WeakHydrogenDonor = 9,
     IonicTypePartner = 10,
     DativeBondPartner = 11,
@@ -91,8 +87,8 @@ export function featureTypeLabel(type: FeatureType): string {
             return 'Halogen Donor'
         case FeatureType.HalogenAcceptor:
             return 'Halogen Acceptor'
-        case FeatureType.Hydrophobic:
-            return 'Hydrophobic'
+        case FeatureType.HydrophobicAtom:
+            return 'HydrophobicAtom'
         case FeatureType.WeakHydrogenDonor:
             return 'Weak Hydrogen Donor'
         case FeatureType.IonicTypePartner:

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

@@ -128,7 +128,7 @@ namespace Features {
     }
 
     export interface Provider {
-        name: string
+        type: FeatureType
         add: (structure: Structure, unit: Unit.Atomic, featuresBuilder: FeaturesBuilder) => void
     }
 }

+ 9 - 4
src/mol-model-props/computed/interactions/halogen-bonds.ts

@@ -18,12 +18,12 @@ import { degToRad } from '../../../mol-math/misc';
 import { FeatureType, FeatureGroup, InteractionType } from './common';
 import { LinkProvider } from './links';
 
-export const HalogenBondsParams = {
+const HalogenBondsParams = {
     distanceMax: PD.Numeric(4.0, { min: 1, max: 5, step: 0.1 }),
     angleMax: PD.Numeric(30, { min: 0, max: 60, step: 1 }),
 }
-export type HalogenBondsParams = typeof HalogenBondsParams
-export type HalogenBondsProps = PD.Values<HalogenBondsParams>
+type HalogenBondsParams = typeof HalogenBondsParams
+type HalogenBondsProps = PD.Values<HalogenBondsParams>
 
 const halBondElements = [Elements.CL, Elements.BR, Elements.I, Elements.AT] as ElementSymbol[]
 
@@ -81,7 +81,6 @@ const OptimalAcceptorAngle = degToRad(120)
 
 function getOptions(props: HalogenBondsProps) {
     return {
-        distanceMax: props.distanceMax,
         angleMax: degToRad(props.angleMax),
     }
 }
@@ -111,9 +110,15 @@ function testHalogenBond(structure: Structure, infoA: Features.Info, infoB: Feat
     return InteractionType.HalogenBond
 }
 
+//
+
+export const HalogenDonorProvider = { type: FeatureType.HalogenDonor, add: addUnitHalogenDonors }
+export const HalogenAcceptorProvider = { type: FeatureType.HalogenAcceptor, add: addUnitHalogenAcceptors }
+
 export const HalogenBondsProvider: LinkProvider<HalogenBondsParams> = {
     name: 'halogen-bonds',
     params: HalogenBondsParams,
+    requiredFeatures: [FeatureType.HalogenDonor, FeatureType.HalogenAcceptor],
     createTester: (props: HalogenBondsProps) => {
         const opts = getOptions(props)
         return {

+ 94 - 35
src/mol-model-props/computed/interactions/hydrogen-bonds.ts

@@ -11,24 +11,38 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
 import { AtomGeometry, AtomGeometryAngles, calcAngles, calcPlaneAngle } from '../chemistry/geometry';
 import { FeaturesBuilder, Features } from './features';
-import { MoleculeType, ProteinBackboneAtoms } from '../../../mol-model/structure/model/types';
-import { typeSymbol, bondToElementCount, bondCount, formalCharge, atomId, compId } from '../chemistry/util';
+import { typeSymbol, bondToElementCount, bondCount, formalCharge, compId, atomId } from '../chemistry/util';
 import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
 import { ValenceModelProvider } from '../valence-model';
 import { degToRad } from '../../../mol-math/misc';
 import { FeatureType, FeatureGroup, InteractionType } from './common';
 import { LinkProvider } from './links';
+import { MoleculeType, ProteinBackboneAtoms } from '../../../mol-model/structure/model/types';
 
-export const HydrogenBondsParams = {
+const GeometryParams = {
     distanceMax: PD.Numeric(3.5, { min: 1, max: 5, step: 0.1 }),
-    sulfurDistanceMax: PD.Numeric(4.1, { min: 1, max: 5, step: 0.1 }),
+    backbone: PD.Boolean(false, { description: 'Include backbone-to-backbone hydrogen bonds' }),
     accAngleDevMax: PD.Numeric(45, { min: 0, max: 180, step: 1 }, { description: 'Max deviation from ideal acceptor angle' }),
     donAngleDevMax: PD.Numeric(45, { min: 0, max: 180, step: 1 }, { description: 'Max deviation from ideal donor angle' }),
     accOutOfPlaneAngleMax: PD.Numeric(90, { min: 0, max: 180, step: 1 }),
     donOutOfPlaneAngleMax: PD.Numeric(45, { min: 0, max: 180, step: 1 }),
 }
-export type HydrogenBondsParams = typeof HydrogenBondsParams
-export type HydrogenBondsProps = PD.Values<HydrogenBondsParams>
+type GeometryParams = typeof GeometryParams
+type GeometryProps = PD.Values<GeometryParams>
+
+const HydrogenBondsParams = {
+    ...GeometryParams,
+    water: PD.Boolean(false, { description: 'Include water-to-water hydrogen bonds' }),
+    sulfurDistanceMax: PD.Numeric(4.1, { min: 1, max: 5, step: 0.1 }),
+}
+type HydrogenBondsParams = typeof HydrogenBondsParams
+type HydrogenBondsProps = PD.Values<HydrogenBondsParams>
+
+const WeakHydrogenBondsParams = {
+    ...GeometryParams,
+}
+type WeakHydrogenBondsParams = typeof WeakHydrogenBondsParams
+type WeakHydrogenBondsProps = PD.Values<WeakHydrogenBondsParams>
 
 //
 
@@ -50,7 +64,7 @@ function getUnitValenceModel(structure: Structure, unit: Unit.Atomic) {
 /**
  * Potential hydrogen donor
  */
-export function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { totalH } = getUnitValenceModel(structure, unit)
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
@@ -74,7 +88,7 @@ export function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, b
 /**
  * Weak hydrogen donor.
  */
-export function addUnitWeakHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitWeakHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { totalH } = getUnitValenceModel(structure, unit)
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
@@ -120,7 +134,7 @@ function inAromaticRingWithElectronNegativeElement(structure: Structure, unit: U
 /**
  * Potential hydrogen acceptor
  */
-export function addUnitHydrogenAcceptors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+function addUnitHydrogenAcceptors(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
     const { charge, implicitH, idealGeometry } = getUnitValenceModel(structure, unit)
     const { elements } = unit
     const { x, y, z } = unit.model.atomicConformation
@@ -200,44 +214,35 @@ function isWeakHydrogenBond(ti: FeatureType, tj: FeatureType) {
     )
 }
 
-function getHydrogenBondType(unitA: Unit.Atomic, indexA: StructureElement.UnitIndex, unitB: Unit.Atomic, indexB: StructureElement.UnitIndex) {
-    if (isWaterHydrogenBond(unitA, indexA, unitB, indexB)) {
-        return InteractionType.WaterHydrogenBond
-    } else if (isBackboneHydrogenBond(unitA, indexA, unitB, indexB)) {
-        return InteractionType.BackboneHydrogenBond
-    } else {
-        return InteractionType.HydrogenBond
-    }
-}
-
-function getOptions(props: HydrogenBondsProps) {
+function getGeometryOptions(props: GeometryProps) {
     return {
+        includeBackbone: props.backbone,
         maxAccAngleDev: degToRad(props.accAngleDevMax),
         maxDonAngleDev: degToRad(props.donAngleDevMax),
         maxAccOutOfPlaneAngle: degToRad(props.accOutOfPlaneAngleMax),
         maxDonOutOfPlaneAngle: degToRad(props.donOutOfPlaneAngleMax),
-        maxDist: Math.max(props.distanceMax, props.sulfurDistanceMax),
-        maxHbondDistSq: props.distanceMax * props.distanceMax,
     }
 }
-type Options = ReturnType<typeof getOptions>
+type GeometryOptions = ReturnType<typeof getGeometryOptions>
 
-const deg120InRad = degToRad(120)
-
-function testHydrogenBond(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: Options): InteractionType | undefined {
-    const typeA = infoA.types[infoA.feature]
-    const typeB = infoB.types[infoB.feature]
+function getHydrogenBondsOptions(props: HydrogenBondsProps) {
+    const maxDist = Math.max(props.distanceMax, props.sulfurDistanceMax)
+    return {
+        ...getGeometryOptions(props),
+        includeWater: props.water,
+        maxHbondDistSq: maxDist * maxDist
+    }
+}
+type HydrogenBondsOptions = ReturnType<typeof getHydrogenBondsOptions>
 
-    const isWeak = isWeakHydrogenBond(typeA, typeB)
-    if (!isWeak && !isHydrogenBond(typeA, typeB)) return
+const deg120InRad = degToRad(120)
 
-    const [don, acc] = typeB === FeatureType.HydrogenAcceptor ? [infoA, infoB] : [infoB, infoA]
+function checkGeometry(structure: Structure, don: Features.Info, acc: Features.Info, opts: GeometryOptions): true | undefined {
 
     const donIndex = don.members[don.offsets[don.feature]]
     const accIndex = acc.members[acc.offsets[acc.feature]]
 
-    // check if distance is ok for non-sulfur-containing hbond
-    if (typeSymbol(don.unit, donIndex) !== Elements.S && typeSymbol(acc.unit, accIndex) !== Elements.S && distanceSq > opts.maxHbondDistSq) return
+    if (!opts.includeBackbone && isBackboneHydrogenBond(don.unit, donIndex, acc.unit, accIndex)) return
 
     const donAngles = calcAngles(structure, don.unit, donIndex, acc.unit, accIndex)
     const idealDonAngle = AtomGeometryAngles.get(don.idealGeometry[donIndex]) || deg120InRad
@@ -259,18 +264,72 @@ function testHydrogenBond(structure: Structure, infoA: Features.Info, infoB: Fea
         if (outOfPlane !== undefined && outOfPlane > opts.maxAccOutOfPlaneAngle) return
     }
 
-    return isWeak ? InteractionType.WeakHydrogenBond : getHydrogenBondType(don.unit, donIndex, acc.unit, accIndex)
+    return true
 }
 
+function testHydrogenBond(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: HydrogenBondsOptions): InteractionType | undefined {
+    const typeA = infoA.types[infoA.feature]
+    const typeB = infoB.types[infoB.feature]
+
+    if (!isHydrogenBond(typeA, typeB)) return
+
+    const [don, acc] = typeB === FeatureType.HydrogenAcceptor ? [infoA, infoB] : [infoB, infoA]
+
+    const donIndex = don.members[don.offsets[don.feature]]
+    const accIndex = acc.members[acc.offsets[acc.feature]]
+
+    // check if distance is ok for non-sulfur-containing hbond
+    if (typeSymbol(don.unit, donIndex) !== Elements.S && typeSymbol(acc.unit, accIndex) !== Elements.S && distanceSq > opts.maxHbondDistSq) return
+
+    if (!opts.includeWater && isWaterHydrogenBond(don.unit, donIndex, acc.unit, accIndex)) return
+
+    if (!checkGeometry(structure, don, acc, opts)) return
+
+    return InteractionType.HydrogenBond
+}
+
+function testWeakHydrogenBond(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, opts: GeometryOptions): InteractionType | undefined {
+    const typeA = infoA.types[infoA.feature]
+    const typeB = infoB.types[infoB.feature]
+
+    if (!isWeakHydrogenBond(typeA, typeB)) return
+
+    const [don, acc] = typeB === FeatureType.HydrogenAcceptor ? [infoA, infoB] : [infoB, infoA]
+
+    if (!checkGeometry(structure, don, acc, opts)) return
+
+    return InteractionType.WeakHydrogenBond
+}
+
+//
+
+export const HydrogenDonorProvider = { type: FeatureType.HydrogenDonor, add: addUnitHydrogenDonors }
+export const WeakHydrogenDonorProvider = { type: FeatureType.WeakHydrogenDonor, add: addUnitWeakHydrogenDonors }
+export const HydrogenAcceptorProvider = { type: FeatureType.HydrogenAcceptor, add: addUnitHydrogenAcceptors }
+
 export const HydrogenBondsProvider: LinkProvider<HydrogenBondsParams> = {
     name: 'hydrogen-bonds',
     params: HydrogenBondsParams,
+    requiredFeatures: [FeatureType.HydrogenDonor, FeatureType.HydrogenAcceptor],
     createTester: (props: HydrogenBondsProps) => {
         const maxDistance = Math.max(props.distanceMax, props.sulfurDistanceMax)
-        const opts = getOptions(props)
+        const opts = getHydrogenBondsOptions(props)
         return {
             maxDistanceSq: maxDistance * maxDistance,
             getType: (structure, infoA, infoB, distanceSq) => testHydrogenBond(structure, infoA, infoB, distanceSq, opts)
         }
     }
+}
+
+export const WeakHydrogenBondsProvider: LinkProvider<WeakHydrogenBondsParams> = {
+    name: 'weak-hydrogen-bonds',
+    params: WeakHydrogenBondsParams,
+    requiredFeatures: [FeatureType.WeakHydrogenDonor, FeatureType.HydrogenAcceptor],
+    createTester: (props: WeakHydrogenBondsProps) => {
+        const opts = getGeometryOptions(props)
+        return {
+            maxDistanceSq: props.distanceMax * props.distanceMax,
+            getType: (structure, infoA, infoB, distanceSq) => testWeakHydrogenBond(structure, infoA, infoB, distanceSq, opts)
+        }
+    }
 }

+ 83 - 0
src/mol-model-props/computed/interactions/hydrophobic.ts

@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Fred Ludlow <Fred.Ludlow@astx.com>
+ *
+ * based in part on NGL (https://github.com/arose/ngl)
+ */
+
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
+import { FeaturesBuilder, Features } from './features';
+import { typeSymbol, eachBondedAtom } from '../chemistry/util';
+import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
+import { FeatureType, FeatureGroup, InteractionType } from './common';
+import { LinkProvider } from './links';
+
+export const HydrophobicParams = {
+    distanceMax: PD.Numeric(4.0, { min: 1, max: 5, step: 0.1 }),
+}
+export type HydrophobicParams = typeof HydrophobicParams
+export type HydrophobicProps = PD.Values<HydrophobicParams>
+
+/**
+ * Hydropbobic atoms
+ * - Carbon only bonded to carbon or hydrogen
+ * - Fluorine
+ */
+export function addHydrophobicAtom(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
+    const { elements } = unit
+    const { x, y, z } = unit.model.atomicConformation
+
+    for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
+        const element = typeSymbol(unit, i)
+        let flag = false
+        if (element === Elements.C) {
+            flag = true
+            eachBondedAtom(structure, unit, i, (unitB, indexB) => {
+                const elementB = typeSymbol(unitB, indexB)
+                if (elementB !== Elements.C && elementB !== Elements.H) flag = false
+            })
+        } else if (element === Elements.F) {
+            flag = true
+        }
+
+        if (flag) {
+            builder.add(FeatureType.HydrophobicAtom, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i)
+        }
+    }
+}
+
+function isHydrophobicContact (ti: FeatureType, tj: FeatureType) {
+    return ti === FeatureType.HydrophobicAtom && tj === FeatureType.HydrophobicAtom
+}
+
+function testHydrophobic(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number): InteractionType | undefined {
+    const typeA = infoA.types[infoA.feature]
+    const typeB = infoB.types[infoB.feature]
+
+    if (!isHydrophobicContact(typeA, typeB)) return
+
+    const indexA = infoA.members[infoA.offsets[infoA.feature]]
+    const indexB = infoB.members[infoB.offsets[infoB.feature]]
+    if (typeSymbol(infoA.unit, indexA) === Elements.F && typeSymbol(infoB.unit, indexB) === Elements.F) return
+
+    return InteractionType.Hydrophobic
+}
+
+//
+
+export const HydrophobicAtomProvider = { type: FeatureType.HydrophobicAtom, add: addHydrophobicAtom }
+
+export const HydrophobicProvider: LinkProvider<HydrophobicParams> = {
+    name: 'hydrophobic',
+    params: HydrophobicParams,
+    requiredFeatures: [FeatureType.HydrophobicAtom],
+    createTester: (props: HydrophobicProps) => {
+        return {
+            maxDistanceSq: props.distanceMax * props.distanceMax,
+            getType: (structure, infoA, infoB, distanceSq) => testHydrophobic(structure, infoA, infoB, distanceSq)
+        }
+    }
+}

+ 63 - 36
src/mol-model-props/computed/interactions/interactions.ts

@@ -9,14 +9,15 @@ import { Structure, Unit } from '../../../mol-model/structure';
 import { RuntimeContext } from '../../../mol-task';
 import { Features, FeaturesBuilder } from './features';
 import { ValenceModelProvider } from '../valence-model';
-import { InteractionsIntraLinks, InteractionsInterLinks } from './common';
+import { InteractionsIntraLinks, InteractionsInterLinks, FeatureType } from './common';
 import { IntraLinksBuilder, InterLinksBuilder } from './builder';
 import { IntMap } from '../../../mol-data/int';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { addUnitLinks, LinkTester, addStructureLinks } from './links';
-import { addUnitHalogenDonors, addUnitHalogenAcceptors, HalogenBondsProvider } from './halogen-bonds';
-import { addUnitHydrogenDonors, addUnitWeakHydrogenDonors, addUnitHydrogenAcceptors, HydrogenBondsProvider } from './hydrogen-bonds';
-import { addUnitPositiveCharges, addUnitNegativeCharges, addUnitAromaticRings, ChargedProvider } from './charged';
+import { addUnitLinks, LinkTester, addStructureLinks, LinkProvider } from './links';
+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';
 
 export { Interactions }
 
@@ -99,10 +100,43 @@ namespace Interactions {
     }
 }
 
+const FeatureProviders = [
+    HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider,
+    NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider,
+    HalogenDonorProvider, HalogenAcceptorProvider,
+    HydrophobicAtomProvider,
+]
+
+const LinkProviders = {
+    'ionic': IonicProvider,
+    'pi-stacking': PiStackingProvider,
+    'cation-pi': CationPiProvider,
+    'halogen-bonds': HalogenBondsProvider,
+    'hydrogen-bonds': HydrogenBondsProvider,
+    'weak-hydrogen-bonds': WeakHydrogenBondsProvider,
+    'hydrophobic': HydrophobicProvider,
+}
+type LinkProviders = typeof LinkProviders
+
+function getProvidersParams() {
+    const params: { [k in keyof LinkProviders]: PD.Group<LinkProviders[k]['params']> } = Object.create(null)
+    Object.keys(LinkProviders).forEach(k => {
+        (params as any)[k] = PD.Group(LinkProviders[k as keyof LinkProviders].params)
+    })
+    return params
+}
 export const InteractionsParams = {
-    hydrogenBonds: PD.Group(HydrogenBondsProvider.params),
-    halogenBonds: PD.Group(HalogenBondsProvider.params),
-    charged: PD.Group(ChargedProvider.params),
+    types: PD.MultiSelect([
+        'ionic',
+        'cation-pi',
+        'pi-stacking',
+        'hydrogen-bonds',
+        'halogen-bonds',
+        // 'hydrophobic',
+        // 'metal-coordination',
+        'weak-hydrogen-bonds',
+    ], PD.objectToOptions(LinkProviders)),
+    ...getProvidersParams()
 }
 export type InteractionsParams = typeof InteractionsParams
 export type InteractionsProps = PD.Values<InteractionsParams>
@@ -111,11 +145,15 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
     const p = { ...PD.getDefaultValues(InteractionsParams), ...props }
     await ValenceModelProvider.attach(structure).runInContext(runtime)
 
-    const linkTesters: LinkTester[] = [
-        HydrogenBondsProvider.createTester(p.hydrogenBonds),
-        HalogenBondsProvider.createTester(p.halogenBonds),
-        ChargedProvider.createTester(p.charged),
-    ]
+    const linkProviders: LinkProvider<any>[] = []
+    Object.keys(LinkProviders).forEach(k => {
+        if (p.types.includes(k)) linkProviders.push(LinkProviders[k as keyof typeof LinkProviders])
+    })
+    const linkTesters = linkProviders.map(l => l.createTester(p[l.name as keyof InteractionsProps]))
+
+    const requiredFeatures = new Set<FeatureType>()
+    linkProviders.forEach(l => { for (const f of l.requiredFeatures) requiredFeatures.add(f) })
+    const featureProviders = FeatureProviders.filter(f => requiredFeatures.has(f.type))
 
     const unitsFeatures = IntMap.Mutable<Features>()
     const unitsLinks = IntMap.Mutable<InteractionsIntraLinks>()
@@ -125,11 +163,12 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
         if (runtime.shouldUpdate) {
             await runtime.update({ message: 'computing interactions', current: i, max: il })
         }
-        const d = findIntraUnitLinksAndFeatures(structure, group.units[0], linkTesters)
+        const features = findUnitFeatures(structure, group.units[0], featureProviders)
+        const intraUnitLinks = findIntraUnitLinks(structure, group.units[0], features, linkTesters)
         for (let j = 0, jl = group.units.length; j < jl; ++j) {
             const u = group.units[j]
-            unitsFeatures.set(u.id, d.features)
-            unitsLinks.set(u.id, d.links)
+            unitsFeatures.set(u.id, features)
+            unitsLinks.set(u.id, intraUnitLinks)
         }
     }
 
@@ -138,35 +177,23 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St
     return { unitsFeatures, unitsLinks, links }
 }
 
-const FeatureProviders: Features.Provider[] = [
-    { name: 'hydrogen-donors', add: addUnitHydrogenDonors },
-    { name: 'weak-hydrogen-donors', add: addUnitWeakHydrogenDonors },
-    { name: 'hydrogen-acceptors', add: addUnitHydrogenAcceptors },
-
-    { name: 'halogen-donors', add: addUnitHalogenDonors },
-    { name: 'halogen-acceptors', add: addUnitHalogenAcceptors },
-
-    { name: 'positive-charges', add: addUnitPositiveCharges },
-    { name: 'negative-charges', add: addUnitNegativeCharges },
-    { name: 'aromatic-rings', add: addUnitAromaticRings },
-]
-
-function findIntraUnitLinksAndFeatures(structure: Structure, unit: Unit, linkTesters: ReadonlyArray<LinkTester>) {
+function findUnitFeatures(structure: Structure, unit: Unit, featureProviders: Features.Provider[]) {
     const count = unit.elements.length
     const featuresBuilder = FeaturesBuilder.create(count, count / 2)
     if (Unit.isAtomic(unit)) {
-        for (const featureProvider of FeatureProviders) {
-            featureProvider.add(structure, unit, featuresBuilder)
+        for (const fp of featureProviders) {
+            fp.add(structure, unit, featuresBuilder)
         }
     }
-    const features = featuresBuilder.getFeatures(count)
+    return featuresBuilder.getFeatures(count)
+}
 
-    const linksBuilder = IntraLinksBuilder.create(features, count)
+function findIntraUnitLinks(structure: Structure, unit: Unit, features: Features, linkTesters: ReadonlyArray<LinkTester>) {
+    const linksBuilder = IntraLinksBuilder.create(features, unit.elements.length)
     if (Unit.isAtomic(unit)) {
         addUnitLinks(structure, unit, features, linksBuilder, linkTesters)
     }
-
-    return { features, links: linksBuilder.getLinks() }
+    return linksBuilder.getLinks()
 }
 
 function findInterUnitLinks(structure: Structure, unitsFeatures: IntMap<Features>, linkTesters: ReadonlyArray<LinkTester>) {

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

@@ -7,7 +7,7 @@
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Structure, Unit } from '../../../mol-model/structure';
 import { Features } from './features';
-import { InteractionType } from './common';
+import { InteractionType, FeatureType } from './common';
 import { IntraLinksBuilder, InterLinksBuilder } from './builder';
 import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
 import { altLoc, connectedTo } from '../chemistry/util';
@@ -15,8 +15,9 @@ import { altLoc, connectedTo } from '../chemistry/util';
 const MAX_DISTANCE = 5
 
 export interface LinkProvider<P extends PD.Params> {
-    name: string
-    params: P
+    readonly name: string
+    readonly params: P
+    readonly requiredFeatures: ReadonlyArray<FeatureType>
     createTester(props: PD.Values<P>): LinkTester
 }
 
@@ -67,7 +68,10 @@ export function addUnitLinks(structure: Structure, unit: Unit.Atomic, features:
             for (const tester of testers) {
                 if (distanceSq < tester.maxDistanceSq) {
                     const type = tester.getType(structure, infoA, infoB, distanceSq)
-                    if (type) builder.add(i, j, type)
+                    if (type) {
+                        builder.add(i, j, type)
+                        break
+                    }
                 }
             }
         }
@@ -117,7 +121,10 @@ export function addStructureLinks(structure: Structure, unitA: Unit.Atomic, feat
             for (const tester of testers) {
                 if (distanceSq < tester.maxDistanceSq) {
                     const type = tester.getType(structure, infoA, infoB, distanceSq)
-                    if (type) builder.add(i, j, type)
+                    if (type) {
+                        builder.add(i, j, type)
+                        break
+                    }
                 }
             }
         }

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

@@ -43,14 +43,12 @@ const InteractionTypeColorTable: [string, Color][] = [
 function typeColor(type: InteractionType): Color {
     switch (type) {
         case InteractionType.HydrogenBond:
-        case InteractionType.WaterHydrogenBond:
-        case InteractionType.BackboneHydrogenBond:
             return InteractionTypeColors.HydrogenBond
         case InteractionType.Hydrophobic:
             return InteractionTypeColors.Hydrophobic
         case InteractionType.HalogenBond:
             return InteractionTypeColors.HalogenBond
-        case InteractionType.IonicInteraction:
+        case InteractionType.Ionic:
             return InteractionTypeColors.Ionic
         case InteractionType.MetalCoordination:
             return InteractionTypeColors.MetalCoordination