matterviz 0.4.1 → 0.4.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 (395) hide show
  1. package/dist/EmptyState.svelte +10 -4
  2. package/dist/FilePicker.svelte +12 -14
  3. package/dist/Icon.svelte +7 -4
  4. package/dist/MillerIndexInput.svelte +2 -2
  5. package/dist/app.css +53 -0
  6. package/dist/brillouin/BrillouinZone.svelte +75 -85
  7. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
  8. package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
  9. package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
  11. package/dist/brillouin/BrillouinZoneScene.svelte +26 -39
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  13. package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
  14. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +62 -74
  15. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
  16. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +511 -547
  17. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
  18. package/dist/chempot-diagram/async-compute.svelte.js +1 -1
  19. package/dist/chempot-diagram/controls-state.svelte.js +1 -1
  20. package/dist/colors/index.d.ts +0 -4
  21. package/dist/colors/index.js +19 -20
  22. package/dist/composition/BarChart.svelte +37 -31
  23. package/dist/composition/BubbleChart.svelte +17 -17
  24. package/dist/composition/Composition.svelte +7 -11
  25. package/dist/composition/Formula.svelte +18 -25
  26. package/dist/composition/FormulaFilter.svelte +92 -95
  27. package/dist/composition/PieChart.svelte +55 -41
  28. package/dist/composition/format.js +1 -1
  29. package/dist/composition/index.d.ts +2 -0
  30. package/dist/composition/index.js +14 -0
  31. package/dist/composition/parse.d.ts +2 -2
  32. package/dist/composition/parse.js +11 -10
  33. package/dist/constants.js +11 -9
  34. package/dist/convex-hull/ConvexHull.svelte +12 -15
  35. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  36. package/dist/convex-hull/ConvexHull2D.svelte +93 -92
  37. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  38. package/dist/convex-hull/ConvexHull3D.svelte +109 -140
  39. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  40. package/dist/convex-hull/ConvexHull4D.svelte +75 -94
  41. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  42. package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
  43. package/dist/convex-hull/ConvexHullControls.svelte +46 -48
  44. package/dist/convex-hull/ConvexHullStats.svelte +193 -195
  45. package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
  46. package/dist/convex-hull/GasPressureControls.svelte +5 -12
  47. package/dist/convex-hull/StructurePopup.svelte +17 -21
  48. package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
  49. package/dist/convex-hull/TemperatureSlider.svelte +4 -10
  50. package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
  51. package/dist/convex-hull/helpers.d.ts +8 -10
  52. package/dist/convex-hull/helpers.js +36 -36
  53. package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
  54. package/dist/convex-hull/thermodynamics.js +18 -17
  55. package/dist/coordination/CoordinationBarPlot.svelte +38 -33
  56. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  57. package/dist/coordination/calc-coordination.d.ts +1 -1
  58. package/dist/coordination/calc-coordination.js +28 -25
  59. package/dist/element/BohrAtom.svelte +5 -8
  60. package/dist/element/ElementHeading.svelte +4 -1
  61. package/dist/element/ElementPhoto.svelte +11 -9
  62. package/dist/element/ElementStats.svelte +12 -10
  63. package/dist/element/ElementTile.svelte +24 -29
  64. package/dist/element/Nucleus.svelte +4 -2
  65. package/dist/element/data.d.ts +1 -2
  66. package/dist/element/helpers.d.ts +4 -0
  67. package/dist/element/helpers.js +18 -0
  68. package/dist/element/index.d.ts +1 -4
  69. package/dist/element/index.js +3 -18
  70. package/dist/feedback/DragOverlay.svelte +5 -5
  71. package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
  72. package/dist/feedback/StatusMessage.svelte +7 -4
  73. package/dist/fermi-surface/FermiSlice.svelte +21 -18
  74. package/dist/fermi-surface/FermiSurface.svelte +14 -22
  75. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -7
  76. package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
  77. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  78. package/dist/fermi-surface/FermiSurfaceScene.svelte +37 -64
  79. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  80. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
  81. package/dist/fermi-surface/compute.js +3 -3
  82. package/dist/fermi-surface/parse.js +22 -24
  83. package/dist/heatmap-matrix/HeatmapMatrix.svelte +109 -148
  84. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
  85. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
  86. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  87. package/dist/heatmap-matrix/index.d.ts +5 -0
  88. package/dist/icons.d.ts +4 -0
  89. package/dist/icons.js +4 -0
  90. package/dist/io/ExportPane.svelte +10 -6
  91. package/dist/io/decompress.d.ts +2 -1
  92. package/dist/io/decompress.js +22 -35
  93. package/dist/io/export.d.ts +4 -0
  94. package/dist/io/export.js +5 -1
  95. package/dist/io/fetch.d.ts +0 -3
  96. package/dist/io/fetch.js +0 -11
  97. package/dist/io/index.d.ts +0 -1
  98. package/dist/io/index.js +2 -9
  99. package/dist/io/is-binary.d.ts +9 -0
  100. package/dist/io/is-binary.js +45 -1
  101. package/dist/io/url-drop.js +13 -25
  102. package/dist/isosurface/Isosurface.svelte +17 -23
  103. package/dist/isosurface/IsosurfaceControls.svelte +37 -42
  104. package/dist/isosurface/parse-vaspwave.d.ts +3 -0
  105. package/dist/isosurface/parse-vaspwave.js +138 -0
  106. package/dist/isosurface/parse.js +6 -6
  107. package/dist/labels.d.ts +6 -2
  108. package/dist/labels.js +43 -13
  109. package/dist/layout/FullscreenButton.svelte +2 -1
  110. package/dist/layout/FullscreenToggle.svelte +10 -4
  111. package/dist/layout/InfoCard.svelte +7 -18
  112. package/dist/layout/InfoTag.svelte +1 -3
  113. package/dist/layout/NumberRangeInput.svelte +46 -0
  114. package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
  115. package/dist/layout/PropertyFilter.svelte +2 -6
  116. package/dist/layout/SettingsSection.svelte +21 -17
  117. package/dist/layout/SubpageGrid.svelte +3 -1
  118. package/dist/layout/ViewerChrome.svelte +9 -10
  119. package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
  120. package/dist/layout/fullscreen.js +3 -5
  121. package/dist/layout/fullscreen.svelte.js +1 -2
  122. package/dist/layout/index.d.ts +1 -0
  123. package/dist/layout/index.js +1 -0
  124. package/dist/layout/json-tree/JsonNode.svelte +8 -11
  125. package/dist/layout/json-tree/JsonTree.svelte +53 -77
  126. package/dist/layout/json-tree/JsonValue.svelte +7 -15
  127. package/dist/layout/json-tree/utils.js +14 -23
  128. package/dist/math.d.ts +2 -0
  129. package/dist/math.js +19 -0
  130. package/dist/overlays/ContextMenu.svelte +4 -2
  131. package/dist/overlays/CopyButton.svelte +11 -5
  132. package/dist/overlays/DragControlTab.svelte +6 -1
  133. package/dist/overlays/DraggablePane.svelte +81 -26
  134. package/dist/overlays/DraggablePane.svelte.d.ts +1 -0
  135. package/dist/overlays/GlassChip.svelte +30 -0
  136. package/dist/overlays/GlassChip.svelte.d.ts +8 -0
  137. package/dist/overlays/InfoPaneCards.svelte +11 -23
  138. package/dist/overlays/index.d.ts +7 -0
  139. package/dist/overlays/index.js +26 -0
  140. package/dist/overlays/portal.d.ts +2 -0
  141. package/dist/overlays/portal.js +14 -0
  142. package/dist/periodic-table/PeriodicTable.svelte +113 -145
  143. package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
  144. package/dist/periodic-table/PeriodicTableControls.svelte +11 -63
  145. package/dist/periodic-table/TableInset.svelte +9 -4
  146. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +50 -71
  147. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
  148. package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
  149. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  150. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  151. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
  152. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
  153. package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
  154. package/dist/phase-diagram/build-diagram.js +3 -3
  155. package/dist/phase-diagram/diagram-input.d.ts +1 -2
  156. package/dist/phase-diagram/parse.js +15 -15
  157. package/dist/phase-diagram/svg-to-diagram.js +21 -15
  158. package/dist/phase-diagram/utils.js +2 -3
  159. package/dist/plot/bar/BarPlot.svelte +430 -391
  160. package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
  161. package/dist/plot/bar/BarPlotControls.svelte +1 -3
  162. package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
  163. package/dist/plot/bar/SpacegroupBarPlot.svelte +29 -36
  164. package/dist/plot/bar/data.d.ts +5 -4
  165. package/dist/plot/bar/data.js +2 -1
  166. package/dist/plot/box/BoxPlot.svelte +301 -209
  167. package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
  168. package/dist/plot/box/Violin.svelte.d.ts +1 -0
  169. package/dist/plot/box/box-plot.js +1 -1
  170. package/dist/plot/core/auto-place.js +1 -1
  171. package/dist/plot/core/axis-utils.d.ts +12 -7
  172. package/dist/plot/core/axis-utils.js +48 -48
  173. package/dist/plot/core/components/AxisLabel.svelte +82 -20
  174. package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
  175. package/dist/plot/core/components/ColorBar.svelte +101 -106
  176. package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
  177. package/dist/plot/core/components/FillArea.svelte +10 -17
  178. package/dist/plot/core/components/HierarchyControls.svelte +249 -0
  179. package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
  180. package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
  181. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
  182. package/dist/plot/core/components/Line.svelte +18 -9
  183. package/dist/plot/core/components/Line.svelte.d.ts +2 -0
  184. package/dist/plot/core/components/PlotAxis.svelte +40 -17
  185. package/dist/plot/core/components/PlotControls.svelte +65 -41
  186. package/dist/plot/core/components/PlotLegend.svelte +31 -35
  187. package/dist/plot/core/components/PlotMarginals.svelte +616 -0
  188. package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
  189. package/dist/plot/core/components/PlotTooltip.svelte +1 -1
  190. package/dist/plot/core/components/PortalSelect.svelte +4 -8
  191. package/dist/plot/core/components/ReferenceLine.svelte +27 -24
  192. package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
  193. package/dist/plot/core/components/ReferencePlane.svelte +7 -8
  194. package/dist/plot/core/components/ZeroLines.svelte +4 -8
  195. package/dist/plot/core/components/ZoomRect.svelte +1 -2
  196. package/dist/plot/core/components/index.d.ts +1 -0
  197. package/dist/plot/core/components/index.js +1 -0
  198. package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
  199. package/dist/plot/core/data-cleaning-signal.js +467 -0
  200. package/dist/plot/core/data-cleaning.d.ts +2 -42
  201. package/dist/plot/core/data-cleaning.js +27 -474
  202. package/dist/plot/core/data-transform.js +9 -5
  203. package/dist/plot/core/fill-utils.d.ts +3 -1
  204. package/dist/plot/core/fill-utils.js +28 -6
  205. package/dist/plot/core/index.d.ts +1 -0
  206. package/dist/plot/core/index.js +1 -0
  207. package/dist/plot/core/interactions.js +0 -2
  208. package/dist/plot/core/layout.d.ts +3 -1
  209. package/dist/plot/core/layout.js +22 -13
  210. package/dist/plot/core/marginals.d.ts +170 -0
  211. package/dist/plot/core/marginals.js +436 -0
  212. package/dist/plot/core/reference-line.js +1 -1
  213. package/dist/plot/core/scales.d.ts +23 -0
  214. package/dist/plot/core/scales.js +39 -13
  215. package/dist/plot/core/types/fills.d.ts +101 -0
  216. package/dist/plot/core/types/fills.js +15 -0
  217. package/dist/plot/core/types/plot-3d.d.ts +87 -0
  218. package/dist/plot/core/types/plot-3d.js +4 -0
  219. package/dist/plot/core/types/reference-lines.d.ts +140 -0
  220. package/dist/plot/core/types/reference-lines.js +9 -0
  221. package/dist/plot/core/types.d.ts +29 -324
  222. package/dist/plot/core/types.js +23 -20
  223. package/dist/plot/core/utils/hierarchy-chart.d.ts +51 -0
  224. package/dist/plot/core/utils/hierarchy-chart.js +179 -0
  225. package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
  226. package/dist/plot/core/utils/hierarchy-labels.js +99 -0
  227. package/dist/plot/core/utils/label-placement.js +2 -2
  228. package/dist/plot/histogram/Histogram.svelte +304 -325
  229. package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
  230. package/dist/plot/histogram/HistogramControls.svelte +30 -28
  231. package/dist/plot/histogram/histogram.d.ts +31 -0
  232. package/dist/plot/histogram/histogram.js +70 -0
  233. package/dist/plot/histogram/index.d.ts +1 -0
  234. package/dist/plot/histogram/index.js +1 -0
  235. package/dist/plot/index.d.ts +1 -0
  236. package/dist/plot/index.js +1 -0
  237. package/dist/plot/sankey/Sankey.svelte +78 -69
  238. package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
  239. package/dist/plot/sankey/SankeyControls.svelte +22 -16
  240. package/dist/plot/scatter/BinnedScatterPlot.svelte +263 -109
  241. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
  242. package/dist/plot/scatter/ElementScatter.svelte +4 -1
  243. package/dist/plot/scatter/ScatterPlot.svelte +477 -385
  244. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
  245. package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
  246. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  247. package/dist/plot/scatter/ScatterPoint.svelte +16 -9
  248. package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
  249. package/dist/plot/scatter/binned-scatter-types.js +6 -1
  250. package/dist/plot/scatter/scatter-data.d.ts +1 -1
  251. package/dist/plot/scatter/scatter-data.js +8 -7
  252. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
  253. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
  254. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
  255. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
  256. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
  257. package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
  258. package/dist/plot/sunburst/Sunburst.svelte +251 -329
  259. package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
  260. package/dist/plot/sunburst/index.d.ts +1 -1
  261. package/dist/plot/sunburst/index.js +2 -1
  262. package/dist/plot/sunburst/render.d.ts +2 -2
  263. package/dist/plot/sunburst/render.js +47 -22
  264. package/dist/plot/sunburst/sunburst.d.ts +5 -1
  265. package/dist/plot/sunburst/sunburst.js +10 -3
  266. package/dist/plot/treemap/Treemap.svelte +907 -0
  267. package/dist/plot/treemap/Treemap.svelte.d.ts +84 -0
  268. package/dist/plot/treemap/index.d.ts +3 -0
  269. package/dist/plot/treemap/index.js +4 -0
  270. package/dist/plot/treemap/treemap.d.ts +26 -0
  271. package/dist/plot/treemap/treemap.js +99 -0
  272. package/dist/rdf/RdfPlot.svelte +22 -26
  273. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  274. package/dist/rdf/calc-rdf.js +4 -8
  275. package/dist/sanitize.js +1 -1
  276. package/dist/scene/bind-renderer.svelte.js +14 -6
  277. package/dist/settings.d.ts +19 -9
  278. package/dist/settings.js +77 -50
  279. package/dist/spectral/Bands.svelte +223 -230
  280. package/dist/spectral/BandsAndDos.svelte +11 -9
  281. package/dist/spectral/BrillouinBandsDos.svelte +19 -27
  282. package/dist/spectral/Dos.svelte +70 -73
  283. package/dist/spectral/helpers.d.ts +3 -3
  284. package/dist/spectral/helpers.js +25 -35
  285. package/dist/state.svelte.js +5 -18
  286. package/dist/structure/Arrow.svelte +5 -18
  287. package/dist/structure/ArrowInstances.svelte +160 -0
  288. package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
  289. package/dist/structure/AtomLegend.svelte +115 -75
  290. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  291. package/dist/structure/Bond.svelte +31 -23
  292. package/dist/structure/CanvasTooltip.svelte +15 -3
  293. package/dist/structure/CellSelect.svelte +11 -26
  294. package/dist/structure/Cylinder.svelte +24 -40
  295. package/dist/structure/InstancedAtoms.svelte +114 -0
  296. package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
  297. package/dist/structure/Lattice.svelte +85 -149
  298. package/dist/structure/SiteLabels.svelte +122 -0
  299. package/dist/structure/SiteLabels.svelte.d.ts +18 -0
  300. package/dist/structure/Structure.svelte +473 -419
  301. package/dist/structure/Structure.svelte.d.ts +4 -2
  302. package/dist/structure/StructureCarousel.svelte +630 -0
  303. package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
  304. package/dist/structure/StructureControls.svelte +157 -399
  305. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  306. package/dist/structure/StructureExportPane.svelte +17 -21
  307. package/dist/structure/StructureInfoPane.svelte +72 -66
  308. package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
  309. package/dist/structure/StructureScene.svelte +467 -476
  310. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  311. package/dist/structure/StructureViewport.svelte +386 -0
  312. package/dist/structure/StructureViewport.svelte.d.ts +63 -0
  313. package/dist/structure/atom-label-placement.d.ts +16 -12
  314. package/dist/structure/atom-label-placement.js +70 -37
  315. package/dist/structure/atom-properties.d.ts +2 -0
  316. package/dist/structure/atom-properties.js +104 -38
  317. package/dist/structure/bonding.d.ts +10 -3
  318. package/dist/structure/bonding.js +84 -28
  319. package/dist/structure/edit-history.d.ts +6 -0
  320. package/dist/structure/edit-history.js +27 -0
  321. package/dist/structure/export.d.ts +1 -0
  322. package/dist/structure/export.js +39 -31
  323. package/dist/structure/format-detect.d.ts +1 -0
  324. package/dist/structure/format-detect.js +22 -0
  325. package/dist/structure/geometry.d.ts +9 -0
  326. package/dist/structure/geometry.js +27 -0
  327. package/dist/structure/index.d.ts +16 -1
  328. package/dist/structure/index.js +13 -2
  329. package/dist/structure/measure.d.ts +1 -1
  330. package/dist/structure/measure.js +6 -12
  331. package/dist/structure/parse.d.ts +2 -1
  332. package/dist/structure/parse.js +243 -222
  333. package/dist/structure/partial-occupancy.js +4 -2
  334. package/dist/structure/pbc.js +78 -98
  335. package/dist/structure/polyhedra.d.ts +0 -1
  336. package/dist/structure/polyhedra.js +3 -18
  337. package/dist/structure/supercell.d.ts +1 -2
  338. package/dist/structure/supercell.js +7 -13
  339. package/dist/symmetry/SymmetryElements.svelte +11 -15
  340. package/dist/symmetry/SymmetryStats.svelte +56 -68
  341. package/dist/symmetry/WyckoffTable.svelte +5 -24
  342. package/dist/symmetry/cell-transform.js +1 -0
  343. package/dist/symmetry/index.d.ts +1 -0
  344. package/dist/symmetry/index.js +8 -2
  345. package/dist/symmetry/spacegroups.js +3 -2
  346. package/dist/table/HeatmapTable.svelte +1102 -365
  347. package/dist/table/HeatmapTable.svelte.d.ts +12 -2
  348. package/dist/table/ToggleMenu.svelte +5 -12
  349. package/dist/table/index.d.ts +21 -6
  350. package/dist/table/index.js +34 -21
  351. package/dist/theme/ThemeControl.svelte +16 -8
  352. package/dist/theme/index.d.ts +1 -5
  353. package/dist/theme/index.js +4 -10
  354. package/dist/theme/themes.mjs +14 -7
  355. package/dist/tooltip/KCoords.svelte +4 -1
  356. package/dist/tooltip/TooltipContent.svelte +2 -5
  357. package/dist/trajectory/Trajectory.svelte +515 -308
  358. package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
  359. package/dist/trajectory/TrajectoryError.svelte +11 -8
  360. package/dist/trajectory/TrajectoryExportPane.svelte +14 -31
  361. package/dist/trajectory/TrajectoryInfoPane.svelte +85 -97
  362. package/dist/trajectory/extract.js +56 -34
  363. package/dist/trajectory/format-detect.js +11 -9
  364. package/dist/trajectory/frame-reader.d.ts +2 -0
  365. package/dist/trajectory/frame-reader.js +33 -29
  366. package/dist/trajectory/helpers.d.ts +2 -0
  367. package/dist/trajectory/helpers.js +18 -7
  368. package/dist/trajectory/index.d.ts +1 -0
  369. package/dist/trajectory/index.js +3 -0
  370. package/dist/trajectory/parse/h5-utils.d.ts +13 -0
  371. package/dist/trajectory/parse/h5-utils.js +109 -0
  372. package/dist/trajectory/parse/hdf5.d.ts +1 -1
  373. package/dist/trajectory/parse/hdf5.js +116 -106
  374. package/dist/trajectory/parse/index.d.ts +2 -3
  375. package/dist/trajectory/parse/index.js +17 -20
  376. package/dist/trajectory/parse/lammps.js +8 -12
  377. package/dist/trajectory/parse/vasp.js +10 -7
  378. package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
  379. package/dist/trajectory/parse/vaspout-electronic.js +207 -0
  380. package/dist/trajectory/parse/vaspout-h5.d.ts +5 -0
  381. package/dist/trajectory/parse/vaspout-h5.js +284 -0
  382. package/dist/trajectory/parse/xyz.js +11 -7
  383. package/dist/trajectory/plotting.d.ts +8 -9
  384. package/dist/trajectory/plotting.js +99 -27
  385. package/dist/utils.d.ts +3 -0
  386. package/dist/utils.js +7 -0
  387. package/dist/xrd/XrdPlot.svelte +47 -61
  388. package/dist/xrd/broadening.js +2 -1
  389. package/dist/xrd/calc-xrd.js +2 -4
  390. package/dist/xrd/parse.d.ts +0 -1
  391. package/dist/xrd/parse.js +56 -73
  392. package/package.json +61 -33
  393. package/dist/api/mp.d.ts +0 -6
  394. package/dist/api/mp.js +0 -22
  395. package/dist/plot/sunburst/SunburstControls.svelte +0 -200
