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
|
@@ -4,15 +4,31 @@ import { format_fractional, format_num, symbol_map } from '../labels';
|
|
|
4
4
|
import { scaleSequential } from 'd3-scale';
|
|
5
5
|
import { symbol } from 'd3-shape';
|
|
6
6
|
import { analyze_gas_data as _analyze_gas_data, apply_gas_corrections as _apply_gas_corrections, } from './gas-thermodynamics';
|
|
7
|
-
import { DEFAULT_GAS_TEMP
|
|
7
|
+
import { DEFAULT_GAS_TEMP } from './types';
|
|
8
8
|
export { DEFAULT_GAS_TEMP };
|
|
9
|
+
// Tolerance for classifying a phase as on the convex hull (eV/atom)
|
|
10
|
+
export const HULL_STABILITY_TOL = 1e-6;
|
|
11
|
+
// Clamp raw hull distance and compute stability for a single entry.
|
|
12
|
+
// Excluded entries keep their raw (possibly negative) distance and are never stable.
|
|
13
|
+
export function compute_hull_stability(raw_distance, exclude_from_hull, tol = HULL_STABILITY_TOL) {
|
|
14
|
+
if (exclude_from_hull)
|
|
15
|
+
return { e_above_hull: raw_distance, is_stable: false };
|
|
16
|
+
const e_above_hull = Math.abs(raw_distance) < tol ? 0 : Math.max(0, raw_distance);
|
|
17
|
+
return { e_above_hull, is_stable: e_above_hull <= tol };
|
|
18
|
+
}
|
|
19
|
+
// Check if entry is on the convex hull (stable or e_above_hull ≈ 0)
|
|
20
|
+
export const is_on_hull = (entry, tol = HULL_STABILITY_TOL) => !entry.exclude_from_hull &&
|
|
21
|
+
(entry.is_stable === true ||
|
|
22
|
+
(typeof entry.e_above_hull === `number` && entry.e_above_hull < tol));
|
|
23
|
+
export const get_arity = (entry) => Object.values(entry.composition).filter((count) => count > 0).length;
|
|
24
|
+
export const is_unary_entry = (entry) => get_arity(entry) === 1;
|
|
9
25
|
// Energy color scale factory (shared)
|
|
10
26
|
export function get_energy_color_scale(color_mode, color_scale, plot_entries) {
|
|
11
27
|
if (color_mode !== `energy` || plot_entries.length === 0)
|
|
12
28
|
return null;
|
|
13
29
|
const hull_distances = plot_entries
|
|
14
30
|
.map((entry) => entry.e_above_hull)
|
|
15
|
-
.filter((
|
|
31
|
+
.filter((val) => typeof val === `number`);
|
|
16
32
|
if (hull_distances.length === 0)
|
|
17
33
|
return null;
|
|
18
34
|
const lo = Math.min(...hull_distances);
|
|
@@ -25,7 +41,7 @@ export function get_energy_color_scale(color_mode, color_scale, plot_entries) {
|
|
|
25
41
|
export function get_point_color_for_entry(entry, color_mode, colors, energy_scale) {
|
|
26
42
|
const is_stable = Boolean(entry.is_stable) || entry.e_above_hull === 0;
|
|
27
43
|
if (color_mode === `stability`) {
|
|
28
|
-
return is_stable ?
|
|
44
|
+
return is_stable ? colors?.stable || `#0072B2` : colors?.unstable || `#E69F00`;
|
|
29
45
|
}
|
|
30
46
|
return energy_scale && typeof entry.e_above_hull === `number`
|
|
31
47
|
? energy_scale(entry.e_above_hull)
|
|
@@ -55,10 +71,9 @@ export function calc_max_hull_dist_in_data(processed_entries) {
|
|
|
55
71
|
if (processed_entries.length === 0)
|
|
56
72
|
return 0.5;
|
|
57
73
|
const hull_distances = processed_entries
|
|
58
|
-
.map((
|
|
59
|
-
.filter((
|
|
60
|
-
const max_val = (hull_distances.length ? Math.max(...hull_distances) : 0) +
|
|
61
|
-
0.001;
|
|
74
|
+
.map((entry) => entry.e_above_hull)
|
|
75
|
+
.filter((val) => typeof val === `number` && Number.isFinite(val));
|
|
76
|
+
const max_val = (hull_distances.length ? Math.max(...hull_distances) : 0) + 0.001;
|
|
62
77
|
return Math.max(0.1, max_val);
|
|
63
78
|
}
|
|
64
79
|
// Smart threshold for showing unstable entries based on entry count.
|
|
@@ -76,9 +91,7 @@ export function compute_auto_hull_dist_threshold(n_entries, max_hull_dist_in_dat
|
|
|
76
91
|
export function build_entry_tooltip_text(entry) {
|
|
77
92
|
const is_element = is_unary_entry(entry);
|
|
78
93
|
const elem_symbol = is_element ? Object.keys(entry.composition)[0] : ``;
|
|
79
|
-
const elem_name = is_element
|
|
80
|
-
? ELEM_SYMBOL_TO_NAME[elem_symbol] ?? ``
|
|
81
|
-
: ``;
|
|
94
|
+
const elem_name = is_element ? (ELEM_SYMBOL_TO_NAME[elem_symbol] ?? ``) : ``;
|
|
82
95
|
let text = is_element
|
|
83
96
|
? `${elem_symbol}${elem_name ? ` (${elem_name})` : ``}\n`
|
|
84
97
|
: `${entry.name || entry.reduced_formula || ``}\n`;
|
|
@@ -98,9 +111,7 @@ export function build_entry_tooltip_text(entry) {
|
|
|
98
111
|
text += `E<sub>above hull</sub>: ${e_hull_str} eV/atom\n`;
|
|
99
112
|
}
|
|
100
113
|
// Fallback to energy_per_atom if e_form_per_atom is absent
|
|
101
|
-
const e_form_display = entry.e_form_per_atom !== undefined
|
|
102
|
-
? entry.e_form_per_atom
|
|
103
|
-
: entry.energy_per_atom;
|
|
114
|
+
const e_form_display = entry.e_form_per_atom !== undefined ? entry.e_form_per_atom : entry.energy_per_atom;
|
|
104
115
|
if (e_form_display !== undefined) {
|
|
105
116
|
const e_form_str = format_num(e_form_display, `.3~`);
|
|
106
117
|
text += `E<sub>form</sub>: ${e_form_str} eV/atom`;
|
|
@@ -122,8 +133,7 @@ export function find_hull_entry_at_mouse(canvas, event, plot_entries, project_po
|
|
|
122
133
|
continue;
|
|
123
134
|
const projected = project_point(entry.x, entry.y, entry.z);
|
|
124
135
|
const distance = Math.hypot(mouse_x - projected.x, mouse_y - projected.y);
|
|
125
|
-
const base = entry.size ??
|
|
126
|
-
((entry.is_stable || entry.e_above_hull === 0) ? 6 : 4);
|
|
136
|
+
const base = entry.size ?? (entry.is_stable || entry.e_above_hull === 0 ? 6 : 4);
|
|
127
137
|
if (distance < base * container_scale + 5)
|
|
128
138
|
return entry;
|
|
129
139
|
}
|
|
@@ -144,12 +154,10 @@ export function calculate_modal_side(wrapper) {
|
|
|
144
154
|
export function compute_energy_mode_info(entries, // Array of phase entries to analyze
|
|
145
155
|
find_lowest_energy_unary_refs_fn, // Function to find unary references
|
|
146
156
|
energy_source_mode) {
|
|
147
|
-
const has_precomputed_e_form = entries.length > 0 &&
|
|
148
|
-
|
|
149
|
-
const has_precomputed_hull = entries.length > 0 &&
|
|
150
|
-
entries.every((e) => typeof e.e_above_hull === `number`);
|
|
157
|
+
const has_precomputed_e_form = entries.length > 0 && entries.every((entry) => typeof entry.e_form_per_atom === `number`);
|
|
158
|
+
const has_precomputed_hull = entries.length > 0 && entries.every((entry) => typeof entry.e_above_hull === `number`);
|
|
151
159
|
const unary_refs = find_lowest_energy_unary_refs_fn(entries);
|
|
152
|
-
const elements_in_entries = Array.from(new Set(entries.flatMap((
|
|
160
|
+
const elements_in_entries = Array.from(new Set(entries.flatMap((entry) => Object.keys(entry.composition))));
|
|
153
161
|
const can_compute_e_form = elements_in_entries.every((el) => Boolean(unary_refs[el]));
|
|
154
162
|
const can_compute_hull = can_compute_e_form;
|
|
155
163
|
// Resolve mode to avoid inconsistent states:
|
|
@@ -259,6 +267,42 @@ function get_entry_energy_by_metric(entry, metric) {
|
|
|
259
267
|
}
|
|
260
268
|
return null;
|
|
261
269
|
}
|
|
270
|
+
function get_label_representative_energy(entry) {
|
|
271
|
+
if (is_finite(entry.e_form_per_atom))
|
|
272
|
+
return entry.e_form_per_atom;
|
|
273
|
+
if (is_finite(entry.energy_per_atom))
|
|
274
|
+
return entry.energy_per_atom;
|
|
275
|
+
const energy_per_atom = compute_energy_per_atom(entry);
|
|
276
|
+
if (energy_per_atom !== null)
|
|
277
|
+
return energy_per_atom;
|
|
278
|
+
if (is_finite(entry.energy))
|
|
279
|
+
return entry.energy;
|
|
280
|
+
if (is_finite(entry.e_above_hull))
|
|
281
|
+
return entry.e_above_hull;
|
|
282
|
+
return Number.POSITIVE_INFINITY;
|
|
283
|
+
}
|
|
284
|
+
function get_fractional_composition_key(composition) {
|
|
285
|
+
return Object.entries(get_fractional_composition(composition))
|
|
286
|
+
.sort(([elem_a], [elem_b]) => elem_a.localeCompare(elem_b))
|
|
287
|
+
.map(([elem, frac]) => `${elem}:${frac.toFixed(6)}`)
|
|
288
|
+
.join(`|`);
|
|
289
|
+
}
|
|
290
|
+
// Pick one label target per normalized composition. Multiple polymorphs, supercell
|
|
291
|
+
// formulas, or same-composition entries often project to the same screen position.
|
|
292
|
+
export function get_composition_label_entries(entries) {
|
|
293
|
+
const label_entry_by_composition = new Map();
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const comp_key = get_fractional_composition_key(entry.composition);
|
|
296
|
+
if (!comp_key)
|
|
297
|
+
continue;
|
|
298
|
+
const existing = label_entry_by_composition.get(comp_key);
|
|
299
|
+
if (!existing ||
|
|
300
|
+
get_label_representative_energy(entry) < get_label_representative_energy(existing)) {
|
|
301
|
+
label_entry_by_composition.set(comp_key, entry);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return Array.from(label_entry_by_composition.values());
|
|
305
|
+
}
|
|
262
306
|
// Determine which energy metric to use for a composition group
|
|
263
307
|
// Returns the first metric that ALL entries in the group can provide
|
|
264
308
|
function select_group_energy_metric(polymorphs) {
|
|
@@ -267,8 +311,7 @@ function select_group_energy_metric(polymorphs) {
|
|
|
267
311
|
return `e_form_per_atom`;
|
|
268
312
|
}
|
|
269
313
|
// Try energy_per_atom (either direct field or computed from total energy)
|
|
270
|
-
if (polymorphs.every((entry) => is_finite(entry.energy_per_atom) ||
|
|
271
|
-
compute_energy_per_atom(entry) !== null))
|
|
314
|
+
if (polymorphs.every((entry) => is_finite(entry.energy_per_atom) || compute_energy_per_atom(entry) !== null))
|
|
272
315
|
return `energy_per_atom`;
|
|
273
316
|
// Last resort: e_above_hull (will fail to differentiate stable polymorphs with e_above_hull=0)
|
|
274
317
|
if (polymorphs.every((entry) => is_finite(entry.e_above_hull))) {
|
|
@@ -284,11 +327,7 @@ export function compute_all_polymorph_stats(all_entries) {
|
|
|
284
327
|
// Group entries by fractional composition (normalized stoichiometry)
|
|
285
328
|
const composition_groups = new Map();
|
|
286
329
|
for (const entry of all_entries) {
|
|
287
|
-
const
|
|
288
|
-
const comp_key = Object.entries(fractional)
|
|
289
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
290
|
-
.map(([elem, frac]) => `${elem}:${frac.toFixed(6)}`)
|
|
291
|
-
.join(`|`);
|
|
330
|
+
const comp_key = get_fractional_composition_key(entry.composition);
|
|
292
331
|
const group = composition_groups.get(comp_key) ?? [];
|
|
293
332
|
if (group.length === 0)
|
|
294
333
|
composition_groups.set(comp_key, group);
|
|
@@ -339,15 +378,19 @@ function apply_alpha_to_color(color, alpha) {
|
|
|
339
378
|
// Handle existing rgba format
|
|
340
379
|
if (color.includes(`rgba`))
|
|
341
380
|
return color.replace(/[\d.]+\)$/, `${alpha})`);
|
|
342
|
-
if (color.includes(`rgb(`)) {
|
|
381
|
+
if (color.includes(`rgb(`)) {
|
|
382
|
+
// Convert rgb to rgba
|
|
343
383
|
return color.replace(/rgb\(/, `rgba(`).replace(/\)$/, `, ${alpha})`);
|
|
344
384
|
}
|
|
345
|
-
const hex_match =
|
|
385
|
+
const hex_match = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(color); // Convert hex to rgba
|
|
346
386
|
if (hex_match) {
|
|
347
387
|
let hex = hex_match[1];
|
|
348
388
|
// Expand short form (e.g. "03F") to full form (e.g. "0033FF")
|
|
349
389
|
if (hex.length === 3)
|
|
350
|
-
hex =
|
|
390
|
+
hex = hex
|
|
391
|
+
.split(``)
|
|
392
|
+
.map((char) => char + char)
|
|
393
|
+
.join(``);
|
|
351
394
|
const red = parseInt(hex.slice(0, 2), 16);
|
|
352
395
|
const green = parseInt(hex.slice(2, 4), 16);
|
|
353
396
|
const blue = parseInt(hex.slice(4, 6), 16);
|
|
@@ -417,7 +460,8 @@ export function get_canvas_text_color(dark_mode, element) {
|
|
|
417
460
|
if (typeof document === `undefined`)
|
|
418
461
|
return fallback;
|
|
419
462
|
const css_value = getComputedStyle(element ?? document.documentElement)
|
|
420
|
-
.getPropertyValue(`--text-color`)
|
|
463
|
+
.getPropertyValue(`--text-color`)
|
|
464
|
+
?.trim();
|
|
421
465
|
// Check for unsupported CSS functions that canvas can't render
|
|
422
466
|
return css_value && !/light-dark|var\(/i.test(css_value) ? css_value : fallback;
|
|
423
467
|
}
|
|
@@ -461,8 +505,7 @@ function entry_has_temp_data(entry) {
|
|
|
461
505
|
}
|
|
462
506
|
// Check if entry has data at exact temperature T
|
|
463
507
|
export function entry_has_temperature(entry, T) {
|
|
464
|
-
return entry_has_temp_data(entry) &&
|
|
465
|
-
(entry.temperatures?.includes(T) ?? false);
|
|
508
|
+
return entry_has_temp_data(entry) && (entry.temperatures?.includes(T) ?? false);
|
|
466
509
|
}
|
|
467
510
|
// Get energy at temperature T (throws if T not found - validate with entry_has_temperature first)
|
|
468
511
|
export function get_energy_at_temperature(entry, T) {
|
|
@@ -623,6 +666,6 @@ export function get_entry_label(entry, elements) {
|
|
|
623
666
|
pairs = pairs.sort(([el1], [el2]) => elements.indexOf(el1) - elements.indexOf(el2));
|
|
624
667
|
}
|
|
625
668
|
return pairs
|
|
626
|
-
.map(([el, amt]) => Math.abs(amt - 1) < 1e-6 ? el : `${el}${format_num(amt, `.2~`)}`)
|
|
669
|
+
.map(([el, amt]) => (Math.abs(amt - 1) < 1e-6 ? el : `${el}${format_num(amt, `.2~`)}`))
|
|
627
670
|
.join(``);
|
|
628
671
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { count_atoms_in_composition, extract_formula_elements, sort_by_electronegativity, } from '../composition';
|
|
2
2
|
import * as math from '../math';
|
|
3
3
|
import { barycentric_to_ternary_xyz, barycentric_to_tetrahedral, composition_to_barycentric_3d, composition_to_barycentric_4d, composition_to_barycentric_nd, } from './barycentric-coords';
|
|
4
|
-
import { get_arity, HULL_STABILITY_TOL, is_on_hull, is_unary_entry } from './
|
|
4
|
+
import { get_arity, HULL_STABILITY_TOL, is_on_hull, is_unary_entry } from './helpers';
|
|
5
5
|
// Track warned keys to avoid log spam on large datasets with repeated invalid keys
|
|
6
6
|
const warned_keys = new Set();
|
|
7
7
|
// Normalize convex hull composition keys by stripping oxidation states (e.g. "V4+" -> "V")
|
|
@@ -41,7 +41,7 @@ export function process_hull_entries(entries) {
|
|
|
41
41
|
for (const entry of normalized_entries) {
|
|
42
42
|
const stable = typeof entry.is_stable === `boolean`
|
|
43
43
|
? entry.is_stable
|
|
44
|
-
: (entry.e_above_hull ?? Infinity) <=
|
|
44
|
+
: (entry.e_above_hull ?? Infinity) <= HULL_STABILITY_TOL;
|
|
45
45
|
(stable ? stable_entries : unstable_entries).push(entry);
|
|
46
46
|
}
|
|
47
47
|
// Extract unique element symbols from normalized compositions
|
|
@@ -120,9 +120,9 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
120
120
|
}
|
|
121
121
|
// 3. Compute formation energies
|
|
122
122
|
const refs = find_lowest_energy_unary_refs(reference_entries);
|
|
123
|
-
const compute_e_form = (
|
|
124
|
-
?
|
|
125
|
-
: compute_e_form_per_atom(
|
|
123
|
+
const compute_e_form = (entry) => typeof entry.e_form_per_atom === `number`
|
|
124
|
+
? entry.e_form_per_atom
|
|
125
|
+
: compute_e_form_per_atom(entry, refs);
|
|
126
126
|
const interest_data = entries_of_interest.map((entry) => ({
|
|
127
127
|
entry,
|
|
128
128
|
e_form: compute_e_form(entry),
|
|
@@ -192,8 +192,8 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
192
192
|
continue;
|
|
193
193
|
try {
|
|
194
194
|
const bary = composition_to_barycentric_3d(ref.composition, elements);
|
|
195
|
-
const
|
|
196
|
-
ref_points.push(
|
|
195
|
+
const point = barycentric_to_ternary_xyz(bary, e_form);
|
|
196
|
+
ref_points.push(point);
|
|
197
197
|
}
|
|
198
198
|
catch {
|
|
199
199
|
// Ignore invalid compositions
|
|
@@ -202,7 +202,7 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
202
202
|
// Ensure corner points (pure elements default to e_form = 0)
|
|
203
203
|
for (const el of elements) {
|
|
204
204
|
const corner = barycentric_to_ternary_xyz(composition_to_barycentric_3d({ [el]: 1 }, elements), 0);
|
|
205
|
-
if (!ref_points.some((
|
|
205
|
+
if (!ref_points.some((point) => Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z) < 1e-9)) {
|
|
206
206
|
ref_points.push(corner);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
@@ -216,9 +216,9 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
216
216
|
}
|
|
217
217
|
try {
|
|
218
218
|
const bary = composition_to_barycentric_3d(entry.composition, elements);
|
|
219
|
-
const
|
|
220
|
-
const z_hull = e_hull_at_xy(hull_models,
|
|
221
|
-
results[id] = z_hull === null ? NaN : Math.max(0,
|
|
219
|
+
const point = barycentric_to_ternary_xyz(bary, e_form);
|
|
220
|
+
const z_hull = e_hull_at_xy(hull_models, point.x, point.y);
|
|
221
|
+
results[id] = z_hull === null ? NaN : Math.max(0, point.z - z_hull);
|
|
222
222
|
}
|
|
223
223
|
catch {
|
|
224
224
|
results[id] = NaN;
|
|
@@ -245,8 +245,8 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
245
245
|
for (const el of elements) {
|
|
246
246
|
const tet = barycentric_to_tetrahedral(composition_to_barycentric_4d({ [el]: 1 }, elements));
|
|
247
247
|
const corner = { ...tet, w: 0 };
|
|
248
|
-
const dist = (
|
|
249
|
-
if (!ref_points.some((
|
|
248
|
+
const dist = (point) => Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z, point.w);
|
|
249
|
+
if (!ref_points.some((point) => dist(point) < 1e-9))
|
|
250
250
|
ref_points.push(corner);
|
|
251
251
|
}
|
|
252
252
|
const hull_tetrahedra = compute_lower_hull_4d(ref_points);
|
|
@@ -359,8 +359,7 @@ export function calculate_e_above_hull(input, reference_entries) {
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
if (is_single) {
|
|
362
|
-
const id = entries_of_interest[0].entry_id ??
|
|
363
|
-
JSON.stringify(entries_of_interest[0].composition);
|
|
362
|
+
const id = entries_of_interest[0].entry_id ?? JSON.stringify(entries_of_interest[0].composition);
|
|
364
363
|
return results[id];
|
|
365
364
|
}
|
|
366
365
|
return results;
|
|
@@ -404,8 +403,8 @@ export function get_convex_hull_stats(processed_entries, elements, max_arity = 4
|
|
|
404
403
|
// Use reduce instead of Math.min/max(...arr) to avoid stack overflow on large datasets
|
|
405
404
|
const energy_range = energies.length > 0
|
|
406
405
|
? {
|
|
407
|
-
min: energies.reduce((min, val) => val < min ? val : min, Infinity),
|
|
408
|
-
max: energies.reduce((max, val) => val > max ? val : max, -Infinity),
|
|
406
|
+
min: energies.reduce((min, val) => (val < min ? val : min), Infinity),
|
|
407
|
+
max: energies.reduce((max, val) => (val > max ? val : max), -Infinity),
|
|
409
408
|
avg: energies.reduce((sum, val) => sum + val, 0) / energies.length,
|
|
410
409
|
}
|
|
411
410
|
: { min: 0, max: 0, avg: 0 };
|
|
@@ -414,7 +413,7 @@ export function get_convex_hull_stats(processed_entries, elements, max_arity = 4
|
|
|
414
413
|
.filter((val) => typeof val === `number` && val >= 0);
|
|
415
414
|
const hull_distance = hull_distances.length > 0
|
|
416
415
|
? {
|
|
417
|
-
max: hull_distances.reduce((max, val) => val > max ? val : max, -Infinity),
|
|
416
|
+
max: hull_distances.reduce((max, val) => (val > max ? val : max), -Infinity),
|
|
418
417
|
avg: hull_distances.reduce((sum, val) => sum + val, 0) / hull_distances.length,
|
|
419
418
|
}
|
|
420
419
|
: { max: 0, avg: 0 };
|
|
@@ -500,14 +499,14 @@ export function process_hull_for_stats(entries, elements) {
|
|
|
500
499
|
export function compute_lower_hull_2d(points) {
|
|
501
500
|
// Andrew's monotone chain for lower hull
|
|
502
501
|
// Sort by x then y
|
|
503
|
-
const sorted = [...points].sort((p1, p2) =>
|
|
502
|
+
const sorted = [...points].sort((p1, p2) => p1.x - p2.x || p1.y - p2.y);
|
|
504
503
|
const lower = [];
|
|
505
504
|
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
506
|
-
for (const
|
|
505
|
+
for (const point of sorted) {
|
|
507
506
|
while (lower.length >= 2 &&
|
|
508
|
-
cross(lower[lower.length - 2], lower[lower.length - 1],
|
|
507
|
+
cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0)
|
|
509
508
|
lower.pop();
|
|
510
|
-
lower.push(
|
|
509
|
+
lower.push(point);
|
|
511
510
|
}
|
|
512
511
|
return lower;
|
|
513
512
|
}
|
|
@@ -555,8 +554,7 @@ function compute_plane(p1, p2, p3) {
|
|
|
555
554
|
const offset = -(normal.x * p1.x + normal.y * p1.y + normal.z * p1.z);
|
|
556
555
|
return { normal, offset };
|
|
557
556
|
}
|
|
558
|
-
const point_plane_signed_distance = (plane, point) => plane.normal.x * point.x + plane.normal.y * point.y + plane.normal.z * point.z +
|
|
559
|
-
plane.offset;
|
|
557
|
+
const point_plane_signed_distance = (plane, point) => plane.normal.x * point.x + plane.normal.y * point.y + plane.normal.z * point.z + plane.offset;
|
|
560
558
|
const compute_centroid = (p1, p2, p3) => ({
|
|
561
559
|
x: (p1.x + p2.x + p3.x) / 3,
|
|
562
560
|
y: (p1.y + p2.y + p3.y) / 3,
|
|
@@ -658,7 +656,11 @@ function build_horizon(faces, visible_face_indices) {
|
|
|
658
656
|
for (const face_idx of visible_face_indices) {
|
|
659
657
|
const face = faces[face_idx];
|
|
660
658
|
const [a, b, c] = face.vertices;
|
|
661
|
-
const edges = [
|
|
659
|
+
const edges = [
|
|
660
|
+
[a, b],
|
|
661
|
+
[b, c],
|
|
662
|
+
[c, a],
|
|
663
|
+
];
|
|
662
664
|
for (const [u, v] of edges) {
|
|
663
665
|
const key = u < v ? `${u}|${v}` : `${v}|${u}`;
|
|
664
666
|
if (!edge_count.has(key))
|
|
@@ -781,8 +783,7 @@ export const build_lower_hull_model = (faces) => faces.map((tri) => {
|
|
|
781
783
|
return { a: 0, b: 0, c: (z1 + z2 + z3) / 3 };
|
|
782
784
|
const a = (z1 * (y2 - y3) + z2 * (y3 - y1) + z3 * (y1 - y2)) / det;
|
|
783
785
|
const b = (z1 * (x3 - x2) + z2 * (x1 - x3) + z3 * (x2 - x1)) / det;
|
|
784
|
-
const c = (z1 * (x2 * y3 - x3 * y2) + z2 * (x3 * y1 - x1 * y3) + z3 * (x1 * y2 - x2 * y1)) /
|
|
785
|
-
det;
|
|
786
|
+
const c = (z1 * (x2 * y3 - x3 * y2) + z2 * (x3 * y1 - x1 * y3) + z3 * (x1 * y2 - x2 * y1)) / det;
|
|
786
787
|
return { a, b, c };
|
|
787
788
|
})();
|
|
788
789
|
const [min_x, _mx, max_x] = [p1.x, p2.x, p3.x].sort((a, b) => a - b);
|
|
@@ -820,22 +821,24 @@ function point_in_triangle_xy(model, x, y) {
|
|
|
820
821
|
}
|
|
821
822
|
export function e_hull_at_xy(models, x, y) {
|
|
822
823
|
let z = null;
|
|
823
|
-
for (const
|
|
824
|
-
if (x <
|
|
824
|
+
for (const model of models) {
|
|
825
|
+
if (x < model.min_x - 1e-9 ||
|
|
826
|
+
x > model.max_x + 1e-9 ||
|
|
827
|
+
y < model.min_y - 1e-9 ||
|
|
828
|
+
y > model.max_y + 1e-9)
|
|
825
829
|
continue;
|
|
826
|
-
if (!point_in_triangle_xy(
|
|
830
|
+
if (!point_in_triangle_xy(model, x, y))
|
|
827
831
|
continue;
|
|
828
|
-
const z_face =
|
|
832
|
+
const z_face = model.a * x + model.b * y + model.c;
|
|
829
833
|
z = z === null ? z_face : Math.min(z, z_face);
|
|
830
834
|
}
|
|
831
835
|
return z;
|
|
832
836
|
}
|
|
833
|
-
export const compute_e_above_hull_for_points = (points, models) => points.map((
|
|
834
|
-
const z_hull = e_hull_at_xy(models,
|
|
837
|
+
export const compute_e_above_hull_for_points = (points, models) => points.map((point) => {
|
|
838
|
+
const z_hull = e_hull_at_xy(models, point.x, point.y);
|
|
835
839
|
if (z_hull === null)
|
|
836
840
|
return 0;
|
|
837
|
-
|
|
838
|
-
return e_above_hull > EPS ? e_above_hull : 0;
|
|
841
|
+
return point.z - z_hull;
|
|
839
842
|
});
|
|
840
843
|
const subtract_4d = (pt1, pt2) => ({
|
|
841
844
|
x: pt1.x - pt2.x,
|
|
@@ -905,8 +908,7 @@ function compute_plane_4d(p1, p2, p3, p4) {
|
|
|
905
908
|
const [x, y, z, w] = normal_components;
|
|
906
909
|
const normal = normalize_4d({ x, y, z, w });
|
|
907
910
|
// Guard against degenerate (nearly co-planar) points
|
|
908
|
-
const normal_magnitude = Math.abs(normal.x) + Math.abs(normal.y) + Math.abs(normal.z) +
|
|
909
|
-
Math.abs(normal.w);
|
|
911
|
+
const normal_magnitude = Math.abs(normal.x) + Math.abs(normal.y) + Math.abs(normal.z) + Math.abs(normal.w);
|
|
910
912
|
if (normal_magnitude < EPS) {
|
|
911
913
|
return { normal: { x: 0, y: 0, z: 0, w: 0 }, offset: 0 };
|
|
912
914
|
}
|
|
@@ -989,8 +991,7 @@ function choose_initial_4_simplex(points) {
|
|
|
989
991
|
continue;
|
|
990
992
|
const pa = points[idx_a];
|
|
991
993
|
const pb = points[idx_b];
|
|
992
|
-
const dist_sq = (pa.x - pb.x) ** 2 + (pa.y - pb.y) ** 2 + (pa.z - pb.z) ** 2 +
|
|
993
|
-
(pa.w - pb.w) ** 2;
|
|
994
|
+
const dist_sq = (pa.x - pb.x) ** 2 + (pa.y - pb.y) ** 2 + (pa.z - pb.z) ** 2 + (pa.w - pb.w) ** 2;
|
|
994
995
|
if (dist_sq > max_dist_sq) {
|
|
995
996
|
max_dist_sq = dist_sq;
|
|
996
997
|
idx_far_a = idx_a;
|
|
@@ -1033,7 +1034,9 @@ function choose_initial_4_simplex(points) {
|
|
|
1033
1034
|
let idx_far_hyperplane = -1;
|
|
1034
1035
|
let best_dist_hyperplane = -1;
|
|
1035
1036
|
for (let idx = 0; idx < points.length; idx++) {
|
|
1036
|
-
if (idx === idx_far_a ||
|
|
1037
|
+
if (idx === idx_far_a ||
|
|
1038
|
+
idx === idx_far_b ||
|
|
1039
|
+
idx === idx_far_line ||
|
|
1037
1040
|
idx === idx_far_plane)
|
|
1038
1041
|
continue;
|
|
1039
1042
|
const dist = Math.abs(point_plane_signed_distance_4d(plane0, points[idx]));
|
|
@@ -1096,7 +1099,12 @@ function build_horizon_4d(faces, visible_face_indices) {
|
|
|
1096
1099
|
const face = faces[face_idx];
|
|
1097
1100
|
const [a, b, c, d] = face.vertices;
|
|
1098
1101
|
// Each tetrahedron face has 4 triangular ridges
|
|
1099
|
-
const ridges = [
|
|
1102
|
+
const ridges = [
|
|
1103
|
+
[a, b, c],
|
|
1104
|
+
[a, b, d],
|
|
1105
|
+
[a, c, d],
|
|
1106
|
+
[b, c, d],
|
|
1107
|
+
];
|
|
1100
1108
|
for (const ridge of ridges) {
|
|
1101
1109
|
const sorted = ridge.slice().sort((x, y) => x - y);
|
|
1102
1110
|
const key = sorted.join(`|`);
|
|
@@ -1252,8 +1260,8 @@ function point_in_tetrahedron_3d(p0, p1, p2, p3, point) {
|
|
|
1252
1260
|
}
|
|
1253
1261
|
// Check if inside: all barycentric coords must be >= 0 and sum to 1
|
|
1254
1262
|
const eps_bary = -1e-9;
|
|
1255
|
-
const inside = bary.every((
|
|
1256
|
-
Math.abs(bary.reduce((
|
|
1263
|
+
const inside = bary.every((coord) => coord >= eps_bary) &&
|
|
1264
|
+
Math.abs(bary.reduce((sum, coord) => sum + coord, 0) - 1) < 1e-6;
|
|
1257
1265
|
return { inside, bary };
|
|
1258
1266
|
}
|
|
1259
1267
|
const build_tetrahedron_models = (hull_tetrahedra) => hull_tetrahedra.map((tet) => {
|
|
@@ -1286,17 +1294,19 @@ export const compute_e_above_hull_4d = (points, hull_tetrahedra) => {
|
|
|
1286
1294
|
let hull_w = null;
|
|
1287
1295
|
for (const model of models) {
|
|
1288
1296
|
// Fast bounding box prefilter
|
|
1289
|
-
if (x < model.min_x - EPS ||
|
|
1290
|
-
|
|
1291
|
-
|
|
1297
|
+
if (x < model.min_x - EPS ||
|
|
1298
|
+
x > model.max_x + EPS ||
|
|
1299
|
+
y < model.min_y - EPS ||
|
|
1300
|
+
y > model.max_y + EPS ||
|
|
1301
|
+
z < model.min_z - EPS ||
|
|
1302
|
+
z > model.max_z + EPS)
|
|
1292
1303
|
continue;
|
|
1293
1304
|
// Check if point's (x,y,z) is inside the 3D projection of the tetrahedron
|
|
1294
1305
|
const { inside, bary } = point_in_tetrahedron_3d(model.vertices_3d[0], model.vertices_3d[1], model.vertices_3d[2], model.vertices_3d[3], { x, y, z });
|
|
1295
1306
|
if (inside) {
|
|
1296
1307
|
// Compute w on the hull at this (x,y,z) using barycentric interpolation
|
|
1297
1308
|
const [p0, p1, p2, p3] = model.vertices;
|
|
1298
|
-
const w_on_hull = bary[0] * p0.w + bary[1] * p1.w + bary[2] * p2.w +
|
|
1299
|
-
bary[3] * p3.w;
|
|
1309
|
+
const w_on_hull = bary[0] * p0.w + bary[1] * p1.w + bary[2] * p2.w + bary[3] * p3.w;
|
|
1300
1310
|
hull_w = hull_w === null ? w_on_hull : Math.min(hull_w, w_on_hull);
|
|
1301
1311
|
}
|
|
1302
1312
|
}
|
|
@@ -1304,8 +1314,7 @@ export const compute_e_above_hull_4d = (points, hull_tetrahedra) => {
|
|
|
1304
1314
|
// composition domain. Return NaN to indicate invalid input.
|
|
1305
1315
|
if (hull_w === null)
|
|
1306
1316
|
return NaN;
|
|
1307
|
-
|
|
1308
|
-
return distance > EPS ? distance : 0;
|
|
1317
|
+
return w - hull_w;
|
|
1309
1318
|
});
|
|
1310
1319
|
};
|
|
1311
1320
|
// --- N-Dimensional Convex Hull (for 5+ element systems) ---
|
|
@@ -1343,10 +1352,7 @@ function compute_hyperplane_nd(points) {
|
|
|
1343
1352
|
const normal_components = [];
|
|
1344
1353
|
for (let col = 0; col < dim; col++) {
|
|
1345
1354
|
// Build (N-1)×(N-1) submatrix by removing column col
|
|
1346
|
-
const submatrix = edges.map((row) => [
|
|
1347
|
-
...row.slice(0, col),
|
|
1348
|
-
...row.slice(col + 1),
|
|
1349
|
-
]);
|
|
1355
|
+
const submatrix = edges.map((row) => [...row.slice(0, col), ...row.slice(col + 1)]);
|
|
1350
1356
|
const sign = col % 2 === 0 ? 1 : -1;
|
|
1351
1357
|
normal_components.push(sign * math.det_nxn(submatrix));
|
|
1352
1358
|
}
|
|
@@ -1741,7 +1747,6 @@ export function compute_e_above_hull_nd(query_points, hull_facets, all_points) {
|
|
|
1741
1747
|
// would falsely imply the point is stable/on-hull).
|
|
1742
1748
|
if (hull_energy === null)
|
|
1743
1749
|
return NaN;
|
|
1744
|
-
|
|
1745
|
-
return distance > EPS ? distance : 0;
|
|
1750
|
+
return energy - hull_energy;
|
|
1746
1751
|
});
|
|
1747
1752
|
}
|
|
@@ -3,12 +3,14 @@ import type { ShowControlsProp } from '../controls';
|
|
|
3
3
|
import type { ElementSymbol } from '../element';
|
|
4
4
|
import type { Vec3 } from '../math';
|
|
5
5
|
import type { Sides } from '../plot';
|
|
6
|
+
import type { Rect } from '../plot/layout';
|
|
6
7
|
export interface PhaseData {
|
|
7
8
|
composition: CompositionType;
|
|
8
9
|
energy: number;
|
|
9
10
|
entry_id?: string;
|
|
10
11
|
e_above_hull?: number;
|
|
11
12
|
is_stable?: boolean;
|
|
13
|
+
exclude_from_hull?: boolean;
|
|
12
14
|
energy_per_atom?: number;
|
|
13
15
|
e_form_per_atom?: number;
|
|
14
16
|
reduced_formula?: string;
|
|
@@ -38,7 +40,8 @@ export interface Point2D {
|
|
|
38
40
|
export interface Point3D extends Point2D {
|
|
39
41
|
z: number;
|
|
40
42
|
}
|
|
41
|
-
export type MarkerSymbol =
|
|
43
|
+
export type MarkerSymbol = // Marker symbol types for convex hull entries
|
|
44
|
+
`circle` | `star` | `triangle` | `cross` | `diamond` | `square` | `wye`;
|
|
42
45
|
export type HullFaceColorMode = `uniform` | `formation_energy` | `dominant_element` | `facet_index`;
|
|
43
46
|
export declare const HULL_FACE_COLOR_MODES: readonly HullFaceColorMode[];
|
|
44
47
|
export interface ConvexHullEntry extends PhaseData, Point3D {
|
|
@@ -93,6 +96,11 @@ export interface ConvexHullTriangle {
|
|
|
93
96
|
normal: Point3D;
|
|
94
97
|
centroid: Point3D;
|
|
95
98
|
}
|
|
99
|
+
export interface LabelPlacement {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
rect: Rect;
|
|
103
|
+
}
|
|
96
104
|
export interface HoverData3D<T = ConvexHullEntry> {
|
|
97
105
|
entry: T;
|
|
98
106
|
position: {
|
|
@@ -123,19 +131,6 @@ export interface PhaseStats {
|
|
|
123
131
|
max_arity: number;
|
|
124
132
|
}
|
|
125
133
|
export type PhaseArityField = Extract<keyof PhaseStats, `unary` | `binary` | `ternary` | `quaternary` | `quinary_plus`>;
|
|
126
|
-
export declare const HULL_STABILITY_TOL = 0.000001;
|
|
127
|
-
export declare const is_on_hull: (entry: PhaseData, tol?: number) => boolean;
|
|
128
|
-
export declare const get_arity: (entry: PhaseData) => number;
|
|
129
|
-
export declare const is_unary_entry: (entry: PhaseData) => boolean;
|
|
130
|
-
export declare const is_binary_entry: (entry: PhaseData) => boolean;
|
|
131
|
-
export declare const is_ternary_entry: (entry: PhaseData) => boolean;
|
|
132
|
-
export declare const is_quaternary_entry: (entry: PhaseData) => boolean;
|
|
133
|
-
export declare const is_quinary_entry: (entry: PhaseData) => boolean;
|
|
134
|
-
export declare const is_senary_entry: (entry: PhaseData) => boolean;
|
|
135
|
-
export declare const is_septenary_entry: (entry: PhaseData) => boolean;
|
|
136
|
-
export declare const is_octonary_entry: (entry: PhaseData) => boolean;
|
|
137
|
-
export declare const is_nonary_entry: (entry: PhaseData) => boolean;
|
|
138
|
-
export declare const is_denary_entry: (entry: PhaseData) => boolean;
|
|
139
134
|
export interface HighlightStyle {
|
|
140
135
|
effect?: `pulse` | `glow` | `size` | `color` | `both`;
|
|
141
136
|
color?: string;
|
|
@@ -4,23 +4,6 @@ export const HULL_FACE_COLOR_MODES = [
|
|
|
4
4
|
`dominant_element`,
|
|
5
5
|
`facet_index`,
|
|
6
6
|
];
|
|
7
|
-
// Tolerance for classifying a phase as on the convex hull (eV/atom)
|
|
8
|
-
export const HULL_STABILITY_TOL = 1e-6;
|
|
9
|
-
// Check if entry is on the convex hull (stable or e_above_hull ≈ 0)
|
|
10
|
-
export const is_on_hull = (entry, tol = HULL_STABILITY_TOL) => entry.is_stable === true ||
|
|
11
|
-
(typeof entry.e_above_hull === `number` && entry.e_above_hull < tol);
|
|
12
|
-
// Arity helpers (inlined from former arity.ts)
|
|
13
|
-
export const get_arity = (entry) => Object.values(entry.composition).filter((count) => count > 0).length;
|
|
14
|
-
export const is_unary_entry = (entry) => get_arity(entry) === 1;
|
|
15
|
-
export const is_binary_entry = (entry) => get_arity(entry) === 2;
|
|
16
|
-
export const is_ternary_entry = (entry) => get_arity(entry) === 3;
|
|
17
|
-
export const is_quaternary_entry = (entry) => get_arity(entry) === 4;
|
|
18
|
-
export const is_quinary_entry = (entry) => get_arity(entry) === 5;
|
|
19
|
-
export const is_senary_entry = (entry) => get_arity(entry) === 6;
|
|
20
|
-
export const is_septenary_entry = (entry) => get_arity(entry) === 7;
|
|
21
|
-
export const is_octonary_entry = (entry) => get_arity(entry) === 8;
|
|
22
|
-
export const is_nonary_entry = (entry) => get_arity(entry) === 9;
|
|
23
|
-
export const is_denary_entry = (entry) => get_arity(entry) === 10;
|
|
24
7
|
// --- Gas Phase Thermodynamics ---
|
|
25
8
|
// Default temperature (Kelvin) for gas corrections when no temperature is specified
|
|
26
9
|
export const DEFAULT_GAS_TEMP = 300;
|