cubes.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * Inspired by https://github.com/dgasmith/gau2grid.
  5. *
  6. * @author David Sehnal <david.sehnal@gmail.com>
  7. */
  8. import { sortArray } from '../../mol-data/util';
  9. import { WebGLContext } from '../../mol-gl/webgl/context';
  10. import { Box3D } from '../../mol-math/geometry';
  11. import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
  12. import { Grid } from '../../mol-model/volume';
  13. import { Task } from '../../mol-task';
  14. import { arrayMax, arrayMin, arrayRms } from '../../mol-util/array';
  15. import { CollocationParams, sphericalCollocation } from './collocation';
  16. import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
  17. import { SphericalBasisOrder } from './orbitals';
  18. export interface CubeGridInfo {
  19. dimensions: Vec3;
  20. box: Box3D;
  21. size: Vec3;
  22. npoints: number;
  23. delta: Vec3;
  24. }
  25. export interface CubeGrid {
  26. grid: Grid;
  27. isovalues?: { negative?: number; positive?: number };
  28. }
  29. export interface Basis {
  30. atoms: {
  31. // in Bohr units!
  32. center: Vec3;
  33. shells: SphericalElectronShell[];
  34. }[];
  35. }
  36. // Note: generally contracted gaussians are currently not supported.
  37. export interface SphericalElectronShell {
  38. exponents: number[];
  39. angularMomentum: number[];
  40. // number[] for each angular momentum
  41. coefficients: number[][];
  42. }
  43. export interface SphericalCollocationParams {
  44. basis: Basis;
  45. /**
  46. * for each electron shell compute a cutoff radius as
  47. *
  48. * const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
  49. *
  50. */
  51. cutoffThreshold: number;
  52. sphericalOrder: SphericalBasisOrder;
  53. boxExpand: number;
  54. gridSpacing: number | [atomCountThreshold: number, spacing: number][];
  55. alphaOrbitals: number[];
  56. doNotComputeIsovalues?: boolean
  57. }
  58. export function createSphericalCollocationGrid(
  59. params: SphericalCollocationParams, webgl?: WebGLContext
  60. ): Task<CubeGrid> {
  61. return Task.create('Spherical Collocation Grid', async (ctx) => {
  62. const centers = params.basis.atoms.map(a => a.center);
  63. const cParams: CollocationParams = {
  64. grid: initBox(centers, params.gridSpacing, params.boxExpand),
  65. basis: params.basis,
  66. alphaOrbitals: params.alphaOrbitals,
  67. cutoffThreshold: params.cutoffThreshold,
  68. sphericalOrder: params.sphericalOrder
  69. };
  70. let matrix: Float32Array;
  71. if (canComputeAlphaOrbitalsOnGPU(webgl)) {
  72. // console.time('gpu');
  73. matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cParams);
  74. // console.timeEnd('gpu');
  75. } else {
  76. // console.time('cpu');
  77. matrix = await sphericalCollocation(cParams, ctx);
  78. // console.timeEnd('cpu');
  79. }
  80. return createCubeGrid(cParams.grid, matrix, [0, 1, 2], !params.doNotComputeIsovalues);
  81. });
  82. }
  83. const BohrToAngstromFactor = 0.529177210859;
  84. function createCubeGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[], computeIsovalues: boolean) {
  85. const boxSize = Box3D.size(Vec3(), gridInfo.box);
  86. const boxOrigin = Vec3.clone(gridInfo.box.min);
  87. Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
  88. Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
  89. const scale = Mat4.fromScaling(
  90. Mat4(),
  91. Vec3.div(
  92. Vec3(),
  93. boxSize,
  94. Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
  95. )
  96. );
  97. const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
  98. const matrix = Mat4.mul(Mat4(), translate, scale);
  99. const grid: Grid = {
  100. transform: { kind: 'matrix', matrix },
  101. cells: Tensor.create(
  102. Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
  103. (values as any) as Tensor.Data
  104. ),
  105. stats: {
  106. min: arrayMin(values),
  107. max: arrayMax(values),
  108. mean: arrayMax(values),
  109. sigma: arrayRms(values),
  110. },
  111. };
  112. // TODO: when using GPU rendering, the cumulative sum can be computed
  113. // along the ray on the fly?
  114. let isovalues: { negative?: number, positive?: number } | undefined;
  115. if (computeIsovalues) {
  116. isovalues = computeIsocontourValues(values, 0.85);
  117. }
  118. return { grid, isovalues };
  119. }
  120. function initBox(
  121. geometry: Vec3[],
  122. spacing: SphericalCollocationParams['gridSpacing'],
  123. expand: number
  124. ): CubeGridInfo {
  125. const count = geometry.length;
  126. const box = Box3D.expand(
  127. Box3D(),
  128. Box3D.fromVec3Array(Box3D(), geometry),
  129. Vec3.create(expand, expand, expand)
  130. );
  131. const size = Box3D.size(Vec3(), box);
  132. const spacingThresholds =
  133. typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
  134. spacingThresholds.sort((a, b) => b[0] - a[0]);
  135. let s = 0.4;
  136. for (let i = 0; i <= spacingThresholds.length; i++) {
  137. s = spacingThresholds[i][1];
  138. if (spacingThresholds[i][0] <= count) break;
  139. }
  140. const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
  141. return {
  142. box,
  143. dimensions,
  144. size,
  145. npoints: dimensions[0] * dimensions[1] * dimensions[2],
  146. delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
  147. };
  148. }
  149. export function computeIsocontourValues(
  150. input: Float32Array,
  151. cumulativeThreshold: number
  152. ) {
  153. let weightSum = 0;
  154. for (let i = 0, _i = input.length; i < _i; i++) {
  155. const v = input[i];
  156. const w = v * v;
  157. weightSum += w;
  158. }
  159. const avgWeight = weightSum / input.length;
  160. let minWeight = 3 * avgWeight;
  161. // do not try to identify isovalues for degenerate data
  162. // e.g. all values are almost same
  163. if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
  164. return { negative: void 0, positive: void 0 };
  165. }
  166. let size = 0;
  167. while (true) {
  168. let csum = 0;
  169. size = 0;
  170. for (let i = 0, _i = input.length; i < _i; i++) {
  171. const v = input[i];
  172. const w = v * v;
  173. if (w >= minWeight) {
  174. csum += w;
  175. size++;
  176. }
  177. }
  178. if (csum / weightSum > cumulativeThreshold) {
  179. break;
  180. }
  181. minWeight -= avgWeight;
  182. }
  183. const values = new Float32Array(size);
  184. const weights = new Float32Array(size);
  185. const indices = new Int32Array(size);
  186. let o = 0;
  187. for (let i = 0, _i = input.length; i < _i; i++) {
  188. const v = input[i];
  189. const w = v * v;
  190. if (w >= minWeight) {
  191. values[o] = v;
  192. weights[o] = w;
  193. indices[o] = o;
  194. o++;
  195. }
  196. }
  197. sortArray(
  198. indices,
  199. (indices, i, j) => weights[indices[j]] - weights[indices[i]]
  200. );
  201. let cweight = 0,
  202. cutoffIndex = 0;
  203. for (let i = 0; i < size; i++) {
  204. cweight += weights[indices[i]];
  205. if (cweight / weightSum >= cumulativeThreshold) {
  206. cutoffIndex = i;
  207. break;
  208. }
  209. }
  210. let positive = Number.POSITIVE_INFINITY,
  211. negative = Number.NEGATIVE_INFINITY;
  212. for (let i = 0; i < cutoffIndex; i++) {
  213. const v = values[indices[i]];
  214. if (v > 0) {
  215. if (v < positive) positive = v;
  216. } else if (v < 0) {
  217. if (v > negative) negative = v;
  218. }
  219. }
  220. return {
  221. negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
  222. positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
  223. };
  224. }