matterviz 0.4.0 → 0.4.1

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 (326) hide show
  1. package/dist/brillouin/BrillouinZone.svelte +68 -145
  2. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -14
  3. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  4. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  7. package/dist/brillouin/BrillouinZoneScene.svelte +49 -203
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
  9. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  10. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  11. package/dist/brillouin/compute.d.ts +2 -0
  12. package/dist/brillouin/compute.js +80 -77
  13. package/dist/brillouin/geometry.d.ts +8 -0
  14. package/dist/brillouin/geometry.js +57 -0
  15. package/dist/brillouin/index.d.ts +2 -0
  16. package/dist/brillouin/index.js +2 -0
  17. package/dist/brillouin/types.d.ts +2 -2
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +100 -191
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +176 -464
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  23. package/dist/chempot-diagram/color.d.ts +3 -6
  24. package/dist/chempot-diagram/color.js +5 -5
  25. package/dist/chempot-diagram/compute.d.ts +3 -3
  26. package/dist/chempot-diagram/compute.js +3 -1
  27. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  28. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  29. package/dist/chempot-diagram/export.d.ts +47 -0
  30. package/dist/chempot-diagram/export.js +133 -0
  31. package/dist/chempot-diagram/index.d.ts +1 -0
  32. package/dist/chempot-diagram/index.js +1 -0
  33. package/dist/chempot-diagram/pointer.d.ts +0 -10
  34. package/dist/chempot-diagram/pointer.js +4 -4
  35. package/dist/chempot-diagram/types.d.ts +3 -3
  36. package/dist/colors/index.js +2 -2
  37. package/dist/composition/FormulaFilter.svelte +6 -5
  38. package/dist/composition/PieChart.svelte +5 -5
  39. package/dist/composition/chem-sys.js +3 -2
  40. package/dist/composition/format.js +3 -2
  41. package/dist/composition/parse.d.ts +0 -1
  42. package/dist/composition/parse.js +17 -19
  43. package/dist/controls.d.ts +1 -0
  44. package/dist/controls.js +0 -1
  45. package/dist/convex-hull/ConvexHull.svelte +8 -10
  46. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  47. package/dist/convex-hull/ConvexHull2D.svelte +94 -175
  48. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  49. package/dist/convex-hull/ConvexHull3D.svelte +176 -680
  50. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  51. package/dist/convex-hull/ConvexHull4D.svelte +180 -680
  52. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  53. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  54. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  55. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  56. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  57. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  58. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  59. package/dist/convex-hull/ConvexHullStats.svelte +29 -168
  60. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  61. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  62. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  63. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  64. package/dist/convex-hull/barycentric-coords.js +6 -33
  65. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  66. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  67. package/dist/convex-hull/helpers.d.ts +39 -7
  68. package/dist/convex-hull/helpers.js +154 -69
  69. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  70. package/dist/convex-hull/hull-state.svelte.js +124 -0
  71. package/dist/convex-hull/index.d.ts +9 -7
  72. package/dist/convex-hull/index.js +7 -2
  73. package/dist/convex-hull/thermodynamics.js +91 -920
  74. package/dist/convex-hull/types.d.ts +12 -4
  75. package/dist/convex-hull/types.js +12 -0
  76. package/dist/coordination/CoordinationBarPlot.svelte +4 -11
  77. package/dist/element/BohrAtom.svelte +2 -1
  78. package/dist/element/ElementTile.svelte.d.ts +1 -1
  79. package/dist/element/index.d.ts +4 -0
  80. package/dist/element/index.js +18 -0
  81. package/dist/feedback/DragOverlay.svelte +3 -1
  82. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  83. package/dist/feedback/StatusMessage.svelte +13 -3
  84. package/dist/fermi-surface/FermiSurface.svelte +67 -146
  85. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  86. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -224
  88. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  89. package/dist/fermi-surface/compute.js +11 -10
  90. package/dist/fermi-surface/export.js +4 -15
  91. package/dist/fermi-surface/index.d.ts +0 -1
  92. package/dist/fermi-surface/index.js +0 -1
  93. package/dist/fermi-surface/parse.d.ts +1 -1
  94. package/dist/fermi-surface/parse.js +64 -75
  95. package/dist/fermi-surface/types.d.ts +2 -2
  96. package/dist/heatmap-matrix/HeatmapMatrix.svelte +55 -40
  97. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  98. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  99. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  100. package/dist/heatmap-matrix/index.d.ts +3 -2
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/io/ExportPane.svelte +166 -0
  104. package/dist/io/ExportPane.svelte.d.ts +17 -0
  105. package/dist/io/decompress.js +1 -2
  106. package/dist/io/export.d.ts +5 -1
  107. package/dist/io/export.js +32 -28
  108. package/dist/io/fetch.d.ts +2 -1
  109. package/dist/io/file-drop.d.ts +7 -0
  110. package/dist/io/file-drop.js +13 -0
  111. package/dist/io/index.d.ts +2 -0
  112. package/dist/io/index.js +10 -0
  113. package/dist/io/types.d.ts +13 -0
  114. package/dist/isosurface/parse.js +46 -44
  115. package/dist/labels.js +1 -1
  116. package/dist/layout/FullscreenButton.svelte +33 -0
  117. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  118. package/dist/layout/FullscreenToggle.svelte +8 -14
  119. package/dist/layout/ViewerChrome.svelte +116 -0
  120. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  121. package/dist/layout/fullscreen.d.ts +4 -0
  122. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  123. package/dist/layout/fullscreen.svelte.js +37 -0
  124. package/dist/layout/index.d.ts +3 -0
  125. package/dist/layout/index.js +3 -0
  126. package/dist/math.d.ts +7 -3
  127. package/dist/math.js +18 -21
  128. package/dist/overlays/index.d.ts +4 -0
  129. package/dist/periodic-table/PeriodicTable.svelte +9 -8
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  131. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  133. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  137. package/dist/phase-diagram/colors.js +1 -1
  138. package/dist/phase-diagram/parse.d.ts +2 -1
  139. package/dist/plot/bar/BarPlot.svelte +79 -316
  140. package/dist/plot/bar/BarPlot.svelte.d.ts +7 -15
  141. package/dist/plot/bar/BarPlotControls.svelte.d.ts +1 -1
  142. package/dist/plot/bar/SpacegroupBarPlot.svelte +2 -1
  143. package/dist/plot/box/BoxPlot.svelte +76 -246
  144. package/dist/plot/box/BoxPlot.svelte.d.ts +4 -3
  145. package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
  146. package/dist/plot/box/Violin.svelte.d.ts +1 -1
  147. package/dist/plot/box/box-plot.d.ts +3 -2
  148. package/dist/plot/box/box-plot.js +5 -2
  149. package/dist/plot/box/kde.d.ts +2 -1
  150. package/dist/plot/box/kde.js +4 -4
  151. package/dist/plot/core/auto-place.d.ts +1 -1
  152. package/dist/plot/core/auto-place.js +4 -1
  153. package/dist/plot/core/components/ColorBar.svelte +5 -5
  154. package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
  155. package/dist/plot/core/components/Line.svelte +3 -2
  156. package/dist/plot/core/components/Line.svelte.d.ts +3 -2
  157. package/dist/plot/core/components/PlotAxis.svelte +2 -1
  158. package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
  159. package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
  160. package/dist/plot/core/components/ReferenceLine3D.svelte +2 -2
  161. package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
  162. package/dist/plot/core/components/ReferencePlane.svelte +2 -2
  163. package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
  164. package/dist/plot/core/data-cleaning.js +18 -18
  165. package/dist/plot/core/fill-utils.d.ts +4 -3
  166. package/dist/plot/core/fill-utils.js +6 -3
  167. package/dist/plot/core/interactions.d.ts +5 -1
  168. package/dist/plot/core/interactions.js +14 -0
  169. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  170. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  171. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  172. package/dist/plot/core/placed-tween.svelte.js +68 -0
  173. package/dist/plot/core/reference-line.d.ts +10 -10
  174. package/dist/plot/core/reference-line.js +6 -6
  175. package/dist/plot/core/scales.d.ts +17 -25
  176. package/dist/plot/core/scales.js +10 -8
  177. package/dist/plot/core/svg.d.ts +2 -1
  178. package/dist/plot/core/types.d.ts +18 -7
  179. package/dist/plot/core/utils/label-placement.d.ts +1 -1
  180. package/dist/plot/core/utils/label-placement.js +3 -3
  181. package/dist/plot/core/utils.d.ts +2 -1
  182. package/dist/plot/histogram/Histogram.svelte +77 -314
  183. package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
  184. package/dist/plot/sankey/Sankey.svelte +2 -5
  185. package/dist/plot/sankey/Sankey.svelte.d.ts +1 -1
  186. package/dist/plot/sankey/sankey.js +3 -1
  187. package/dist/plot/scatter/BinnedScatterPlot.svelte +3 -5
  188. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +4 -4
  189. package/dist/plot/scatter/ScatterPlot.svelte +160 -450
  190. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +7 -15
  191. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  192. package/dist/plot/scatter/binned-scatter-types.d.ts +4 -11
  193. package/dist/plot/scatter/index.d.ts +1 -1
  194. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +15 -26
  195. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
  196. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +9 -10
  197. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
  198. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +122 -121
  199. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
  200. package/dist/plot/scatter-3d/Surface3D.svelte +6 -5
  201. package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
  202. package/dist/plot/sunburst/Sunburst.svelte +16 -20
  203. package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
  204. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +1 -1
  205. package/dist/plot/sunburst/sunburst.js +4 -1
  206. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  207. package/dist/sanitize.js +13 -2
  208. package/dist/scene/SceneCamera.svelte +62 -0
  209. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  210. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  211. package/dist/scene/bind-renderer.svelte.js +14 -0
  212. package/dist/scene/index.d.ts +4 -0
  213. package/dist/scene/index.js +5 -0
  214. package/dist/scene/props.js +52 -0
  215. package/dist/scene/types.d.ts +26 -0
  216. package/dist/scene/types.js +1 -0
  217. package/dist/settings.d.ts +14 -2
  218. package/dist/settings.js +59 -1
  219. package/dist/spectral/Bands.svelte +8 -7
  220. package/dist/spectral/Bands.svelte.d.ts +3 -2
  221. package/dist/spectral/BandsAndDos.svelte +22 -24
  222. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  223. package/dist/spectral/Dos.svelte +5 -4
  224. package/dist/spectral/Dos.svelte.d.ts +2 -1
  225. package/dist/spectral/helpers.d.ts +6 -6
  226. package/dist/spectral/helpers.js +43 -37
  227. package/dist/state.svelte.d.ts +0 -7
  228. package/dist/state.svelte.js +0 -6
  229. package/dist/structure/Arrow.svelte +2 -4
  230. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  231. package/dist/structure/CanvasTooltip.svelte +1 -0
  232. package/dist/structure/CellSelect.svelte +11 -3
  233. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  234. package/dist/structure/Lattice.svelte +2 -2
  235. package/dist/structure/Structure.svelte +291 -355
  236. package/dist/structure/Structure.svelte.d.ts +5 -15
  237. package/dist/structure/StructureControls.svelte +217 -2
  238. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  239. package/dist/structure/StructureExportPane.svelte +54 -156
  240. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  241. package/dist/structure/StructureInfoPane.svelte +5 -3
  242. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  243. package/dist/structure/StructureScene.svelte +365 -198
  244. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  245. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  246. package/dist/structure/{label-placement.js → atom-label-placement.js} +12 -2
  247. package/dist/structure/atom-properties.d.ts +1 -1
  248. package/dist/structure/atom-properties.js +11 -16
  249. package/dist/structure/bond-order-perception.js +2 -4
  250. package/dist/structure/bonding.d.ts +3 -0
  251. package/dist/structure/bonding.js +91 -48
  252. package/dist/structure/export.d.ts +24 -4
  253. package/dist/structure/export.js +64 -122
  254. package/dist/structure/index.d.ts +2 -0
  255. package/dist/structure/index.js +2 -0
  256. package/dist/structure/parse.d.ts +3 -2
  257. package/dist/structure/parse.js +333 -370
  258. package/dist/structure/partial-occupancy.d.ts +0 -1
  259. package/dist/structure/partial-occupancy.js +1 -1
  260. package/dist/structure/pbc.d.ts +1 -1
  261. package/dist/structure/pbc.js +186 -13
  262. package/dist/structure/polyhedra.d.ts +41 -0
  263. package/dist/structure/polyhedra.js +602 -0
  264. package/dist/structure/site.d.ts +4 -0
  265. package/dist/structure/site.js +1 -0
  266. package/dist/structure/supercell.js +3 -2
  267. package/dist/structure/validation.js +5 -6
  268. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  269. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  270. package/dist/symmetry/SymmetryElements.svelte +354 -0
  271. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  272. package/dist/symmetry/SymmetryStats.svelte +111 -6
  273. package/dist/symmetry/WyckoffTable.svelte +68 -7
  274. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  275. package/dist/symmetry/cell-transform.js +7 -14
  276. package/dist/symmetry/index.d.ts +14 -4
  277. package/dist/symmetry/index.js +301 -80
  278. package/dist/symmetry/spacegroups.d.ts +5 -1
  279. package/dist/symmetry/spacegroups.js +15 -1
  280. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  281. package/dist/symmetry/symmetry-elements.js +521 -0
  282. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  283. package/dist/symmetry/wyckoff-db.js +87 -0
  284. package/dist/table/HeatmapTable.svelte +4 -15
  285. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  286. package/dist/trajectory/Trajectory.svelte +58 -61
  287. package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
  288. package/dist/trajectory/TrajectoryExportPane.svelte +15 -24
  289. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  290. package/dist/trajectory/TrajectoryInfoPane.svelte +3 -2
  291. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  292. package/dist/trajectory/constants.js +6 -2
  293. package/dist/trajectory/extract.js +17 -37
  294. package/dist/trajectory/format-detect.d.ts +0 -1
  295. package/dist/trajectory/format-detect.js +3 -9
  296. package/dist/trajectory/frame-reader.d.ts +0 -1
  297. package/dist/trajectory/frame-reader.js +62 -128
  298. package/dist/trajectory/helpers.d.ts +10 -2
  299. package/dist/trajectory/helpers.js +56 -36
  300. package/dist/trajectory/parse/ase.d.ts +9 -1
  301. package/dist/trajectory/parse/ase.js +47 -32
  302. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  303. package/dist/trajectory/parse/diagnostics.js +14 -0
  304. package/dist/trajectory/parse/index.d.ts +1 -1
  305. package/dist/trajectory/parse/index.js +54 -102
  306. package/dist/trajectory/parse/lammps.d.ts +0 -2
  307. package/dist/trajectory/parse/lammps.js +8 -6
  308. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  309. package/dist/trajectory/parse/pymatgen.js +74 -0
  310. package/dist/trajectory/parse/vasp.js +4 -3
  311. package/dist/trajectory/parse/xyz.d.ts +9 -21
  312. package/dist/trajectory/parse/xyz.js +28 -33
  313. package/dist/trajectory/plotting.d.ts +0 -1
  314. package/dist/trajectory/plotting.js +3 -100
  315. package/dist/utils.d.ts +1 -0
  316. package/dist/utils.js +1 -1
  317. package/dist/xrd/XrdPlot.svelte +14 -29
  318. package/dist/xrd/broadening.d.ts +2 -1
  319. package/dist/xrd/calc-xrd.js +6 -11
  320. package/dist/xrd/index.d.ts +2 -2
  321. package/package.json +29 -16
  322. package/dist/element/data.json +0 -11864
  323. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  324. package/dist/fermi-surface/marching-cubes.js +0 -2
  325. package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
  326. package/dist/plot/core/hover-lock.svelte.js +0 -45
