matterviz 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/dist/FilePicker.svelte +37 -20
  2. package/dist/Icon.svelte +2 -2
  3. package/dist/app.css +29 -0
  4. package/dist/brillouin/BrillouinZone.svelte +19 -61
  5. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  6. package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
  7. package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  9. package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
  10. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  11. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
  12. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  13. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  15. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
  16. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  17. package/dist/chempot-diagram/color.d.ts +10 -0
  18. package/dist/chempot-diagram/color.js +33 -0
  19. package/dist/chempot-diagram/compute.d.ts +38 -0
  20. package/dist/chempot-diagram/compute.js +650 -0
  21. package/dist/chempot-diagram/index.d.ts +5 -0
  22. package/dist/chempot-diagram/index.js +5 -0
  23. package/dist/chempot-diagram/pointer.d.ts +16 -0
  24. package/dist/chempot-diagram/pointer.js +40 -0
  25. package/dist/chempot-diagram/temperature.d.ts +15 -0
  26. package/dist/chempot-diagram/temperature.js +37 -0
  27. package/dist/chempot-diagram/types.d.ts +83 -0
  28. package/dist/chempot-diagram/types.js +27 -0
  29. package/dist/colors/index.d.ts +3 -1
  30. package/dist/colors/index.js +4 -0
  31. package/dist/composition/BarChart.svelte +13 -22
  32. package/dist/composition/BubbleChart.svelte +5 -3
  33. package/dist/composition/FormulaFilter.svelte +586 -94
  34. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  35. package/dist/composition/PieChart.svelte +43 -18
  36. package/dist/composition/PieChart.svelte.d.ts +1 -1
  37. package/dist/convex-hull/ConvexHull.svelte +4 -2
  38. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  39. package/dist/convex-hull/ConvexHull2D.svelte +13 -44
  40. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  41. package/dist/convex-hull/ConvexHull3D.svelte +16 -7
  42. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  43. package/dist/convex-hull/ConvexHull4D.svelte +17 -7
  44. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  45. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHullStats.svelte +701 -226
  47. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  48. package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
  49. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  50. package/dist/convex-hull/demo-temperature.js +36 -0
  51. package/dist/convex-hull/helpers.d.ts +1 -1
  52. package/dist/convex-hull/helpers.js +2 -4
  53. package/dist/convex-hull/index.d.ts +1 -0
  54. package/dist/convex-hull/index.js +1 -0
  55. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  56. package/dist/convex-hull/thermodynamics.js +106 -17
  57. package/dist/convex-hull/types.d.ts +5 -0
  58. package/dist/convex-hull/types.js +5 -0
  59. package/dist/coordination/CoordinationBarPlot.svelte +29 -46
  60. package/dist/element/BohrAtom.svelte +1 -1
  61. package/dist/element/data.js +2 -14
  62. package/dist/element/data.json.gz +0 -0
  63. package/dist/element/types.d.ts +1 -0
  64. package/dist/fermi-surface/FermiSurface.svelte +20 -64
  65. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  66. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  67. package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
  68. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  69. package/dist/fermi-surface/parse.js +16 -22
  70. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
  71. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  72. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
  73. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
  74. package/dist/heatmap-matrix/index.d.ts +53 -0
  75. package/dist/heatmap-matrix/index.js +100 -0
  76. package/dist/heatmap-matrix/shared.d.ts +2 -0
  77. package/dist/heatmap-matrix/shared.js +4 -0
  78. package/dist/icons.d.ts +111 -0
  79. package/dist/icons.js +111 -0
  80. package/dist/index.d.ts +3 -1
  81. package/dist/index.js +3 -1
  82. package/dist/io/export.js +15 -3
  83. package/dist/io/file-drop.d.ts +7 -0
  84. package/dist/io/file-drop.js +43 -0
  85. package/dist/io/index.d.ts +2 -2
  86. package/dist/io/index.js +2 -112
  87. package/dist/io/types.d.ts +1 -0
  88. package/dist/io/url-drop.d.ts +2 -0
  89. package/dist/io/url-drop.js +118 -0
  90. package/dist/isosurface/Isosurface.svelte +101 -45
  91. package/dist/isosurface/IsosurfaceControls.svelte +19 -0
  92. package/dist/isosurface/parse.js +73 -30
  93. package/dist/isosurface/slice.d.ts +2 -1
  94. package/dist/isosurface/slice.js +3 -3
  95. package/dist/isosurface/types.d.ts +13 -1
  96. package/dist/isosurface/types.js +98 -0
  97. package/dist/labels.d.ts +2 -1
  98. package/dist/labels.js +1 -0
  99. package/dist/layout/InfoTag.svelte +62 -62
  100. package/dist/layout/SubpageGrid.svelte +74 -0
  101. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  102. package/dist/layout/index.d.ts +1 -0
  103. package/dist/layout/index.js +1 -0
  104. package/dist/layout/json-tree/JsonNode.svelte +83 -85
  105. package/dist/layout/json-tree/JsonTree.svelte +20 -19
  106. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  107. package/dist/layout/json-tree/JsonValue.svelte +196 -116
  108. package/dist/layout/json-tree/types.d.ts +10 -2
  109. package/dist/layout/json-tree/utils.d.ts +2 -0
  110. package/dist/layout/json-tree/utils.js +33 -0
  111. package/dist/math.d.ts +7 -0
  112. package/dist/math.js +358 -7
  113. package/dist/overlays/ContextMenu.svelte +3 -2
  114. package/dist/overlays/DraggablePane.svelte +163 -58
  115. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  116. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
  117. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  118. package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
  119. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  120. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
  121. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  122. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
  123. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
  124. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
  125. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  126. package/dist/phase-diagram/index.d.ts +2 -0
  127. package/dist/phase-diagram/index.js +2 -0
  128. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  129. package/dist/phase-diagram/svg-to-diagram.js +865 -0
  130. package/dist/phase-diagram/types.d.ts +10 -0
  131. package/dist/phase-diagram/utils.d.ts +7 -4
  132. package/dist/phase-diagram/utils.js +149 -59
  133. package/dist/plot/AxisLabel.svelte +26 -0
  134. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  135. package/dist/plot/BarPlot.svelte +473 -228
  136. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  137. package/dist/plot/BarPlotControls.svelte +3 -2
  138. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  139. package/dist/plot/ColorBar.svelte +54 -54
  140. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  141. package/dist/plot/ColorScaleSelect.svelte +1 -1
  142. package/dist/plot/ElementScatter.svelte +3 -2
  143. package/dist/plot/FillArea.svelte +4 -1
  144. package/dist/plot/Histogram.svelte +320 -230
  145. package/dist/plot/Histogram.svelte.d.ts +2 -2
  146. package/dist/plot/HistogramControls.svelte +29 -10
  147. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  148. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
  149. package/dist/plot/PlotControls.svelte +109 -27
  150. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  151. package/dist/plot/PlotLegend.svelte +1 -1
  152. package/dist/plot/PortalSelect.svelte +2 -1
  153. package/dist/plot/ReferenceLine.svelte +2 -1
  154. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  155. package/dist/plot/ReferencePlane.svelte +1 -3
  156. package/dist/plot/ScatterPlot.svelte +343 -209
  157. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  158. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  159. package/dist/plot/ScatterPlot3DControls.svelte +203 -250
  160. package/dist/plot/ScatterPlot3DScene.svelte +4 -7
  161. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  162. package/dist/plot/ScatterPlotControls.svelte +95 -55
  163. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  164. package/dist/plot/ZeroLines.svelte +44 -0
  165. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  166. package/dist/plot/ZoomRect.svelte +21 -0
  167. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  168. package/dist/plot/axis-utils.d.ts +1 -1
  169. package/dist/plot/index.d.ts +6 -2
  170. package/dist/plot/index.js +6 -2
  171. package/dist/plot/interactions.d.ts +8 -10
  172. package/dist/plot/interactions.js +2 -3
  173. package/dist/plot/layout.d.ts +7 -1
  174. package/dist/plot/layout.js +12 -4
  175. package/dist/plot/reference-line.d.ts +4 -21
  176. package/dist/plot/reference-line.js +7 -81
  177. package/dist/plot/types.d.ts +42 -17
  178. package/dist/plot/types.js +10 -0
  179. package/dist/plot/utils/label-placement.js +13 -10
  180. package/dist/plot/utils.d.ts +1 -0
  181. package/dist/plot/utils.js +14 -0
  182. package/dist/rdf/RdfPlot.svelte +55 -66
  183. package/dist/settings.d.ts +3 -0
  184. package/dist/settings.js +17 -3
  185. package/dist/spectral/Bands.svelte +515 -143
  186. package/dist/spectral/Bands.svelte.d.ts +22 -2
  187. package/dist/spectral/helpers.d.ts +23 -1
  188. package/dist/spectral/helpers.js +65 -9
  189. package/dist/spectral/types.d.ts +2 -0
  190. package/dist/structure/AtomLegend.svelte +29 -8
  191. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  192. package/dist/structure/CellSelect.svelte +92 -22
  193. package/dist/structure/Structure.svelte +108 -118
  194. package/dist/structure/Structure.svelte.d.ts +1 -1
  195. package/dist/structure/StructureControls.svelte +25 -22
  196. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  197. package/dist/structure/StructureInfoPane.svelte +7 -1
  198. package/dist/structure/StructureScene.svelte +104 -66
  199. package/dist/structure/StructureScene.svelte.d.ts +2 -1
  200. package/dist/structure/atom-properties.d.ts +6 -2
  201. package/dist/structure/atom-properties.js +38 -25
  202. package/dist/structure/export.js +10 -7
  203. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  204. package/dist/structure/ferrox-wasm-types.js +0 -3
  205. package/dist/structure/ferrox-wasm.d.ts +3 -2
  206. package/dist/structure/ferrox-wasm.js +1 -2
  207. package/dist/structure/index.d.ts +6 -0
  208. package/dist/structure/index.js +22 -0
  209. package/dist/structure/parse.js +19 -16
  210. package/dist/structure/partial-occupancy.d.ts +25 -0
  211. package/dist/structure/partial-occupancy.js +102 -0
  212. package/dist/structure/validation.js +6 -3
  213. package/dist/symmetry/SymmetryStats.svelte +18 -4
  214. package/dist/symmetry/WyckoffTable.svelte +18 -10
  215. package/dist/symmetry/index.d.ts +7 -4
  216. package/dist/symmetry/index.js +83 -18
  217. package/dist/table/HeatmapTable.svelte +425 -65
  218. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  219. package/dist/table/ToggleMenu.svelte +2 -0
  220. package/dist/table/index.d.ts +2 -0
  221. package/dist/trajectory/Trajectory.svelte +147 -145
  222. package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
  223. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  224. package/dist/trajectory/constants.d.ts +6 -0
  225. package/dist/trajectory/constants.js +7 -0
  226. package/dist/trajectory/extract.js +3 -5
  227. package/dist/trajectory/format-detect.d.ts +9 -0
  228. package/dist/trajectory/format-detect.js +76 -0
  229. package/dist/trajectory/frame-reader.d.ts +17 -0
  230. package/dist/trajectory/frame-reader.js +339 -0
  231. package/dist/trajectory/helpers.d.ts +15 -0
  232. package/dist/trajectory/helpers.js +187 -0
  233. package/dist/trajectory/index.d.ts +1 -0
  234. package/dist/trajectory/index.js +11 -4
  235. package/dist/trajectory/parse/ase.d.ts +2 -0
  236. package/dist/trajectory/parse/ase.js +76 -0
  237. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  238. package/dist/trajectory/parse/hdf5.js +121 -0
  239. package/dist/trajectory/parse/index.d.ts +12 -0
  240. package/dist/trajectory/parse/index.js +304 -0
  241. package/dist/trajectory/parse/lammps.d.ts +5 -0
  242. package/dist/trajectory/parse/lammps.js +169 -0
  243. package/dist/trajectory/parse/vasp.d.ts +2 -0
  244. package/dist/trajectory/parse/vasp.js +65 -0
  245. package/dist/trajectory/parse/xyz.d.ts +2 -0
  246. package/dist/trajectory/parse/xyz.js +109 -0
  247. package/dist/trajectory/types.d.ts +11 -0
  248. package/dist/trajectory/types.js +1 -0
  249. package/dist/utils.d.ts +2 -0
  250. package/dist/utils.js +4 -0
  251. package/dist/xrd/XrdPlot.svelte +6 -4
  252. package/dist/xrd/calc-xrd.js +0 -1
  253. package/package.json +30 -24
  254. package/readme.md +4 -4
  255. package/dist/trajectory/parse.d.ts +0 -42
  256. package/dist/trajectory/parse.js +0 -1267
  257. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
