structure.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. /**
  2. * Copyright (c) 2017-2019 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 { IntMap, SortedArray, Iterator, Segmentation, Interval } from '../../../mol-data/int'
  8. import { UniqueArray } from '../../../mol-data/generic'
  9. import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator'
  10. import { Model, ElementIndex } from '../model'
  11. import { sort, arraySwap, hash1, sortArray, hashString, hashFnv32a } from '../../../mol-data/util';
  12. import StructureElement from './element'
  13. import Unit from './unit'
  14. import { StructureLookup3D } from './util/lookup3d';
  15. import { CoarseElements } from '../model/properties/coarse';
  16. import { StructureSubsetBuilder } from './util/subset-builder';
  17. import { InterUnitBonds, computeInterUnitBonds } from './unit/links';
  18. import { PairRestraints, CrossLinkRestraint, extractCrossLinkRestraints } from './unit/pair-restraints';
  19. import StructureSymmetry from './symmetry';
  20. import StructureProperties from './properties';
  21. import { ResidueIndex, ChainIndex, EntityIndex } from '../model/indexing';
  22. import { Carbohydrates } from './carbohydrates/data';
  23. import { computeCarbohydrates } from './carbohydrates/compute';
  24. import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
  25. import { idFactory } from '../../../mol-util/id-factory';
  26. import { GridLookup3D } from '../../../mol-math/geometry';
  27. import { UUID } from '../../../mol-util';
  28. import { CustomProperties } from '../common/custom-property';
  29. import { AtomicHierarchy } from '../model/properties/atomic';
  30. class Structure {
  31. /** Maps unit.id to unit */
  32. readonly unitMap: IntMap<Unit>;
  33. /** Maps unit.id to index of unit in units array */
  34. readonly unitIndexMap: IntMap<number>;
  35. /** Array of all units in the structure, sorted by unit.id */
  36. readonly units: ReadonlyArray<Unit>;
  37. private _props: {
  38. parent?: Structure,
  39. lookup3d?: StructureLookup3D,
  40. links?: InterUnitBonds,
  41. crossLinkRestraints?: PairRestraints<CrossLinkRestraint>,
  42. unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
  43. carbohydrates?: Carbohydrates,
  44. models?: ReadonlyArray<Model>,
  45. model?: Model,
  46. masterModel?: Model,
  47. representativeModel?: Model,
  48. uniqueResidueNames?: Set<string>,
  49. entityIndices?: ReadonlyArray<EntityIndex>,
  50. uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
  51. serialMapping?: SerialMapping,
  52. hashCode: number,
  53. /** Hash based on all unit.id values in the structure, reflecting the units transformation */
  54. transformHash: number,
  55. elementCount: number,
  56. polymerResidueCount: number,
  57. coordinateSystem: SymmetryOperator,
  58. label: string,
  59. propertyData?: any,
  60. customProps?: CustomProperties
  61. } = {
  62. hashCode: -1,
  63. transformHash: -1,
  64. elementCount: -1,
  65. polymerResidueCount: -1,
  66. coordinateSystem: SymmetryOperator.Default,
  67. label: ''
  68. };
  69. subsetBuilder(isSorted: boolean) {
  70. return new StructureSubsetBuilder(this, isSorted);
  71. }
  72. /** Count of all elements in the structure, i.e. the sum of the elements in the units */
  73. get elementCount() {
  74. return this._props.elementCount;
  75. }
  76. get hasCustomProperties() {
  77. return !!this._props.customProps && this._props.customProps.all.length > 0;
  78. }
  79. get customPropertyDescriptors() {
  80. if (!this._props.customProps) this._props.customProps = new CustomProperties();
  81. return this._props.customProps;
  82. }
  83. /**
  84. * Property data unique to this instance of the structure.
  85. */
  86. get currentPropertyData() {
  87. if (!this._props.propertyData) this._props.propertyData = Object.create(null);
  88. return this._props.propertyData;
  89. }
  90. /**
  91. * Property data of the parent structure if it exists, currentPropertyData otherwise.
  92. */
  93. get inheritedPropertyData() {
  94. return this.parent ? this.parent.currentPropertyData : this.currentPropertyData;
  95. }
  96. /** Count of all polymer residues in the structure */
  97. get polymerResidueCount() {
  98. if (this._props.polymerResidueCount === -1) {
  99. let polymerResidueCount = 0
  100. for (let i = 0, _i = this.units.length; i < _i; i++) {
  101. polymerResidueCount += this.units[i].polymerElements.length;
  102. }
  103. this._props.polymerResidueCount = polymerResidueCount
  104. }
  105. return this._props.polymerResidueCount;
  106. }
  107. /** Coarse structure, defined as Containing less than twice as many elements as polymer residues */
  108. get isCoarse() {
  109. const ec = this.elementCount
  110. const prc = this.polymerResidueCount
  111. return prc && ec ? ec / prc < 2 : false
  112. }
  113. get isEmpty() {
  114. return this.units.length === 0;
  115. }
  116. get hashCode() {
  117. if (this._props.hashCode !== -1) return this._props.hashCode;
  118. return this.computeHash();
  119. }
  120. get transformHash() {
  121. if (this._props.transformHash !== -1) return this._props.transformHash;
  122. this._props.transformHash = hashFnv32a(this.units.map(u => u.id))
  123. return this._props.transformHash;
  124. }
  125. private computeHash() {
  126. let hash = 23;
  127. for (let i = 0, _i = this.units.length; i < _i; i++) {
  128. const u = this.units[i];
  129. hash = (31 * hash + u.id) | 0;
  130. hash = (31 * hash + SortedArray.hashCode(u.elements)) | 0;
  131. }
  132. hash = (31 * hash + this.elementCount) | 0;
  133. hash = hash1(hash);
  134. if (hash === -1) hash = 0;
  135. this._props.hashCode = hash;
  136. return hash;
  137. }
  138. /** Returns a new element location iterator */
  139. elementLocations(): Iterator<StructureElement.Location> {
  140. return new Structure.ElementLocationIterator(this);
  141. }
  142. /** The parent or itself in case this is the root */
  143. get root() {
  144. return this._props.parent || this;
  145. }
  146. /** The root/top-most parent or `undefined` in case this is the root */
  147. get parent() {
  148. return this._props.parent;
  149. }
  150. get coordinateSystem() {
  151. return this._props.coordinateSystem;
  152. }
  153. get label() {
  154. return this._props.label;
  155. }
  156. get boundary() {
  157. return this.lookup3d.boundary;
  158. }
  159. get lookup3d() {
  160. if (this._props.lookup3d) return this._props.lookup3d;
  161. this._props.lookup3d = new StructureLookup3D(this);
  162. return this._props.lookup3d;
  163. }
  164. get links() {
  165. if (this._props.links) return this._props.links;
  166. this._props.links = computeInterUnitBonds(this);
  167. return this._props.links;
  168. }
  169. get crossLinkRestraints() {
  170. if (this._props.crossLinkRestraints) return this._props.crossLinkRestraints;
  171. this._props.crossLinkRestraints = extractCrossLinkRestraints(this);
  172. return this._props.crossLinkRestraints;
  173. }
  174. get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
  175. if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
  176. this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
  177. return this._props.unitSymmetryGroups;
  178. }
  179. get carbohydrates(): Carbohydrates {
  180. if (this._props.carbohydrates) return this._props.carbohydrates;
  181. this._props.carbohydrates = computeCarbohydrates(this);
  182. return this._props.carbohydrates;
  183. }
  184. get models(): ReadonlyArray<Model> {
  185. if (this._props.models) return this._props.models;
  186. this._props.models = getModels(this);
  187. return this._props.models;
  188. }
  189. get uniqueResidueNames() {
  190. return this._props.uniqueResidueNames
  191. || (this._props.uniqueResidueNames = getUniqueResidueNames(this));
  192. }
  193. get entityIndices() {
  194. return this._props.entityIndices || (this._props.entityIndices = getEntityIndices(this));
  195. }
  196. get uniqueAtomicResidueIndices() {
  197. return this._props.uniqueAtomicResidueIndices
  198. || (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
  199. }
  200. get isAtomic() {
  201. for (const u of this.units) {
  202. if (u.kind !== Unit.Kind.Atomic) return false;
  203. }
  204. return true;
  205. }
  206. /**
  207. * Provides mapping for serial element indices accross all units.
  208. *
  209. * Note that this is especially costly for structures with many units that are grouped
  210. * into few symmetry groups. Use only when needed and prefer `StructureElement`
  211. * to address elements in a structure.
  212. */
  213. get serialMapping() {
  214. return this._props.serialMapping || (this._props.serialMapping = getSerialMapping(this));
  215. }
  216. /**
  217. * If the structure is based on a single model or has a master-/representative-model, return it.
  218. * Otherwise throw an exception.
  219. */
  220. get model(): Model {
  221. if (this._props.model) return this._props.model;
  222. if (this._props.representativeModel) return this._props.representativeModel;
  223. if (this._props.masterModel) return this._props.masterModel;
  224. const models = this.models;
  225. if (models.length > 1) {
  226. throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
  227. }
  228. this._props.model = models[0];
  229. return this._props.model;
  230. }
  231. get masterModel(): Model | undefined {
  232. return this._props.masterModel
  233. }
  234. get representativeModel(): Model | undefined {
  235. return this._props.representativeModel
  236. }
  237. hasElement(e: StructureElement.Location) {
  238. if (!this.unitMap.has(e.unit.id)) return false;
  239. return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element);
  240. }
  241. getModelIndex(m: Model) {
  242. return this.models.indexOf(m)
  243. }
  244. private initUnits(units: ArrayLike<Unit>) {
  245. const unitMap = IntMap.Mutable<Unit>();
  246. const unitIndexMap = IntMap.Mutable<number>();
  247. let elementCount = 0;
  248. let isSorted = true;
  249. let lastId = units.length > 0 ? units[0].id : 0;
  250. for (let i = 0, _i = units.length; i < _i; i++) {
  251. const u = units[i];
  252. unitMap.set(u.id, u);
  253. elementCount += u.elements.length;
  254. if (u.id < lastId) isSorted = false;
  255. lastId = u.id;
  256. }
  257. if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
  258. for (let i = 0, _i = units.length; i < _i; i++) {
  259. unitIndexMap.set(units[i].id, i);
  260. }
  261. this._props.elementCount = elementCount;
  262. return { unitMap, unitIndexMap };
  263. }
  264. constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
  265. const { unitMap, unitIndexMap } = this.initUnits(units);
  266. this.unitMap = unitMap;
  267. this.unitIndexMap = unitIndexMap;
  268. this.units = units as ReadonlyArray<Unit>;
  269. if (props.parent) this._props.parent = props.parent.parent || props.parent;
  270. if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
  271. else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
  272. if (props.label) this._props.label = props.label;
  273. else if (props.parent) this._props.label = props.parent.label;
  274. if (props.masterModel) this._props.masterModel = props.masterModel;
  275. else if (props.parent) this._props.masterModel = props.parent.masterModel;
  276. if (props.representativeModel) this._props.representativeModel = props.representativeModel;
  277. else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
  278. }
  279. }
  280. function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
  281. function getModels(s: Structure) {
  282. const { units } = s;
  283. const arr = UniqueArray.create<Model['id'], Model>();
  284. for (const u of units) {
  285. UniqueArray.add(arr, u.model.id, u.model);
  286. }
  287. return arr.array;
  288. }
  289. function getUniqueResidueNames(s: Structure) {
  290. const prop = StructureProperties.residue.label_comp_id;
  291. const names = new Set<string>();
  292. const loc = StructureElement.Location.create();
  293. for (const unit of s.units) {
  294. // TODO: support coarse unit?
  295. if (!Unit.isAtomic(unit)) continue;
  296. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  297. loc.unit = unit;
  298. while (residues.hasNext) {
  299. const seg = residues.move();
  300. loc.element = unit.elements[seg.start];
  301. names.add(prop(loc));
  302. }
  303. }
  304. return names;
  305. }
  306. function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
  307. const { units } = structure;
  308. const l = StructureElement.Location.create();
  309. const keys = UniqueArray.create<number, EntityIndex>();
  310. for (const unit of units) {
  311. const prop = unit.kind === Unit.Kind.Atomic ? StructureProperties.entity.key : StructureProperties.coarse.entityKey;
  312. l.unit = unit;
  313. const elements = unit.elements;
  314. const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
  315. while (chainsIt.hasNext) {
  316. const chainSegment = chainsIt.move();
  317. l.element = elements[chainSegment.start];
  318. const key = prop(l);
  319. UniqueArray.add(keys, key, key);
  320. }
  321. }
  322. sortArray(keys.array);
  323. return keys.array;
  324. }
  325. function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>> {
  326. const map = new Map<UUID, UniqueArray<ResidueIndex, ResidueIndex>>();
  327. const modelIds: UUID[] = [];
  328. const unitGroups = structure.unitSymmetryGroups;
  329. for (const unitGroup of unitGroups) {
  330. const unit = unitGroup.units[0];
  331. if (!Unit.isAtomic(unit)) continue;
  332. let uniqueResidues: UniqueArray<ResidueIndex, ResidueIndex>;
  333. if (map.has(unit.model.id)) uniqueResidues = map.get(unit.model.id)!;
  334. else {
  335. uniqueResidues = UniqueArray.create<ResidueIndex, ResidueIndex>();
  336. modelIds.push(unit.model.id);
  337. map.set(unit.model.id, uniqueResidues);
  338. }
  339. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  340. while (residues.hasNext) {
  341. const seg = residues.move();
  342. UniqueArray.add(uniqueResidues, seg.index, seg.index);
  343. }
  344. }
  345. const ret = new Map<UUID, ReadonlyArray<ResidueIndex>>();
  346. for (const id of modelIds) {
  347. const array = map.get(id)!.array;
  348. sortArray(array);
  349. ret.set(id, array)
  350. }
  351. return ret;
  352. }
  353. interface SerialMapping {
  354. /** Cummulative count of elements for each unit */
  355. unitElementCount: ArrayLike<number>
  356. /** Unit index for each serial element in the structure */
  357. unitIndices: ArrayLike<number>
  358. /** Element index for each serial element in the structure */
  359. elementIndices: ArrayLike<ElementIndex>
  360. }
  361. function getSerialMapping(structure: Structure): SerialMapping {
  362. const { units, elementCount } = structure
  363. const unitElementCount = new Uint32Array(units.length)
  364. const unitIndices = new Uint32Array(elementCount)
  365. const elementIndices = new Uint32Array(elementCount)
  366. for (let i = 0, m = 0, il = units.length; i < il; ++i) {
  367. unitElementCount[i] = m
  368. const { elements } = units[i]
  369. for (let j = 0, jl = elements.length; j < jl; ++j) {
  370. const mj = m + j
  371. unitIndices[mj] = i
  372. elementIndices[mj] = elements[j]
  373. }
  374. m += elements.length
  375. }
  376. return {
  377. unitElementCount,
  378. unitIndices,
  379. elementIndices: elementIndices as unknown as ElementIndex[]
  380. }
  381. }
  382. namespace Structure {
  383. export const Empty = new Structure([]);
  384. export interface Props {
  385. parent?: Structure
  386. coordinateSystem?: SymmetryOperator
  387. label?: string
  388. /** Master model for structures of a protein model and multiple ligand models */
  389. masterModel?: Model
  390. /** Representative model for structures of a model trajectory */
  391. representativeModel?: Model
  392. }
  393. /** Serial index of an element in the structure accross all units */
  394. export type SerialIndex = { readonly '@type': 'serial-index' } & number
  395. /** Represents a single structure */
  396. export interface Loci {
  397. readonly kind: 'structure-loci',
  398. readonly structure: Structure,
  399. }
  400. export function Loci(structure: Structure): Loci {
  401. return { kind: 'structure-loci', structure };
  402. }
  403. export function toStructureElementLoci(loci: Loci): StructureElement.Loci {
  404. const elements: StructureElement.Loci['elements'][0][] = []
  405. for (const unit of loci.structure.units) {
  406. elements.push({ unit, indices: Interval.ofBounds(0, unit.elements.length) })
  407. }
  408. return StructureElement.Loci(loci.structure, elements);
  409. }
  410. export function isLoci(x: any): x is Loci {
  411. return !!x && x.kind === 'structure-loci';
  412. }
  413. export function areLociEqual(a: Loci, b: Loci) {
  414. return a.structure === b.structure
  415. }
  416. export function isLociEmpty(loci: Loci) {
  417. return loci.structure.isEmpty
  418. }
  419. export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
  420. return new Structure(units, props);
  421. }
  422. export function ofTrajectory(trajectory: ReadonlyArray<Model>): Structure {
  423. if (trajectory.length === 0) return Empty
  424. const units: Unit[] = [];
  425. let count = 0
  426. for (let i = 0, il = trajectory.length; i < il; ++i) {
  427. const structure = ofModel(trajectory[i])
  428. for (let j = 0, jl = structure.units.length; j < jl; ++j) {
  429. const u = structure.units[j]
  430. const invariantId = u.invariantId + count
  431. const newUnit = Unit.create(units.length, invariantId, u.kind, u.model, u.conformation.operator, u.elements)
  432. units.push(newUnit)
  433. }
  434. count = units.length
  435. }
  436. return create(units, { representativeModel: trajectory[0], label: trajectory[0].label });
  437. }
  438. /**
  439. * Construct a Structure from a model.
  440. *
  441. * Generally, a single unit corresponds to a single chain, with the exception
  442. * of consecutive "single atom chains" with same entity id.
  443. */
  444. export function ofModel(model: Model): Structure {
  445. const chains = model.atomicHierarchy.chainAtomSegments;
  446. const builder = new StructureBuilder({ label: model.label });
  447. for (let c = 0 as ChainIndex; c < chains.count; c++) {
  448. const start = chains.offsets[c];
  449. // set to true for chains that consist of "single atom residues",
  450. // note that it assumes there are no "zero atom residues"
  451. let singleAtomResidues = AtomicHierarchy.chainResidueCount(model.atomicHierarchy, c) === chains.offsets[c + 1] - chains.offsets[c]
  452. // merge all consecutive "single atom chains" with same entity id
  453. while (c + 1 < chains.count
  454. && chains.offsets[c + 1] - chains.offsets[c] === 1
  455. && chains.offsets[c + 2] - chains.offsets[c + 1] === 1
  456. ) {
  457. c++;
  458. singleAtomResidues = true
  459. const e1 = model.atomicHierarchy.index.getEntityFromChain(c);
  460. const e2 = model.atomicHierarchy.index.getEntityFromChain(c + 1 as ChainIndex);
  461. if (e1 !== e2) break
  462. }
  463. const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
  464. if (singleAtomResidues) {
  465. partitionAtomicUnitByAtom(model, elements, builder);
  466. } else if (elements.length > 200000 || isWaterChain(model, c)) {
  467. // split up very large chains e.g. lipid bilayers, micelles or water with explicit H
  468. partitionAtomicUnitByResidue(model, elements, builder);
  469. } else {
  470. builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
  471. }
  472. }
  473. const cs = model.coarseHierarchy;
  474. if (cs.isDefined) {
  475. if (cs.spheres.count > 0) {
  476. addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
  477. }
  478. if (cs.gaussians.count > 0) {
  479. addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
  480. }
  481. }
  482. return builder.getStructure();
  483. }
  484. function isWaterChain(model: Model, chainIndex: ChainIndex) {
  485. const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
  486. return model.entities.data.type.value(e) === 'water';
  487. }
  488. function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder) {
  489. const { x, y, z } = model.atomicConformation;
  490. const lookup = GridLookup3D({ x, y, z, indices }, 8192);
  491. const { offset, count, array } = lookup.buckets;
  492. for (let i = 0, _i = offset.length; i < _i; i++) {
  493. const start = offset[i];
  494. const set = new Int32Array(count[i]);
  495. for (let j = 0, _j = count[i]; j < _j; j++) {
  496. set[j] = indices[array[start + j]];
  497. }
  498. builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set));
  499. }
  500. }
  501. // keeps atoms of residues together
  502. function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder) {
  503. const { residueAtomSegments } = model.atomicHierarchy
  504. const startIndices: number[] = []
  505. const endIndices: number[] = []
  506. const residueIt = Segmentation.transientSegments(residueAtomSegments, indices)
  507. while (residueIt.hasNext) {
  508. const residueSegment = residueIt.move();
  509. startIndices[startIndices.length] = indices[residueSegment.start]
  510. endIndices[endIndices.length] = indices[residueSegment.end]
  511. }
  512. const firstResidueAtomCount = endIndices[0] - startIndices[0]
  513. const gridCellCount = 512 * firstResidueAtomCount
  514. const { x, y, z } = model.atomicConformation;
  515. const lookup = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(startIndices) }, gridCellCount);
  516. const { offset, count, array } = lookup.buckets;
  517. for (let i = 0, _i = offset.length; i < _i; i++) {
  518. const start = offset[i];
  519. const set: number[] = [];
  520. for (let j = 0, _j = count[i]; j < _j; j++) {
  521. const k = array[start + j]
  522. for (let l = startIndices[k], _l = endIndices[k]; l < _l; l++) {
  523. set[set.length] = l;
  524. }
  525. }
  526. builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)));
  527. }
  528. }
  529. function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
  530. const { chainElementSegments } = elements;
  531. for (let cI = 0; cI < chainElementSegments.count; cI++) {
  532. const elements = SortedArray.ofBounds<ElementIndex>(chainElementSegments.offsets[cI], chainElementSegments.offsets[cI + 1]);
  533. builder.addUnit(kind, model, SymmetryOperator.Default, elements);
  534. }
  535. }
  536. export function transform(s: Structure, transform: Mat4) {
  537. if (Mat4.isIdentity(transform)) return s;
  538. if (!Mat4.isRotationAndTranslation(transform, SymmetryOperator.RotationTranslationEpsilon)) throw new Error('Only rotation/translation combination can be applied.');
  539. const units: Unit[] = [];
  540. for (const u of s.units) {
  541. const old = u.conformation.operator;
  542. const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl);
  543. units.push(u.applyOperator(u.id, op));
  544. }
  545. const cs = s.coordinateSystem;
  546. const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
  547. return new Structure(units, { parent: s, coordinateSystem: newCS });
  548. }
  549. export class StructureBuilder {
  550. private units: Unit[] = [];
  551. private invariantId = idFactory()
  552. addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, invariantId?: number): Unit {
  553. if (invariantId === undefined) invariantId = this.invariantId()
  554. const unit = Unit.create(this.units.length, invariantId, kind, model, operator, elements);
  555. this.units.push(unit);
  556. return unit;
  557. }
  558. addWithOperator(unit: Unit, operator: SymmetryOperator): Unit {
  559. const newUnit = unit.applyOperator(this.units.length, operator);
  560. this.units.push(newUnit);
  561. return newUnit;
  562. }
  563. getStructure(): Structure {
  564. return create(this.units, this.props);
  565. }
  566. get isEmpty() {
  567. return this.units.length === 0;
  568. }
  569. constructor(private props: Props = {}) {
  570. }
  571. }
  572. export function Builder(props: Props = {}) {
  573. return new StructureBuilder(props);
  574. }
  575. export function hashCode(s: Structure) {
  576. return s.hashCode;
  577. }
  578. /** Hash based on all unit.model conformation values in the structure */
  579. export function conformationHash(s: Structure) {
  580. return hashString(s.units.map(u => Unit.conformationId(u)).join('|'))
  581. }
  582. export function areUnitAndIndicesEqual(a: Structure, b: Structure) {
  583. if (a.elementCount !== b.elementCount) return false;
  584. const len = a.units.length;
  585. if (len !== b.units.length) return false;
  586. for (let i = 0; i < len; i++) {
  587. if (a.units[i].id !== b.units[i].id) return false;
  588. }
  589. for (let i = 0; i < len; i++) {
  590. if (!SortedArray.areEqual(a.units[i].elements, b.units[i].elements)) return false;
  591. }
  592. return true;
  593. }
  594. export function areEquivalent(a: Structure, b: Structure) {
  595. return a === b || (
  596. a.hashCode === b.hashCode &&
  597. StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups)
  598. )
  599. }
  600. /** Check if the structures or their parents are equivalent */
  601. export function areRootsEquivalent(a: Structure, b: Structure) {
  602. return areEquivalent(a.root, b.root)
  603. }
  604. /** Check if the structures or their parents are equal */
  605. export function areRootsEqual(a: Structure, b: Structure) {
  606. return a.root === b.root
  607. }
  608. export class ElementLocationIterator implements Iterator<StructureElement.Location> {
  609. private current = StructureElement.Location.create();
  610. private unitIndex = 0;
  611. private elements: StructureElement.Set;
  612. private maxIdx = 0;
  613. private idx = -1;
  614. hasNext: boolean;
  615. move(): StructureElement.Location {
  616. this.advance();
  617. this.current.element = this.elements[this.idx];
  618. return this.current;
  619. }
  620. private advance() {
  621. if (this.idx < this.maxIdx) {
  622. this.idx++;
  623. if (this.idx === this.maxIdx) this.hasNext = this.unitIndex + 1 < this.structure.units.length;
  624. return;
  625. }
  626. this.idx = 0;
  627. this.unitIndex++;
  628. if (this.unitIndex >= this.structure.units.length) {
  629. this.hasNext = false;
  630. return;
  631. }
  632. this.current.unit = this.structure.units[this.unitIndex];
  633. this.elements = this.current.unit.elements;
  634. this.maxIdx = this.elements.length - 1;
  635. }
  636. constructor(private structure: Structure) {
  637. this.hasNext = structure.elementCount > 0;
  638. if (this.hasNext) {
  639. this.elements = structure.units[0].elements;
  640. this.maxIdx = this.elements.length - 1;
  641. this.current.unit = structure.units[0];
  642. }
  643. }
  644. }
  645. const distVec = Vec3.zero();
  646. function unitElementMinDistance(unit: Unit, p: Vec3, eRadius: number) {
  647. const { elements, conformation: { position, r } } = unit, dV = distVec;
  648. let minD = Number.MAX_VALUE;
  649. for (let i = 0, _i = elements.length; i < _i; i++) {
  650. const e = elements[i];
  651. const d = Vec3.distance(p, position(e, dV)) - eRadius - r(e);
  652. if (d < minD) minD = d;
  653. }
  654. return minD;
  655. }
  656. export function minDistanceToPoint(s: Structure, point: Vec3, radius: number) {
  657. const { units } = s;
  658. let minD = Number.MAX_VALUE;
  659. for (let i = 0, _i = units.length; i < _i; i++) {
  660. const unit = units[i];
  661. const d = unitElementMinDistance(unit, point, radius);
  662. if (d < minD) minD = d;
  663. }
  664. return minD;
  665. }
  666. const distPivot = Vec3.zero();
  667. export function distance(a: Structure, b: Structure) {
  668. if (a.elementCount === 0 || b.elementCount === 0) return 0;
  669. const { units } = a;
  670. let minD = Number.MAX_VALUE;
  671. for (let i = 0, _i = units.length; i < _i; i++) {
  672. const unit = units[i];
  673. const { elements, conformation: { position, r } } = unit;
  674. for (let i = 0, _i = elements.length; i < _i; i++) {
  675. const e = elements[i];
  676. const d = minDistanceToPoint(b, position(e, distPivot), r(e));
  677. if (d < minD) minD = d;
  678. }
  679. }
  680. return minD;
  681. }
  682. }
  683. export default Structure