matterviz 0.3.3 → 0.3.5

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 (126) hide show
  1. package/dist/FilePicker.svelte +1 -1
  2. package/dist/app.css +7 -0
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/compute.js +8 -4
  5. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +6 -6
  6. package/dist/chempot-diagram/async-compute.svelte.js +6 -5
  7. package/dist/chempot-diagram/chempot-worker.js +2 -2
  8. package/dist/chempot-diagram/compute.js +16 -16
  9. package/dist/composition/FormulaFilter.svelte +3 -3
  10. package/dist/constants.js +2 -8
  11. package/dist/convex-hull/ConvexHull.svelte +2 -2
  12. package/dist/convex-hull/ConvexHull2D.svelte +11 -10
  13. package/dist/convex-hull/ConvexHull3D.svelte +16 -14
  14. package/dist/convex-hull/ConvexHull4D.svelte +26 -14
  15. package/dist/convex-hull/ConvexHullControls.svelte +1 -1
  16. package/dist/convex-hull/ConvexHullInfoPane.svelte +68 -61
  17. package/dist/convex-hull/ConvexHullStats.svelte +23 -6
  18. package/dist/convex-hull/GasPressureControls.svelte +3 -3
  19. package/dist/convex-hull/TemperatureSlider.svelte +1 -1
  20. package/dist/convex-hull/barycentric-coords.js +2 -2
  21. package/dist/convex-hull/helpers.js +45 -27
  22. package/dist/convex-hull/thermodynamics.js +2 -2
  23. package/dist/element/BohrAtom.svelte +25 -27
  24. package/dist/element/BohrAtom.svelte.d.ts +2 -2
  25. package/dist/element/data.d.ts +2 -3
  26. package/dist/element/data.js +1 -1
  27. package/dist/fermi-surface/FermiSurface.svelte +5 -2
  28. package/dist/fermi-surface/compute.js +3 -3
  29. package/dist/fermi-surface/parse.js +2 -2
  30. package/dist/fermi-surface/symmetry.js +1 -1
  31. package/dist/heatmap-matrix/HeatmapMatrix.svelte +8 -8
  32. package/dist/icons.d.ts +6 -6
  33. package/dist/icons.js +6 -6
  34. package/dist/io/decompress.js +12 -7
  35. package/dist/io/export.js +20 -16
  36. package/dist/io/is-binary.js +19 -4
  37. package/dist/isosurface/parse.js +8 -8
  38. package/dist/isosurface/types.js +9 -9
  39. package/dist/layout/InfoTag.svelte +1 -1
  40. package/dist/layout/json-tree/JsonNode.svelte +1 -0
  41. package/dist/layout/json-tree/utils.js +2 -1
  42. package/dist/marching-cubes.js +1 -1
  43. package/dist/math.js +1 -1
  44. package/dist/overlays/CopyButton.svelte +45 -0
  45. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  46. package/dist/overlays/InfoPaneCards.svelte +149 -0
  47. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  48. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +33 -35
  49. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +2 -2
  50. package/dist/phase-diagram/PhaseDiagramControls.svelte +27 -29
  51. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +2 -2
  52. package/dist/phase-diagram/parse.js +3 -3
  53. package/dist/phase-diagram/svg-to-diagram.js +10 -12
  54. package/dist/plot/BarPlot.svelte +24 -15
  55. package/dist/plot/BarPlot.svelte.d.ts +3 -2
  56. package/dist/plot/FillArea.svelte +2 -3
  57. package/dist/plot/FillArea.svelte.d.ts +3 -2
  58. package/dist/plot/Histogram.svelte +37 -19
  59. package/dist/plot/Line.svelte +2 -3
  60. package/dist/plot/Line.svelte.d.ts +2 -2
  61. package/dist/plot/PlotLegend.svelte +79 -8
  62. package/dist/plot/PlotLegend.svelte.d.ts +4 -0
  63. package/dist/plot/PortalSelect.svelte +5 -5
  64. package/dist/plot/ScatterPlot.svelte +47 -33
  65. package/dist/plot/ScatterPlot.svelte.d.ts +5 -4
  66. package/dist/plot/ScatterPlot3D.svelte +6 -3
  67. package/dist/plot/ScatterPoint.svelte +10 -4
  68. package/dist/plot/ScatterPoint.svelte.d.ts +4 -2
  69. package/dist/plot/SpacegroupBarPlot.svelte +5 -4
  70. package/dist/plot/data-cleaning.js +9 -9
  71. package/dist/plot/index.d.ts +0 -6
  72. package/dist/plot/scales.d.ts +3 -3
  73. package/dist/plot/scales.js +29 -29
  74. package/dist/plot/types.d.ts +5 -9
  75. package/dist/rdf/calc-rdf.js +1 -1
  76. package/dist/sanitize.js +22 -15
  77. package/dist/settings.d.ts +2 -0
  78. package/dist/settings.js +12 -3
  79. package/dist/spectral/Bands.svelte +6 -6
  80. package/dist/spectral/BandsAndDos.svelte +4 -4
  81. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  82. package/dist/spectral/Dos.svelte +2 -2
  83. package/dist/spectral/helpers.js +1 -1
  84. package/dist/structure/AtomLegend.svelte +4 -4
  85. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  86. package/dist/structure/Cylinder.svelte +7 -7
  87. package/dist/structure/Structure.svelte +169 -27
  88. package/dist/structure/Structure.svelte.d.ts +6 -2
  89. package/dist/structure/StructureControls.svelte +130 -16
  90. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  91. package/dist/structure/StructureInfoPane.svelte +519 -218
  92. package/dist/structure/StructureInfoPane.svelte.d.ts +2 -1
  93. package/dist/structure/StructureScene.svelte +399 -68
  94. package/dist/structure/StructureScene.svelte.d.ts +8 -4
  95. package/dist/structure/atom-properties.js +3 -1
  96. package/dist/structure/bond-order-perception.d.ts +13 -0
  97. package/dist/structure/bond-order-perception.js +367 -0
  98. package/dist/structure/bonding.d.ts +10 -1
  99. package/dist/structure/bonding.js +232 -11
  100. package/dist/structure/export.js +6 -4
  101. package/dist/structure/index.d.ts +19 -4
  102. package/dist/structure/index.js +3 -0
  103. package/dist/structure/label-placement.d.ts +14 -0
  104. package/dist/structure/label-placement.js +72 -0
  105. package/dist/structure/parse.d.ts +2 -1
  106. package/dist/structure/parse.js +25 -36
  107. package/dist/structure/supercell.js +35 -2
  108. package/dist/symmetry/SymmetryStats.svelte +1 -1
  109. package/dist/symmetry/cell-transform.js +15 -1
  110. package/dist/symmetry/index.js +3 -3
  111. package/dist/table/HeatmapTable.svelte +3 -3
  112. package/dist/table/ToggleMenu.svelte +1 -1
  113. package/dist/trajectory/Trajectory.svelte +2 -2
  114. package/dist/trajectory/TrajectoryInfoPane.svelte +14 -88
  115. package/dist/trajectory/extract.js +4 -4
  116. package/dist/trajectory/frame-reader.js +2 -2
  117. package/dist/trajectory/parse/ase.js +2 -6
  118. package/dist/trajectory/parse/hdf5.js +1 -3
  119. package/dist/trajectory/plotting.js +1 -1
  120. package/dist/utils.js +1 -1
  121. package/dist/xrd/calc-xrd.js +1 -1
  122. package/package.json +23 -38
  123. package/dist/structure/ferrox-wasm-types.d.ts +0 -46
  124. package/dist/structure/ferrox-wasm-types.js +0 -18
  125. package/dist/structure/ferrox-wasm.d.ts +0 -94
  126. package/dist/structure/ferrox-wasm.js +0 -249
