common.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /**
  2. * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Unit, Structure, ElementIndex, StructureElement, ResidueIndex } from '../../../../mol-model/structure';
  7. import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
  8. import { TransformData, createTransform } from '../../../../mol-geo/geometry/transform-data';
  9. import { OrderedSet, SortedArray } from '../../../../mol-data/int';
  10. import { EmptyLoci, Loci } from '../../../../mol-model/loci';
  11. import { PhysicalSizeTheme } from '../../../../mol-theme/size/physical';
  12. import { AtomicNumbers } from '../../../../mol-model/structure/model/properties/atomic';
  13. import { fillSerial } from '../../../../mol-util/array';
  14. import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
  15. import { AssignableArrayLike } from '../../../../mol-util/type-helpers';
  16. import { getBoundary } from '../../../../mol-math/geometry/boundary';
  17. import { Box3D } from '../../../../mol-math/geometry';
  18. /** Return a Loci for the elements of a whole residue the elementIndex belongs to. */
  19. export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci {
  20. const { elements, model } = unit;
  21. if (OrderedSet.indexOf(elements, elementIndex) !== -1) {
  22. const { index, offsets } = model.atomicHierarchy.residueAtomSegments;
  23. const rI = index[elementIndex];
  24. const _indices: number[] = [];
  25. for (let i = offsets[rI], il = offsets[rI + 1]; i < il; ++i) {
  26. const unitIndex = OrderedSet.indexOf(elements, i);
  27. if (unitIndex !== -1) _indices.push(unitIndex);
  28. }
  29. const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices));
  30. return StructureElement.Loci(structure, [{ unit, indices }]);
  31. }
  32. return EmptyLoci;
  33. }
  34. /**
  35. * Return a Loci for the elements of a whole residue the elementIndex belongs to but
  36. * restrict to elements that have the same label_alt_id or none
  37. */
  38. export function getAltResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex) {
  39. const { elements, model } = unit;
  40. const { label_alt_id } = model.atomicHierarchy.atoms;
  41. const elementAltId = label_alt_id.value(elementIndex);
  42. if (OrderedSet.indexOf(elements, elementIndex) !== -1) {
  43. const { index } = model.atomicHierarchy.residueAtomSegments;
  44. const rI = index[elementIndex];
  45. return getAltResidueLociFromId(structure, unit, rI, elementAltId);
  46. }
  47. return StructureElement.Loci(structure, []);
  48. }
  49. export function getAltResidueLociFromId(structure: Structure, unit: Unit.Atomic, residueIndex: ResidueIndex, elementAltId: string) {
  50. const { elements, model } = unit;
  51. const { label_alt_id } = model.atomicHierarchy.atoms;
  52. const { offsets } = model.atomicHierarchy.residueAtomSegments;
  53. const _indices: number[] = [];
  54. for (let i = offsets[residueIndex], il = offsets[residueIndex + 1]; i < il; ++i) {
  55. const unitIndex = OrderedSet.indexOf(elements, i);
  56. if (unitIndex !== -1) {
  57. const altId = label_alt_id.value(i);
  58. if (elementAltId === altId || altId === '') {
  59. _indices.push(unitIndex);
  60. }
  61. }
  62. }
  63. const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices));
  64. return StructureElement.Loci(structure, [{ unit, indices }]);
  65. }
  66. //
  67. export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
  68. const unitCount = units.length;
  69. const n = unitCount * 16;
  70. const array = transformData && transformData.aTransform.ref.value.length >= n ? transformData.aTransform.ref.value : new Float32Array(n);
  71. for (let i = 0; i < unitCount; i++) {
  72. Mat4.toArray(units[i].conformation.operator.matrix, array, i * 16);
  73. }
  74. return createTransform(array, unitCount, transformData);
  75. }
  76. export const UnitKindInfo = {
  77. 'atomic': {},
  78. 'spheres': {},
  79. 'gaussians': {},
  80. };
  81. export type UnitKind = keyof typeof UnitKindInfo
  82. export const UnitKindOptions = PD.objectToOptions(UnitKindInfo);
  83. export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
  84. for (let i = 0, il = unitKinds.length; i < il; ++i) {
  85. if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true;
  86. if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true;
  87. if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true;
  88. }
  89. return false;
  90. }
  91. //
  92. const DefaultMaxCells = 500_000_000;
  93. export function getVolumeSliceInfo(box: Box3D, resolution: number, maxCells = DefaultMaxCells) {
  94. const size = Box3D.size(Vec3(), box);
  95. Vec3.ceil(size, size);
  96. size.sort((a, b) => b - a); // descending
  97. const maxAreaCells = Math.floor(Math.cbrt(maxCells) * Math.cbrt(maxCells));
  98. const area = size[0] * size[1];
  99. const areaCells = Math.ceil(area / (resolution * resolution));
  100. return { area, areaCells, maxAreaCells };
  101. }
  102. /**
  103. * Guard against overly high resolution for the given box size.
  104. * Internally it uses the largest 2d slice of the box to determine the
  105. * maximum resolution to account for the 2d texture layout on the GPU.
  106. */
  107. export function ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T, maxCells = DefaultMaxCells) {
  108. const { area, areaCells, maxAreaCells } = getVolumeSliceInfo(box, props.resolution, maxCells);
  109. const resolution = areaCells > maxAreaCells ? Math.sqrt(area / maxAreaCells) : props.resolution;
  110. return { ...props, resolution };
  111. }
  112. export function getConformation(unit: Unit) {
  113. switch (unit.kind) {
  114. case Unit.Kind.Atomic: return unit.model.atomicConformation;
  115. case Unit.Kind.Spheres: return unit.model.coarseConformation.spheres;
  116. case Unit.Kind.Gaussians: return unit.model.coarseConformation.gaussians;
  117. }
  118. }
  119. export const CommonSurfaceParams = {
  120. ignoreHydrogens: PD.Boolean(false, { description: 'Whether or not to include hydrogen atoms in the surface calculation.' }),
  121. traceOnly: PD.Boolean(false, { description: 'Whether or not to only use trace atoms in the surface calculation.' }),
  122. includeParent: PD.Boolean(false, { description: 'Include elements of the parent structure in surface calculation to get a surface patch of the current structure.' }),
  123. };
  124. export const DefaultCommonSurfaceProps = PD.getDefaultValues(CommonSurfaceParams);
  125. export type CommonSurfaceProps = typeof DefaultCommonSurfaceProps
  126. const v = Vec3();
  127. function squaredDistance(x: number, y: number, z: number, center: Vec3) {
  128. return Vec3.squaredDistance(Vec3.set(v, x, y, z), center);
  129. }
  130. /** marks `indices` for filtering/ignoring in `id` when not in `elements` */
  131. function filterId(id: AssignableArrayLike<number>, elements: SortedArray, indices: SortedArray) {
  132. let start = 0;
  133. const end = elements.length;
  134. for (let i = 0, il = indices.length; i < il; ++i) {
  135. const idx = SortedArray.indexOfInRange(elements, indices[i], start, end);
  136. if (idx === -1) {
  137. id[i] = -2;
  138. } else {
  139. id[i] = idx;
  140. start = idx;
  141. }
  142. }
  143. }
  144. export function getUnitConformationAndRadius(structure: Structure, unit: Unit, props: CommonSurfaceProps) {
  145. const { ignoreHydrogens, traceOnly, includeParent } = props;
  146. const rootUnit = includeParent ? structure.root.unitMap.get(unit.id) : unit;
  147. const { x, y, z } = getConformation(rootUnit);
  148. const { elements } = rootUnit;
  149. const { center, radius: sphereRadius } = unit.boundary.sphere;
  150. const extraRadius = (2 + 1.5) * 2; // TODO should be twice (the max vdW/sphere radius plus the probe radius)
  151. const radiusSq = (sphereRadius + extraRadius) * (sphereRadius + extraRadius);
  152. let indices: SortedArray<ElementIndex>;
  153. let id: AssignableArrayLike<number>;
  154. if (ignoreHydrogens || traceOnly || (includeParent && rootUnit !== unit)) {
  155. const _indices = [];
  156. const _id = [];
  157. for (let i = 0, il = elements.length; i < il; ++i) {
  158. const eI = elements[i];
  159. if (ignoreHydrogens && isHydrogen(rootUnit, eI)) continue;
  160. if (traceOnly && !isTrace(rootUnit, eI)) continue;
  161. if (includeParent && squaredDistance(x[eI], y[eI], z[eI], center) > radiusSq) continue;
  162. _indices.push(eI);
  163. _id.push(i);
  164. }
  165. indices = SortedArray.ofSortedArray(_indices);
  166. id = _id;
  167. } else {
  168. indices = elements;
  169. id = fillSerial(new Int32Array(indices.length));
  170. }
  171. if (includeParent && rootUnit !== unit) {
  172. filterId(id, unit.elements, indices);
  173. }
  174. const position = { indices, x, y, z, id };
  175. const boundary = unit === rootUnit ? unit.boundary : getBoundary(position);
  176. const l = StructureElement.Location.create(structure, rootUnit);
  177. const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
  178. const radius = (index: number) => {
  179. l.element = index as ElementIndex;
  180. return sizeTheme.size(l);
  181. };
  182. return { position, boundary, radius };
  183. }
  184. export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens: boolean, traceOnly: boolean) {
  185. const l = StructureElement.Location.create(structure);
  186. const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
  187. let xs: ArrayLike<number>;
  188. let ys: ArrayLike<number>;
  189. let zs: ArrayLike<number>;
  190. let rs: ArrayLike<number>;
  191. let id: ArrayLike<number>;
  192. if (ignoreHydrogens || traceOnly) {
  193. const _xs: number[] = [];
  194. const _ys: number[] = [];
  195. const _zs: number[] = [];
  196. const _rs: number[] = [];
  197. const _id: number[] = [];
  198. for (let i = 0, m = 0, il = structure.units.length; i < il; ++i) {
  199. const unit = structure.units[i];
  200. const { elements } = unit;
  201. const { x, y, z } = unit.conformation;
  202. l.unit = unit;
  203. for (let j = 0, jl = elements.length; j < jl; ++j) {
  204. const eI = elements[j];
  205. if (ignoreHydrogens && isHydrogen(unit, eI)) continue;
  206. if (traceOnly && !isTrace(unit, eI)) continue;
  207. _xs.push(x(eI));
  208. _ys.push(y(eI));
  209. _zs.push(z(eI));
  210. l.element = eI;
  211. _rs.push(sizeTheme.size(l));
  212. _id.push(m + j);
  213. }
  214. m += elements.length;
  215. }
  216. xs = _xs, ys = _ys, zs = _zs, rs = _rs;
  217. id = _id;
  218. } else {
  219. const { elementCount } = structure;
  220. const _xs = new Float32Array(elementCount);
  221. const _ys = new Float32Array(elementCount);
  222. const _zs = new Float32Array(elementCount);
  223. const _rs = new Float32Array(elementCount);
  224. for (let i = 0, m = 0, il = structure.units.length; i < il; ++i) {
  225. const unit = structure.units[i];
  226. const { elements } = unit;
  227. const { x, y, z } = unit.conformation;
  228. l.unit = unit;
  229. for (let j = 0, jl = elements.length; j < jl; ++j) {
  230. const eI = elements[j];
  231. const mj = m + j;
  232. _xs[mj] = x(eI);
  233. _ys[mj] = y(eI);
  234. _zs[mj] = z(eI);
  235. l.element = eI;
  236. _rs[mj] = sizeTheme.size(l);
  237. }
  238. m += elements.length;
  239. }
  240. xs = _xs, ys = _ys, zs = _zs, rs = _rs;
  241. id = fillSerial(new Uint32Array(elementCount));
  242. }
  243. const position = { indices: OrderedSet.ofRange(0, id.length), x: xs, y: ys, z: zs, id };
  244. const radius = (index: number) => rs[index];
  245. return { position, radius };
  246. }
  247. const _H = AtomicNumbers['H'];
  248. export function isHydrogen(unit: Unit, element: ElementIndex) {
  249. if (Unit.isCoarse(unit)) return false;
  250. return unit.model.atomicHierarchy.derived.atom.atomicNumber[element] === _H;
  251. }
  252. export function isH(atomicNumber: ArrayLike<number>, element: ElementIndex) {
  253. return atomicNumber[element] === _H;
  254. }
  255. export function isTrace(unit: Unit, element: ElementIndex) {
  256. if (Unit.isCoarse(unit)) return true;
  257. const atomId = unit.model.atomicHierarchy.atoms.label_atom_id.value(element);
  258. if (atomId === 'CA' || atomId === 'P') return true;
  259. return false;
  260. }
  261. export function getUnitExtraRadius(unit: Unit) {
  262. if (Unit.isAtomic(unit)) return 4;
  263. let max = 0;
  264. const { elements } = unit;
  265. const { r } = unit.conformation;
  266. for (let i = 0, _i = elements.length; i < _i; i++) {
  267. const _r = r(elements[i]);
  268. if (_r > max) max = _r;
  269. }
  270. return max + 1;
  271. }
  272. export function getStructureExtraRadius(structure: Structure) {
  273. let max = 0;
  274. for (const ug of structure.unitSymmetryGroups) {
  275. const r = getUnitExtraRadius(ug.units[0]);
  276. if (r > max) max = r;
  277. }
  278. return max;
  279. }