@@ -2,9 +2,8 @@ import { COMPRESSION_EXTENSIONS_REGEX, CONFIG_DIRS_REGEX, STRUCT_KEYWORDS_REGEX,
2
2
  import { ELEM_SYMBOLS } from '../labels';
3
3
  import * as math from '../math';
4
4
  import { wrap_to_unit_cell } from './pbc';
5
+ import { normalize_scientific_notation } from '../utils';
5
6
  import { load as yaml_load } from 'js-yaml';
6
- // Normalize scientific notation in coordinate strings (handles eEdD and *^ notation variants)
7
- const normalize_scientific_notation = (str) => str.toLowerCase().replace(/d/g, `e`).replace(/\*\^/g, `e`);
8
7
  // Parse a coordinate value that might be in various scientific notation formats
9
8
  function parse_coordinate(str) {
10
9
  const normalized = normalize_scientific_notation(str.trim());
@@ -939,11 +938,10 @@ function find_structure_in_json(obj, visited = new WeakSet()) {
939
938
  return null;
940
939
  }
941
940
  // Check if this object looks like a valid structure
942
- const potential_structure = obj;
943
- if (is_parsed_structure(potential_structure))
944
- return potential_structure;
941
+ if (is_parsed_structure(obj))
942
+ return obj;
945
943
  // Otherwise, recursively search through all properties
946
- for (const value of Object.values(potential_structure)) {
944
+ for (const value of Object.values(obj)) {
947
945
  const result = find_structure_in_json(value, visited);
948
946
  if (result)
949
947
  return result;
@@ -954,17 +952,19 @@ function find_structure_in_json(obj, visited = new WeakSet()) {
954
952
  function is_parsed_structure(obj) {
955
953
  if (!obj || typeof obj !== `object`)
956
954
  return false;
957
- const record = obj;
958
- // Must have non-empty sites array
959
- if (!Array.isArray(record.sites) || record.sites.length === 0)
955
+ const parsed_obj = obj;
956
+ const sites = parsed_obj.sites;
957
+ if (!Array.isArray(sites) || sites.length === 0)
960
958
  return false;
961
- // Check if first site looks valid (has species and coordinates)
962
- const first_site = record.sites[0];
959
+ const first_site = sites[0];
963
960
  if (!first_site || typeof first_site !== `object`)
964
961
  return false;
965
- // Must have species (array) and either abc or xyz coordinates
966
- const has_species = Array.isArray(first_site.species) && first_site.species.length > 0;
967
- const has_coords = Array.isArray(first_site.abc) || Array.isArray(first_site.xyz);
962
+ const first_site_obj = first_site;
963
+ const species = first_site_obj.species;
964
+ const abc = first_site_obj.abc;
965
+ const xyz = first_site_obj.xyz;
966
+ const has_species = Array.isArray(species) && species.length > 0;
967
+ const has_coords = Array.isArray(abc) || Array.isArray(xyz);
968
968
  return has_species && has_coords;
969
969
  }
970
970
  // Normalize structure coordinates: wrap fractional coords to [0,1) and recompute Cartesian
@@ -1268,8 +1268,11 @@ function is_optimade_structure_object(value) {
1268
1268
  if (!value || typeof value !== `object`)
1269
1269
  return false;
1270
1270
  const obj = value;
1271
- return obj.type === `structures` && typeof obj.id === `string` &&
1272
- typeof obj.attributes === `object` && obj.attributes !== null;
1271
+ const type = obj.type;
1272
+ const id = obj.id;
1273
+ const attributes = obj.attributes;
1274
+ return type === `structures` && typeof id === `string` &&
1275
+ typeof attributes === `object` && attributes !== null;
1273
1276
  }
1274
1277
  // Convert OPTIMADE structure to Crystal format
1275
1278
  export function optimade_to_crystal(optimade_structure) {
@@ -0,0 +1,25 @@
1
+ import type { Site } from './';
2
+ export declare const PARTIAL_OCCUPANCY_SLICE_GAP_RAD = 0.001;
3
+ export type RenderSite = {
4
+ site_idx: number;
5
+ site: Site;
6
+ is_image_atom: boolean;
7
+ source_site_indices: number[];
8
+ };
9
+ export type SliceGeometry = {
10
+ element: string;
11
+ occupancy: number;
12
+ start_phi: number;
13
+ end_phi: number;
14
+ phi_length: number;
15
+ render_start_cap: boolean;
16
+ render_end_cap: boolean;
17
+ };
18
+ export type CapArcConfig = {
19
+ start_cap_arc_start: number;
20
+ end_cap_arc_start: number;
21
+ arc_length: number;
22
+ };
23
+ export declare const PARTIAL_OCCUPANCY_CAP_ARC: CapArcConfig;
24
+ export declare const merge_split_partial_sites: (sites: Site[], hidden_elements?: ReadonlySet<string>) => RenderSite[];
25
+ export declare const compute_slice_geometry: (visible_species: Site[`species`], slice_gap_rad?: number) => SliceGeometry[];
@@ -0,0 +1,102 @@
1
+ export const PARTIAL_OCCUPANCY_SLICE_GAP_RAD = 1e-3;
2
+ const OCCUPANCY_EPS = 1e-6;
3
+ const MIN_PHI_LENGTH = 1e-4;
4
+ const MERGE_DISTANCE_TOLERANCE = 1e-8;
5
+ const CAP_ARC_START = Math.PI / 2;
6
+ const is_image_atom = (site) => typeof site.properties?.orig_site_idx === `number`;
7
+ const make_render_site = (sites, site_idx, source_site_indices, site_override) => ({
8
+ site_idx,
9
+ site: site_override ?? sites[site_idx],
10
+ is_image_atom: source_site_indices.some((source_site_idx) => is_image_atom(sites[source_site_idx])),
11
+ source_site_indices,
12
+ });
13
+ const sq_dist = (xyz_1, xyz_2) => (xyz_1[0] - xyz_2[0]) ** 2 +
14
+ (xyz_1[1] - xyz_2[1]) ** 2 +
15
+ (xyz_1[2] - xyz_2[2]) ** 2;
16
+ const is_split_partial_site = (site, hidden_elements) => {
17
+ const visible_species = site.species.filter(({ element }) => !hidden_elements.has(element));
18
+ const total_visible_occupancy = visible_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
19
+ return visible_species.length === 1 && total_visible_occupancy < 1 - OCCUPANCY_EPS;
20
+ };
21
+ const group_split_partial_indices = (sites, hidden_elements) => {
22
+ const grouped_centers = [];
23
+ const grouped_site_indices = [];
24
+ const non_grouped_site_indices = [];
25
+ for (const [site_idx, site] of sites.entries()) {
26
+ if (!is_split_partial_site(site, hidden_elements)) {
27
+ non_grouped_site_indices.push(site_idx);
28
+ continue;
29
+ }
30
+ const matched_group_idx = grouped_centers.findIndex((center_xyz) => sq_dist(center_xyz, site.xyz) <= MERGE_DISTANCE_TOLERANCE ** 2);
31
+ if (matched_group_idx === -1) {
32
+ grouped_centers.push(site.xyz);
33
+ grouped_site_indices.push([site_idx]);
34
+ continue;
35
+ }
36
+ grouped_site_indices[matched_group_idx].push(site_idx);
37
+ }
38
+ return { non_grouped_site_indices, grouped_site_indices };
39
+ };
40
+ const build_render_sites = (sites, non_grouped_site_indices, grouped_site_indices) => {
41
+ const render_sites = non_grouped_site_indices.map((site_idx) => make_render_site(sites, site_idx, [site_idx]));
42
+ for (const grouped_indices of grouped_site_indices) {
43
+ if (grouped_indices.length === 1) {
44
+ const site_idx = grouped_indices[0];
45
+ render_sites.push(make_render_site(sites, site_idx, [site_idx]));
46
+ continue;
47
+ }
48
+ const representative_site_idx = grouped_indices[0];
49
+ const representative_site = sites[representative_site_idx];
50
+ const merged_species = grouped_indices.flatMap((grouped_site_idx) => sites[grouped_site_idx].species);
51
+ render_sites.push(make_render_site(sites, representative_site_idx, [...grouped_indices], { ...representative_site, species: merged_species }));
52
+ }
53
+ return render_sites;
54
+ };
55
+ export const PARTIAL_OCCUPANCY_CAP_ARC = {
56
+ start_cap_arc_start: CAP_ARC_START,
57
+ end_cap_arc_start: CAP_ARC_START,
58
+ arc_length: Math.PI,
59
+ };
60
+ export const merge_split_partial_sites = (sites, hidden_elements = new Set()) => {
61
+ const grouped_indices = group_split_partial_indices(sites, hidden_elements);
62
+ return build_render_sites(sites, grouped_indices.non_grouped_site_indices, grouped_indices.grouped_site_indices);
63
+ };
64
+ export const compute_slice_geometry = (visible_species, slice_gap_rad = PARTIAL_OCCUPANCY_SLICE_GAP_RAD) => {
65
+ if (visible_species.length === 0)
66
+ return [];
67
+ const total_visible_occupancy = visible_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
68
+ // Preserve total angular coverage at one full turn for invalid overfull inputs.
69
+ const occupancy_scale_factor = total_visible_occupancy > 1 + OCCUPANCY_EPS
70
+ ? 1 / total_visible_occupancy
71
+ : 1;
72
+ const normalized_species = visible_species.map(({ element, occu }) => ({
73
+ element,
74
+ occu: occu * occupancy_scale_factor,
75
+ }));
76
+ const normalized_total_occupancy = normalized_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
77
+ const has_vacancy_gap = normalized_total_occupancy < 1 - OCCUPANCY_EPS;
78
+ const last_visible_species_idx = normalized_species.length - 1;
79
+ let start_angle = 0;
80
+ return normalized_species.map(({ element, occu }, species_idx) => {
81
+ const start_phi_raw = 2 * Math.PI * start_angle;
82
+ const end_phi_raw = 2 * Math.PI * (start_angle += occu);
83
+ // Keep neighboring wedges from sharing the exact same plane (z-fighting).
84
+ const phi_span_raw = Math.max(0, end_phi_raw - start_phi_raw);
85
+ const max_safe_gap = Math.max(0, phi_span_raw - MIN_PHI_LENGTH);
86
+ const desired_gap = visible_species.length > 1
87
+ ? Math.min(slice_gap_rad, phi_span_raw * 0.25)
88
+ : 0;
89
+ const phi_gap = Math.min(desired_gap, max_safe_gap);
90
+ const start_phi = start_phi_raw + phi_gap / 2;
91
+ const end_phi = end_phi_raw - phi_gap / 2;
92
+ return {
93
+ element,
94
+ occupancy: occu,
95
+ start_phi,
96
+ end_phi,
97
+ phi_length: Math.max(MIN_PHI_LENGTH, end_phi - start_phi),
98
+ render_start_cap: has_vacancy_gap && species_idx === 0,
99
+ render_end_cap: has_vacancy_gap && species_idx === last_visible_species_idx,
100
+ };
101
+ });
102
+ };
@@ -1,8 +1,11 @@
1
1
  export function is_crystal(obj) {
2
2
  if (obj === null || typeof obj !== `object`)
3
3
  return false;
4
- const input = obj;
5
- const has_sites = Array.isArray(input.sites) && input.sites.length > 0;
6
- const has_lattice = Boolean(input.lattice) && typeof input.lattice === `object`;
4
+ const structure_obj = obj;
5
+ const sites = structure_obj.sites;
6
+ const lattice = structure_obj.lattice;
7
+ const has_sites = Array.isArray(sites) && sites.length > 0;
8
+ const has_lattice = lattice !== undefined && lattice !== null &&
9
+ typeof lattice === `object`;
7
10
  return has_sites && has_lattice;
8
11
  }
@@ -4,6 +4,7 @@ import { default_sym_settings, wyckoff_positions_from_moyo } from './index';
4
4
  import * as spg from './spacegroups';
5
5
  let { sym_data, settings = $bindable(default_sym_settings), show_tooltips = true, children, label = `Symmetry`, header, ...rest } = $props();
6
6
  const wyckoff_count = $derived(sym_data ? wyckoff_positions_from_moyo(sym_data).length : 0);
7
+ const display_hm_symbol = $derived(sym_data?.hm_symbol?.replace(/\s+/g, ``) ?? `?`);
7
8
  const sym_ops_counts = $derived.by(() => {
8
9
  const EPS = 1e-10;
9
10
  if (!sym_data?.operations) {
@@ -36,6 +37,13 @@ const titles = {
36
37
  roto_translations: `Number of roto-translations in the crystal structure.`,
37
38
  };
38
39
  const tooltips = $derived(show_tooltips ? titles : {});
40
+ function get_step_from_order_of_magnitude(value) {
41
+ if (!Number.isFinite(value) || value <= 0)
42
+ return 1e-5;
43
+ const exponent = Math.floor(Math.log10(value));
44
+ return Math.pow(10, exponent);
45
+ }
46
+ const symprec_step = $derived(get_step_from_order_of_magnitude(settings.symprec));
39
47
  </script>
40
48
 
41
49
  <div {...rest} class="symmetry-stats {rest.class ?? ``}">
@@ -55,15 +63,21 @@ const tooltips = $derived(show_tooltips ? titles : {});
55
63
  <span {@attach tooltip()} title={tooltips?.symprec}>Precision</span>
56
64
  <input
57
65
  type="number"
58
- step="1e-5"
66
+ step={symprec_step}
59
67
  value={settings.symprec}
60
- onchange={(evt) => {
68
+ oninput={(evt) => {
61
69
  const { value } = evt.currentTarget
62
- const parsed = parseFloat(value)
70
+ if (value === ``) return
71
+ const parsed = Number(value)
63
72
  if (Number.isFinite(parsed)) {
64
73
  settings = { ...settings, symprec: parsed }
65
74
  }
66
75
  }}
76
+ onkeydown={(evt) => {
77
+ if (evt.key === `Escape`) {
78
+ evt.currentTarget.blur()
79
+ }
80
+ }}
67
81
  />
68
82
  </label>
69
83
  <label>
@@ -91,7 +105,7 @@ const tooltips = $derived(show_tooltips ? titles : {});
91
105
  title="{tooltips?.space_group} at {settings.symprec} (using {settings.algo} algo). {tooltips?.hermann_mauguin}"
92
106
  {@attach tooltip()}
93
107
  >
94
- Space Group <strong>{sym_data.number} ({sym_data.hm_symbol ?? `?`})</strong>
108
+ Space Group <strong>{sym_data.number} ({display_hm_symbol})</strong>
95
109
  </div>
96
110
  <div title={tooltips?.crystal_system} {@attach tooltip()}>
97
111
  Crystal System <strong>{spg.spacegroup_to_crystal_sys(sym_data.number)}</strong>
@@ -2,7 +2,8 @@
2
2
  import { format_fractional } from '../labels';
3
3
  import { colors } from '../state.svelte';
4
4
  let { wyckoff_positions, on_hover, on_click, active_color = `#2563eb`, ...rest } = $props();
5
- let selected_wyckoff = $state(null);
5
+ let selected_key = $state(null);
6
+ const get_row_key = (wyckoff_pos, row_idx) => `${wyckoff_pos.wyckoff}-${wyckoff_pos.elem}-${wyckoff_pos.site_indices?.join(`,`) ?? `none`}-${row_idx}`;
6
7
  </script>
7
8
 
8
9
  {#if wyckoff_positions && wyckoff_positions.length > 0}
@@ -22,10 +23,14 @@ let selected_wyckoff = $state(null);
22
23
  </tr>
23
24
  </thead>
24
25
  <tbody>
25
- {#each wyckoff_positions as wyckoff_pos (JSON.stringify(wyckoff_pos))}
26
+ {#each wyckoff_positions as
27
+ wyckoff_pos,
28
+ row_idx
29
+ (get_row_key(wyckoff_pos, row_idx))
30
+ }
26
31
  {@const { wyckoff, elem, abc, site_indices } = wyckoff_pos}
27
- {@const is_selected =
28
- JSON.stringify(selected_wyckoff) === JSON.stringify(wyckoff_pos)}
32
+ {@const row_key = get_row_key(wyckoff_pos, row_idx)}
33
+ {@const is_selected = selected_key === row_key}
29
34
  <tr
30
35
  class="wyckoff-row"
31
36
  tabindex="0"
@@ -35,16 +40,14 @@ let selected_wyckoff = $state(null);
35
40
  onmouseenter={() => on_hover?.(site_indices ?? null)}
36
41
  onmouseleave={() => on_hover?.(null)}
37
42
  onclick={() => {
38
- selected_wyckoff = is_selected ? null : wyckoff_pos
39
- on_click?.(selected_wyckoff?.site_indices ?? null)
43
+ selected_key = is_selected ? null : row_key
44
+ on_click?.(is_selected ? null : (wyckoff_pos.site_indices ?? null))
40
45
  }}
41
46
  onkeydown={(event) => {
42
47
  if ([`Enter`, ` `].includes(event.key)) {
43
48
  event.preventDefault()
44
- const is_selected =
45
- JSON.stringify(selected_wyckoff) === JSON.stringify(wyckoff_pos)
46
- selected_wyckoff = is_selected ? null : wyckoff_pos
47
- on_click?.(selected_wyckoff?.site_indices ?? null)
49
+ selected_key = is_selected ? null : row_key
50
+ on_click?.(is_selected ? null : (wyckoff_pos.site_indices ?? null))
48
51
  }
49
52
  }}
50
53
  >
@@ -69,6 +72,11 @@ let selected_wyckoff = $state(null);
69
72
  .wyckoff-table {
70
73
  margin-top: 1em;
71
74
  }
75
+ .wyckoff-table :is(th, td) {
76
+ padding: 2px 6px;
77
+ text-align: center;
78
+ vertical-align: middle;
79
+ }
72
80
  .wyckoff-row {
73
81
  cursor: pointer;
74
82
  transition: background-color 0.2s ease;
@@ -29,12 +29,15 @@ export type WyckoffPos = {
29
29
  abc: Vec3;
30
30
  site_indices?: number[];
31
31
  };
32
+ export type SymmetryDataset = MoyoDataset & {
33
+ orig_indices?: number[];
34
+ orig_site_indices_by_std_idx?: number[][];
35
+ };
32
36
  export declare function ensure_moyo_wasm_ready(wasm_url?: string): Promise<void>;
33
37
  export declare function to_cell_json(structure: Crystal): string;
34
- export declare function analyze_structure_symmetry(struct_or_mol: AnyStructure, settings: Partial<SymmetrySettings>): Promise<MoyoDataset>;
38
+ export declare function map_std_to_orig_site_indices(std_positions: Vec3[], std_numbers: number[], input_positions: Vec3[], input_numbers: number[], orig_site_indices_by_input_idx: number[][]): number[][];
39
+ export declare function analyze_structure_symmetry(struct_or_mol: AnyStructure, settings: Partial<SymmetrySettings>): Promise<SymmetryDataset>;
35
40
  export declare function simplicity_score(vec: number[]): number;
36
- export declare function wyckoff_positions_from_moyo(sym_data: (MoyoDataset & {
37
- orig_indices?: number[];
38
- }) | null): WyckoffPos[];
41
+ export declare function wyckoff_positions_from_moyo(sym_data: SymmetryDataset | null): WyckoffPos[];
39
42
  export declare function apply_symmetry_operations(position: Vec3, operations: MoyoDataset[`operations`], _tolerance?: number): Vec3[];
40
43
  export declare function map_wyckoff_to_all_atoms(wyckoff_positions: WyckoffPos[], displayed_structure: Crystal, orig_structure: Crystal, sym_data: MoyoDataset | null, tolerance?: number): WyckoffPos[];
@@ -1,5 +1,6 @@
1
1
  import { ATOMIC_NUMBER_TO_SYMBOL, SYMBOL_TO_ATOMIC_NUMBER } from '../composition/parse';
2
2
  import { DEFAULTS } from '../settings';
3
+ import { merge_split_partial_sites } from '../structure/partial-occupancy';
3
4
  import init, { analyze_cell } from '@spglib/moyo-wasm';
4
5
  import moyo_wasm_url from '@spglib/moyo-wasm/moyo_wasm_bg.wasm?url';
5
6
  export * from './cell-transform';
@@ -21,6 +22,7 @@ export const default_sym_settings = {
21
22
  algo: DEFAULTS.symmetry.algo,
22
23
  };
23
24
  let initialized = false;
25
+ const OCCUPANCY_EPS = 1e-8;
24
26
  export async function ensure_moyo_wasm_ready(wasm_url) {
25
27
  if (initialized)
26
28
  return;
@@ -29,34 +31,90 @@ export async function ensure_moyo_wasm_ready(wasm_url) {
29
31
  await init({ module_or_path: url });
30
32
  initialized = true;
31
33
  }
32
- export function to_cell_json(structure) {
34
+ function get_site_atomic_number(site, site_idx) {
35
+ const occupancy_by_element = new Map();
36
+ for (const { element, occu } of site.species) {
37
+ if (occu <= OCCUPANCY_EPS)
38
+ continue;
39
+ occupancy_by_element.set(element, (occupancy_by_element.get(element) ?? 0) + occu);
40
+ }
41
+ let selected_element = site
42
+ .species[0]?.element;
43
+ let best_occupancy = -Infinity;
44
+ occupancy_by_element.forEach((occupancy, element) => {
45
+ if (occupancy > best_occupancy ||
46
+ (occupancy === best_occupancy && element.localeCompare(selected_element ?? ``) < 0)) {
47
+ selected_element = element;
48
+ best_occupancy = occupancy;
49
+ }
50
+ });
51
+ if (selected_element === undefined) {
52
+ throw new Error(`Unknown element at site ${site_idx}: ${String(selected_element)}`);
53
+ }
54
+ const atomic_number = SYMBOL_TO_ATOMIC_NUMBER[selected_element];
55
+ if (atomic_number === undefined) {
56
+ throw new Error(`Unknown element at site ${site_idx}: ${String(selected_element)}`);
57
+ }
58
+ return atomic_number;
59
+ }
60
+ function build_moyo_input_cell(structure) {
61
+ const merged_render_sites = merge_split_partial_sites(structure.sites);
62
+ return {
63
+ positions: merged_render_sites.map(({ site }) => site.abc),
64
+ numbers: merged_render_sites.map(({ site, site_idx }) => get_site_atomic_number(site, site_idx)),
65
+ orig_site_indices_by_input_idx: merged_render_sites.map(({ source_site_indices }) => source_site_indices),
66
+ };
67
+ }
68
+ function build_moyo_cell(structure, positions, numbers) {
33
69
  // nalgebra Matrix3 deserializes as a flat list in COLUMN-MAJOR of the internal basis B
34
70
  // Internal B = transpose(row-basis RB). column-major(B) == row-major(RB).
35
71
  // So supply row-major of the pymatgen lattice.matrix (RB).
36
72
  const [v_a, v_b, v_c] = structure.lattice.matrix;
37
- const basis = [...v_a, ...v_b, ...v_c];
38
- const positions = structure.sites.map((site) => site.abc);
39
- const numbers = structure.sites.map((site, idx) => {
40
- const sym = site.species?.[0]?.element;
41
- const num = sym !== null ? SYMBOL_TO_ATOMIC_NUMBER[sym] : undefined;
42
- if (typeof num !== `number`) {
43
- throw new Error(`Unknown element at site ${idx}: ${String(sym)}`);
73
+ return {
74
+ lattice: { basis: [...v_a, ...v_b, ...v_c] },
75
+ positions,
76
+ numbers,
77
+ };
78
+ }
79
+ export function to_cell_json(structure) {
80
+ const { positions, numbers } = build_moyo_input_cell(structure);
81
+ return JSON.stringify(build_moyo_cell(structure, positions, numbers));
82
+ }
83
+ const fractional_sq_dist = (pos_1, pos_2) => (pos_1[0] - pos_2[0] - Math.round(pos_1[0] - pos_2[0])) ** 2 +
84
+ (pos_1[1] - pos_2[1] - Math.round(pos_1[1] - pos_2[1])) ** 2 +
85
+ (pos_1[2] - pos_2[2] - Math.round(pos_1[2] - pos_2[2])) ** 2;
86
+ export function map_std_to_orig_site_indices(std_positions, std_numbers, input_positions, input_numbers, orig_site_indices_by_input_idx) {
87
+ return std_positions.map((std_pos, std_idx) => {
88
+ const std_number = std_numbers[std_idx];
89
+ let nearest_input_idx = -1;
90
+ let nearest_sq_dist = Infinity;
91
+ for (let input_idx = 0; input_idx < input_positions.length; input_idx += 1) {
92
+ if (input_numbers[input_idx] !== std_number)
93
+ continue;
94
+ const sq_dist = fractional_sq_dist(std_pos, input_positions[input_idx]);
95
+ if (sq_dist < nearest_sq_dist) {
96
+ nearest_sq_dist = sq_dist;
97
+ nearest_input_idx = input_idx;
98
+ }
44
99
  }
45
- return num;
100
+ if (nearest_input_idx === -1)
101
+ return [];
102
+ return orig_site_indices_by_input_idx[nearest_input_idx] ?? [];
46
103
  });
47
- const cell = { lattice: { basis }, positions, numbers };
48
- return JSON.stringify(cell);
49
104
  }
50
105
  export async function analyze_structure_symmetry(struct_or_mol, settings) {
51
106
  await ensure_moyo_wasm_ready();
52
107
  if (!(`lattice` in struct_or_mol)) {
53
108
  throw new Error(`Symmetry analysis requires a periodic structure with a lattice`);
54
109
  }
55
- const cell_json = to_cell_json(struct_or_mol);
110
+ const moyo_input_cell = build_moyo_input_cell(struct_or_mol);
111
+ const cell_json = JSON.stringify(build_moyo_cell(struct_or_mol, moyo_input_cell.positions, moyo_input_cell.numbers));
56
112
  const { symprec, algo } = { ...default_sym_settings, ...settings };
57
113
  // Map "Moyo" to "Standard" for moyo-wasm
58
114
  const moyo_algo = algo === `Moyo` ? `Standard` : algo;
59
- return analyze_cell(cell_json, symprec, moyo_algo);
115
+ const sym_data = analyze_cell(cell_json, symprec, moyo_algo);
116
+ const orig_site_indices_by_std_idx = map_std_to_orig_site_indices(sym_data.std_cell.positions, sym_data.std_cell.numbers, moyo_input_cell.positions, moyo_input_cell.numbers, moyo_input_cell.orig_site_indices_by_input_idx);
117
+ return { ...sym_data, orig_site_indices_by_std_idx };
60
118
  }
61
119
  // Helper function to score coordinate simplicity for Wyckoff table
62
120
  export function simplicity_score(vec) {
@@ -72,7 +130,7 @@ export function wyckoff_positions_from_moyo(sym_data) {
72
130
  if (!sym_data)
73
131
  return [];
74
132
  const { positions, numbers } = sym_data.std_cell;
75
- const { wyckoffs, orig_indices } = sym_data;
133
+ const { wyckoffs, orig_indices, orig_site_indices_by_std_idx } = sym_data;
76
134
  // Group sites by letter-element combination and track all indices
77
135
  const groups = new Map();
78
136
  // Process all atoms in the standardized cell
@@ -97,11 +155,18 @@ export function wyckoff_positions_from_moyo(sym_data) {
97
155
  return score < best.score ? { pos, score } : best;
98
156
  }, { pos: positions[0], score: simplicity_score(positions[0]) }).pos;
99
157
  // Map standardized cell indices back to original structure indices
100
- const orig_site_indices = orig_indices
101
- ? indices.map((i) => orig_indices[i]).filter((idx) => idx !== undefined)
102
- : indices;
158
+ const orig_site_indices = orig_site_indices_by_std_idx
159
+ ? indices.flatMap((std_idx) => orig_site_indices_by_std_idx[std_idx] ?? [])
160
+ : orig_indices
161
+ ? indices.map((std_idx) => orig_indices[std_idx]).filter((idx) => idx !== undefined)
162
+ : indices;
103
163
  const wyckoff = letter ? `${indices.length}${letter}` : `1`;
104
- return { wyckoff, elem, abc: best_pos, site_indices: orig_site_indices };
164
+ return {
165
+ wyckoff,
166
+ elem,
167
+ abc: best_pos,
168
+ site_indices: [...new Set(orig_site_indices)].sort((idx_a, idx_b) => idx_a - idx_b),
169
+ };
105
170
  });
106
171
  rows.sort((w1, w2) => {
107
172
  const [w1_mult, w2_mult] = [parseInt(w1.wyckoff), parseInt(w2.wyckoff)];