matterviz 0.3.5 → 0.3.7

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 (229) hide show
  1. package/dist/MillerIndexInput.svelte +5 -5
  2. package/dist/api/optimade.js +3 -3
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
  8. package/dist/brillouin/compute.js +21 -21
  9. package/dist/brillouin/index.d.ts +1 -1
  10. package/dist/brillouin/index.js +0 -1
  11. package/dist/brillouin/types.d.ts +8 -13
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
  15. package/dist/chempot-diagram/compute.js +1 -7
  16. package/dist/chempot-diagram/temperature.d.ts +1 -1
  17. package/dist/chempot-diagram/temperature.js +1 -3
  18. package/dist/chempot-diagram/types.d.ts +4 -9
  19. package/dist/colors/index.js +5 -5
  20. package/dist/composition/Composition.svelte +2 -1
  21. package/dist/composition/Formula.svelte +7 -4
  22. package/dist/composition/FormulaFilter.svelte +1 -3
  23. package/dist/composition/format.js +4 -4
  24. package/dist/composition/parse.d.ts +2 -1
  25. package/dist/composition/parse.js +61 -46
  26. package/dist/convex-hull/ConvexHull2D.svelte +62 -51
  27. package/dist/convex-hull/ConvexHull3D.svelte +101 -90
  28. package/dist/convex-hull/ConvexHull4D.svelte +70 -58
  29. package/dist/convex-hull/ConvexHullControls.svelte +24 -35
  30. package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
  31. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
  32. package/dist/convex-hull/ConvexHullStats.svelte +9 -2
  33. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
  34. package/dist/convex-hull/GasPressureControls.svelte +7 -7
  35. package/dist/convex-hull/StructurePopup.svelte +65 -30
  36. package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
  37. package/dist/convex-hull/TemperatureSlider.svelte +8 -5
  38. package/dist/convex-hull/barycentric-coords.d.ts +2 -2
  39. package/dist/convex-hull/barycentric-coords.js +2 -2
  40. package/dist/convex-hull/gas-thermodynamics.js +2 -4
  41. package/dist/convex-hull/helpers.d.ts +13 -2
  42. package/dist/convex-hull/helpers.js +37 -16
  43. package/dist/convex-hull/index.d.ts +1 -0
  44. package/dist/convex-hull/index.js +1 -0
  45. package/dist/convex-hull/thermodynamics.d.ts +2 -1
  46. package/dist/convex-hull/thermodynamics.js +7 -7
  47. package/dist/convex-hull/types.d.ts +15 -15
  48. package/dist/effects.svelte.d.ts +12 -0
  49. package/dist/effects.svelte.js +37 -0
  50. package/dist/element/BohrAtom.svelte +4 -4
  51. package/dist/element/data.json.gz.d.ts +3 -1
  52. package/dist/element/index.d.ts +1 -1
  53. package/dist/element/index.js +0 -1
  54. package/dist/fermi-surface/FermiSurface.svelte +4 -4
  55. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  56. package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
  57. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  58. package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
  59. package/dist/fermi-surface/compute.js +2 -2
  60. package/dist/fermi-surface/export.js +13 -26
  61. package/dist/fermi-surface/parse.js +8 -12
  62. package/dist/fermi-surface/types.d.ts +2 -5
  63. package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
  64. package/dist/heatmap-matrix/index.js +6 -6
  65. package/dist/io/decompress.d.ts +2 -1
  66. package/dist/io/decompress.js +1 -1
  67. package/dist/io/export.js +1 -1
  68. package/dist/io/index.d.ts +1 -1
  69. package/dist/io/index.js +0 -1
  70. package/dist/io/url-drop.js +7 -1
  71. package/dist/isosurface/IsosurfaceControls.svelte +11 -25
  72. package/dist/isosurface/slice.js +1 -1
  73. package/dist/isosurface/types.js +12 -12
  74. package/dist/labels.d.ts +1 -1
  75. package/dist/labels.js +14 -11
  76. package/dist/layout/InfoTag.svelte +6 -4
  77. package/dist/layout/PropertyFilter.svelte +4 -2
  78. package/dist/layout/json-tree/JsonTree.svelte +22 -14
  79. package/dist/layout/json-tree/JsonValue.svelte +2 -2
  80. package/dist/layout/json-tree/types.d.ts +3 -2
  81. package/dist/layout/json-tree/types.js +0 -1
  82. package/dist/layout/json-tree/utils.d.ts +4 -4
  83. package/dist/layout/json-tree/utils.js +12 -20
  84. package/dist/marching-cubes.js +13 -15
  85. package/dist/math.d.ts +11 -1
  86. package/dist/math.js +15 -6
  87. package/dist/overlays/DragControlTab.svelte +98 -0
  88. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  89. package/dist/overlays/DraggablePane.svelte +7 -84
  90. package/dist/overlays/index.d.ts +1 -0
  91. package/dist/overlays/index.js +1 -0
  92. package/dist/periodic-table/PeriodicTable.svelte +11 -11
  93. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
  94. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  95. package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
  96. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  97. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
  98. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
  99. package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
  100. package/dist/phase-diagram/build-diagram.js +11 -18
  101. package/dist/phase-diagram/diagram-input.d.ts +5 -9
  102. package/dist/phase-diagram/index.d.ts +2 -2
  103. package/dist/phase-diagram/index.js +0 -2
  104. package/dist/phase-diagram/parse.d.ts +2 -2
  105. package/dist/phase-diagram/parse.js +6 -10
  106. package/dist/phase-diagram/svg-to-diagram.js +15 -15
  107. package/dist/phase-diagram/types.d.ts +5 -11
  108. package/dist/phase-diagram/utils.d.ts +2 -2
  109. package/dist/phase-diagram/utils.js +9 -11
  110. package/dist/plot/BarPlot.svelte +162 -314
  111. package/dist/plot/BarPlot.svelte.d.ts +5 -4
  112. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  113. package/dist/plot/BinnedScatterPlot.svelte +1114 -0
  114. package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
  115. package/dist/plot/ColorBar.svelte +19 -17
  116. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  117. package/dist/plot/FillArea.svelte +2 -4
  118. package/dist/plot/FillArea.svelte.d.ts +1 -1
  119. package/dist/plot/Histogram.svelte +167 -281
  120. package/dist/plot/Histogram.svelte.d.ts +1 -1
  121. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  122. package/dist/plot/InteractiveAxisLabel.svelte +5 -3
  123. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  124. package/dist/plot/PlotAxis.svelte +169 -0
  125. package/dist/plot/PlotAxis.svelte.d.ts +24 -0
  126. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  127. package/dist/plot/ReferenceLine3D.svelte +53 -51
  128. package/dist/plot/ReferencePlane.svelte +39 -42
  129. package/dist/plot/ScatterPlot.svelte +300 -367
  130. package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
  131. package/dist/plot/ScatterPlot3D.svelte +33 -6
  132. package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
  133. package/dist/plot/ScatterPlot3DControls.svelte +9 -9
  134. package/dist/plot/ScatterPlotControls.svelte +3 -4
  135. package/dist/plot/ScatterPoint.svelte +18 -27
  136. package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
  137. package/dist/plot/Surface3D.svelte +4 -7
  138. package/dist/plot/ZeroLines.svelte +2 -1
  139. package/dist/plot/ZeroLines.svelte.d.ts +2 -1
  140. package/dist/plot/ZoomRect.svelte +2 -2
  141. package/dist/plot/ZoomRect.svelte.d.ts +3 -3
  142. package/dist/plot/adaptive-density.d.ts +69 -0
  143. package/dist/plot/adaptive-density.js +191 -0
  144. package/dist/plot/auto-place.d.ts +43 -0
  145. package/dist/plot/auto-place.js +122 -0
  146. package/dist/plot/axis-utils.js +3 -5
  147. package/dist/plot/binned-scatter-types.d.ts +59 -0
  148. package/dist/plot/binned-scatter-types.js +1 -0
  149. package/dist/plot/data-cleaning.js +1 -1
  150. package/dist/plot/data-transform.js +1 -1
  151. package/dist/plot/fill-utils.d.ts +4 -9
  152. package/dist/plot/fill-utils.js +29 -44
  153. package/dist/plot/index.d.ts +4 -0
  154. package/dist/plot/index.js +2 -0
  155. package/dist/plot/interactions.d.ts +4 -4
  156. package/dist/plot/interactions.js +4 -3
  157. package/dist/plot/layout.d.ts +20 -2
  158. package/dist/plot/layout.js +59 -16
  159. package/dist/plot/reference-line.d.ts +1 -1
  160. package/dist/plot/reference-line.js +9 -11
  161. package/dist/plot/scales.d.ts +1 -1
  162. package/dist/plot/scales.js +20 -23
  163. package/dist/plot/types.d.ts +30 -58
  164. package/dist/plot/types.js +2 -6
  165. package/dist/plot/utils/label-placement.d.ts +24 -3
  166. package/dist/plot/utils/label-placement.js +82 -12
  167. package/dist/plot/utils/series-visibility.d.ts +8 -2
  168. package/dist/plot/utils/series-visibility.js +23 -5
  169. package/dist/rdf/RdfPlot.svelte +5 -5
  170. package/dist/rdf/calc-rdf.js +3 -3
  171. package/dist/sanitize.d.ts +2 -0
  172. package/dist/sanitize.js +2 -0
  173. package/dist/spectral/Bands.svelte +1 -1
  174. package/dist/spectral/BandsAndDos.svelte +22 -16
  175. package/dist/spectral/BrillouinBandsDos.svelte +20 -16
  176. package/dist/spectral/Dos.svelte +1 -1
  177. package/dist/spectral/helpers.d.ts +4 -2
  178. package/dist/spectral/helpers.js +44 -35
  179. package/dist/spectral/index.d.ts +1 -1
  180. package/dist/spectral/index.js +0 -1
  181. package/dist/structure/AtomLegend.svelte +23 -6
  182. package/dist/structure/AtomLegend.svelte.d.ts +1 -0
  183. package/dist/structure/CanvasTooltip.svelte +9 -9
  184. package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
  185. package/dist/structure/CellSelect.svelte +14 -16
  186. package/dist/structure/Structure.svelte +317 -68
  187. package/dist/structure/Structure.svelte.d.ts +4 -2
  188. package/dist/structure/StructureControls.svelte +20 -45
  189. package/dist/structure/StructureExportPane.svelte +2 -1
  190. package/dist/structure/StructureInfoPane.svelte +10 -8
  191. package/dist/structure/StructureScene.svelte +527 -177
  192. package/dist/structure/StructureScene.svelte.d.ts +5 -2
  193. package/dist/structure/atom-properties.js +4 -4
  194. package/dist/structure/bond-order-perception.js +115 -98
  195. package/dist/structure/bonding.d.ts +27 -1
  196. package/dist/structure/bonding.js +187 -16
  197. package/dist/structure/export.js +1 -1
  198. package/dist/structure/index.d.ts +3 -2
  199. package/dist/structure/index.js +0 -2
  200. package/dist/structure/parse.js +88 -59
  201. package/dist/symmetry/WyckoffTable.svelte +7 -0
  202. package/dist/symmetry/index.js +13 -14
  203. package/dist/table/HeatmapTable.svelte +45 -66
  204. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  205. package/dist/table/ToggleMenu.svelte +19 -10
  206. package/dist/theme/themes.mjs +12 -0
  207. package/dist/tooltip/index.d.ts +1 -1
  208. package/dist/tooltip/index.js +0 -1
  209. package/dist/trajectory/Trajectory.svelte +43 -15
  210. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
  211. package/dist/trajectory/extract.js +1 -1
  212. package/dist/trajectory/frame-reader.js +4 -4
  213. package/dist/trajectory/helpers.d.ts +5 -4
  214. package/dist/trajectory/helpers.js +9 -17
  215. package/dist/trajectory/index.d.ts +2 -2
  216. package/dist/trajectory/index.js +2 -2
  217. package/dist/trajectory/parse/ase.js +4 -4
  218. package/dist/trajectory/parse/hdf5.js +1 -1
  219. package/dist/trajectory/parse/index.js +2 -3
  220. package/dist/trajectory/parse/lammps.js +1 -1
  221. package/dist/trajectory/parse/vasp.js +1 -1
  222. package/dist/trajectory/plotting.d.ts +1 -1
  223. package/dist/trajectory/plotting.js +38 -38
  224. package/dist/trajectory/types.d.ts +1 -1
  225. package/dist/utils.d.ts +1 -0
  226. package/dist/utils.js +9 -0
  227. package/dist/xrd/calc-xrd.js +3 -4
  228. package/dist/xrd/parse.js +1 -1
  229. package/package.json +42 -22