@@ -37,7 +37,9 @@ const get_all_offsets = (pbc) => [-1, 0, 1]
37
37
  (pbc[2] || dz === 0));
38
38
  const make_categorical = (vals, scale, sort_fn) => {
39
39
  const interp_fn = get_interpolator(scale);
40
- const uniq = sort_fn ? [...new Set(vals)].sort(sort_fn) : [...new Set(vals)].sort();
40
+ const uniq = sort_fn
41
+ ? [...new Set(vals)].sort(sort_fn)
42
+ : [...new Set(vals)].sort((val_a, val_b) => String(val_a).localeCompare(String(val_b)));
41
43
  const colors = uniq.map((_, idx) => to_hex(interp_fn, uniq.length === 1 ? 0.5 : idx / (uniq.length - 1)));
42
44
  const map = new Map(uniq.map((val, idx) => [val, colors[idx]]));
43
45
  return {
@@ -0,0 +1,13 @@
1
+ import type { BondOrder, BondPair, Site, StructureBond } from './';
2
+ export type PerceptionOptions = {
3
+ total_charge?: number;
4
+ max_atoms?: number;
5
+ };
6
+ export type PerceivedBond = BondPair & {
7
+ bond_order: BondOrder;
8
+ perceived: boolean;
9
+ aromatic_ring?: number;
10
+ kekule_order?: BondOrder;
11
+ };
12
+ export declare function perceive_bond_orders(sites: Site[], bonds: BondPair[], opts?: PerceptionOptions): PerceivedBond[];
13
+ export declare function compose_perceived_bonds(perceived: PerceivedBond[], explicit_bonds: StructureBond[], aromatic_display: `aromatic` | `kekule`): PerceivedBond[];
@@ -0,0 +1,367 @@
1
+ import { get_bond_key } from './bonding';
2
+ const primary_element = (site) => {
3
+ return (site.species?.reduce((a, b) => (b.occu > a.occu ? b : a), site.species[0])?.element ?? ``);
4
+ };
5
+ // xyz2mol atomic_valence. Valence combinations are re-sorted by total
6
+ // valence sum so the least-saturated solution is tried first.
7
+ const ATOMIC_VALENCE = {
8
+ H: [1],
9
+ B: [3, 4],
10
+ C: [4],
11
+ N: [3, 4],
12
+ O: [2, 1, 3],
13
+ F: [1],
14
+ Si: [4],
15
+ P: [5, 3],
16
+ S: [6, 3, 2],
17
+ Cl: [1],
18
+ Se: [6, 3, 2],
19
+ Br: [1],
20
+ Te: [6, 3, 2],
21
+ I: [1],
22
+ };
23
+ // xyz2mol atomic_valence_electrons (group valence-electron count).
24
+ const VALENCE_ELECTRONS = {
25
+ H: 1,
26
+ B: 3,
27
+ C: 4,
28
+ N: 5,
29
+ O: 6,
30
+ F: 7,
31
+ Si: 4,
32
+ P: 5,
33
+ S: 6,
34
+ Cl: 7,
35
+ Se: 6,
36
+ Br: 7,
37
+ Te: 6,
38
+ I: 7,
39
+ };
40
+ // xyz2mol get_atomic_charge: formal charge from the atom's actual bond
41
+ // valence. H/B use (group-1-like) special forms; hypervalent P(V) and
42
+ // S(VI) are treated as neutral; everything else is group - 8 + valence.
43
+ function formal_charge(symbol, bond_valence) {
44
+ if (symbol === `H`)
45
+ return 1 - bond_valence;
46
+ if (symbol === `B`)
47
+ return 3 - bond_valence;
48
+ if (symbol === `P` && bond_valence === 5)
49
+ return 0;
50
+ if (symbol === `S` && bond_valence === 6)
51
+ return 0;
52
+ return VALENCE_ELECTRONS[symbol] - 8 + bond_valence;
53
+ }
54
+ function is_main_group(symbol) {
55
+ return symbol in ATOMIC_VALENCE;
56
+ }
57
+ // Cap per-fragment valence enumeration (3^k for catenated S/Se/Te/P chains).
58
+ const MAX_VALENCE_COMBOS = 4096;
59
+ function split_fragments(n_atoms, edges) {
60
+ 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`);
64
+ }
65
+ adjacency[a].push(b);
66
+ adjacency[b].push(a);
67
+ }
68
+ const seen = new Set();
69
+ const fragments = [];
70
+ for (let start = 0; start < n_atoms; start++) {
71
+ if (seen.has(start))
72
+ continue;
73
+ const stack = [start];
74
+ const frag = [];
75
+ seen.add(start);
76
+ while (stack.length) {
77
+ const node = stack.pop();
78
+ if (node === undefined)
79
+ break;
80
+ frag.push(node);
81
+ for (const nb of adjacency[node]) {
82
+ if (!seen.has(nb)) {
83
+ seen.add(nb);
84
+ stack.push(nb);
85
+ }
86
+ }
87
+ }
88
+ fragments.push(frag);
89
+ }
90
+ return fragments;
91
+ }
92
+ // Enumerate one target valence per atom, all combinations, lowest total
93
+ // valence-sum first (xyz2mol prefers the least-saturated solution).
94
+ function* valence_combinations(valence_lists) {
95
+ const combos = [];
96
+ const rec = (pos, acc) => {
97
+ if (pos === valence_lists.length) {
98
+ combos.push({ sum: acc.reduce((s, v) => s + v, 0), pick: [...acc] });
99
+ return;
100
+ }
101
+ for (const v of valence_lists[pos])
102
+ rec(pos + 1, [...acc, v]);
103
+ };
104
+ rec(0, []);
105
+ combos.sort((a, b) => a.sum - b.sum);
106
+ for (const c of combos)
107
+ yield c.pick;
108
+ }
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
+ // Greedily raise bond orders toward each atom's target valence; succeeds
111
+ // only if every atom's used valence ends exactly at its target.
112
+ function assign_bond_orders(edges, target_valence) {
113
+ const orders = Array.from({ length: edges.length }, () => 1);
114
+ const used = (atom) => atom_valence(atom, edges, orders);
115
+ let progressed = true;
116
+ while (progressed) {
117
+ progressed = false;
118
+ let best = -1;
119
+ 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;
127
+ }
128
+ });
129
+ if (best >= 0) {
130
+ orders[best] += 1;
131
+ progressed = true;
132
+ }
133
+ }
134
+ for (let atom_idx = 0; atom_idx < target_valence.length; atom_idx++) {
135
+ if (used(atom_idx) !== target_valence[atom_idx])
136
+ return null;
137
+ }
138
+ return orders;
139
+ }
140
+ // 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`);
146
+ }
147
+ adjacency[a].add(b);
148
+ adjacency[b].add(a);
149
+ }
150
+ const parent = Array.from({ length: n }, () => -1);
151
+ const seen = new Set();
152
+ const rings = [];
153
+ for (let s = 0; s < n; s++) {
154
+ if (seen.has(s))
155
+ continue;
156
+ const queue = [s];
157
+ let queue_idx = 0;
158
+ seen.add(s);
159
+ parent[s] = -1;
160
+ 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);
167
+ }
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()];
181
+ if (ring.length >= 3)
182
+ rings.push(ring);
183
+ break;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ const uniq = new Map();
191
+ for (const r of rings) {
192
+ const key = [...r].sort((x, y) => x - y).join(`,`);
193
+ if (!uniq.has(key))
194
+ uniq.set(key, r);
195
+ }
196
+ return [...uniq.values()];
197
+ }
198
+ function order_to_bond_order(o) {
199
+ return o >= 3 ? 3 : o === 2 ? 2 : 1;
200
+ }
201
+ // Conservative planarity check: degenerate first-3-atom planes are non-planar.
202
+ function ring_is_planar(ring, sites) {
203
+ if (ring.length < 3)
204
+ 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]];
208
+ const nx = v1[1] * v2[2] - v1[2] * v2[1];
209
+ const ny = v1[2] * v2[0] - v1[0] * v2[2];
210
+ const nz = v1[0] * v2[1] - v1[1] * v2[0];
211
+ const len = Math.hypot(nx, ny, nz);
212
+ if (len < 1e-6)
213
+ 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;
216
+ return dev < 0.3;
217
+ });
218
+ }
219
+ // Elements that can sit in an aromatic ring contributing a p-orbital to
220
+ // the conjugated π system (C, N, O, S). Other ring members disqualify.
221
+ const SP2_OK = new Set([`C`, `N`, `O`, `S`]);
222
+ // xyz2mol AC->BO core (neutral, main-group). Processes each connected
223
+ // fragment independently; fragments containing a non-main-group atom or
224
+ // with no valence-consistent assignment fall back to single + not perceived.
225
+ export function perceive_bond_orders(sites, bonds, opts = {}) {
226
+ const max_atoms = opts.max_atoms ?? 5000;
227
+ if (sites.length > max_atoms) {
228
+ return bonds.map((b) => ({ ...b, bond_order: 1, perceived: false }));
229
+ }
230
+ const edges = [];
231
+ const result = new Map();
232
+ for (const bond of bonds) {
233
+ result.set(bond, { ...bond, bond_order: 1, perceived: false });
234
+ if (bond.site_idx_1 < 0 ||
235
+ bond.site_idx_2 < 0 ||
236
+ bond.site_idx_1 >= sites.length ||
237
+ bond.site_idx_2 >= sites.length)
238
+ continue;
239
+ edges.push({
240
+ i: bond.site_idx_1,
241
+ j: bond.site_idx_2,
242
+ ref: bond,
243
+ });
244
+ }
245
+ const frags = split_fragments(sites.length, edges.map((e) => [e.i, e.j]));
246
+ let ring_id = 0;
247
+ for (const frag of frags) {
248
+ 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]));
251
+ if (!symbols.every(is_main_group))
252
+ continue;
253
+ const idx_of = Array.from({ length: sites.length }, () => -1);
254
+ frag.forEach((site_idx, local_idx) => {
255
+ idx_of[site_idx] = local_idx;
256
+ });
257
+ const local_edges = frag_edges.map((e) => ({
258
+ i: idx_of[e.i],
259
+ j: idx_of[e.j],
260
+ ref: e.ref,
261
+ }));
262
+ const valence_lists = symbols.map((s) => ATOMIC_VALENCE[s]);
263
+ const combo_count = valence_lists.reduce((p, l) => p * l.length, 1);
264
+ if (combo_count > MAX_VALENCE_COMBOS)
265
+ continue;
266
+ let solved = null;
267
+ const want_charge = opts.total_charge ?? 0;
268
+ for (const target of valence_combinations(valence_lists)) {
269
+ const candidate = assign_bond_orders(local_edges, target);
270
+ if (!candidate)
271
+ continue;
272
+ 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));
275
+ }
276
+ if (sum_fc === want_charge) {
277
+ solved = candidate;
278
+ break;
279
+ }
280
+ }
281
+ if (!solved)
282
+ 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 });
286
+ });
287
+ // 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]));
289
+ 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]))))
292
+ continue;
293
+ if (!ring_is_planar(global_ring, sites))
294
+ continue;
295
+ const ring_set = new Set(ring);
296
+ 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);
301
+ }
302
+ });
303
+ const has_any_multiple_bond = (atom_idx) => local_edges.some((edge, edge_idx) => {
304
+ if (edge.i !== atom_idx && edge.j !== atom_idx)
305
+ return false;
306
+ return solved[edge_idx] > 1;
307
+ });
308
+ const has_non_ring_neighbor = (atom_idx) => local_edges.some((edge) => {
309
+ if (edge.i !== atom_idx && edge.j !== atom_idx)
310
+ return false;
311
+ const neighbor_idx = edge.i === atom_idx ? edge.j : edge.i;
312
+ return !ring_set.has(neighbor_idx);
313
+ });
314
+ const pi_by_atom = ring.map((atom_idx) => {
315
+ const element = primary_element(sites[frag[atom_idx]]);
316
+ if (has_ring_multiple.has(atom_idx))
317
+ return 1;
318
+ if (element === `N` || element === `O` || element === `S`)
319
+ return 2;
320
+ if (element === `C`)
321
+ return Number(has_any_multiple_bond(atom_idx) || !has_non_ring_neighbor(atom_idx));
322
+ return 0;
323
+ });
324
+ const pi = pi_by_atom.reduce((sum, val) => sum + val, 0);
325
+ if (pi_by_atom.every((val) => val > 0) && pi >= 2 && (pi - 2) % 4 === 0) {
326
+ 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);
330
+ if (prev === undefined)
331
+ throw new Error(`Missing perceived bond`);
332
+ result.set(e.ref, {
333
+ ...prev,
334
+ bond_order: `aromatic`,
335
+ aromatic_ring: this_ring,
336
+ kekule_order: prev.kekule_order ?? (prev.bond_order === 1 ? 1 : 2),
337
+ perceived: true,
338
+ });
339
+ }
340
+ });
341
+ }
342
+ }
343
+ }
344
+ return bonds.map((bond) => {
345
+ const perceived = result.get(bond);
346
+ if (perceived === undefined)
347
+ throw new Error(`Missing perceived bond`);
348
+ return perceived;
349
+ });
350
+ }
351
+ // Final display order: explicit bonds win, then Kekulé mode, then perception.
352
+ export function compose_perceived_bonds(perceived, explicit_bonds, aromatic_display) {
353
+ const explicit_orders = new Map(explicit_bonds.map((bond) => [
354
+ get_bond_key(bond.site_idx_1, bond.site_idx_2, bond.cell_shift),
355
+ bond.order,
356
+ ]));
357
+ return perceived.map((bond) => {
358
+ const explicit = explicit_orders.get(get_bond_key(bond.site_idx_1, bond.site_idx_2, bond.cell_shift));
359
+ if (explicit !== undefined)
360
+ return { ...bond, bond_order: explicit };
361
+ if (aromatic_display === `kekule` &&
362
+ bond.bond_order === `aromatic` &&
363
+ bond.kekule_order !== undefined)
364
+ return { ...bond, bond_order: bond.kekule_order };
365
+ return bond;
366
+ });
367
+ }
@@ -1,5 +1,14 @@
1
1
  import type { Vec3 } from '../math';
