matterviz 0.3.2 → 0.3.4
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/EmptyState.svelte +10 -2
- package/dist/FilePicker.svelte +123 -82
- package/dist/Icon.svelte +18 -12
- package/dist/MillerIndexInput.svelte +27 -21
- package/dist/api/optimade.js +6 -6
- package/dist/app.css +216 -207
- package/dist/brillouin/BrillouinZone.svelte +292 -149
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
- package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
- package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
- package/dist/brillouin/compute.js +11 -6
- package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
- package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
- package/dist/chempot-diagram/async-compute.svelte.js +77 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +11 -0
- package/dist/chempot-diagram/color.js +1 -2
- package/dist/chempot-diagram/compute.d.ts +10 -0
- package/dist/chempot-diagram/compute.js +250 -88
- package/dist/chempot-diagram/index.d.ts +2 -1
- package/dist/chempot-diagram/index.js +2 -1
- package/dist/chempot-diagram/temperature.js +8 -9
- package/dist/chempot-diagram/types.d.ts +3 -0
- package/dist/chempot-diagram/types.js +1 -0
- package/dist/colors/index.d.ts +1 -1
- package/dist/colors/index.js +5 -3
- package/dist/composition/BarChart.svelte +128 -55
- package/dist/composition/BubbleChart.svelte +102 -49
- package/dist/composition/Composition.svelte +100 -79
- package/dist/composition/Formula.svelte +108 -62
- package/dist/composition/FormulaFilter.svelte +665 -537
- package/dist/composition/PieChart.svelte +183 -108
- package/dist/composition/format.d.ts +5 -0
- package/dist/composition/format.js +20 -3
- package/dist/composition/parse.js +14 -9
- package/dist/convex-hull/ConvexHull.svelte +93 -40
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +549 -360
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +115 -28
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
- package/dist/convex-hull/ConvexHullStats.svelte +425 -328
- package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
- package/dist/convex-hull/GasPressureControls.svelte +104 -61
- package/dist/convex-hull/StructurePopup.svelte +25 -4
- package/dist/convex-hull/TemperatureSlider.svelte +45 -25
- package/dist/convex-hull/barycentric-coords.js +13 -7
- package/dist/convex-hull/demo-temperature.js +8 -4
- package/dist/convex-hull/gas-thermodynamics.js +17 -12
- package/dist/convex-hull/helpers.d.ts +9 -0
- package/dist/convex-hull/helpers.js +77 -34
- package/dist/convex-hull/thermodynamics.js +61 -56
- package/dist/convex-hull/types.d.ts +9 -14
- package/dist/convex-hull/types.js +0 -17
- package/dist/coordination/CoordinationBarPlot.svelte +227 -154
- package/dist/element/BohrAtom.svelte +55 -12
- package/dist/element/ElementHeading.svelte +7 -2
- package/dist/element/ElementPhoto.svelte +15 -9
- package/dist/element/ElementStats.svelte +10 -4
- package/dist/element/ElementTile.svelte +137 -73
- package/dist/element/Nucleus.svelte +39 -11
- package/dist/element/data.js +1 -1
- package/dist/feedback/ClickFeedback.svelte +16 -5
- package/dist/feedback/DragOverlay.svelte +10 -2
- package/dist/feedback/Spinner.svelte +4 -2
- package/dist/feedback/StatusMessage.svelte +8 -2
- package/dist/fermi-surface/FermiSlice.svelte +118 -88
- package/dist/fermi-surface/FermiSurface.svelte +328 -187
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
- package/dist/fermi-surface/compute.js +16 -20
- package/dist/fermi-surface/parse.js +24 -14
- package/dist/fermi-surface/symmetry.js +2 -7
- package/dist/fermi-surface/types.d.ts +3 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
- package/dist/icons.js +47 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.d.ts +3 -0
- package/dist/io/export.js +129 -143
- package/dist/io/is-binary.js +2 -3
- package/dist/io/url-drop.js +1 -2
- package/dist/isosurface/Isosurface.svelte +202 -148
- package/dist/isosurface/IsosurfaceControls.svelte +46 -28
- package/dist/isosurface/parse.js +34 -29
- package/dist/isosurface/slice.js +5 -10
- package/dist/isosurface/types.d.ts +2 -1
- package/dist/isosurface/types.js +61 -12
- package/dist/labels.js +11 -8
- package/dist/layout/FullscreenToggle.svelte +11 -2
- package/dist/layout/InfoCard.svelte +38 -6
- package/dist/layout/InfoTag.svelte +63 -32
- package/dist/layout/PropertyFilter.svelte +82 -37
- package/dist/layout/SettingsSection.svelte +85 -55
- package/dist/layout/SubpageGrid.svelte +10 -2
- package/dist/layout/json-tree/JsonNode.svelte +183 -138
- package/dist/layout/json-tree/JsonTree.svelte +499 -413
- package/dist/layout/json-tree/JsonValue.svelte +127 -99
- package/dist/layout/json-tree/utils.js +4 -2
- package/dist/marching-cubes.js +25 -2
- package/dist/math.d.ts +13 -17
- package/dist/math.js +133 -67
- package/dist/overlays/ContextMenu.svelte +65 -40
- package/dist/overlays/DraggablePane.svelte +211 -139
- package/dist/periodic-table/PeriodicTable.svelte +278 -145
- package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
- package/dist/periodic-table/PropertySelect.svelte +25 -7
- package/dist/periodic-table/TableInset.svelte +8 -3
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
- package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
- package/dist/phase-diagram/build-diagram.js +9 -9
- package/dist/phase-diagram/colors.js +1 -3
- package/dist/phase-diagram/parse.js +10 -9
- package/dist/phase-diagram/svg-to-diagram.js +53 -49
- package/dist/phase-diagram/utils.d.ts +1 -0
- package/dist/phase-diagram/utils.js +80 -25
- package/dist/plot/AxisLabel.svelte +28 -3
- package/dist/plot/BarPlot.svelte +1182 -734
- package/dist/plot/BarPlot.svelte.d.ts +2 -2
- package/dist/plot/BarPlotControls.svelte +31 -5
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +479 -329
- package/dist/plot/ColorScaleSelect.svelte +27 -6
- package/dist/plot/ElementScatter.svelte +36 -15
- package/dist/plot/FillArea.svelte +152 -95
- package/dist/plot/Histogram.svelte +934 -571
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte +53 -9
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +34 -11
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/Line.svelte +63 -28
- package/dist/plot/PlotControls.svelte +157 -114
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +174 -91
- package/dist/plot/PlotTooltip.svelte +45 -6
- package/dist/plot/PortalSelect.svelte +175 -147
- package/dist/plot/ReferenceLine.svelte +76 -22
- package/dist/plot/ReferenceLine3D.svelte +132 -107
- package/dist/plot/ReferencePlane.svelte +146 -121
- package/dist/plot/ScatterPlot.svelte +1681 -1091
- package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3D.svelte +256 -131
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +113 -63
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
- package/dist/plot/ScatterPlot3DScene.svelte +608 -403
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +65 -25
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPoint.svelte +98 -26
- package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +142 -85
- package/dist/plot/Surface3D.svelte +159 -108
- package/dist/plot/ZeroLines.svelte +55 -3
- package/dist/plot/ZoomRect.svelte +4 -2
- package/dist/plot/axis-utils.js +1 -3
- package/dist/plot/data-cleaning.js +12 -28
- package/dist/plot/data-transform.js +2 -1
- package/dist/plot/fill-utils.js +2 -0
- package/dist/plot/layout.d.ts +4 -1
- package/dist/plot/layout.js +33 -14
- package/dist/plot/reference-line.d.ts +2 -2
- package/dist/plot/reference-line.js +7 -5
- package/dist/plot/scales.js +24 -36
- package/dist/plot/types.d.ts +11 -23
- package/dist/plot/types.js +6 -11
- package/dist/plot/utils/label-placement.d.ts +32 -15
- package/dist/plot/utils/label-placement.js +227 -66
- package/dist/plot/utils/series-visibility.js +2 -3
- package/dist/rdf/RdfPlot.svelte +143 -91
- package/dist/rdf/calc-rdf.js +4 -5
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +107 -0
- package/dist/settings.d.ts +18 -6
- package/dist/settings.js +46 -16
- package/dist/spectral/Bands.svelte +632 -453
- package/dist/spectral/BandsAndDos.svelte +90 -49
- package/dist/spectral/BrillouinBandsDos.svelte +151 -93
- package/dist/spectral/Dos.svelte +389 -258
- package/dist/spectral/helpers.js +55 -43
- package/dist/state.svelte.d.ts +1 -1
- package/dist/state.svelte.js +3 -2
- package/dist/structure/Arrow.svelte +59 -20
- package/dist/structure/AtomLegend.svelte +215 -134
- package/dist/structure/Bond.svelte +73 -47
- package/dist/structure/CanvasTooltip.svelte +10 -2
- package/dist/structure/CellSelect.svelte +72 -45
- package/dist/structure/Cylinder.svelte +33 -17
- package/dist/structure/Lattice.svelte +88 -33
- package/dist/structure/Structure.svelte +1063 -797
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +349 -118
- package/dist/structure/StructureExportPane.svelte +124 -89
- package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +304 -237
- package/dist/structure/StructureScene.svelte +879 -443
- package/dist/structure/StructureScene.svelte.d.ts +15 -7
- package/dist/structure/atom-properties.js +8 -8
- package/dist/structure/bonding.js +6 -7
- package/dist/structure/export.js +14 -29
- package/dist/structure/ferrox-wasm.js +1 -1
- package/dist/structure/index.d.ts +13 -3
- package/dist/structure/index.js +83 -23
- package/dist/structure/measure.d.ts +2 -2
- package/dist/structure/measure.js +4 -44
- package/dist/structure/parse.js +113 -141
- package/dist/structure/partial-occupancy.js +7 -10
- package/dist/structure/pbc.d.ts +1 -0
- package/dist/structure/pbc.js +16 -6
- package/dist/structure/supercell.d.ts +2 -2
- package/dist/structure/supercell.js +12 -22
- package/dist/structure/validation.js +1 -2
- package/dist/symmetry/SymmetryStats.svelte +84 -41
- package/dist/symmetry/WyckoffTable.svelte +26 -6
- package/dist/symmetry/cell-transform.js +5 -3
- package/dist/symmetry/index.js +8 -7
- package/dist/symmetry/spacegroups.js +148 -148
- package/dist/table/HeatmapTable.svelte +790 -554
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +125 -92
- package/dist/table/index.js +2 -4
- package/dist/theme/ThemeControl.svelte +21 -12
- package/dist/time.js +4 -1
- package/dist/tooltip/TooltipContent.svelte +33 -8
- package/dist/trajectory/Trajectory.svelte +758 -558
- package/dist/trajectory/TrajectoryError.svelte +14 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
- package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
- package/dist/trajectory/extract.js +10 -26
- package/dist/trajectory/format-detect.js +5 -5
- package/dist/trajectory/frame-reader.d.ts +1 -1
- package/dist/trajectory/frame-reader.js +5 -12
- package/dist/trajectory/helpers.d.ts +0 -1
- package/dist/trajectory/helpers.js +2 -17
- package/dist/trajectory/index.js +14 -12
- package/dist/trajectory/parse/ase.js +5 -4
- package/dist/trajectory/parse/hdf5.js +26 -18
- package/dist/trajectory/parse/index.js +13 -18
- package/dist/trajectory/parse/lammps.js +17 -7
- package/dist/trajectory/parse/vasp.js +5 -2
- package/dist/trajectory/parse/xyz.js +8 -7
- package/dist/trajectory/plotting.js +13 -8
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -0
- package/dist/xrd/XrdPlot.svelte +337 -247
- package/dist/xrd/broadening.js +14 -9
- package/dist/xrd/calc-xrd.js +12 -18
- package/dist/xrd/parse.d.ts +1 -1
- package/dist/xrd/parse.js +17 -17
- package/package.json +99 -103
- package/readme.md +1 -1
- /package/dist/theme/{themes.js → themes.mjs} +0 -0
package/dist/structure/parse.js
CHANGED
|
@@ -43,6 +43,19 @@ function validate_element_symbol(symbol, index) {
|
|
|
43
43
|
console.warn(`Invalid element symbol '${symbol}', using fallback '${fallback}'`);
|
|
44
44
|
return fallback;
|
|
45
45
|
}
|
|
46
|
+
const try_create_cart_to_frac = (lattice_matrix) => {
|
|
47
|
+
try {
|
|
48
|
+
return math.create_cart_to_frac(lattice_matrix);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const approximate_cart_to_frac = (xyz, axis_lengths) => [
|
|
55
|
+
Math.abs(axis_lengths[0]) > math.EPS ? xyz[0] / axis_lengths[0] : 0,
|
|
56
|
+
Math.abs(axis_lengths[1]) > math.EPS ? xyz[1] / axis_lengths[1] : 0,
|
|
57
|
+
Math.abs(axis_lengths[2]) > math.EPS ? xyz[2] / axis_lengths[2] : 0,
|
|
58
|
+
];
|
|
46
59
|
// Parse VASP POSCAR file format
|
|
47
60
|
export function parse_poscar(content) {
|
|
48
61
|
try {
|
|
@@ -97,9 +110,7 @@ export function parse_poscar(content) {
|
|
|
97
110
|
for (let lookahead_idx = 1; lookahead_idx < 10; lookahead_idx++) {
|
|
98
111
|
if (line_index + lookahead_idx >= lines.length)
|
|
99
112
|
break;
|
|
100
|
-
const next_line_first_token = lines[line_index + lookahead_idx]
|
|
101
|
-
.trim()
|
|
102
|
-
.split(/\s+/)[0];
|
|
113
|
+
const next_line_first_token = lines[line_index + lookahead_idx].trim().split(/\s+/)[0];
|
|
103
114
|
const next_token_as_number = parseInt(next_line_first_token);
|
|
104
115
|
if (!isNaN(next_token_as_number)) {
|
|
105
116
|
symbol_lines = lookahead_idx;
|
|
@@ -151,13 +162,18 @@ export function parse_poscar(content) {
|
|
|
151
162
|
}
|
|
152
163
|
// Determine coordinate mode
|
|
153
164
|
const is_direct = coordinate_mode.startsWith(`D`);
|
|
154
|
-
const is_cartesian = coordinate_mode.startsWith(`C`) ||
|
|
155
|
-
coordinate_mode.startsWith(`K`);
|
|
165
|
+
const is_cartesian = coordinate_mode.startsWith(`C`) || coordinate_mode.startsWith(`K`);
|
|
156
166
|
if (!is_direct && !is_cartesian) {
|
|
157
167
|
console.error(`Unknown coordinate mode in POSCAR: ${coordinate_mode}`);
|
|
158
168
|
return null;
|
|
159
169
|
}
|
|
160
170
|
// Parse atomic positions
|
|
171
|
+
const poscar_axis_lengths = scaled_lattice.map((lattice_vec) => Math.hypot(...lattice_vec));
|
|
172
|
+
const poscar_frac_to_cart = math.create_frac_to_cart(scaled_lattice);
|
|
173
|
+
const poscar_cart_to_frac = try_create_cart_to_frac(scaled_lattice);
|
|
174
|
+
if (!is_direct && !poscar_cart_to_frac) {
|
|
175
|
+
console.warn(`POSCAR: singular lattice, using axis-length fallback for cart→frac`);
|
|
176
|
+
}
|
|
161
177
|
const sites = [];
|
|
162
178
|
let atom_index = 0;
|
|
163
179
|
for (let elem_idx = 0; elem_idx < element_symbols.length; elem_idx++) {
|
|
@@ -175,11 +191,7 @@ export function parse_poscar(content) {
|
|
|
175
191
|
if (has_selective_dynamics) {
|
|
176
192
|
const tokens = lines[coord_line_idx].trim().split(/\s+/);
|
|
177
193
|
if (tokens.length >= 6) {
|
|
178
|
-
selective_dynamics = [
|
|
179
|
-
tokens[3] === `T`,
|
|
180
|
-
tokens[4] === `T`,
|
|
181
|
-
tokens[5] === `T`,
|
|
182
|
-
];
|
|
194
|
+
selective_dynamics = [tokens[3] === `T`, tokens[4] === `T`, tokens[5] === `T`];
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
let xyz;
|
|
@@ -187,45 +199,24 @@ export function parse_poscar(content) {
|
|
|
187
199
|
const [x, y, z] = coords;
|
|
188
200
|
if (is_direct) {
|
|
189
201
|
// Store fractional coordinates, wrapping to [0, 1) range
|
|
190
|
-
abc = [x
|
|
191
|
-
|
|
192
|
-
const lattice_transposed = math.transpose_3x3_matrix(scaled_lattice);
|
|
193
|
-
xyz = math.mat3x3_vec3_multiply(lattice_transposed, abc);
|
|
202
|
+
abc = wrap_to_unit_cell([x, y, z]);
|
|
203
|
+
xyz = poscar_frac_to_cart(abc);
|
|
194
204
|
}
|
|
195
|
-
else {
|
|
205
|
+
else {
|
|
206
|
+
// Already Cartesian, scale if needed
|
|
196
207
|
xyz = math.scale([x, y, z], scale_factor);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let raw_abc;
|
|
201
|
-
try {
|
|
202
|
-
const lattice_transposed = math.transpose_3x3_matrix(scaled_lattice);
|
|
203
|
-
const lattice_inv = math.matrix_inverse_3x3(lattice_transposed);
|
|
204
|
-
raw_abc = math.mat3x3_vec3_multiply(lattice_inv, xyz);
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
// Fallback to simplified method if matrix is singular
|
|
208
|
-
raw_abc = [
|
|
209
|
-
xyz[0] / scaled_lattice[0][0],
|
|
210
|
-
xyz[1] / scaled_lattice[1][1],
|
|
211
|
-
xyz[2] / scaled_lattice[2][2],
|
|
212
|
-
];
|
|
213
|
-
}
|
|
208
|
+
const raw_abc = poscar_cart_to_frac
|
|
209
|
+
? poscar_cart_to_frac(xyz)
|
|
210
|
+
: approximate_cart_to_frac(xyz, poscar_axis_lengths);
|
|
214
211
|
// Wrap fractional coordinates to [0, 1) range
|
|
215
|
-
abc =
|
|
216
|
-
raw_abc[0] - Math.floor(raw_abc[0]),
|
|
217
|
-
raw_abc[1] - Math.floor(raw_abc[1]),
|
|
218
|
-
raw_abc[2] - Math.floor(raw_abc[2]),
|
|
219
|
-
];
|
|
212
|
+
abc = wrap_to_unit_cell(raw_abc);
|
|
220
213
|
}
|
|
221
214
|
const site = {
|
|
222
215
|
species: [{ element, occu: 1, oxidation_state: 0 }],
|
|
223
216
|
abc,
|
|
224
217
|
xyz,
|
|
225
218
|
label: `${element}${atom_index + atom_count_idx + 1}`,
|
|
226
|
-
properties: selective_dynamics
|
|
227
|
-
? { selective_dynamics: selective_dynamics }
|
|
228
|
-
: {},
|
|
219
|
+
properties: selective_dynamics ? { selective_dynamics: selective_dynamics } : {},
|
|
229
220
|
};
|
|
230
221
|
sites.push(site);
|
|
231
222
|
}
|
|
@@ -262,9 +253,7 @@ export function parse_xyz(content) {
|
|
|
262
253
|
let line_idx = 0;
|
|
263
254
|
while (line_idx < all_lines.length) {
|
|
264
255
|
const numAtoms = parseInt(all_lines[line_idx].trim(), 10);
|
|
265
|
-
if (!isNaN(numAtoms) &&
|
|
266
|
-
numAtoms > 0 &&
|
|
267
|
-
line_idx + numAtoms + 1 < all_lines.length) {
|
|
256
|
+
if (!isNaN(numAtoms) && numAtoms > 0 && line_idx + numAtoms + 1 < all_lines.length) {
|
|
268
257
|
const frameLines = all_lines.slice(line_idx, line_idx + numAtoms + 2);
|
|
269
258
|
frames.push(frameLines.join(`\n`));
|
|
270
259
|
line_idx += numAtoms + 2;
|
|
@@ -292,7 +281,7 @@ export function parse_xyz(content) {
|
|
|
292
281
|
const comment_line = lines[1];
|
|
293
282
|
let lattice;
|
|
294
283
|
// Check for extended XYZ lattice information in comment line
|
|
295
|
-
const lattice_match =
|
|
284
|
+
const lattice_match = /Lattice="([^"]+)"/.exec(comment_line);
|
|
296
285
|
if (lattice_match) {
|
|
297
286
|
const lattice_values = lattice_match[1].split(/\s+/).map(parse_coordinate);
|
|
298
287
|
if (lattice_values.length === 9) {
|
|
@@ -306,6 +295,13 @@ export function parse_xyz(content) {
|
|
|
306
295
|
}
|
|
307
296
|
}
|
|
308
297
|
// Parse atomic coordinates (starting from line 3)
|
|
298
|
+
const xyz_axis_lengths = lattice ? [lattice.a, lattice.b, lattice.c] : null;
|
|
299
|
+
let xyz_frac_to_cart = null;
|
|
300
|
+
let xyz_cart_to_frac = null;
|
|
301
|
+
if (lattice) {
|
|
302
|
+
xyz_frac_to_cart = math.create_frac_to_cart(lattice.matrix);
|
|
303
|
+
xyz_cart_to_frac = try_create_cart_to_frac(lattice.matrix);
|
|
304
|
+
}
|
|
309
305
|
const sites = [];
|
|
310
306
|
for (let atom_idx = 0; atom_idx < num_atoms; atom_idx++) {
|
|
311
307
|
const line_idx = atom_idx + 2;
|
|
@@ -328,29 +324,14 @@ export function parse_xyz(content) {
|
|
|
328
324
|
const xyz = [coords[0], coords[1], coords[2]];
|
|
329
325
|
// Calculate fractional coordinates if lattice is available
|
|
330
326
|
let abc = [0, 0, 0];
|
|
331
|
-
if (lattice) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
const lattice_transposed = math.transpose_3x3_matrix(lattice.matrix);
|
|
337
|
-
const lattice_inv = math.matrix_inverse_3x3(lattice_transposed);
|
|
338
|
-
abc = math.mat3x3_vec3_multiply(lattice_inv, xyz);
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
// Fallback to simplified method if matrix is singular
|
|
342
|
-
abc = [xyz[0] / lattice.a, xyz[1] / lattice.b, xyz[2] / lattice.c];
|
|
343
|
-
}
|
|
327
|
+
if (lattice && xyz_frac_to_cart && xyz_axis_lengths) {
|
|
328
|
+
abc = xyz_cart_to_frac
|
|
329
|
+
? xyz_cart_to_frac(xyz)
|
|
330
|
+
: approximate_cart_to_frac(xyz, xyz_axis_lengths);
|
|
344
331
|
// Ensure fractional coordinates are wrapped into [0, 1) for consistency
|
|
345
|
-
abc =
|
|
346
|
-
abc[0] - Math.floor(abc[0]),
|
|
347
|
-
abc[1] - Math.floor(abc[1]),
|
|
348
|
-
abc[2] - Math.floor(abc[2]),
|
|
349
|
-
];
|
|
332
|
+
abc = wrap_to_unit_cell(abc);
|
|
350
333
|
// Keep rendered atoms inside primary unit cell by recomputing xyz
|
|
351
|
-
|
|
352
|
-
const lattice_transposed = math.transpose_3x3_matrix(lattice.matrix);
|
|
353
|
-
const wrapped_xyz = math.mat3x3_vec3_multiply(lattice_transposed, abc);
|
|
334
|
+
const wrapped_xyz = xyz_frac_to_cart(abc);
|
|
354
335
|
xyz[0] = wrapped_xyz[0];
|
|
355
336
|
xyz[1] = wrapped_xyz[1];
|
|
356
337
|
xyz[2] = wrapped_xyz[2];
|
|
@@ -395,7 +376,7 @@ const parse_symmetry_expression = (expr_input) => {
|
|
|
395
376
|
tokens.push(current_token);
|
|
396
377
|
for (const token of tokens) {
|
|
397
378
|
// Check if this token is a variable term (x, y, or z with optional sign)
|
|
398
|
-
const var_match =
|
|
379
|
+
const var_match = /^([+-]?)([xyz])$/.exec(token);
|
|
399
380
|
if (var_match) {
|
|
400
381
|
const sign = var_match[1] === `-` ? -1 : 1;
|
|
401
382
|
const var_char = var_match[2];
|
|
@@ -444,9 +425,7 @@ const apply_symmetry_ops = (atom, symmetry_ops, wrap_fractional_coords) => {
|
|
|
444
425
|
return [atom];
|
|
445
426
|
const equivalent_atoms = [];
|
|
446
427
|
const seen = new Set();
|
|
447
|
-
const wrap = (coords) => (
|
|
448
|
-
? coords.map((val) => val - Math.floor(val))
|
|
449
|
-
: coords);
|
|
428
|
+
const wrap = (coords) => wrap_fractional_coords ? wrap_to_unit_cell(coords) : coords;
|
|
450
429
|
// Use 6 decimal places for deduplication to handle floating point imprecision
|
|
451
430
|
// from compound symmetry operations like x-y, -x+y which can produce small errors
|
|
452
431
|
const key = (coords) => `${coords[0].toFixed(6)},${coords[1].toFixed(6)},${coords[2].toFixed(6)}`;
|
|
@@ -455,7 +434,7 @@ const apply_symmetry_ops = (atom, symmetry_ops, wrap_fractional_coords) => {
|
|
|
455
434
|
seen.add(key(base_coords));
|
|
456
435
|
equivalent_atoms.push({ ...atom, coords: base_coords });
|
|
457
436
|
for (const operation of symmetry_ops) {
|
|
458
|
-
const operation_match =
|
|
437
|
+
const operation_match = /['"]([^'"]+)['"]/.exec(operation);
|
|
459
438
|
const expr_str = operation_match ? operation_match[1] : operation.trim();
|
|
460
439
|
const parts = expr_str.split(`,`).map((part) => part.trim());
|
|
461
440
|
if (parts.length !== 3)
|
|
@@ -464,17 +443,18 @@ const apply_symmetry_ops = (atom, symmetry_ops, wrap_fractional_coords) => {
|
|
|
464
443
|
for (let dim = 0; dim < 3; dim++) {
|
|
465
444
|
const { coefficients, translation } = parse_symmetry_expression(parts[dim]);
|
|
466
445
|
// Apply: new_coord = coeff_x * x + coeff_y * y + coeff_z * z + translation
|
|
467
|
-
new_coords[dim] =
|
|
468
|
-
coefficients[
|
|
469
|
-
|
|
470
|
-
|
|
446
|
+
new_coords[dim] =
|
|
447
|
+
coefficients[0] * atom.coords[0] +
|
|
448
|
+
coefficients[1] * atom.coords[1] +
|
|
449
|
+
coefficients[2] * atom.coords[2] +
|
|
450
|
+
translation;
|
|
471
451
|
}
|
|
472
452
|
// Wrap and deduplicate transformed coordinates
|
|
473
453
|
const wrapped = wrap(new_coords);
|
|
474
|
-
const
|
|
475
|
-
if (seen.has(
|
|
454
|
+
const cache_key = key(wrapped);
|
|
455
|
+
if (seen.has(cache_key))
|
|
476
456
|
continue;
|
|
477
|
-
seen.add(
|
|
457
|
+
seen.add(cache_key);
|
|
478
458
|
equivalent_atoms.push({
|
|
479
459
|
...atom,
|
|
480
460
|
coords: wrapped,
|
|
@@ -548,7 +528,7 @@ const parse_cif_atom_data = (raw_data, indices, coords_type) => {
|
|
|
548
528
|
const occu = occupancy >= 0 && raw_data[occupancy]
|
|
549
529
|
? parseFloat(raw_data[occupancy].split(`(`)[0]) || 1.0
|
|
550
530
|
: 1.0;
|
|
551
|
-
const element_symbol = (symbol >= 0 &&
|
|
531
|
+
const element_symbol = (symbol >= 0 && /^([A-Z][a-z]*)/.exec(raw_data[symbol])?.[1]) ||
|
|
552
532
|
raw_data[label]?.match(/([A-Z][a-z]*)/g)?.[0] ||
|
|
553
533
|
(() => {
|
|
554
534
|
throw new Error(`Could not extract element symbol from: ${raw_data.join(` `)}`);
|
|
@@ -604,9 +584,11 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
604
584
|
continue;
|
|
605
585
|
// Check if this loop contains coordinate headers
|
|
606
586
|
const indices_preview = build_cif_atom_site_header_indices(headers);
|
|
607
|
-
const has_coords = (indices_preview.x !== undefined &&
|
|
587
|
+
const has_coords = (indices_preview.x !== undefined &&
|
|
588
|
+
indices_preview.y !== undefined &&
|
|
608
589
|
indices_preview.z !== undefined) ||
|
|
609
|
-
(indices_preview.cart_x !== undefined &&
|
|
590
|
+
(indices_preview.cart_x !== undefined &&
|
|
591
|
+
indices_preview.cart_y !== undefined &&
|
|
610
592
|
indices_preview.cart_z !== undefined);
|
|
611
593
|
if (!has_coords) {
|
|
612
594
|
ii = jj - 1;
|
|
@@ -644,10 +626,12 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
644
626
|
// Parse atom data with error handling
|
|
645
627
|
const header_indices = build_cif_atom_site_header_indices(atom_headers);
|
|
646
628
|
// Determine available coordinate type
|
|
647
|
-
const coords_type = header_indices.x !== undefined &&
|
|
629
|
+
const coords_type = header_indices.x !== undefined &&
|
|
630
|
+
header_indices.y !== undefined &&
|
|
648
631
|
header_indices.z !== undefined
|
|
649
632
|
? `fract`
|
|
650
|
-
: header_indices.cart_x !== undefined &&
|
|
633
|
+
: header_indices.cart_x !== undefined &&
|
|
634
|
+
header_indices.cart_y !== undefined &&
|
|
651
635
|
header_indices.cart_z !== undefined
|
|
652
636
|
? `cart`
|
|
653
637
|
: null;
|
|
@@ -698,21 +682,15 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
698
682
|
const [alpha, beta, gamma] = angles;
|
|
699
683
|
const lattice_matrix = math.cell_to_lattice_matrix(a, b, c, alpha, beta, gamma);
|
|
700
684
|
const lattice_params = math.calc_lattice_params(lattice_matrix);
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
lattice_inv_transposed = math.matrix_inverse_3x3(lattice_transposed);
|
|
705
|
-
}
|
|
706
|
-
catch {
|
|
707
|
-
lattice_inv_transposed = null;
|
|
708
|
-
}
|
|
685
|
+
const frac_to_cart = math.create_frac_to_cart(lattice_matrix);
|
|
686
|
+
const cart_to_frac = try_create_cart_to_frac(lattice_matrix);
|
|
709
687
|
// Create sites with coordinate conversion and symmetry operations
|
|
710
|
-
const wrap_vec3 = (v) => wrap_fractional_coords ? v
|
|
688
|
+
const wrap_vec3 = (v) => (wrap_fractional_coords ? wrap_to_unit_cell(v) : v);
|
|
711
689
|
// Apply symmetry operations to generate all equivalent positions
|
|
712
690
|
const all_sites = [];
|
|
713
691
|
// Normalize symmetry operations (trim/strip quotes) but preserve duplicates; we deduplicate positions later
|
|
714
692
|
const normalized_ops = symmetry_ops
|
|
715
|
-
.map((op) =>
|
|
693
|
+
.map((op) => /['"]([^'"]+)['"]/.exec(op)?.[1] || op.trim())
|
|
716
694
|
.map((op) => op.replace(/\s+/g, ``));
|
|
717
695
|
// Rely on symmetry operations list for all centering/translations to avoid double-counting
|
|
718
696
|
// TODO: Support conventional cells with centering by discovering centering from space group metadata
|
|
@@ -746,7 +724,7 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
746
724
|
const toks = (line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).map((tok) => tok.replace(/['"]/g, ``));
|
|
747
725
|
if (toks.length > Math.max(sym_idx, num_idx)) {
|
|
748
726
|
// Normalize type symbol to bare element (e.g. 'Sn2+' -> 'Sn')
|
|
749
|
-
const match =
|
|
727
|
+
const match = /^([A-Z][a-z]*)/.exec(toks[sym_idx]);
|
|
750
728
|
const sym = match ? match[1] : toks[sym_idx];
|
|
751
729
|
const num = parseInt(toks[num_idx]);
|
|
752
730
|
if (sym && !Number.isNaN(num))
|
|
@@ -784,13 +762,9 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
784
762
|
}
|
|
785
763
|
else {
|
|
786
764
|
const xyz_base = [atom.coords[0], atom.coords[1], atom.coords[2]];
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
atom_abc = wrap_vec3(raw);
|
|
791
|
-
}
|
|
792
|
-
else
|
|
793
|
-
atom_abc = wrap_vec3([xyz_base[0] / a, xyz_base[1] / b, xyz_base[2] / c]);
|
|
765
|
+
const atom_abc = wrap_vec3(cart_to_frac
|
|
766
|
+
? cart_to_frac(xyz_base)
|
|
767
|
+
: approximate_cart_to_frac(xyz_base, [a, b, c]));
|
|
794
768
|
fractional_atom = { ...atom, coords: atom_abc, coords_type: `fract` };
|
|
795
769
|
}
|
|
796
770
|
// First apply symmetry operations in fractional space
|
|
@@ -807,7 +781,7 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
|
|
|
807
781
|
if (seen_site_keys.has(key))
|
|
808
782
|
continue;
|
|
809
783
|
seen_site_keys.add(key);
|
|
810
|
-
const xyz =
|
|
784
|
+
const xyz = frac_to_cart(abc);
|
|
811
785
|
all_sites.push({
|
|
812
786
|
species: [{ element, occu: equiv_atom.occupancy, oxidation_state: 0 }],
|
|
813
787
|
abc,
|
|
@@ -832,15 +806,11 @@ function convert_phonopy_cell(cell) {
|
|
|
832
806
|
// Phonopy stores lattice vectors as rows, use them directly
|
|
833
807
|
const lattice_matrix = cell.lattice;
|
|
834
808
|
// Process each atomic site
|
|
809
|
+
const phonopy_frac_to_cart = math.create_frac_to_cart(lattice_matrix);
|
|
835
810
|
for (const point of cell.points) {
|
|
836
811
|
const element = validate_element_symbol(point.symbol, sites.length);
|
|
837
|
-
const abc = [
|
|
838
|
-
|
|
839
|
-
point.coordinates[1],
|
|
840
|
-
point.coordinates[2],
|
|
841
|
-
];
|
|
842
|
-
// Convert fractional to Cartesian coordinates
|
|
843
|
-
const xyz = math.mat3x3_vec3_multiply(math.transpose_3x3_matrix(lattice_matrix), abc);
|
|
812
|
+
const abc = [point.coordinates[0], point.coordinates[1], point.coordinates[2]];
|
|
813
|
+
const xyz = phonopy_frac_to_cart(abc);
|
|
844
814
|
const properties = {
|
|
845
815
|
mass: point.mass,
|
|
846
816
|
...(point.reduced_to !== undefined && { reduced_to: point.reduced_to }),
|
|
@@ -868,7 +838,7 @@ export function parse_phonopy_yaml(content, cell_type) {
|
|
|
868
838
|
}
|
|
869
839
|
// Check if we're still in the phonon_displacements section
|
|
870
840
|
if (skip_displacements) {
|
|
871
|
-
if (
|
|
841
|
+
if (/^[a-zA-Z_]/.exec(line)) {
|
|
872
842
|
// New top-level key, stop skipping
|
|
873
843
|
skip_displacements = false;
|
|
874
844
|
}
|
|
@@ -921,7 +891,7 @@ export function parse_phonopy_yaml(content, cell_type) {
|
|
|
921
891
|
// Recursively search for a valid structure object in nested JSON
|
|
922
892
|
function find_structure_in_json(obj, visited = new WeakSet()) {
|
|
923
893
|
// Check if current object is null or undefined
|
|
924
|
-
if (obj
|
|
894
|
+
if (obj == null)
|
|
925
895
|
return null;
|
|
926
896
|
if (typeof obj !== `object`)
|
|
927
897
|
return null; // If it's not an object, skip it
|
|
@@ -1073,8 +1043,7 @@ export function parse_structure_file(content, filename) {
|
|
|
1073
1043
|
const coords = parts.slice(1, 4);
|
|
1074
1044
|
// Check if first token looks like an element symbol (not a number)
|
|
1075
1045
|
// and the next 3 tokens look like coordinates (numbers)
|
|
1076
|
-
const is_element_symbol = isNaN(parseInt(first_token)) &&
|
|
1077
|
-
first_token.length <= 3;
|
|
1046
|
+
const is_element_symbol = isNaN(parseInt(first_token)) && first_token.length <= 3;
|
|
1078
1047
|
const are_coordinates = coords.every((coord) => !isNaN(parseFloat(coord)));
|
|
1079
1048
|
if (is_element_symbol && are_coordinates) {
|
|
1080
1049
|
// First token is likely an element symbol, likely XYZ
|
|
@@ -1188,7 +1157,24 @@ export function parse_optimade_from_raw(raw) {
|
|
|
1188
1157
|
const species = species_raw;
|
|
1189
1158
|
// Optimade stores lattice vectors as rows, so use as is
|
|
1190
1159
|
const lattice_matrix = attrs.lattice_vectors;
|
|
1160
|
+
const optimade_lattice_params = lattice_matrix
|
|
1161
|
+
? math.calc_lattice_params(lattice_matrix)
|
|
1162
|
+
: null;
|
|
1191
1163
|
// Parse atomic sites
|
|
1164
|
+
const optimade_exact_cart_to_frac = lattice_matrix
|
|
1165
|
+
? try_create_cart_to_frac(lattice_matrix)
|
|
1166
|
+
: null;
|
|
1167
|
+
const optimade_cart_to_frac = lattice_matrix && optimade_lattice_params
|
|
1168
|
+
? (optimade_exact_cart_to_frac ??
|
|
1169
|
+
((xyz) => approximate_cart_to_frac(xyz, [
|
|
1170
|
+
optimade_lattice_params.a,
|
|
1171
|
+
optimade_lattice_params.b,
|
|
1172
|
+
optimade_lattice_params.c,
|
|
1173
|
+
])))
|
|
1174
|
+
: null;
|
|
1175
|
+
if (lattice_matrix && !optimade_exact_cart_to_frac) {
|
|
1176
|
+
console.warn(`Failed to create exact coordinate converter for OPTIMADE structure`);
|
|
1177
|
+
}
|
|
1192
1178
|
const sites = [];
|
|
1193
1179
|
for (let idx = 0; idx < positions.length; idx++) {
|
|
1194
1180
|
const pos = positions[idx];
|
|
@@ -1200,18 +1186,7 @@ export function parse_optimade_from_raw(raw) {
|
|
|
1200
1186
|
const element = validate_element_symbol(element_symbol, idx);
|
|
1201
1187
|
const xyz = [pos[0], pos[1], pos[2]];
|
|
1202
1188
|
// Calculate fractional coordinates if lattice is available
|
|
1203
|
-
|
|
1204
|
-
if (lattice_matrix) {
|
|
1205
|
-
try {
|
|
1206
|
-
const lattice_transposed = math.transpose_3x3_matrix(lattice_matrix);
|
|
1207
|
-
const lattice_inv = math.matrix_inverse_3x3(lattice_transposed);
|
|
1208
|
-
abc = math.mat3x3_vec3_multiply(lattice_inv, xyz);
|
|
1209
|
-
}
|
|
1210
|
-
catch {
|
|
1211
|
-
// Fallback if matrix inversion fails
|
|
1212
|
-
console.warn(`Failed to calculate fractional coordinates for OPTIMADE structure`);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1189
|
+
const abc = optimade_cart_to_frac ? optimade_cart_to_frac(xyz) : [0, 0, 0];
|
|
1215
1190
|
const site = {
|
|
1216
1191
|
species: [{ element, occu: 1, oxidation_state: 0 }],
|
|
1217
1192
|
abc,
|
|
@@ -1227,9 +1202,8 @@ export function parse_optimade_from_raw(raw) {
|
|
|
1227
1202
|
}
|
|
1228
1203
|
// Create structure object
|
|
1229
1204
|
let lattice;
|
|
1230
|
-
if (lattice_matrix) {
|
|
1231
|
-
|
|
1232
|
-
lattice = { matrix: lattice_matrix, ...lattice_params };
|
|
1205
|
+
if (lattice_matrix && optimade_lattice_params) {
|
|
1206
|
+
lattice = { matrix: lattice_matrix, ...optimade_lattice_params };
|
|
1233
1207
|
}
|
|
1234
1208
|
const structure_result = {
|
|
1235
1209
|
sites,
|
|
@@ -1260,7 +1234,7 @@ function extract_optimade_structure_from_raw(raw) {
|
|
|
1260
1234
|
const candidate = Array.isArray(payload) ? payload[0] : payload;
|
|
1261
1235
|
return is_optimade_structure_object(candidate) ? candidate : null;
|
|
1262
1236
|
}
|
|
1263
|
-
const unwrap_data = (value) =>
|
|
1237
|
+
const unwrap_data = (value) => value && typeof value === `object` && `data` in value
|
|
1264
1238
|
? value.data
|
|
1265
1239
|
: value;
|
|
1266
1240
|
// Type guard: verify minimal OPTIMADE structure shape
|
|
@@ -1271,8 +1245,10 @@ function is_optimade_structure_object(value) {
|
|
|
1271
1245
|
const type = obj.type;
|
|
1272
1246
|
const id = obj.id;
|
|
1273
1247
|
const attributes = obj.attributes;
|
|
1274
|
-
return type === `structures` &&
|
|
1275
|
-
typeof
|
|
1248
|
+
return (type === `structures` &&
|
|
1249
|
+
typeof id === `string` &&
|
|
1250
|
+
typeof attributes === `object` &&
|
|
1251
|
+
attributes !== null);
|
|
1276
1252
|
}
|
|
1277
1253
|
// Convert OPTIMADE structure to Crystal format
|
|
1278
1254
|
export function optimade_to_crystal(optimade_structure) {
|
|
@@ -1290,21 +1266,15 @@ export function optimade_to_crystal(optimade_structure) {
|
|
|
1290
1266
|
const lattice_params = math.calc_lattice_params(lattice_matrix);
|
|
1291
1267
|
// Build species lookup for site properties (mass, concentration, etc.)
|
|
1292
1268
|
const species_map = new Map(species?.map((spec) => [spec.name, spec]));
|
|
1269
|
+
const crystal_cart_to_frac = try_create_cart_to_frac(lattice_matrix) ??
|
|
1270
|
+
((xyz) => approximate_cart_to_frac(xyz, [lattice_params.a, lattice_params.b, lattice_params.c]));
|
|
1293
1271
|
const sites = cartesian_site_positions.map((pos, idx) => {
|
|
1294
1272
|
const element_symbol = species_at_sites[idx];
|
|
1295
1273
|
if (!element_symbol)
|
|
1296
1274
|
throw new Error(`Missing species for site ${idx}`);
|
|
1297
1275
|
const element = validate_element_symbol(element_symbol, idx);
|
|
1298
1276
|
const xyz = [pos[0], pos[1], pos[2]];
|
|
1299
|
-
|
|
1300
|
-
try {
|
|
1301
|
-
const lattice_transposed = math.transpose_3x3_matrix(lattice_matrix);
|
|
1302
|
-
const inv_matrix = math.matrix_inverse_3x3(lattice_transposed);
|
|
1303
|
-
abc = math.mat3x3_vec3_multiply(inv_matrix, xyz);
|
|
1304
|
-
}
|
|
1305
|
-
catch {
|
|
1306
|
-
abc = [0, 0, 0];
|
|
1307
|
-
}
|
|
1277
|
+
const abc = crystal_cart_to_frac ? crystal_cart_to_frac(xyz) : [0, 0, 0];
|
|
1308
1278
|
// Extract mass/concentration from species data
|
|
1309
1279
|
const spec = species_map.get(element_symbol);
|
|
1310
1280
|
const site_props = {};
|
|
@@ -1351,8 +1321,10 @@ export function is_structure_file(filename) {
|
|
|
1351
1321
|
if (/\.(yaml|yml|xml)$/i.test(name) && STRUCT_KEYWORDS_REGEX.test(name))
|
|
1352
1322
|
return true;
|
|
1353
1323
|
// More restrictive keyword detection for JSON files
|
|
1354
|
-
if (/\.json$/i.test(name) &&
|
|
1355
|
-
|
|
1324
|
+
if (/\.json$/i.test(name) &&
|
|
1325
|
+
STRUCT_KEYWORDS_STRICT_REGEX.test(name) &&
|
|
1326
|
+
!TRAJ_KEYWORDS_REGEX.test(name) &&
|
|
1327
|
+
!CONFIG_DIRS_REGEX.test(name))
|
|
1356
1328
|
return true;
|
|
1357
1329
|
// Compressed files - check base filename recursively
|
|
1358
1330
|
if (COMPRESSION_EXTENSIONS_REGEX.test(name)) {
|
|
@@ -10,9 +10,7 @@ const make_render_site = (sites, site_idx, source_site_indices, site_override) =
|
|
|
10
10
|
is_image_atom: source_site_indices.some((source_site_idx) => is_image_atom(sites[source_site_idx])),
|
|
11
11
|
source_site_indices,
|
|
12
12
|
});
|
|
13
|
-
const sq_dist = (xyz_1, xyz_2) => (xyz_1[0] - xyz_2[0]) ** 2 +
|
|
14
|
-
(xyz_1[1] - xyz_2[1]) ** 2 +
|
|
15
|
-
(xyz_1[2] - xyz_2[2]) ** 2;
|
|
13
|
+
const sq_dist = (xyz_1, xyz_2) => (xyz_1[0] - xyz_2[0]) ** 2 + (xyz_1[1] - xyz_2[1]) ** 2 + (xyz_1[2] - xyz_2[2]) ** 2;
|
|
16
14
|
const is_split_partial_site = (site, hidden_elements) => {
|
|
17
15
|
const visible_species = site.species.filter(({ element }) => !hidden_elements.has(element));
|
|
18
16
|
const total_visible_occupancy = visible_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
|
|
@@ -48,7 +46,10 @@ const build_render_sites = (sites, non_grouped_site_indices, grouped_site_indice
|
|
|
48
46
|
const representative_site_idx = grouped_indices[0];
|
|
49
47
|
const representative_site = sites[representative_site_idx];
|
|
50
48
|
const merged_species = grouped_indices.flatMap((grouped_site_idx) => sites[grouped_site_idx].species);
|
|
51
|
-
render_sites.push(make_render_site(sites, representative_site_idx, [...grouped_indices], {
|
|
49
|
+
render_sites.push(make_render_site(sites, representative_site_idx, [...grouped_indices], {
|
|
50
|
+
...representative_site,
|
|
51
|
+
species: merged_species,
|
|
52
|
+
}));
|
|
52
53
|
}
|
|
53
54
|
return render_sites;
|
|
54
55
|
};
|
|
@@ -66,9 +67,7 @@ export const compute_slice_geometry = (visible_species, slice_gap_rad = PARTIAL_
|
|
|
66
67
|
return [];
|
|
67
68
|
const total_visible_occupancy = visible_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
|
|
68
69
|
// Preserve total angular coverage at one full turn for invalid overfull inputs.
|
|
69
|
-
const occupancy_scale_factor = total_visible_occupancy > 1 + OCCUPANCY_EPS
|
|
70
|
-
? 1 / total_visible_occupancy
|
|
71
|
-
: 1;
|
|
70
|
+
const occupancy_scale_factor = total_visible_occupancy > 1 + OCCUPANCY_EPS ? 1 / total_visible_occupancy : 1;
|
|
72
71
|
const normalized_species = visible_species.map(({ element, occu }) => ({
|
|
73
72
|
element,
|
|
74
73
|
occu: occu * occupancy_scale_factor,
|
|
@@ -83,9 +82,7 @@ export const compute_slice_geometry = (visible_species, slice_gap_rad = PARTIAL_
|
|
|
83
82
|
// Keep neighboring wedges from sharing the exact same plane (z-fighting).
|
|
84
83
|
const phi_span_raw = Math.max(0, end_phi_raw - start_phi_raw);
|
|
85
84
|
const max_safe_gap = Math.max(0, phi_span_raw - MIN_PHI_LENGTH);
|
|
86
|
-
const desired_gap = visible_species.length > 1
|
|
87
|
-
? Math.min(slice_gap_rad, phi_span_raw * 0.25)
|
|
88
|
-
: 0;
|
|
85
|
+
const desired_gap = visible_species.length > 1 ? Math.min(slice_gap_rad, phi_span_raw * 0.25) : 0;
|
|
89
86
|
const phi_gap = Math.min(desired_gap, max_safe_gap);
|
|
90
87
|
const start_phi = start_phi_raw + phi_gap / 2;
|
|
91
88
|
const end_phi = end_phi_raw - phi_gap / 2;
|
package/dist/structure/pbc.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Vec3 } from '../math';
|
|
2
2
|
import type { ParsedStructure } from './parse';
|
|
3
3
|
export type Pbc = readonly [boolean, boolean, boolean];
|
|
4
|
+
export declare const wrap_frac_coord: (coord: number) => number;
|
|
4
5
|
export declare const wrap_to_unit_cell: (frac: Vec3) => Vec3;
|
|
5
6
|
export declare function find_image_atoms(structure: ParsedStructure, { tolerance }?: {
|
|
6
7
|
tolerance?: number;
|
package/dist/structure/pbc.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import * as math from '../math';
|
|
2
|
+
// Wrap a single fractional coordinate to [0, 1), clamping near-1 values to 0
|
|
3
|
+
// and rounding to 15 digits to suppress floating-point noise.
|
|
4
|
+
export const wrap_frac_coord = (coord) => {
|
|
5
|
+
const wrapped = coord - Math.floor(coord);
|
|
6
|
+
if (wrapped >= 1 - 1e-10)
|
|
7
|
+
return 0;
|
|
8
|
+
return Number(wrapped.toFixed(15));
|
|
9
|
+
};
|
|
2
10
|
// Wrap fractional coordinates to [0, 1) range for periodicity.
|
|
3
|
-
export const wrap_to_unit_cell = (frac) =>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
export const wrap_to_unit_cell = (frac) => [
|
|
12
|
+
wrap_frac_coord(frac[0]),
|
|
13
|
+
wrap_frac_coord(frac[1]),
|
|
14
|
+
wrap_frac_coord(frac[2]),
|
|
15
|
+
];
|
|
7
16
|
export function find_image_atoms(structure, { tolerance } = {}) {
|
|
8
17
|
// Find image atoms for PBC. Returns [atom_idx, image_xyz, image_abc] tuples.
|
|
9
18
|
// Skips image generation for trajectory data with scattered atoms.
|
|
@@ -47,7 +56,7 @@ export function find_image_atoms(structure, { tolerance } = {}) {
|
|
|
47
56
|
edge_dims.push({ dim, direction: -1 });
|
|
48
57
|
}
|
|
49
58
|
// Generate all translation combinations
|
|
50
|
-
for (let mask = 1; mask <
|
|
59
|
+
for (let mask = 1; mask < 1 << edge_dims.length; mask++) {
|
|
51
60
|
// Track selected translation per dimension. If both +1 and -1 are selected for a dim,
|
|
52
61
|
// the net shift is zero and we skip because it yields no image.
|
|
53
62
|
const selected_shift = [0, 0, 0];
|
|
@@ -69,7 +78,8 @@ export function find_image_atoms(structure, { tolerance } = {}) {
|
|
|
69
78
|
site.abc[2] + selected_shift[2],
|
|
70
79
|
];
|
|
71
80
|
// If no dimension actually shifted, continue
|
|
72
|
-
if (img_abc[0] === site.abc[0] &&
|
|
81
|
+
if (img_abc[0] === site.abc[0] &&
|
|
82
|
+
img_abc[1] === site.abc[1] &&
|
|
73
83
|
img_abc[2] === site.abc[2])
|
|
74
84
|
continue;
|
|
75
85
|
// Compute xyz from img_abc to ensure consistency
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Vec3 } from '../math';
|
|
2
|
-
import
|
|
2
|
+
import { scale_lattice_matrix } from '../math';
|
|
3
3
|
import type { Crystal } from './index';
|
|
4
4
|
export declare function parse_supercell_scaling(scaling: string | number | Vec3): Vec3;
|
|
5
5
|
export declare function generate_lattice_points(scaling_factors: Vec3): Vec3[];
|
|
6
|
-
export
|
|
6
|
+
export { scale_lattice_matrix };
|
|
7
7
|
export declare function make_supercell(structure: Crystal, scaling: string | number | Vec3, to_unit_cell?: boolean): Crystal;
|
|
8
8
|
export declare function is_valid_supercell_input(input: string): boolean;
|