transforms.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
  7. import { createSphericalCollocationGrid } from './orbitals';
  8. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  9. import { Task } from '../../mol-task';
  10. import { CustomProperties } from '../../mol-model/custom-property';
  11. import { SphericalBasisOrder } from './spherical-functions';
  12. import { Volume } from '../../mol-model/volume';
  13. import { PluginContext } from '../../mol-plugin/context';
  14. import { ColorNames } from '../../mol-util/color/names';
  15. import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
  16. import { StateTransformer } from '../../mol-state';
  17. import { Theme } from '../../mol-theme/theme';
  18. import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
  19. import { AlphaOrbital, Basis, CubeGrid } from './data-model';
  20. import { createSphericalCollocationDensityGrid } from './density';
  21. import { Tensor } from '../../mol-math/linear-algebra';
  22. export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
  23. export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
  24. name: 'static-basis-and-orbitals',
  25. display: 'Basis and Orbitals',
  26. from: PluginStateObject.Root,
  27. to: BasisAndOrbitals,
  28. params: {
  29. label: PD.Text('Orbital Data', { isHidden: true }),
  30. basis: PD.Value<Basis>(void 0 as any, { isHidden: true }),
  31. order: PD.Text<SphericalBasisOrder>('gaussian' as SphericalBasisOrder, { isHidden: true }),
  32. orbitals: PD.Value<AlphaOrbital[]>([], { isHidden: true })
  33. },
  34. })({
  35. apply({ params }) {
  36. return new BasisAndOrbitals({ basis: params.basis, order: params.order, orbitals: params.orbitals }, { label: params.label });
  37. }
  38. });
  39. const CreateOrbitalVolumeParamBase = {
  40. cutoffThreshold: PD.Numeric(0.0015, { min: 0, max: 0.1, step: 0.0001 }),
  41. boxExpand: PD.Numeric(4.5, { min: 0, max: 7, step: 0.1 }),
  42. gridSpacing: PD.ObjectList({ atomCount: PD.Numeric(0), spacing: PD.Numeric(0.35, { min: 0.1, max: 2, step: 0.01 }) }, e => `Atoms ${e.atomCount}: ${e.spacing}`, {
  43. defaultValue: [
  44. { atomCount: 55, spacing: 0.5 },
  45. { atomCount: 40, spacing: 0.45 },
  46. { atomCount: 25, spacing: 0.4 },
  47. { atomCount: 0, spacing: 0.35 },
  48. ]
  49. }),
  50. clampValues: PD.MappedStatic('off', {
  51. off: PD.EmptyGroup(),
  52. on: PD.Group({
  53. sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
  54. })
  55. })
  56. };
  57. function clampData(matrix: Tensor.Data, min: number, max: number) {
  58. for (let i = 0, _i = matrix.length; i < _i; i++) {
  59. const v = matrix[i];
  60. if (v < min) matrix[i] = min;
  61. else if (v > max) matrix[i] = max;
  62. }
  63. }
  64. function clampGrid(data: CubeGrid, v: number) {
  65. const grid = data.grid;
  66. const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
  67. const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
  68. // clamp values for better direct volume resolution
  69. // current implementation uses Byte array for values
  70. // if this is not enough, update mol* to use float
  71. // textures instead
  72. if (grid.stats.min < min || grid.stats.max > max) {
  73. clampData(data.grid.cells.data, min, max);
  74. if (grid.stats.min < min) {
  75. (grid.stats.min as number) = min;
  76. }
  77. if (grid.stats.max > max) {
  78. (grid.stats.max as number) = max;
  79. }
  80. }
  81. }
  82. export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
  83. name: 'create-orbital-volume',
  84. display: 'Orbital Volume',
  85. from: BasisAndOrbitals,
  86. to: PluginStateObject.Volume.Data,
  87. params: (a) => {
  88. if (!a) {
  89. return { index: PD.Numeric(0), ...CreateOrbitalVolumeParamBase };
  90. }
  91. return {
  92. index: PD.Select(0, a.data.orbitals.map((o, i) => [i, `[${i + 1}] ${o.energy.toFixed(4)}`])),
  93. ...CreateOrbitalVolumeParamBase
  94. };
  95. }
  96. })({
  97. apply({ a, params }, plugin: PluginContext) {
  98. return Task.create('Orbital Volume', async ctx => {
  99. const data = await createSphericalCollocationGrid({
  100. basis: a.data.basis,
  101. cutoffThreshold: params.cutoffThreshold,
  102. sphericalOrder: a.data.order,
  103. boxExpand: params.boxExpand,
  104. gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
  105. }, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
  106. const volume: Volume = {
  107. grid: data.grid,
  108. sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
  109. customProperties: new CustomProperties(),
  110. _propertyData: Object.create(null),
  111. };
  112. if (params.clampValues?.name === 'on') {
  113. clampGrid(data, params.clampValues?.params?.sigma ?? 8);
  114. }
  115. return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
  116. });
  117. }
  118. });
  119. export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
  120. name: 'create-orbital-density-volume',
  121. display: 'Orbital Density Volume',
  122. from: BasisAndOrbitals,
  123. to: PluginStateObject.Volume.Data,
  124. params: CreateOrbitalVolumeParamBase
  125. })({
  126. apply({ a, params }, plugin: PluginContext) {
  127. return Task.create('Orbital Volume', async ctx => {
  128. const data = await createSphericalCollocationDensityGrid({
  129. basis: a.data.basis,
  130. cutoffThreshold: params.cutoffThreshold,
  131. sphericalOrder: a.data.order,
  132. boxExpand: params.boxExpand,
  133. gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
  134. }, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
  135. const volume: Volume = {
  136. grid: data.grid,
  137. sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
  138. customProperties: new CustomProperties(),
  139. _propertyData: Object.create(null),
  140. };
  141. if (params.clampValues?.name === 'on') {
  142. clampGrid(data, params.clampValues?.params?.sigma ?? 8);
  143. }
  144. return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
  145. });
  146. }
  147. });
  148. export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
  149. name: 'create-orbital-representation-3d',
  150. display: 'Orbital Representation 3D',
  151. from: PluginStateObject.Volume.Data,
  152. to: PluginStateObject.Volume.Representation3D,
  153. params: {
  154. directVolume: PD.Boolean(false),
  155. relativeIsovalue: PD.Numeric(1, { min: 0.01, max: 5, step: 0.01 }),
  156. kind: PD.Select<'positive' | 'negative'>('positive', [['positive', 'Positive'], ['negative', 'Negative']]),
  157. color: PD.Color(ColorNames.blue),
  158. alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
  159. xrayShaded: PD.Boolean(false),
  160. pickable: PD.Boolean(true)
  161. }
  162. })({
  163. canAutoUpdate() {
  164. return true;
  165. },
  166. apply({ a, params: srcParams }, plugin: PluginContext) {
  167. return Task.create('Orbitals Representation 3D', async ctx => {
  168. const params = volumeParams(plugin, a, srcParams);
  169. const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
  170. const provider = plugin.representation.volume.registry.get(params.type.name);
  171. if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
  172. const props = params.type.params || {};
  173. const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
  174. repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
  175. await repr.createOrUpdate(props, a.data).runInContext(ctx);
  176. repr.setState({ pickable: srcParams.pickable });
  177. return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
  178. });
  179. },
  180. update({ a, b, newParams: srcParams }, plugin: PluginContext) {
  181. return Task.create('Orbitals Representation 3D', async ctx => {
  182. const newParams = volumeParams(plugin, a, srcParams);
  183. const props = { ...b.data.repr.props, ...newParams.type.params };
  184. b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
  185. await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
  186. b.data.repr.setState({ pickable: srcParams.pickable });
  187. b.description = VolumeRepresentation3DHelpers.getDescription(props);
  188. return StateTransformer.UpdateResult.Updated;
  189. });
  190. }
  191. });
  192. function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
  193. if (volume.data.sourceData.kind !== 'alpha-orbitals') throw new Error('Invalid data source kind.');
  194. const { isovalues } = volume.data.sourceData.data as CubeGrid;
  195. if (!isovalues) throw new Error('Isovalues are not computed.');
  196. const value = isovalues[params.kind];
  197. return createVolumeRepresentationParams(plugin, volume.data, params.directVolume ? {
  198. type: 'direct-volume',
  199. typeParams: {
  200. alpha: params.alpha,
  201. renderMode: {
  202. name: 'isosurface',
  203. params: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, singleLayer: false }
  204. },
  205. xrayShaded: params.xrayShaded
  206. },
  207. color: 'uniform',
  208. colorParams: { value: params.color }
  209. } : {
  210. type: 'isosurface',
  211. typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
  212. color: 'uniform',
  213. colorParams: { value: params.color }
  214. });
  215. }