links.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * Copyright (c) 2019 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 } from './features';
  9. import { InteractionType, FeatureType } from './common';
  10. import { IntraLinksBuilder, InterLinksBuilder } from './links-builder';
  11. import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
  12. import { altLoc, connectedTo } from '../chemistry/util';
  13. const MAX_DISTANCE = 5
  14. export interface LinkProvider<P extends PD.Params> {
  15. readonly name: string
  16. readonly params: P
  17. readonly requiredFeatures: ReadonlyArray<FeatureType>
  18. createTester(props: PD.Values<P>): LinkTester
  19. }
  20. export interface LinkTester {
  21. maxDistanceSq: number
  22. getType: (structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number, ) => InteractionType | undefined
  23. }
  24. function validPair(structure: Structure, infoA: Features.Info, infoB: Features.Info): boolean {
  25. const indexA = infoA.members[infoA.offsets[infoA.feature]]
  26. const indexB = infoB.members[infoB.offsets[infoB.feature]]
  27. if (indexA === indexB) return false // no self interaction
  28. const altA = altLoc(infoA.unit, indexA)
  29. const altB = altLoc(infoB.unit, indexB)
  30. if (altA && altB && altA !== altB) return false // incompatible alternate location id
  31. if (infoA.unit.residueIndex[infoA.unit.elements[indexA]] === infoB.unit.residueIndex[infoB.unit.elements[indexB]]) return false // same residue
  32. // no hbond if donor and acceptor are bonded
  33. if (connectedTo(structure, infoA.unit, indexA, infoB.unit, indexB)) return false
  34. return true
  35. }
  36. /**
  37. * Add all intra-unit links, i.e. pairs of features
  38. */
  39. export function addUnitLinks(structure: Structure, unit: Unit.Atomic, features: Features, builder: IntraLinksBuilder, testers: ReadonlyArray<LinkTester>) {
  40. const { x, y, z, count, lookup3d } = features
  41. const infoA = Features.Info(structure, unit, features)
  42. const infoB = { ...infoA }
  43. const maxDistanceSq = Math.max(...testers.map(t => t.maxDistanceSq))
  44. for (let i = 0; i < count; ++i) {
  45. const { count, indices, squaredDistances } = lookup3d.find(x[i], y[i], z[i], maxDistanceSq)
  46. if (count === 0) continue
  47. infoA.feature = i
  48. for (let r = 0; r < count; ++r) {
  49. const j = indices[r]
  50. if (j <= i) continue
  51. const distanceSq = squaredDistances[r]
  52. infoB.feature = j
  53. if (!validPair(structure, infoA, infoB)) continue
  54. for (const tester of testers) {
  55. if (distanceSq < tester.maxDistanceSq) {
  56. const type = tester.getType(structure, infoA, infoB, distanceSq)
  57. if (type) {
  58. builder.add(i, j, type)
  59. break
  60. }
  61. }
  62. }
  63. }
  64. }
  65. }
  66. const _imageTransform = Mat4()
  67. /**
  68. * Add all inter-unit links, i.e. pairs of features
  69. */
  70. export function addStructureLinks(structure: Structure, unitA: Unit.Atomic, featuresA: Features, unitB: Unit.Atomic, featuresB: Features, builder: InterLinksBuilder, testers: ReadonlyArray<LinkTester>) {
  71. const { count, x: xA, y: yA, z: zA } = featuresA;
  72. const { lookup3d } = featuresB;
  73. // the lookup queries need to happen in the "unitB space".
  74. // that means imageA = inverseOperB(operA(i))
  75. const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix)
  76. const isNotIdentity = !Mat4.isIdentity(imageTransform)
  77. const imageA = Vec3()
  78. const maxDistanceSq = Math.max(...testers.map(t => t.maxDistanceSq))
  79. const { center, radius } = lookup3d.boundary.sphere;
  80. const testDistanceSq = (radius + maxDistanceSq) * (radius + maxDistanceSq);
  81. const infoA = Features.Info(structure, unitA, featuresA)
  82. const infoB = Features.Info(structure, unitB, featuresB)
  83. builder.startUnitPair(unitA, unitB)
  84. for (let i = 0; i < count; ++i) {
  85. Vec3.set(imageA, xA[i], yA[i], zA[i])
  86. if (isNotIdentity) Vec3.transformMat4(imageA, imageA, imageTransform)
  87. if (Vec3.squaredDistance(imageA, center) > testDistanceSq) continue
  88. const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_DISTANCE)
  89. if (count === 0) continue
  90. infoA.feature = i
  91. for (let r = 0; r < count; ++r) {
  92. const j = indices[r]
  93. const distanceSq = squaredDistances[r]
  94. infoB.feature = j
  95. if (!validPair(structure, infoA, infoB)) continue
  96. for (const tester of testers) {
  97. if (distanceSq < tester.maxDistanceSq) {
  98. const type = tester.getType(structure, infoA, infoB, distanceSq)
  99. if (type) {
  100. builder.add(i, j, type)
  101. break
  102. }
  103. }
  104. }
  105. }
  106. }
  107. builder.finishUnitPair()
  108. }