common.ts 14 KB

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