@@ -1,9 +1,16 @@
1
1
  // Unified frame loader for XYZ and ASE trajectories (large file indexing)
2
2
  import * as math from '../math';
3
3
  import { MAX_METADATA_SIZE } from './constants';
4
- import { convert_atomic_numbers, count_xyz_frames, create_trajectory_frame, read_ndarray_from_view, validate_3x3_matrix, } from './helpers';
5
- import { strip_compression_extensions } from './format-detect';
6
- import { parse_extxyz_lattice, parse_xyz_atom_lines, parse_xyz_comment_metadata, } from './parse/xyz';
4
+ import { copy_numeric_fields, count_xyz_frames, iter_xyz_frames, validate_3x3_matrix, } from './helpers';
5
+ import { strip_compression_extensions } from '../io';
6
+ import { decode_ase_frame, read_ase_header } from './parse/ase';
7
+ import { build_xyz_frame, parse_xyz_comment_metadata } from './parse/xyz';
8
+ // Restrict frame metadata to the requested property keys (no-op when unset)
9
+ const filter_properties = (metadata, properties) => {
10
+ if (!properties)
11
+ return;
12
+ metadata.properties = Object.fromEntries(Object.entries(metadata.properties).filter(([key]) => properties.includes(key)));
13
+ };
7
14
  export class TrajFrameReader {
8
15
  format;
9
16
  global_numbers;
@@ -19,8 +26,7 @@ export class TrajFrameReader {
19
26
  }
20
27
  if (!(data instanceof ArrayBuffer))
21
28
  throw new Error(`ASE loader requires binary data`);
22
- const view = new DataView(data);
23
- return Number(view.getBigInt64(32, true));
29
+ return read_ase_header(new DataView(data)).n_items;
24
30
  }
