matterviz 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
// Core computational logic for chemical potential diagrams.
|
|
2
|
+
// Ports pymatgen's ChemicalPotentialDiagram algorithm to TypeScript.
|
|
3
|
+
// Reference: pymatgen/analysis/chempot_diagram.py
|
|
4
|
+
import { count_atoms_in_composition, get_reduced_formula } from '../composition';
|
|
5
|
+
import { convex_hull_2d, EPS, polygon_centroid, solve_linear_system, } from '../math';
|
|
6
|
+
import { CHEMPOT_DEFAULTS, } from './types';
|
|
7
|
+
// === Entry Helpers ===
|
|
8
|
+
// Get energy per atom for a PhaseData entry
|
|
9
|
+
export function get_energy_per_atom(entry) {
|
|
10
|
+
if (typeof entry.energy_per_atom === `number`)
|
|
11
|
+
return entry.energy_per_atom;
|
|
12
|
+
const atoms = count_atoms_in_composition(entry.composition);
|
|
13
|
+
if (atoms <= 0) {
|
|
14
|
+
throw new Error(`Invalid composition with non-positive atom count: ${JSON.stringify(entry.composition)}`);
|
|
15
|
+
}
|
|
16
|
+
return entry.energy / atoms;
|
|
17
|
+
}
|
|
18
|
+
// Cache for reduced formula strings -- avoids recomputing get_reduced_formula
|
|
19
|
+
// in hot loops. Key is object identity (WeakMap), value is the formula string.
|
|
20
|
+
const formula_cache = new WeakMap();
|
|
21
|
+
// Get a stable reduced formula string from composition dict (cached)
|
|
22
|
+
export function formula_key_from_composition(composition) {
|
|
23
|
+
const cached = formula_cache.get(composition);
|
|
24
|
+
if (cached)
|
|
25
|
+
return cached;
|
|
26
|
+
const reduced = get_reduced_formula(composition);
|
|
27
|
+
const key = Object.entries(reduced)
|
|
28
|
+
.filter(([, amt]) => amt > 0)
|
|
29
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
30
|
+
.map(([el, amt]) => (amt === 1 ? el : `${el}${amt}`))
|
|
31
|
+
.join(``);
|
|
32
|
+
formula_cache.set(composition, key);
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
35
|
+
// === Core Algorithm ===
|
|
36
|
+
// Group entries by reduced formula, keep only the minimum-energy entry per composition.
|
|
37
|
+
// Also extract elemental reference entries.
|
|
38
|
+
export function get_min_entries_and_el_refs(entries) {
|
|
39
|
+
const by_formula = new Map();
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const key = formula_key_from_composition(entry.composition);
|
|
42
|
+
const epa = get_energy_per_atom(entry);
|
|
43
|
+
const existing = by_formula.get(key);
|
|
44
|
+
if (!existing || epa < existing.epa) {
|
|
45
|
+
by_formula.set(key, { entry, epa });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const min_entries = Array.from(by_formula.values(), ({ entry }) => entry);
|
|
49
|
+
const el_refs = {};
|
|
50
|
+
for (const entry of min_entries) {
|
|
51
|
+
const positive = Object.entries(entry.composition).filter(([, amt]) => amt > 0);
|
|
52
|
+
if (positive.length === 1)
|
|
53
|
+
el_refs[positive[0][0]] = entry;
|
|
54
|
+
}
|
|
55
|
+
return { min_entries, el_refs };
|
|
56
|
+
}
|
|
57
|
+
// Compute formation energy per atom against elemental references.
|
|
58
|
+
export function compute_form_energy_per_atom(entry, el_refs) {
|
|
59
|
+
const atom_count = count_atoms_in_composition(entry.composition);
|
|
60
|
+
const energy_per_atom = get_energy_per_atom(entry);
|
|
61
|
+
let ref_energy = 0;
|
|
62
|
+
for (const [element, amount] of Object.entries(entry.composition)) {
|
|
63
|
+
if (amount <= 0)
|
|
64
|
+
continue;
|
|
65
|
+
const fraction = amount / atom_count;
|
|
66
|
+
const ref_entry = el_refs[element];
|
|
67
|
+
if (ref_entry)
|
|
68
|
+
ref_energy += fraction * get_energy_per_atom(ref_entry);
|
|
69
|
+
}
|
|
70
|
+
return energy_per_atom - ref_energy;
|
|
71
|
+
}
|
|
72
|
+
// Find minimum formation energy per atom across entries of one formula.
|
|
73
|
+
export function best_form_energy_for_formula(entries, formula, el_refs) {
|
|
74
|
+
let best_value;
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (formula_key_from_composition(entry.composition) !== formula)
|
|
77
|
+
continue;
|
|
78
|
+
const e_form = entry.e_form_per_atom ?? compute_form_energy_per_atom(entry, el_refs);
|
|
79
|
+
if (best_value === undefined || e_form < best_value)
|
|
80
|
+
best_value = e_form;
|
|
81
|
+
}
|
|
82
|
+
return best_value;
|
|
83
|
+
}
|
|
84
|
+
// Renormalize entry energies to be relative to elemental references (formal chemical potentials).
|
|
85
|
+
// For each entry, subtracts sum(x_i * E_ref_i) from its energy per atom.
|
|
86
|
+
export function renormalize_entries(entries, el_refs, elements) {
|
|
87
|
+
return entries.map((entry) => {
|
|
88
|
+
const atoms = count_atoms_in_composition(entry.composition);
|
|
89
|
+
let renorm_energy = 0;
|
|
90
|
+
for (const el of elements) {
|
|
91
|
+
const frac = atoms > 0
|
|
92
|
+
? (entry.composition[el] ?? 0) / atoms
|
|
93
|
+
: 0;
|
|
94
|
+
const ref = el_refs[el];
|
|
95
|
+
if (ref)
|
|
96
|
+
renorm_energy += frac * get_energy_per_atom(ref);
|
|
97
|
+
}
|
|
98
|
+
const new_energy_per_atom = get_energy_per_atom(entry) - renorm_energy;
|
|
99
|
+
return {
|
|
100
|
+
...entry,
|
|
101
|
+
energy: new_energy_per_atom * atoms,
|
|
102
|
+
energy_per_atom: new_energy_per_atom,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// Build hyperplane representation for minimum entries.
|
|
107
|
+
// Each row is [x_1, ..., x_n, -E_per_atom].
|
|
108
|
+
// Filters to entries with negative formation energy plus all elemental refs.
|
|
109
|
+
export function build_hyperplanes(min_entries, el_refs, elements) {
|
|
110
|
+
const n_elems = elements.length;
|
|
111
|
+
const element_ref_energies = elements.map((element) => {
|
|
112
|
+
const ref_entry = el_refs[element];
|
|
113
|
+
return ref_entry ? get_energy_per_atom(ref_entry) : 0;
|
|
114
|
+
});
|
|
115
|
+
const always_include = new Set(Object.values(el_refs));
|
|
116
|
+
const tol = 1e-6; // PhaseDiagram.formation_energy_tol
|
|
117
|
+
const hyperplanes = [];
|
|
118
|
+
const hyperplane_entries = [];
|
|
119
|
+
for (const entry of min_entries) {
|
|
120
|
+
const atom_count = count_atoms_in_composition(entry.composition);
|
|
121
|
+
const composition = entry.composition;
|
|
122
|
+
const energy_per_atom = get_energy_per_atom(entry);
|
|
123
|
+
const row = new Array(n_elems + 1).fill(0);
|
|
124
|
+
let ref_energy = 0;
|
|
125
|
+
for (let elem_idx = 0; elem_idx < n_elems; elem_idx++) {
|
|
126
|
+
const element = elements[elem_idx];
|
|
127
|
+
const fraction = atom_count > 0 ? (composition[element] ?? 0) / atom_count : 0;
|
|
128
|
+
row[elem_idx] = fraction;
|
|
129
|
+
ref_energy += fraction * element_ref_energies[elem_idx];
|
|
130
|
+
}
|
|
131
|
+
const form_energy = energy_per_atom - ref_energy;
|
|
132
|
+
if (form_energy < -tol || always_include.has(entry)) {
|
|
133
|
+
row[n_elems] = -energy_per_atom;
|
|
134
|
+
hyperplanes.push(row);
|
|
135
|
+
hyperplane_entries.push(entry);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { hyperplanes, hyperplane_entries };
|
|
139
|
+
}
|
|
140
|
+
// Build border hyperplanes from per-element limits.
|
|
141
|
+
// For each axis with limits [lo, hi], creates two halfspace rows.
|
|
142
|
+
export function build_border_hyperplanes(lims) {
|
|
143
|
+
const dim = lims.length;
|
|
144
|
+
const borders = [];
|
|
145
|
+
for (let idx = 0; idx < dim; idx++) {
|
|
146
|
+
// Lower bound: -mu_i + lo <= 0 → [-1, 0, ..., lo]
|
|
147
|
+
const lower = new Array(dim + 1).fill(0);
|
|
148
|
+
lower[idx] = -1;
|
|
149
|
+
lower[dim] = lims[idx][0];
|
|
150
|
+
borders.push(lower);
|
|
151
|
+
// Upper bound: mu_i - hi <= 0 → [1, 0, ..., -hi]
|
|
152
|
+
const upper = new Array(dim + 1).fill(0);
|
|
153
|
+
upper[idx] = 1;
|
|
154
|
+
upper[dim] = -lims[idx][1];
|
|
155
|
+
borders.push(upper);
|
|
156
|
+
}
|
|
157
|
+
return borders;
|
|
158
|
+
}
|
|
159
|
+
// Inline 2x2 linear solve (Cramer's rule). Returns false if singular.
|
|
160
|
+
// Writes solution into out[0], out[1].
|
|
161
|
+
function solve_2x2(a00, a01, b0, a10, a11, b1, out) {
|
|
162
|
+
const det = a00 * a11 - a01 * a10;
|
|
163
|
+
if (Math.abs(det) < EPS)
|
|
164
|
+
return false;
|
|
165
|
+
out[0] = (b0 * a11 - b1 * a01) / det;
|
|
166
|
+
out[1] = (a00 * b1 - a10 * b0) / det;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
// Inline 3x3 linear solve via Cramer's rule. Returns false if singular.
|
|
170
|
+
// Takes three halfspace rows directly to avoid array allocation in the hot loop.
|
|
171
|
+
function solve_3x3(a, b, c, offsets, out) {
|
|
172
|
+
const det = a[0] * (b[1] * c[2] - b[2] * c[1]) -
|
|
173
|
+
a[1] * (b[0] * c[2] - b[2] * c[0]) +
|
|
174
|
+
a[2] * (b[0] * c[1] - b[1] * c[0]);
|
|
175
|
+
if (Math.abs(det) < EPS)
|
|
176
|
+
return false;
|
|
177
|
+
const inv = 1 / det;
|
|
178
|
+
out[0] = (offsets[0] * (b[1] * c[2] - b[2] * c[1]) -
|
|
179
|
+
a[1] * (offsets[1] * c[2] - b[2] * offsets[2]) +
|
|
180
|
+
a[2] * (offsets[1] * c[1] - b[1] * offsets[2])) * inv;
|
|
181
|
+
out[1] = (a[0] * (offsets[1] * c[2] - b[2] * offsets[2]) -
|
|
182
|
+
offsets[0] * (b[0] * c[2] - b[2] * c[0]) +
|
|
183
|
+
a[2] * (b[0] * offsets[2] - offsets[1] * c[0])) * inv;
|
|
184
|
+
out[2] = (a[0] * (b[1] * offsets[2] - offsets[1] * c[1]) -
|
|
185
|
+
a[1] * (b[0] * offsets[2] - offsets[1] * c[0]) +
|
|
186
|
+
offsets[0] * (b[0] * c[1] - b[1] * c[0])) * inv;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
// Compute chemical potential domains via vertex enumeration.
|
|
190
|
+
// This replaces scipy's HalfspaceIntersection.
|
|
191
|
+
// For each combination of dim halfspaces, solves the linear system to find a
|
|
192
|
+
// candidate vertex, checks feasibility against all halfspaces, then assigns
|
|
193
|
+
// vertices to the phases whose hyperplanes are active at that vertex.
|
|
194
|
+
export function compute_domains(hyperplanes, border_hyperplanes, hyperplane_entries, dim) {
|
|
195
|
+
const n_entries = hyperplanes.length;
|
|
196
|
+
const all_hs = [...hyperplanes, ...border_hyperplanes];
|
|
197
|
+
const n_total = all_hs.length;
|
|
198
|
+
const tol = 1e-6;
|
|
199
|
+
// Pre-compute formula keys for entry hyperplanes (avoid repeated work in hot loop)
|
|
200
|
+
const entry_formulas = hyperplane_entries.map((entry) => formula_key_from_composition(entry.composition));
|
|
201
|
+
const domains = {};
|
|
202
|
+
for (const formula of entry_formulas) {
|
|
203
|
+
if (!domains[formula])
|
|
204
|
+
domains[formula] = [];
|
|
205
|
+
}
|
|
206
|
+
// Pre-allocate reusable buffers to avoid GC pressure in the combo loop
|
|
207
|
+
const mu = new Array(dim).fill(0);
|
|
208
|
+
const offsets = new Array(dim).fill(0);
|
|
209
|
+
// For dim <= 3, use inline solvers; for larger dims, build A on the fly
|
|
210
|
+
const A_rows = dim > 3
|
|
211
|
+
? Array.from({ length: dim }, () => new Array(dim).fill(0))
|
|
212
|
+
: [];
|
|
213
|
+
// Generate all combinations of dim indices from n_total halfspaces
|
|
214
|
+
const combo = new Array(dim).fill(0);
|
|
215
|
+
for (let idx = 0; idx < dim; idx++)
|
|
216
|
+
combo[idx] = idx;
|
|
217
|
+
function advance_combo() {
|
|
218
|
+
let pos = dim - 1;
|
|
219
|
+
while (pos >= 0 && combo[pos] >= n_total - dim + pos)
|
|
220
|
+
pos--;
|
|
221
|
+
if (pos < 0)
|
|
222
|
+
return false;
|
|
223
|
+
combo[pos]++;
|
|
224
|
+
for (let idx = pos + 1; idx < dim; idx++)
|
|
225
|
+
combo[idx] = combo[idx - 1] + 1;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
while (true) {
|
|
229
|
+
// Combinations containing only border halfspaces cannot be assigned to any
|
|
230
|
+
// entry domain, so skip solving them entirely.
|
|
231
|
+
let has_entry_hyperplane = false;
|
|
232
|
+
if (dim === 2) {
|
|
233
|
+
has_entry_hyperplane = combo[0] < n_entries || combo[1] < n_entries;
|
|
234
|
+
}
|
|
235
|
+
else if (dim === 3) {
|
|
236
|
+
has_entry_hyperplane = combo[0] < n_entries || combo[1] < n_entries ||
|
|
237
|
+
combo[2] < n_entries;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
for (let row = 0; row < dim; row++) {
|
|
241
|
+
if (combo[row] < n_entries) {
|
|
242
|
+
has_entry_hyperplane = true;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!has_entry_hyperplane) {
|
|
248
|
+
if (!advance_combo())
|
|
249
|
+
break;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// Compute offsets (negated last column of selected halfspaces)
|
|
253
|
+
for (let row = 0; row < dim; row++)
|
|
254
|
+
offsets[row] = -all_hs[combo[row]][dim];
|
|
255
|
+
// Solve for vertex using dimension-specialized solvers
|
|
256
|
+
let solved = false;
|
|
257
|
+
if (dim === 2) {
|
|
258
|
+
const h0 = all_hs[combo[0]];
|
|
259
|
+
const h1 = all_hs[combo[1]];
|
|
260
|
+
solved = solve_2x2(h0[0], h0[1], offsets[0], h1[0], h1[1], offsets[1], mu);
|
|
261
|
+
}
|
|
262
|
+
else if (dim === 3) {
|
|
263
|
+
solved = solve_3x3(all_hs[combo[0]], all_hs[combo[1]], all_hs[combo[2]], offsets, mu);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// General case: build A matrix and use LU solver
|
|
267
|
+
for (let row = 0; row < dim; row++) {
|
|
268
|
+
const hs = all_hs[combo[row]];
|
|
269
|
+
for (let col = 0; col < dim; col++)
|
|
270
|
+
A_rows[row][col] = hs[col];
|
|
271
|
+
}
|
|
272
|
+
const result = solve_linear_system(A_rows, offsets);
|
|
273
|
+
if (result) {
|
|
274
|
+
for (let idx = 0; idx < dim; idx++)
|
|
275
|
+
mu[idx] = result[idx];
|
|
276
|
+
solved = true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (solved) {
|
|
280
|
+
// Feasibility check: all halfspaces must be satisfied (a·mu + b <= tol)
|
|
281
|
+
const selected_hs_idx_0 = dim > 0 ? combo[0] : -1;
|
|
282
|
+
const selected_hs_idx_1 = dim > 1 ? combo[1] : -1;
|
|
283
|
+
const selected_hs_idx_2 = dim > 2 ? combo[2] : -1;
|
|
284
|
+
for (let idx = 0; idx < n_total; idx++) {
|
|
285
|
+
if (dim <= 3) {
|
|
286
|
+
if (idx === selected_hs_idx_0 || idx === selected_hs_idx_1 ||
|
|
287
|
+
idx === selected_hs_idx_2)
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
let is_active_halfspace = false;
|
|
292
|
+
for (let combo_idx = 0; combo_idx < dim; combo_idx++) {
|
|
293
|
+
if (combo[combo_idx] === idx) {
|
|
294
|
+
is_active_halfspace = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (is_active_halfspace)
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const hs = all_hs[idx];
|
|
302
|
+
let val = hs[dim];
|
|
303
|
+
if (dim === 2) {
|
|
304
|
+
val += hs[0] * mu[0] + hs[1] * mu[1];
|
|
305
|
+
}
|
|
306
|
+
else if (dim === 3) {
|
|
307
|
+
val += hs[0] * mu[0] + hs[1] * mu[1] + hs[2] * mu[2];
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
for (let jdx = 0; jdx < dim; jdx++)
|
|
311
|
+
val += hs[jdx] * mu[jdx];
|
|
312
|
+
}
|
|
313
|
+
if (val > tol) {
|
|
314
|
+
solved = false;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (solved) {
|
|
319
|
+
// Assign vertex to entries whose hyperplanes are active
|
|
320
|
+
const vertex = dim === 2
|
|
321
|
+
? [mu[0], mu[1]]
|
|
322
|
+
: dim === 3
|
|
323
|
+
? [mu[0], mu[1], mu[2]]
|
|
324
|
+
: [...mu];
|
|
325
|
+
for (let idx = 0; idx < dim; idx++) {
|
|
326
|
+
const hs_idx = combo[idx];
|
|
327
|
+
if (hs_idx < n_entries) {
|
|
328
|
+
domains[entry_formulas[hs_idx]].push([...vertex]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Advance to next combination (lexicographic order)
|
|
334
|
+
if (!advance_combo())
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
// Remove empty domains
|
|
338
|
+
for (const key of Object.keys(domains)) {
|
|
339
|
+
if (domains[key].length === 0)
|
|
340
|
+
delete domains[key];
|
|
341
|
+
}
|
|
342
|
+
return domains;
|
|
343
|
+
}
|
|
344
|
+
// Apply element padding: replace coordinates close to default_min_limit with
|
|
345
|
+
// actual_min - padding for cleaner visual bounds. Single pass over all points.
|
|
346
|
+
export function apply_element_padding(domains, elem_indices, padding, default_min_limit) {
|
|
347
|
+
const replace_threshold = Math.max(Math.abs(padding), EPS);
|
|
348
|
+
// Single-pass: track min per axis, skipping default_min_limit values
|
|
349
|
+
const mins = elem_indices.map(() => Infinity);
|
|
350
|
+
for (const pts of Object.values(domains)) {
|
|
351
|
+
for (const pt of pts) {
|
|
352
|
+
for (let idx = 0; idx < elem_indices.length; idx++) {
|
|
353
|
+
const val = pt[elem_indices[idx]];
|
|
354
|
+
if (Math.abs(val - default_min_limit) > replace_threshold && val < mins[idx]) {
|
|
355
|
+
mins[idx] = val;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return mins.map((min_val) => (Number.isFinite(min_val) ? min_val : default_min_limit) - padding);
|
|
361
|
+
}
|
|
362
|
+
// Replace default_min_limit coordinates with padded limits for display
|
|
363
|
+
export function pad_domain_points(pts, elem_indices, new_lims, default_min_limit, padding) {
|
|
364
|
+
const replace_threshold = Math.max(Math.abs(padding), EPS);
|
|
365
|
+
return pts.map((pt) => {
|
|
366
|
+
const padded = [...pt];
|
|
367
|
+
for (let idx = 0; idx < elem_indices.length; idx++) {
|
|
368
|
+
const col = elem_indices[idx];
|
|
369
|
+
if (Math.abs(padded[col] - default_min_limit) < replace_threshold) {
|
|
370
|
+
padded[col] = new_lims[idx];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return padded;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
// Build per-axis min/max ranges for a set of points
|
|
377
|
+
export function build_axis_ranges(points, elements) {
|
|
378
|
+
return elements.map((element, axis_idx) => {
|
|
379
|
+
let min_val = Infinity;
|
|
380
|
+
let max_val = -Infinity;
|
|
381
|
+
for (const point of points) {
|
|
382
|
+
const val = point[axis_idx];
|
|
383
|
+
if (val < min_val)
|
|
384
|
+
min_val = val;
|
|
385
|
+
if (val > max_val)
|
|
386
|
+
max_val = val;
|
|
387
|
+
}
|
|
388
|
+
return { element: element ?? `\u03BC${axis_idx}`, min_val, max_val };
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// === Label Placement Helpers ===
|
|
392
|
+
// Simple PCA: center data, compute covariance, eigendecompose, project to top-k.
|
|
393
|
+
// Used in 3D for finding domain polygon orientation for label placement.
|
|
394
|
+
export function simple_pca(data, k = 2) {
|
|
395
|
+
const n_rows = data.length;
|
|
396
|
+
const n_cols = data[0]?.length ?? 0;
|
|
397
|
+
if (n_rows === 0 || n_cols === 0) {
|
|
398
|
+
return { scores: [], eigenvectors: [] };
|
|
399
|
+
}
|
|
400
|
+
// Center the data
|
|
401
|
+
const means = new Array(n_cols).fill(0);
|
|
402
|
+
for (const row of data) {
|
|
403
|
+
for (let col = 0; col < n_cols; col++)
|
|
404
|
+
means[col] += row[col];
|
|
405
|
+
}
|
|
406
|
+
for (let col = 0; col < n_cols; col++)
|
|
407
|
+
means[col] /= n_rows;
|
|
408
|
+
const centered = data.map((row) => row.map((val, col) => val - means[col]));
|
|
409
|
+
// Covariance matrix
|
|
410
|
+
const cov = Array.from({ length: n_cols }, () => new Array(n_cols).fill(0));
|
|
411
|
+
for (const row of centered) {
|
|
412
|
+
for (let idx = 0; idx < n_cols; idx++) {
|
|
413
|
+
for (let jdx = idx; jdx < n_cols; jdx++) {
|
|
414
|
+
cov[idx][jdx] += row[idx] * row[jdx];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (let idx = 0; idx < n_cols; idx++) {
|
|
419
|
+
cov[idx][idx] /= n_rows;
|
|
420
|
+
for (let jdx = idx + 1; jdx < n_cols; jdx++) {
|
|
421
|
+
cov[idx][jdx] /= n_rows;
|
|
422
|
+
cov[jdx][idx] = cov[idx][jdx];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Power iteration for top-k eigenvectors (sufficient for k=2 on small matrices)
|
|
426
|
+
const eigenvectors = [];
|
|
427
|
+
const work_cov = cov.map((row) => [...row]);
|
|
428
|
+
for (let comp = 0; comp < k; comp++) {
|
|
429
|
+
let vec = new Array(n_cols).fill(0);
|
|
430
|
+
vec[comp % n_cols] = 1; // initial guess
|
|
431
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
432
|
+
// Matrix-vector multiply
|
|
433
|
+
const new_vec = new Array(n_cols).fill(0);
|
|
434
|
+
for (let idx = 0; idx < n_cols; idx++) {
|
|
435
|
+
for (let jdx = 0; jdx < n_cols; jdx++) {
|
|
436
|
+
new_vec[idx] += work_cov[idx][jdx] * vec[jdx];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Normalize
|
|
440
|
+
const norm = Math.hypot(...new_vec);
|
|
441
|
+
if (norm < EPS)
|
|
442
|
+
break;
|
|
443
|
+
const prev = vec;
|
|
444
|
+
vec = new_vec.map((val) => val / norm);
|
|
445
|
+
// Early exit when eigenvector has converged
|
|
446
|
+
if (prev.every((val, idx) => Math.abs(val - vec[idx]) < EPS))
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
// Rayleigh quotient for deflation
|
|
450
|
+
const eigenvalue = vec.reduce((sum, val, idx) => {
|
|
451
|
+
let mv = 0;
|
|
452
|
+
for (let jdx = 0; jdx < n_cols; jdx++)
|
|
453
|
+
mv += work_cov[idx][jdx] * vec[jdx];
|
|
454
|
+
return sum + val * mv;
|
|
455
|
+
}, 0);
|
|
456
|
+
eigenvectors.push(vec);
|
|
457
|
+
// Deflate: remove this component from the covariance matrix
|
|
458
|
+
for (let idx = 0; idx < n_cols; idx++) {
|
|
459
|
+
for (let jdx = 0; jdx < n_cols; jdx++) {
|
|
460
|
+
work_cov[idx][jdx] -= eigenvalue * vec[idx] * vec[jdx];
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Project data onto eigenvectors
|
|
465
|
+
const scores = centered.map((row) => eigenvectors.map((ev) => row.reduce((sum, val, idx) => sum + val * ev[idx], 0)));
|
|
466
|
+
return { scores, eigenvectors };
|
|
467
|
+
}
|
|
468
|
+
// Compute orthonormal vector to a 2D line segment (for label offset in 2D diagrams)
|
|
469
|
+
export function orthonormal_2d(line_pts) {
|
|
470
|
+
const dx = line_pts[1][0] - line_pts[0][0];
|
|
471
|
+
const dy = line_pts[1][1] - line_pts[0][1];
|
|
472
|
+
const perp = [-dy, dx];
|
|
473
|
+
const len = Math.hypot(perp[0], perp[1]);
|
|
474
|
+
if (len < EPS)
|
|
475
|
+
return [0, 1];
|
|
476
|
+
return [perp[0] / len, perp[1] / len];
|
|
477
|
+
}
|
|
478
|
+
// Deduplicate points within tolerance, returning unique points and index mapping
|
|
479
|
+
export function dedup_points(pts, tol = 1e-4) {
|
|
480
|
+
const unique = [];
|
|
481
|
+
const orig_indices = [];
|
|
482
|
+
for (let idx = 0; idx < pts.length; idx++) {
|
|
483
|
+
const pt = pts[idx];
|
|
484
|
+
const is_dup = unique.some((existing) => existing.every((val, dim) => Math.abs(val - pt[dim]) < tol));
|
|
485
|
+
if (!is_dup) {
|
|
486
|
+
unique.push(pt);
|
|
487
|
+
orig_indices.push(idx);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { unique, orig_indices };
|
|
491
|
+
}
|
|
492
|
+
// For a 3D domain, compute convex hull boundary edges and annotation location.
|
|
493
|
+
// Deduplicates vertices first, then uses PCA to project to 2D for the convex
|
|
494
|
+
// hull computation. Returns only the outer boundary edges, not interior lines.
|
|
495
|
+
export function get_3d_domain_simplexes_and_ann_loc(points_3d) {
|
|
496
|
+
// Deduplicate vertices to avoid cluttered interior edges
|
|
497
|
+
const { unique, orig_indices } = dedup_points(points_3d);
|
|
498
|
+
if (unique.length < 3) {
|
|
499
|
+
if (unique.length === 2) {
|
|
500
|
+
const midpoint = unique[0].map((val, dim) => (val + unique[1][dim]) / 2);
|
|
501
|
+
return { simplex_indices: [[orig_indices[0], orig_indices[1]]], ann_loc: midpoint };
|
|
502
|
+
}
|
|
503
|
+
return { simplex_indices: [], ann_loc: unique[0] ?? points_3d[0] ?? [0, 0, 0] };
|
|
504
|
+
}
|
|
505
|
+
const { scores, eigenvectors } = simple_pca(unique, 2);
|
|
506
|
+
// 2D convex hull of PCA-projected unique points → only boundary edges
|
|
507
|
+
const pts_2d = scores.map((row) => [row[0], row[1]]);
|
|
508
|
+
const hull = convex_hull_2d(pts_2d);
|
|
509
|
+
const centroid = polygon_centroid(hull);
|
|
510
|
+
// Map centroid back to 3D
|
|
511
|
+
const n_dims = unique[0].length;
|
|
512
|
+
const mean_3d = Array.from({ length: n_dims }, (_, dim) => unique.reduce((sum, pt) => sum + pt[dim], 0) / unique.length);
|
|
513
|
+
const centroid_x = centroid[0] ?? 0;
|
|
514
|
+
const centroid_y = centroid[1] ?? 0;
|
|
515
|
+
const first_eigenvector = eigenvectors[0] ?? new Array(n_dims).fill(0);
|
|
516
|
+
const second_eigenvector = eigenvectors[1] ?? new Array(n_dims).fill(0);
|
|
517
|
+
const ann_loc = mean_3d.map((m, dim) => m + centroid_x * first_eigenvector[dim] + centroid_y * second_eigenvector[dim]);
|
|
518
|
+
// Map hull vertices back to original point indices using nearest projected
|
|
519
|
+
// vertex instead of stringified coordinates to avoid precision aliasing.
|
|
520
|
+
function nearest_projected_idx(target) {
|
|
521
|
+
let nearest_idx = 0;
|
|
522
|
+
let min_sq_dist = Infinity;
|
|
523
|
+
for (let idx = 0; idx < pts_2d.length; idx++) {
|
|
524
|
+
const dx = pts_2d[idx][0] - target[0];
|
|
525
|
+
const dy = pts_2d[idx][1] - target[1];
|
|
526
|
+
const sq_dist = dx * dx + dy * dy;
|
|
527
|
+
if (sq_dist < min_sq_dist) {
|
|
528
|
+
min_sq_dist = sq_dist;
|
|
529
|
+
nearest_idx = idx;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return nearest_idx;
|
|
533
|
+
}
|
|
534
|
+
const simplex_indices = [];
|
|
535
|
+
for (let hull_idx = 0; hull_idx < hull.length; hull_idx++) {
|
|
536
|
+
const pt_a = hull[hull_idx];
|
|
537
|
+
const pt_b = hull[(hull_idx + 1) % hull.length];
|
|
538
|
+
const ui_a = nearest_projected_idx(pt_a);
|
|
539
|
+
const ui_b = nearest_projected_idx(pt_b);
|
|
540
|
+
// Map back to original array indices
|
|
541
|
+
simplex_indices.push([orig_indices[ui_a], orig_indices[ui_b]]);
|
|
542
|
+
}
|
|
543
|
+
return { simplex_indices, ann_loc };
|
|
544
|
+
}
|
|
545
|
+
// === Main Pipeline ===
|
|
546
|
+
// Compute the full chemical potential diagram from entries and config.
|
|
547
|
+
// Returns domains, elements, refs, and all intermediate data.
|
|
548
|
+
//
|
|
549
|
+
// Supports two modes based on config.elements vs data dimensionality:
|
|
550
|
+
// - **Subsystem mode**: config.elements matches data element count → filter entries
|
|
551
|
+
// to subsystem, compute in reduced dimensionality (fast for ternary from ternary data)
|
|
552
|
+
// - **Projection mode**: config.elements has fewer elements than data → compute in
|
|
553
|
+
// full N-D, then project domain vertices to selected display axes (column extraction).
|
|
554
|
+
// This matches pymatgen's ChemicalPotentialDiagram behavior for multinary systems.
|
|
555
|
+
export function compute_chempot_diagram(entries, config = {}) {
|
|
556
|
+
const { formal_chempots = CHEMPOT_DEFAULTS.formal_chempots, default_min_limit = CHEMPOT_DEFAULTS.default_min_limit, limits, } = config;
|
|
557
|
+
// Detect all unique elements across all input entries (single pass, low allocation)
|
|
558
|
+
const all_data_elements_set = new Set();
|
|
559
|
+
for (const entry of entries) {
|
|
560
|
+
for (const [element, amount] of Object.entries(entry.composition)) {
|
|
561
|
+
if (amount > 0)
|
|
562
|
+
all_data_elements_set.add(element);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const all_data_elements = Array.from(all_data_elements_set).sort();
|
|
566
|
+
// Display elements: user-specified order (controls axis mapping), or auto-detect
|
|
567
|
+
const display_elements = config.elements?.length
|
|
568
|
+
? [...config.elements]
|
|
569
|
+
: all_data_elements;
|
|
570
|
+
// Projection mode: display fewer axes than the data has elements
|
|
571
|
+
// In this mode, compute in full N-D and project afterward
|
|
572
|
+
const is_projection = display_elements.length < all_data_elements.length &&
|
|
573
|
+
display_elements.every((el) => all_data_elements.includes(el));
|
|
574
|
+
// Computation elements: full element set for projection, display set for subsystem
|
|
575
|
+
const compute_elements = is_projection ? all_data_elements : display_elements;
|
|
576
|
+
// In subsystem mode, filter entries to only those within the element set
|
|
577
|
+
let working_entries = entries;
|
|
578
|
+
if (!is_projection && config.elements?.length) {
|
|
579
|
+
const target_set = new Set(config.elements);
|
|
580
|
+
working_entries = entries.filter((entry) => {
|
|
581
|
+
for (const [element, amount] of Object.entries(entry.composition)) {
|
|
582
|
+
if (amount > 0 && !target_set.has(element))
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
// Sort entries by composition (Schwartzian transform to avoid recomputing keys)
|
|
589
|
+
const sorted_entries = working_entries
|
|
590
|
+
.map((entry) => ({ entry, key: formula_key_from_composition(entry.composition) }))
|
|
591
|
+
.sort((a, b) => a.key.localeCompare(b.key))
|
|
592
|
+
.map(({ entry }) => entry);
|
|
593
|
+
// Get min entries and elemental references
|
|
594
|
+
let { min_entries, el_refs } = get_min_entries_and_el_refs(sorted_entries);
|
|
595
|
+
const dim = compute_elements.length;
|
|
596
|
+
if (dim < 2) {
|
|
597
|
+
throw new Error(`ChemicalPotentialDiagram requires 2+ elements, got ${dim}`);
|
|
598
|
+
}
|
|
599
|
+
// Check all elemental refs exist for the computation elements
|
|
600
|
+
const missing_refs = compute_elements.filter((el) => !el_refs[el]);
|
|
601
|
+
if (missing_refs.length > 0) {
|
|
602
|
+
throw new Error(`Missing elemental reference entries for: ${missing_refs.join(`, `)}`);
|
|
603
|
+
}
|
|
604
|
+
// Renormalize if using formal chemical potentials
|
|
605
|
+
if (formal_chempots) {
|
|
606
|
+
min_entries = renormalize_entries(min_entries, el_refs, compute_elements);
|
|
607
|
+
const renorm_result = get_min_entries_and_el_refs(min_entries);
|
|
608
|
+
el_refs = renorm_result.el_refs;
|
|
609
|
+
}
|
|
610
|
+
// Build limits array for computation elements
|
|
611
|
+
const compute_lims = compute_elements.map((el) => {
|
|
612
|
+
if (limits?.[el])
|
|
613
|
+
return limits[el];
|
|
614
|
+
return [default_min_limit, 0];
|
|
615
|
+
});
|
|
616
|
+
// Build hyperplanes and compute domains in full dimensionality
|
|
617
|
+
const { hyperplanes, hyperplane_entries } = build_hyperplanes(min_entries, el_refs, compute_elements);
|
|
618
|
+
const border_hyperplanes = build_border_hyperplanes(compute_lims);
|
|
619
|
+
let domains = compute_domains(hyperplanes, border_hyperplanes, hyperplane_entries, dim);
|
|
620
|
+
// Project domain vertices from N-D to display axes (column extraction)
|
|
621
|
+
let output_lims = compute_lims;
|
|
622
|
+
if (is_projection) {
|
|
623
|
+
const compute_index_by_element = new Map();
|
|
624
|
+
for (let idx = 0; idx < compute_elements.length; idx++) {
|
|
625
|
+
compute_index_by_element.set(compute_elements[idx], idx);
|
|
626
|
+
}
|
|
627
|
+
const col_indices = display_elements.map((element) => {
|
|
628
|
+
const idx = compute_index_by_element.get(element);
|
|
629
|
+
if (idx === undefined) {
|
|
630
|
+
throw new Error(`Display element ${element} not present in compute element set`);
|
|
631
|
+
}
|
|
632
|
+
return idx;
|
|
633
|
+
});
|
|
634
|
+
const projected = {};
|
|
635
|
+
for (const [formula, pts] of Object.entries(domains)) {
|
|
636
|
+
projected[formula] = pts.map((pt) => col_indices.map((idx) => pt[idx]));
|
|
637
|
+
}
|
|
638
|
+
domains = projected;
|
|
639
|
+
output_lims = col_indices.map((idx) => compute_lims[idx]);
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
domains,
|
|
643
|
+
elements: display_elements,
|
|
644
|
+
el_refs,
|
|
645
|
+
min_entries,
|
|
646
|
+
hyperplanes,
|
|
647
|
+
hyperplane_entries,
|
|
648
|
+
lims: output_lims,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as ChemPotDiagram } from './ChemPotDiagram.svelte';
|
|
2
|
+
export { default as ChemPotDiagram2D } from './ChemPotDiagram2D.svelte';
|
|
3
|
+
export { default as ChemPotDiagram3D } from './ChemPotDiagram3D.svelte';
|
|
4
|
+
export { compute_chempot_diagram } from './compute';
|
|
5
|
+
export * from './types';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as ChemPotDiagram } from './ChemPotDiagram.svelte';
|
|
2
|
+
export { default as ChemPotDiagram2D } from './ChemPotDiagram2D.svelte';
|
|
3
|
+
export { default as ChemPotDiagram3D } from './ChemPotDiagram3D.svelte';
|
|
4
|
+
export { compute_chempot_diagram } from './compute';
|
|
5
|
+
export * from './types';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function get_pointer_coords(raw_event: unknown): {
|
|
2
|
+
clientX: number;
|
|
3
|
+
clientY: number;
|
|
4
|
+
} | null;
|
|
5
|
+
/** Convert pointer event to container-relative coords for tooltip placement. */
|
|
6
|
+
export declare function get_hover_pointer(raw_event: unknown, container_rect: DOMRect | null | undefined): {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
} | null;
|
|
10
|
+
/** Add hover pointer to an info object for tooltip placement. */
|
|
11
|
+
export declare function with_hover_pointer<T extends {
|
|
12
|
+
pointer?: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
}>(info: Omit<T, `pointer`>, raw_event: unknown, container_rect: DOMRect | null | undefined): T;
|