matterviz 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (852) hide show
  1. package/.vscode/launch.json +13 -0
  2. package/.vscodeignore +7 -0
  3. package/dist/assets/STLExporter-BpTH3YHE.js +8 -0
  4. package/dist/assets/browser-DdDecX_W.js +1 -0
  5. package/dist/assets/export-qgn-H9y6.js +2 -0
  6. package/dist/assets/main-DiKYzti2.css +1 -0
  7. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  8. package/dist/extension.js +31293 -0
  9. package/dist/src/lib/FilePicker.svelte +360 -0
  10. package/dist/src/lib/MillerIndexInput.svelte +66 -0
  11. package/dist/src/lib/api/mp.ts +26 -0
  12. package/dist/src/lib/api/optimade.ts +204 -0
  13. package/dist/src/lib/app.css +247 -0
  14. package/dist/src/lib/brillouin/BrillouinZone.svelte +549 -0
  15. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +144 -0
  16. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +146 -0
  17. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  18. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +476 -0
  19. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +92 -0
  20. package/dist/src/lib/brillouin/compute.ts +529 -0
  21. package/dist/src/lib/brillouin/index.ts +8 -0
  22. package/dist/src/lib/brillouin/types.ts +51 -0
  23. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +327 -0
  24. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +846 -0
  25. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +3193 -0
  26. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +94 -0
  27. package/dist/src/lib/chempot-diagram/chempot-worker.ts +11 -0
  28. package/dist/src/lib/chempot-diagram/color.ts +42 -0
  29. package/dist/src/lib/chempot-diagram/compute.ts +1014 -0
  30. package/dist/src/lib/chempot-diagram/index.ts +6 -0
  31. package/dist/src/lib/chempot-diagram/pointer.ts +56 -0
  32. package/dist/src/lib/chempot-diagram/temperature.ts +77 -0
  33. package/dist/src/lib/chempot-diagram/types.ts +130 -0
  34. package/dist/src/lib/colors/index.ts +249 -0
  35. package/dist/src/lib/composition/BarChart.svelte +297 -0
  36. package/dist/src/lib/composition/BubbleChart.svelte +218 -0
  37. package/dist/src/lib/composition/Composition.svelte +165 -0
  38. package/dist/src/lib/composition/Formula.svelte +268 -0
  39. package/dist/src/lib/composition/FormulaFilter.svelte +1257 -0
  40. package/dist/src/lib/composition/PieChart.svelte +323 -0
  41. package/dist/src/lib/composition/format.ts +155 -0
  42. package/dist/src/lib/composition/index.ts +37 -0
  43. package/dist/src/lib/composition/parse.ts +605 -0
  44. package/dist/src/lib/constants.ts +134 -0
  45. package/dist/src/lib/controls.ts +42 -0
  46. package/dist/src/lib/convex-hull/ConvexHull.svelte +157 -0
  47. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +825 -0
  48. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +1801 -0
  49. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +1398 -0
  50. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +535 -0
  51. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +125 -0
  52. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +929 -0
  53. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +131 -0
  54. package/dist/src/lib/convex-hull/GasPressureControls.svelte +247 -0
  55. package/dist/src/lib/convex-hull/StructurePopup.svelte +151 -0
  56. package/dist/src/lib/convex-hull/TemperatureSlider.svelte +140 -0
  57. package/dist/src/lib/convex-hull/barycentric-coords.ts +246 -0
  58. package/dist/src/lib/convex-hull/demo-temperature.ts +63 -0
  59. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +405 -0
  60. package/dist/src/lib/convex-hull/helpers.ts +932 -0
  61. package/dist/src/lib/convex-hull/index.ts +202 -0
  62. package/dist/src/lib/convex-hull/thermodynamics.ts +2192 -0
  63. package/dist/src/lib/convex-hull/types.ts +267 -0
  64. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +311 -0
  65. package/dist/src/lib/coordination/calc-coordination.ts +93 -0
  66. package/dist/src/lib/coordination/index.ts +9 -0
  67. package/dist/src/lib/effects.svelte.ts +48 -0
  68. package/dist/src/lib/element/BohrAtom.svelte +147 -0
  69. package/dist/src/lib/element/ElementHeading.svelte +26 -0
  70. package/dist/src/lib/element/ElementPhoto.svelte +57 -0
  71. package/dist/src/lib/element/ElementStats.svelte +80 -0
  72. package/dist/src/lib/element/ElementTile.svelte +484 -0
  73. package/dist/src/lib/element/data.json.gz.d.ts +4 -0
  74. package/dist/src/lib/element/data.ts +14 -0
  75. package/dist/src/lib/element/index.ts +8 -0
  76. package/dist/src/lib/element/types.ts +62 -0
  77. package/dist/src/lib/feedback/ClickFeedback.svelte +58 -0
  78. package/dist/src/lib/feedback/DragOverlay.svelte +42 -0
  79. package/dist/src/lib/feedback/index.ts +4 -0
  80. package/dist/src/lib/fermi-surface/FermiSlice.svelte +189 -0
  81. package/dist/src/lib/fermi-surface/FermiSurface.svelte +600 -0
  82. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +448 -0
  83. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +794 -0
  84. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +111 -0
  85. package/dist/src/lib/fermi-surface/compute.ts +728 -0
  86. package/dist/src/lib/fermi-surface/constants.ts +32 -0
  87. package/dist/src/lib/fermi-surface/export.ts +64 -0
  88. package/dist/src/lib/fermi-surface/index.ts +14 -0
  89. package/dist/src/lib/fermi-surface/marching-cubes.ts +3 -0
  90. package/dist/src/lib/fermi-surface/parse.ts +574 -0
  91. package/dist/src/lib/fermi-surface/symmetry.ts +56 -0
  92. package/dist/src/lib/fermi-surface/types.ts +159 -0
  93. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +1545 -0
  94. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  95. package/dist/src/lib/heatmap-matrix/index.ts +167 -0
  96. package/dist/src/lib/heatmap-matrix/shared.ts +7 -0
  97. package/dist/src/lib/icons.ts +650 -0
  98. package/dist/src/lib/index.ts +61 -0
  99. package/dist/src/lib/io/decompress.ts +92 -0
  100. package/dist/src/lib/io/export.ts +385 -0
  101. package/dist/src/lib/io/fetch.ts +46 -0
  102. package/dist/src/lib/io/file-drop.ts +51 -0
  103. package/dist/src/lib/io/index.ts +7 -0
  104. package/dist/src/lib/io/is-binary.ts +24 -0
  105. package/dist/src/lib/io/types.ts +8 -0
  106. package/dist/src/lib/io/url-drop.ts +141 -0
  107. package/dist/src/lib/isosurface/Isosurface.svelte +285 -0
  108. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +277 -0
  109. package/dist/src/lib/isosurface/index.ts +7 -0
  110. package/dist/src/lib/isosurface/parse.ts +656 -0
  111. package/dist/src/lib/isosurface/slice.ts +175 -0
  112. package/dist/src/lib/isosurface/types.ts +309 -0
  113. package/dist/src/lib/labels.ts +320 -0
  114. package/dist/src/lib/layout/FullscreenToggle.svelte +50 -0
  115. package/dist/src/lib/layout/InfoCard.svelte +120 -0
  116. package/dist/src/lib/layout/InfoTag.svelte +185 -0
  117. package/dist/src/lib/layout/PropertyFilter.svelte +246 -0
  118. package/dist/src/lib/layout/SettingsSection.svelte +148 -0
  119. package/dist/src/lib/layout/SubpageGrid.svelte +82 -0
  120. package/dist/src/lib/layout/fullscreen.ts +65 -0
  121. package/dist/src/lib/layout/index.ts +11 -0
  122. package/dist/src/lib/layout/json-tree/JsonNode.svelte +548 -0
  123. package/dist/src/lib/layout/json-tree/JsonTree.svelte +1230 -0
  124. package/dist/src/lib/layout/json-tree/JsonValue.svelte +334 -0
  125. package/dist/src/lib/layout/json-tree/index.ts +3 -0
  126. package/dist/src/lib/layout/json-tree/types.ts +126 -0
  127. package/dist/src/lib/layout/json-tree/utils.ts +682 -0
  128. package/dist/src/lib/marching-cubes.ts +614 -0
  129. package/dist/src/lib/math.ts +1081 -0
  130. package/dist/src/lib/overlays/ContextMenu.svelte +162 -0
  131. package/dist/src/lib/overlays/CopyButton.svelte +45 -0
  132. package/dist/src/lib/overlays/DragControlTab.svelte +98 -0
  133. package/dist/src/lib/overlays/DraggablePane.svelte +487 -0
  134. package/dist/src/lib/overlays/InfoPaneCards.svelte +149 -0
  135. package/dist/src/lib/overlays/index.ts +3 -0
  136. package/dist/src/lib/periodic-table/PeriodicTable.svelte +469 -0
  137. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +557 -0
  138. package/dist/src/lib/periodic-table/PropertySelect.svelte +37 -0
  139. package/dist/src/lib/periodic-table/index.ts +12 -0
  140. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1086 -0
  141. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +444 -0
  142. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  143. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  144. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  145. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +203 -0
  146. package/dist/src/lib/phase-diagram/build-diagram.ts +186 -0
  147. package/dist/src/lib/phase-diagram/colors.ts +58 -0
  148. package/dist/src/lib/phase-diagram/diagram-input.ts +40 -0
  149. package/dist/src/lib/phase-diagram/index.ts +13 -0
  150. package/dist/src/lib/phase-diagram/parse.ts +348 -0
  151. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +1023 -0
  152. package/dist/src/lib/phase-diagram/types.ts +144 -0
  153. package/dist/src/lib/phase-diagram/utils.ts +775 -0
  154. package/dist/src/lib/plot/AxisLabel.svelte +51 -0
  155. package/dist/src/lib/plot/BarPlot.svelte +2113 -0
  156. package/dist/src/lib/plot/BarPlotControls.svelte +66 -0
  157. package/dist/src/lib/plot/BinnedScatterPlot.svelte +1114 -0
  158. package/dist/src/lib/plot/ColorBar.svelte +721 -0
  159. package/dist/src/lib/plot/ColorScaleSelect.svelte +54 -0
  160. package/dist/src/lib/plot/ElementScatter.svelte +63 -0
  161. package/dist/src/lib/plot/FillArea.svelte +223 -0
  162. package/dist/src/lib/plot/Histogram.svelte +1558 -0
  163. package/dist/src/lib/plot/HistogramControls.svelte +212 -0
  164. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +96 -0
  165. package/dist/src/lib/plot/Line.svelte +84 -0
  166. package/dist/src/lib/plot/PlotAxis.svelte +169 -0
  167. package/dist/src/lib/plot/PlotControls.svelte +537 -0
  168. package/dist/src/lib/plot/PlotLegend.svelte +569 -0
  169. package/dist/src/lib/plot/PlotTooltip.svelte +67 -0
  170. package/dist/src/lib/plot/PortalSelect.svelte +253 -0
  171. package/dist/src/lib/plot/ReferenceLine3D.svelte +156 -0
  172. package/dist/src/lib/plot/ReferencePlane.svelte +175 -0
  173. package/dist/src/lib/plot/ScatterPlot.svelte +2778 -0
  174. package/dist/src/lib/plot/ScatterPlot3D.svelte +529 -0
  175. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +437 -0
  176. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +912 -0
  177. package/dist/src/lib/plot/ScatterPlotControls.svelte +306 -0
  178. package/dist/src/lib/plot/ScatterPoint.svelte +182 -0
  179. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +293 -0
  180. package/dist/src/lib/plot/Surface3D.svelte +197 -0
  181. package/dist/src/lib/plot/ZeroLines.svelte +97 -0
  182. package/dist/src/lib/plot/ZoomRect.svelte +23 -0
  183. package/dist/src/lib/plot/adaptive-density.ts +316 -0
  184. package/dist/src/lib/plot/auto-place.ts +184 -0
  185. package/dist/src/lib/plot/axis-utils.ts +122 -0
  186. package/dist/src/lib/plot/binned-scatter-types.ts +83 -0
  187. package/dist/src/lib/plot/data-cleaning.ts +1069 -0
  188. package/dist/src/lib/plot/data-transform.ts +69 -0
  189. package/dist/src/lib/plot/defaults.ts +9 -0
  190. package/dist/src/lib/plot/fill-utils.ts +494 -0
  191. package/dist/src/lib/plot/hover-lock.svelte.ts +60 -0
  192. package/dist/src/lib/plot/index.ts +53 -0
  193. package/dist/src/lib/plot/interactions.ts +119 -0
  194. package/dist/src/lib/plot/layout.ts +425 -0
  195. package/dist/src/lib/plot/reference-line.ts +426 -0
  196. package/dist/src/lib/plot/scales.ts +654 -0
  197. package/dist/src/lib/plot/svg.ts +23 -0
  198. package/dist/src/lib/plot/types.ts +1144 -0
  199. package/dist/src/lib/plot/utils/label-placement.ts +541 -0
  200. package/dist/src/lib/plot/utils/series-visibility.ts +140 -0
  201. package/dist/src/lib/plot/utils.ts +11 -0
  202. package/dist/src/lib/rdf/RdfPlot.svelte +247 -0
  203. package/dist/src/lib/rdf/calc-rdf.ts +167 -0
  204. package/dist/src/lib/rdf/index.ts +27 -0
  205. package/dist/src/lib/sanitize.ts +126 -0
  206. package/dist/src/lib/settings.ts +1479 -0
  207. package/dist/src/lib/spectral/Bands.svelte +1040 -0
  208. package/dist/src/lib/spectral/BandsAndDos.svelte +134 -0
  209. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +252 -0
  210. package/dist/src/lib/spectral/Dos.svelte +697 -0
  211. package/dist/src/lib/spectral/helpers.ts +1381 -0
  212. package/dist/src/lib/spectral/index.ts +8 -0
  213. package/dist/src/lib/spectral/types.ts +112 -0
  214. package/dist/src/lib/state.svelte.ts +64 -0
  215. package/dist/src/lib/structure/Arrow.svelte +72 -0
  216. package/dist/src/lib/structure/AtomLegend.svelte +815 -0
  217. package/dist/src/lib/structure/Bond.svelte +140 -0
  218. package/dist/src/lib/structure/CanvasTooltip.svelte +33 -0
  219. package/dist/src/lib/structure/CellSelect.svelte +349 -0
  220. package/dist/src/lib/structure/Cylinder.svelte +45 -0
  221. package/dist/src/lib/structure/Lattice.svelte +196 -0
  222. package/dist/src/lib/structure/Structure.svelte +2248 -0
  223. package/dist/src/lib/structure/StructureControls.svelte +1273 -0
  224. package/dist/src/lib/structure/StructureExportPane.svelte +252 -0
  225. package/dist/src/lib/structure/StructureInfoPane.svelte +737 -0
  226. package/dist/src/lib/structure/StructureScene.svelte +2255 -0
  227. package/dist/src/lib/structure/atom-properties.ts +316 -0
  228. package/dist/src/lib/structure/bond-order-perception.ts +447 -0
  229. package/dist/src/lib/structure/bonding.ts +944 -0
  230. package/dist/src/lib/structure/export.ts +861 -0
  231. package/dist/src/lib/structure/index.ts +291 -0
  232. package/dist/src/lib/structure/label-placement.ts +130 -0
  233. package/dist/src/lib/structure/measure.ts +45 -0
  234. package/dist/src/lib/structure/parse.ts +1705 -0
  235. package/dist/src/lib/structure/partial-occupancy.ts +183 -0
  236. package/dist/src/lib/structure/pbc.ts +164 -0
  237. package/dist/src/lib/structure/supercell.ts +226 -0
  238. package/dist/src/lib/structure/validation.ts +11 -0
  239. package/dist/src/lib/symmetry/SymmetryStats.svelte +226 -0
  240. package/dist/src/lib/symmetry/WyckoffTable.svelte +120 -0
  241. package/dist/src/lib/symmetry/cell-transform.ts +118 -0
  242. package/dist/src/lib/symmetry/index.ts +348 -0
  243. package/dist/src/lib/symmetry/spacegroups.ts +404 -0
  244. package/dist/src/lib/table/HeatmapTable.svelte +1833 -0
  245. package/dist/src/lib/table/ToggleMenu.svelte +385 -0
  246. package/dist/src/lib/table/index.ts +139 -0
  247. package/dist/src/lib/theme/ThemeControl.svelte +53 -0
  248. package/dist/src/lib/theme/index.ts +107 -0
  249. package/dist/src/lib/theme/themes.mjs +297 -0
  250. package/dist/src/lib/time.ts +71 -0
  251. package/dist/src/lib/tooltip/TooltipContent.svelte +58 -0
  252. package/dist/src/lib/tooltip/index.ts +2 -0
  253. package/dist/src/lib/tooltip/types.ts +13 -0
  254. package/dist/src/lib/trajectory/Trajectory.svelte +1545 -0
  255. package/dist/src/lib/trajectory/TrajectoryError.svelte +128 -0
  256. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +357 -0
  257. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +313 -0
  258. package/dist/src/lib/trajectory/constants.ts +7 -0
  259. package/dist/src/lib/trajectory/extract.ts +196 -0
  260. package/dist/src/lib/trajectory/format-detect.ts +96 -0
  261. package/dist/src/lib/trajectory/frame-reader.ts +456 -0
  262. package/dist/src/lib/trajectory/helpers.ts +217 -0
  263. package/dist/src/lib/trajectory/index.ts +218 -0
  264. package/dist/src/lib/trajectory/parse/ase.ts +109 -0
  265. package/dist/src/lib/trajectory/parse/hdf5.ts +173 -0
  266. package/dist/src/lib/trajectory/parse/index.ts +411 -0
  267. package/dist/src/lib/trajectory/parse/lammps.ts +215 -0
  268. package/dist/src/lib/trajectory/parse/vasp.ts +102 -0
  269. package/dist/src/lib/trajectory/parse/xyz.ts +143 -0
  270. package/dist/src/lib/trajectory/plotting.ts +599 -0
  271. package/dist/src/lib/trajectory/types.ts +13 -0
  272. package/dist/src/lib/utils.ts +56 -0
  273. package/dist/src/lib/xrd/XrdPlot.svelte +615 -0
  274. package/dist/src/lib/xrd/broadening.ts +130 -0
  275. package/dist/src/lib/xrd/calc-xrd.ts +397 -0
  276. package/dist/src/lib/xrd/index.ts +38 -0
  277. package/dist/src/lib/xrd/parse.ts +858 -0
  278. package/dist/webview.js +29421 -0
  279. package/icon.png +0 -0
  280. package/license +1 -1
  281. package/matterviz-0.3.2.vsix +0 -0
  282. package/matterviz-0.3.4.vsix +0 -0
  283. package/matterviz-0.3.5.vsix +0 -0
  284. package/package.json +1461 -231
  285. package/readme.md +171 -98
  286. package/scripts/sync-config.ts +101 -0
  287. package/src/declarations.d.ts +2 -0
  288. package/src/extension.ts +972 -0
  289. package/src/node-io.ts +65 -0
  290. package/src/types.ts +17 -0
  291. package/src/webview/JsonBrowser.svelte +1079 -0
  292. package/src/webview/PlotPanel.svelte +346 -0
  293. package/src/webview/detect.ts +444 -0
  294. package/src/webview/main.ts +764 -0
  295. package/src/webview/plot-utils.ts +250 -0
  296. package/test-fixtures/all-viz-types.json.gz +0 -0
  297. package/test-fixtures/plot-demo-data.json.gz +0 -0
  298. package/tests/detect.test.ts +604 -0
  299. package/tests/extension.test.ts +2041 -0
  300. package/tests/node-io.test.ts +39 -0
  301. package/tests/plot-utils.test.ts +302 -0
  302. package/tests/vite-plugin-json-gz.test.ts +114 -0
  303. package/tests/vscode-mock.ts +18 -0
  304. package/tests/webview.test.ts +231 -0
  305. package/tsconfig.json +20 -0
  306. package/vite-plugin-json-gz.ts +29 -0
  307. package/vite.config.ts +34 -0
  308. package/vite.extension.config.ts +34 -0
  309. package/dist/EmptyState.svelte.d.ts +0 -9
  310. package/dist/FilePicker.svelte +0 -360
  311. package/dist/FilePicker.svelte.d.ts +0 -17
  312. package/dist/Icon.svelte.d.ts +0 -13
  313. package/dist/MillerIndexInput.svelte +0 -66
  314. package/dist/MillerIndexInput.svelte.d.ts +0 -7
  315. package/dist/api/mp.d.ts +0 -6
  316. package/dist/api/mp.js +0 -22
  317. package/dist/api/optimade.d.ts +0 -45
  318. package/dist/api/optimade.js +0 -135
  319. package/dist/app.css +0 -240
  320. package/dist/brillouin/BrillouinZone.svelte +0 -543
  321. package/dist/brillouin/BrillouinZone.svelte.d.ts +0 -83
  322. package/dist/brillouin/BrillouinZoneControls.svelte +0 -144
  323. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +0 -17
  324. package/dist/brillouin/BrillouinZoneExportPane.svelte +0 -148
  325. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +0 -15
  326. package/dist/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  327. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +0 -13
  328. package/dist/brillouin/BrillouinZoneScene.svelte +0 -476
  329. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +0 -48
  330. package/dist/brillouin/BrillouinZoneTooltip.svelte +0 -92
  331. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +0 -8
  332. package/dist/brillouin/compute.d.ts +0 -17
  333. package/dist/brillouin/compute.js +0 -422
  334. package/dist/brillouin/index.d.ts +0 -8
  335. package/dist/brillouin/index.js +0 -8
  336. package/dist/brillouin/types.d.ts +0 -48
  337. package/dist/brillouin/types.js +0 -1
  338. package/dist/chempot-diagram/ChemPotDiagram.svelte +0 -327
  339. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +0 -13
  340. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +0 -847
  341. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +0 -16
  342. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +0 -3194
  343. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +0 -16
  344. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +0 -7
  345. package/dist/chempot-diagram/async-compute.svelte.d.ts +0 -3
  346. package/dist/chempot-diagram/async-compute.svelte.js +0 -77
  347. package/dist/chempot-diagram/chempot-worker.d.ts +0 -1
  348. package/dist/chempot-diagram/chempot-worker.js +0 -11
  349. package/dist/chempot-diagram/color.d.ts +0 -10
  350. package/dist/chempot-diagram/color.js +0 -32
  351. package/dist/chempot-diagram/compute.d.ts +0 -48
  352. package/dist/chempot-diagram/compute.js +0 -812
  353. package/dist/chempot-diagram/index.d.ts +0 -6
  354. package/dist/chempot-diagram/index.js +0 -6
  355. package/dist/chempot-diagram/pointer.d.ts +0 -16
  356. package/dist/chempot-diagram/pointer.js +0 -40
  357. package/dist/chempot-diagram/temperature.d.ts +0 -15
  358. package/dist/chempot-diagram/temperature.js +0 -36
  359. package/dist/chempot-diagram/types.d.ts +0 -86
  360. package/dist/chempot-diagram/types.js +0 -28
  361. package/dist/colors/index.d.ts +0 -47
  362. package/dist/colors/index.js +0 -203
  363. package/dist/composition/BarChart.svelte +0 -297
  364. package/dist/composition/BarChart.svelte.d.ts +0 -39
  365. package/dist/composition/BubbleChart.svelte +0 -218
  366. package/dist/composition/BubbleChart.svelte.d.ts +0 -28
  367. package/dist/composition/Composition.svelte +0 -164
  368. package/dist/composition/Composition.svelte.d.ts +0 -15
  369. package/dist/composition/Formula.svelte +0 -265
  370. package/dist/composition/Formula.svelte.d.ts +0 -19
  371. package/dist/composition/FormulaFilter.svelte +0 -1259
  372. package/dist/composition/FormulaFilter.svelte.d.ts +0 -51
  373. package/dist/composition/PieChart.svelte +0 -323
  374. package/dist/composition/PieChart.svelte.d.ts +0 -37
  375. package/dist/composition/format.d.ts +0 -15
  376. package/dist/composition/format.js +0 -109
  377. package/dist/composition/index.d.ts +0 -20
  378. package/dist/composition/index.js +0 -14
  379. package/dist/composition/parse.d.ts +0 -55
  380. package/dist/composition/parse.js +0 -459
  381. package/dist/constants.d.ts +0 -29
  382. package/dist/constants.js +0 -105
  383. package/dist/controls.d.ts +0 -14
  384. package/dist/controls.js +0 -30
  385. package/dist/convex-hull/ConvexHull.svelte +0 -157
  386. package/dist/convex-hull/ConvexHull.svelte.d.ts +0 -13
  387. package/dist/convex-hull/ConvexHull2D.svelte +0 -813
  388. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +0 -11
  389. package/dist/convex-hull/ConvexHull3D.svelte +0 -1788
  390. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +0 -8
  391. package/dist/convex-hull/ConvexHull4D.svelte +0 -1374
  392. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +0 -8
  393. package/dist/convex-hull/ConvexHullControls.svelte +0 -546
  394. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +0 -48
  395. package/dist/convex-hull/ConvexHullInfoPane.svelte +0 -115
  396. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +0 -18
  397. package/dist/convex-hull/ConvexHullStats.svelte +0 -905
  398. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +0 -15
  399. package/dist/convex-hull/ConvexHullTooltip.svelte +0 -131
  400. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +0 -33
  401. package/dist/convex-hull/GasPressureControls.svelte +0 -247
  402. package/dist/convex-hull/GasPressureControls.svelte.d.ts +0 -11
  403. package/dist/convex-hull/StructurePopup.svelte +0 -116
  404. package/dist/convex-hull/StructurePopup.svelte.d.ts +0 -18
  405. package/dist/convex-hull/TemperatureSlider.svelte +0 -137
  406. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +0 -8
  407. package/dist/convex-hull/barycentric-coords.d.ts +0 -18
  408. package/dist/convex-hull/barycentric-coords.js +0 -182
  409. package/dist/convex-hull/demo-temperature.d.ts +0 -6
  410. package/dist/convex-hull/demo-temperature.js +0 -40
  411. package/dist/convex-hull/gas-thermodynamics.d.ts +0 -16
  412. package/dist/convex-hull/gas-thermodynamics.js +0 -316
  413. package/dist/convex-hull/helpers.d.ts +0 -103
  414. package/dist/convex-hull/helpers.js +0 -671
  415. package/dist/convex-hull/index.d.ts +0 -118
  416. package/dist/convex-hull/index.js +0 -57
  417. package/dist/convex-hull/thermodynamics.d.ts +0 -66
  418. package/dist/convex-hull/thermodynamics.js +0 -1752
  419. package/dist/convex-hull/types.d.ts +0 -162
  420. package/dist/convex-hull/types.js +0 -36
  421. package/dist/coordination/CoordinationBarPlot.svelte +0 -311
  422. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +0 -30
  423. package/dist/coordination/calc-coordination.d.ts +0 -15
  424. package/dist/coordination/calc-coordination.js +0 -63
  425. package/dist/coordination/index.d.ts +0 -8
  426. package/dist/coordination/index.js +0 -7
  427. package/dist/element/BohrAtom.svelte +0 -149
  428. package/dist/element/BohrAtom.svelte.d.ts +0 -20
  429. package/dist/element/ElementHeading.svelte +0 -26
  430. package/dist/element/ElementHeading.svelte.d.ts +0 -8
  431. package/dist/element/ElementPhoto.svelte +0 -57
  432. package/dist/element/ElementPhoto.svelte.d.ts +0 -9
  433. package/dist/element/ElementStats.svelte +0 -80
  434. package/dist/element/ElementStats.svelte.d.ts +0 -8
  435. package/dist/element/ElementTile.svelte +0 -484
  436. package/dist/element/ElementTile.svelte.d.ts +0 -29
  437. package/dist/element/Nucleus.svelte.d.ts +0 -17
  438. package/dist/element/data.d.ts +0 -3
  439. package/dist/element/data.js +0 -2
  440. package/dist/element/data.json.gz.d.ts +0 -2
  441. package/dist/element/index.d.ts +0 -8
  442. package/dist/element/index.js +0 -8
  443. package/dist/element/types.d.ts +0 -57
  444. package/dist/element/types.js +0 -1
  445. package/dist/feedback/ClickFeedback.svelte +0 -58
  446. package/dist/feedback/ClickFeedback.svelte.d.ts +0 -12
  447. package/dist/feedback/DragOverlay.svelte +0 -42
  448. package/dist/feedback/DragOverlay.svelte.d.ts +0 -7
  449. package/dist/feedback/Spinner.svelte.d.ts +0 -7
  450. package/dist/feedback/StatusMessage.svelte.d.ts +0 -9
  451. package/dist/feedback/index.d.ts +0 -4
  452. package/dist/feedback/index.js +0 -4
  453. package/dist/fermi-surface/FermiSlice.svelte +0 -189
  454. package/dist/fermi-surface/FermiSlice.svelte.d.ts +0 -24
  455. package/dist/fermi-surface/FermiSurface.svelte +0 -597
  456. package/dist/fermi-surface/FermiSurface.svelte.d.ts +0 -83
  457. package/dist/fermi-surface/FermiSurfaceControls.svelte +0 -452
  458. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +0 -35
  459. package/dist/fermi-surface/FermiSurfaceScene.svelte +0 -792
  460. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +0 -50
  461. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  462. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +0 -8
  463. package/dist/fermi-surface/compute.d.ts +0 -5
  464. package/dist/fermi-surface/compute.js +0 -538
  465. package/dist/fermi-surface/constants.d.ts +0 -9
  466. package/dist/fermi-surface/constants.js +0 -27
  467. package/dist/fermi-surface/export.d.ts +0 -5
  468. package/dist/fermi-surface/export.js +0 -63
  469. package/dist/fermi-surface/index.d.ts +0 -12
  470. package/dist/fermi-surface/index.js +0 -13
  471. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  472. package/dist/fermi-surface/marching-cubes.js +0 -2
  473. package/dist/fermi-surface/parse.d.ts +0 -2
  474. package/dist/fermi-surface/parse.js +0 -495
  475. package/dist/fermi-surface/symmetry.d.ts +0 -3
  476. package/dist/fermi-surface/symmetry.js +0 -46
  477. package/dist/fermi-surface/types.d.ts +0 -113
  478. package/dist/fermi-surface/types.js +0 -4
  479. package/dist/heatmap-matrix/HeatmapMatrix.svelte +0 -1527
  480. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +0 -110
  481. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  482. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +0 -30
  483. package/dist/heatmap-matrix/index.d.ts +0 -53
  484. package/dist/heatmap-matrix/index.js +0 -100
  485. package/dist/heatmap-matrix/shared.d.ts +0 -2
  486. package/dist/heatmap-matrix/shared.js +0 -4
  487. package/dist/icons.d.ts +0 -569
  488. package/dist/icons.js +0 -648
  489. package/dist/index.d.ts +0 -39
  490. package/dist/index.js +0 -39
  491. package/dist/io/decompress.d.ts +0 -10
  492. package/dist/io/decompress.js +0 -69
  493. package/dist/io/export.d.ts +0 -16
  494. package/dist/io/export.js +0 -312
  495. package/dist/io/fetch.d.ts +0 -5
  496. package/dist/io/fetch.js +0 -39
  497. package/dist/io/file-drop.d.ts +0 -7
  498. package/dist/io/file-drop.js +0 -43
  499. package/dist/io/index.d.ts +0 -7
  500. package/dist/io/index.js +0 -7
  501. package/dist/io/is-binary.d.ts +0 -1
  502. package/dist/io/is-binary.js +0 -5
  503. package/dist/io/types.d.ts +0 -8
  504. package/dist/io/types.js +0 -1
  505. package/dist/io/url-drop.d.ts +0 -2
  506. package/dist/io/url-drop.js +0 -117
  507. package/dist/isosurface/Isosurface.svelte +0 -285
  508. package/dist/isosurface/Isosurface.svelte.d.ts +0 -8
  509. package/dist/isosurface/IsosurfaceControls.svelte +0 -291
  510. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +0 -9
  511. package/dist/isosurface/index.d.ts +0 -5
  512. package/dist/isosurface/index.js +0 -6
  513. package/dist/isosurface/parse.d.ts +0 -6
  514. package/dist/isosurface/parse.js +0 -553
  515. package/dist/isosurface/slice.d.ts +0 -11
  516. package/dist/isosurface/slice.js +0 -140
  517. package/dist/isosurface/types.d.ts +0 -56
  518. package/dist/isosurface/types.js +0 -227
  519. package/dist/labels.d.ts +0 -53
  520. package/dist/labels.js +0 -274
  521. package/dist/layout/FullscreenToggle.svelte +0 -50
  522. package/dist/layout/FullscreenToggle.svelte.d.ts +0 -7
  523. package/dist/layout/InfoCard.svelte +0 -120
  524. package/dist/layout/InfoCard.svelte.d.ts +0 -21
  525. package/dist/layout/InfoTag.svelte +0 -183
  526. package/dist/layout/InfoTag.svelte.d.ts +0 -19
  527. package/dist/layout/PropertyFilter.svelte +0 -244
  528. package/dist/layout/PropertyFilter.svelte.d.ts +0 -24
  529. package/dist/layout/SettingsSection.svelte +0 -148
  530. package/dist/layout/SettingsSection.svelte.d.ts +0 -17
  531. package/dist/layout/SubpageGrid.svelte +0 -82
  532. package/dist/layout/SubpageGrid.svelte.d.ts +0 -14
  533. package/dist/layout/fullscreen.d.ts +0 -9
  534. package/dist/layout/fullscreen.js +0 -53
  535. package/dist/layout/index.d.ts +0 -10
  536. package/dist/layout/index.js +0 -8
  537. package/dist/layout/json-tree/JsonNode.svelte +0 -547
  538. package/dist/layout/json-tree/JsonNode.svelte.d.ts +0 -11
  539. package/dist/layout/json-tree/JsonTree.svelte +0 -1222
  540. package/dist/layout/json-tree/JsonTree.svelte.d.ts +0 -6
  541. package/dist/layout/json-tree/JsonValue.svelte +0 -334
  542. package/dist/layout/json-tree/JsonValue.svelte.d.ts +0 -9
  543. package/dist/layout/json-tree/index.d.ts +0 -3
  544. package/dist/layout/json-tree/index.js +0 -3
  545. package/dist/layout/json-tree/types.d.ts +0 -73
  546. package/dist/layout/json-tree/types.js +0 -3
  547. package/dist/layout/json-tree/utils.d.ts +0 -29
  548. package/dist/layout/json-tree/utils.js +0 -648
  549. package/dist/marching-cubes.d.ts +0 -14
  550. package/dist/marching-cubes.js +0 -542
  551. package/dist/math.d.ts +0 -91
  552. package/dist/math.js +0 -896
  553. package/dist/overlays/ContextMenu.svelte +0 -162
  554. package/dist/overlays/ContextMenu.svelte.d.ts +0 -25
  555. package/dist/overlays/DraggablePane.svelte +0 -564
  556. package/dist/overlays/DraggablePane.svelte.d.ts +0 -36
  557. package/dist/overlays/index.d.ts +0 -2
  558. package/dist/overlays/index.js +0 -2
  559. package/dist/periodic-table/PeriodicTable.svelte +0 -469
  560. package/dist/periodic-table/PeriodicTable.svelte.d.ts +0 -55
  561. package/dist/periodic-table/PeriodicTableControls.svelte +0 -557
  562. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +0 -24
  563. package/dist/periodic-table/PropertySelect.svelte +0 -37
  564. package/dist/periodic-table/PropertySelect.svelte.d.ts +0 -13
  565. package/dist/periodic-table/TableInset.svelte.d.ts +0 -9
  566. package/dist/periodic-table/index.d.ts +0 -10
  567. package/dist/periodic-table/index.js +0 -4
  568. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
  569. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +0 -44
  570. package/dist/phase-diagram/PhaseDiagramControls.svelte +0 -451
  571. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +0 -30
  572. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  573. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +0 -15
  574. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +0 -192
  575. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +0 -19
  576. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +0 -392
  577. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +0 -16
  578. package/dist/phase-diagram/TdbInfoPanel.svelte +0 -203
  579. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +0 -12
  580. package/dist/phase-diagram/build-diagram.d.ts +0 -11
  581. package/dist/phase-diagram/build-diagram.js +0 -167
  582. package/dist/phase-diagram/colors.d.ts +0 -35
  583. package/dist/phase-diagram/colors.js +0 -51
  584. package/dist/phase-diagram/diagram-input.d.ts +0 -33
  585. package/dist/phase-diagram/diagram-input.js +0 -3
  586. package/dist/phase-diagram/index.d.ts +0 -13
  587. package/dist/phase-diagram/index.js +0 -13
  588. package/dist/phase-diagram/parse.d.ts +0 -55
  589. package/dist/phase-diagram/parse.js +0 -276
  590. package/dist/phase-diagram/svg-to-diagram.d.ts +0 -2
  591. package/dist/phase-diagram/svg-to-diagram.js +0 -869
  592. package/dist/phase-diagram/types.d.ts +0 -99
  593. package/dist/phase-diagram/types.js +0 -1
  594. package/dist/phase-diagram/utils.d.ts +0 -118
  595. package/dist/phase-diagram/utils.js +0 -606
  596. package/dist/plot/AxisLabel.svelte +0 -51
  597. package/dist/plot/AxisLabel.svelte.d.ts +0 -16
  598. package/dist/plot/BarPlot.svelte +0 -2256
  599. package/dist/plot/BarPlot.svelte.d.ts +0 -82
  600. package/dist/plot/BarPlotControls.svelte +0 -66
  601. package/dist/plot/BarPlotControls.svelte.d.ts +0 -18
  602. package/dist/plot/ColorBar.svelte +0 -719
  603. package/dist/plot/ColorBar.svelte.d.ts +0 -31
  604. package/dist/plot/ColorScaleSelect.svelte +0 -54
  605. package/dist/plot/ColorScaleSelect.svelte.d.ts +0 -15
  606. package/dist/plot/ElementScatter.svelte +0 -63
  607. package/dist/plot/ElementScatter.svelte.d.ts +0 -14
  608. package/dist/plot/FillArea.svelte +0 -226
  609. package/dist/plot/FillArea.svelte.d.ts +0 -20
  610. package/dist/plot/Histogram.svelte +0 -1654
  611. package/dist/plot/Histogram.svelte.d.ts +0 -50
  612. package/dist/plot/HistogramControls.svelte +0 -212
  613. package/dist/plot/HistogramControls.svelte.d.ts +0 -22
  614. package/dist/plot/InteractiveAxisLabel.svelte +0 -94
  615. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +0 -14
  616. package/dist/plot/Line.svelte +0 -85
  617. package/dist/plot/Line.svelte.d.ts +0 -15
  618. package/dist/plot/PlotControls.svelte +0 -537
  619. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  620. package/dist/plot/PlotLegend.svelte +0 -498
  621. package/dist/plot/PlotLegend.svelte.d.ts +0 -25
  622. package/dist/plot/PlotTooltip.svelte +0 -67
  623. package/dist/plot/PlotTooltip.svelte.d.ts +0 -17
  624. package/dist/plot/PortalSelect.svelte +0 -253
  625. package/dist/plot/PortalSelect.svelte.d.ts +0 -16
  626. package/dist/plot/ReferenceLine.svelte.d.ts +0 -20
  627. package/dist/plot/ReferenceLine3D.svelte +0 -154
  628. package/dist/plot/ReferenceLine3D.svelte.d.ts +0 -14
  629. package/dist/plot/ReferencePlane.svelte +0 -178
  630. package/dist/plot/ReferencePlane.svelte.d.ts +0 -14
  631. package/dist/plot/ScatterPlot.svelte +0 -2831
  632. package/dist/plot/ScatterPlot.svelte.d.ts +0 -92
  633. package/dist/plot/ScatterPlot3D.svelte +0 -499
  634. package/dist/plot/ScatterPlot3D.svelte.d.ts +0 -94
  635. package/dist/plot/ScatterPlot3DControls.svelte +0 -437
  636. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +0 -20
  637. package/dist/plot/ScatterPlot3DScene.svelte +0 -912
  638. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +0 -74
  639. package/dist/plot/ScatterPlotControls.svelte +0 -307
  640. package/dist/plot/ScatterPlotControls.svelte.d.ts +0 -17
  641. package/dist/plot/ScatterPoint.svelte +0 -185
  642. package/dist/plot/ScatterPoint.svelte.d.ts +0 -19
  643. package/dist/plot/SpacegroupBarPlot.svelte +0 -292
  644. package/dist/plot/SpacegroupBarPlot.svelte.d.ts +0 -9
  645. package/dist/plot/Surface3D.svelte +0 -200
  646. package/dist/plot/Surface3D.svelte.d.ts +0 -13
  647. package/dist/plot/ZeroLines.svelte +0 -96
  648. package/dist/plot/ZeroLines.svelte.d.ts +0 -32
  649. package/dist/plot/ZoomRect.svelte +0 -23
  650. package/dist/plot/ZoomRect.svelte.d.ts +0 -8
  651. package/dist/plot/axis-utils.d.ts +0 -19
  652. package/dist/plot/axis-utils.js +0 -80
  653. package/dist/plot/data-cleaning.d.ts +0 -37
  654. package/dist/plot/data-cleaning.js +0 -855
  655. package/dist/plot/data-transform.d.ts +0 -16
  656. package/dist/plot/data-transform.js +0 -45
  657. package/dist/plot/defaults.d.ts +0 -19
  658. package/dist/plot/defaults.js +0 -9
  659. package/dist/plot/fill-utils.d.ts +0 -51
  660. package/dist/plot/fill-utils.js +0 -337
  661. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  662. package/dist/plot/hover-lock.svelte.js +0 -46
  663. package/dist/plot/index.d.ts +0 -43
  664. package/dist/plot/index.js +0 -37
  665. package/dist/plot/interactions.d.ts +0 -12
  666. package/dist/plot/interactions.js +0 -100
  667. package/dist/plot/layout.d.ts +0 -60
  668. package/dist/plot/layout.js +0 -230
  669. package/dist/plot/reference-line.d.ts +0 -60
  670. package/dist/plot/reference-line.js +0 -316
  671. package/dist/plot/scales.d.ts +0 -48
  672. package/dist/plot/scales.js +0 -484
  673. package/dist/plot/svg.d.ts +0 -1
  674. package/dist/plot/svg.js +0 -11
  675. package/dist/plot/types.d.ts +0 -863
  676. package/dist/plot/types.js +0 -103
  677. package/dist/plot/utils/label-placement.d.ts +0 -47
  678. package/dist/plot/utils/label-placement.js +0 -256
  679. package/dist/plot/utils/series-visibility.d.ts +0 -9
  680. package/dist/plot/utils/series-visibility.js +0 -67
  681. package/dist/plot/utils.d.ts +0 -1
  682. package/dist/plot/utils.js +0 -14
  683. package/dist/rdf/RdfPlot.svelte +0 -247
  684. package/dist/rdf/RdfPlot.svelte.d.ts +0 -27
  685. package/dist/rdf/calc-rdf.d.ts +0 -4
  686. package/dist/rdf/calc-rdf.js +0 -111
  687. package/dist/rdf/index.d.ts +0 -23
  688. package/dist/rdf/index.js +0 -2
  689. package/dist/sanitize.d.ts +0 -4
  690. package/dist/sanitize.js +0 -107
  691. package/dist/settings.d.ts +0 -253
  692. package/dist/settings.js +0 -1123
  693. package/dist/spectral/Bands.svelte +0 -1040
  694. package/dist/spectral/Bands.svelte.d.ts +0 -40
  695. package/dist/spectral/BandsAndDos.svelte +0 -128
  696. package/dist/spectral/BandsAndDos.svelte.d.ts +0 -18
  697. package/dist/spectral/BrillouinBandsDos.svelte +0 -248
  698. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +0 -20
  699. package/dist/spectral/Dos.svelte +0 -697
  700. package/dist/spectral/Dos.svelte.d.ts +0 -29
  701. package/dist/spectral/helpers.d.ts +0 -117
  702. package/dist/spectral/helpers.js +0 -1023
  703. package/dist/spectral/index.d.ts +0 -6
  704. package/dist/spectral/index.js +0 -7
  705. package/dist/spectral/types.d.ts +0 -84
  706. package/dist/spectral/types.js +0 -2
  707. package/dist/state.svelte.d.ts +0 -25
  708. package/dist/state.svelte.js +0 -45
  709. package/dist/structure/Arrow.svelte +0 -72
  710. package/dist/structure/Arrow.svelte.d.ts +0 -15
  711. package/dist/structure/AtomLegend.svelte +0 -798
  712. package/dist/structure/AtomLegend.svelte.d.ts +0 -34
  713. package/dist/structure/Bond.svelte +0 -140
  714. package/dist/structure/Bond.svelte.d.ts +0 -9
  715. package/dist/structure/CanvasTooltip.svelte +0 -33
  716. package/dist/structure/CanvasTooltip.svelte.d.ts +0 -12
  717. package/dist/structure/CellSelect.svelte +0 -351
  718. package/dist/structure/CellSelect.svelte.d.ts +0 -13
  719. package/dist/structure/Cylinder.svelte +0 -45
  720. package/dist/structure/Cylinder.svelte.d.ts +0 -10
  721. package/dist/structure/Lattice.svelte +0 -196
  722. package/dist/structure/Lattice.svelte.d.ts +0 -17
  723. package/dist/structure/Structure.svelte +0 -1857
  724. package/dist/structure/Structure.svelte.d.ts +0 -83
  725. package/dist/structure/StructureControls.svelte +0 -1184
  726. package/dist/structure/StructureControls.svelte.d.ts +0 -31
  727. package/dist/structure/StructureExportPane.svelte +0 -251
  728. package/dist/structure/StructureExportPane.svelte.d.ts +0 -17
  729. package/dist/structure/StructureInfoPane.svelte +0 -434
  730. package/dist/structure/StructureInfoPane.svelte.d.ts +0 -18
  731. package/dist/structure/StructureScene.svelte +0 -1574
  732. package/dist/structure/StructureScene.svelte.d.ts +0 -104
  733. package/dist/structure/atom-properties.d.ts +0 -37
  734. package/dist/structure/atom-properties.js +0 -198
  735. package/dist/structure/bonding.d.ts +0 -33
  736. package/dist/structure/bonding.js +0 -304
  737. package/dist/structure/export.d.ts +0 -20
  738. package/dist/structure/export.js +0 -725
  739. package/dist/structure/ferrox-wasm-types.d.ts +0 -46
  740. package/dist/structure/ferrox-wasm-types.js +0 -18
  741. package/dist/structure/ferrox-wasm.d.ts +0 -94
  742. package/dist/structure/ferrox-wasm.js +0 -249
  743. package/dist/structure/index.d.ts +0 -110
  744. package/dist/structure/index.js +0 -168
  745. package/dist/structure/measure.d.ts +0 -6
  746. package/dist/structure/measure.js +0 -29
  747. package/dist/structure/parse.d.ts +0 -65
  748. package/dist/structure/parse.js +0 -1374
  749. package/dist/structure/partial-occupancy.d.ts +0 -25
  750. package/dist/structure/partial-occupancy.js +0 -99
  751. package/dist/structure/pbc.d.ts +0 -9
  752. package/dist/structure/pbc.js +0 -123
  753. package/dist/structure/supercell.d.ts +0 -8
  754. package/dist/structure/supercell.js +0 -137
  755. package/dist/structure/validation.d.ts +0 -2
  756. package/dist/structure/validation.js +0 -10
  757. package/dist/symmetry/SymmetryStats.svelte +0 -226
  758. package/dist/symmetry/SymmetryStats.svelte.d.ts +0 -21
  759. package/dist/symmetry/WyckoffTable.svelte +0 -113
  760. package/dist/symmetry/WyckoffTable.svelte.d.ts +0 -11
  761. package/dist/symmetry/cell-transform.d.ts +0 -12
  762. package/dist/symmetry/cell-transform.js +0 -77
  763. package/dist/symmetry/index.d.ts +0 -43
  764. package/dist/symmetry/index.js +0 -229
  765. package/dist/symmetry/spacegroups.d.ts +0 -9
  766. package/dist/symmetry/spacegroups.js +0 -394
  767. package/dist/table/HeatmapTable.svelte +0 -1854
  768. package/dist/table/HeatmapTable.svelte.d.ts +0 -49
  769. package/dist/table/ToggleMenu.svelte +0 -376
  770. package/dist/table/ToggleMenu.svelte.d.ts +0 -11
  771. package/dist/table/index.d.ts +0 -74
  772. package/dist/table/index.js +0 -38
  773. package/dist/theme/ThemeControl.svelte +0 -53
  774. package/dist/theme/ThemeControl.svelte.d.ts +0 -9
  775. package/dist/theme/index.d.ts +0 -29
  776. package/dist/theme/index.js +0 -79
  777. package/dist/theme/themes.mjs +0 -285
  778. package/dist/time.d.ts +0 -4
  779. package/dist/time.js +0 -70
  780. package/dist/tooltip/TooltipContent.svelte +0 -58
  781. package/dist/tooltip/TooltipContent.svelte.d.ts +0 -31
  782. package/dist/tooltip/index.d.ts +0 -2
  783. package/dist/tooltip/index.js +0 -2
  784. package/dist/tooltip/types.d.ts +0 -8
  785. package/dist/tooltip/types.js +0 -1
  786. package/dist/trajectory/Trajectory.svelte +0 -1517
  787. package/dist/trajectory/Trajectory.svelte.d.ts +0 -77
  788. package/dist/trajectory/TrajectoryError.svelte +0 -128
  789. package/dist/trajectory/TrajectoryError.svelte.d.ts +0 -13
  790. package/dist/trajectory/TrajectoryExportPane.svelte +0 -357
  791. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +0 -17
  792. package/dist/trajectory/TrajectoryInfoPane.svelte +0 -387
  793. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +0 -17
  794. package/dist/trajectory/constants.d.ts +0 -6
  795. package/dist/trajectory/constants.js +0 -7
  796. package/dist/trajectory/extract.d.ts +0 -5
  797. package/dist/trajectory/extract.js +0 -162
  798. package/dist/trajectory/format-detect.d.ts +0 -9
  799. package/dist/trajectory/format-detect.js +0 -76
  800. package/dist/trajectory/frame-reader.d.ts +0 -17
  801. package/dist/trajectory/frame-reader.js +0 -332
  802. package/dist/trajectory/helpers.d.ts +0 -14
  803. package/dist/trajectory/helpers.js +0 -172
  804. package/dist/trajectory/index.d.ts +0 -63
  805. package/dist/trajectory/index.js +0 -126
  806. package/dist/trajectory/parse/ase.d.ts +0 -2
  807. package/dist/trajectory/parse/ase.js +0 -77
  808. package/dist/trajectory/parse/hdf5.d.ts +0 -2
  809. package/dist/trajectory/parse/hdf5.js +0 -129
  810. package/dist/trajectory/parse/index.d.ts +0 -12
  811. package/dist/trajectory/parse/index.js +0 -299
  812. package/dist/trajectory/parse/lammps.d.ts +0 -5
  813. package/dist/trajectory/parse/lammps.js +0 -179
  814. package/dist/trajectory/parse/vasp.d.ts +0 -2
  815. package/dist/trajectory/parse/vasp.js +0 -68
  816. package/dist/trajectory/parse/xyz.d.ts +0 -2
  817. package/dist/trajectory/parse/xyz.js +0 -110
  818. package/dist/trajectory/plotting.d.ts +0 -28
  819. package/dist/trajectory/plotting.js +0 -423
  820. package/dist/trajectory/types.d.ts +0 -11
  821. package/dist/trajectory/types.js +0 -1
  822. package/dist/utils.d.ts +0 -5
  823. package/dist/utils.js +0 -36
  824. package/dist/xrd/XrdPlot.svelte +0 -615
  825. package/dist/xrd/XrdPlot.svelte.d.ts +0 -28
  826. package/dist/xrd/broadening.d.ts +0 -20
  827. package/dist/xrd/broadening.js +0 -97
  828. package/dist/xrd/calc-xrd.d.ts +0 -37
  829. package/dist/xrd/calc-xrd.js +0 -337
  830. package/dist/xrd/index.d.ts +0 -37
  831. package/dist/xrd/index.js +0 -4
  832. package/dist/xrd/parse.d.ts +0 -13
  833. package/dist/xrd/parse.js +0 -749
  834. /package/dist/{EmptyState.svelte → src/lib/EmptyState.svelte} +0 -0
  835. /package/dist/{Icon.svelte → src/lib/Icon.svelte} +0 -0
  836. /package/dist/{chempot-diagram → src/lib/chempot-diagram}/ChemPotScene3D.svelte +0 -0
  837. /package/dist/{colors → src/lib/colors}/alloy-colors.json +0 -0
  838. /package/dist/{colors → src/lib/colors}/dark-mode-colors.json +0 -0
  839. /package/dist/{colors → src/lib/colors}/jmol-colors.json +0 -0
  840. /package/dist/{colors → src/lib/colors}/muted-colors.json +0 -0
  841. /package/dist/{colors → src/lib/colors}/pastel-colors.json +0 -0
  842. /package/dist/{colors → src/lib/colors}/vesta-colors.json +0 -0
  843. /package/dist/{element → src/lib/element}/Nucleus.svelte +0 -0
  844. /package/dist/{element → src/lib/element}/data.json +0 -0
  845. /package/dist/{element → src/lib/element}/data.json.gz +0 -0
  846. /package/dist/{element → src/lib/element}/data.schema.json +0 -0
  847. /package/dist/{element-image-urls.json → src/lib/element-image-urls.json} +0 -0
  848. /package/dist/{feedback → src/lib/feedback}/Spinner.svelte +0 -0
  849. /package/dist/{feedback → src/lib/feedback}/StatusMessage.svelte +0 -0
  850. /package/dist/{periodic-table → src/lib/periodic-table}/TableInset.svelte +0 -0
  851. /package/dist/{plot → src/lib/plot}/ReferenceLine.svelte +0 -0
  852. /package/dist/{xrd → src/lib/xrd}/atomic_scattering_params.json +0 -0