@@ -1,12 +1,13 @@
1
- import { COMPRESSION_EXTENSIONS_REGEX, CONFIG_DIRS_REGEX, STRUCT_KEYWORDS_REGEX, STRUCT_KEYWORDS_STRICT_REGEX, STRUCTURE_EXTENSIONS_REGEX, TRAJ_KEYWORDS_REGEX, VASP_FILES_REGEX, XYZ_EXTXYZ_REGEX, } from '../constants';
2
- import { FALLBACK_ELEMENTS, is_elem_symbol } from '../element';
3
- import { strip_compression_extensions } from '../io';
1
+ import { XYZ_EXTXYZ_REGEX } from '../constants';
2
+ import { FALLBACK_ELEMENTS, is_elem_symbol } from '../element/helpers';
3
+ import { strip_compression_extensions } from '../io/decompress';
4
4
  import * as math from '../math';
5
5
  import { wrap_to_unit_cell } from './pbc';
6
6
  import { make_site } from './site';
7
- import { iter_xyz_frames } from '../trajectory/helpers';
8
- import { normalize_scientific_notation, to_error } from '../utils';
7
+ import { is_xyz_atom_line, iter_xyz_frames } from '../trajectory/helpers';
8
+ import { normalize_scientific_notation, parse_leading_num, parse_num_token, to_error, } from '../utils';
9
9
  import { load as yaml_load } from 'js-yaml';
