inter-compute.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /**
  2. * Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { BondType } from '../../../model/types';
  8. import Structure from '../../structure';
  9. import Unit from '../../unit';
  10. import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps } from './common';
  11. import { InterUnitBonds, InterUnitEdgeProps } from './data';
  12. import { SortedArray } from '../../../../../mol-data/int';
  13. import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
  14. import StructureElement from '../../element';
  15. import { ElementIndex } from '../../../model/indexing';
  16. import { getInterBondOrderFromTable } from '../../../model/properties/atomic/bonds';
  17. import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair';
  18. import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
  19. import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
  20. const MAX_RADIUS = 4;
  21. const tmpDistVecA = Vec3()
  22. const tmpDistVecB = Vec3()
  23. function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) {
  24. unitA.conformation.position(indexA, tmpDistVecA)
  25. unitB.conformation.position(indexB, tmpDistVecB)
  26. return Vec3.distance(tmpDistVecA, tmpDistVecB)
  27. }
  28. const _imageTransform = Mat4.zero();
  29. function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComputationProps, builder: InterUnitGraph.Builder<Unit.Atomic, StructureElement.UnitIndex, InterUnitEdgeProps>) {
  30. const { elements: atomsA, residueIndex: residueIndexA } = unitA;
  31. const { x: xA, y: yA, z: zA } = unitA.model.atomicConformation;
  32. const { elements: atomsB, residueIndex: residueIndexB } = unitB;
  33. const atomCount = unitA.elements.length;
  34. const { type_symbol: type_symbolA, label_alt_id: label_alt_idA, label_atom_id: label_atom_idA } = unitA.model.atomicHierarchy.atoms;
  35. const { type_symbol: type_symbolB, label_alt_id: label_alt_idB, label_atom_id: label_atom_idB } = unitB.model.atomicHierarchy.atoms;
  36. const { label_comp_id: label_comp_idA, auth_seq_id: auth_seq_idA } = unitA.model.atomicHierarchy.residues;
  37. const { label_comp_id: label_comp_idB, auth_seq_id: auth_seq_idB } = unitB.model.atomicHierarchy.residues;
  38. const { occupancy: occupancyA } = unitA.model.atomicConformation;
  39. const { occupancy: occupancyB } = unitB.model.atomicConformation;
  40. const { lookup3d } = unitB;
  41. const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model)
  42. const indexPairs = unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model)
  43. // the lookup queries need to happen in the "unitB space".
  44. // that means imageA = inverseOperB(operA(aI))
  45. const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix);
  46. const isNotIdentity = !Mat4.isIdentity(imageTransform);
  47. const imageA = Vec3.zero();
  48. const { center: bCenter, radius: bRadius } = lookup3d.boundary.sphere;
  49. const testDistanceSq = (bRadius + MAX_RADIUS) * (bRadius + MAX_RADIUS);
  50. builder.startUnitPair(unitA, unitB)
  51. for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
  52. const aI = atomsA[_aI];
  53. Vec3.set(imageA, xA[aI], yA[aI], zA[aI]);
  54. if (isNotIdentity) Vec3.transformMat4(imageA, imageA, imageTransform);
  55. if (Vec3.squaredDistance(imageA, bCenter) > testDistanceSq) continue;
  56. if (!props.forceCompute && indexPairs) {
  57. for (let i = indexPairs.offset[aI], il = indexPairs.offset[aI + 1]; i < il; ++i) {
  58. const _bI = SortedArray.indexOf(unitA.elements, indexPairs.b[i]) as StructureElement.UnitIndex;
  59. if (_bI < 0) continue;
  60. builder.add(_aI, _bI, { order: indexPairs.edgeProps.order[i], flag: BondType.Flag.Covalent });
  61. }
  62. continue // assume `indexPairs` supplies all bonds
  63. }
  64. const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
  65. if (structConnEntries && structConnEntries.length) {
  66. let added = false;
  67. for (const se of structConnEntries) {
  68. const { partnerA, partnerB } = se
  69. const p = partnerA.atomIndex === aI ? partnerB : partnerA
  70. const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex;
  71. if (_bI < 0) continue;
  72. // check if the bond is within MAX_RADIUS for this pair of units
  73. if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue;
  74. builder.add(_aI, _bI, { order: se.order, flag: se.flags });
  75. added = true;
  76. }
  77. // assume, for an atom, that if any inter unit bond is given
  78. // all are given and thus we don't need to compute any other
  79. if (added) continue;
  80. }
  81. const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_RADIUS);
  82. if (count === 0) continue;
  83. const aeI = getElementIdx(type_symbolA.value(aI));
  84. const isHa = isHydrogen(aeI);
  85. const thresholdA = getElementThreshold(aeI);
  86. const altA = label_alt_idA.value(aI);
  87. const metalA = MetalsSet.has(aeI);
  88. const atomIdA = label_atom_idA.value(aI);
  89. const compIdA = label_comp_idA.value(residueIndexA[aI]);
  90. const occA = occupancyA.value(aI);
  91. for (let ni = 0; ni < count; ni++) {
  92. const _bI = indices[ni] as StructureElement.UnitIndex;
  93. const bI = atomsB[_bI];
  94. const altB = label_alt_idB.value(bI);
  95. if (altA && altB && altA !== altB) continue;
  96. // Do not include bonds between images of the same residue with partial occupancy.
  97. // TODO: is this condition good enough?
  98. // - It works for cases like 3WQJ (label_asym_id: I) which have partial occupancy.
  99. // - Does NOT work for cases like 1RB8 (DC 7) with full occupancy.
  100. if (occupancyB.value(bI) < 1 && occA < 1) {
  101. if (auth_seq_idA.value(aI) === auth_seq_idB.value(bI)) {
  102. continue;
  103. }
  104. }
  105. const beI = getElementIdx(type_symbolB.value(bI)!);
  106. const isHb = isHydrogen(beI);
  107. if (isHa && isHb) continue;
  108. const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
  109. const dist = Math.sqrt(squaredDistances[ni]);
  110. if (dist === 0) continue;
  111. const thresholdAB = getElementPairThreshold(aeI, beI);
  112. const pairingThreshold = thresholdAB > 0
  113. ? thresholdAB
  114. : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI));
  115. if (dist <= pairingThreshold) {
  116. const atomIdB = label_atom_idB.value(bI);
  117. const compIdB = label_comp_idB.value(residueIndexB[bI]);
  118. builder.add(_aI, _bI, {
  119. order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB),
  120. flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed
  121. });
  122. }
  123. }
  124. }
  125. builder.finishUnitPair()
  126. }
  127. export interface InterBondComputationProps extends BondComputationProps {
  128. validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
  129. }
  130. function findBonds(structure: Structure, props: InterBondComputationProps) {
  131. const builder = new InterUnitGraph.Builder<Unit.Atomic, StructureElement.UnitIndex, InterUnitEdgeProps>()
  132. if (props.noCompute) {
  133. // TODO add function that only adds bonds defined in structConn and avoids using
  134. // structure.lookup and unit.lookup (expensive for large structure and not
  135. // needed for archival files or files with an MD topology)
  136. return new InterUnitBonds(builder.getMap());
  137. }
  138. Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
  139. findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder)
  140. }, {
  141. maxRadius: MAX_RADIUS,
  142. validUnit: (unit: Unit) => Unit.isAtomic(unit),
  143. validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB)
  144. })
  145. return new InterUnitBonds(builder.getMap());
  146. }
  147. function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondComputationProps>): InterUnitBonds {
  148. return findBonds(structure, {
  149. ...DefaultBondComputationProps,
  150. validUnitPair: (props && props.validUnitPair) || Structure.validUnitPair,
  151. });
  152. }
  153. export { computeInterUnitBonds };