inter-compute.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /**
  2. * Copyright (c) 2017-2022 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, MoleculeType } from '../../../model/types';
  8. import { Structure } from '../../structure';
  9. import { Unit } from '../../unit';
  10. import { getElementIdx, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps, getPairingThreshold } 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. import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
  21. import { Model } from '../../../model';
  22. // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  23. const v3distance = Vec3.distance;
  24. const v3set = Vec3.set;
  25. const v3squaredDistance = Vec3.squaredDistance;
  26. const v3transformMat4 = Vec3.transformMat4;
  27. const tmpDistVecA = Vec3();
  28. const tmpDistVecB = Vec3();
  29. function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) {
  30. unitA.conformation.position(indexA, tmpDistVecA);
  31. unitB.conformation.position(indexB, tmpDistVecB);
  32. return v3distance(tmpDistVecA, tmpDistVecB);
  33. }
  34. const _imageTransform = Mat4();
  35. const _imageA = Vec3();
  36. function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComputationProps, builder: InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>) {
  37. const { maxRadius } = props;
  38. const { elements: atomsA, residueIndex: residueIndexA } = unitA;
  39. const { x: xA, y: yA, z: zA } = unitA.model.atomicConformation;
  40. const { elements: atomsB, residueIndex: residueIndexB } = unitB;
  41. const atomCount = unitA.elements.length;
  42. const { type_symbol: type_symbolA, label_alt_id: label_alt_idA, label_atom_id: label_atom_idA, label_comp_id: label_comp_idA } = unitA.model.atomicHierarchy.atoms;
  43. const { type_symbol: type_symbolB, label_alt_id: label_alt_idB, label_atom_id: label_atom_idB, label_comp_id: label_comp_idB } = unitB.model.atomicHierarchy.atoms;
  44. const { auth_seq_id: auth_seq_idA } = unitA.model.atomicHierarchy.residues;
  45. const { auth_seq_id: auth_seq_idB } = unitB.model.atomicHierarchy.residues;
  46. const { occupancy: occupancyA } = unitA.model.atomicConformation;
  47. const { occupancy: occupancyB } = unitB.model.atomicConformation;
  48. const hasOccupancy = occupancyA.isDefined && occupancyB.isDefined;
  49. const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model);
  50. const indexPairs = !props.forceCompute && unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model);
  51. const { atomSourceIndex: sourceIndex } = unitA.model.atomicHierarchy;
  52. const { invertedIndex } = indexPairs ? Model.getInvertedAtomSourceIndex(unitB.model) : { invertedIndex: void 0 };
  53. const structConnExhaustive = unitA.model === unitB.model && StructConn.isExhaustive(unitA.model);
  54. // the lookup queries need to happen in the "unitB space".
  55. // that means _imageA = inverseOperB(operA(aI))
  56. const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix);
  57. const isNotIdentity = !Mat4.isIdentity(imageTransform);
  58. const { center: bCenter, radius: bRadius } = unitB.boundary.sphere;
  59. const testDistanceSq = (bRadius + maxRadius) * (bRadius + maxRadius);
  60. builder.startUnitPair(unitA.id, unitB.id);
  61. const opKeyA = unitA.conformation.operator.key;
  62. const opKeyB = unitB.conformation.operator.key;
  63. for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
  64. const aI = atomsA[_aI];
  65. v3set(_imageA, xA[aI], yA[aI], zA[aI]);
  66. if (isNotIdentity) v3transformMat4(_imageA, _imageA, imageTransform);
  67. if (v3squaredDistance(_imageA, bCenter) > testDistanceSq) continue;
  68. if (!props.forceCompute && indexPairs) {
  69. const { maxDistance } = indexPairs;
  70. const { offset, b, edgeProps: { order, distance, flag, key, operatorA, operatorB } } = indexPairs.bonds;
  71. const srcA = sourceIndex.value(aI);
  72. const aeI = getElementIdx(type_symbolA.value(aI));
  73. for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
  74. const bI = invertedIndex![b[i]];
  75. const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
  76. if (_bI < 0) continue;
  77. const opA = operatorA[i];
  78. const opB = operatorB[i];
  79. if ((opA >= 0 && opA !== opKeyA && opA !== opKeyB) ||
  80. (opB >= 0 && opB !== opKeyB && opB !== opKeyA)) continue;
  81. const beI = getElementIdx(type_symbolA.value(bI));
  82. const d = distance[i];
  83. const dist = getDistance(unitA, aI, unitB, bI);
  84. let add = false;
  85. if (d >= 0) {
  86. add = equalEps(dist, d, 0.3);
  87. } else if (maxDistance >= 0) {
  88. add = dist < maxDistance;
  89. } else {
  90. const pairingThreshold = getPairingThreshold(
  91. aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
  92. );
  93. add = dist < pairingThreshold;
  94. if (isHydrogen(aeI) && isHydrogen(beI)) {
  95. // TODO handle molecular hydrogen
  96. add = false;
  97. }
  98. }
  99. if (add) {
  100. builder.add(_aI, _bI, { order: order[i], flag: flag[i], key: key[i] });
  101. }
  102. }
  103. continue; // assume `indexPairs` supplies all bonds
  104. }
  105. const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
  106. if (structConnEntries && structConnEntries.length) {
  107. let added = false;
  108. for (const se of structConnEntries) {
  109. const { partnerA, partnerB } = se;
  110. const p = partnerA.atomIndex === aI ? partnerB : partnerA;
  111. const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex;
  112. if (_bI < 0) continue;
  113. // check if the bond is within MAX_RADIUS for this pair of units
  114. if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
  115. builder.add(_aI, _bI, { order: se.order, flag: se.flags, key: se.rowIndex });
  116. added = true;
  117. }
  118. // assume, for an atom, that if any inter unit bond is given
  119. // all are given and thus we don't need to compute any other
  120. if (added) continue;
  121. }
  122. if (structConnExhaustive) continue;
  123. const occA = occupancyA.value(aI);
  124. const { lookup3d } = unitB;
  125. const { indices, count, squaredDistances } = lookup3d.find(_imageA[0], _imageA[1], _imageA[2], maxRadius);
  126. if (count === 0) continue;
  127. const aeI = getElementIdx(type_symbolA.value(aI));
  128. const isHa = isHydrogen(aeI);
  129. const thresholdA = getElementThreshold(aeI);
  130. const altA = label_alt_idA.value(aI);
  131. const metalA = MetalsSet.has(aeI);
  132. const atomIdA = label_atom_idA.value(aI);
  133. const compIdA = label_comp_idA.value(residueIndexA[aI]);
  134. for (let ni = 0; ni < count; ni++) {
  135. const _bI = indices[ni] as StructureElement.UnitIndex;
  136. const bI = atomsB[_bI];
  137. const altB = label_alt_idB.value(bI);
  138. if (altA && altB && altA !== altB) continue;
  139. // Do not include bonds between images of the same residue with partial occupancy.
  140. // TODO: is this condition good enough?
  141. // - It works for cases like 3WQJ (label_asym_id: I) which have partial occupancy.
  142. // - Does NOT work for cases like 1RB8 (DC 7) with full occupancy.
  143. if (hasOccupancy && occupancyB.value(bI) < 1 && occA < 1) {
  144. if (auth_seq_idA.value(aI) === auth_seq_idB.value(bI)) {
  145. continue;
  146. }
  147. }
  148. const beI = getElementIdx(type_symbolB.value(bI)!);
  149. const isHb = isHydrogen(beI);
  150. if (isHa && isHb) continue;
  151. const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
  152. const dist = Math.sqrt(squaredDistances[ni]);
  153. if (dist === 0) continue;
  154. const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
  155. if (dist <= pairingThreshold) {
  156. const atomIdB = label_atom_idB.value(bI);
  157. const compIdB = label_comp_idB.value(residueIndexB[bI]);
  158. builder.add(_aI, _bI, {
  159. order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB),
  160. flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed,
  161. key: -1
  162. });
  163. }
  164. }
  165. }
  166. builder.finishUnitPair();
  167. }
  168. export interface InterBondComputationProps extends BondComputationProps {
  169. validUnit: (unit: Unit) => boolean
  170. validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
  171. ignoreWater: boolean
  172. ignoreIon: boolean
  173. }
  174. const DefaultInterBondComputationProps = {
  175. ...DefaultBondComputationProps,
  176. ignoreWater: true,
  177. ignoreIon: true,
  178. };
  179. function findBonds(structure: Structure, props: InterBondComputationProps) {
  180. const builder = new InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>();
  181. const hasIndexPairBonds = structure.models.some(m => IndexPairBonds.Provider.get(m));
  182. const hasExhaustiveStructConn = structure.models.some(m => StructConn.isExhaustive(m));
  183. if (props.noCompute || (structure.isCoarseGrained && !hasIndexPairBonds && !hasExhaustiveStructConn)) {
  184. return new InterUnitBonds(builder.getMap());
  185. }
  186. Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
  187. findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder);
  188. }, {
  189. maxRadius: props.maxRadius,
  190. validUnit: (unit: Unit) => props.validUnit(unit),
  191. validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB)
  192. });
  193. return new InterUnitBonds(builder.getMap());
  194. }
  195. function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondComputationProps>): InterUnitBonds {
  196. const p = { ...DefaultInterBondComputationProps, ...props };
  197. return findBonds(structure, {
  198. ...p,
  199. validUnit: (props && props.validUnit) || (u => Unit.isAtomic(u)),
  200. validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
  201. const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
  202. const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
  203. const notWater = (
  204. (!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
  205. (!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
  206. );
  207. const sameModel = a.model === b.model;
  208. const notIonA = (!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Ion) || (sameModel && hasStructConnRecord(a));
  209. const notIonB = (!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Ion) || (sameModel && hasStructConnRecord(b));
  210. const notIon = notIonA && notIonB;
  211. return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
  212. }),
  213. });
  214. }
  215. function hasStructConnRecord(unit: Unit) {
  216. if (!Unit.isAtomic(unit)) return false;
  217. const elements = unit.elements;
  218. const structConn = StructConn.Provider.get(unit.model);
  219. if (structConn) {
  220. for (let i = 0, _i = elements.length; i < _i; i++) {
  221. if (structConn.byAtomIndex.get(elements[i])) {
  222. return true;
  223. }
  224. }
  225. }
  226. return false;
  227. }
  228. export { computeInterUnitBonds };