structure.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /**
  2. * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int'
  7. import { UniqueArray } from 'mol-data/generic'
  8. import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
  9. import { Model, ElementIndex } from '../model'
  10. import { sort, arraySwap, hash1, sortArray, hashString } from 'mol-data/util';
  11. import StructureElement from './element'
  12. import Unit from './unit'
  13. import { StructureLookup3D } from './util/lookup3d';
  14. import { CoarseElements } from '../model/properties/coarse';
  15. import { StructureSubsetBuilder } from './util/subset-builder';
  16. import { InterUnitBonds, computeInterUnitBonds } from './unit/links';
  17. import { PairRestraints, CrossLinkRestraint, extractCrossLinkRestraints } from './unit/pair-restraints';
  18. import StructureSymmetry from './symmetry';
  19. import StructureProperties from './properties';
  20. import { ResidueIndex, ChainIndex } from '../model/indexing';
  21. import { Carbohydrates } from './carbohydrates/data';
  22. import { computeCarbohydrates } from './carbohydrates/compute';
  23. import { Vec3 } from 'mol-math/linear-algebra';
  24. import { idFactory } from 'mol-util/id-factory';
  25. import { GridLookup3D } from 'mol-math/geometry';
  26. class Structure {
  27. /** Maps unit.id to unit */
  28. readonly unitMap: IntMap<Unit>;
  29. /** Array of all units in the structure, sorted by unit.id */
  30. readonly units: ReadonlyArray<Unit>;
  31. private _props: {
  32. lookup3d?: StructureLookup3D,
  33. links?: InterUnitBonds,
  34. crossLinkRestraints?: PairRestraints<CrossLinkRestraint>,
  35. unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
  36. carbohydrates?: Carbohydrates,
  37. models?: ReadonlyArray<Model>,
  38. hashCode: number,
  39. elementCount: number,
  40. polymerResidueCount: number,
  41. } = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 };
  42. subsetBuilder(isSorted: boolean) {
  43. return new StructureSubsetBuilder(this, isSorted);
  44. }
  45. /** Count of all elements in the structure, i.e. the sum of the elements in the units */
  46. get elementCount() {
  47. return this._props.elementCount;
  48. }
  49. /** Count of all polymer residues in the structure */
  50. get polymerResidueCount() {
  51. return this._props.polymerResidueCount;
  52. }
  53. /** Coarse structure, defined as Containing less than twice as many elements as polymer residues */
  54. get isCoarse() {
  55. const ec = this.elementCount
  56. const prc = this.polymerResidueCount
  57. return prc && ec ? ec / prc < 2 : false
  58. }
  59. get hashCode() {
  60. if (this._props.hashCode !== -1) return this._props.hashCode;
  61. return this.computeHash();
  62. }
  63. private computeHash() {
  64. let hash = 23;
  65. for (let i = 0, _i = this.units.length; i < _i; i++) {
  66. const u = this.units[i];
  67. hash = (31 * hash + u.id) | 0;
  68. hash = (31 * hash + SortedArray.hashCode(u.elements)) | 0;
  69. }
  70. hash = (31 * hash + this.elementCount) | 0;
  71. hash = hash1(hash);
  72. if (hash === -1) hash = 0;
  73. this._props.hashCode = hash;
  74. return hash;
  75. }
  76. /** Returns a new element location iterator */
  77. elementLocations(): Iterator<StructureElement> {
  78. return new Structure.ElementLocationIterator(this);
  79. }
  80. get boundary() {
  81. return this.lookup3d.boundary;
  82. }
  83. get lookup3d() {
  84. if (this._props.lookup3d) return this._props.lookup3d;
  85. this._props.lookup3d = new StructureLookup3D(this);
  86. return this._props.lookup3d;
  87. }
  88. get links() {
  89. if (this._props.links) return this._props.links;
  90. this._props.links = computeInterUnitBonds(this);
  91. return this._props.links;
  92. }
  93. get crossLinkRestraints() {
  94. if (this._props.crossLinkRestraints) return this._props.crossLinkRestraints;
  95. this._props.crossLinkRestraints = extractCrossLinkRestraints(this);
  96. return this._props.crossLinkRestraints;
  97. }
  98. get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
  99. if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
  100. this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
  101. return this._props.unitSymmetryGroups;
  102. }
  103. get carbohydrates(): Carbohydrates {
  104. if (this._props.carbohydrates) return this._props.carbohydrates;
  105. this._props.carbohydrates = computeCarbohydrates(this);
  106. return this._props.carbohydrates;
  107. }
  108. get models(): ReadonlyArray<Model> {
  109. if (this._props.models) return this._props.models;
  110. this._props.models = getModels(this);
  111. return this._props.models;
  112. }
  113. hasElement(e: StructureElement) {
  114. if (!this.unitMap.has(e.unit.id)) return false;
  115. return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element);
  116. }
  117. constructor(units: ArrayLike<Unit>) {
  118. const map = IntMap.Mutable<Unit>();
  119. let elementCount = 0;
  120. let polymerResidueCount = 0;
  121. let isSorted = true;
  122. let lastId = units.length > 0 ? units[0].id : 0;
  123. for (let i = 0, _i = units.length; i < _i; i++) {
  124. const u = units[i];
  125. map.set(u.id, u);
  126. elementCount += u.elements.length;
  127. polymerResidueCount += u.polymerElements.length;
  128. if (u.id < lastId) isSorted = false;
  129. lastId = u.id;
  130. }
  131. if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap)
  132. this.unitMap = map;
  133. this.units = units as ReadonlyArray<Unit>;
  134. this._props.elementCount = elementCount;
  135. this._props.polymerResidueCount = polymerResidueCount;
  136. }
  137. }
  138. function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
  139. function getModels(s: Structure) {
  140. const { units } = s;
  141. const arr = UniqueArray.create<Model['id'], Model>();
  142. for (const u of units) {
  143. UniqueArray.add(arr, u.model.id, u.model);
  144. }
  145. return arr.array;
  146. }
  147. namespace Structure {
  148. export const Empty = new Structure([]);
  149. export function create(units: ReadonlyArray<Unit>): Structure { return new Structure(units); }
  150. /**
  151. * Construct a Structure from a model.
  152. *
  153. * Generally, a single unit corresponds to a single chain, with the exception
  154. * of consecutive "single atom chains".
  155. */
  156. export function ofModel(model: Model): Structure {
  157. const chains = model.atomicHierarchy.chainAtomSegments;
  158. const builder = new StructureBuilder();
  159. for (let c = 0; c < chains.count; c++) {
  160. const start = chains.offsets[c];
  161. // merge all consecutive "single atom chains"
  162. while (c + 1 < chains.count
  163. && chains.offsets[c + 1] - chains.offsets[c] === 1
  164. && chains.offsets[c + 2] - chains.offsets[c + 1] === 1) {
  165. c++;
  166. }
  167. const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
  168. if (isWaterChain(model, c as ChainIndex, elements)) {
  169. partitionAtomicUnit(model, elements, builder);
  170. } else {
  171. builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
  172. }
  173. }
  174. const cs = model.coarseHierarchy;
  175. if (cs.isDefined) {
  176. if (cs.spheres.count > 0) {
  177. addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
  178. }
  179. if (cs.gaussians.count > 0) {
  180. addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
  181. }
  182. }
  183. return builder.getStructure();
  184. }
  185. function isWaterChain(model: Model, chainIndex: ChainIndex, indices: SortedArray) {
  186. const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
  187. return model.entities.data.type.value(e) === 'water';
  188. }
  189. function partitionAtomicUnit(model: Model, indices: SortedArray, builder: StructureBuilder) {
  190. const { x, y, z } = model.atomicConformation;
  191. const lookup = GridLookup3D({ x, y, z, indices }, Vec3.create(64, 64, 64));
  192. const { offset, count, array } = lookup.buckets;
  193. for (let i = 0, _i = offset.length; i < _i; i++) {
  194. const start = offset[i];
  195. const set = new Int32Array(count[i]);
  196. for (let j = 0, _j = count[i]; j < _j; j++) {
  197. set[j] = indices[array[start + j]];
  198. }
  199. builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set));
  200. }
  201. }
  202. function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
  203. const { chainElementSegments } = elements;
  204. for (let cI = 0; cI < chainElementSegments.count; cI++) {
  205. const elements = SortedArray.ofBounds<ElementIndex>(chainElementSegments.offsets[cI], chainElementSegments.offsets[cI + 1]);
  206. builder.addUnit(kind, model, SymmetryOperator.Default, elements);
  207. }
  208. }
  209. export class StructureBuilder {
  210. private units: Unit[] = [];
  211. private invariantId = idFactory()
  212. addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
  213. const unit = Unit.create(this.units.length, this.invariantId(), kind, model, operator, elements);
  214. this.units.push(unit);
  215. return unit;
  216. }
  217. addWithOperator(unit: Unit, operator: SymmetryOperator): Unit {
  218. const newUnit = unit.applyOperator(this.units.length, operator);
  219. this.units.push(newUnit);
  220. return newUnit;
  221. }
  222. getStructure(): Structure {
  223. return create(this.units);
  224. }
  225. get isEmpty() {
  226. return this.units.length === 0;
  227. }
  228. }
  229. export function Builder() { return new StructureBuilder(); }
  230. export function hashCode(s: Structure) {
  231. return s.hashCode;
  232. }
  233. export function conformationHash(s: Structure) {
  234. return hashString(s.units.map(u => Unit.conformationId(u)).join('|'))
  235. }
  236. export function areEqual(a: Structure, b: Structure) {
  237. if (a.elementCount !== b.elementCount) return false;
  238. const len = a.units.length;
  239. if (len !== b.units.length) return false;
  240. for (let i = 0; i < len; i++) {
  241. if (a.units[i].id !== b.units[i].id) return false;
  242. }
  243. for (let i = 0; i < len; i++) {
  244. if (!SortedArray.areEqual(a.units[i].elements, b.units[i].elements)) return false;
  245. }
  246. return true;
  247. }
  248. export class ElementLocationIterator implements Iterator<StructureElement> {
  249. private current = StructureElement.create();
  250. private unitIndex = 0;
  251. private elements: StructureElement.Set;
  252. private maxIdx = 0;
  253. private idx = -1;
  254. hasNext: boolean;
  255. move(): StructureElement {
  256. this.advance();
  257. this.current.element = this.elements[this.idx];
  258. return this.current;
  259. }
  260. private advance() {
  261. if (this.idx < this.maxIdx) {
  262. this.idx++;
  263. if (this.idx === this.maxIdx) this.hasNext = this.unitIndex + 1 < this.structure.units.length;
  264. return;
  265. }
  266. this.idx = 0;
  267. this.unitIndex++;
  268. if (this.unitIndex >= this.structure.units.length) {
  269. this.hasNext = false;
  270. return;
  271. }
  272. this.current.unit = this.structure.units[this.unitIndex];
  273. this.elements = this.current.unit.elements;
  274. this.maxIdx = this.elements.length - 1;
  275. }
  276. constructor(private structure: Structure) {
  277. this.hasNext = structure.elementCount > 0;
  278. if (this.hasNext) {
  279. this.elements = structure.units[0].elements;
  280. this.maxIdx = this.elements.length - 1;
  281. this.current.unit = structure.units[0];
  282. }
  283. }
  284. }
  285. export function getEntityKeys(structure: Structure) {
  286. const { units } = structure;
  287. const l = StructureElement.create();
  288. const keys = UniqueArray.create<number, number>();
  289. for (const unit of units) {
  290. const prop = unit.kind === Unit.Kind.Atomic ? StructureProperties.entity.key : StructureProperties.coarse.entityKey;
  291. l.unit = unit;
  292. const elements = unit.elements;
  293. const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
  294. while (chainsIt.hasNext) {
  295. const chainSegment = chainsIt.move();
  296. l.element = elements[chainSegment.start];
  297. const key = prop(l);
  298. UniqueArray.add(keys, key, key);
  299. }
  300. }
  301. sortArray(keys.array);
  302. return keys.array;
  303. }
  304. export function getUniqueAtomicResidueIndices(structure: Structure, model: Model): ReadonlyArray<ResidueIndex> {
  305. const uniqueResidues = UniqueArray.create<ResidueIndex, ResidueIndex>();
  306. const unitGroups = structure.unitSymmetryGroups;
  307. for (const unitGroup of unitGroups) {
  308. const unit = unitGroup.units[0];
  309. if (unit.model !== model || !Unit.isAtomic(unit)) {
  310. continue;
  311. }
  312. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  313. while (residues.hasNext) {
  314. const seg = residues.move();
  315. UniqueArray.add(uniqueResidues, seg.index, seg.index);
  316. }
  317. }
  318. sortArray(uniqueResidues.array);
  319. return uniqueResidues.array;
  320. }
  321. const distVec = Vec3.zero();
  322. function unitElementMinDistance(unit: Unit, p: Vec3, eRadius: number) {
  323. const { elements, conformation: { position, r } } = unit, dV = distVec;
  324. let minD = Number.MAX_VALUE;
  325. for (let i = 0, _i = elements.length; i < _i; i++) {
  326. const e = elements[i];
  327. const d = Vec3.distance(p, position(e, dV)) - eRadius - r(e);
  328. if (d < minD) minD = d;
  329. }
  330. return minD;
  331. }
  332. export function minDistanceToPoint(s: Structure, point: Vec3, radius: number) {
  333. const { units } = s;
  334. let minD = Number.MAX_VALUE;
  335. for (let i = 0, _i = units.length; i < _i; i++) {
  336. const unit = units[i];
  337. const d = unitElementMinDistance(unit, point, radius);
  338. if (d < minD) minD = d;
  339. }
  340. return minD;
  341. }
  342. const distPivot = Vec3.zero();
  343. export function distance(a: Structure, b: Structure) {
  344. if (a.elementCount === 0 || b.elementCount === 0) return 0;
  345. const { units } = a;
  346. let minD = Number.MAX_VALUE;
  347. for (let i = 0, _i = units.length; i < _i; i++) {
  348. const unit = units[i];
  349. const { elements, conformation: { position, r } } = unit;
  350. for (let i = 0, _i = elements.length; i < _i; i++) {
  351. const e = elements[i];
  352. const d = minDistanceToPoint(b, position(e, distPivot), r(e));
  353. if (d < minD) minD = d;
  354. }
  355. }
  356. return minD;
  357. }
  358. }
  359. export default Structure