@@ -3,7 +3,7 @@ import type { ElementSymbol } from '../element';
3
3
  import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types';
4
4
  import type { Vec3 } from '../math';
5
5
  import type { CameraProjection, ShowBonds, VectorColorMode, VectorLayerConfig } from '../settings';
6
- import type { AnyStructure, MeasureMode, Site, StructureBond } from './';
6
+ import type { AnyStructure, BondEditMode, BondOrder, MeasureMode, Site, StructureBond } from './';
7
7
  import { Lattice } from './';
8
8
  import type { AtomColorConfig } from './atom-properties';
9
9
  import type { MoyoDataset } from '@spglib/moyo-wasm';
@@ -78,6 +78,8 @@ type $$ComponentProps = {
78
78
  removed_bonds?: StructureBond[];
79
79
  bond_order_overrides?: StructureBond[];
80
80
  bond_edits_enabled?: boolean;
81
+ bond_edit_mode?: BondEditMode;
82
+ bond_edit_order?: BondOrder;
81
83
  selection_highlight_color?: string;
82
84
  active_sites?: number[];
83
85
  active_highlight_color?: string;
@@ -95,6 +97,7 @@ type $$ComponentProps = {
95
97
  sym_data?: MoyoDataset | null;
96
98
  on_sites_moved?: (scene_indices: number[], delta: Vec3) => void;
97
99
  on_operation_start?: () => void;
100
+ on_bond_edit_start?: () => void;
98
101
  on_add_atom?: (xyz: Vec3, element: ElementSymbol) => void;
99
102
  add_atom_mode?: boolean;
100
103
  add_element?: ElementSymbol;
@@ -103,6 +106,6 @@ type $$ComponentProps = {
103
106
  volumetric_data?: VolumetricData;
104
107
  isosurface_settings?: IsosurfaceSettings;
105
108
  };
106
- declare const StructureScene: import("svelte").Component<$$ComponentProps, {}, "cursor" | "site_label_offset" | "vector_configs" | "camera" | "scene" | "orbit_controls" | "site_radius_overrides" | "hovered_idx" | "hovered_site" | "camera_is_moving" | "selected_sites" | "measured_sites" | "added_bonds" | "removed_bonds" | "bond_order_overrides" | "active_sites" | "rotation_target_ref" | "initial_computed_zoom" | "hidden_elements" | "hidden_prop_vals" | "element_radius_overrides" | "add_atom_mode" | "add_element" | "dragging_atoms">;
109
+ declare const StructureScene: import("svelte").Component<$$ComponentProps, {}, "cursor" | "site_label_offset" | "vector_configs" | "camera" | "scene" | "orbit_controls" | "site_radius_overrides" | "hovered_idx" | "hovered_site" | "camera_is_moving" | "selected_sites" | "measured_sites" | "added_bonds" | "removed_bonds" | "bond_order_overrides" | "bond_edit_mode" | "active_sites" | "rotation_target_ref" | "initial_computed_zoom" | "hidden_elements" | "hidden_prop_vals" | "element_radius_overrides" | "add_atom_mode" | "add_element" | "dragging_atoms">;
107
110
  type StructureScene = ReturnType<typeof StructureScene>;
108
111
  export default StructureScene;
@@ -126,11 +126,11 @@ export function get_coordination_colors(structure, strategy = `electroneg_ratio`
126
126
  return build_prop_colors(coord_nums, colors, unique_values);
127
127
  }
128
128
  export function get_wyckoff_colors(structure, sym_data, scale = DEFAULT_COLOR_SCALE) {
129
- const n = structure.sites.length;
129
+ const n_sites = structure.sites.length;
130
130
  if (!sym_data?.wyckoffs || sym_data.wyckoffs.length === 0) {
131
131
  return {
132
- colors: Array(n).fill(GRAY),
133
- values: Array(n).fill(`unknown`),
132
+ colors: Array(n_sites).fill(GRAY),
133
+ values: Array(n_sites).fill(`unknown`),
134
134
  unique_values: [`unknown`],
135
135
  };
136
136
  }
@@ -155,7 +155,7 @@ export function get_wyckoff_colors(structure, sym_data, scale = DEFAULT_COLOR_SC
155
155
  }
156
156
  if (sym_idx >= sym_data.wyckoffs.length) {
157
157
  console.error(`[get_wyckoff_colors] Site ${idx} (maps to ${sym_idx}) has no Wyckoff data. ` +
158
- `Structure has ${n} sites but symmetry data only has ${sym_data.wyckoffs.length}.`);
158
+ `Structure has ${n_sites} sites but symmetry data only has ${sym_data.wyckoffs.length}.`);
159
159
  return `unknown`;
160
160
  }
161
161
  const wyckoff = sym_data.wyckoffs[sym_idx];
@@ -1,6 +1,6 @@
1
1
  import { get_bond_key } from './bonding';
2
2
  const primary_element = (site) => {
3
- return (site.species?.reduce((a, b) => (b.occu > a.occu ? b : a), site.species[0])?.element ?? ``);
3
+ return (site.species?.reduce((best_species, species) => (species.occu > best_species.occu ? species : best_species), site.species[0])?.element ?? ``);
4
4
  };
5
5
  // xyz2mol atomic_valence. Valence combinations are re-sorted by total
6
6
  // valence sum so the least-saturated solution is tried first.
@@ -51,19 +51,17 @@ function formal_charge(symbol, bond_valence) {
51
51
  return 0;
52
52
  return VALENCE_ELECTRONS[symbol] - 8 + bond_valence;
53
53
  }
54
- function is_main_group(symbol) {
55
- return symbol in ATOMIC_VALENCE;
56
- }
54
+ const is_main_group = (symbol) => symbol in ATOMIC_VALENCE;
57
55
  // Cap per-fragment valence enumeration (3^k for catenated S/Se/Te/P chains).
58
56
  const MAX_VALENCE_COMBOS = 4096;
59
57
  function split_fragments(n_atoms, edges) {
60
58
  const adjacency = Array.from({ length: n_atoms }, () => []);
61
- for (const [a, b] of edges) {
62
- if (adjacency[a] === undefined || adjacency[b] === undefined) {
63
- throw new Error(`Invalid edge ${a}-${b} for ${n_atoms} atoms`);
59
+ for (const [atom_idx_1, atom_idx_2] of edges) {
60
+ if (adjacency[atom_idx_1] === undefined || adjacency[atom_idx_2] === undefined) {
61
+ throw new Error(`Invalid edge ${atom_idx_1}-${atom_idx_2} for ${n_atoms} atoms`);
64
62
  }
65
- adjacency[a].push(b);
66
- adjacency[b].push(a);
63
+ adjacency[atom_idx_1].push(atom_idx_2);
64
+ adjacency[atom_idx_2].push(atom_idx_1);
67
65
  }
68
66
  const seen = new Set();
69
67
  const fragments = [];
@@ -95,18 +93,21 @@ function* valence_combinations(valence_lists) {
95
93
  const combos = [];
96
94
  const rec = (pos, acc) => {
97
95
  if (pos === valence_lists.length) {
98
- combos.push({ sum: acc.reduce((s, v) => s + v, 0), pick: [...acc] });
96
+ combos.push({
97
+ sum: acc.reduce((sum, valence) => sum + valence, 0),
98
+ pick: [...acc],
99
+ });
99
100
  return;
100
101
  }
101
- for (const v of valence_lists[pos])
102
- rec(pos + 1, [...acc, v]);
102
+ for (const valence of valence_lists[pos])
103
+ rec(pos + 1, [...acc, valence]);
103
104
  };
104
105
  rec(0, []);
105
- combos.sort((a, b) => a.sum - b.sum);
106
- for (const c of combos)
107
- yield c.pick;
106
+ combos.sort((left_combo, right_combo) => left_combo.sum - right_combo.sum);
107
+ for (const combo of combos)
108
+ yield combo.pick;
108
109
  }
109
- const atom_valence = (atom, edges, orders) => edges.reduce((sum, edge, edge_idx) => sum + (edge.i === atom || edge.j === atom ? orders[edge_idx] : 0), 0);
110
+ const atom_valence = (atom, edges, orders) => edges.reduce((sum, edge, edge_idx) => sum + (edge.from === atom || edge.to === atom ? orders[edge_idx] : 0), 0);
110
111
  // Greedily raise bond orders toward each atom's target valence; succeeds
111
112
  // only if every atom's used valence ends exactly at its target.
112
113
  function assign_bond_orders(edges, target_valence) {
@@ -117,13 +118,13 @@ function assign_bond_orders(edges, target_valence) {
117
118
  progressed = false;
118
119
  let best = -1;
119
120
  let best_deficit = 0;
120
- edges.forEach((e, ei) => {
121
- const da = target_valence[e.i] - used(e.i);
122
- const db = target_valence[e.j] - used(e.j);
123
- const d = Math.min(da, db);
124
- if (d > best_deficit && orders[ei] < 3) {
125
- best_deficit = d;
126
- best = ei;
121
+ edges.forEach((edge, edge_idx) => {
122
+ const deficit_1 = target_valence[edge.from] - used(edge.from);
123
+ const deficit_2 = target_valence[edge.to] - used(edge.to);
124
+ const shared_deficit = Math.min(deficit_1, deficit_2);
125
+ if (shared_deficit > best_deficit && orders[edge_idx] < 3) {
126
+ best_deficit = shared_deficit;
127
+ best = edge_idx;
127
128
  }
128
129
  });
129
130
  if (best >= 0) {
@@ -138,46 +139,52 @@ function assign_bond_orders(edges, target_valence) {
138
139
  return orders;
139
140
  }
140
141
  // Spanning-tree cycle basis, deduplicated by sorted vertex set.
141
- function find_rings(n, edges) {
142
- const adjacency = Array.from({ length: n }, () => new Set());
143
- for (const [a, b] of edges) {
144
- if (adjacency[a] === undefined || adjacency[b] === undefined) {
145
- throw new Error(`Invalid edge ${a}-${b} for ${n} atoms`);
142
+ function find_rings(n_atoms, edges) {
143
+ const adjacency = Array.from({ length: n_atoms }, () => new Set());
144
+ for (const [atom_idx_1, atom_idx_2] of edges) {
145
+ if (adjacency[atom_idx_1] === undefined || adjacency[atom_idx_2] === undefined) {
146
+ throw new Error(`Invalid edge ${atom_idx_1}-${atom_idx_2} for ${n_atoms} atoms`);
146
147
  }
147
- adjacency[a].add(b);
148
- adjacency[b].add(a);
148
+ adjacency[atom_idx_1].add(atom_idx_2);
149
+ adjacency[atom_idx_2].add(atom_idx_1);
149
150
  }
150
- const parent = Array.from({ length: n }, () => -1);
151
+ const parent = Array.from({ length: n_atoms }, () => -1);
151
152
  const seen = new Set();
152
153
  const rings = [];
153
- for (let s = 0; s < n; s++) {
154
- if (seen.has(s))
154
+ const path_to_ancestor = (start_idx, ancestor_idx) => {
155
+ const path = [];
156
+ for (let path_atom_idx = start_idx; path_atom_idx !== ancestor_idx; path_atom_idx = parent[path_atom_idx]) {
157
+ path.push(path_atom_idx);
158
+ }
159
+ return path;
160
+ };
161
+ for (let start_idx = 0; start_idx < n_atoms; start_idx++) {
162
+ if (seen.has(start_idx))
155
163
  continue;
156
- const queue = [s];
164
+ const queue = [start_idx];
157
165
  let queue_idx = 0;
158
- seen.add(s);
159
- parent[s] = -1;
166
+ seen.add(start_idx);
167
+ parent[start_idx] = -1;
160
168
  while (queue_idx < queue.length) {
161
- const u = queue[queue_idx++];
162
- for (const v of adjacency[u]) {
163
- if (!seen.has(v)) {
164
- seen.add(v);
165
- parent[v] = u;
166
- queue.push(v);
169
+ const current_atom_idx = queue[queue_idx++];
170
+ for (const neighbor_idx of adjacency[current_atom_idx]) {
171
+ if (!seen.has(neighbor_idx)) {
172
+ seen.add(neighbor_idx);
173
+ parent[neighbor_idx] = current_atom_idx;
174
+ queue.push(neighbor_idx);
167
175
  }
168
- else if (parent[u] !== v) {
169
- const path_u = [];
170
- const path_v = [];
171
- const anc_u = new Set();
172
- for (let x = u; x !== -1; x = parent[x])
173
- anc_u.add(x);
174
- for (let x = v; x !== -1; x = parent[x]) {
175
- if (anc_u.has(x)) {
176
- for (let y = u; y !== x; y = parent[y])
177
- path_u.push(y);
178
- for (let y = v; y !== x; y = parent[y])
179
- path_v.push(y);
180
- const ring = [x, ...path_u, ...path_v.toReversed()];
176
+ else if (parent[current_atom_idx] !== neighbor_idx) {
177
+ const current_ancestors = new Set();
178
+ for (let ancestor_idx = current_atom_idx; ancestor_idx !== -1; ancestor_idx = parent[ancestor_idx]) {
179
+ current_ancestors.add(ancestor_idx);
180
+ }
181
+ for (let ancestor_idx = neighbor_idx; ancestor_idx !== -1; ancestor_idx = parent[ancestor_idx]) {
182
+ if (current_ancestors.has(ancestor_idx)) {
183
+ const ring = [
184
+ ancestor_idx,
185
+ ...path_to_ancestor(current_atom_idx, ancestor_idx),
186
+ ...path_to_ancestor(neighbor_idx, ancestor_idx).toReversed(),
187
+ ];
181
188
  if (ring.length >= 3)
182
189
  rings.push(ring);
183
190
  break;
@@ -188,31 +195,39 @@ function find_rings(n, edges) {
188
195
  }
189
196
  }
190
197
  const uniq = new Map();
191
- for (const r of rings) {
192
- const key = [...r].sort((x, y) => x - y).join(`,`);
198
+ for (const ring of rings) {
199
+ const key = [...ring].sort((left_idx, right_idx) => left_idx - right_idx).join(`,`);
193
200
  if (!uniq.has(key))
194
- uniq.set(key, r);
201
+ uniq.set(key, ring);
195
202
  }
196
203
  return [...uniq.values()];
197
204
  }
198
- function order_to_bond_order(o) {
199
- return o >= 3 ? 3 : o === 2 ? 2 : 1;
200
- }
205
+ const order_to_bond_order = (order) => order >= 3 ? 3 : order === 2 ? 2 : 1;
201
206
  // Conservative planarity check: degenerate first-3-atom planes are non-planar.
202
207
  function ring_is_planar(ring, sites) {
203
208
  if (ring.length < 3)
204
209
  return false;
205
- const p = ring.map((a) => sites[a].xyz);
206
- const v1 = [p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2]];
207
- const v2 = [p[2][0] - p[0][0], p[2][1] - p[0][1], p[2][2] - p[0][2]];
210
+ const points = ring.map((atom_idx) => sites[atom_idx].xyz);
211
+ const v1 = [
212
+ points[1][0] - points[0][0],
213
+ points[1][1] - points[0][1],
214
+ points[1][2] - points[0][2],
215
+ ];
216
+ const v2 = [
217
+ points[2][0] - points[0][0],
218
+ points[2][1] - points[0][1],
219
+ points[2][2] - points[0][2],
220
+ ];
208
221
  const nx = v1[1] * v2[2] - v1[2] * v2[1];
209
222
  const ny = v1[2] * v2[0] - v1[0] * v2[2];
210
223
  const nz = v1[0] * v2[1] - v1[1] * v2[0];
211
224
  const len = Math.hypot(nx, ny, nz);
212
225
  if (len < 1e-6)
213
226
  return false;
214
- return p.every((q) => {
215
- const dev = Math.abs((q[0] - p[0][0]) * nx + (q[1] - p[0][1]) * ny + (q[2] - p[0][2]) * nz) / len;
227
+ return points.every((point) => {
228
+ const dev = Math.abs((point[0] - points[0][0]) * nx +
229
+ (point[1] - points[0][1]) * ny +
230
+ (point[2] - points[0][2]) * nz) / len;
216
231
  return dev < 0.3;
217
232
  });
218
233
  }
@@ -225,7 +240,7 @@ const SP2_OK = new Set([`C`, `N`, `O`, `S`]);
225
240
  export function perceive_bond_orders(sites, bonds, opts = {}) {
226
241
  const max_atoms = opts.max_atoms ?? 5000;
227
242
  if (sites.length > max_atoms) {
228
- return bonds.map((b) => ({ ...b, bond_order: 1, perceived: false }));
243
+ return bonds.map((bond) => ({ ...bond, bond_order: 1, perceived: false }));
229
244
  }
230
245
  const edges = [];
231
246
  const result = new Map();
@@ -237,30 +252,30 @@ export function perceive_bond_orders(sites, bonds, opts = {}) {
237
252
  bond.site_idx_2 >= sites.length)
238
253
  continue;
239
254
  edges.push({
240
- i: bond.site_idx_1,
241
- j: bond.site_idx_2,
242
- ref: bond,
255
+ from: bond.site_idx_1,
256
+ to: bond.site_idx_2,
257
+ bond,
243
258
  });
244
259
  }
245
- const frags = split_fragments(sites.length, edges.map((e) => [e.i, e.j]));
260
+ const frags = split_fragments(sites.length, edges.map((edge) => [edge.from, edge.to]));
246
261
  let ring_id = 0;
247
262
  for (const frag of frags) {
248
263
  const atom_set = new Set(frag);
249
- const frag_edges = edges.filter((e) => atom_set.has(e.i));
250
- const symbols = frag.map((a) => primary_element(sites[a]));
264
+ const frag_edges = edges.filter((edge) => atom_set.has(edge.from));
265
+ const symbols = frag.map((atom_idx) => primary_element(sites[atom_idx]));
251
266
  if (!symbols.every(is_main_group))
252
267
  continue;
253
268
  const idx_of = Array.from({ length: sites.length }, () => -1);
254
269
  frag.forEach((site_idx, local_idx) => {
255
270
  idx_of[site_idx] = local_idx;
256
271
  });
257
- const local_edges = frag_edges.map((e) => ({
258
- i: idx_of[e.i],
259
- j: idx_of[e.j],
260
- ref: e.ref,
272
+ const local_edges = frag_edges.map((edge) => ({
273
+ from: idx_of[edge.from],
274
+ to: idx_of[edge.to],
275
+ bond: edge.bond,
261
276
  }));
262
- const valence_lists = symbols.map((s) => ATOMIC_VALENCE[s]);
263
- const combo_count = valence_lists.reduce((p, l) => p * l.length, 1);
277
+ const valence_lists = symbols.map((symbol) => ATOMIC_VALENCE[symbol]);
278
+ const combo_count = valence_lists.reduce((product, valence_list) => product * valence_list.length, 1);
264
279
  if (combo_count > MAX_VALENCE_COMBOS)
265
280
  continue;
266
281
  let solved = null;
@@ -270,8 +285,8 @@ export function perceive_bond_orders(sites, bonds, opts = {}) {
270
285
  if (!candidate)
271
286
  continue;
272
287
  let sum_fc = 0;
273
- for (let k = 0; k < frag.length; k++) {
274
- sum_fc += formal_charge(symbols[k], atom_valence(k, local_edges, candidate));
288
+ for (let local_atom_idx = 0; local_atom_idx < frag.length; local_atom_idx++) {
289
+ sum_fc += formal_charge(symbols[local_atom_idx], atom_valence(local_atom_idx, local_edges, candidate));
275
290
  }
276
291
  if (sum_fc === want_charge) {
277
292
  solved = candidate;
@@ -280,35 +295,37 @@ export function perceive_bond_orders(sites, bonds, opts = {}) {
280
295
  }
281
296
  if (!solved)
282
297
  continue;
283
- local_edges.forEach((e, ei) => {
284
- const ord = order_to_bond_order(solved[ei]);
285
- result.set(e.ref, { ...e.ref, bond_order: ord, perceived: true });
298
+ local_edges.forEach((edge, edge_idx) => {
299
+ const order = order_to_bond_order(solved[edge_idx]);
300
+ result.set(edge.bond, { ...edge.bond, bond_order: order, perceived: true });
286
301
  });
287
302
  // Hückel aromatic post-pass, retaining Kekulé orders for display toggles.
288
- const rings = find_rings(frag.length, local_edges.map((e) => [e.i, e.j]));
303
+ const rings = find_rings(frag.length, local_edges.map((edge) => [edge.from, edge.to]));
289
304
  for (const ring of rings) {
290
- const global_ring = ring.map((k) => frag[k]);
291
- if (!global_ring.every((g) => SP2_OK.has(primary_element(sites[g]))))
305
+ const global_ring = ring.map((local_atom_idx) => frag[local_atom_idx]);
306
+ const has_only_sp2_ring_atoms = global_ring.every((global_atom_idx) => SP2_OK.has(primary_element(sites[global_atom_idx])));
307
+ if (!has_only_sp2_ring_atoms)
292
308
  continue;
293
309
  if (!ring_is_planar(global_ring, sites))
294
310
  continue;
295
311
  const ring_set = new Set(ring);
312
+ const edge_is_in_ring = (edge) => ring_set.has(edge.from) && ring_set.has(edge.to);
296
313
  const has_ring_multiple = new Set();
297
- local_edges.forEach((e, ei) => {
298
- if (ring_set.has(e.i) && ring_set.has(e.j) && solved[ei] > 1) {
299
- has_ring_multiple.add(e.i);
300
- has_ring_multiple.add(e.j);
314
+ local_edges.forEach((edge, edge_idx) => {
315
+ if (edge_is_in_ring(edge) && solved[edge_idx] > 1) {
316
+ has_ring_multiple.add(edge.from);
317
+ has_ring_multiple.add(edge.to);
301
318
  }
302
319
  });
303
320
  const has_any_multiple_bond = (atom_idx) => local_edges.some((edge, edge_idx) => {
304
- if (edge.i !== atom_idx && edge.j !== atom_idx)
321
+ if (edge.from !== atom_idx && edge.to !== atom_idx)
305
322
  return false;
306
323
  return solved[edge_idx] > 1;
307
324
  });
308
325
  const has_non_ring_neighbor = (atom_idx) => local_edges.some((edge) => {
309
- if (edge.i !== atom_idx && edge.j !== atom_idx)
326
+ if (edge.from !== atom_idx && edge.to !== atom_idx)
310
327
  return false;
311
- const neighbor_idx = edge.i === atom_idx ? edge.j : edge.i;
328
+ const neighbor_idx = edge.from === atom_idx ? edge.to : edge.from;
312
329
  return !ring_set.has(neighbor_idx);
313
330
  });
314
331
  const pi_by_atom = ring.map((atom_idx) => {
@@ -324,12 +341,12 @@ export function perceive_bond_orders(sites, bonds, opts = {}) {
324
341
  const pi = pi_by_atom.reduce((sum, val) => sum + val, 0);
325
342
  if (pi_by_atom.every((val) => val > 0) && pi >= 2 && (pi - 2) % 4 === 0) {
326
343
  const this_ring = ring_id++;
327
- local_edges.forEach((e) => {
328
- if (ring_set.has(e.i) && ring_set.has(e.j)) {
329
- const prev = result.get(e.ref);
344
+ local_edges.forEach((edge) => {
345
+ if (edge_is_in_ring(edge)) {
346
+ const prev = result.get(edge.bond);
330
347
  if (prev === undefined)
331
348
  throw new Error(`Missing perceived bond`);
332
- result.set(e.ref, {
349
+ result.set(edge.bond, {
333
350
  ...prev,
334
351
  bond_order: `aromatic`,
335
352
  aromatic_ring: this_ring,
@@ -1,7 +1,32 @@
1
1
  import type { Vec3 } from '../math';
2
- import type { AnyStructure, BondOrder, BondPair, StructureBond } from './';
2
+ import type { AnyStructure, BondOrder, BondPair, Site, StructureBond } from './';
3
3
  export declare const normalize_structure_bond: (site_idx_1: number, site_idx_2: number, order: BondOrder, cell_shift?: Vec3) => StructureBond;
4
4
  export declare const get_bond_key: (idx_1: number, idx_2: number, cell_shift?: Vec3) => string;
5
+ export type BondEditState = {
6
+ added_bonds: StructureBond[];
7
+ removed_bonds: StructureBond[];
8
+ bond_order_overrides: StructureBond[];
9
+ };
10
+ export type BondEditAction = `added` | `already-visible` | `deleted-added` | `deleted-calculated` | `not-visible` | `ordered-added` | `ordered-calculated` | `restored`;
11
+ export type BondEditResult = {
12
+ action: BondEditAction;
13
+ changed: boolean;
14
+ state: BondEditState;
15
+ };
16
+ export type BondKeyTarget = Pick<StructureBond, `site_idx_1` | `site_idx_2` | `cell_shift`>;
17
+ type BondOrderTarget = BondKeyTarget & {
18
+ bond_order?: BondOrder;
19
+ order?: BondOrder;
20
+ };
21
+ export declare const BOND_ORDER_OPTIONS: {
22
+ order: BondOrder;
23
+ label: string;
24
+ }[];
25
+ export declare const canonicalize_bond_target: (bond: BondKeyTarget, sites: Site[] | undefined) => BondKeyTarget;
26
+ export declare function has_visible_bond(edit_state: BondEditState, bond: BondKeyTarget, calculated_bonds: BondOrderTarget[]): boolean;
27
+ export declare function add_or_restore_bond(edit_state: BondEditState, bond: BondKeyTarget, calculated_bonds: BondOrderTarget[], order: BondOrder): BondEditResult;
28
+ export declare function delete_bond(edit_state: BondEditState, bond: BondKeyTarget, calculated_bonds: BondOrderTarget[]): BondEditResult;
29
+ export declare function set_bond_order(edit_state: BondEditState, bond: BondKeyTarget, calculated_bonds: BondOrderTarget[], order: BondOrder): BondEditResult;
5
30
  export declare const merge_bond_edits: (base_bonds: StructureBond[], added: StructureBond[], removed: StructureBond[], overrides: StructureBond[]) => StructureBond[];
6
31
  export declare function normalize_bond_order(order: unknown): BondOrder | null;
7
32
  export declare function structure_bond_to_bond_pair(structure: AnyStructure, bond: StructureBond): BondPair;
@@ -40,3 +65,4 @@ export declare function solid_angle(structure: AnyStructure, { min_solid_angle,
40
65
  min_bond_dist?: number | undefined;
41
66
  strength_threshold?: number | undefined;
42
67
  }): BondPair[];
68
+ export {};