10
+ export { is_structure_file } from './format-detect';
10
11
  // === Parse error contract ===
11
12
  // Individual format parsers (parse_poscar, parse_cif, parse_xyz, parse_phonopy_yaml,
12
13
  // parse_optimade_json, ...) return `T | null` on failure and record failure reasons in a
@@ -37,7 +38,35 @@ const aggregate_parse_error = (filename) => {
37
38
  };
38
39
  const cif_coords_key = (coords) => `${coords[0].toFixed(6)},${coords[1].toFixed(6)},${coords[2].toFixed(6)}`;
39
40
  const cif_site_key = (element, abc, label) => `${element}|${label}|${cif_coords_key(abc)}`;
40
- const clone_structure_properties = (properties) => structuredClone(properties);
41
+ // Bravais lattice centering translations (excluding the identity) keyed by the
42
+ // leading letter of a space-group Hermann-Mauguin symbol. R is the obverse
43
+ // hexagonal setting.
44
+ const CENTERING_VECTORS = {
45
+ P: [],
46
+ I: [[0.5, 0.5, 0.5]],
47
+ F: [
48
+ [0, 0.5, 0.5],
49
+ [0.5, 0, 0.5],
50
+ [0.5, 0.5, 0],
51
+ ],
52
+ A: [[0, 0.5, 0.5]],
53
+ B: [[0.5, 0, 0.5]],
54
+ C: [[0.5, 0.5, 0]],
55
+ R: [
56
+ [2 / 3, 1 / 3, 1 / 3],
57
+ [1 / 3, 2 / 3, 2 / 3],
58
+ ],
59
+ };
60
+ // Detect the centering letter from a CIF's space-group H-M symbol, if present.
61
+ const extract_cif_centering = (text) => {
62
+ for (const line of text.split(`\n`)) {
63
+ const match = /^_(?:symmetry_space_group_name_h-m|space_group_name_h-m(?:_alt)?)\s+(?<symbol>.+)/i.exec(line.trim());
64
+ const letter = match?.groups?.symbol.replaceAll(/['"]/g, ``).trim()[0]?.toUpperCase();
65
+ if (letter && letter in CENTERING_VECTORS)
66
+ return letter;
67
+ }
68
+ return null;
69
+ };
41
70
  const vec3_from_values = (values, context) => {
42
71
  if (values?.length !== 3) {
43
72
  throw new Error(`Invalid ${context}: expected 3 coordinates, got ${values?.length ?? 0}`);
@@ -56,7 +85,7 @@ const vec3_from_values = (values, context) => {
56
85
  // Parse a coordinate value that might be in various scientific notation formats
57
86
  function parse_coordinate(str) {
58
87
  const normalized = normalize_scientific_notation(str.trim());
59
- const value = parseFloat(normalized);
88
+ const value = Number(normalized);
60
89
  if (isNaN(value))
61
90
  throw new Error(`Invalid coordinate value: ${str}`);
62
91
  return value;
@@ -70,9 +99,9 @@ function parse_coordinate_line(line) {
70
99
  const sanitized = line
71
100
  .trim()
72
101
  // Add space when '-' follows a digit and precedes a digit or dot
73
- .replaceAll(/(\d)-(?=[\d.])/g, `$1 -`)
102
+ .replaceAll(/(?<digit>\d)-(?=[\d.])/g, `$1 -`)
74
103
  // Revert accidental spaces after exponent markers
75
- .replaceAll(/([eE])\s-\s/g, `$1-`);
104
+ .replaceAll(/(?<exp_marker>[eE])\s-\s/g, `$1-`);
76
105
  tokens = sanitized.split(/\s+/);
77
106
  }
78
107
  if (tokens.length < 3)
@@ -114,14 +143,6 @@ function resolve_optimade_element(species_name, species_list, index) {
114
143
  return { symbol: stripped, sym_idx: -1 };
115
144
  return { symbol: validate_element_symbol(species_name, index), sym_idx: -1 };
116
145
  }
117
- const try_create_cart_to_frac = (lattice_matrix) => {
118
- try {
119
- return math.create_cart_to_frac(lattice_matrix);
120
- }
121
- catch {
122
- return null;
123
- }
124
- };
125
146
  const approximate_cart_to_frac = (xyz, axis_lengths) => [
126
147
  Math.abs(axis_lengths[0]) > math.EPS ? xyz[0] / axis_lengths[0] : 0,
127
148
  Math.abs(axis_lengths[1]) > math.EPS ? xyz[1] / axis_lengths[1] : 0,
@@ -136,9 +157,12 @@ const matrix3x3_from_rows = (rows, context) => [
136
157
  // cart→frac converter that falls back to per-axis-length division for singular lattices.
137
158
  // axis_lengths defaults to the row norms of the lattice matrix.
138
159
  const cart_to_frac_with_fallback = (matrix, axis_lengths) => {
139
- const exact_converter = try_create_cart_to_frac(matrix);
140
- if (exact_converter)
141
- return { convert: exact_converter, exact: true };
160
+ try {
161
+ return { convert: math.create_cart_to_frac(matrix), exact: true };
162
+ }
163
+ catch {
164
+ // fall through to the per-axis-length approximation below
165
+ }
142
166
  const lengths = axis_lengths ?? [
143
167
  Math.hypot(...matrix[0]),
144
168
  Math.hypot(...matrix[1]),
@@ -193,9 +217,7 @@ export function parse_poscar(content) {
193
217
  let atom_counts = [];
194
218
  // Detect if this is VASP 5+ format (has element symbols)
195
219
  // Try to parse the first token as a number - if it succeeds, it's VASP 4 format
196
- const first_token = lines[line_index].trim().split(/\s+/)[0];
197
- const first_token_as_number = parseInt(first_token, 10);
198
- const has_element_symbols = isNaN(first_token_as_number);
220
+ const has_element_symbols = isNaN(parse_leading_num(lines[line_index]));
199
221
  if (has_element_symbols) {
200
222
  // VASP 5+ format - parse element symbols (may span multiple lines)
201
223
  let symbol_lines = 1;
@@ -203,9 +225,7 @@ export function parse_poscar(content) {
203
225
  for (let lookahead_idx = 1; lookahead_idx < 10; lookahead_idx++) {
204
226
  if (line_index + lookahead_idx >= lines.length)
205
227
  break;
206
- const next_line_first_token = lines[line_index + lookahead_idx].trim().split(/\s+/)[0];
207
- const next_token_as_number = parseInt(next_line_first_token, 10);
208
- if (!isNaN(next_token_as_number)) {
228
+ if (!isNaN(parse_leading_num(lines[line_index + lookahead_idx]))) {
209
229
  symbol_lines = lookahead_idx;
210
230
  break;
211
231
  }
@@ -329,8 +349,9 @@ export function parse_xyz(content) {
329
349
  diag_error(`XYZ frame too short`);
330
350
  return null;
331
351
  }
332
- // Parse number of atoms (line 1)
333
- const num_atoms = parseInt(lines[0].trim(), 10);
352
+ // Parse number of atoms (line 1). Only the first token counts: Tinker-style
353
+ // XYZ files put a title after the count (e.g. `6 methane`)
354
+ const num_atoms = Math.trunc(parse_leading_num(lines[0]));
334
355
  if (isNaN(num_atoms) || num_atoms <= 0) {
335
356
  diag_error(`Invalid number of atoms in XYZ file`);
336
357
  return null;
@@ -339,7 +360,7 @@ export function parse_xyz(content) {
339
360
  const comment_line = lines[1];
340
361
  let lattice;
341
362
  // Check for extended XYZ lattice information in comment line
342
- const lattice_match = /Lattice="([^"]+)"/.exec(comment_line);
363
+ const lattice_match = /Lattice="(?<lattice>[^"]+)"/.exec(comment_line);
343
364
  if (lattice_match) {
344
365
  const lattice_values = lattice_match[1].split(/\s+/).map(parse_coordinate);
345
366
  if (lattice_values.length === 9) {
@@ -407,8 +428,7 @@ const parse_symmetry_expression = (expr_input) => {
407
428
  // E.g., "x-y+1/3" → ["x", "-y", "+1/3"] or "-x+y" → ["-x", "+y"]
408
429
  const tokens = [];
409
430
  let current_token = ``;
410
- for (let idx = 0; idx < expr.length; idx++) {
411
- const char = expr[idx];
431
+ for (const char of expr) {
412
432
  if ((char === `+` || char === `-`) && current_token.length > 0) {
413
433
  tokens.push(current_token);
414
434
  current_token = char;
@@ -421,7 +441,7 @@ const parse_symmetry_expression = (expr_input) => {
421
441
  tokens.push(current_token);
422
442
  for (const token of tokens) {
423
443
  // Check if this token is a variable term (x, y, or z with optional sign)
424
- const var_match = /^([+-]?)([xyz])$/.exec(token);
444
+ const var_match = /^(?<sign>[+-]?)(?<axis>[xyz])$/.exec(token);
425
445
  if (var_match) {
426
446
  const sign = var_match[1] === `-` ? -1 : 1;
427
447
  const var_char = var_match[2];
@@ -447,8 +467,8 @@ const parse_symmetry_expression = (expr_input) => {
447
467
  // Fraction
448
468
  const parts = num_str.split(`/`);
449
469
  if (parts.length === 2) {
450
- const numerator = parseFloat(parts[0]);
451
- const denominator = parseFloat(parts[1]);
470
+ const numerator = Number(parts[0]);
471
+ const denominator = Number(parts[1]);
452
472
  if (!isNaN(numerator) && !isNaN(denominator) && denominator !== 0) {
453
473
  translation += sign * (numerator / denominator);
454
474
  }
@@ -456,7 +476,7 @@ const parse_symmetry_expression = (expr_input) => {
456
476
  }
457
477
  else {
458
478
  // Integer or decimal
459
- const val = parseFloat(num_str);
479
+ const val = Number(num_str);
460
480
  if (!isNaN(val)) {
461
481
  translation += sign * val;
462
482
  }
@@ -464,49 +484,50 @@ const parse_symmetry_expression = (expr_input) => {
464
484
  }
465
485
  return { coefficients, translation };
466
486
  };
467
- // Apply symmetry operations to generate equivalent positions
468
- const apply_symmetry_ops = (atom, symmetry_ops, wrap_fractional_coords) => {
469
- if (symmetry_ops.length === 0)
487
+ // Apply symmetry operations (and optional lattice-centering translations) to
488
+ // generate all equivalent positions. Deduplication uses 6 decimal places to
489
+ // absorb floating point error from compound ops like x-y, -x+y.
490
+ const apply_symmetry_ops = (atom, symmetry_ops, wrap_fractional_coords, centering = []) => {
491
+ if (symmetry_ops.length === 0 && centering.length === 0)
470
492
  return [atom];
471
493
  const equivalent_atoms = [];
472
494
  const seen = new Set();
473
495
  const wrap = (coords) => wrap_fractional_coords ? wrap_to_unit_cell(coords) : coords;
474
- // Use 6 decimal places for deduplication to handle floating point imprecision
475
- // from compound symmetry operations like x-y, -x+y which can produce small errors
476
- // Always include base atom (optionally wrapped)
477
- const base_coords = wrap(atom.coords);
478
- seen.add(cif_coords_key(base_coords));
479
- equivalent_atoms.push({ ...atom, coords: base_coords });
496
+ // Every generated position is also offset by each centering translation
497
+ const shifts = [[0, 0, 0], ...centering];
498
+ // Record a position plus its centering images, deduplicating on wrapped coords
499
+ const add_position = (coords) => {
500
+ for (const [dx, dy, dz] of shifts) {
501
+ const wrapped = wrap([coords[0] + dx, coords[1] + dy, coords[2] + dz]);
502
+ const key = cif_coords_key(wrapped);
503
+ if (seen.has(key))
504
+ continue;
505
+ seen.add(key);
506
+ const id = equivalent_atoms.length === 0 ? atom.id : `${atom.id}_${equivalent_atoms.length}`;
507
+ equivalent_atoms.push({ ...atom, coords: wrapped, id });
508
+ }
509
+ };
510
+ add_position(atom.coords); // base atom (+ centering images)
511
+ // ops arrive pre-normalized (quotes + whitespace already stripped, see normalized_ops)
480
512
  for (const operation of symmetry_ops) {
481
- const operation_match = /['"]([^'"]+)['"]/.exec(operation);
482
- const expr_str = operation_match ? operation_match[1] : operation.trim();
483
- const parts = expr_str.split(`,`).map((part) => part.trim());
513
+ const parts = operation.split(`,`);
484
514
  if (parts.length !== 3)
485
515
  continue;
486
516
  const new_coords = [0, 0, 0];
487
517
  for (let dim = 0; dim < 3; dim++) {
488
518
  const { coefficients, translation } = parse_symmetry_expression(parts[dim]);
489
- // Apply: new_coord = coeff_x * x + coeff_y * y + coeff_z * z + translation
490
- new_coords[dim] =
491
- coefficients[0] * atom.coords[0] +
492
- coefficients[1] * atom.coords[1] +
493
- coefficients[2] * atom.coords[2] +
494
- translation;
519
+ // new_coord = coeff_x * x + coeff_y * y + coeff_z * z + translation
520
+ new_coords[dim] = math.dot(coefficients, atom.coords) + translation;
495
521
  }
496
- // Wrap and deduplicate transformed coordinates
497
- const wrapped = wrap(new_coords);
498
- const cache_key = cif_coords_key(wrapped);
499
- if (seen.has(cache_key))
500
- continue;
501
- seen.add(cache_key);
502
- equivalent_atoms.push({
503
- ...atom,
504
- coords: wrapped,
505
- id: `${atom.id}_${equivalent_atoms.length}`,
506
- });
522
+ add_position(new_coords);
507
523
  }
508
524
  return equivalent_atoms;
509
525
  };
526
+ // Parse a CIF numeric token, stripping a trailing uncertainty like "1.234(5)"
527
+ const parse_cif_uncertain_number = (token) => {
528
+ const value = parse_num_token(token.split(`(`)[0]);
529
+ return isNaN(value) ? null : value;
530
+ };
510
531
  const extract_cif_cell_parameters = (text, type, strict = true) => text
511
532
  .split(`\n`)
512
533
  .filter((line) => line.startsWith(`_${type}`))
@@ -519,11 +540,9 @@ const extract_cif_cell_parameters = (text, type, strict = true) => text
519
540
  throw new Error(`Invalid CIF cell parameter line format: ${line}`);
520
541
  return null;
521
542
  }
522
- const value = parseFloat(tokens[1].split(`(`)[0]);
523
- if (isNaN(value)) {
524
- if (strict)
525
- throw new Error(`Invalid CIF cell parameter in line: ${line}`);
526
- return null; // Return null for invalid values in non-strict mode
543
+ const value = parse_cif_uncertain_number(tokens[1]);
544
+ if (value === null && strict) {
545
+ throw new Error(`Invalid CIF cell parameter in line: ${line}`);
527
546
  }
528
547
  return value;
529
548
  })
@@ -551,6 +570,22 @@ const build_cif_atom_site_header_indices = (headers) => {
551
570
  });
552
571
  return indices;
553
572
  };
573
+ // Which coordinate triple a CIF atom-site loop provides (fractional preferred), or null
574
+ const cif_coords_type = (indices) => {
575
+ if (indices.x !== undefined && indices.y !== undefined && indices.z !== undefined) {
576
+ return `fract`;
577
+ }
578
+ if (indices.cart_x !== undefined &&
579
+ indices.cart_y !== undefined &&
580
+ indices.cart_z !== undefined) {
581
+ return `cart`;
582
+ }
583
+ return null;
584
+ };
585
+ // The 3 column indices for the requested coordinate type
586
+ const cif_coord_indices = (indices, coords_type) => coords_type === `fract`
587
+ ? [indices.x, indices.y, indices.z]
588
+ : [indices.cart_x, indices.cart_y, indices.cart_z];
554
589
  // Walk CIF loop_ blocks: yields each loop's header tags plus the index of its first data line
555
590
  function* iter_cif_loops(lines) {
556
591
  for (let idx = 0; idx < lines.length; idx++) {
@@ -571,28 +606,27 @@ const split_cif_tokens = (line) => (line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)
571
606
  // Parse atom data from CIF with robust error handling
572
607
  const parse_cif_atom_data = (raw_data, indices, coords_type) => {
573
608
  const { label = 0, symbol = -1, occupancy = -1 } = indices;
574
- const coord_indices = coords_type === `fract`
575
- ? [indices.x, indices.y, indices.z]
576
- : [indices.cart_x, indices.cart_y, indices.cart_z];
609
+ const coord_indices = cif_coord_indices(indices, coords_type);
577
610
  if (coord_indices.some((idx) => idx === undefined)) {
578
611
  throw new Error(`Missing coordinate indices`);
579
612
  }
580
613
  const coords_triplet = vec3_from_values(coord_indices.map((idx) => {
581
- if (idx === undefined)
582
- throw new Error(`Invalid coordinate index`);
614
+ // idx cannot be undefined: the `.some` guard above already threw
583
615
  const coord_str = raw_data[idx];
584
616
  if (!coord_str)
585
617
  throw new Error(`Missing coordinate at index ${idx}`);
586
- const coord = parseFloat(coord_str.split(`(`)[0]);
587
- if (isNaN(coord))
618
+ const coord = parse_cif_uncertain_number(coord_str);
619
+ if (coord === null)
588
620
  throw new Error(`Invalid coordinate: ${coord_str}`);
589
621
  return coord;
590
622
  }), `CIF atom coordinates`);
591
- const occu = occupancy >= 0 && raw_data[occupancy]
592
- ? parseFloat(raw_data[occupancy].split(`(`)[0]) || 1.0
593
- : 1.0;
594
- const from_symbol = symbol >= 0 ? /^([A-Z][a-z]*)/.exec(raw_data[symbol])?.[1] : undefined;
595
- const element_symbol = from_symbol ?? raw_data[label]?.match(/([A-Z][a-z]*)/g)?.[0];
623
+ const raw_occu = occupancy >= 0 && raw_data[occupancy]
624
+ ? parse_cif_uncertain_number(raw_data[occupancy])
625
+ : null;
626
+ // invalid or zero occupancy defaults to fully occupied
627
+ const occu = raw_occu == null || raw_occu === 0 ? 1.0 : raw_occu;
628
+ const from_symbol = symbol >= 0 ? /^(?<element>[A-Z][a-z]*)/.exec(raw_data[symbol])?.[1] : undefined;
629
+ const element_symbol = from_symbol ?? raw_data[label]?.match(/(?:[A-Z][a-z]*)/g)?.[0];
596
630
  if (!element_symbol) {
597
631
  throw new Error(`Could not extract element symbol from: ${raw_data.join(` `)}`);
598
632
  }
@@ -639,13 +673,7 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
639
673
  continue;
640
674
  // Check if this loop contains coordinate headers
641
675
  const indices_preview = build_cif_atom_site_header_indices(headers);
642
- const has_coords = (indices_preview.x !== undefined &&
643
- indices_preview.y !== undefined &&
644
- indices_preview.z !== undefined) ||
645
- (indices_preview.cart_x !== undefined &&
646
- indices_preview.cart_y !== undefined &&
647
- indices_preview.cart_z !== undefined);
648
- if (!has_coords)
676
+ if (cif_coords_type(indices_preview) === null)
649
677
  continue;
650
678
  // This is the desired atom-site loop with coordinates: collect data lines
651
679
  atom_headers = headers;
@@ -679,23 +707,13 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
679
707
  // Parse atom data with error handling
680
708
  const header_indices = build_cif_atom_site_header_indices(atom_headers);
681
709
  // Determine available coordinate type
682
- const coords_type = header_indices.x !== undefined &&
683
- header_indices.y !== undefined &&
684
- header_indices.z !== undefined
685
- ? `fract`
686
- : header_indices.cart_x !== undefined &&
687
- header_indices.cart_y !== undefined &&
688
- header_indices.cart_z !== undefined
689
- ? `cart`
690
- : null;
710
+ const coords_type = cif_coords_type(header_indices);
691
711
  if (!coords_type) {
692
712
  diag_error(`CIF atom site loop missing coordinates (fract or Cartn)`);
693
713
  return null;
694
714
  }
695
715
  // Collect required coordinate indices
696
- const required_indices = coords_type === `fract`
697
- ? [header_indices.x, header_indices.y, header_indices.z]
698
- : [header_indices.cart_x, header_indices.cart_y, header_indices.cart_z];
716
+ const required_indices = cif_coord_indices(header_indices, coords_type);
699
717
  const atoms = atom_data_lines
700
718
  .map(split_cif_tokens)
701
719
  .filter((tokens) => {
@@ -734,15 +752,9 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
734
752
  const cart_to_frac = cart_to_frac_with_fallback(lattice_matrix, [a, b, c]).convert;
735
753
  // Create sites with coordinate conversion and symmetry operations
736
754
  const wrap_vec3 = (vec) => wrap_fractional_coords ? wrap_to_unit_cell(vec) : vec;
737
- // Apply symmetry operations to generate all equivalent positions
738
- const all_sites = [];
739
- // Normalize symmetry operations (trim/strip quotes) but preserve duplicates; we deduplicate positions later
740
- const normalized_ops = symmetry_ops
741
- .map((op) => /['"]([^'"]+)['"]/.exec(op)?.[1] ?? op.trim())
742
- .map((op) => op.replaceAll(/\s+/g, ``));
743
- // Rely on symmetry operations list for all centering/translations to avoid double-counting
744
- // TODO: Support conventional cells with centering by discovering centering from space group metadata
745
- // when present (e.g. P, I, F, C, R centering types)
755
+ // Strip surrounding quotes and all whitespace (preserving duplicates; positions
756
+ // are deduplicated later). Leaves ops as bare `x,y,z`-style expressions.
757
+ const normalized_ops = symmetry_ops.map((op) => (/['"](?<expr>[^'"]+)['"]/.exec(op)?.groups?.expr ?? op).replaceAll(/\s+/g, ``));
746
758
  // Inspect optional _atom_type_number_in_cell loop to see if atom sites are already expanded
747
759
  const atom_type_counts = {};
748
760
  for (const { headers, data_start } of iter_cif_loops(lines)) {
@@ -760,11 +772,15 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
760
772
  const toks = split_cif_tokens(line);
761
773
  if (toks.length > Math.max(sym_idx, num_idx)) {
762
774
  // Normalize type symbol to bare element (e.g. 'Sn2+' -> 'Sn')
763
- const match = /^([A-Z][a-z]*)/.exec(toks[sym_idx]);
775
+ const match = /^(?<element>[A-Z][a-z]*)/.exec(toks[sym_idx]);
764
776
  const sym = match ? match[1] : toks[sym_idx];
765
- const num = parseInt(toks[num_idx], 10);
766
- if (sym && !Number.isNaN(num))
767
- atom_type_counts[sym] = num;
777
+ // Strip standard-uncertainty parentheses (`8(0)` -> `8`) like other CIF
778
+ // readers; empty prefixes like `(8)` parse as NaN and get skipped
779
+ const num = Math.trunc(parse_num_token(toks[num_idx].split(`(`)[0]));
780
+ // sum rows that normalize to the same element (e.g. Fe2+ and Fe3+ → Fe)
781
+ if (sym && !Number.isNaN(num)) {
782
+ atom_type_counts[sym] = (atom_type_counts[sym] ?? 0) + num;
783
+ }
768
784
  }
769
785
  }
770
786
  break;
@@ -777,38 +793,61 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
777
793
  const already_enumerated = has_expected_counts &&
778
794
  Object.entries(atom_type_counts).every(([el, exp]) => (observed_counts[el] || 0) >= exp);
779
795
  const ops_to_use = already_enumerated ? [] : normalized_ops;
780
- // Global deduplication of final sites (per element + coordinates + label)
781
- // Use 6 decimal places to handle floating point imprecision from compound symmetry ops
782
- const seen_site_keys = new Set();
783
- for (const atom of atoms) {
784
- const element = validate_element_symbol(atom.element, all_sites.length);
785
- // Convert to fractional coordinates if needed
786
- let fractional_atom;
787
- if (atom.coords_type === `fract`) {
788
- fractional_atom = {
789
- ...atom,
790
- coords: wrap_vec3(atom.coords),
791
- coords_type: `fract`,
792
- };
793
- }
794
- else {
795
- const xyz_base = [atom.coords[0], atom.coords[1], atom.coords[2]];
796
- const atom_abc = wrap_vec3(cart_to_frac(xyz_base));
797
- fractional_atom = { ...atom, coords: atom_abc, coords_type: `fract` };
796
+ // Candidate lattice-centering translations from the space-group symbol (R
797
+ // only valid in the hexagonal setting, α≈β≈90°, γ≈120°). Whether to actually
798
+ // apply them is decided below by reconciling against _atom_type_number_in_cell.
799
+ const centering_letter = extract_cif_centering(text);
800
+ const is_hexagonal_setting = Math.abs(alpha - 90) <= 1 && Math.abs(beta - 90) <= 1 && Math.abs(gamma - 120) <= 1;
801
+ const centering = centering_letter && (centering_letter !== `R` || is_hexagonal_setting)
802
+ ? CENTERING_VECTORS[centering_letter]
803
+ : [];
804
+ // Build all sites by expanding each atom via the symmetry ops (+ optional
805
+ // centering). Deduplicate globally on element + coordinates + label (6 dp to
806
+ // absorb floating point error from compound ops).
807
+ const build_sites = (extra_centering) => {
808
+ const sites = [];
809
+ const seen_site_keys = new Set();
810
+ for (const atom of atoms) {
811
+ const element = validate_element_symbol(atom.element, sites.length);
812
+ const coords = atom.coords_type === `fract`
813
+ ? wrap_vec3(atom.coords)
814
+ : wrap_vec3(cart_to_frac([atom.coords[0], atom.coords[1], atom.coords[2]]));
815
+ const fractional_atom = { ...atom, coords, coords_type: `fract` };
816
+ const equiv_atoms = apply_symmetry_ops(fractional_atom, ops_to_use, wrap_fractional_coords, extra_centering);
817
+ for (const equiv_atom of equiv_atoms) {
818
+ const abc = wrap_vec3(equiv_atom.coords);
819
+ const key = cif_site_key(element, abc, equiv_atom.id);
820
+ if (seen_site_keys.has(key))
821
+ continue;
822
+ seen_site_keys.add(key);
823
+ sites.push(make_site(element, abc, frac_to_cart(abc), equiv_atom.id, {}, equiv_atom.occupancy));
824
+ }
798
825
  }
799
- // First apply symmetry operations in fractional space
800
- const equiv_atoms = apply_symmetry_ops(fractional_atom, ops_to_use, wrap_fractional_coords);
801
- for (const equiv_atom of equiv_atoms) {
802
- const abc = wrap_vec3(equiv_atom.coords);
803
- const key = cif_site_key(element, abc, equiv_atom.id);
804
- if (seen_site_keys.has(key))
805
- continue;
806
- seen_site_keys.add(key);
807
- const xyz = frac_to_cart(abc);
808
- all_sites.push(make_site(element, abc, xyz, equiv_atom.id, {}, equiv_atom.occupancy));
826
+ return sites;
827
+ };
828
+ // Expand with point-group ops first. If the space group is centered and the
829
+ // result falls short of _atom_type_number_in_cell, retry with centering and
830
+ // adopt it only when it reconciles the expected total exactly — this fixes
831
+ // CIFs listing point-only ops for the asymmetric unit while avoiding
832
+ // double-counting CIFs whose atom list already embeds centering (e.g. C2/c
833
+ // COD 7008984, where listed ops + atoms already total the cell contents).
834
+ let sites = build_sites([]);
835
+ const expected_total = Object.values(atom_type_counts).reduce((sum, num) => sum + num, 0);
836
+ if (centering.length > 0 && expected_total > sites.length) {
837
+ const centered_sites = build_sites(centering);
838
+ // Adopt centering only when per-element counts reconcile exactly. Checking
839
+ // the total alone is insufficient: it can coincide while individual element
840
+ // counts are wrong (e.g. expected Fe 1 / O 3 but centering yields Fe 2 / O 2).
841
+ const counts = {};
842
+ for (const site of centered_sites) {
843
+ const element = site.species[0].element;
844
+ counts[element] = (counts[element] ?? 0) + 1;
809
845
  }
846
+ const reconciles = centered_sites.length === expected_total &&
847
+ Object.entries(atom_type_counts).every(([element, exp]) => counts[element] === exp);
848
+ if (reconciles)
849
+ sites = centered_sites;
810
850
  }
811
- const sites = all_sites;
812
851
  return { sites, lattice: { matrix: lattice_matrix, ...lattice_params } };
813
852
  }
814
853
  catch (error) {
@@ -865,7 +904,7 @@ export function parse_phonopy_yaml(content, cell_type) {
865
904
  }
866
905
  // Check if we're still in the phonon_displacements section
867
906
  if (skip_displacements) {
868
- if (/^[a-zA-Z_]/.exec(line)) {
907
+ if (/^[a-zA-Z_]/.test(line)) {
869
908
  // New top-level key, stop skipping
870
909
  skip_displacements = false;
871
910
  }
@@ -888,17 +927,16 @@ export function parse_phonopy_yaml(content, cell_type) {
888
927
  diag_error(`Requested cell type '${cell_type}' not found in phonopy YAML`);
889
928
  return null;
890
929
  }
891
- // Auto mode: return preferred structure in order of preference
892
- // 1. supercell (most detailed)
893
- // 2. phonon_supercell
894
- // 3. unit_cell
895
- // 4. phonon_primitive_cell
896
- // 5. primitive_cell
897
- const auto_cell = get_phonopy_cell(data, `supercell`) ??
898
- get_phonopy_cell(data, `phonon_supercell`) ??
899
- get_phonopy_cell(data, `unit_cell`) ??
900
- get_phonopy_cell(data, `phonon_primitive_cell`) ??
901
- get_phonopy_cell(data, `primitive_cell`);
930
+ // Auto mode: return first available cell, most detailed first
931
+ const auto_cell = [
932
+ `supercell`,
933
+ `phonon_supercell`,
934
+ `unit_cell`,
935
+ `phonon_primitive_cell`,
936
+ `primitive_cell`,
937
+ ]
938
+ .map((kind) => get_phonopy_cell(data, kind))
939
+ .find(Boolean);
902
940
  if (auto_cell)
903
941
  return convert_phonopy_cell(auto_cell);
904
942
  diag_error(`No valid cells found in phonopy YAML`);
@@ -956,6 +994,31 @@ export function is_parsed_structure(obj) {
956
994
  const has_coords = Array.isArray(abc) || Array.isArray(xyz);
957
995
  return has_species && has_coords;
958
996
  }
997
+ // Structure JSON serialized by pymatgen (default verbosity) stores only the lattice
998
+ // matrix + pbc; derive the missing scalar params (a/b/c/angles/volume) from the matrix
999
+ // so downstream consumers (camera auto-fit, density, export) never see NaN.
1000
+ export function ensure_lattice_params(structure) {
1001
+ const lattice = structure.lattice;
1002
+ if (!lattice?.matrix)
1003
+ return structure;
1004
+ const params = [
1005
+ lattice.a,
1006
+ lattice.b,
1007
+ lattice.c,
1008
+ lattice.alpha,
1009
+ lattice.beta,
1010
+ lattice.gamma,
1011
+ lattice.volume,
1012
+ ];
1013
+ if (params.every(Number.isFinite))
1014
+ return structure;
1015
+ // The matrix is authoritative: recompute all params from it rather than
1016
+ // trusting a partially-populated (or non-numeric) set of values.
1017
+ return {
1018
+ ...structure,
1019
+ lattice: { ...lattice, ...math.calc_lattice_params(lattice.matrix) },
1020
+ };
1021
+ }
959
1022
  // Normalize structure coordinates: wrap fractional coords to [0,1) and recompute Cartesian
960
1023
  // Only normalizes when lattice matrix is available to ensure abc/xyz stay consistent
961
1024
  export function normalize_fractional_coords(structure) {
@@ -989,7 +1052,7 @@ const detect_json_structure = (content) => {
989
1052
  }
990
1053
  // Otherwise try parsing as pymatgen/nested structure JSON
991
1054
  const structure = find_structure_in_json(parsed);
992
- return structure ? normalize_fractional_coords(structure) : null;
1055
+ return structure ? ensure_lattice_params(normalize_fractional_coords(structure)) : null;
993
1056
  };
994
1057
  // Internal: auto-detect file format, returns null on failure after recording reasons (see parse error contract at top)
995
1058
  function parse_structure_file_impl(content, filename) {
@@ -1048,36 +1111,20 @@ function parse_structure_file_impl(content, filename) {
1048
1111
  diag_error(`File too short to determine format`);
1049
1112
  return null;
1050
1113
  }
1051
- // XYZ format detection: first line should be a number, second line is comment
1052
- const first_line_number = parseInt(lines[0].trim(), 10);
1053
- if (!isNaN(first_line_number) && first_line_number > 0) {
1054
- // Check if this looks like XYZ format
1055
- if (lines.length >= first_line_number + 2) {
1056
- // Try to parse a coordinate line to see if it looks like XYZ
1057
- const coord_line_idx = 2; // First coordinate line in XYZ
1058
- if (coord_line_idx < lines.length) {
1059
- const parts = lines[coord_line_idx].trim().split(/\s+/);
1060
- // XYZ format: element symbol followed by 3 coordinates
1061
- if (parts.length >= 4) {
1062
- const first_token = parts[0];
1063
- const coords = parts.slice(1, 4);
1064
- // Check if first token looks like an element symbol (not a number)
1065
- // and the next 3 tokens look like coordinates (numbers)
1066
- const is_element_symbol = isNaN(parseInt(first_token, 10)) && first_token.length <= 3;
1067
- const are_coordinates = coords.every((coord) => !isNaN(parseFloat(coord)));
1068
- if (is_element_symbol && are_coordinates) {
1069
- // First token is likely an element symbol, likely XYZ
1070
- return parse_xyz(content);
1071
- }
1072
- }
1073
- }
1074
- }
1075
- }
1114
+ // XYZ format detection: first line is a positive atom count, second line is a
1115
+ // comment, and the first coordinate line looks like "<element> <x> <y> <z>"
1116
+ const first_line_number = Math.trunc(parse_leading_num(lines[0]));
1117
+ if (!isNaN(first_line_number) &&
1118
+ first_line_number > 0 &&
1119
+ lines.length >= first_line_number + 2 &&
1120
+ is_xyz_atom_line(lines[2]?.trim().split(/\s+/)))
1121
+ return parse_xyz(content);
1076
1122
  // POSCAR format detection: look for typical structure
1077
1123
  if (lines.length >= 8) {
1078
- const second_line_number = parseFloat(lines[1].trim());
1079
- // Second line is a number (scale factor), likely POSCAR
1080
- if (!isNaN(second_line_number))
1124
+ // Second line starts with a number (scale factor), likely POSCAR. First
1125
+ // token only: POSCAR allows three per-axis scale factors (or trailing
1126
+ // comments) on line 2, and blank lines must not pass
1127
+ if (!isNaN(parse_leading_num(lines[1])))
1081
1128
  return parse_poscar(content);
1082
1129
  }
1083
1130
  // CIF format detection: look for CIF-specific keywords
@@ -1109,16 +1156,19 @@ export function parse_structure_file(content, filename) {
1109
1156
  // Universal parser for JSON and structure files; throws an Error aggregating per-format failure reasons when nothing parses
1110
1157
  export function parse_any_structure(content, filename) {
1111
1158
  reset_parse_diagnostics();
1112
- const finalize_structure = (structure) => ({
1113
- sites: structure.sites,
1114
- charge: 0,
1115
- ...(structure.properties && {
1116
- properties: clone_structure_properties(structure.properties),
1117
- }),
1118
- ...(structure.lattice && {
1119
- lattice: { ...structure.lattice, pbc: [true, true, true] },
1120
- }),
1121
- });
1159
+ const finalize_structure = (parsed_structure) => {
1160
+ const structure = ensure_lattice_params(parsed_structure);
1161
+ return {
1162
+ sites: structure.sites,
1163
+ charge: 0,
1164
+ ...(structure.properties && {
1165
+ properties: structuredClone(structure.properties),
1166
+ }),
1167
+ ...(structure.lattice && {
1168
+ lattice: { ...structure.lattice, pbc: [true, true, true] },
1169
+ }),
1170
+ };
1171
+ };
1122
1172
  // Fast path: content is already a serialized structure object
1123
1173
  try {
1124
1174
  const parsed = JSON.parse(content);
@@ -1306,35 +1356,6 @@ export function optimade_to_crystal(optimade_structure) {
1306
1356
  return null;
1307
1357
  }
1308
1358
  }
1309
- // Check if filename indicates a structure file
1310
- export function is_structure_file(filename) {
1311
- const name = filename.toLowerCase();
1312
- // Trajectory-only formats (can't be structures)
1313
- if (/\.(traj|xtc|h5|hdf5)$/i.test(name) || /xdatcar/i.test(name))
1314
- return false;
1315
- // Always structure formats
1316
- if (STRUCTURE_EXTENSIONS_REGEX.test(name))
1317
- return true;
1318
- if (VASP_FILES_REGEX.test(name))
1319
- return true;
1320
- // .xyz/.extxyz files: structure unless they have trajectory keywords
1321
- if (/\.(xyz|extxyz)$/i.test(name))
1322
- return !TRAJ_KEYWORDS_REGEX.test(name);
1323
- // Keyword-based detection for YAML/XML
1324
- if (/\.(yaml|yml|xml)$/i.test(name) && STRUCT_KEYWORDS_REGEX.test(name))
1325
- return true;
1326
- // More restrictive keyword detection for JSON files
1327
- if (/\.json$/i.test(name) &&
1328
- STRUCT_KEYWORDS_STRICT_REGEX.test(name) &&
1329
- !TRAJ_KEYWORDS_REGEX.test(name) &&
1330
- !CONFIG_DIRS_REGEX.test(name))
1331
- return true;
1332
- // Compressed files - check base filename recursively
1333
- if (COMPRESSION_EXTENSIONS_REGEX.test(name)) {
1334
- return is_structure_file(name.replace(COMPRESSION_EXTENSIONS_REGEX, ``));
1335
- }
1336
- return false;
1337
- }
1338
1359
  export const detect_structure_type = (filename, content) => {
1339
1360
  // Normalize compressed suffixes (gz, gzip, zip, xz, bz2) for detection parity
1340
1361
  const name_to_check = strip_compression_extensions(filename);