25
31
  async build_frame_index(data, sample_rate, on_progress) {
26
32
  const total_frames = await this.get_total_frames(data);
@@ -31,35 +37,25 @@ export class TrajFrameReader {
31
37
  const encoder = new TextEncoder();
32
38
  const newline_sequence = data_str.includes(`\r\n`) ? `\r\n` : `\n`;
33
39
  const newline_byte_len = encoder.encode(newline_sequence).length;
34
- let [current_frame, line_idx, byte_offset] = [0, 0, 0];
35
- while (line_idx < lines.length && current_frame < total_frames) {
36
- if (!lines[line_idx]?.trim()) {
37
- byte_offset += encoder.encode(lines[line_idx]).length + newline_byte_len;
38
- line_idx++;
39
- continue;
40
- }
41
- const num_atoms = parseInt(lines[line_idx].trim(), 10);
42
- if (isNaN(num_atoms) || num_atoms <= 0 || line_idx + num_atoms + 1 >= lines.length) {
43
- byte_offset += encoder.encode(lines[line_idx]).length + newline_byte_len;
44
- line_idx++;
45
- continue;
46
- }
40
+ const line_bytes = (idx) => encoder.encode(lines[idx]).length + newline_byte_len;
41
+ // cursor = next line whose bytes haven't been added to byte_offset yet
42
+ let [current_frame, cursor, byte_offset] = [0, 0, 0];
43
+ for (const { start, num_atoms } of iter_xyz_frames(lines)) {
44
+ if (current_frame >= total_frames)
45
+ break;
46
+ // Accumulate bytes of blank/invalid lines skipped before this frame
47
+ for (; cursor < start; cursor++)
48
+ byte_offset += line_bytes(cursor);
49
+ let frame_size = 0;
50
+ for (; cursor < start + num_atoms + 2; cursor++)
51
+ frame_size += line_bytes(cursor);
47
52
  if (current_frame % sample_rate === 0) {
48
53
  frame_index.push({
49
54
  frame_number: current_frame,
50
55
  byte_offset,
51
- estimated_size: 0,
56
+ estimated_size: frame_size,
52
57
  });
53
58
  }
54
- const frame_start = line_idx;
55
- line_idx += 2 + num_atoms;
56
- let frame_size = 0;
57
- for (let idx = frame_start; idx < line_idx; idx++) {
58
- frame_size += encoder.encode(lines[idx]).length + newline_byte_len;
59
- }
60
- if (current_frame % sample_rate === 0) {
61
- frame_index[frame_index.length - 1].estimated_size = frame_size;
62
- }
63
59
  byte_offset += frame_size;
64
60
  current_frame++;
65
61
  if (on_progress && current_frame % 1000 === 0) {
@@ -73,7 +69,7 @@ export class TrajFrameReader {
73
69
  }
74
70
  else {
75
71
  const view = new DataView(data);
76
- const offsets_pos = Number(view.getBigInt64(40, true));
72
+ const { offsets_pos } = read_ase_header(view);
77
73
  for (let idx = 0; idx < total_frames; idx += sample_rate) {
78
74
  const frame_offset = Number(view.getBigInt64(offsets_pos + idx * 8, true));
79
75
  frame_index.push({
@@ -111,34 +107,28 @@ export class TrajFrameReader {
111
107
  const total_frames = await this.get_total_frames(data);
112
108
  if (this.format === `xyz`) {
113
109
  const lines = data.trim().split(/\r?\n/);
114
- let [current_frame, line_idx] = [0, 0];
115
- while (line_idx < lines.length && current_frame < total_frames) {
116
- if (!lines[line_idx]?.trim()) {
117
- line_idx++;
118
- continue;
119
- }
120
- const num_atoms = parseInt(lines[line_idx].trim(), 10);
121
- if (isNaN(num_atoms) || num_atoms <= 0 || line_idx + num_atoms + 1 >= lines.length) {
122
- line_idx++;
123
- continue;
124
- }
110
+ let current_frame = 0;
111
+ for (const { start, comment } of iter_xyz_frames(lines)) {
112
+ if (current_frame >= total_frames)
113
+ break;
125
114
  if (current_frame % sample_rate === 0) {
126
- const comment = lines[line_idx + 1] || ``;
127
115
  let frame_metadata = null;
128
116
  try {
129
- frame_metadata = this.parse_xyz_metadata(comment, current_frame);
117
+ const { step, properties: props } = parse_xyz_comment_metadata(comment);
118
+ frame_metadata = {
119
+ frame_number: current_frame,
120
+ step: step ?? current_frame,
121
+ properties: props,
122
+ };
130
123
  }
131
124
  catch (error) {
132
- console.warn(`Failed to parse XYZ metadata for frame ${current_frame} at line ${line_idx + 1}:`, error);
133
- }
134
- if (frame_metadata && properties) {
135
- const filtered = Object.fromEntries(Object.entries(frame_metadata.properties).filter(([key]) => properties.includes(key)));
136
- frame_metadata.properties = filtered;
125
+ console.warn(`Failed to parse XYZ metadata for frame ${current_frame} at line ${start + 1}:`, error);
137
126
  }
138
- if (frame_metadata)
127
+ if (frame_metadata) {
128
+ filter_properties(frame_metadata, properties);
139
129
  metadata_list.push(frame_metadata);
130
+ }
140
131
  }
141
- line_idx += 2 + num_atoms;
142
132
  current_frame++;
143
133
  if (on_progress && current_frame % 5000 === 0) {
144
134
  on_progress({
@@ -151,8 +141,7 @@ export class TrajFrameReader {
151
141
  }
152
142
  else if (this.format === `ase`) {
153
143
  const view = new DataView(data);
154
- const n_items = Number(view.getBigInt64(32, true));
155
- const offsets_pos = Number(view.getBigInt64(40, true));
144
+ const { n_items, offsets_pos } = read_ase_header(view);
156
145
  for (let idx = 0; idx < n_items; idx += sample_rate) {
157
146
  try {
158
147
  const frame_offset = Number(view.getBigInt64(offsets_pos + idx * 8, true));
@@ -163,10 +152,7 @@ export class TrajFrameReader {
163
152
  }
164
153
  const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
165
154
  const frame_metadata = this.parse_ase_metadata(frame_data, idx);
166
- if (properties) {
167
- const filtered = Object.fromEntries(Object.entries(frame_metadata.properties).filter(([key]) => properties.includes(key)));
168
- frame_metadata.properties = filtered;
169
- }
155
+ filter_properties(frame_metadata, properties);
170
156
  metadata_list.push(frame_metadata);
171
157
  if (on_progress && idx % 5000 === 0) {
172
158
  on_progress({
@@ -185,105 +171,53 @@ export class TrajFrameReader {
185
171
  }
186
172
  load_xyz_frame(data, frame_number) {
187
173
  const lines = data.trim().split(/\r?\n/);
188
- let [current_frame, line_idx] = [0, 0];
189
- while (line_idx < lines.length && current_frame < frame_number) {
190
- const skip_atoms = parseInt(lines[line_idx].trim(), 10);
191
- if (isNaN(skip_atoms) || skip_atoms <= 0) {
192
- line_idx++; // skip blank/invalid lines until the next frame's atom-count line
193
- continue;
194
- }
195
- line_idx += 2 + skip_atoms;
196
- current_frame++;
174
+ let current_frame = 0;
175
+ for (const frame of iter_xyz_frames(lines)) {
176
+ if (current_frame++ < frame_number)
177
+ continue; // skip frames before the target
178
+ return build_xyz_frame(lines, frame, {
179
+ frame_label: `indexed frame ${frame_number}`,
180
+ default_step: frame_number,
181
+ });
197
182
  }
198
- if (line_idx >= lines.length)
199
- return null;
200
- const num_atoms = parseInt(lines[line_idx].trim(), 10);
201
- if (isNaN(num_atoms) || line_idx + num_atoms + 1 >= lines.length)
202
- return null;
203
- const comment = lines[line_idx + 1] || ``;
204
- const lattice_matrix = parse_extxyz_lattice(comment);
205
- const { elements, positions, force_stats } = parse_xyz_atom_lines(lines, line_idx + 2, num_atoms, comment, `indexed frame ${frame_number}`);
206
- const { step, properties } = this.parse_xyz_metadata(comment, frame_number);
207
- const metadata = { ...properties, ...force_stats };
208
- // Derive volume from the lattice (parity with the eager parse_xyz_trajectory parser)
209
- if (lattice_matrix)
210
- metadata.volume = math.calc_lattice_params(lattice_matrix).volume;
211
- return create_trajectory_frame(positions, elements, lattice_matrix, lattice_matrix ? [true, true, true] : undefined, step, metadata);
183
+ return null;
212
184
  }
213
185
  load_ase_frame(data, frame_number) {
214
186
  try {
215
187
  const view = new DataView(data);
216
- const n_items = Number(view.getBigInt64(32, true));
217
- const offsets_pos = Number(view.getBigInt64(40, true));
188
+ const { n_items, offsets_pos } = read_ase_header(view);
218
189
  if (frame_number >= n_items)
219
190
  return null;
220
191
  const frame_offset = Number(view.getBigInt64(offsets_pos + frame_number * 8, true));
221
- const json_length = Number(view.getBigInt64(frame_offset, true));
222
- const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
223
- const positions_ref = frame_data[`positions.`] ?? frame_data.positions;
224
- const positions = positions_ref?.ndarray
225
- ? read_ndarray_from_view(view, positions_ref)
226
- : positions_ref;
227
- const numbers_ref = frame_data[`numbers.`] ?? frame_data.numbers ?? this.global_numbers;
228
- const numbers = numbers_ref?.ndarray
229
- ? read_ndarray_from_view(view, numbers_ref).flat()
230
- : numbers_ref;
231
- if (numbers)
232
- this.global_numbers = numbers;
233
- if (!numbers || !positions)
234
- throw new Error(`Missing atomic numbers or positions`);
235
- const cell = frame_data.cell ? validate_3x3_matrix(frame_data.cell) : undefined;
236
- const metadata = {
237
- step: frame_number,
238
- ...frame_data.calculator,
239
- ...frame_data.info,
240
- };
241
- if (cell) {
242
- try {
243
- metadata.volume = Math.abs(math.det_3x3(cell));
244
- }
245
- catch (error) {
246
- console.warn(`Failed to calculate volume for frame ${frame_number}:`, error);
247
- }
248
- }
249
- return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc ?? [true, true, true], frame_number, metadata);
192
+ const { frame, numbers } = decode_ase_frame(view, data, frame_offset, frame_number, this.global_numbers);
193
+ this.global_numbers = numbers;
194
+ return frame;
250
195
  }
251
196
  catch (error) {
252
197
  console.warn(`Failed to load ASE frame ${frame_number}:`, error);
253
198
  return null;
254
199
  }
255
200
  }
256
- parse_xyz_metadata(comment, frame_number) {
257
- const { step, properties } = parse_xyz_comment_metadata(comment);
258
- return { frame_number, step: step ?? frame_number, properties };
259
- }
260
201
  parse_ase_metadata(frame_data, frame_number) {
261
202
  const properties = {};
262
203
  const step = frame_number;
263
204
  if (frame_data.calculator && typeof frame_data.calculator === `object`) {
264
- const calculator = frame_data.calculator;
265
- const calc_properties = [`energy`, `potential_energy`, `kinetic_energy`, `total_energy`];
266
- for (const prop of calc_properties) {
267
- if (prop in calculator && typeof calculator[prop] === `number`) {
268
- properties[prop] = calculator[prop];
269
- }
270
- }
205
+ copy_numeric_fields(properties, frame_data.calculator, [
206
+ `energy`,
207
+ `potential_energy`,
208
+ `kinetic_energy`,
209
+ `total_energy`,
210
+ ]);
271
211
  }
272
212
  if (frame_data.info && typeof frame_data.info === `object`) {
273
- const info = frame_data.info;
274
- const info_properties = [
213
+ copy_numeric_fields(properties, frame_data.info, [
275
214
  `force_max`,
276
215
  `force_norm`,
277
216
  `stress_max`,
278
217
  `stress_frobenius`,
279
218
  `pressure`,
280
219
  `temperature`,
281
- ];
282
- for (const prop of info_properties) {
283
- if (prop in info && typeof info[prop] === `number`) {
284
- properties[prop] = info[prop];
285
- }
286
- }
220
+ ]);
287
221
  }
288
222
  if (frame_data.cell && Array.isArray(frame_data.cell)) {
289
223
  try {
@@ -3,8 +3,6 @@ import * as math from '../math';
3
3
  import type { AnyStructure } from '../structure/index';
4
4
  import type { Pbc } from '../structure/pbc';
5
5
  import type { TrajectoryFrame } from './index';
6
- export declare const is_valid_element_symbol: (symbol: string) => symbol is ElementSymbol;
7
- export declare const coerce_element_symbol: (symbol: string) => ElementSymbol | undefined;
8
6
  export declare function validate_3x3_matrix(data: unknown): math.Matrix3x3;
9
7
  export declare const convert_atomic_numbers: (numbers: number[]) => ElementSymbol[];
10
8
  export declare const create_structure: (positions: number[][], elements: ElementSymbol[], lattice_matrix?: math.Matrix3x3, pbc?: Pbc, force_data?: number[][]) => AnyStructure;
@@ -12,4 +10,14 @@ export declare const create_trajectory_frame: (positions: number[][], elements:
12
10
  export declare const read_ndarray_from_view: (view: DataView, ref: {
13
11
  ndarray: unknown[];
14
12
  }) => number[][];
13
+ export declare const copy_numeric_fields: (target: Record<string, number>, source: Record<string, unknown>, fields: readonly string[]) => void;
14
+ export declare function calc_force_stats(forces: number[][]): {
15
+ force_max: number;
16
+ force_norm: number;
17
+ } | null;
18
+ export declare function iter_xyz_frames(lines: string[]): Generator<{
19
+ start: number;
20
+ num_atoms: number;
21
+ comment: string;
22
+ }>;
15
23
  export declare function count_xyz_frames(data: string): number;
@@ -1,16 +1,14 @@
1
1
  // Shared utilities for trajectory parsing
2
2
  import { ATOMIC_NUMBER_TO_SYMBOL } from '../composition/parse';
3
- import { ELEM_SYMBOLS } from '../labels';
3
+ import { is_elem_symbol } from '../element';
4
4
  import * as math from '../math';
5
- const element_symbol_set = new Set(ELEM_SYMBOLS);
5
+ import { make_site } from '../structure/site';
6
6
  const is_valid_row = (row) => {
7
7
  if (!(Array.isArray(row) || (ArrayBuffer.isView(row) && `length` in row)))
8
8
  return false;
9
9
  return math.is_finite_vec3_like(row);
10
10
  };
11
11
  const is_valid_vec3 = (coords) => Array.isArray(coords) && math.is_finite_vec3_like(coords);
12
- export const is_valid_element_symbol = (symbol) => element_symbol_set.has(symbol);
13
- export const coerce_element_symbol = (symbol) => is_valid_element_symbol(symbol) ? symbol : undefined;
14
12
  // Validate that data is a proper 3x3 matrix
15
13
  // Accepts both regular arrays and typed arrays (Float32Array, Float64Array, etc.)
16
14
  export function validate_3x3_matrix(data) {
@@ -24,7 +22,7 @@ export function validate_3x3_matrix(data) {
24
22
  }
25
23
  export const convert_atomic_numbers = (numbers) => numbers.map((num) => {
26
24
  const symbol = ATOMIC_NUMBER_TO_SYMBOL[num];
27
- if (!symbol || !is_valid_element_symbol(symbol)) {
25
+ if (!symbol || !is_elem_symbol(symbol)) {
28
26
  throw new Error(`Unknown atomic number in trajectory data: ${num}`);
29
27
  }
30
28
  return symbol;
@@ -42,13 +40,7 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
42
40
  const abc = cart_to_frac ? cart_to_frac(xyz) : [0, 0, 0];
43
41
  const force = force_data?.[idx];
44
42
  const properties = is_valid_vec3(force) ? { force } : {};
45
- return {
46
- species: [{ element: elements[idx], occu: 1, oxidation_state: 0 }],
47
- abc,
48
- xyz,
49
- label: `${elements[idx]}${idx + 1}`,
50
- properties,
51
- };
43
+ return make_site(elements[idx], abc, xyz, `${elements[idx]}${idx + 1}`, properties);
52
44
  });
53
45
  return lattice_matrix
54
46
  ? {
@@ -126,39 +118,67 @@ export const read_ndarray_from_view = (view, ref) => {
126
118
  throw new Error(`Unsupported shape`);
127
119
  })();
128
120
  };
129
- // Unified frame counting for XYZ
130
- export function count_xyz_frames(data) {
131
- if (!data || typeof data !== `string`)
132
- return 0;
133
- const lines = data.trim().split(/\r?\n/);
134
- let frame_count = 0;
121
+ // Copy listed fields from source to target when they hold numbers
122
+ export const copy_numeric_fields = (target, source, fields) => {
123
+ for (const field of fields) {
124
+ if (field in source && typeof source[field] === `number`)
125
+ target[field] = source[field];
126
+ }
127
+ };
128
+ // Max and RMS of per-atom force magnitudes, or null when no forces present. Loop-based
129
+ // rather than Math.max(...spread) to avoid call-stack overflow on very large frames.
130
+ export function calc_force_stats(forces) {
131
+ if (forces.length === 0)
132
+ return null;
133
+ let force_max = -Infinity;
134
+ let sum_sq = 0;
135
+ for (const force of forces) {
136
+ const magnitude = Math.hypot(...force);
137
+ if (magnitude > force_max)
138
+ force_max = magnitude;
139
+ sum_sq += magnitude ** 2;
140
+ }
141
+ return { force_max, force_norm: Math.sqrt(sum_sq / forces.length) };
142
+ }
143
+ // Walk concatenated (ext)XYZ frames in `lines`, yielding each frame's atom-count line
144
+ // index, parsed atom count, and comment line. A candidate frame is accepted only when its
145
+ // first few atom lines look like "<element> <x> <y> <z>"; otherwise we advance one line and
146
+ // rescan. That validation doubles as content sniffing so numeric-leading non-XYZ formats
147
+ // (e.g. VASP XDATCAR) aren't misread as frames, and keeps count_xyz_frames consistent with
148
+ // the actual parse/index walk (both go through this single source of truth).
149
+ export function* iter_xyz_frames(lines) {
135
150
  let line_idx = 0;
136
151
  while (line_idx < lines.length) {
137
- if (!lines[line_idx]?.trim()) {
138
- line_idx++;
139
- continue;
140
- }
141
- const num_atoms = parseInt(lines[line_idx].trim(), 10);
152
+ const num_atoms = parseInt(lines[line_idx]?.trim(), 10);
142
153
  if (isNaN(num_atoms) || num_atoms <= 0 || line_idx + num_atoms + 2 > lines.length) {
143
- line_idx++;
154
+ line_idx++; // skip blank/invalid lines until the next frame's atom-count line
144
155
  continue;
145
156
  }
146
- // Quick validation of first few atom lines
147
157
  let valid_coords = 0;
148
- for (let idx = 0; idx < Math.min(num_atoms, 3); idx++) {
158
+ const sample = Math.min(num_atoms, 3);
159
+ for (let idx = 0; idx < sample; idx++) {
149
160
  const parts = lines[line_idx + 2 + idx]?.trim().split(/\s+/);
150
- if (parts?.length >= 4 && isNaN(parseInt(parts[0], 10)) && parts[0].length <= 3) {
151
- if (parts.slice(1, 4).every((coord) => !isNaN(parseFloat(coord))))
152
- valid_coords++;
153
- }
154
- }
155
- if (valid_coords >= Math.min(num_atoms, 3)) {
156
- frame_count++;
157
- line_idx += 2 + num_atoms;
161
+ if (parts?.length >= 4 &&
162
+ isNaN(parseInt(parts[0], 10)) &&
163
+ parts[0].length <= 3 &&
164
+ parts.slice(1, 4).every((coord) => !isNaN(parseFloat(coord))))
165
+ valid_coords++;
158
166
  }
159
- else {
160
- line_idx++;
167
+ if (valid_coords < sample) {
168
+ line_idx++; // count line looks valid but atom lines don't — likely non-XYZ content
169
+ continue;
161
170
  }
171
+ yield { start: line_idx, num_atoms, comment: lines[line_idx + 1] || `` };
172
+ line_idx += num_atoms + 2;
162
173
  }
174
+ }
175
+ // Count XYZ frames via iter_xyz_frames so total_frames matches what gets indexed/loaded
176
+ export function count_xyz_frames(data) {
177
+ if (!data || typeof data !== `string`)
178
+ return 0;
179
+ const frames = iter_xyz_frames(data.trim().split(/\r?\n/));
180
+ let frame_count = 0;
181
+ while (!frames.next().done)
182
+ frame_count += 1;
163
183
  return frame_count;
164
184
  }
@@ -1,2 +1,10 @@
1
- import type { TrajectoryType } from '../index';
1
+ import type { TrajectoryFrame, TrajectoryType } from '../index';
2
+ export declare const read_ase_header: (view: DataView) => {
3
+ n_items: number;
4
+ offsets_pos: number;
5
+ };
6
+ export declare function decode_ase_frame(view: DataView, buffer: ArrayBuffer, frame_offset: number, step: number, fallback_numbers?: number[], max_json_length?: number): {
7
+ frame: TrajectoryFrame;
8
+ numbers: number[];
9
+ };
2
10
  export declare function parse_ase_trajectory(buffer: ArrayBuffer, filename?: string): TrajectoryType;
@@ -1,18 +1,55 @@
1
1
  // ASE trajectory (.traj) parsing - binary format
2
+ import * as math from '../../math';
2
3
  import { MAX_SAFE_STRING_LENGTH } from '../constants';
3
4
  import { convert_atomic_numbers, create_trajectory_frame, read_ndarray_from_view, validate_3x3_matrix, } from '../helpers';
5
+ // ULM header: frame count lives at byte 32, frame-offsets table position at byte 40
6
+ export const read_ase_header = (view) => ({
7
+ n_items: Number(view.getBigInt64(32, true)),
8
+ offsets_pos: Number(view.getBigInt64(40, true)),
9
+ });
10
+ // Decode a single ASE/ULM frame (JSON header + optional ndarray payloads) into a
11
+ // TrajectoryFrame. Returns the atomic numbers actually used so callers can cache them
12
+ // as fallback for later frames that omit `numbers` (ASE stores them only once).
13
+ export function decode_ase_frame(view, buffer, frame_offset, step, fallback_numbers, max_json_length) {
14
+ const json_length = Number(view.getBigInt64(frame_offset, true));
15
+ if (max_json_length !== undefined && json_length > max_json_length) {
16
+ throw new Error(`frame JSON too large: ${json_length} bytes`);
17
+ }
18
+ const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, frame_offset + 8, json_length)));
19
+ const positions_ref = frame_data[`positions.`] ?? frame_data.positions;
20
+ const positions = positions_ref?.ndarray
21
+ ? read_ndarray_from_view(view, positions_ref)
22
+ : positions_ref;
23
+ const numbers_ref = frame_data[`numbers.`] ?? frame_data.numbers ?? fallback_numbers;
24
+ const numbers = numbers_ref?.ndarray
25
+ ? read_ndarray_from_view(view, numbers_ref).flat()
26
+ : numbers_ref;
27
+ if (!numbers || !positions) {
28
+ throw new Error(`missing ${!numbers ? `numbers` : `positions`}`);
29
+ }
30
+ const cell = frame_data.cell ? validate_3x3_matrix(frame_data.cell) : undefined;
31
+ const metadata = {
32
+ step,
33
+ ...frame_data.calculator,
34
+ ...frame_data.info,
35
+ };
36
+ if (cell) {
37
+ try {
38
+ metadata.volume = Math.abs(math.det_3x3(cell));
39
+ }
40
+ catch (error) {
41
+ console.warn(`Failed to calculate volume for frame ${step}:`, error);
42
+ }
43
+ }
44
+ const frame = create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc ?? [true, true, true], step, metadata);
45
+ return { frame, numbers };
46
+ }
4
47
  export function parse_ase_trajectory(buffer, filename) {
5
48
  const view = new DataView(buffer);
6
- let offset = 0;
7
49
  const signature = new TextDecoder().decode(new Uint8Array(buffer, 0, 8));
8
50
  if (signature !== `- of Ulm`)
9
51
  throw new Error(`Invalid ASE trajectory`);
10
- offset += 24;
11
- // Skip ASE/Ulm version field; current parsing logic is version-independent.
12
- offset += 8;
13
- const n_items = Number(view.getBigInt64(offset, true));
14
- offset += 8;
15
- const offsets_pos = Number(view.getBigInt64(offset, true));
52
+ const { n_items, offsets_pos } = read_ase_header(view);
16
53
  if (n_items <= 0)
17
54
  throw new Error(`Invalid frame count`);
18
55
  if (offsets_pos < 0 || offsets_pos + n_items * 8 > buffer.byteLength) {
@@ -23,31 +60,9 @@ export function parse_ase_trajectory(buffer, filename) {
23
60
  let global_numbers;
24
61
  for (let idx = 0; idx < n_items; idx++) {
25
62
  try {
26
- offset = frame_offsets[idx];
27
- const json_length = Number(view.getBigInt64(offset, true));
28
- offset += 8;
29
- if (json_length > MAX_SAFE_STRING_LENGTH) {
30
- console.warn(`Skipping frame ${idx + 1}/${n_items}: too large`);
31
- continue;
32
- }
33
- const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, offset, json_length)));
34
- const positions_ref = frame_data[`positions.`] ?? frame_data.positions;
35
- const positions = positions_ref?.ndarray
36
- ? read_ndarray_from_view(view, positions_ref)
37
- : positions_ref;
38
- const numbers_ref = frame_data[`numbers.`] ?? frame_data.numbers ?? global_numbers;
39
- const numbers = numbers_ref?.ndarray
40
- ? read_ndarray_from_view(view, numbers_ref).flat()
41
- : numbers_ref;
42
- if (numbers)
43
- global_numbers = numbers;
44
- if (!numbers || !positions) {
45
- console.warn(`Skipping ASE frame ${idx + 1}/${n_items}: missing ${!numbers ? `numbers` : `positions`}`);
46
- continue;
47
- }
48
- const elements = convert_atomic_numbers(numbers);
49
- const metadata = { step: idx, ...frame_data.calculator, ...frame_data.info };
50
- frames.push(create_trajectory_frame(positions, elements, frame_data.cell ? validate_3x3_matrix(frame_data.cell) : undefined, frame_data.pbc ?? [true, true, true], idx, metadata));
63
+ const { frame, numbers } = decode_ase_frame(view, buffer, frame_offsets[idx], idx, global_numbers, MAX_SAFE_STRING_LENGTH);
64
+ global_numbers = numbers;
65
+ frames.push(frame);
51
66
  }
52
67
  catch (error) {
53
68
  console.warn(`Error processing frame ${idx + 1}/${n_items}:`, error);
@@ -0,0 +1,3 @@
1
+ export declare const get_traj_parse_warnings: () => string[];
2
+ export declare const reset_traj_parse_warnings: () => void;
3
+ export declare const traj_warn: (message: string, error?: unknown) => void;
@@ -0,0 +1,14 @@
1
+ import { to_error } from '../../utils';
2
+ // Collector for non-fatal trajectory parse warnings (skipped atoms, dropped frames, plot-metadata extraction failures, ...) so they reach the UI not just the console. Reset at the start of each top-level parse call; format parsers append via traj_warn (mirrored to console.warn). Fatal failures throw — see parse_trajectory_data.
3
+ let traj_parse_warnings = [];
4
+ // Read-only snapshot of warnings since the last top-level parse (attached to trajectory metadata by parse/index.ts for UI surfacing)
5
+ export const get_traj_parse_warnings = () => [...traj_parse_warnings];
6
+ export const reset_traj_parse_warnings = () => void (traj_parse_warnings = []);
7
+ export const traj_warn = (message, error) => {
8
+ const detail = error === undefined ? `` : `: ${to_error(error).message}`;
9
+ traj_parse_warnings.push(`${message}${detail}`);
10
+ if (error === undefined)
11
+ console.warn(message);
12
+ else
13
+ console.warn(`${message}:`, error);
14
+ };
@@ -2,7 +2,7 @@ import { is_trajectory_file } from '../format-detect';
2
2
  import { TrajFrameReader } from '../frame-reader';
3
3
  import type { FrameLoader, ParseProgress, TrajectoryType } from '../index';
4
4
  import type { AtomTypeMapping, LoadingOptions } from '../types';
5
- export { INDEX_SAMPLE_RATE, LARGE_FILE_THRESHOLD, MAX_BIN_FILE_SIZE, MAX_METADATA_SIZE, MAX_SAFE_STRING_LENGTH, MAX_TEXT_FILE_SIZE, } from '../constants';
5
+ export { LARGE_FILE_THRESHOLD, MAX_BIN_FILE_SIZE, MAX_TEXT_FILE_SIZE, } from '../constants';
6
6
  export type { AtomTypeMapping, LoadingOptions } from '../types';
7
7
  export { is_trajectory_file, TrajFrameReader };
8
8
  export declare function parse_trajectory_data(data: unknown, filename?: string, atom_type_mapping?: AtomTypeMapping): Promise<TrajectoryType>;