structure.ts 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  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 { IntMap, SortedArray, Iterator, Segmentation, Interval, OrderedSet } 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, Bond } from './unit/bonds';
  18. import StructureSymmetry from './symmetry';
  19. import StructureProperties from './properties';
  20. import { ResidueIndex, ChainIndex, EntityIndex } from '../model/indexing';
  21. import { Carbohydrates } from './carbohydrates/data';
  22. import { computeCarbohydrates } from './carbohydrates/compute';
  23. import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
  24. import { idFactory } from '../../../mol-util/id-factory';
  25. import { GridLookup3D } from '../../../mol-math/geometry';
  26. import { UUID } from '../../../mol-util';
  27. import { CustomProperties } from '../../custom-property';
  28. import { AtomicHierarchy } from '../model/properties/atomic';
  29. import { StructureSelection } from '../query/selection';
  30. import { getBoundary, Boundary } from '../../../mol-math/geometry/boundary';
  31. import { ElementSymbol } from '../model/types';
  32. import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
  33. import { Trajectory } from '../trajectory';
  34. import { RuntimeContext, Task } from '../../../mol-task';
  35. import { computeStructureBoundary } from './util/boundary';
  36. class Structure {
  37. /** Maps unit.id to unit */
  38. readonly unitMap: IntMap<Unit>;
  39. /** Maps unit.id to index of unit in units array */
  40. readonly unitIndexMap: IntMap<number>;
  41. /** Array of all units in the structure, sorted by unit.id */
  42. readonly units: ReadonlyArray<Unit>;
  43. private _props: {
  44. parent?: Structure,
  45. boundary?: Boundary,
  46. lookup3d?: StructureLookup3D,
  47. interUnitBonds?: InterUnitBonds,
  48. unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
  49. unitSymmetryGroupsIndexMap?: IntMap<number>,
  50. carbohydrates?: Carbohydrates,
  51. models?: ReadonlyArray<Model>,
  52. model?: Model,
  53. masterModel?: Model,
  54. representativeModel?: Model,
  55. uniqueResidueNames?: Set<string>,
  56. uniqueElementSymbols?: Set<ElementSymbol>,
  57. entityIndices?: ReadonlyArray<EntityIndex>,
  58. uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
  59. serialMapping?: SerialMapping,
  60. hashCode: number,
  61. /** Hash based on all unit.id values in the structure, reflecting the units transformation */
  62. transformHash: number,
  63. elementCount: number,
  64. bondCount: number,
  65. uniqueElementCount: number,
  66. atomicResidueCount: number,
  67. polymerResidueCount: number,
  68. polymerUnitCount: number,
  69. coordinateSystem: SymmetryOperator,
  70. label: string,
  71. propertyData?: any,
  72. customProps?: CustomProperties
  73. } = {
  74. hashCode: -1,
  75. transformHash: -1,
  76. elementCount: -1,
  77. bondCount: -1,
  78. uniqueElementCount: -1,
  79. atomicResidueCount: -1,
  80. polymerResidueCount: -1,
  81. polymerUnitCount: -1,
  82. coordinateSystem: SymmetryOperator.Default,
  83. label: ''
  84. };
  85. subsetBuilder(isSorted: boolean) {
  86. return new StructureSubsetBuilder(this, isSorted);
  87. }
  88. /** Count of all elements in the structure, i.e. the sum of the elements in the units */
  89. get elementCount() {
  90. return this._props.elementCount;
  91. }
  92. /** Count of all bonds (intra- and inter-unit) in the structure */
  93. get bondCount() {
  94. if (!this._props.bondCount) {
  95. this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
  96. }
  97. return this._props.bondCount;
  98. }
  99. get hasCustomProperties() {
  100. return !!this._props.customProps && this._props.customProps.all.length > 0;
  101. }
  102. get customPropertyDescriptors() {
  103. if (!this._props.customProps) this._props.customProps = new CustomProperties();
  104. return this._props.customProps;
  105. }
  106. /**
  107. * Property data unique to this instance of the structure.
  108. */
  109. get currentPropertyData() {
  110. if (!this._props.propertyData) this._props.propertyData = Object.create(null);
  111. return this._props.propertyData;
  112. }
  113. /**
  114. * Property data of the parent structure if it exists, currentPropertyData otherwise.
  115. */
  116. get inheritedPropertyData() {
  117. return this.parent ? this.parent.currentPropertyData : this.currentPropertyData;
  118. }
  119. /** Count of all polymer residues in the structure */
  120. get polymerResidueCount() {
  121. if (this._props.polymerResidueCount === -1) {
  122. this._props.polymerResidueCount = getPolymerResidueCount(this);
  123. }
  124. return this._props.polymerResidueCount;
  125. }
  126. get polymerUnitCount() {
  127. if (this._props.polymerUnitCount === -1) {
  128. this._props.polymerUnitCount = getPolymerUnitCount(this);
  129. }
  130. return this._props.polymerUnitCount;
  131. }
  132. get uniqueElementCount() {
  133. if (this._props.uniqueElementCount === -1) {
  134. this._props.uniqueElementCount = getUniqueElementCount(this);
  135. }
  136. return this._props.uniqueElementCount;
  137. }
  138. get atomicResidueCount() {
  139. if (this._props.atomicResidueCount === -1) {
  140. this._props.atomicResidueCount = getAtomicResidueCount(this);
  141. }
  142. return this._props.atomicResidueCount;
  143. }
  144. /**
  145. * Coarse-grained structure, defined as containing less than
  146. * twice as many elements as polymer residues
  147. */
  148. get isCoarseGrained() {
  149. const ec = this.elementCount;
  150. const prc = this.polymerResidueCount;
  151. return prc && ec ? ec / prc < 2 : false;
  152. }
  153. get isEmpty() {
  154. return this.units.length === 0;
  155. }
  156. get hashCode() {
  157. if (this._props.hashCode !== -1) return this._props.hashCode;
  158. return this.computeHash();
  159. }
  160. /** Hash based on all unit.id values in the structure, reflecting the units transformation */
  161. get transformHash() {
  162. if (this._props.transformHash !== -1) return this._props.transformHash;
  163. this._props.transformHash = hashFnv32a(this.units.map(u => u.id));
  164. return this._props.transformHash;
  165. }
  166. private computeHash() {
  167. let hash = 23;
  168. for (let i = 0, _i = this.units.length; i < _i; i++) {
  169. const u = this.units[i];
  170. hash = (31 * hash + u.id) | 0;
  171. hash = (31 * hash + SortedArray.hashCode(u.elements)) | 0;
  172. }
  173. hash = (31 * hash + this.elementCount) | 0;
  174. hash = hash1(hash);
  175. if (hash === -1) hash = 0;
  176. this._props.hashCode = hash;
  177. return hash;
  178. }
  179. /** Returns a new element location iterator */
  180. elementLocations(): Iterator<StructureElement.Location> {
  181. return new Structure.ElementLocationIterator(this);
  182. }
  183. /** The parent or itself in case this is the root */
  184. get root() {
  185. return this._props.parent || this;
  186. }
  187. /** The root/top-most parent or `undefined` in case this is the root */
  188. get parent() {
  189. return this._props.parent;
  190. }
  191. /**
  192. * Conformation transformation that was applied to every unit of this structure.
  193. *
  194. * Coordinate system applies to the *current* structure only.
  195. * A parent structure can have a different coordinate system and thefore it has to be composed "manualy"
  196. * by the consumer.
  197. */
  198. get coordinateSystem(): SymmetryOperator {
  199. return this._props.coordinateSystem;
  200. }
  201. get label() {
  202. return this._props.label;
  203. }
  204. get boundary() {
  205. if (this._props.boundary) return this._props.boundary;
  206. this._props.boundary = computeStructureBoundary(this);
  207. return this._props.boundary;
  208. }
  209. get lookup3d() {
  210. if (this._props.lookup3d) return this._props.lookup3d;
  211. this._props.lookup3d = new StructureLookup3D(this);
  212. return this._props.lookup3d;
  213. }
  214. get interUnitBonds() {
  215. if (this._props.interUnitBonds) return this._props.interUnitBonds;
  216. this._props.interUnitBonds = computeInterUnitBonds(this);
  217. return this._props.interUnitBonds;
  218. }
  219. get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
  220. if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
  221. this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
  222. return this._props.unitSymmetryGroups;
  223. }
  224. /** Maps unit.id to index of SymmetryGroup in unitSymmetryGroups array */
  225. get unitSymmetryGroupsIndexMap(): IntMap<number> {
  226. if (this._props.unitSymmetryGroupsIndexMap) return this._props.unitSymmetryGroupsIndexMap;
  227. this._props.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
  228. return this._props.unitSymmetryGroupsIndexMap;
  229. }
  230. get carbohydrates(): Carbohydrates {
  231. if (this._props.carbohydrates) return this._props.carbohydrates;
  232. this._props.carbohydrates = computeCarbohydrates(this);
  233. return this._props.carbohydrates;
  234. }
  235. get models(): ReadonlyArray<Model> {
  236. if (this._props.models) return this._props.models;
  237. this._props.models = getModels(this);
  238. return this._props.models;
  239. }
  240. get uniqueResidueNames() {
  241. return this._props.uniqueResidueNames
  242. || (this._props.uniqueResidueNames = getUniqueResidueNames(this));
  243. }
  244. get uniqueElementSymbols() {
  245. return this._props.uniqueElementSymbols
  246. || (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
  247. }
  248. get entityIndices() {
  249. return this._props.entityIndices
  250. || (this._props.entityIndices = getEntityIndices(this));
  251. }
  252. get uniqueAtomicResidueIndices() {
  253. return this._props.uniqueAtomicResidueIndices
  254. || (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
  255. }
  256. /** Contains only atomic units */
  257. get isAtomic() {
  258. for (const u of this.units) if (Unit.isAtomic(u)) return false;
  259. return true;
  260. }
  261. /** Contains some atomic units */
  262. get hasAtomic() {
  263. for (const u of this.units) if (Unit.isAtomic(u)) return true;
  264. return false;
  265. }
  266. /** Contains only coarse units */
  267. get isCoarse() {
  268. for (const u of this.units) if (Unit.isCoarse(u)) return false;
  269. return true;
  270. }
  271. /** Contains some coarse units */
  272. get hasCoarse() {
  273. for (const u of this.units) if (Unit.isCoarse(u)) return true;
  274. return false;
  275. }
  276. /**
  277. * Provides mapping for serial element indices accross all units.
  278. *
  279. * Note that this is especially costly for structures with many units that are grouped
  280. * into few symmetry groups. Use only when needed and prefer `StructureElement`
  281. * to address elements in a structure.
  282. */
  283. get serialMapping() {
  284. return this._props.serialMapping || (this._props.serialMapping = getSerialMapping(this));
  285. }
  286. /**
  287. * If the structure is based on a single model or has a master-/representative-model, return it.
  288. * Otherwise throw an exception.
  289. */
  290. get model(): Model {
  291. if (this._props.model) return this._props.model;
  292. if (this._props.representativeModel) return this._props.representativeModel;
  293. if (this._props.masterModel) return this._props.masterModel;
  294. const models = this.models;
  295. if (models.length > 1) {
  296. throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
  297. }
  298. this._props.model = models[0];
  299. return this._props.model;
  300. }
  301. /** The master-model, other models can have bonds to it */
  302. get masterModel(): Model | undefined {
  303. return this._props.masterModel;
  304. }
  305. /** A representative model, e.g. the first model of a trajectory */
  306. get representativeModel(): Model | undefined {
  307. return this._props.representativeModel;
  308. }
  309. hasElement(e: StructureElement.Location) {
  310. if (!this.unitMap.has(e.unit.id)) return false;
  311. return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element);
  312. }
  313. getModelIndex(m: Model) {
  314. return this.models.indexOf(m);
  315. }
  316. remapModel(m: Model) {
  317. const units: Unit[] = [];
  318. for (const ug of this.unitSymmetryGroups) {
  319. const unit = ug.units[0].remapModel(m);
  320. units.push(unit);
  321. for (let i = 1, il = ug.units.length; i < il; ++i) {
  322. const u = ug.units[i];
  323. units.push(u.remapModel(m, unit.props));
  324. }
  325. }
  326. return Structure.create(units, {
  327. label: this.label,
  328. interUnitBonds: this._props.interUnitBonds,
  329. });
  330. }
  331. private initUnits(units: ArrayLike<Unit>) {
  332. const unitMap = IntMap.Mutable<Unit>();
  333. const unitIndexMap = IntMap.Mutable<number>();
  334. let elementCount = 0;
  335. let isSorted = true;
  336. let lastId = units.length > 0 ? units[0].id : 0;
  337. for (let i = 0, _i = units.length; i < _i; i++) {
  338. const u = units[i];
  339. unitMap.set(u.id, u);
  340. elementCount += u.elements.length;
  341. if (u.id < lastId) isSorted = false;
  342. lastId = u.id;
  343. }
  344. if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
  345. for (let i = 0, _i = units.length; i < _i; i++) {
  346. unitIndexMap.set(units[i].id, i);
  347. }
  348. this._props.elementCount = elementCount;
  349. return { unitMap, unitIndexMap };
  350. }
  351. constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
  352. const { unitMap, unitIndexMap } = this.initUnits(units);
  353. this.unitMap = unitMap;
  354. this.unitIndexMap = unitIndexMap;
  355. this.units = units as ReadonlyArray<Unit>;
  356. if (props.parent) this._props.parent = props.parent.parent || props.parent;
  357. if (props.interUnitBonds) this._props.interUnitBonds = props.interUnitBonds;
  358. if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
  359. else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
  360. if (props.label) this._props.label = props.label;
  361. else if (props.parent) this._props.label = props.parent.label;
  362. if (props.masterModel) this._props.masterModel = props.masterModel;
  363. else if (props.parent) this._props.masterModel = props.parent.masterModel;
  364. if (props.representativeModel) this._props.representativeModel = props.representativeModel;
  365. else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
  366. }
  367. }
  368. function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
  369. function getModels(s: Structure) {
  370. const { units } = s;
  371. const arr = UniqueArray.create<Model['id'], Model>();
  372. for (const u of units) {
  373. UniqueArray.add(arr, u.model.id, u.model);
  374. }
  375. return arr.array;
  376. }
  377. function getUniqueResidueNames(s: Structure) {
  378. const { microheterogeneityCompIds } = StructureProperties.residue;
  379. const names = new Set<string>();
  380. const loc = StructureElement.Location.create(s);
  381. for (const unitGroup of s.unitSymmetryGroups) {
  382. const unit = unitGroup.units[0];
  383. // TODO: support coarse unit?
  384. if (!Unit.isAtomic(unit)) continue;
  385. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  386. loc.unit = unit;
  387. while (residues.hasNext) {
  388. const seg = residues.move();
  389. loc.element = unit.elements[seg.start];
  390. const compIds = microheterogeneityCompIds(loc);
  391. for (const compId of compIds) names.add(compId);
  392. }
  393. }
  394. return names;
  395. }
  396. function getUniqueElementSymbols(s: Structure) {
  397. const prop = StructureProperties.atom.type_symbol;
  398. const symbols = new Set<ElementSymbol>();
  399. const loc = StructureElement.Location.create(s);
  400. for (const unitGroup of s.unitSymmetryGroups) {
  401. const unit = unitGroup.units[0];
  402. if (!Unit.isAtomic(unit)) continue;
  403. loc.unit = unit;
  404. for (let i = 0, il = unit.elements.length; i < il; ++i) {
  405. loc.element = unit.elements[i];
  406. symbols.add(prop(loc));
  407. }
  408. }
  409. return symbols;
  410. }
  411. function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
  412. const { units } = structure;
  413. const l = StructureElement.Location.create(structure);
  414. const keys = UniqueArray.create<number, EntityIndex>();
  415. for (const unit of units) {
  416. const prop = unit.kind === Unit.Kind.Atomic ? StructureProperties.entity.key : StructureProperties.coarse.entityKey;
  417. l.unit = unit;
  418. const elements = unit.elements;
  419. const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
  420. while (chainsIt.hasNext) {
  421. const chainSegment = chainsIt.move();
  422. l.element = elements[chainSegment.start];
  423. const key = prop(l);
  424. UniqueArray.add(keys, key, key);
  425. }
  426. }
  427. sortArray(keys.array);
  428. return keys.array;
  429. }
  430. function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>> {
  431. const map = new Map<UUID, UniqueArray<ResidueIndex, ResidueIndex>>();
  432. const modelIds: UUID[] = [];
  433. const unitGroups = structure.unitSymmetryGroups;
  434. for (const unitGroup of unitGroups) {
  435. const unit = unitGroup.units[0];
  436. if (!Unit.isAtomic(unit)) continue;
  437. let uniqueResidues: UniqueArray<ResidueIndex, ResidueIndex>;
  438. if (map.has(unit.model.id)) uniqueResidues = map.get(unit.model.id)!;
  439. else {
  440. uniqueResidues = UniqueArray.create<ResidueIndex, ResidueIndex>();
  441. modelIds.push(unit.model.id);
  442. map.set(unit.model.id, uniqueResidues);
  443. }
  444. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  445. while (residues.hasNext) {
  446. const seg = residues.move();
  447. UniqueArray.add(uniqueResidues, seg.index, seg.index);
  448. }
  449. }
  450. const ret = new Map<UUID, ReadonlyArray<ResidueIndex>>();
  451. for (const id of modelIds) {
  452. const array = map.get(id)!.array;
  453. sortArray(array);
  454. ret.set(id, array);
  455. }
  456. return ret;
  457. }
  458. function getUniqueElementCount(structure: Structure): number {
  459. const { unitSymmetryGroups } = structure;
  460. let uniqueElementCount = 0;
  461. for (let i = 0, _i = unitSymmetryGroups.length; i < _i; i++) {
  462. uniqueElementCount += unitSymmetryGroups[i].elements.length;
  463. }
  464. return uniqueElementCount;
  465. }
  466. function getPolymerResidueCount(structure: Structure): number {
  467. const { units } = structure;
  468. let polymerResidueCount = 0;
  469. for (let i = 0, _i = units.length; i < _i; i++) {
  470. polymerResidueCount += units[i].polymerElements.length;
  471. }
  472. return polymerResidueCount;
  473. }
  474. function getPolymerUnitCount(structure: Structure): number {
  475. const { units } = structure;
  476. let polymerUnitCount = 0;
  477. for (let i = 0, _i = units.length; i < _i; i++) {
  478. if (units[i].polymerElements.length > 0) polymerUnitCount += 1;
  479. }
  480. return polymerUnitCount;
  481. }
  482. function getAtomicResidueCount(structure: Structure): number {
  483. const { units } = structure;
  484. let atomicResidueCount = 0;
  485. for (let i = 0, _i = units.length; i < _i; i++) {
  486. const unit = units[i];
  487. if (!Unit.isAtomic(unit)) continue;
  488. const { elements, residueIndex } = unit;
  489. let idx = -1;
  490. let prevIdx = -1;
  491. for (let j = 0, jl = elements.length; j < jl; ++j) {
  492. idx = residueIndex[elements[j]];
  493. if (idx !== prevIdx) {
  494. atomicResidueCount += 1;
  495. prevIdx = idx;
  496. }
  497. }
  498. }
  499. return atomicResidueCount;
  500. }
  501. interface SerialMapping {
  502. /** Cumulative count of preceding elements for each unit */
  503. cumulativeUnitElementCount: ArrayLike<number>
  504. /** Unit index for each serial element in the structure */
  505. unitIndices: ArrayLike<number>
  506. /** Element index for each serial element in the structure */
  507. elementIndices: ArrayLike<ElementIndex>
  508. /** Get serial index of element in the structure */
  509. getSerialIndex: (unit: Unit, element: ElementIndex) => Structure.SerialIndex
  510. }
  511. function getSerialMapping(structure: Structure): SerialMapping {
  512. const { units, elementCount, unitIndexMap } = structure;
  513. const cumulativeUnitElementCount = new Uint32Array(units.length);
  514. const unitIndices = new Uint32Array(elementCount);
  515. const elementIndices = new Uint32Array(elementCount) as unknown as ElementIndex[];
  516. for (let i = 0, m = 0, il = units.length; i < il; ++i) {
  517. cumulativeUnitElementCount[i] = m;
  518. const { elements } = units[i];
  519. for (let j = 0, jl = elements.length; j < jl; ++j) {
  520. const mj = m + j;
  521. unitIndices[mj] = i;
  522. elementIndices[mj] = elements[j];
  523. }
  524. m += elements.length;
  525. }
  526. return {
  527. cumulativeUnitElementCount,
  528. unitIndices,
  529. elementIndices,
  530. getSerialIndex: (unit, element) => cumulativeUnitElementCount[unitIndexMap.get(unit.id)] + OrderedSet.indexOf(unit.elements, element) as Structure.SerialIndex
  531. };
  532. }
  533. namespace Structure {
  534. export const Empty = new Structure([]);
  535. export interface Props {
  536. parent?: Structure
  537. interUnitBonds?: InterUnitBonds
  538. coordinateSystem?: SymmetryOperator
  539. label?: string
  540. /** Master model for structures of a protein model and multiple ligand models */
  541. masterModel?: Model
  542. /** Representative model for structures of a model trajectory */
  543. representativeModel?: Model
  544. }
  545. /** Serial index of an element in the structure accross all units */
  546. export type SerialIndex = { readonly '@type': 'serial-index' } & number
  547. /** Represents a single structure */
  548. export interface Loci {
  549. readonly kind: 'structure-loci',
  550. readonly structure: Structure,
  551. }
  552. export function Loci(structure: Structure): Loci {
  553. return { kind: 'structure-loci', structure };
  554. }
  555. export function toStructureElementLoci(structure: Structure): StructureElement.Loci {
  556. const elements: StructureElement.Loci['elements'][0][] = [];
  557. for (const unit of structure.units) {
  558. elements.push({ unit, indices: Interval.ofBounds(0, unit.elements.length) });
  559. }
  560. return StructureElement.Loci(structure, elements);
  561. }
  562. export function toSubStructureElementLoci(parent: Structure, structure: Structure): StructureElement.Loci {
  563. return StructureSelection.toLociWithSourceUnits(StructureSelection.Singletons(parent, structure));
  564. }
  565. export function isLoci(x: any): x is Loci {
  566. return !!x && x.kind === 'structure-loci';
  567. }
  568. export function areLociEqual(a: Loci, b: Loci) {
  569. return a.structure === b.structure;
  570. }
  571. export function isLociEmpty(loci: Loci) {
  572. return loci.structure.isEmpty;
  573. }
  574. export function remapLoci(loci: Loci, structure: Structure) {
  575. if (structure === loci.structure) return loci;
  576. return Loci(structure);
  577. }
  578. export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
  579. return new Structure(units, props);
  580. }
  581. export async function ofTrajectory(trajectory: Trajectory, ctx: RuntimeContext): Promise<Structure> {
  582. if (trajectory.frameCount === 0) return Empty;
  583. const units: Unit[] = [];
  584. let first: Model | undefined = void 0;
  585. let count = 0;
  586. for (let i = 0, il = trajectory.frameCount; i < il; ++i) {
  587. const frame = await Task.resolveInContext(trajectory.getFrameAtIndex(i), ctx);
  588. if (!first) first = frame;
  589. const structure = ofModel(frame);
  590. for (let j = 0, jl = structure.units.length; j < jl; ++j) {
  591. const u = structure.units[j];
  592. const invariantId = u.invariantId + count;
  593. const chainGroupId = u.chainGroupId + count;
  594. const newUnit = Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements);
  595. units.push(newUnit);
  596. }
  597. count = units.length;
  598. }
  599. return create(units, { representativeModel: first!, label: first!.label });
  600. }
  601. const PARTITION = false;
  602. /**
  603. * Construct a Structure from a model.
  604. *
  605. * Generally, a single unit corresponds to a single chain, with the exception
  606. * of consecutive "single atom chains" with same entity_id and same auth_asym_id.
  607. */
  608. export function ofModel(model: Model): Structure {
  609. const chains = model.atomicHierarchy.chainAtomSegments;
  610. const { index } = model.atomicHierarchy;
  611. const { auth_asym_id } = model.atomicHierarchy.chains;
  612. const { atomicChainOperatorMappinng } = model;
  613. const builder = new StructureBuilder({ label: model.label });
  614. for (let c = 0 as ChainIndex; c < chains.count; c++) {
  615. const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;
  616. const start = chains.offsets[c];
  617. // set to true for chains that consist of "single atom residues",
  618. // note that it assumes there are no "zero atom residues"
  619. let singleAtomResidues = AtomicHierarchy.chainResidueCount(model.atomicHierarchy, c) === chains.offsets[c + 1] - chains.offsets[c];
  620. let multiChain = false;
  621. if (isWaterChain(model, c)) {
  622. // merge consecutive water chains
  623. while (c + 1 < chains.count && isWaterChain(model, c + 1 as ChainIndex)) {
  624. const op1 = atomicChainOperatorMappinng.get(c);
  625. const op2 = atomicChainOperatorMappinng.get(c + 1 as ChainIndex);
  626. if (op1 !== op2) break;
  627. multiChain = true;
  628. c++;
  629. }
  630. } else {
  631. // merge consecutive "single atom chains" with same entity_id and auth_asym_id
  632. while (c + 1 < chains.count
  633. && chains.offsets[c + 1] - chains.offsets[c] === 1
  634. && chains.offsets[c + 2] - chains.offsets[c + 1] === 1
  635. ) {
  636. singleAtomResidues = true;
  637. const e1 = index.getEntityFromChain(c);
  638. const e2 = index.getEntityFromChain(c + 1 as ChainIndex);
  639. if (e1 !== e2) break;
  640. const a1 = auth_asym_id.value(c);
  641. const a2 = auth_asym_id.value(c + 1);
  642. if (a1 !== a2) break;
  643. const op1 = atomicChainOperatorMappinng.get(c);
  644. const op2 = atomicChainOperatorMappinng.get(c + 1 as ChainIndex);
  645. if (op1 !== op2) break;
  646. multiChain = true;
  647. c++;
  648. }
  649. }
  650. const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
  651. if (PARTITION) {
  652. // check for polymer to exclude CA/P-only models
  653. if (singleAtomResidues && !isPolymerChain(model, c)) {
  654. partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
  655. } else if (elements.length > 200000 || isWaterChain(model, c)) {
  656. // split up very large chains e.g. lipid bilayers, micelles or water with explicit H
  657. partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
  658. } else {
  659. builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
  660. }
  661. } else {
  662. builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
  663. }
  664. }
  665. const cs = model.coarseHierarchy;
  666. if (cs.isDefined) {
  667. if (cs.spheres.count > 0) {
  668. addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
  669. }
  670. if (cs.gaussians.count > 0) {
  671. addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
  672. }
  673. }
  674. return builder.getStructure();
  675. }
  676. function isWaterChain(model: Model, chainIndex: ChainIndex) {
  677. const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
  678. return model.entities.data.type.value(e) === 'water';
  679. }
  680. function isPolymerChain(model: Model, chainIndex: ChainIndex) {
  681. const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
  682. return model.entities.data.type.value(e) === 'polymer';
  683. }
  684. function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
  685. const { x, y, z } = model.atomicConformation;
  686. const position = { x, y, z, indices };
  687. const lookup = GridLookup3D(position, getBoundary(position), 8192);
  688. const { offset, count, array } = lookup.buckets;
  689. const traits = (multiChain ? Unit.Trait.MultiChain : Unit.Trait.None) | (offset.length > 1 ? Unit.Trait.Partitioned : Unit.Trait.None);
  690. builder.beginChainGroup();
  691. for (let i = 0, _i = offset.length; i < _i; i++) {
  692. const start = offset[i];
  693. const set = new Int32Array(count[i]);
  694. for (let j = 0, _j = count[i]; j < _j; j++) {
  695. set[j] = indices[array[start + j]];
  696. }
  697. builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(set), traits);
  698. }
  699. builder.endChainGroup();
  700. }
  701. // keeps atoms of residues together
  702. function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
  703. const { residueAtomSegments } = model.atomicHierarchy;
  704. const startIndices: number[] = [];
  705. const endIndices: number[] = [];
  706. const residueIt = Segmentation.transientSegments(residueAtomSegments, indices);
  707. while (residueIt.hasNext) {
  708. const residueSegment = residueIt.move();
  709. startIndices[startIndices.length] = indices[residueSegment.start];
  710. endIndices[endIndices.length] = indices[residueSegment.end];
  711. }
  712. const firstResidueAtomCount = endIndices[0] - startIndices[0];
  713. const gridCellCount = 512 * firstResidueAtomCount;
  714. const { x, y, z } = model.atomicConformation;
  715. const position = { x, y, z, indices: SortedArray.ofSortedArray(startIndices) };
  716. const lookup = GridLookup3D(position, getBoundary(position), gridCellCount);
  717. const { offset, count, array } = lookup.buckets;
  718. const traits = (multiChain ? Unit.Trait.MultiChain : Unit.Trait.None) | (offset.length > 1 ? Unit.Trait.Partitioned : Unit.Trait.None);
  719. builder.beginChainGroup();
  720. for (let i = 0, _i = offset.length; i < _i; i++) {
  721. const start = offset[i];
  722. const set: number[] = [];
  723. for (let j = 0, _j = count[i]; j < _j; j++) {
  724. const k = array[start + j];
  725. for (let l = startIndices[k], _l = endIndices[k]; l < _l; l++) {
  726. set[set.length] = l;
  727. }
  728. }
  729. builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(new Int32Array(set)), traits);
  730. }
  731. builder.endChainGroup();
  732. }
  733. function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
  734. const { chainElementSegments } = elements;
  735. for (let cI = 0; cI < chainElementSegments.count; cI++) {
  736. const elements = SortedArray.ofBounds<ElementIndex>(chainElementSegments.offsets[cI], chainElementSegments.offsets[cI + 1]);
  737. builder.addUnit(kind, model, SymmetryOperator.Default, elements, Unit.Trait.None);
  738. }
  739. }
  740. export function transform(s: Structure, transform: Mat4) {
  741. if (Mat4.isIdentity(transform)) return s;
  742. if (!Mat4.isRotationAndTranslation(transform, SymmetryOperator.RotationTranslationEpsilon)) throw new Error('Only rotation/translation combination can be applied.');
  743. const units: Unit[] = [];
  744. for (const u of s.units) {
  745. const old = u.conformation.operator;
  746. const op = SymmetryOperator.create(old.name, transform, old);
  747. units.push(u.applyOperator(u.id, op));
  748. }
  749. const cs = s.coordinateSystem;
  750. const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
  751. return new Structure(units, { parent: s, coordinateSystem: newCS });
  752. }
  753. export class StructureBuilder {
  754. private units: Unit[] = [];
  755. private invariantId = idFactory()
  756. private chainGroupId = -1;
  757. private inChainGroup = false;
  758. private p = Vec3();
  759. private singleElementUnits = new Map<string, Unit>();
  760. beginChainGroup() {
  761. this.chainGroupId++;
  762. this.inChainGroup = true;
  763. }
  764. endChainGroup() {
  765. this.inChainGroup = false;
  766. }
  767. addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, traits: Unit.Traits, invariantId?: number): Unit {
  768. if (invariantId === undefined) invariantId = this.invariantId();
  769. const chainGroupId = this.inChainGroup ? this.chainGroupId : ++this.chainGroupId;
  770. const unit = Unit.create(this.units.length, invariantId, chainGroupId, traits, kind, model, operator, elements);
  771. return this.add(unit);
  772. }
  773. private add(unit: Unit) {
  774. // this is to avoid adding many identical single atom units,
  775. // for example, from 'degenerate' spacegroups
  776. // - Diamond (COD 9008564)
  777. if (unit.elements.length === 1) {
  778. unit.conformation.position(unit.elements[0], this.p);
  779. const hash = [unit.invariantId, this.p[0], this.p[1], this.p[2]].join('|');
  780. if (this.singleElementUnits.has(hash)) {
  781. return this.singleElementUnits.get(hash)!;
  782. } else {
  783. this.singleElementUnits.set(hash, unit);
  784. }
  785. }
  786. this.units.push(unit);
  787. return unit;
  788. }
  789. addWithOperator(unit: Unit, operator: SymmetryOperator, dontCompose = false): Unit {
  790. return this.add(unit.applyOperator(this.units.length, operator, dontCompose));
  791. }
  792. getStructure(): Structure {
  793. return create(this.units, this.props);
  794. }
  795. get isEmpty() {
  796. return this.units.length === 0;
  797. }
  798. constructor(private props: Props = {}) {
  799. }
  800. }
  801. export function Builder(props: Props = {}) {
  802. return new StructureBuilder(props);
  803. }
  804. export function hashCode(s: Structure) {
  805. return s.hashCode;
  806. }
  807. /** Hash based on all unit.model conformation values in the structure */
  808. export function conformationHash(s: Structure) {
  809. return hashString(s.units.map(u => Unit.conformationId(u)).join('|'));
  810. }
  811. // TODO: there should be a version that properly supports partitioned units
  812. export function areUnitIdsEqual(a: Structure, b: Structure) {
  813. if (a === b) return true;
  814. if (a.elementCount !== b.elementCount) return false;
  815. const len = a.units.length;
  816. if (len !== b.units.length) return false;
  817. for (let i = 0; i < len; i++) {
  818. if (a.units[i].id !== b.units[i].id) return false;
  819. }
  820. return true;
  821. }
  822. export function areUnitIdsAndIndicesEqual(a: Structure, b: Structure) {
  823. if (!areUnitIdsEqual(a, b)) return false;
  824. for (let i = 0, il = a.units.length; i < il; i++) {
  825. if (!SortedArray.areEqual(a.units[i].elements, b.units[i].elements)) return false;
  826. }
  827. return true;
  828. }
  829. export function areHierarchiesEqual(a: Structure, b: Structure) {
  830. if (a.hashCode !== b.hashCode) return false;
  831. const len = a.models.length;
  832. if (len !== b.models.length) return false;
  833. for (let i = 0; i < len; i++) {
  834. if (!Model.areHierarchiesEqual(a.models[i], b.models[i])) return false;
  835. }
  836. return true;
  837. }
  838. export function areEquivalent(a: Structure, b: Structure) {
  839. return a === b || (
  840. a.hashCode === b.hashCode &&
  841. StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups)
  842. );
  843. }
  844. /** Check if the structures or their parents are equivalent */
  845. export function areRootsEquivalent(a: Structure, b: Structure) {
  846. return areEquivalent(a.root, b.root);
  847. }
  848. /** Check if the structures or their parents are equal */
  849. export function areRootsEqual(a: Structure, b: Structure) {
  850. return a.root === b.root;
  851. }
  852. export class ElementLocationIterator implements Iterator<StructureElement.Location> {
  853. private current: StructureElement.Location;
  854. private unitIndex = 0;
  855. private elements: StructureElement.Set;
  856. private maxIdx = 0;
  857. private idx = -1;
  858. hasNext: boolean;
  859. move(): StructureElement.Location {
  860. this.advance();
  861. this.current.element = this.elements[this.idx];
  862. return this.current;
  863. }
  864. private advance() {
  865. if (this.idx < this.maxIdx) {
  866. this.idx++;
  867. if (this.idx === this.maxIdx) this.hasNext = this.unitIndex + 1 < this.structure.units.length;
  868. return;
  869. }
  870. this.idx = 0;
  871. this.unitIndex++;
  872. if (this.unitIndex >= this.structure.units.length) {
  873. this.hasNext = false;
  874. return;
  875. }
  876. this.current.unit = this.structure.units[this.unitIndex];
  877. this.elements = this.current.unit.elements;
  878. this.maxIdx = this.elements.length - 1;
  879. if (this.maxIdx === 0) {
  880. this.hasNext = this.unitIndex + 1 < this.structure.units.length;
  881. }
  882. }
  883. constructor(private structure: Structure) {
  884. this.current = StructureElement.Location.create(structure);
  885. this.hasNext = structure.elementCount > 0;
  886. if (this.hasNext) {
  887. this.elements = structure.units[0].elements;
  888. this.maxIdx = this.elements.length - 1;
  889. this.current.unit = structure.units[0];
  890. }
  891. }
  892. }
  893. const distVec = Vec3();
  894. function unitElementMinDistance(unit: Unit, p: Vec3, eRadius: number) {
  895. const { elements, conformation: { position, r } } = unit, dV = distVec;
  896. let minD = Number.MAX_VALUE;
  897. for (let i = 0, _i = elements.length; i < _i; i++) {
  898. const e = elements[i];
  899. const d = Vec3.distance(p, position(e, dV)) - eRadius - r(e);
  900. if (d < minD) minD = d;
  901. }
  902. return minD;
  903. }
  904. export function minDistanceToPoint(s: Structure, point: Vec3, radius: number) {
  905. const { units } = s;
  906. let minD = Number.MAX_VALUE;
  907. for (let i = 0, _i = units.length; i < _i; i++) {
  908. const unit = units[i];
  909. const d = unitElementMinDistance(unit, point, radius);
  910. if (d < minD) minD = d;
  911. }
  912. return minD;
  913. }
  914. const distPivot = Vec3();
  915. export function distance(a: Structure, b: Structure) {
  916. if (a.elementCount === 0 || b.elementCount === 0) return 0;
  917. const { units } = a;
  918. let minD = Number.MAX_VALUE;
  919. for (let i = 0, _i = units.length; i < _i; i++) {
  920. const unit = units[i];
  921. const { elements, conformation: { position, r } } = unit;
  922. for (let i = 0, _i = elements.length; i < _i; i++) {
  923. const e = elements[i];
  924. const d = minDistanceToPoint(b, position(e, distPivot), r(e));
  925. if (d < minD) minD = d;
  926. }
  927. }
  928. return minD;
  929. }
  930. export function elementDescription(s: Structure) {
  931. return s.elementCount === 1 ? '1 element' : `${s.elementCount} elements`;
  932. }
  933. export function validUnitPair(s: Structure, a: Unit, b: Unit) {
  934. return s.masterModel
  935. ? a.model === b.model || a.model === s.masterModel || b.model === s.masterModel
  936. : a.model === b.model;
  937. }
  938. export interface EachUnitPairProps {
  939. maxRadius: number
  940. validUnit: (unit: Unit) => boolean
  941. validUnitPair: (unitA: Unit, unitB: Unit) => boolean
  942. }
  943. /**
  944. * Iterate over all unit pairs of a structure and invokes callback for valid units
  945. * and unit pairs if within a max distance.
  946. */
  947. export function eachUnitPair(structure: Structure, callback: (unitA: Unit, unitB: Unit) => void, props: EachUnitPairProps) {
  948. const { maxRadius, validUnit, validUnitPair } = props;
  949. if (!structure.units.some(u => validUnit(u))) return;
  950. const lookup = structure.lookup3d;
  951. const imageCenter = Vec3();
  952. for (const unit of structure.units) {
  953. if (!validUnit(unit)) continue;
  954. const bs = unit.boundary.sphere;
  955. Vec3.transformMat4(imageCenter, bs.center, unit.conformation.operator.matrix);
  956. const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + maxRadius);
  957. for (let i = 0; i < closeUnits.count; i++) {
  958. const other = structure.units[closeUnits.indices[i]];
  959. if (!validUnit(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue;
  960. if (other.elements.length >= unit.elements.length) callback(unit, other);
  961. else callback(other, unit);
  962. }
  963. }
  964. }
  965. //
  966. export const DefaultSizeThresholds = {
  967. /** Must be lower to be small */
  968. smallResidueCount: 10,
  969. /** Must be lower to be medium */
  970. mediumResidueCount: 5000,
  971. /** Must be lower to be large (big ribosomes like 4UG0 should still be `large`) */
  972. largeResidueCount: 20000,
  973. /**
  974. * Structures above `largeResidueCount` are consider huge when they have
  975. * a `highSymmetryUnitCount` or gigantic when not
  976. */
  977. highSymmetryUnitCount: 10,
  978. /** Fiber-like structure are consider small when below this */
  979. fiberResidueCount: 15,
  980. };
  981. export type SizeThresholds = typeof DefaultSizeThresholds
  982. function getPolymerSymmetryGroups(structure: Structure) {
  983. return structure.unitSymmetryGroups.filter(ug => ug.units[0].polymerElements.length > 0);
  984. }
  985. /**
  986. * Try to match fiber-like structures like 6nk4
  987. */
  988. function isFiberLike(structure: Structure, thresholds: SizeThresholds) {
  989. const polymerSymmetryGroups = getPolymerSymmetryGroups(structure);
  990. return (
  991. polymerSymmetryGroups.length === 1 &&
  992. polymerSymmetryGroups[0].units.length > 2 &&
  993. polymerSymmetryGroups[0].units[0].polymerElements.length < thresholds.fiberResidueCount
  994. );
  995. }
  996. function hasHighSymmetry(structure: Structure, thresholds: SizeThresholds) {
  997. const polymerSymmetryGroups = getPolymerSymmetryGroups(structure);
  998. return (
  999. polymerSymmetryGroups.length >= 1 &&
  1000. polymerSymmetryGroups[0].units.length > thresholds.highSymmetryUnitCount
  1001. );
  1002. }
  1003. export enum Size { Small, Medium, Large, Huge, Gigantic }
  1004. /**
  1005. * @param residueCountFactor - modifies the threshold counts, useful when estimating
  1006. * the size of a structure comprised of multiple models
  1007. */
  1008. export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}, residueCountFactor = 1): Size {
  1009. const t = { ...DefaultSizeThresholds, ...thresholds };
  1010. if (structure.polymerResidueCount >= t.largeResidueCount * residueCountFactor) {
  1011. if (hasHighSymmetry(structure, t)) {
  1012. return Size.Huge;
  1013. } else {
  1014. return Size.Gigantic;
  1015. }
  1016. } else if (isFiberLike(structure, t)) {
  1017. return Size.Small;
  1018. } else if (structure.polymerResidueCount < t.smallResidueCount * residueCountFactor) {
  1019. return Size.Small;
  1020. } else if (structure.polymerResidueCount < t.mediumResidueCount * residueCountFactor) {
  1021. return Size.Medium;
  1022. } else {
  1023. return Size.Large;
  1024. }
  1025. }
  1026. //
  1027. export type Index = number;
  1028. export const Index = CustomStructureProperty.createSimple<Index>('index', 'root');
  1029. }
  1030. export default Structure;