@@ -0,0 +1,2192 @@
1
+ import {
2
+ count_atoms_in_composition,
3
+ extract_formula_elements,
4
+ sort_by_electronegativity,
5
+ } from '$lib/composition'
6
+ import type { ElementSymbol } from '$lib/element'
7
+ import * as math from '$lib/math'
8
+ import type { Point2D, Point3D } from '$lib/math'
9
+ import {
10
+ barycentric_to_ternary_xyz,
11
+ barycentric_to_tetrahedral,
12
+ composition_to_barycentric_3d,
13
+ composition_to_barycentric_4d,
14
+ composition_to_barycentric_nd,
15
+ } from './barycentric-coords'
16
+ import type {
17
+ ConvexHullEntry,
18
+ ConvexHullFace,
19
+ ConvexHullTriangle,
20
+ PhaseData,
21
+ PhaseStats,
22
+ Plane,
23
+ ProcessedPhaseData,
24
+ } from './types'
25
+ import { get_arity, HULL_STABILITY_TOL, is_on_hull, is_unary_entry } from './helpers'
26
+
27
+ // Track warned keys to avoid log spam on large datasets with repeated invalid keys
28
+ const warned_keys = new Set<string>()
29
+ const cross_point_2d = (origin: Point2D, point_a: Point2D, point_b: Point2D) =>
30
+ (point_a.x - origin.x) * (point_b.y - origin.y) -
31
+ (point_a.y - origin.y) * (point_b.x - origin.x)
32
+
33
+ // Normalize convex hull composition keys by stripping oxidation states (e.g. "V4+" -> "V")
34
+ // and merging amounts for keys that map to the same element. Filters non-positive amounts.
35
+ // Only extracts FIRST valid element from each key (e.g. "Fe2O3" -> "Fe", not both Fe and O).
36
+ export function normalize_hull_composition_keys(
37
+ composition: Record<string, number>,
38
+ ): Partial<Record<ElementSymbol, number>> {
39
+ const normalized: Partial<Record<ElementSymbol, number>> = {}
40
+ for (const [key, amount] of Object.entries(composition)) {
41
+ if (typeof amount !== `number` || !Number.isFinite(amount) || amount <= 0) continue
42
+ // Extract first valid element symbol from key (handles oxidation states like "V4+", "Fe2+")
43
+ const elem = extract_formula_elements(key, { unique: false })[0]
44
+ if (!elem) {
45
+ // Dedupe warnings to avoid log spam on large datasets
46
+ if (!warned_keys.has(key)) {
47
+ warned_keys.add(key)
48
+ console.warn(`Skipping unrecognized composition key: "${key}"`)
49
+ }
50
+ continue
51
+ }
52
+ normalized[elem] = (normalized[elem] ?? 0) + amount
53
+ }
54
+ return normalized
55
+ }
56
+
57
+ export function process_hull_entries(entries: PhaseData[]): ProcessedPhaseData {
58
+ // Normalize composition keys to strip oxidation states (e.g. "Fe3+" -> "Fe")
59
+ // Filter out entries whose composition normalizes to {} (all keys invalid or non-positive)
60
+ const normalized_entries = entries
61
+ .map((entry) => ({
62
+ ...entry,
63
+ composition: normalize_hull_composition_keys(entry.composition),
64
+ }))
65
+ .filter((entry) => Object.keys(entry.composition).length > 0)
66
+
67
+ // Single-pass partition instead of two filter passes
68
+ const stable_entries: PhaseData[] = []
69
+ const unstable_entries: PhaseData[] = []
70
+ for (const entry of normalized_entries) {
71
+ const stable =
72
+ typeof entry.is_stable === `boolean`
73
+ ? entry.is_stable
74
+ : (entry.e_above_hull ?? Infinity) <= HULL_STABILITY_TOL
75
+ ;(stable ? stable_entries : unstable_entries).push(entry)
76
+ }
77
+
78
+ // Extract unique element symbols from normalized compositions
79
+ const elements = Array.from(
80
+ new Set(normalized_entries.flatMap((entry) => Object.keys(entry.composition))),
81
+ ).sort() as ElementSymbol[]
82
+
83
+ const el_refs = Object.fromEntries(
84
+ stable_entries
85
+ .filter(is_unary_entry)
86
+ .map((entry) => [Object.keys(entry.composition)[0], entry]),
87
+ )
88
+
89
+ return {
90
+ entries: normalized_entries,
91
+ stable_entries,
92
+ unstable_entries,
93
+ elements,
94
+ el_refs,
95
+ }
96
+ }
97
+
98
+ // Get energy per atom with correction applied, or fallback to raw energy_per_atom/energy.
99
+ // Note: correction is expected to be a total-entry value (eV), not per-atom.
100
+ // This matches the Materials Project convention where corrections are applied to total energies.
101
+ function get_energy_per_atom(entry: PhaseData): number {
102
+ // Use Math.max instead of || to prevent pathological negative totals from flipping sign
103
+ const atoms = Math.max(count_atoms_in_composition(entry.composition), 1e-12)
104
+ if (typeof entry.correction === `number`) {
105
+ const total =
106
+ typeof entry.energy_per_atom === `number`
107
+ ? entry.energy_per_atom * atoms
108
+ : (entry.energy ?? 0)
109
+ return (total + entry.correction) / atoms
110
+ }
111
+ return entry.energy_per_atom ?? (entry.energy ?? 0) / atoms
112
+ }
113
+
114
+ export function compute_e_form_per_atom(
115
+ entry: PhaseData,
116
+ el_refs: Record<string, PhaseData>,
117
+ ): number | null {
118
+ const atoms = count_atoms_in_composition(entry.composition)
119
+ if (atoms <= 0) return null
120
+ let ref_sum = 0
121
+ for (const [el, amt] of Object.entries(entry.composition)) {
122
+ const ref = el_refs[el]
123
+ if (!ref) return null
124
+ ref_sum += (amt / atoms) * get_energy_per_atom(ref)
125
+ }
126
+ return get_energy_per_atom(entry) - ref_sum
127
+ }
128
+
129
+ export function find_lowest_energy_unary_refs(
130
+ entries: PhaseData[],
131
+ ): Record<string, PhaseData> {
132
+ const refs: Record<string, PhaseData> = {}
133
+ for (const entry of entries) {
134
+ if (!is_unary_entry(entry)) continue
135
+ const el = Object.keys(entry.composition).find(
136
+ (key) => (entry.composition[key as ElementSymbol] ?? 0) > 0,
137
+ )
138
+ if (!el) continue
139
+ const current = refs[el]
140
+ if (!current || get_energy_per_atom(entry) < get_energy_per_atom(current)) {
141
+ refs[el] = entry
142
+ }
143
+ }
144
+ return refs
145
+ }
146
+
147
+ // Calculate energy above hull (eV/atom). Missing pure element refs default to E_form = 0.
148
+ export function calculate_e_above_hull(
149
+ entry: PhaseData,
150
+ reference_entries: PhaseData[],
151
+ ): number
152
+ export function calculate_e_above_hull(
153
+ entries: PhaseData[],
154
+ reference_entries: PhaseData[],
155
+ ): Record<string, number>
156
+ export function calculate_e_above_hull(
157
+ input: PhaseData | PhaseData[],
158
+ reference_entries: PhaseData[],
159
+ ): number | Record<string, number> {
160
+ const is_single = !Array.isArray(input)
161
+ const entries_of_interest = is_single ? [input] : input
162
+
163
+ if (entries_of_interest.length === 0) return {} // Empty input → empty result (not an error)
164
+ if (reference_entries.length === 0) {
165
+ throw new Error(`Reference entries cannot be empty`)
166
+ }
167
+
168
+ // 1. Identify chemical system
169
+ const elements = Array.from(
170
+ new Set(reference_entries.flatMap((entry) => Object.keys(entry.composition))),
171
+ ).sort() as ElementSymbol[]
172
+
173
+ // 2. Validate subset
174
+ const element_set = new Set(elements)
175
+ for (const entry of entries_of_interest) {
176
+ for (const el of Object.keys(entry.composition)) {
177
+ if (!element_set.has(el as ElementSymbol)) {
178
+ throw new Error(
179
+ `Entry contains element ${el} not present in reference system: ${elements.join(`-`)}`,
180
+ )
181
+ }
182
+ }
183
+ }
184
+
185
+ // 3. Compute formation energies
186
+ const refs = find_lowest_energy_unary_refs(reference_entries)
187
+ const compute_e_form = (entry: PhaseData) =>
188
+ typeof entry.e_form_per_atom === `number`
189
+ ? entry.e_form_per_atom
190
+ : compute_e_form_per_atom(entry, refs)
191
+
192
+ const interest_data = entries_of_interest.map((entry) => ({
193
+ entry,
194
+ e_form: compute_e_form(entry),
195
+ }))
196
+
197
+ // 4. Branch by arity
198
+ const arity = elements.length
199
+ const results: Record<string, number> = {}
200
+
201
+ if (arity === 1) {
202
+ // Unary system
203
+ for (const { entry, e_form } of interest_data) {
204
+ const id = entry.entry_id ?? JSON.stringify(entry.composition)
205
+ // For unary, e_above_hull is simply e_form (since stable state is 0)
206
+ // Unless we have multiple polymorphs, in which case the hull is at min(e_form) which should be 0
207
+ // But compute_e_form_per_atom already subtracts the stable unary reference energy.
208
+ // So e_form IS e_above_hull for unary systems if correction logic holds.
209
+ results[id] = e_form ?? NaN
210
+ }
211
+ } else if (arity === 2) {
212
+ // Binary system
213
+ const [_el1, el2] = elements
214
+ // Build hull points from references
215
+ const hull_input_map = new Map<number, number>() // x -> min_e_form
216
+
217
+ for (const ref of reference_entries) {
218
+ const e_form = compute_e_form(ref)
219
+ if (typeof e_form !== `number`) continue
220
+ const total = count_atoms_in_composition(ref.composition)
221
+ if (total <= 0) continue
222
+ const x = (ref.composition[el2] ?? 0) / total
223
+ const current = hull_input_map.get(x)
224
+ if (current === undefined || e_form < current) {
225
+ hull_input_map.set(x, e_form)
226
+ }
227
+ }
228
+ // Ensure endpoints (pure elements default to e_form = 0)
229
+ if (!hull_input_map.has(0)) hull_input_map.set(0, 0)
230
+ if (!hull_input_map.has(1)) hull_input_map.set(1, 0)
231
+
232
+ const hull_points: Point2D[] = Array.from(hull_input_map, ([x, y]) => ({ x, y }))
233
+ const lower_hull = compute_lower_hull_2d(hull_points)
234
+
235
+ for (const { entry, e_form } of interest_data) {
236
+ const id = entry.entry_id ?? JSON.stringify(entry.composition)
237
+ if (typeof e_form !== `number`) {
238
+ results[id] = NaN
239
+ continue
240
+ }
241
+ const total = count_atoms_in_composition(entry.composition)
242
+ // Guard for degenerate compositions (mirror the refs loop check)
243
+ if (total <= 0) {
244
+ results[id] = NaN
245
+ continue
246
+ }
247
+ const x = (entry.composition[el2] ?? 0) / total
248
+ const y_hull = interpolate_hull_2d(lower_hull, x)
249
+ results[id] = y_hull === null ? NaN : Math.max(0, e_form - y_hull)
250
+ }
251
+ } else if (arity === 3) {
252
+ // Ternary system
253
+ const ref_points: Point3D[] = []
254
+ for (const ref of reference_entries) {
255
+ const e_form = compute_e_form(ref)
256
+ if (typeof e_form !== `number`) continue
257
+ try {
258
+ const bary = composition_to_barycentric_3d(ref.composition, elements)
259
+ const point = barycentric_to_ternary_xyz(bary, e_form)
260
+ ref_points.push(point)
261
+ } catch {
262
+ // Ignore invalid compositions
263
+ }
264
+ }
265
+ // Ensure corner points (pure elements default to e_form = 0)
266
+ for (const el of elements) {
267
+ const corner = barycentric_to_ternary_xyz(
268
+ composition_to_barycentric_3d({ [el]: 1 }, elements),
269
+ 0,
270
+ )
271
+ if (
272
+ !ref_points.some(
273
+ (point) =>
274
+ Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z) < 1e-9,
275
+ )
276
+ ) {
277
+ ref_points.push(corner)
278
+ }
279
+ }
280
+
281
+ const hull_triangles = compute_lower_hull_triangles(ref_points)
282
+ const hull_models = build_lower_hull_model(hull_triangles)
283
+
284
+ for (const { entry, e_form } of interest_data) {
285
+ const id = entry.entry_id ?? JSON.stringify(entry.composition)
286
+ if (typeof e_form !== `number`) {
287
+ results[id] = NaN
288
+ continue
289
+ }
290
+ try {
291
+ const bary = composition_to_barycentric_3d(entry.composition, elements)
292
+ const point = barycentric_to_ternary_xyz(bary, e_form)
293
+ const z_hull = e_hull_at_xy(hull_models, point.x, point.y)
294
+ results[id] = z_hull === null ? NaN : Math.max(0, point.z - z_hull)
295
+ } catch {
296
+ results[id] = NaN
297
+ }
298
+ }
299
+ } else if (arity === 4) {
300
+ // Quaternary system
301
+ const ref_points: Point4D[] = []
302
+ for (const ref of reference_entries) {
303
+ const e_form = compute_e_form(ref)
304
+ if (typeof e_form !== `number`) continue
305
+ try {
306
+ const bary = composition_to_barycentric_4d(ref.composition, elements)
307
+ const tet = barycentric_to_tetrahedral(bary)
308
+ ref_points.push({ ...tet, w: e_form })
309
+ } catch {
310
+ // Ignore invalid
311
+ }
312
+ }
313
+
314
+ // Ensure corner points (pure elements default to e_form = 0)
315
+ for (const el of elements) {
316
+ const tet = barycentric_to_tetrahedral(
317
+ composition_to_barycentric_4d({ [el]: 1 }, elements),
318
+ )
319
+ const corner = { ...tet, w: 0 }
320
+ const dist = (point: Point4D) =>
321
+ Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z, point.w)
322
+ if (!ref_points.some((point) => dist(point) < 1e-9)) ref_points.push(corner)
323
+ }
324
+
325
+ const hull_tetrahedra = compute_lower_hull_4d(ref_points)
326
+ const interest_points: Point4D[] = []
327
+ const interest_indices: number[] = []
328
+
329
+ interest_data.forEach(({ entry, e_form }, idx) => {
330
+ if (typeof e_form === `number`) {
331
+ try {
332
+ const bary = composition_to_barycentric_4d(entry.composition, elements)
333
+ const tet = barycentric_to_tetrahedral(bary)
334
+ interest_points.push({ ...tet, w: e_form })
335
+ interest_indices.push(idx)
336
+ } catch {
337
+ // Skip
338
+ }
339
+ }
340
+ })
341
+
342
+ const distances = compute_e_above_hull_4d(interest_points, hull_tetrahedra)
343
+
344
+ // Build reverse lookup for O(1) access
345
+ const idx_to_point_idx = new Map<number, number>()
346
+ interest_indices.forEach((original_idx, point_idx) => {
347
+ idx_to_point_idx.set(original_idx, point_idx)
348
+ })
349
+
350
+ // Map back
351
+ for (let idx = 0; idx < interest_data.length; idx++) {
352
+ const { entry } = interest_data[idx]
353
+ const id = entry.entry_id ?? JSON.stringify(entry.composition)
354
+ const point_idx = idx_to_point_idx.get(idx) ?? -1
355
+ if (point_idx !== -1) {
356
+ results[id] = Math.max(0, distances[point_idx])
357
+ } else {
358
+ results[id] = NaN
359
+ }
360
+ }
361
+ } else {
362
+ // Arity 5+ uses generalized N-dimensional convex hull
363
+ // Helper to convert entry to hull point, returns null on expected errors
364
+ const to_hull_point = (entry: PhaseData, e_form: number): number[] | null => {
365
+ try {
366
+ return [...composition_to_barycentric_nd(entry.composition, elements), e_form]
367
+ } catch (err) {
368
+ // Skip expected errors (missing elements), warn on unexpected
369
+ if (err instanceof Error && !err.message.includes(`no elements from the system`)) {
370
+ console.warn(`Skipping entry: ${err.message}`)
371
+ }
372
+ return null
373
+ }
374
+ }
375
+
376
+ // Build reference points
377
+ const ref_points: number[][] = []
378
+ for (const ref of reference_entries) {
379
+ const e_form = compute_e_form(ref)
380
+ if (typeof e_form !== `number`) continue
381
+ const point = to_hull_point(ref, e_form)
382
+ if (point) ref_points.push(point)
383
+ }
384
+
385
+ // Ensure corner points (pure elements default to e_form = 0)
386
+ for (let el_idx = 0; el_idx < arity; el_idx++) {
387
+ const corner = Array(arity + 1).fill(0)
388
+ corner[el_idx] = 1 // ith barycentric coord = 1
389
+ if (!ref_points.some((pt) => norm_nd(subtract_nd(pt, corner)) < EPS)) {
390
+ ref_points.push(corner)
391
+ }
392
+ }
393
+
394
+ const hull_facets = compute_lower_hull_nd(compute_quickhull_nd(ref_points))
395
+
396
+ // Warn if hull is degenerate (all points coplanar or insufficient spread)
397
+ if (hull_facets.length === 0 && ref_points.length >= arity + 1) {
398
+ console.warn(
399
+ `N-dimensional hull for ${arity}-element system is degenerate. ` +
400
+ `Falling back to tie-hyperplane at energy 0. ` +
401
+ `Consider using pymatgen for complex high-dimensional phase diagrams.`,
402
+ )
403
+ }
404
+
405
+ // Build query points with mapping back to original indices
406
+ const interest_points: number[][] = []
407
+ const idx_to_point_idx = new Map<number, number>()
408
+
409
+ for (let idx = 0; idx < interest_data.length; idx++) {
410
+ const { entry, e_form } = interest_data[idx]
411
+ if (typeof e_form !== `number`) continue
412
+ const point = to_hull_point(entry, e_form)
413
+ if (point) {
414
+ idx_to_point_idx.set(idx, interest_points.length)
415
+ interest_points.push(point)
416
+ }
417
+ }
418
+
419
+ // Compute hull distances (empty array if degenerate hull)
420
+ const distances =
421
+ hull_facets.length > 0
422
+ ? compute_e_above_hull_nd(interest_points, hull_facets, ref_points)
423
+ : []
424
+
425
+ // Map results back to entries
426
+ for (let idx = 0; idx < interest_data.length; idx++) {
427
+ const { entry, e_form } = interest_data[idx]
428
+ const id = entry.entry_id ?? JSON.stringify(entry.composition)
429
+ const point_idx = idx_to_point_idx.get(idx)
430
+
431
+ if (point_idx === undefined) {
432
+ results[id] = NaN
433
+ } else if (hull_facets.length === 0 && typeof e_form === `number`) {
434
+ // Degenerate case: hull is tie-hyperplane at energy 0
435
+ results[id] = Math.max(0, e_form)
436
+ } else {
437
+ results[id] = Math.max(0, distances[point_idx])
438
+ }
439
+ }
440
+ }
441
+
442
+ if (is_single) {
443
+ const id =
444
+ entries_of_interest[0].entry_id ?? JSON.stringify(entries_of_interest[0].composition)
445
+ return results[id]
446
+ }
447
+ return results
448
+ }
449
+
450
+ export function get_convex_hull_stats(
451
+ processed_entries: PhaseData[],
452
+ elements: ElementSymbol[],
453
+ max_arity: number = 4,
454
+ ): PhaseStats | null {
455
+ if (processed_entries.length === 0) return null
456
+ max_arity = Math.max(1, max_arity)
457
+
458
+ let unary = 0
459
+ let binary = 0
460
+ let ternary = 0
461
+ let quaternary = 0
462
+ let quinary_plus = 0
463
+ for (const entry of processed_entries) {
464
+ const arity = get_arity(entry)
465
+ if (arity === 1) unary++
466
+ else if (arity === 2) binary++
467
+ else if (arity === 3) ternary++
468
+ else if (arity === 4) quaternary++
469
+ else if (arity >= 5) quinary_plus++
470
+ }
471
+ // Zero out counts beyond system dimensionality for cleaner display
472
+ // quinary_plus is intentionally not zeroed — it's a catch-all bucket that
473
+ // is naturally 0 for systems with fewer than 5 elements
474
+ if (max_arity < 4) quaternary = 0
475
+ if (max_arity < 3) ternary = 0
476
+ if (max_arity < 2) binary = 0
477
+
478
+ const stable_count = processed_entries.filter((entry) => is_on_hull(entry)).length
479
+ const unstable_count = processed_entries.length - stable_count
480
+
481
+ const energies = processed_entries
482
+ .map(
483
+ (entry) => entry.e_form_per_atom ?? entry.energy_per_atom ?? get_energy_per_atom(entry),
484
+ )
485
+ .filter(Number.isFinite)
486
+
487
+ // Use reduce instead of Math.min/max(...arr) to avoid stack overflow on large datasets
488
+ const energy_range =
489
+ energies.length > 0
490
+ ? {
491
+ min: energies.reduce((min, val) => (val < min ? val : min), Infinity),
492
+ max: energies.reduce((max, val) => (val > max ? val : max), -Infinity),
493
+ avg: energies.reduce((sum, val) => sum + val, 0) / energies.length,
494
+ }
495
+ : { min: 0, max: 0, avg: 0 }
496
+
497
+ const hull_distances = processed_entries
498
+ .map((entry) => entry.e_above_hull)
499
+ .filter((val): val is number => typeof val === `number` && val >= 0)
500
+ const hull_distance =
501
+ hull_distances.length > 0
502
+ ? {
503
+ max: hull_distances.reduce((max, val) => (val > max ? val : max), -Infinity),
504
+ avg: hull_distances.reduce((sum, val) => sum + val, 0) / hull_distances.length,
505
+ }
506
+ : { max: 0, avg: 0 }
507
+
508
+ return {
509
+ total: processed_entries.length,
510
+ unary,
511
+ binary,
512
+ ternary,
513
+ quaternary,
514
+ quinary_plus,
515
+ stable: stable_count,
516
+ unstable: unstable_count,
517
+ energy_range,
518
+ hull_distance,
519
+ elements: elements.length,
520
+ chemical_system: sort_by_electronegativity([...elements]).join(`-`),
521
+ max_arity,
522
+ }
523
+ }
524
+
525
+ // Result type for process_hull_for_stats
526
+ export interface HighDimHullResult {
527
+ stable_entries: ConvexHullEntry[]
528
+ unstable_entries: ConvexHullEntry[]
529
+ phase_stats: PhaseStats | null
530
+ }
531
+
532
+ // Convert a PhaseData entry to a ConvexHullEntry with default visual fields.
533
+ // x/y/z default to 0 since high-dim systems aren't visually plotted.
534
+ function to_hull_entry(entry: PhaseData): ConvexHullEntry {
535
+ return {
536
+ ...entry,
537
+ is_element: get_arity(entry) === 1,
538
+ x: 0,
539
+ y: 0,
540
+ z: 0,
541
+ }
542
+ }
543
+
544
+ // Process raw hull entries for high-dimensional systems (5+ elements) where the
545
+ // ConvexHull visual component can't render. Computes formation energies, hull distances,
546
+ // stable/unstable classification, and phase stats. Returns null on failure.
547
+ // Optionally accepts `elements` to scope the chemical system; if omitted, elements
548
+ // are derived from the entries' compositions.
549
+ export function process_hull_for_stats(
550
+ entries: PhaseData[],
551
+ elements?: ElementSymbol[],
552
+ ): HighDimHullResult | null {
553
+ if (!entries.length) return null
554
+
555
+ const processed = process_hull_entries(entries)
556
+ if (!processed.entries.length) return null
557
+ const hull_elements = elements ?? processed.elements
558
+
559
+ // Compute formation energies
560
+ const el_refs = find_lowest_energy_unary_refs(processed.entries)
561
+ for (const entry of processed.entries) {
562
+ if (entry.e_form_per_atom === undefined) {
563
+ const e_form = compute_e_form_per_atom(entry, el_refs)
564
+ if (e_form !== null) entry.e_form_per_atom = e_form
565
+ }
566
+ }
567
+
568
+ // Compute hull distances. Note: entries without entry_id are keyed by
569
+ // JSON.stringify(composition), so polymorphs at the same composition
570
+ // collide — the last-processed entry's distance wins for all of them.
571
+ try {
572
+ const hull_distances = calculate_e_above_hull(processed.entries, processed.entries)
573
+
574
+ for (const entry of processed.entries) {
575
+ const dist = hull_distances[entry.entry_id ?? JSON.stringify(entry.composition)]
576
+ if (typeof dist === `number` && Number.isFinite(dist)) {
577
+ entry.e_above_hull = dist
578
+ entry.is_stable = dist < HULL_STABILITY_TOL
579
+ } else {
580
+ // Clear stale hull metadata so previous values don't persist
581
+ entry.e_above_hull = undefined
582
+ entry.is_stable = undefined
583
+ }
584
+ }
585
+ } catch (err) {
586
+ console.warn(`Failed to compute high-dim hull:`, err)
587
+ return null
588
+ }
589
+
590
+ const hull_entries = processed.entries.map(to_hull_entry)
591
+ return {
592
+ stable_entries: hull_entries.filter((entry) => is_on_hull(entry)),
593
+ unstable_entries: hull_entries.filter((entry) => !is_on_hull(entry)),
594
+ phase_stats: get_convex_hull_stats(processed.entries, hull_elements, hull_elements.length),
595
+ }
596
+ }
597
+
598
+ // --- 2D Convex Hull (Binary Phase Diagrams) ---
599
+
600
+ export function compute_lower_hull_2d(points: Point2D[]): Point2D[] {
601
+ // Andrew's monotone chain for lower hull
602
+ // Sort by x then y
603
+ const sorted = [...points].sort((p1, p2) => p1.x - p2.x || p1.y - p2.y)
604
+ const lower: Point2D[] = []
605
+
606
+ for (const point of sorted) {
607
+ while (
608
+ lower.length >= 2 &&
609
+ cross_point_2d(lower[lower.length - 2], lower[lower.length - 1], point) <= 0
610
+ )
611
+ lower.pop()
612
+ lower.push(point)
613
+ }
614
+ return lower
615
+ }
616
+
617
+ export function interpolate_hull_2d(hull: Point2D[], x: number): number | null {
618
+ if (hull.length < 2) return null
619
+ // Handle out of bounds by clamping to endpoints
620
+ if (x <= hull[0].x) return hull[0].y
621
+ if (x >= hull[hull.length - 1].x) return hull[hull.length - 1].y
622
+
623
+ for (let idx = 0; idx < hull.length - 1; idx++) {
624
+ const p1 = hull[idx]
625
+ const p2 = hull[idx + 1]
626
+ if (x >= p1.x && x <= p2.x) {
627
+ const t = (x - p1.x) / Math.max(1e-12, p2.x - p1.x)
628
+ return p1.y * (1 - t) + p2.y * t
629
+ }
630
+ }
631
+ return null
632
+ }
633
+
634
+ // --- Convex hull geometry ---
635
+
636
+ const EPS = 1e-9
637
+
638
+ const subtract = (pt1: Point3D, pt2: Point3D): Point3D => ({
639
+ x: pt1.x - pt2.x,
640
+ y: pt1.y - pt2.y,
641
+ z: pt1.z - pt2.z,
642
+ })
643
+
644
+ const cross = (vec1: Point3D, vec2: Point3D): Point3D => ({
645
+ x: vec1.y * vec2.z - vec1.z * vec2.y,
646
+ y: vec1.z * vec2.x - vec1.x * vec2.z,
647
+ z: vec1.x * vec2.y - vec1.y * vec2.x,
648
+ })
649
+
650
+ const norm = (point: Point3D): number =>
651
+ Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z)
652
+
653
+ function normalize(point: Point3D): Point3D {
654
+ const length = norm(point)
655
+ if (length < EPS) return { x: 0, y: 0, z: 0 }
656
+ return { x: point.x / length, y: point.y / length, z: point.z / length }
657
+ }
658
+
659
+ function compute_plane(p1: Point3D, p2: Point3D, p3: Point3D): Plane {
660
+ const edge_12 = subtract(p2, p1)
661
+ const edge_13 = subtract(p3, p1)
662
+ const normal = normalize(cross(edge_12, edge_13))
663
+ const offset = -(normal.x * p1.x + normal.y * p1.y + normal.z * p1.z)
664
+ return { normal, offset }
665
+ }
666
+
667
+ const point_plane_signed_distance = (plane: Plane, point: Point3D): number =>
668
+ plane.normal.x * point.x + plane.normal.y * point.y + plane.normal.z * point.z + plane.offset
669
+
670
+ const compute_centroid = (p1: Point3D, p2: Point3D, p3: Point3D): Point3D => ({
671
+ x: (p1.x + p2.x + p3.x) / 3,
672
+ y: (p1.y + p2.y + p3.y) / 3,
673
+ z: (p1.z + p2.z + p3.z) / 3,
674
+ })
675
+
676
+ function distance_point_to_line(
677
+ line_start: Point3D,
678
+ line_end: Point3D,
679
+ point: Point3D,
680
+ ): number {
681
+ const line_vec = subtract(line_end, line_start)
682
+ const to_point = subtract(point, line_start)
683
+ const cross_prod = cross(line_vec, to_point)
684
+ const line_len = norm(line_vec)
685
+ if (line_len < EPS) return 0
686
+ return norm(cross_prod) / line_len
687
+ }
688
+
689
+ function choose_initial_tetrahedron(
690
+ points: Point3D[],
691
+ ): [number, number, number, number] | null {
692
+ if (points.length < 4) return null
693
+ let idx_min_x = 0
694
+ let idx_max_x = 0
695
+ for (let idx = 1; idx < points.length; idx++) {
696
+ if (points[idx].x < points[idx_min_x].x) idx_min_x = idx
697
+ if (points[idx].x > points[idx_max_x].x) idx_max_x = idx
698
+ }
699
+ if (idx_min_x === idx_max_x) return null
700
+ let idx_far_line = -1
701
+ let best_dist_line = -1
702
+ for (let idx = 0; idx < points.length; idx++) {
703
+ if (idx === idx_min_x || idx === idx_max_x) continue
704
+ const dist = distance_point_to_line(points[idx_min_x], points[idx_max_x], points[idx])
705
+ if (dist > best_dist_line) {
706
+ best_dist_line = dist
707
+ idx_far_line = idx
708
+ }
709
+ }
710
+ if (idx_far_line === -1 || best_dist_line < EPS) return null
711
+ const plane0 = compute_plane(points[idx_min_x], points[idx_max_x], points[idx_far_line])
712
+ let idx_far_plane = -1
713
+ let best_dist_plane = -1
714
+ for (let idx = 0; idx < points.length; idx++) {
715
+ if (idx === idx_min_x || idx === idx_max_x || idx === idx_far_line) continue
716
+ const dist = Math.abs(point_plane_signed_distance(plane0, points[idx]))
717
+ if (dist > best_dist_plane) {
718
+ best_dist_plane = dist
719
+ idx_far_plane = idx
720
+ }
721
+ }
722
+ if (idx_far_plane === -1 || best_dist_plane < EPS) return null
723
+ return [idx_min_x, idx_max_x, idx_far_line, idx_far_plane]
724
+ }
725
+
726
+ function make_face(
727
+ points: Point3D[],
728
+ a: number,
729
+ b: number,
730
+ c: number,
731
+ interior_point: Point3D,
732
+ ): ConvexHullFace {
733
+ let plane = compute_plane(points[a], points[b], points[c])
734
+ let centroid = compute_centroid(points[a], points[b], points[c])
735
+ const dist_interior = point_plane_signed_distance(plane, interior_point)
736
+ if (dist_interior > 0) {
737
+ plane = compute_plane(points[a], points[c], points[b])
738
+ centroid = compute_centroid(points[a], points[c], points[b])
739
+ return { vertices: [a, c, b], plane, centroid, outside_points: new Set<number>() }
740
+ }
741
+ return { vertices: [a, b, c], plane, centroid, outside_points: new Set<number>() }
742
+ }
743
+
744
+ function assign_outside_points(
745
+ face: ConvexHullFace,
746
+ points: Point3D[],
747
+ candidate_indices: number[],
748
+ ): void {
749
+ face.outside_points.clear()
750
+ for (const idx of candidate_indices) {
751
+ const distance = point_plane_signed_distance(face.plane, points[idx])
752
+ if (distance > EPS) face.outside_points.add(idx)
753
+ }
754
+ }
755
+
756
+ function collect_candidate_points(faces: ConvexHullFace[]): number[] {
757
+ const set = new Set<number>()
758
+ for (const face of faces) for (const idx of face.outside_points) set.add(idx)
759
+ return Array.from(set)
760
+ }
761
+
762
+ function farthest_point_for_face(
763
+ points: Point3D[],
764
+ face: ConvexHullFace,
765
+ ): { idx: number; distance: number } | null {
766
+ let best_idx = -1
767
+ let best_distance = -1
768
+ for (const idx of face.outside_points) {
769
+ const distance = point_plane_signed_distance(face.plane, points[idx])
770
+ if (distance > best_distance) {
771
+ best_distance = distance
772
+ best_idx = idx
773
+ }
774
+ }
775
+ if (best_idx === -1) return null
776
+ return { idx: best_idx, distance: best_distance }
777
+ }
778
+
779
+ function build_horizon(
780
+ faces: ConvexHullFace[],
781
+ visible_face_indices: Set<number>,
782
+ ): [number, number][] {
783
+ const edge_count = new Map<string, [number, number]>()
784
+ for (const face_idx of visible_face_indices) {
785
+ const face = faces[face_idx]
786
+ const [a, b, c] = face.vertices
787
+ const edges: [number, number][] = [
788
+ [a, b],
789
+ [b, c],
790
+ [c, a],
791
+ ]
792
+ for (const [u, v] of edges) {
793
+ const key = u < v ? `${u}|${v}` : `${v}|${u}`
794
+ if (!edge_count.has(key)) edge_count.set(key, [u, v])
795
+ else edge_count.set(key, [Number.NaN, Number.NaN])
796
+ }
797
+ }
798
+ const horizon: [number, number][] = []
799
+ for (const uv of edge_count.values()) {
800
+ if (Number.isNaN(uv[0])) continue
801
+ horizon.push(uv)
802
+ }
803
+ return horizon
804
+ }
805
+
806
+ export function compute_quickhull_triangles(points: Point3D[]): ConvexHullTriangle[] {
807
+ if (points.length < 4) return [] // hull needs at least 4 non-coplanar points, bail if not provided
808
+ const initial = choose_initial_tetrahedron(points)
809
+ if (!initial) return []
810
+ const [i0, i1, i2, i3] = initial
811
+ const interior_point = {
812
+ x: (points[i0].x + points[i1].x + points[i2].x + points[i3].x) / 4,
813
+ y: (points[i0].y + points[i1].y + points[i2].y + points[i3].y) / 4,
814
+ z: (points[i0].z + points[i1].z + points[i2].z + points[i3].z) / 4,
815
+ }
816
+ const faces: ConvexHullFace[] = [
817
+ make_face(points, i0, i1, i2, interior_point),
818
+ make_face(points, i0, i2, i3, interior_point),
819
+ make_face(points, i0, i3, i1, interior_point),
820
+ make_face(points, i1, i3, i2, interior_point),
821
+ ]
822
+ const all_indices: number[] = []
823
+ for (let idx = 0; idx < points.length; idx++) {
824
+ if (idx === i0 || idx === i1 || idx === i2 || idx === i3) continue
825
+ all_indices.push(idx)
826
+ }
827
+ for (const face of faces) assign_outside_points(face, points, all_indices)
828
+ while (true) {
829
+ let chosen_face_idx = -1
830
+ let chosen_point_idx = -1
831
+ let max_distance = -1
832
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
833
+ const face = faces[face_idx]
834
+ if (face.outside_points.size === 0) continue
835
+ const far = farthest_point_for_face(points, face)
836
+ if (far && far.distance > max_distance) {
837
+ max_distance = far.distance
838
+ chosen_face_idx = face_idx
839
+ chosen_point_idx = far.idx
840
+ }
841
+ }
842
+ if (chosen_face_idx === -1) break
843
+ const eye_idx = chosen_point_idx
844
+ const visible_face_indices = new Set<number>()
845
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
846
+ const face = faces[face_idx]
847
+ const dist = point_plane_signed_distance(face.plane, points[eye_idx])
848
+ if (dist > EPS) visible_face_indices.add(face_idx)
849
+ }
850
+ const horizon_edges = build_horizon(faces, visible_face_indices)
851
+ const visible_faces = Array.from(visible_face_indices).sort((a, b) => b - a)
852
+ const candidate_points = collect_candidate_points(visible_faces.map((idx) => faces[idx]))
853
+ for (const idx of visible_faces) faces.splice(idx, 1)
854
+ const new_faces: ConvexHullFace[] = []
855
+ for (const [u, v] of horizon_edges) {
856
+ const new_face = make_face(points, u, v, eye_idx, interior_point)
857
+ new_faces.push(new_face)
858
+ }
859
+ for (const face of new_faces) face.outside_points.clear()
860
+ for (const idx of candidate_points) {
861
+ if (idx === eye_idx) continue
862
+ let best_face: ConvexHullFace | null = null
863
+ let best_distance = EPS
864
+ for (const face of new_faces) {
865
+ const dist = point_plane_signed_distance(face.plane, points[idx])
866
+ if (dist > best_distance) {
867
+ best_distance = dist
868
+ best_face = face
869
+ }
870
+ }
871
+ if (best_face) best_face.outside_points.add(idx)
872
+ }
873
+ faces.push(...new_faces)
874
+ }
875
+ return faces.map((face) => {
876
+ const [a, b, c] = face.vertices
877
+ const normal = face.plane.normal
878
+ const centroid = face.centroid
879
+ return {
880
+ vertices: [points[a], points[b], points[c]] as [Point3D, Point3D, Point3D],
881
+ normal,
882
+ centroid,
883
+ }
884
+ })
885
+ }
886
+
887
+ export function compute_lower_hull_triangles(points: Point3D[]): ConvexHullTriangle[] {
888
+ const all_faces = compute_quickhull_triangles(points)
889
+ return all_faces.filter((face) => face.normal.z < 0 - EPS)
890
+ }
891
+
892
+ // ---------- Lower hull face models and energy-above-hull ----------
893
+
894
+ export interface HullFaceModel {
895
+ a: number
896
+ b: number
897
+ c: number
898
+ x1: number
899
+ y1: number
900
+ x2: number
901
+ y2: number
902
+ x3: number
903
+ y3: number
904
+ min_x: number
905
+ max_x: number
906
+ min_y: number
907
+ max_y: number
908
+ denom: number
909
+ }
910
+
911
+ export const build_lower_hull_model = (faces: ConvexHullTriangle[]): HullFaceModel[] =>
912
+ faces.map((tri) => {
913
+ const [p1, p2, p3] = tri.vertices
914
+ const plane = (() => {
915
+ const x1 = p1.x,
916
+ y1 = p1.y,
917
+ z1 = p1.z
918
+ const x2 = p2.x,
919
+ y2 = p2.y,
920
+ z2 = p2.z
921
+ const x3 = p3.x,
922
+ y3 = p3.y,
923
+ z3 = p3.z
924
+ const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)
925
+ if (Math.abs(det) < 1e-12) return { a: 0, b: 0, c: (z1 + z2 + z3) / 3 }
926
+ const a = (z1 * (y2 - y3) + z2 * (y3 - y1) + z3 * (y1 - y2)) / det
927
+ const b = (z1 * (x3 - x2) + z2 * (x1 - x3) + z3 * (x2 - x1)) / det
928
+ const c =
929
+ (z1 * (x2 * y3 - x3 * y2) + z2 * (x3 * y1 - x1 * y3) + z3 * (x1 * y2 - x2 * y1)) / det
930
+ return { a, b, c }
931
+ })()
932
+ const [min_x, _mx, max_x] = [p1.x, p2.x, p3.x].sort((a, b) => a - b)
933
+ const [min_y, _my, max_y] = [p1.y, p2.y, p3.y].sort((a, b) => a - b)
934
+ const { x: x1, y: y1 } = p1
935
+ const { x: x2, y: y2 } = p2
936
+ const { x: x3, y: y3 } = p3
937
+ const denom = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)
938
+ return {
939
+ a: plane.a,
940
+ b: plane.b,
941
+ c: plane.c,
942
+ x1,
943
+ y1,
944
+ x2,
945
+ y2,
946
+ x3,
947
+ y3,
948
+ min_x,
949
+ max_x,
950
+ min_y,
951
+ max_y,
952
+ denom,
953
+ }
954
+ })
955
+
956
+ function point_in_triangle_xy(model: HullFaceModel, x: number, y: number): boolean {
957
+ const { x1, y1, x2, y2, x3, y3, denom } = model
958
+ if (Math.abs(denom) < 1e-14) return false
959
+ const l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denom
960
+ const l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denom
961
+ const l3 = 1 - l1 - l2
962
+ const eps = -1e-9
963
+ return l1 >= eps && l2 >= eps && l3 >= eps
964
+ }
965
+
966
+ export function e_hull_at_xy(models: HullFaceModel[], x: number, y: number) {
967
+ let z: number | null = null
968
+ for (const model of models) {
969
+ if (
970
+ x < model.min_x - 1e-9 ||
971
+ x > model.max_x + 1e-9 ||
972
+ y < model.min_y - 1e-9 ||
973
+ y > model.max_y + 1e-9
974
+ )
975
+ continue
976
+ if (!point_in_triangle_xy(model, x, y)) continue
977
+ const z_face = model.a * x + model.b * y + model.c
978
+ z = z === null ? z_face : Math.min(z, z_face)
979
+ }
980
+ return z
981
+ }
982
+
983
+ export const compute_e_above_hull_for_points = (points: Point3D[], models: HullFaceModel[]) =>
984
+ points.map((point) => {
985
+ const z_hull = e_hull_at_xy(models, point.x, point.y)
986
+ if (z_hull === null) return 0
987
+ return Math.max(0, point.z - z_hull)
988
+ })
989
+
990
+ // --- 4D Convex Hull (Quaternary Phase Diagrams) ---
991
+
992
+ export interface Point4D {
993
+ x: number
994
+ y: number
995
+ z: number
996
+ w: number // formation energy dimension
997
+ }
998
+
999
+ export interface ConvexHullTetrahedron {
1000
+ vertices: [Point4D, Point4D, Point4D, Point4D]
1001
+ normal: Point4D // 4D normal vector
1002
+ centroid: Point4D
1003
+ }
1004
+
1005
+ interface ConvexHullFace4D {
1006
+ vertices: [number, number, number, number] // indices
1007
+ plane: Plane4D
1008
+ centroid: Point4D
1009
+ outside_points: Set<number>
1010
+ }
1011
+
1012
+ interface Plane4D {
1013
+ normal: Point4D
1014
+ offset: number
1015
+ }
1016
+
1017
+ const subtract_4d = (pt1: Point4D, pt2: Point4D): Point4D => ({
1018
+ x: pt1.x - pt2.x,
1019
+ y: pt1.y - pt2.y,
1020
+ z: pt1.z - pt2.z,
1021
+ w: pt1.w - pt2.w,
1022
+ })
1023
+
1024
+ const dot_4d = (vec_a: Point4D, vec_b: Point4D): number =>
1025
+ vec_a.x * vec_b.x + vec_a.y * vec_b.y + vec_a.z * vec_b.z + vec_a.w * vec_b.w
1026
+
1027
+ const norm_4d = (point: Point4D): number =>
1028
+ Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z + point.w * point.w)
1029
+
1030
+ function normalize_4d(point: Point4D): Point4D {
1031
+ const length = norm_4d(point)
1032
+ if (length < EPS) return { x: 0, y: 0, z: 0, w: 0 }
1033
+ return {
1034
+ x: point.x / length,
1035
+ y: point.y / length,
1036
+ z: point.z / length,
1037
+ w: point.w / length,
1038
+ }
1039
+ }
1040
+
1041
+ // Compute normal to a 3D hyperplane in 4D space defined by 4 points
1042
+ //
1043
+ // Mathematical Background:
1044
+ // A 3D hyperplane (tetrahedral facet) in 4D is defined by 4 points. The normal vector
1045
+ // must be orthogonal to all three edge vectors spanning the hyperplane. This is the
1046
+ // 4D analog of computing a cross product of two vectors in 3D.
1047
+ //
1048
+ // Approach:
1049
+ // 1. Form three edge vectors v1, v2, v3 from point p1
1050
+ // 2. Find vector n such that: n · v1 = 0, n · v2 = 0, n · v3 = 0
1051
+ // 3. This is equivalent to finding the null space of the 3×4 matrix [v1; v2; v3]
1052
+ //
1053
+ // Implementation:
1054
+ // The normal components (nx, ny, nz, nw) are computed using Laplace expansion
1055
+ // (cofactor method) along each column of the matrix. Each component is the determinant
1056
+ // of the 3×3 submatrix obtained by removing that column, with alternating signs.
1057
+ //
1058
+ // References:
1059
+ // - Barber et al. (1996) "The Quickhull Algorithm for Convex Hulls"
1060
+ // - https://en.wikipedia.org/wiki/Cross_product#Multilinear_algebra
1061
+ // - https://mathworld.wolfram.com/Nullspace.html
1062
+ function compute_plane_4d(p1: Point4D, p2: Point4D, p3: Point4D, p4: Point4D): Plane4D {
1063
+ // Three edge vectors from p1
1064
+ const v1 = subtract_4d(p2, p1)
1065
+ const v2 = subtract_4d(p3, p1)
1066
+ const v3 = subtract_4d(p4, p1)
1067
+
1068
+ // Build matrix [v1; v2; v3] and compute normal via cofactor expansion
1069
+ const matrix = [
1070
+ [v1.x, v1.y, v1.z, v1.w],
1071
+ [v2.x, v2.y, v2.z, v2.w],
1072
+ [v3.x, v3.y, v3.z, v3.w],
1073
+ ]
1074
+
1075
+ // Helper: extract 3×3 submatrix by removing column col_skip, then compute determinant
1076
+ const det_submatrix = (col_skip: number): number => {
1077
+ const cols = [0, 1, 2, 3].filter((col) => col !== col_skip)
1078
+ const submatrix: math.Matrix3x3 = [
1079
+ [matrix[0][cols[0]], matrix[0][cols[1]], matrix[0][cols[2]]],
1080
+ [matrix[1][cols[0]], matrix[1][cols[1]], matrix[1][cols[2]]],
1081
+ [matrix[2][cols[0]], matrix[2][cols[1]], matrix[2][cols[2]]],
1082
+ ]
1083
+ return math.det_3x3(submatrix)
1084
+ }
1085
+
1086
+ // Compute normal components using Laplace expansion along each column
1087
+ // Alternating signs: +, -, +, -
1088
+ const signs = [1, -1, 1, -1]
1089
+ const normal_components = [0, 1, 2, 3].map(
1090
+ (col_idx) => signs[col_idx] * det_submatrix(col_idx),
1091
+ )
1092
+
1093
+ const [x, y, z, w] = normal_components
1094
+ const normal = normalize_4d({ x, y, z, w })
1095
+
1096
+ // Guard against degenerate (nearly co-planar) points
1097
+ const normal_magnitude =
1098
+ Math.abs(normal.x) + Math.abs(normal.y) + Math.abs(normal.z) + Math.abs(normal.w)
1099
+ if (normal_magnitude < EPS) {
1100
+ return { normal: { x: 0, y: 0, z: 0, w: 0 }, offset: 0 }
1101
+ }
1102
+
1103
+ const offset = -dot_4d(normal, p1)
1104
+
1105
+ return { normal, offset }
1106
+ }
1107
+
1108
+ const point_plane_signed_distance_4d = (plane: Plane4D, point: Point4D): number =>
1109
+ dot_4d(plane.normal, point) + plane.offset
1110
+
1111
+ const compute_centroid_4d = (p1: Point4D, p2: Point4D, p3: Point4D, p4: Point4D): Point4D => ({
1112
+ x: (p1.x + p2.x + p3.x + p4.x) / 4,
1113
+ y: (p1.y + p2.y + p3.y + p4.y) / 4,
1114
+ z: (p1.z + p2.z + p3.z + p4.z) / 4,
1115
+ w: (p1.w + p2.w + p3.w + p4.w) / 4,
1116
+ })
1117
+
1118
+ function distance_point_to_hyperplane_4d(
1119
+ p1: Point4D,
1120
+ p2: Point4D,
1121
+ p3: Point4D,
1122
+ point: Point4D,
1123
+ ): number {
1124
+ // Distance from point to the 2D hyperplane spanned by p1, p2, p3
1125
+ const v1 = subtract_4d(p2, p1)
1126
+ const v2 = subtract_4d(p3, p1)
1127
+ const vp = subtract_4d(point, p1)
1128
+
1129
+ // Project vp onto the plane spanned by v1 and v2
1130
+ // Use Gram-Schmidt to find orthogonal component
1131
+ const v1_norm_sq = dot_4d(v1, v1)
1132
+ const v2_norm_sq = dot_4d(v2, v2)
1133
+ const v1_dot_v2 = dot_4d(v1, v2)
1134
+
1135
+ if (v1_norm_sq < EPS || v2_norm_sq < EPS) return 0
1136
+
1137
+ const vp_dot_v1 = dot_4d(vp, v1)
1138
+ const vp_dot_v2 = dot_4d(vp, v2)
1139
+
1140
+ // Solve linear system for projection coefficients
1141
+ const det = v1_norm_sq * v2_norm_sq - v1_dot_v2 * v1_dot_v2
1142
+ if (Math.abs(det) < EPS) return 0
1143
+
1144
+ const alpha = (v2_norm_sq * vp_dot_v1 - v1_dot_v2 * vp_dot_v2) / det
1145
+ const beta = (v1_norm_sq * vp_dot_v2 - v1_dot_v2 * vp_dot_v1) / det
1146
+
1147
+ // Compute projection
1148
+ const proj_x = p1.x + alpha * v1.x + beta * v2.x
1149
+ const proj_y = p1.y + alpha * v1.y + beta * v2.y
1150
+ const proj_z = p1.z + alpha * v1.z + beta * v2.z
1151
+ const proj_w = p1.w + alpha * v1.w + beta * v2.w
1152
+
1153
+ // Distance is the length of (point - projection)
1154
+ const dx = point.x - proj_x
1155
+ const dy = point.y - proj_y
1156
+ const dz = point.z - proj_z
1157
+ const dw = point.w - proj_w
1158
+
1159
+ return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw)
1160
+ }
1161
+
1162
+ // Distance from point to line in 4D
1163
+ function distance_point_to_line_4d(a: Point4D, b: Point4D, p: Point4D): number {
1164
+ const ab = subtract_4d(b, a)
1165
+ const ap = subtract_4d(p, a)
1166
+
1167
+ const ab_len_sq = dot_4d(ab, ab)
1168
+ if (ab_len_sq < EPS) return norm_4d(ap)
1169
+
1170
+ // Project ap onto ab
1171
+ const t = dot_4d(ap, ab) / ab_len_sq
1172
+ const projection = {
1173
+ x: a.x + t * ab.x,
1174
+ y: a.y + t * ab.y,
1175
+ z: a.z + t * ab.z,
1176
+ w: a.w + t * ab.w,
1177
+ }
1178
+
1179
+ return norm_4d(subtract_4d(p, projection))
1180
+ }
1181
+
1182
+ // Maximum sample size for initial simplex selection in 4D hulls (avoids O(n²) for large datasets)
1183
+ const INITIAL_SIMPLEX_SAMPLE_SIZE = 100
1184
+
1185
+ function choose_initial_4_simplex(
1186
+ points: Point4D[],
1187
+ ): [number, number, number, number, number] | null {
1188
+ if (points.length < 5) return null
1189
+
1190
+ // Find two points farthest apart across all dimensions for better numerical stability
1191
+ // Sample a small subset if dataset is large to avoid O(n²) scaling
1192
+ const sample_size = Math.min(points.length, INITIAL_SIMPLEX_SAMPLE_SIZE)
1193
+ const sample_indices =
1194
+ points.length <= sample_size
1195
+ ? points.map((_, idx) => idx)
1196
+ : Array.from({ length: sample_size }, (_, idx) =>
1197
+ Math.floor((idx * points.length) / sample_size),
1198
+ )
1199
+
1200
+ let idx_far_a = 0
1201
+ let idx_far_b = 0
1202
+ let max_dist_sq = -1
1203
+
1204
+ for (const idx_a of sample_indices) {
1205
+ for (const idx_b of sample_indices) {
1206
+ if (idx_a >= idx_b) continue
1207
+ const pa = points[idx_a]
1208
+ const pb = points[idx_b]
1209
+ const dist_sq =
1210
+ (pa.x - pb.x) ** 2 + (pa.y - pb.y) ** 2 + (pa.z - pb.z) ** 2 + (pa.w - pb.w) ** 2
1211
+ if (dist_sq > max_dist_sq) {
1212
+ max_dist_sq = dist_sq
1213
+ idx_far_a = idx_a
1214
+ idx_far_b = idx_b
1215
+ }
1216
+ }
1217
+ }
1218
+ if (idx_far_a === idx_far_b || max_dist_sq < EPS) return null
1219
+
1220
+ // Find point farthest from line through idx_far_a and idx_far_b
1221
+ let idx_far_line = -1
1222
+ let best_dist_line = -1
1223
+ for (let idx = 0; idx < points.length; idx++) {
1224
+ if (idx === idx_far_a || idx === idx_far_b) continue
1225
+ const dist = distance_point_to_line_4d(points[idx_far_a], points[idx_far_b], points[idx])
1226
+ if (dist > best_dist_line) {
1227
+ best_dist_line = dist
1228
+ idx_far_line = idx
1229
+ }
1230
+ }
1231
+ if (idx_far_line === -1 || best_dist_line < EPS) return null
1232
+
1233
+ // Find point farthest from 2D plane through first three points
1234
+ let idx_far_plane = -1
1235
+ let best_dist_plane = -1
1236
+ for (let idx = 0; idx < points.length; idx++) {
1237
+ if (idx === idx_far_a || idx === idx_far_b || idx === idx_far_line) continue
1238
+ const dist = distance_point_to_hyperplane_4d(
1239
+ points[idx_far_a],
1240
+ points[idx_far_b],
1241
+ points[idx_far_line],
1242
+ points[idx],
1243
+ )
1244
+ if (dist > best_dist_plane) {
1245
+ best_dist_plane = dist
1246
+ idx_far_plane = idx
1247
+ }
1248
+ }
1249
+ if (idx_far_plane === -1 || best_dist_plane < EPS) return null
1250
+
1251
+ // Find point farthest from 3D hyperplane through first four points
1252
+ const plane0 = compute_plane_4d(
1253
+ points[idx_far_a],
1254
+ points[idx_far_b],
1255
+ points[idx_far_line],
1256
+ points[idx_far_plane],
1257
+ )
1258
+ let idx_far_hyperplane = -1
1259
+ let best_dist_hyperplane = -1
1260
+ for (let idx = 0; idx < points.length; idx++) {
1261
+ if (
1262
+ idx === idx_far_a ||
1263
+ idx === idx_far_b ||
1264
+ idx === idx_far_line ||
1265
+ idx === idx_far_plane
1266
+ )
1267
+ continue
1268
+ const dist = Math.abs(point_plane_signed_distance_4d(plane0, points[idx]))
1269
+ if (dist > best_dist_hyperplane) {
1270
+ best_dist_hyperplane = dist
1271
+ idx_far_hyperplane = idx
1272
+ }
1273
+ }
1274
+ if (idx_far_hyperplane === -1 || best_dist_hyperplane < EPS) return null
1275
+
1276
+ return [idx_far_a, idx_far_b, idx_far_line, idx_far_plane, idx_far_hyperplane]
1277
+ }
1278
+
1279
+ function make_face_4d(
1280
+ points: Point4D[],
1281
+ a: number,
1282
+ b: number,
1283
+ c: number,
1284
+ d: number,
1285
+ interior_point: Point4D,
1286
+ ): ConvexHullFace4D {
1287
+ let plane = compute_plane_4d(points[a], points[b], points[c], points[d])
1288
+ let centroid = compute_centroid_4d(points[a], points[b], points[c], points[d])
1289
+
1290
+ const dist_interior = point_plane_signed_distance_4d(plane, interior_point)
1291
+
1292
+ // Ensure normal points outward (away from interior)
1293
+ if (dist_interior > 0) {
1294
+ // Swap two vertices to flip normal
1295
+ plane = compute_plane_4d(points[a], points[c], points[b], points[d])
1296
+ centroid = compute_centroid_4d(points[a], points[c], points[b], points[d])
1297
+ return { vertices: [a, c, b, d], plane, centroid, outside_points: new Set<number>() }
1298
+ }
1299
+
1300
+ return { vertices: [a, b, c, d], plane, centroid, outside_points: new Set<number>() }
1301
+ }
1302
+
1303
+ function assign_outside_points_4d(
1304
+ face: ConvexHullFace4D,
1305
+ points: Point4D[],
1306
+ candidate_indices: number[],
1307
+ ): void {
1308
+ face.outside_points.clear()
1309
+ for (const idx of candidate_indices) {
1310
+ const distance = point_plane_signed_distance_4d(face.plane, points[idx])
1311
+ if (distance > EPS) face.outside_points.add(idx)
1312
+ }
1313
+ }
1314
+
1315
+ function collect_candidate_points_4d(faces: ConvexHullFace4D[]): number[] {
1316
+ const set = new Set<number>()
1317
+ for (const face of faces) {
1318
+ for (const idx of face.outside_points) set.add(idx)
1319
+ }
1320
+ return Array.from(set)
1321
+ }
1322
+
1323
+ function farthest_point_for_face_4d(
1324
+ points: Point4D[],
1325
+ face: ConvexHullFace4D,
1326
+ ): { idx: number; distance: number } | null {
1327
+ let best_idx = -1
1328
+ let best_distance = -1
1329
+ for (const idx of face.outside_points) {
1330
+ const distance = point_plane_signed_distance_4d(face.plane, points[idx])
1331
+ if (distance > best_distance) {
1332
+ best_distance = distance
1333
+ best_idx = idx
1334
+ }
1335
+ }
1336
+ if (best_idx === -1) return null
1337
+ return { idx: best_idx, distance: best_distance }
1338
+ }
1339
+
1340
+ function build_horizon_4d(
1341
+ faces: ConvexHullFace4D[],
1342
+ visible_face_indices: Set<number>,
1343
+ ): math.Vec3[] {
1344
+ // In 4D, horizon "ridges" are triangles (3 vertices)
1345
+ const ridge_count = new Map<string, math.Vec3>()
1346
+
1347
+ for (const face_idx of visible_face_indices) {
1348
+ const face = faces[face_idx]
1349
+ const [a, b, c, d] = face.vertices
1350
+
1351
+ // Each tetrahedron face has 4 triangular ridges
1352
+ const ridges: math.Vec3[] = [
1353
+ [a, b, c],
1354
+ [a, b, d],
1355
+ [a, c, d],
1356
+ [b, c, d],
1357
+ ]
1358
+
1359
+ for (const ridge of ridges) {
1360
+ const sorted = ridge.slice().sort((x, y) => x - y)
1361
+ const key = sorted.join(`|`)
1362
+
1363
+ if (!ridge_count.has(key)) {
1364
+ ridge_count.set(key, ridge)
1365
+ } else {
1366
+ // Mark as seen twice (internal ridge)
1367
+ ridge_count.set(key, [Number.NaN, Number.NaN, Number.NaN])
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ const horizon: math.Vec3[] = []
1373
+ for (const ridge of ridge_count.values()) {
1374
+ if (!Number.isNaN(ridge[0])) {
1375
+ horizon.push(ridge)
1376
+ }
1377
+ }
1378
+
1379
+ return horizon
1380
+ }
1381
+
1382
+ export function compute_quickhull_4d(points: Point4D[]): ConvexHullTetrahedron[] {
1383
+ if (points.length < 5) return [] // Need at least 5 non-coplanar points for 4D hull
1384
+
1385
+ const initial = choose_initial_4_simplex(points)
1386
+ if (!initial) return []
1387
+
1388
+ const [i0, i1, i2, i3, i4] = initial
1389
+
1390
+ // Interior point for orientation
1391
+ const interior_point = {
1392
+ x: (points[i0].x + points[i1].x + points[i2].x + points[i3].x + points[i4].x) / 5,
1393
+ y: (points[i0].y + points[i1].y + points[i2].y + points[i3].y + points[i4].y) / 5,
1394
+ z: (points[i0].z + points[i1].z + points[i2].z + points[i3].z + points[i4].z) / 5,
1395
+ w: (points[i0].w + points[i1].w + points[i2].w + points[i3].w + points[i4].w) / 5,
1396
+ }
1397
+
1398
+ // Initial 4-simplex has 5 tetrahedral faces
1399
+ const faces: ConvexHullFace4D[] = [
1400
+ make_face_4d(points, i0, i1, i2, i3, interior_point),
1401
+ make_face_4d(points, i0, i1, i2, i4, interior_point),
1402
+ make_face_4d(points, i0, i1, i3, i4, interior_point),
1403
+ make_face_4d(points, i0, i2, i3, i4, interior_point),
1404
+ make_face_4d(points, i1, i2, i3, i4, interior_point),
1405
+ ]
1406
+
1407
+ const all_indices: number[] = []
1408
+ for (let idx = 0; idx < points.length; idx++) {
1409
+ if (idx === i0 || idx === i1 || idx === i2 || idx === i3 || idx === i4) continue
1410
+ all_indices.push(idx)
1411
+ }
1412
+
1413
+ for (const face of faces) {
1414
+ assign_outside_points_4d(face, points, all_indices)
1415
+ }
1416
+
1417
+ // Main Quick Hull iteration
1418
+ while (true) {
1419
+ // Step 1: Find face with farthest outside point (the "eye" point)
1420
+ let chosen_face_idx = -1
1421
+ let chosen_point_idx = -1
1422
+ let max_distance = -1
1423
+
1424
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
1425
+ const face = faces[face_idx]
1426
+ if (face.outside_points.size === 0) continue
1427
+
1428
+ const far = farthest_point_for_face_4d(points, face)
1429
+ if (far && far.distance > max_distance) {
1430
+ max_distance = far.distance
1431
+ chosen_face_idx = face_idx
1432
+ chosen_point_idx = far.idx
1433
+ }
1434
+ }
1435
+
1436
+ if (chosen_face_idx === -1) break // All points processed
1437
+
1438
+ const eye_idx = chosen_point_idx
1439
+
1440
+ // Step 2: Find all faces visible from the eye point
1441
+ const visible_face_indices = new Set<number>()
1442
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
1443
+ const face = faces[face_idx]
1444
+ const dist = point_plane_signed_distance_4d(face.plane, points[eye_idx])
1445
+ if (dist > EPS) visible_face_indices.add(face_idx)
1446
+ }
1447
+
1448
+ // Step 3: Build horizon ridges (boundary between visible and non-visible faces)
1449
+ const horizon_ridges = build_horizon_4d(faces, visible_face_indices)
1450
+ const visible_faces = Array.from(visible_face_indices).sort((a, b) => b - a)
1451
+ const candidate_points = collect_candidate_points_4d(
1452
+ visible_faces.map((idx) => faces[idx]),
1453
+ )
1454
+
1455
+ // Step 4: Remove visible faces (they'll be replaced by new ones through eye point)
1456
+ for (const idx of visible_faces) {
1457
+ faces.splice(idx, 1)
1458
+ }
1459
+
1460
+ // Step 5: Create new faces connecting horizon ridges to eye point
1461
+ const new_faces: ConvexHullFace4D[] = []
1462
+ for (const [u, v, w] of horizon_ridges) {
1463
+ const new_face = make_face_4d(points, u, v, w, eye_idx, interior_point)
1464
+ new_faces.push(new_face)
1465
+ }
1466
+
1467
+ // Step 6: Reassign outside points from removed faces to new faces
1468
+ for (const face of new_faces) face.outside_points.clear()
1469
+
1470
+ for (const idx of candidate_points) {
1471
+ if (idx === eye_idx) continue
1472
+
1473
+ let best_face: ConvexHullFace4D | null = null
1474
+ let best_distance = EPS
1475
+
1476
+ for (const face of new_faces) {
1477
+ const dist = point_plane_signed_distance_4d(face.plane, points[idx])
1478
+ if (dist > best_distance) {
1479
+ best_distance = dist
1480
+ best_face = face
1481
+ }
1482
+ }
1483
+
1484
+ if (best_face) best_face.outside_points.add(idx)
1485
+ }
1486
+
1487
+ faces.push(...new_faces)
1488
+ }
1489
+
1490
+ return faces.map((face) => {
1491
+ const [a, b, c, d] = face.vertices
1492
+ return {
1493
+ vertices: [points[a], points[b], points[c], points[d]] as [
1494
+ Point4D,
1495
+ Point4D,
1496
+ Point4D,
1497
+ Point4D,
1498
+ ],
1499
+ normal: face.plane.normal,
1500
+ centroid: face.centroid,
1501
+ }
1502
+ })
1503
+ }
1504
+
1505
+ export function compute_lower_hull_4d(points: Point4D[]): ConvexHullTetrahedron[] {
1506
+ const all_faces = compute_quickhull_4d(points)
1507
+ // Filter for "lower" faces: those with normal pointing down in w direction
1508
+ return all_faces.filter((face) => face.normal.w < 0 - EPS)
1509
+ }
1510
+
1511
+ // Check if 3D point (x,y,z) is inside 3D tetrahedron using barycentric coordinates
1512
+ function point_in_tetrahedron_3d(
1513
+ p0: Point3D,
1514
+ p1: Point3D,
1515
+ p2: Point3D,
1516
+ p3: Point3D,
1517
+ point: Point3D,
1518
+ ): { inside: boolean; bary: [number, number, number, number] } {
1519
+ // Solve for barycentric coordinates: point = l0*p0 + l1*p1 + l2*p2 + l3*p3
1520
+ // with l0 + l1 + l2 + l3 = 1
1521
+ // Build the linear system
1522
+ const matrix: math.Matrix4x4 = [
1523
+ [p0.x, p1.x, p2.x, p3.x],
1524
+ [p0.y, p1.y, p2.y, p3.y],
1525
+ [p0.z, p1.z, p2.z, p3.z],
1526
+ [1, 1, 1, 1],
1527
+ ]
1528
+ const rhs = [point.x, point.y, point.z, 1]
1529
+
1530
+ // Solve using Cramer's rule with 4x4 determinants
1531
+ const det_main = math.det_4x4(matrix)
1532
+ if (Math.abs(det_main) < EPS) {
1533
+ return { inside: false, bary: [0, 0, 0, 0] }
1534
+ }
1535
+
1536
+ // Compute barycentric coordinates using Cramer's rule
1537
+ const bary: [number, number, number, number] = [0, 0, 0, 0]
1538
+ for (let idx = 0; idx < 4; idx++) {
1539
+ const m_i = matrix.map((row) => [...row]) as math.Matrix4x4
1540
+ for (let row = 0; row < 4; row++) {
1541
+ m_i[row][idx] = rhs[row]
1542
+ }
1543
+ bary[idx] = math.det_4x4(m_i) / det_main
1544
+ }
1545
+
1546
+ // Check if inside: all barycentric coords must be >= 0 and sum to 1
1547
+ const eps_bary = -1e-9
1548
+ const inside =
1549
+ bary.every((coord) => coord >= eps_bary) &&
1550
+ Math.abs(bary.reduce((sum, coord) => sum + coord, 0) - 1) < 1e-6
1551
+
1552
+ return { inside, bary }
1553
+ }
1554
+
1555
+ // Precomputed bounding box for fast 3D containment checks
1556
+ // Speed boost: 6 cheap comparisons (bounding box) vs expensive matrix solve (barycentric)
1557
+ // helps filter out most tetrahedra for containment checks before doing the slow calculation
1558
+ interface TetrahedronModel {
1559
+ vertices: [Point4D, Point4D, Point4D, Point4D]
1560
+ vertices_3d: [Point3D, Point3D, Point3D, Point3D]
1561
+ min_x: number
1562
+ max_x: number
1563
+ min_y: number
1564
+ max_y: number
1565
+ min_z: number
1566
+ max_z: number
1567
+ }
1568
+
1569
+ const build_tetrahedron_models = (
1570
+ hull_tetrahedra: ConvexHullTetrahedron[],
1571
+ ): TetrahedronModel[] =>
1572
+ hull_tetrahedra.map((tet) => {
1573
+ const [p0, p1, p2, p3] = tet.vertices
1574
+ const vertices_3d: [Point3D, Point3D, Point3D, Point3D] = [
1575
+ { x: p0.x, y: p0.y, z: p0.z },
1576
+ { x: p1.x, y: p1.y, z: p1.z },
1577
+ { x: p2.x, y: p2.y, z: p2.z },
1578
+ { x: p3.x, y: p3.y, z: p3.z },
1579
+ ]
1580
+ const xs = [p0.x, p1.x, p2.x, p3.x]
1581
+ const ys = [p0.y, p1.y, p2.y, p3.y]
1582
+ const zs = [p0.z, p1.z, p2.z, p3.z]
1583
+ return {
1584
+ vertices: tet.vertices,
1585
+ vertices_3d,
1586
+ min_x: Math.min(...xs),
1587
+ max_x: Math.max(...xs),
1588
+ min_y: Math.min(...ys),
1589
+ max_y: Math.max(...ys),
1590
+ min_z: Math.min(...zs),
1591
+ max_z: Math.max(...zs),
1592
+ }
1593
+ })
1594
+
1595
+ // Compute distance from point to lower hull in 4D
1596
+ export const compute_e_above_hull_4d = (
1597
+ points: Point4D[],
1598
+ hull_tetrahedra: ConvexHullTetrahedron[],
1599
+ ) => {
1600
+ // Precompute bounding boxes for fast prefiltering
1601
+ const models = build_tetrahedron_models(hull_tetrahedra)
1602
+
1603
+ return points.map(({ x, y, z, w }) => {
1604
+ let hull_w: number | null = null
1605
+
1606
+ for (const model of models) {
1607
+ // Fast bounding box prefilter
1608
+ if (
1609
+ x < model.min_x - EPS ||
1610
+ x > model.max_x + EPS ||
1611
+ y < model.min_y - EPS ||
1612
+ y > model.max_y + EPS ||
1613
+ z < model.min_z - EPS ||
1614
+ z > model.max_z + EPS
1615
+ )
1616
+ continue
1617
+
1618
+ // Check if point's (x,y,z) is inside the 3D projection of the tetrahedron
1619
+ const { inside, bary } = point_in_tetrahedron_3d(
1620
+ model.vertices_3d[0],
1621
+ model.vertices_3d[1],
1622
+ model.vertices_3d[2],
1623
+ model.vertices_3d[3],
1624
+ { x, y, z },
1625
+ )
1626
+
1627
+ if (inside) {
1628
+ // Compute w on the hull at this (x,y,z) using barycentric interpolation
1629
+ const [p0, p1, p2, p3] = model.vertices
1630
+ const w_on_hull = bary[0] * p0.w + bary[1] * p1.w + bary[2] * p2.w + bary[3] * p3.w
1631
+ hull_w = hull_w === null ? w_on_hull : Math.min(hull_w, w_on_hull)
1632
+ }
1633
+ }
1634
+
1635
+ // If no tetrahedron contains this point's spatial projection, it's outside the valid
1636
+ // composition domain. Return NaN to indicate invalid input.
1637
+ if (hull_w === null) return NaN
1638
+ return w - hull_w
1639
+ })
1640
+ }
1641
+
1642
+ // --- N-Dimensional Convex Hull (for 5+ element systems) ---
1643
+
1644
+ // N-dimensional vector operations with dimension validation
1645
+ const subtract_nd = (vec_a: number[], vec_b: number[]): number[] => {
1646
+ if (vec_a.length !== vec_b.length) {
1647
+ throw new Error(`Vector dimension mismatch: ${vec_a.length} vs ${vec_b.length}`)
1648
+ }
1649
+ return vec_a.map((val, idx) => val - vec_b[idx])
1650
+ }
1651
+
1652
+ const dot_nd = (vec_a: number[], vec_b: number[]): number => {
1653
+ if (vec_a.length !== vec_b.length) {
1654
+ throw new Error(`Vector dimension mismatch: ${vec_a.length} vs ${vec_b.length}`)
1655
+ }
1656
+ return vec_a.reduce((sum, val, idx) => sum + val * vec_b[idx], 0)
1657
+ }
1658
+
1659
+ const norm_nd = (vec: number[]): number => Math.sqrt(dot_nd(vec, vec))
1660
+
1661
+ const normalize_nd = (vec: number[]): number[] => {
1662
+ const len = norm_nd(vec)
1663
+ if (len < EPS) return vec.map(() => 0)
1664
+ return vec.map((val) => val / len)
1665
+ }
1666
+
1667
+ // Hyperplane in N dimensions: normal · x + offset = 0
1668
+ interface HyperplaneND {
1669
+ normal: number[]
1670
+ offset: number
1671
+ }
1672
+
1673
+ // Simplex facet of the convex hull (N vertices in N-dimensional space)
1674
+ export interface SimplexFaceND {
1675
+ vertex_indices: number[]
1676
+ plane: HyperplaneND
1677
+ centroid: number[]
1678
+ outside_points: Set<number>
1679
+ }
1680
+
1681
+ // Compute normal to hyperplane through N points in N-dimensional space
1682
+ // Uses null space computation via cofactor expansion
1683
+ function compute_hyperplane_nd(points: number[][]): HyperplaneND {
1684
+ const n = points.length
1685
+ if (n < 2) return { normal: [], offset: 0 }
1686
+
1687
+ // Build (N-1) edge vectors from points[0]
1688
+ const edges = points.slice(1).map((pt) => subtract_nd(pt, points[0]))
1689
+
1690
+ // Compute normal via cofactors of the (N-1)×N edge matrix
1691
+ // Each component is ±det of (N-1)×(N-1) submatrix with that column removed
1692
+ const dim = points[0].length
1693
+ const normal_components: number[] = []
1694
+
1695
+ for (let col = 0; col < dim; col++) {
1696
+ // Build (N-1)×(N-1) submatrix by removing column col
1697
+ const submatrix = edges.map((row) => [...row.slice(0, col), ...row.slice(col + 1)])
1698
+ const sign = col % 2 === 0 ? 1 : -1
1699
+ normal_components.push(sign * math.det_nxn(submatrix))
1700
+ }
1701
+
1702
+ const normal = normalize_nd(normal_components)
1703
+
1704
+ // Degenerate case: co-hyperplanar points produce zero normal
1705
+ if (norm_nd(normal) < EPS) return { normal, offset: 0 }
1706
+
1707
+ const offset = -dot_nd(normal, points[0])
1708
+ return { normal, offset }
1709
+ }
1710
+
1711
+ const point_hyperplane_signed_distance_nd = (plane: HyperplaneND, point: number[]): number =>
1712
+ dot_nd(plane.normal, point) + plane.offset
1713
+
1714
+ const compute_centroid_nd = (points: number[][]): number[] => {
1715
+ if (points.length === 0) return []
1716
+ const dim = points[0].length
1717
+ return Array.from(
1718
+ { length: dim },
1719
+ (_, idx) => points.reduce((sum, pt) => sum + pt[idx], 0) / points.length,
1720
+ )
1721
+ }
1722
+
1723
+ // Find N+1 points that span N dimensions (initial simplex for quickhull)
1724
+ function choose_initial_simplex_nd(points: number[][]): number[] | null {
1725
+ const n = points[0]?.length
1726
+ if (!n || points.length < n + 1) return null
1727
+
1728
+ const chosen: number[] = []
1729
+
1730
+ // Greedily pick points that maximize distance from current affine hull
1731
+ // Start with two points that are farthest apart
1732
+ let [best_i, best_j, best_dist] = [0, 1, -1]
1733
+ const sample_size = Math.min(points.length, 100)
1734
+ const sample_indices =
1735
+ points.length <= sample_size
1736
+ ? points.map((_, idx) => idx)
1737
+ : Array.from({ length: sample_size }, (_, idx) =>
1738
+ Math.floor((idx * points.length) / sample_size),
1739
+ )
1740
+
1741
+ for (const idx_a of sample_indices) {
1742
+ for (const idx_b of sample_indices) {
1743
+ if (idx_a >= idx_b) continue
1744
+ const dist = norm_nd(subtract_nd(points[idx_a], points[idx_b]))
1745
+ if (dist > best_dist) {
1746
+ best_dist = dist
1747
+ best_i = idx_a
1748
+ best_j = idx_b
1749
+ }
1750
+ }
1751
+ }
1752
+
1753
+ if (best_dist < EPS) return null
1754
+ chosen.push(best_i, best_j)
1755
+ const chosen_set = new Set(chosen)
1756
+
1757
+ // Add remaining points to span higher dimensions
1758
+ while (chosen.length < n + 1) {
1759
+ let [best_idx, best_distance] = [-1, -1]
1760
+ // Hoist chosen_points computation outside inner loop for O(n) instead of O(n²)
1761
+ const chosen_points = chosen.map((idx_c) => points[idx_c])
1762
+
1763
+ for (let idx = 0; idx < points.length; idx++) {
1764
+ if (chosen_set.has(idx)) continue
1765
+
1766
+ // Compute distance from point to affine hull of chosen points
1767
+ const dist = distance_to_affine_hull_nd(points[idx], chosen_points)
1768
+
1769
+ if (dist > best_distance) {
1770
+ best_distance = dist
1771
+ best_idx = idx
1772
+ }
1773
+ }
1774
+
1775
+ if (best_idx === -1 || best_distance < EPS) return null
1776
+ chosen.push(best_idx)
1777
+ chosen_set.add(best_idx)
1778
+ }
1779
+
1780
+ return chosen
1781
+ }
1782
+
1783
+ // Distance from point to affine hull spanned by given points
1784
+ function distance_to_affine_hull_nd(point: number[], hull_points: number[][]): number {
1785
+ if (hull_points.length === 1) {
1786
+ return norm_nd(subtract_nd(point, hull_points[0]))
1787
+ }
1788
+
1789
+ if (hull_points.length === 2) {
1790
+ // Distance to line
1791
+ const [pt_a, pt_b] = hull_points
1792
+ const ab = subtract_nd(pt_b, pt_a)
1793
+ const ap = subtract_nd(point, pt_a)
1794
+ const ab_len_sq = dot_nd(ab, ab)
1795
+ if (ab_len_sq < EPS) return norm_nd(ap)
1796
+ const t = dot_nd(ap, ab) / ab_len_sq
1797
+ const proj = pt_a.map((val, idx) => val + t * ab[idx])
1798
+ return norm_nd(subtract_nd(point, proj))
1799
+ }
1800
+
1801
+ // For 3+ points, use orthogonal projection
1802
+ // Build edge vectors from hull_points[0]
1803
+ const origin = hull_points[0]
1804
+ const edges = hull_points.slice(1).map((pt) => subtract_nd(pt, origin))
1805
+ const vp = subtract_nd(point, origin)
1806
+
1807
+ // Solve least squares using Gram matrix G[i][j] = dot(edge_i, edge_j)
1808
+ const gram = edges.map((edge_i) => edges.map((edge_j) => dot_nd(edge_i, edge_j)))
1809
+ const rhs = edges.map((edge) => dot_nd(edge, vp))
1810
+
1811
+ // Solve Gram * coeffs = rhs using simple Gaussian elimination
1812
+ const coeffs = solve_linear_system(gram, rhs)
1813
+ if (!coeffs) {
1814
+ // Fallback: Gram-Schmidt when Gram matrix is singular (linearly dependent edges)
1815
+ // Build orthogonal basis and accumulate projection in single pass
1816
+ const ortho_basis: number[][] = []
1817
+ let projection = vp.map(() => 0)
1818
+
1819
+ for (const edge of edges) {
1820
+ // Orthogonalize edge against existing basis
1821
+ let ortho = [...edge]
1822
+ for (const basis of ortho_basis) {
1823
+ const norm_sq = dot_nd(basis, basis)
1824
+ if (norm_sq > EPS) {
1825
+ const coeff = dot_nd(ortho, basis) / norm_sq
1826
+ ortho = ortho.map((val, idx) => val - coeff * basis[idx])
1827
+ }
1828
+ }
1829
+
1830
+ // Add to basis and update projection if linearly independent
1831
+ const ortho_norm_sq = dot_nd(ortho, ortho)
1832
+ if (ortho_norm_sq > EPS) {
1833
+ ortho_basis.push(ortho)
1834
+ const proj_coeff = dot_nd(vp, ortho) / ortho_norm_sq
1835
+ projection = projection.map((val, idx) => val + proj_coeff * ortho[idx])
1836
+ }
1837
+ }
1838
+
1839
+ return norm_nd(subtract_nd(vp, projection))
1840
+ }
1841
+
1842
+ // Compute projection: origin + sum(coeffs[i] * edges[i])
1843
+ const proj = origin.map(
1844
+ (val, dim) => val + coeffs.reduce((sum, coeff, idx) => sum + coeff * edges[idx][dim], 0),
1845
+ )
1846
+ return norm_nd(subtract_nd(point, proj))
1847
+ }
1848
+
1849
+ // Solve linear system Ax = b using Gaussian elimination with partial pivoting
1850
+ function solve_linear_system(matrix_a: number[][], vec_b: number[]): number[] | null {
1851
+ const n = matrix_a.length
1852
+ if (n === 0) return []
1853
+ if (vec_b.length !== n) return null // Dimension mismatch
1854
+
1855
+ // Augmented matrix
1856
+ const aug = matrix_a.map((row, idx) => [...row, vec_b[idx]])
1857
+
1858
+ for (let col = 0; col < n; col++) {
1859
+ // Find pivot
1860
+ let max_row = col
1861
+ for (let row = col + 1; row < n; row++) {
1862
+ if (Math.abs(aug[row][col]) > Math.abs(aug[max_row][col])) {
1863
+ max_row = row
1864
+ }
1865
+ }
1866
+
1867
+ if (Math.abs(aug[max_row][col]) < EPS) return null // Singular
1868
+
1869
+ // Swap rows if needed
1870
+ if (max_row !== col) {
1871
+ const temp = aug[col]
1872
+ aug[col] = aug[max_row]
1873
+ aug[max_row] = temp
1874
+ }
1875
+
1876
+ // Eliminate
1877
+ for (let row = col + 1; row < n; row++) {
1878
+ const factor = aug[row][col] / aug[col][col]
1879
+ for (let elim_col = col; elim_col <= n; elim_col++) {
1880
+ aug[row][elim_col] -= factor * aug[col][elim_col]
1881
+ }
1882
+ }
1883
+ }
1884
+
1885
+ // Back substitution
1886
+ const result = Array(n).fill(0)
1887
+ for (let row = n - 1; row >= 0; row--) {
1888
+ let sum = aug[row][n]
1889
+ for (let col = row + 1; col < n; col++) {
1890
+ sum -= aug[row][col] * result[col]
1891
+ }
1892
+ result[row] = sum / aug[row][row]
1893
+ }
1894
+
1895
+ return result
1896
+ }
1897
+
1898
+ // Create a simplex face with correct normal orientation (outward from interior)
1899
+ function make_face_nd(
1900
+ points: number[][],
1901
+ vertex_indices: number[],
1902
+ interior_point: number[],
1903
+ ): SimplexFaceND {
1904
+ const face_points = vertex_indices.map((idx) => points[idx])
1905
+ const plane = compute_hyperplane_nd(face_points)
1906
+ const centroid = compute_centroid_nd(face_points)
1907
+
1908
+ // Flip normal if it points toward interior instead of away
1909
+ const dist_interior = point_hyperplane_signed_distance_nd(plane, interior_point)
1910
+ if (dist_interior > 0) {
1911
+ plane.normal = plane.normal.map((val) => -val)
1912
+ plane.offset = -plane.offset
1913
+ }
1914
+
1915
+ return { vertex_indices, plane, centroid, outside_points: new Set() }
1916
+ }
1917
+
1918
+ function assign_outside_points_nd(
1919
+ face: SimplexFaceND,
1920
+ points: number[][],
1921
+ candidate_indices: number[],
1922
+ ): void {
1923
+ face.outside_points.clear()
1924
+ for (const idx of candidate_indices) {
1925
+ const dist = point_hyperplane_signed_distance_nd(face.plane, points[idx])
1926
+ if (dist > EPS) face.outside_points.add(idx)
1927
+ }
1928
+ }
1929
+
1930
+ function farthest_outside_point_nd(
1931
+ points: number[][],
1932
+ face: SimplexFaceND,
1933
+ ): { idx: number; distance: number } | null {
1934
+ let best: { idx: number; distance: number } | null = null
1935
+ for (const idx of face.outside_points) {
1936
+ const distance = point_hyperplane_signed_distance_nd(face.plane, points[idx])
1937
+ if (!best || distance > best.distance) best = { idx, distance }
1938
+ }
1939
+ return best
1940
+ }
1941
+
1942
+ // Build horizon ridges (boundary between visible and non-visible faces)
1943
+ // In N dimensions, ridges are (N-2)-simplices with (N-1) vertices
1944
+ function build_horizon_nd(faces: SimplexFaceND[], visible_indices: Set<number>): number[][] {
1945
+ const ridge_count = new Map<string, number[]>()
1946
+
1947
+ for (const face_idx of visible_indices) {
1948
+ const face = faces[face_idx]
1949
+ const verts = face.vertex_indices
1950
+ const n = verts.length
1951
+
1952
+ // Each face has n ridges, each ridge omits one vertex
1953
+ for (let skip = 0; skip < n; skip++) {
1954
+ const ridge = verts.filter((_, idx) => idx !== skip)
1955
+ const sorted = [...ridge].sort((a, b) => a - b)
1956
+ const key = sorted.join(`|`)
1957
+
1958
+ if (!ridge_count.has(key)) {
1959
+ ridge_count.set(key, ridge)
1960
+ } else {
1961
+ // Mark as internal (seen twice)
1962
+ ridge_count.set(key, [])
1963
+ }
1964
+ }
1965
+ }
1966
+
1967
+ // Return only boundary ridges (seen exactly once, not marked empty)
1968
+ return [...ridge_count.values()].filter((ridge) => ridge.length > 0)
1969
+ }
1970
+
1971
+ // N-dimensional quickhull algorithm
1972
+ export function compute_quickhull_nd(points: number[][]): SimplexFaceND[] {
1973
+ if (points.length === 0) return []
1974
+ const n = points[0].length
1975
+ if (points.length < n + 1) return []
1976
+
1977
+ // Find initial n-simplex
1978
+ const initial = choose_initial_simplex_nd(points)
1979
+ if (!initial) return []
1980
+
1981
+ // Interior point for normal orientation
1982
+ const interior = compute_centroid_nd(initial.map((idx) => points[idx]))
1983
+
1984
+ // Create initial n+1 facets (each omits one vertex from the simplex)
1985
+ const faces: SimplexFaceND[] = []
1986
+ for (let skip = 0; skip <= n; skip++) {
1987
+ const verts = initial.filter((_, idx) => idx !== skip)
1988
+ faces.push(make_face_nd(points, verts, interior))
1989
+ }
1990
+
1991
+ // Assign outside points to initial faces
1992
+ const remaining = points.map((_, idx) => idx).filter((idx) => !initial.includes(idx))
1993
+ for (const face of faces) {
1994
+ assign_outside_points_nd(face, points, remaining)
1995
+ }
1996
+
1997
+ // Main quickhull loop
1998
+ while (true) {
1999
+ // Find face with farthest outside point
2000
+ let [chosen_face_idx, chosen_point_idx, max_distance] = [-1, -1, -1]
2001
+
2002
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
2003
+ const face = faces[face_idx]
2004
+ if (face.outside_points.size === 0) continue
2005
+
2006
+ const far = farthest_outside_point_nd(points, face)
2007
+ if (far && far.distance > max_distance) {
2008
+ max_distance = far.distance
2009
+ chosen_face_idx = face_idx
2010
+ chosen_point_idx = far.idx
2011
+ }
2012
+ }
2013
+
2014
+ if (chosen_face_idx === -1) break // All points processed
2015
+
2016
+ const eye_idx = chosen_point_idx
2017
+
2018
+ // Find all faces visible from eye point
2019
+ const visible_indices = new Set<number>()
2020
+ for (let face_idx = 0; face_idx < faces.length; face_idx++) {
2021
+ const dist = point_hyperplane_signed_distance_nd(faces[face_idx].plane, points[eye_idx])
2022
+ if (dist > EPS) visible_indices.add(face_idx)
2023
+ }
2024
+
2025
+ // Build horizon ridges
2026
+ const horizon = build_horizon_nd(faces, visible_indices)
2027
+
2028
+ // Collect candidate points from visible faces
2029
+ const candidates = new Set<number>()
2030
+ for (const face_idx of visible_indices) {
2031
+ for (const pt_idx of faces[face_idx].outside_points) {
2032
+ candidates.add(pt_idx)
2033
+ }
2034
+ }
2035
+
2036
+ // Remove visible faces (in reverse order to maintain indices)
2037
+ const sorted_visible = Array.from(visible_indices).sort((a, b) => b - a)
2038
+ for (const idx of sorted_visible) {
2039
+ faces.splice(idx, 1)
2040
+ }
2041
+
2042
+ // Create new faces from horizon ridges to eye point
2043
+ const new_faces: SimplexFaceND[] = []
2044
+ for (const ridge of horizon) {
2045
+ const new_verts = [...ridge, eye_idx]
2046
+ new_faces.push(make_face_nd(points, new_verts, interior))
2047
+ }
2048
+
2049
+ // Reassign candidate points to new faces
2050
+ for (const face of new_faces) face.outside_points.clear()
2051
+
2052
+ for (const pt_idx of candidates) {
2053
+ if (pt_idx === eye_idx) continue
2054
+
2055
+ let best_face: SimplexFaceND | null = null
2056
+ let best_dist = EPS
2057
+
2058
+ for (const face of new_faces) {
2059
+ const dist = point_hyperplane_signed_distance_nd(face.plane, points[pt_idx])
2060
+ if (dist > best_dist) {
2061
+ best_dist = dist
2062
+ best_face = face
2063
+ }
2064
+ }
2065
+
2066
+ if (best_face) best_face.outside_points.add(pt_idx)
2067
+ }
2068
+
2069
+ faces.push(...new_faces)
2070
+ }
2071
+
2072
+ return faces
2073
+ }
2074
+
2075
+ // Filter for lower hull facets (normal pointing "down" in energy dimension)
2076
+ export function compute_lower_hull_nd(faces: SimplexFaceND[]): SimplexFaceND[] {
2077
+ // Last dimension is energy; negative normal means "downward"
2078
+ return faces.filter((face) => (face.plane.normal.at(-1) ?? 0) < -EPS)
2079
+ }
2080
+
2081
+ // Precomputed model for fast containment checks
2082
+ interface SimplexModelND {
2083
+ vertices: number[][]
2084
+ vertices_spatial: number[][] // Without energy dimension
2085
+ bbox_min: number[]
2086
+ bbox_max: number[]
2087
+ }
2088
+
2089
+ function build_simplex_models_nd(
2090
+ faces: SimplexFaceND[],
2091
+ points: number[][],
2092
+ ): SimplexModelND[] {
2093
+ return faces.map((face) => {
2094
+ const vertices = face.vertex_indices.map((idx) => points[idx])
2095
+ const n = vertices[0].length
2096
+ // Spatial coords are all except last (energy)
2097
+ const vertices_spatial = vertices.map((pt) => pt.slice(0, n - 1))
2098
+
2099
+ // Compute bounding box in spatial dimensions
2100
+ const spatial_dim = n - 1
2101
+ const bbox_min = Array.from({ length: spatial_dim }, (_, idx) =>
2102
+ Math.min(...vertices_spatial.map((pt) => pt[idx])),
2103
+ )
2104
+ const bbox_max = Array.from({ length: spatial_dim }, (_, idx) =>
2105
+ Math.max(...vertices_spatial.map((pt) => pt[idx])),
2106
+ )
2107
+
2108
+ return { vertices, vertices_spatial, bbox_min, bbox_max }
2109
+ })
2110
+ }
2111
+
2112
+ // Check if point is inside simplex and return barycentric coordinates
2113
+ // Uses linear system solution: point = sum(bary[i] * vertex[i]) with sum(bary) = 1
2114
+ function point_in_simplex_nd(point: number[], simplex_vertices: number[][]): number[] | null {
2115
+ const n = simplex_vertices.length // Number of vertices = spatial_dim + 1
2116
+ if (n === 0) return null
2117
+
2118
+ const dim = point.length
2119
+ if (dim !== n - 1) return null // Spatial dim should be one less than vertex count
2120
+
2121
+ // Build linear system: [v1-v0, v2-v0, ..., vn-v0] * [b1, b2, ..., bn] = point - v0
2122
+ // Then b0 = 1 - sum(b1..bn)
2123
+ const v0 = simplex_vertices[0]
2124
+ const edges = simplex_vertices.slice(1).map((vert) => subtract_nd(vert, v0))
2125
+ const rhs = subtract_nd(point, v0)
2126
+
2127
+ // For square system (dim == n-1), solve directly
2128
+ // Build matrix where each column is an edge vector
2129
+ const matrix: number[][] = []
2130
+ for (let row = 0; row < dim; row++) {
2131
+ matrix.push(edges.map((edge) => edge[row]))
2132
+ }
2133
+
2134
+ const coeffs = solve_linear_system(matrix, rhs)
2135
+ if (!coeffs) return null
2136
+
2137
+ // Compute b0 = 1 - sum(coeffs), ensuring sum(bary) = 1 by construction
2138
+ const sum_coeffs = coeffs.reduce((sum, val) => sum + val, 0)
2139
+ const bary = [1 - sum_coeffs, ...coeffs]
2140
+
2141
+ // Point is inside simplex if all barycentric coords are non-negative
2142
+ return bary.every((val) => val >= -EPS) ? bary : null
2143
+ }
2144
+
2145
+ // Compute energy above hull for N-dimensional points
2146
+ export function compute_e_above_hull_nd(
2147
+ query_points: number[][],
2148
+ hull_facets: SimplexFaceND[],
2149
+ all_points: number[][],
2150
+ ): number[] {
2151
+ const models = build_simplex_models_nd(hull_facets, all_points)
2152
+
2153
+ return query_points.map((query) => {
2154
+ const n = query.length
2155
+ const spatial = query.slice(0, n - 1) // All but last coord
2156
+ const energy = query[n - 1]
2157
+
2158
+ let hull_energy: number | null = null
2159
+
2160
+ for (const model of models) {
2161
+ // Fast bounding box rejection
2162
+ let outside_bbox = false
2163
+ for (let idx = 0; idx < spatial.length; idx++) {
2164
+ if (
2165
+ spatial[idx] < model.bbox_min[idx] - EPS ||
2166
+ spatial[idx] > model.bbox_max[idx] + EPS
2167
+ ) {
2168
+ outside_bbox = true
2169
+ break
2170
+ }
2171
+ }
2172
+ if (outside_bbox) continue
2173
+
2174
+ // Check if spatial coords are inside simplex projection
2175
+ const bary = point_in_simplex_nd(spatial, model.vertices_spatial)
2176
+ if (!bary) continue
2177
+
2178
+ // Interpolate energy using barycentric coords
2179
+ const e_hull = bary.reduce(
2180
+ (sum, coeff, idx) => sum + coeff * model.vertices[idx][n - 1],
2181
+ 0,
2182
+ )
2183
+ hull_energy = hull_energy === null ? e_hull : Math.min(hull_energy, e_hull)
2184
+ }
2185
+
2186
+ // If no facet contains this point's spatial projection, it's outside the valid
2187
+ // composition domain. Return NaN to indicate invalid input rather than 0 (which
2188
+ // would falsely imply the point is stable/on-hull).
2189
+ if (hull_energy === null) return NaN
2190
+ return energy - hull_energy
2191
+ })
2192
+ }