interactions.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  7. import { Structure, Unit } from '../../../mol-model/structure';
  8. import { Features, FeaturesBuilder } from './features';
  9. import { ValenceModelProvider } from '../valence-model';
  10. import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType, interactionTypeLabel } from './common';
  11. import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
  12. import { IntMap } from '../../../mol-data/int';
  13. import { addUnitContacts, ContactTester, addStructureContacts, ContactProvider, ContactsParams, ContactsProps } from './contacts';
  14. import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds';
  15. import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds';
  16. import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
  17. import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic';
  18. import { SetUtils } from '../../../mol-util/set';
  19. import { MetalCoordinationProvider, MetalProvider, MetalBindingProvider } from './metal';
  20. import { refineInteractions } from './refine';
  21. import { CustomProperty } from '../../common/custom-property';
  22. import { DataLocation } from '../../../mol-model/location';
  23. import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
  24. import { Sphere3D } from '../../../mol-math/geometry';
  25. import { DataLoci } from '../../../mol-model/loci';
  26. export { Interactions }
  27. interface Interactions {
  28. /** Features of each unit */
  29. unitsFeatures: IntMap<Features>
  30. /** Interactions of each unit */
  31. unitsContacts: IntMap<InteractionsIntraContacts>
  32. /** Interactions between units */
  33. contacts: InteractionsInterContacts
  34. }
  35. namespace Interactions {
  36. export interface Element {
  37. unitA: Unit
  38. /** Index into features of unitA */
  39. indexA: Features.FeatureIndex
  40. unitB: Unit
  41. /** Index into features of unitB */
  42. indexB: Features.FeatureIndex
  43. }
  44. export interface Location extends DataLocation<Interactions, Element> {}
  45. export function Location(interactions: Interactions, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
  46. return DataLocation('interactions', interactions, { unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
  47. }
  48. export function isLocation(x: any): x is Location {
  49. return !!x && x.kind === 'data-location' && x.tag === 'interactions';
  50. }
  51. export function areLocationsEqual(locA: Location, locB: Location) {
  52. return (
  53. locA.data === locB.data &&
  54. locA.element.indexA === locB.element.indexA &&
  55. locA.element.indexB === locB.element.indexB &&
  56. locA.element.unitA === locB.element.unitA &&
  57. locA.element.unitB === locB.element.unitB
  58. )
  59. }
  60. function _label(interactions: Interactions, element: Element): string {
  61. const { unitA, indexA, unitB, indexB } = element
  62. const { contacts, unitsContacts } = interactions
  63. if (unitA === unitB) {
  64. const contacts = unitsContacts.get(unitA.id)
  65. const idx = contacts.getDirectedEdgeIndex(indexA, indexB)
  66. return interactionTypeLabel(contacts.edgeProps.type[idx])
  67. } else {
  68. const idx = contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
  69. return interactionTypeLabel(contacts.edges[idx].props.type)
  70. }
  71. }
  72. export function locationLabel(location: Location): string {
  73. return _label(location.data, location.element)
  74. }
  75. type StructureInteractions = { readonly structure: Structure, readonly interactions: Interactions }
  76. export interface Loci extends DataLoci<StructureInteractions, Element> { }
  77. export function Loci(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>): Loci {
  78. return DataLoci('interactions', { structure, interactions }, elements, (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), () => getLabel(interactions, elements));
  79. }
  80. export function isLoci(x: any): x is Loci {
  81. return !!x && x.kind === 'data-loci' && x.tag === 'interactions';
  82. }
  83. export function getBoundingSphere(interactions: Interactions, elements: ReadonlyArray<Element>, boundingSphere: Sphere3D) {
  84. const { unitsFeatures } = interactions
  85. return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
  86. const e = elements[i]
  87. Features.setPosition(pA, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
  88. Features.setPosition(pB, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
  89. }, boundingSphere)
  90. }
  91. export function getLabel(interactions: Interactions, elements: ReadonlyArray<Element>) {
  92. return elements.length > 0 ? _label(interactions, elements[0]) : ''
  93. }
  94. }
  95. const FeatureProviders = [
  96. HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider,
  97. NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider,
  98. HalogenDonorProvider, HalogenAcceptorProvider,
  99. HydrophobicAtomProvider,
  100. MetalProvider, MetalBindingProvider,
  101. ]
  102. const ContactProviders = {
  103. 'ionic': IonicProvider,
  104. 'pi-stacking': PiStackingProvider,
  105. 'cation-pi': CationPiProvider,
  106. 'halogen-bonds': HalogenBondsProvider,
  107. 'hydrogen-bonds': HydrogenBondsProvider,
  108. 'weak-hydrogen-bonds': WeakHydrogenBondsProvider,
  109. 'hydrophobic': HydrophobicProvider,
  110. 'metal-coordination': MetalCoordinationProvider,
  111. }
  112. type ContactProviders = typeof ContactProviders
  113. function getProvidersParams() {
  114. const params: { [k in keyof ContactProviders]: PD.Group<ContactProviders[k]['params']> } = Object.create(null)
  115. Object.keys(ContactProviders).forEach(k => {
  116. (params as any)[k] = PD.Group(ContactProviders[k as keyof ContactProviders].params)
  117. })
  118. return params
  119. }
  120. export const InteractionsParams = {
  121. types: PD.MultiSelect([
  122. // 'ionic',
  123. 'cation-pi',
  124. 'pi-stacking',
  125. 'hydrogen-bonds',
  126. 'halogen-bonds',
  127. // 'hydrophobic',
  128. 'metal-coordination',
  129. // 'weak-hydrogen-bonds',
  130. ], PD.objectToOptions(ContactProviders)),
  131. contacts: PD.Group(ContactsParams, { isFlat: true }),
  132. ...getProvidersParams()
  133. }
  134. export type InteractionsParams = typeof InteractionsParams
  135. export type InteractionsProps = PD.Values<InteractionsParams>
  136. export async function computeInteractions(ctx: CustomProperty.Context, structure: Structure, props: Partial<InteractionsProps>): Promise<Interactions> {
  137. const p = { ...PD.getDefaultValues(InteractionsParams), ...props }
  138. await ValenceModelProvider.attach(ctx, structure)
  139. const contactProviders: ContactProvider<any>[] = []
  140. Object.keys(ContactProviders).forEach(k => {
  141. if (p.types.includes(k)) contactProviders.push(ContactProviders[k as keyof typeof ContactProviders])
  142. })
  143. const contactTesters = contactProviders.map(l => l.createTester(p[l.name as keyof InteractionsProps]))
  144. const requiredFeatures = new Set<FeatureType>()
  145. contactTesters.forEach(l => SetUtils.add(requiredFeatures, l.requiredFeatures))
  146. const featureProviders = FeatureProviders.filter(f => SetUtils.areIntersecting(requiredFeatures, f.types))
  147. const unitsFeatures = IntMap.Mutable<Features>()
  148. const unitsContacts = IntMap.Mutable<InteractionsIntraContacts>()
  149. for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
  150. const group = structure.unitSymmetryGroups[i]
  151. if (ctx.runtime.shouldUpdate) {
  152. await ctx.runtime.update({ message: 'computing interactions', current: i, max: il })
  153. }
  154. const features = findUnitFeatures(structure, group.units[0], featureProviders)
  155. const intraUnitContacts = findIntraUnitContacts(structure, group.units[0], features, contactTesters, p.contacts)
  156. for (let j = 0, jl = group.units.length; j < jl; ++j) {
  157. const u = group.units[j]
  158. unitsFeatures.set(u.id, features)
  159. unitsContacts.set(u.id, intraUnitContacts)
  160. }
  161. }
  162. const contacts = findInterUnitContacts(structure, unitsFeatures, contactTesters, p.contacts)
  163. const interactions = { unitsFeatures, unitsContacts, contacts }
  164. refineInteractions(structure, interactions)
  165. return interactions
  166. }
  167. function findUnitFeatures(structure: Structure, unit: Unit, featureProviders: Features.Provider[]) {
  168. const count = unit.elements.length
  169. const featuresBuilder = FeaturesBuilder.create(count, count / 2)
  170. if (Unit.isAtomic(unit)) {
  171. for (const fp of featureProviders) {
  172. fp.add(structure, unit, featuresBuilder)
  173. }
  174. }
  175. return featuresBuilder.getFeatures(count)
  176. }
  177. function findIntraUnitContacts(structure: Structure, unit: Unit, features: Features, contactTesters: ReadonlyArray<ContactTester>, props: ContactsProps) {
  178. const builder = IntraContactsBuilder.create(features, unit.elements.length)
  179. if (Unit.isAtomic(unit)) {
  180. addUnitContacts(structure, unit, features, builder, contactTesters, props)
  181. }
  182. return builder.getContacts()
  183. }
  184. function findInterUnitContacts(structure: Structure, unitsFeatures: IntMap<Features>, contactTesters: ReadonlyArray<ContactTester>, props: ContactsProps) {
  185. const builder = InterContactsBuilder.create()
  186. Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
  187. const featuresA = unitsFeatures.get(unitA.id)
  188. const featuresB = unitsFeatures.get(unitB.id)
  189. addStructureContacts(structure, unitA as Unit.Atomic, featuresA, unitB as Unit.Atomic, featuresB, builder, contactTesters, props)
  190. }, {
  191. maxRadius: Math.max(...contactTesters.map(t => t.maxDistance)),
  192. validUnit: (unit: Unit) => Unit.isAtomic(unit),
  193. validUnitPair: (unitA: Unit, unitB: Unit) => Structure.validUnitPair(structure, unitA, unitB)
  194. })
  195. return builder.getContacts(unitsFeatures)
  196. }