matterviz 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import * as math from '../math';
|
|
2
|
+
import { MAX_METADATA_SIZE } from './constants';
|
|
3
|
+
import { coerce_element_symbol, convert_atomic_numbers, count_xyz_frames, create_trajectory_frame, read_ndarray_from_view, validate_3x3_matrix, } from './helpers';
|
|
4
|
+
import { strip_compression_extensions } from './format-detect';
|
|
5
|
+
export class TrajFrameReader {
|
|
6
|
+
format;
|
|
7
|
+
global_numbers;
|
|
8
|
+
constructor(filename) {
|
|
9
|
+
const base_filename = strip_compression_extensions(filename);
|
|
10
|
+
this.format = base_filename.endsWith(`.traj`) ? `ase` : `xyz`;
|
|
11
|
+
}
|
|
12
|
+
// deno-lint-ignore require-await
|
|
13
|
+
async get_total_frames(data) {
|
|
14
|
+
if (this.format === `xyz`) {
|
|
15
|
+
if (data instanceof ArrayBuffer)
|
|
16
|
+
throw new Error(`XYZ loader requires text data`);
|
|
17
|
+
return count_xyz_frames(data);
|
|
18
|
+
}
|
|
19
|
+
if (!(data instanceof ArrayBuffer))
|
|
20
|
+
throw new Error(`ASE loader requires binary data`);
|
|
21
|
+
const view = new DataView(data);
|
|
22
|
+
return Number(view.getBigInt64(32, true));
|
|
23
|
+
}
|
|
24
|
+
async build_frame_index(data, sample_rate, on_progress) {
|
|
25
|
+
const total_frames = await this.get_total_frames(data);
|
|
26
|
+
const frame_index = [];
|
|
27
|
+
if (this.format === `xyz`) {
|
|
28
|
+
const data_str = data;
|
|
29
|
+
const lines = data_str.trim().split(/\r?\n/);
|
|
30
|
+
const encoder = new TextEncoder();
|
|
31
|
+
const newline_sequence = data_str.includes(`\r\n`) ? `\r\n` : `\n`;
|
|
32
|
+
const newline_byte_len = encoder.encode(newline_sequence).length;
|
|
33
|
+
let [current_frame, line_idx, byte_offset] = [0, 0, 0];
|
|
34
|
+
while (line_idx < lines.length && current_frame < total_frames) {
|
|
35
|
+
if (!lines[line_idx]?.trim()) {
|
|
36
|
+
byte_offset += encoder.encode(lines[line_idx]).length + newline_byte_len;
|
|
37
|
+
line_idx++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const num_atoms = parseInt(lines[line_idx].trim(), 10);
|
|
41
|
+
if (isNaN(num_atoms) || num_atoms <= 0 || line_idx + num_atoms + 1 >= lines.length) {
|
|
42
|
+
byte_offset += encoder.encode(lines[line_idx]).length + newline_byte_len;
|
|
43
|
+
line_idx++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (current_frame % sample_rate === 0) {
|
|
47
|
+
frame_index.push({
|
|
48
|
+
frame_number: current_frame,
|
|
49
|
+
byte_offset,
|
|
50
|
+
estimated_size: 0,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const frame_start = line_idx;
|
|
54
|
+
line_idx += 2 + num_atoms;
|
|
55
|
+
let frame_size = 0;
|
|
56
|
+
for (let idx = frame_start; idx < line_idx; idx++) {
|
|
57
|
+
frame_size += encoder.encode(lines[idx]).length + newline_byte_len;
|
|
58
|
+
}
|
|
59
|
+
if (current_frame % sample_rate === 0) {
|
|
60
|
+
frame_index[frame_index.length - 1].estimated_size = frame_size;
|
|
61
|
+
}
|
|
62
|
+
byte_offset += frame_size;
|
|
63
|
+
current_frame++;
|
|
64
|
+
if (on_progress && current_frame % 1000 === 0) {
|
|
65
|
+
on_progress({
|
|
66
|
+
current: (current_frame / total_frames) * 100,
|
|
67
|
+
total: 100,
|
|
68
|
+
stage: `Indexing: ${current_frame}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const view = new DataView(data);
|
|
75
|
+
const offsets_pos = Number(view.getBigInt64(40, true));
|
|
76
|
+
for (let idx = 0; idx < total_frames; idx += sample_rate) {
|
|
77
|
+
const frame_offset = Number(view.getBigInt64(offsets_pos + idx * 8, true));
|
|
78
|
+
frame_index.push({
|
|
79
|
+
frame_number: idx,
|
|
80
|
+
byte_offset: frame_offset,
|
|
81
|
+
estimated_size: 0,
|
|
82
|
+
});
|
|
83
|
+
if (on_progress && idx % 10000 === 0) {
|
|
84
|
+
on_progress({
|
|
85
|
+
current: (idx / total_frames) * 100,
|
|
86
|
+
total: 100,
|
|
87
|
+
stage: `Indexing ASE: ${idx}`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return frame_index;
|
|
93
|
+
}
|
|
94
|
+
// deno-lint-ignore require-await
|
|
95
|
+
async load_frame(data, frame_number) {
|
|
96
|
+
const actual_data_type = data instanceof ArrayBuffer ? `ArrayBuffer` : typeof data;
|
|
97
|
+
if (this.format === `xyz`) {
|
|
98
|
+
if (typeof data !== `string`) {
|
|
99
|
+
throw new TypeError(`load_frame expected string data for xyz format, received ${actual_data_type}`);
|
|
100
|
+
}
|
|
101
|
+
return this.load_xyz_frame(data, frame_number);
|
|
102
|
+
}
|
|
103
|
+
if (!(data instanceof ArrayBuffer)) {
|
|
104
|
+
throw new TypeError(`load_frame expected ArrayBuffer data for ase format, received ${actual_data_type}`);
|
|
105
|
+
}
|
|
106
|
+
return this.load_ase_frame(data, frame_number);
|
|
107
|
+
}
|
|
108
|
+
async extract_plot_metadata(data, options, on_progress) {
|
|
109
|
+
const { sample_rate = 1, properties } = options || {};
|
|
110
|
+
const metadata_list = [];
|
|
111
|
+
const total_frames = await this.get_total_frames(data);
|
|
112
|
+
if (this.format === `xyz`) {
|
|
113
|
+
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
|
+
}
|
|
125
|
+
if (current_frame % sample_rate === 0) {
|
|
126
|
+
const comment = lines[line_idx + 1] || ``;
|
|
127
|
+
let frame_metadata = null;
|
|
128
|
+
try {
|
|
129
|
+
frame_metadata = this.parse_xyz_metadata(comment, current_frame);
|
|
130
|
+
}
|
|
131
|
+
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;
|
|
137
|
+
}
|
|
138
|
+
if (frame_metadata)
|
|
139
|
+
metadata_list.push(frame_metadata);
|
|
140
|
+
}
|
|
141
|
+
line_idx += 2 + num_atoms;
|
|
142
|
+
current_frame++;
|
|
143
|
+
if (on_progress && current_frame % 5000 === 0) {
|
|
144
|
+
on_progress({
|
|
145
|
+
current: (current_frame / total_frames) * 100,
|
|
146
|
+
total: 100,
|
|
147
|
+
stage: `Extracting: ${current_frame}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (this.format === `ase`) {
|
|
153
|
+
const view = new DataView(data);
|
|
154
|
+
const n_items = Number(view.getBigInt64(32, true));
|
|
155
|
+
const offsets_pos = Number(view.getBigInt64(40, true));
|
|
156
|
+
for (let idx = 0; idx < n_items; idx += sample_rate) {
|
|
157
|
+
try {
|
|
158
|
+
const frame_offset = Number(view.getBigInt64(offsets_pos + idx * 8, true));
|
|
159
|
+
const json_length = Number(view.getBigInt64(frame_offset, true));
|
|
160
|
+
if (json_length > MAX_METADATA_SIZE) {
|
|
161
|
+
console.warn(`Skipping large frame ${idx}: ${Math.round(json_length / 1024 / 1024)}MB`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
|
|
165
|
+
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
|
+
}
|
|
170
|
+
metadata_list.push(frame_metadata);
|
|
171
|
+
if (on_progress && idx % 5000 === 0) {
|
|
172
|
+
on_progress({
|
|
173
|
+
current: (idx / n_items) * 100,
|
|
174
|
+
total: 100,
|
|
175
|
+
stage: `Extracting ASE: ${idx}/${n_items}`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
console.warn(`Failed to extract metadata from ASE frame ${idx}:`, error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return metadata_list;
|
|
185
|
+
}
|
|
186
|
+
load_xyz_frame(data, frame_number) {
|
|
187
|
+
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
|
+
if (!lines[line_idx]?.trim()) {
|
|
191
|
+
line_idx++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const num_atoms = parseInt(lines[line_idx].trim(), 10);
|
|
195
|
+
if (isNaN(num_atoms) || num_atoms <= 0) {
|
|
196
|
+
line_idx++;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
line_idx += 2 + num_atoms;
|
|
200
|
+
current_frame++;
|
|
201
|
+
}
|
|
202
|
+
if (line_idx >= lines.length)
|
|
203
|
+
return null;
|
|
204
|
+
const num_atoms = parseInt(lines[line_idx].trim(), 10);
|
|
205
|
+
if (isNaN(num_atoms) || line_idx + num_atoms + 1 >= lines.length)
|
|
206
|
+
return null;
|
|
207
|
+
const comment = lines[line_idx + 1] || ``;
|
|
208
|
+
const positions = [];
|
|
209
|
+
const elements = [];
|
|
210
|
+
for (let idx = 0; idx < num_atoms; idx++) {
|
|
211
|
+
const parts = lines[line_idx + 2 + idx]?.trim().split(/\s+/);
|
|
212
|
+
if (parts?.length >= 4) {
|
|
213
|
+
const x_coord = parseFloat(parts[1]);
|
|
214
|
+
const y_coord = parseFloat(parts[2]);
|
|
215
|
+
const z_coord = parseFloat(parts[3]);
|
|
216
|
+
if (!Number.isFinite(x_coord) || !Number.isFinite(y_coord) ||
|
|
217
|
+
!Number.isFinite(z_coord)) {
|
|
218
|
+
console.warn(`Skipping XYZ atom with invalid coordinates in indexed frame ${frame_number} at line ${line_idx + 2 + idx}`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const raw_symbol = parts[0];
|
|
222
|
+
const element_symbol = coerce_element_symbol(raw_symbol);
|
|
223
|
+
if (!element_symbol) {
|
|
224
|
+
console.warn(`Skipping XYZ atom with unknown element symbol "${raw_symbol}" in indexed frame ${frame_number}`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
elements.push(element_symbol);
|
|
228
|
+
positions.push([x_coord, y_coord, z_coord]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const metadata = this.parse_xyz_metadata(comment, frame_number);
|
|
232
|
+
return create_trajectory_frame(positions, elements, undefined, undefined, metadata.step, metadata.properties);
|
|
233
|
+
}
|
|
234
|
+
load_ase_frame(data, frame_number) {
|
|
235
|
+
try {
|
|
236
|
+
const view = new DataView(data);
|
|
237
|
+
const n_items = Number(view.getBigInt64(32, true));
|
|
238
|
+
const offsets_pos = Number(view.getBigInt64(40, true));
|
|
239
|
+
if (frame_number >= n_items)
|
|
240
|
+
return null;
|
|
241
|
+
const frame_offset = Number(view.getBigInt64(offsets_pos + frame_number * 8, true));
|
|
242
|
+
const json_length = Number(view.getBigInt64(frame_offset, true));
|
|
243
|
+
const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
|
|
244
|
+
const positions_ref = frame_data[`positions.`] || frame_data.positions;
|
|
245
|
+
const positions = positions_ref?.ndarray
|
|
246
|
+
? read_ndarray_from_view(view, positions_ref)
|
|
247
|
+
: positions_ref;
|
|
248
|
+
const numbers_ref = frame_data[`numbers.`] || frame_data.numbers ||
|
|
249
|
+
this.global_numbers;
|
|
250
|
+
const numbers = numbers_ref?.ndarray
|
|
251
|
+
? read_ndarray_from_view(view, numbers_ref).flat()
|
|
252
|
+
: numbers_ref;
|
|
253
|
+
if (numbers)
|
|
254
|
+
this.global_numbers = numbers;
|
|
255
|
+
if (!numbers || !positions)
|
|
256
|
+
throw new Error(`Missing atomic numbers or positions`);
|
|
257
|
+
const cell = frame_data.cell ? validate_3x3_matrix(frame_data.cell) : undefined;
|
|
258
|
+
const metadata = {
|
|
259
|
+
step: frame_number,
|
|
260
|
+
...(frame_data.calculator || {}),
|
|
261
|
+
...(frame_data.info || {}),
|
|
262
|
+
};
|
|
263
|
+
if (cell) {
|
|
264
|
+
try {
|
|
265
|
+
metadata.volume = Math.abs(math.det_3x3(cell));
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.warn(`Failed to calculate volume for frame ${frame_number}:`, error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc || [true, true, true], frame_number, metadata);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.warn(`Failed to load ASE frame ${frame_number}:`, error);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
parse_xyz_metadata(comment, frame_number) {
|
|
279
|
+
const properties = {};
|
|
280
|
+
const patterns = {
|
|
281
|
+
energy: /(?:energy|E|etot)\s*[=:]?\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/i,
|
|
282
|
+
volume: /(?:volume|vol|V)\s*[=:]?\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/i,
|
|
283
|
+
pressure: /(?:pressure|press|P)\s*[=:]?\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/i,
|
|
284
|
+
force_max: /(?:max_force|fmax)\s*[=:]?\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/i,
|
|
285
|
+
};
|
|
286
|
+
Object.entries(patterns).forEach(([key, pattern]) => {
|
|
287
|
+
const match = pattern.exec(comment);
|
|
288
|
+
if (match)
|
|
289
|
+
properties[key] = parseFloat(match[1]);
|
|
290
|
+
});
|
|
291
|
+
const step_match = comment.match(/(?:step|frame)\s*[=:]?\s*(\d+)/i);
|
|
292
|
+
const step = step_match ? parseInt(step_match[1]) : frame_number;
|
|
293
|
+
return { frame_number, step, properties };
|
|
294
|
+
}
|
|
295
|
+
parse_ase_metadata(frame_data, frame_number) {
|
|
296
|
+
const properties = {};
|
|
297
|
+
const step = frame_number;
|
|
298
|
+
if (frame_data.calculator && typeof frame_data.calculator === `object`) {
|
|
299
|
+
const calculator = frame_data.calculator;
|
|
300
|
+
const calc_properties = [
|
|
301
|
+
`energy`,
|
|
302
|
+
`potential_energy`,
|
|
303
|
+
`kinetic_energy`,
|
|
304
|
+
`total_energy`,
|
|
305
|
+
];
|
|
306
|
+
for (const prop of calc_properties) {
|
|
307
|
+
if (prop in calculator && typeof calculator[prop] === `number`) {
|
|
308
|
+
properties[prop] = calculator[prop];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (frame_data.info && typeof frame_data.info === `object`) {
|
|
313
|
+
const info = frame_data.info;
|
|
314
|
+
const info_properties = [
|
|
315
|
+
`force_max`,
|
|
316
|
+
`force_norm`,
|
|
317
|
+
`stress_max`,
|
|
318
|
+
`stress_frobenius`,
|
|
319
|
+
`pressure`,
|
|
320
|
+
`temperature`,
|
|
321
|
+
];
|
|
322
|
+
for (const prop of info_properties) {
|
|
323
|
+
if (prop in info && typeof info[prop] === `number`) {
|
|
324
|
+
properties[prop] = info[prop];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (frame_data.cell && Array.isArray(frame_data.cell)) {
|
|
329
|
+
try {
|
|
330
|
+
const validated_cell = validate_3x3_matrix(frame_data.cell);
|
|
331
|
+
properties.volume = Math.abs(math.det_3x3(validated_cell));
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.warn(`Failed to calculate volume for ASE frame ${frame_number}:`, error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { frame_number, step, properties };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ElementSymbol } from '../element';
|
|
2
|
+
import * as math from '../math';
|
|
3
|
+
import type { AnyStructure, Pbc } from '../structure';
|
|
4
|
+
import type { TrajectoryFrame } from './index';
|
|
5
|
+
export declare function is_valid_element_symbol(symbol: string): symbol is ElementSymbol;
|
|
6
|
+
export declare function coerce_element_symbol(symbol: string): ElementSymbol | undefined;
|
|
7
|
+
export declare function validate_3x3_matrix(data: unknown): math.Matrix3x3;
|
|
8
|
+
export declare const convert_atomic_numbers: (numbers: number[]) => ElementSymbol[];
|
|
9
|
+
export declare const get_inverse_matrix: (matrix: math.Matrix3x3) => math.Matrix3x3;
|
|
10
|
+
export declare const create_structure: (positions: number[][], elements: ElementSymbol[], lattice_matrix?: math.Matrix3x3, pbc?: Pbc, force_data?: number[][]) => AnyStructure;
|
|
11
|
+
export declare const create_trajectory_frame: (positions: number[][], elements: ElementSymbol[], lattice_matrix: math.Matrix3x3 | undefined, pbc: Pbc | undefined, step: number, metadata?: Record<string, unknown>) => TrajectoryFrame;
|
|
12
|
+
export declare const read_ndarray_from_view: (view: DataView, ref: {
|
|
13
|
+
ndarray: unknown[];
|
|
14
|
+
}) => number[][];
|
|
15
|
+
export declare function count_xyz_frames(data: string): number;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Shared utilities for trajectory parsing
|
|
2
|
+
import { ATOMIC_NUMBER_TO_SYMBOL } from '../composition/parse';
|
|
3
|
+
import { ELEM_SYMBOLS } from '../labels';
|
|
4
|
+
import * as math from '../math';
|
|
5
|
+
const element_symbol_set = new Set(ELEM_SYMBOLS);
|
|
6
|
+
export function is_valid_element_symbol(symbol) {
|
|
7
|
+
return element_symbol_set.has(symbol);
|
|
8
|
+
}
|
|
9
|
+
export function coerce_element_symbol(symbol) {
|
|
10
|
+
return is_valid_element_symbol(symbol) ? symbol : undefined;
|
|
11
|
+
}
|
|
12
|
+
// Validate that data is a proper 3x3 matrix
|
|
13
|
+
// Accepts both regular arrays and typed arrays (Float32Array, Float64Array, etc.)
|
|
14
|
+
export function validate_3x3_matrix(data) {
|
|
15
|
+
if (!Array.isArray(data) || data.length !== 3) {
|
|
16
|
+
throw new Error(`Expected 3x3 matrix, got array of length ${Array.isArray(data) ? data.length : `non-array`}`);
|
|
17
|
+
}
|
|
18
|
+
const is_valid_row = (row) => {
|
|
19
|
+
if (Array.isArray(row))
|
|
20
|
+
return row.length === 3;
|
|
21
|
+
if (!ArrayBuffer.isView(row))
|
|
22
|
+
return false;
|
|
23
|
+
return `length` in row && typeof row.length === `number` && row.length === 3;
|
|
24
|
+
};
|
|
25
|
+
if (!data.every(is_valid_row)) {
|
|
26
|
+
throw new Error(`Invalid 3x3 matrix structure`);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
export const convert_atomic_numbers = (numbers) => numbers.map((num) => {
|
|
31
|
+
const symbol = ATOMIC_NUMBER_TO_SYMBOL[num];
|
|
32
|
+
if (!symbol || !is_valid_element_symbol(symbol)) {
|
|
33
|
+
throw new Error(`Unknown atomic number in trajectory data: ${num}`);
|
|
34
|
+
}
|
|
35
|
+
return symbol;
|
|
36
|
+
});
|
|
37
|
+
// Cache inverse matrices by original matrix reference for performance
|
|
38
|
+
// IMPORTANT: This cache assumes lattice matrices are immutable. Mutating a cached
|
|
39
|
+
// matrix in place yields incorrect inverses. Always create new matrix instances
|
|
40
|
+
// if modifications are needed.
|
|
41
|
+
const matrix_cache = new WeakMap();
|
|
42
|
+
export const get_inverse_matrix = (matrix) => {
|
|
43
|
+
const cached = matrix_cache.get(matrix);
|
|
44
|
+
if (cached)
|
|
45
|
+
return cached;
|
|
46
|
+
const inverse = math.matrix_inverse_3x3(matrix);
|
|
47
|
+
matrix_cache.set(matrix, inverse);
|
|
48
|
+
return inverse;
|
|
49
|
+
};
|
|
50
|
+
export const create_structure = (positions, elements, lattice_matrix, pbc, force_data) => {
|
|
51
|
+
if (positions.length !== elements.length) {
|
|
52
|
+
throw new Error(`create_structure requires matching positions and elements lengths, got positions=${positions.length}, elements=${elements.length}`);
|
|
53
|
+
}
|
|
54
|
+
const inv_matrix = lattice_matrix ? get_inverse_matrix(lattice_matrix) : null;
|
|
55
|
+
const is_valid_vec3 = (coords) => Array.isArray(coords) &&
|
|
56
|
+
coords.length === 3 &&
|
|
57
|
+
coords.every((value) => typeof value === `number` && Number.isFinite(value));
|
|
58
|
+
const sites = positions.map((pos, idx) => {
|
|
59
|
+
if (!is_valid_vec3(pos)) {
|
|
60
|
+
throw new Error(`Invalid position at index ${idx}: expected 3 finite coordinates`);
|
|
61
|
+
}
|
|
62
|
+
const xyz = pos;
|
|
63
|
+
const abc = inv_matrix
|
|
64
|
+
? math.mat3x3_vec3_multiply(inv_matrix, xyz)
|
|
65
|
+
: [0, 0, 0];
|
|
66
|
+
const force = force_data?.[idx];
|
|
67
|
+
const properties = is_valid_vec3(force) ? { force } : {};
|
|
68
|
+
return {
|
|
69
|
+
species: [{ element: elements[idx], occu: 1, oxidation_state: 0 }],
|
|
70
|
+
abc,
|
|
71
|
+
xyz,
|
|
72
|
+
label: `${elements[idx]}${idx + 1}`,
|
|
73
|
+
properties,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
return lattice_matrix
|
|
77
|
+
? {
|
|
78
|
+
sites,
|
|
79
|
+
lattice: {
|
|
80
|
+
matrix: lattice_matrix,
|
|
81
|
+
...math.calc_lattice_params(lattice_matrix),
|
|
82
|
+
pbc: pbc || [true, true, true],
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: { sites };
|
|
86
|
+
};
|
|
87
|
+
export const create_trajectory_frame = (positions, elements, lattice_matrix, pbc, step, metadata = {}) => ({
|
|
88
|
+
structure: create_structure(positions, elements, lattice_matrix, pbc),
|
|
89
|
+
step,
|
|
90
|
+
metadata,
|
|
91
|
+
});
|
|
92
|
+
// Shared utility to read ndarray data from binary format
|
|
93
|
+
export const read_ndarray_from_view = (view, ref) => {
|
|
94
|
+
const [shape, dtype, array_offset] = ref.ndarray;
|
|
95
|
+
const total = shape.reduce((product, dim_size) => product * dim_size, 1);
|
|
96
|
+
const data = [];
|
|
97
|
+
let pos = array_offset;
|
|
98
|
+
const readers = {
|
|
99
|
+
int64: {
|
|
100
|
+
bytes_per_element: 8,
|
|
101
|
+
read: () => {
|
|
102
|
+
const value = Number(view.getBigInt64(pos, true));
|
|
103
|
+
pos += 8;
|
|
104
|
+
return value;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
int32: {
|
|
108
|
+
bytes_per_element: 4,
|
|
109
|
+
read: () => {
|
|
110
|
+
const value = view.getInt32(pos, true);
|
|
111
|
+
pos += 4;
|
|
112
|
+
return value;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
float64: {
|
|
116
|
+
bytes_per_element: 8,
|
|
117
|
+
read: () => {
|
|
118
|
+
const value = view.getFloat64(pos, true);
|
|
119
|
+
pos += 8;
|
|
120
|
+
return value;
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
float32: {
|
|
124
|
+
bytes_per_element: 4,
|
|
125
|
+
read: () => {
|
|
126
|
+
const value = view.getFloat32(pos, true);
|
|
127
|
+
pos += 4;
|
|
128
|
+
return value;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const reader_config = readers[dtype];
|
|
133
|
+
if (!reader_config)
|
|
134
|
+
throw new Error(`Unsupported dtype: ${dtype}`);
|
|
135
|
+
if (!Number.isInteger(array_offset) || array_offset < 0) {
|
|
136
|
+
throw new Error(`Invalid array_offset: expected non-negative integer, got ${array_offset}`);
|
|
137
|
+
}
|
|
138
|
+
const bytes_needed = total * reader_config.bytes_per_element;
|
|
139
|
+
if (array_offset + bytes_needed > view.byteLength) {
|
|
140
|
+
throw new Error(`Out-of-bounds read: array_offset + bytesNeeded exceeds view.byteLength`);
|
|
141
|
+
}
|
|
142
|
+
for (let idx = 0; idx < total; idx++)
|
|
143
|
+
data.push(reader_config.read());
|
|
144
|
+
return shape.length === 1
|
|
145
|
+
? [data]
|
|
146
|
+
: shape.length === 2
|
|
147
|
+
? Array.from({ length: shape[0] }, (_, idx) => data.slice(idx * shape[1], (idx + 1) * shape[1]))
|
|
148
|
+
: (() => {
|
|
149
|
+
throw new Error(`Unsupported shape`);
|
|
150
|
+
})();
|
|
151
|
+
};
|
|
152
|
+
// Unified frame counting for XYZ
|
|
153
|
+
export function count_xyz_frames(data) {
|
|
154
|
+
if (!data || typeof data !== `string`)
|
|
155
|
+
return 0;
|
|
156
|
+
const lines = data.trim().split(/\r?\n/);
|
|
157
|
+
let frame_count = 0;
|
|
158
|
+
let line_idx = 0;
|
|
159
|
+
while (line_idx < lines.length) {
|
|
160
|
+
if (!lines[line_idx]?.trim()) {
|
|
161
|
+
line_idx++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const num_atoms = parseInt(lines[line_idx].trim(), 10);
|
|
165
|
+
if (isNaN(num_atoms) || num_atoms <= 0 || line_idx + num_atoms + 2 > lines.length) {
|
|
166
|
+
line_idx++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Quick validation of first few atom lines
|
|
170
|
+
let valid_coords = 0;
|
|
171
|
+
for (let idx = 0; idx < Math.min(num_atoms, 3); idx++) {
|
|
172
|
+
const parts = lines[line_idx + 2 + idx]?.trim().split(/\s+/);
|
|
173
|
+
if (parts?.length >= 4 && isNaN(parseInt(parts[0])) && parts[0].length <= 3) {
|
|
174
|
+
if (parts.slice(1, 4).every((coord) => !isNaN(parseFloat(coord))))
|
|
175
|
+
valid_coords++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (valid_coords >= Math.min(num_atoms, 3)) {
|
|
179
|
+
frame_count++;
|
|
180
|
+
line_idx += 2 + num_atoms;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
line_idx++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return frame_count;
|
|
187
|
+
}
|
|
@@ -6,6 +6,7 @@ export { default as TrajectoryError } from './TrajectoryError.svelte';
|
|
|
6
6
|
export { default as TrajectoryExportPane } from './TrajectoryExportPane.svelte';
|
|
7
7
|
export { default as TrajectoryInfoPane } from './TrajectoryInfoPane.svelte';
|
|
8
8
|
export type TrajectoryFormat = `hdf5` | `json` | `xyz` | `xdatcar` | `traj` | `unknown`;
|
|
9
|
+
export type { AtomTypeMapping } from './types';
|
|
9
10
|
export interface ParseProgress {
|
|
10
11
|
current: number;
|
|
11
12
|
total: number;
|
package/dist/trajectory/index.js
CHANGED
|
@@ -20,8 +20,11 @@ export function validate_trajectory(trajectory) {
|
|
|
20
20
|
if (typeof total_frames !== `number` || total_frames < 1) {
|
|
21
21
|
errors.push(`total_frames must be a positive number, got ${total_frames}`);
|
|
22
22
|
}
|
|
23
|
-
else if (indexed_frames
|
|
24
|
-
|
|
23
|
+
else if (indexed_frames?.length) {
|
|
24
|
+
const last_indexed_frame = indexed_frames.at(-1);
|
|
25
|
+
if (last_indexed_frame && last_indexed_frame.frame_number >= total_frames) {
|
|
26
|
+
errors.push(`indexed_frames contains frame_number >= total_frames (${total_frames})`);
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
if (is_indexed === true && !indexed_frames?.length) {
|
|
@@ -36,8 +39,12 @@ export function validate_trajectory(trajectory) {
|
|
|
36
39
|
if (typeof frame_idx.frame_number !== `number`) {
|
|
37
40
|
errors.push(`indexed_frames[${idx}] missing or invalid frame_number`);
|
|
38
41
|
}
|
|
39
|
-
else if (frame_idx.frame_number
|
|
40
|
-
errors.push(`indexed_frames[${idx}] frame_number (${frame_idx.frame_number})
|
|
42
|
+
else if (frame_idx.frame_number < 0) {
|
|
43
|
+
errors.push(`indexed_frames[${idx}] frame_number (${frame_idx.frame_number}) must be non-negative`);
|
|
44
|
+
}
|
|
45
|
+
else if (idx > 0 &&
|
|
46
|
+
frame_idx.frame_number <= indexed_frames[idx - 1].frame_number) {
|
|
47
|
+
errors.push(`indexed_frames[${idx}] frame_number (${frame_idx.frame_number}) must be strictly increasing`);
|
|
41
48
|
}
|
|
42
49
|
if (typeof frame_idx.byte_offset !== `number`) {
|
|
43
50
|
errors.push(`indexed_frames[${idx}] missing or invalid byte_offset`);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// ASE trajectory (.traj) parsing - binary format
|
|
2
|
+
import { MAX_SAFE_STRING_LENGTH } from '../constants';
|
|
3
|
+
import { convert_atomic_numbers, create_trajectory_frame, read_ndarray_from_view, validate_3x3_matrix, } from '../helpers';
|
|
4
|
+
export function parse_ase_trajectory(buffer, filename) {
|
|
5
|
+
const view = new DataView(buffer);
|
|
6
|
+
let offset = 0;
|
|
7
|
+
const signature = new TextDecoder().decode(new Uint8Array(buffer, 0, 8));
|
|
8
|
+
if (signature !== `- of Ulm`)
|
|
9
|
+
throw new Error(`Invalid ASE trajectory`);
|
|
10
|
+
offset += 24;
|
|
11
|
+
const _version = Number(view.getBigInt64(offset, true));
|
|
12
|
+
offset += 8;
|
|
13
|
+
const n_items = Number(view.getBigInt64(offset, true));
|
|
14
|
+
offset += 8;
|
|
15
|
+
const offsets_pos = Number(view.getBigInt64(offset, true));
|
|
16
|
+
if (n_items <= 0)
|
|
17
|
+
throw new Error(`Invalid frame count`);
|
|
18
|
+
if (offsets_pos < 0 ||
|
|
19
|
+
offsets_pos + n_items * 8 > buffer.byteLength) {
|
|
20
|
+
throw new Error(`Invalid ASE frame offsets table bounds: offsets_pos=${offsets_pos}, n_items=${n_items}, byte_length=${buffer.byteLength}`);
|
|
21
|
+
}
|
|
22
|
+
const frame_offsets = Array.from({ length: n_items }, (_, idx) => Number(view.getBigInt64(offsets_pos + idx * 8, true)));
|
|
23
|
+
const frames = [];
|
|
24
|
+
let global_numbers;
|
|
25
|
+
for (let idx = 0; idx < n_items; idx++) {
|
|
26
|
+
try {
|
|
27
|
+
offset = frame_offsets[idx];
|
|
28
|
+
const json_length = Number(view.getBigInt64(offset, true));
|
|
29
|
+
offset += 8;
|
|
30
|
+
if (json_length > MAX_SAFE_STRING_LENGTH) {
|
|
31
|
+
console.warn(`Skipping frame ${idx + 1}/${n_items}: too large`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, offset, json_length)));
|
|
35
|
+
const positions_ref = frame_data[`positions.`] || frame_data.positions;
|
|
36
|
+
const positions = positions_ref?.ndarray
|
|
37
|
+
? read_ndarray_from_view(view, positions_ref)
|
|
38
|
+
: positions_ref;
|
|
39
|
+
const numbers_ref = frame_data[`numbers.`] || frame_data.numbers || global_numbers;
|
|
40
|
+
const numbers = numbers_ref?.ndarray
|
|
41
|
+
? read_ndarray_from_view(view, numbers_ref).flat()
|
|
42
|
+
: numbers_ref;
|
|
43
|
+
if (numbers)
|
|
44
|
+
global_numbers = numbers;
|
|
45
|
+
if (!numbers || !positions) {
|
|
46
|
+
console.warn(`Skipping ASE frame ${idx + 1}/${n_items}: missing ${!numbers ? `numbers` : `positions`}`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const elements = convert_atomic_numbers(numbers);
|
|
50
|
+
const metadata = {
|
|
51
|
+
step: idx,
|
|
52
|
+
...(frame_data.calculator || {}),
|
|
53
|
+
...(frame_data.info || {}),
|
|
54
|
+
};
|
|
55
|
+
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));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.warn(`Error processing frame ${idx + 1}/${n_items}:`, error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (frames.length === 0)
|
|
62
|
+
throw new Error(`No valid frames found`);
|
|
63
|
+
const first_struct = frames[0]?.structure;
|
|
64
|
+
const periodic_boundary_conditions = first_struct !== null && first_struct !== undefined &&
|
|
65
|
+
typeof first_struct === `object` && `lattice` in first_struct
|
|
66
|
+
? first_struct.lattice.pbc
|
|
67
|
+
: [true, true, true];
|
|
68
|
+
const metadata = {
|
|
69
|
+
filename,
|
|
70
|
+
source_format: `ase_trajectory`,
|
|
71
|
+
frame_count: frames.length,
|
|
72
|
+
total_atoms: global_numbers?.length || 0,
|
|
73
|
+
periodic_boundary_conditions,
|
|
74
|
+
};
|
|
75
|
+
return { frames, metadata };
|
|
76
|
+
}
|