2
- import type { AnyStructure, BondPair } from './';
2
+ import type { AnyStructure, BondOrder, BondPair, StructureBond } from './';
3
+ export declare const normalize_structure_bond: (site_idx_1: number, site_idx_2: number, order: BondOrder, cell_shift?: Vec3) => StructureBond;
4
+ export declare const get_bond_key: (idx_1: number, idx_2: number, cell_shift?: Vec3) => string;
5
+ export declare const merge_bond_edits: (base_bonds: StructureBond[], added: StructureBond[], removed: StructureBond[], overrides: StructureBond[]) => StructureBond[];
6
+ export declare function normalize_bond_order(order: unknown): BondOrder | null;
7
+ export declare function structure_bond_to_bond_pair(structure: AnyStructure, bond: StructureBond): BondPair;
8
+ export declare function get_explicit_bond_metadata(structure: AnyStructure): StructureBond[];
9
+ export declare function apply_explicit_bond_metadata(structure: AnyStructure, bonds: BondPair[]): BondPair[];
10
+ export declare function scale_and_offset_bond_matrix(transform_matrix: Float32Array, offset: number, radius_scale: number): Float32Array;
11
+ export declare function get_bond_render_matrices(bond: BondPair, bond_thickness: number): Float32Array[];
3
12
  export declare function compute_bond_transform(pos_1: Vec3, pos_2: Vec3): Float32Array;
4
13
  export declare const BONDING_STRATEGIES: {
5
14
  readonly electroneg_ratio: typeof electroneg_ratio;