@johnhenry/math 0.0.0

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 (242) hide show
  1. package/README.md +165 -0
  2. package/dist/Bivector4.d.ts +52 -0
  3. package/dist/Bivector4.d.ts.map +1 -0
  4. package/dist/Bivector4.js +105 -0
  5. package/dist/Bivector4.js.map +1 -0
  6. package/dist/CellGraph.d.ts +259 -0
  7. package/dist/CellGraph.d.ts.map +1 -0
  8. package/dist/CellGraph.js +637 -0
  9. package/dist/CellGraph.js.map +1 -0
  10. package/dist/Clifford4Internal.d.ts +35 -0
  11. package/dist/Clifford4Internal.d.ts.map +1 -0
  12. package/dist/Clifford4Internal.js +86 -0
  13. package/dist/Clifford4Internal.js.map +1 -0
  14. package/dist/Combinatorics.d.ts +61 -0
  15. package/dist/Combinatorics.d.ts.map +1 -0
  16. package/dist/Combinatorics.js +186 -0
  17. package/dist/Combinatorics.js.map +1 -0
  18. package/dist/CombinatoricsGenerators.d.ts +44 -0
  19. package/dist/CombinatoricsGenerators.d.ts.map +1 -0
  20. package/dist/CombinatoricsGenerators.js +148 -0
  21. package/dist/CombinatoricsGenerators.js.map +1 -0
  22. package/dist/ComplexMath.d.ts +46 -0
  23. package/dist/ComplexMath.d.ts.map +1 -0
  24. package/dist/ComplexMath.js +98 -0
  25. package/dist/ComplexMath.js.map +1 -0
  26. package/dist/ComplexNumber.d.ts +174 -0
  27. package/dist/ComplexNumber.d.ts.map +1 -0
  28. package/dist/ComplexNumber.js +443 -0
  29. package/dist/ComplexNumber.js.map +1 -0
  30. package/dist/Cycle.d.ts +42 -0
  31. package/dist/Cycle.d.ts.map +1 -0
  32. package/dist/Cycle.js +120 -0
  33. package/dist/Cycle.js.map +1 -0
  34. package/dist/Decimal.d.ts +45 -0
  35. package/dist/Decimal.d.ts.map +1 -0
  36. package/dist/Decimal.js +135 -0
  37. package/dist/Decimal.js.map +1 -0
  38. package/dist/Distributions.d.ts +55 -0
  39. package/dist/Distributions.d.ts.map +1 -0
  40. package/dist/Distributions.js +222 -0
  41. package/dist/Distributions.js.map +1 -0
  42. package/dist/DualNumber.d.ts +42 -0
  43. package/dist/DualNumber.d.ts.map +1 -0
  44. package/dist/DualNumber.js +91 -0
  45. package/dist/DualNumber.js.map +1 -0
  46. package/dist/Environment.d.ts +44 -0
  47. package/dist/Environment.d.ts.map +1 -0
  48. package/dist/Environment.js +129 -0
  49. package/dist/Environment.js.map +1 -0
  50. package/dist/Expression.d.ts +19 -0
  51. package/dist/Expression.d.ts.map +1 -0
  52. package/dist/Expression.js +43 -0
  53. package/dist/Expression.js.map +1 -0
  54. package/dist/FFT.d.ts +21 -0
  55. package/dist/FFT.d.ts.map +1 -0
  56. package/dist/FFT.js +136 -0
  57. package/dist/FFT.js.map +1 -0
  58. package/dist/GeometricAlgebra4.d.ts +73 -0
  59. package/dist/GeometricAlgebra4.d.ts.map +1 -0
  60. package/dist/GeometricAlgebra4.js +115 -0
  61. package/dist/GeometricAlgebra4.js.map +1 -0
  62. package/dist/Geometry.d.ts +41 -0
  63. package/dist/Geometry.d.ts.map +1 -0
  64. package/dist/Geometry.js +167 -0
  65. package/dist/Geometry.js.map +1 -0
  66. package/dist/Graph.d.ts +72 -0
  67. package/dist/Graph.d.ts.map +1 -0
  68. package/dist/Graph.js +337 -0
  69. package/dist/Graph.js.map +1 -0
  70. package/dist/Graph3DUtils.d.ts +80 -0
  71. package/dist/Graph3DUtils.d.ts.map +1 -0
  72. package/dist/Graph3DUtils.js +156 -0
  73. package/dist/Graph3DUtils.js.map +1 -0
  74. package/dist/GraphUtils.d.ts +67 -0
  75. package/dist/GraphUtils.d.ts.map +1 -0
  76. package/dist/GraphUtils.js +72 -0
  77. package/dist/GraphUtils.js.map +1 -0
  78. package/dist/GroupTheory.d.ts +73 -0
  79. package/dist/GroupTheory.d.ts.map +1 -0
  80. package/dist/GroupTheory.js +194 -0
  81. package/dist/GroupTheory.js.map +1 -0
  82. package/dist/IntUtils.d.ts +33 -0
  83. package/dist/IntUtils.d.ts.map +1 -0
  84. package/dist/IntUtils.js +185 -0
  85. package/dist/IntUtils.js.map +1 -0
  86. package/dist/Interval.d.ts +44 -0
  87. package/dist/Interval.d.ts.map +1 -0
  88. package/dist/Interval.js +165 -0
  89. package/dist/Interval.js.map +1 -0
  90. package/dist/Lattice.d.ts +48 -0
  91. package/dist/Lattice.d.ts.map +1 -0
  92. package/dist/Lattice.js +65 -0
  93. package/dist/Lattice.js.map +1 -0
  94. package/dist/Logic.d.ts +22 -0
  95. package/dist/Logic.d.ts.map +1 -0
  96. package/dist/Logic.js +34 -0
  97. package/dist/Logic.js.map +1 -0
  98. package/dist/MatrixMath.d.ts +71 -0
  99. package/dist/MatrixMath.d.ts.map +1 -0
  100. package/dist/MatrixMath.js +302 -0
  101. package/dist/MatrixMath.js.map +1 -0
  102. package/dist/NumberTheory.d.ts +49 -0
  103. package/dist/NumberTheory.d.ts.map +1 -0
  104. package/dist/NumberTheory.js +230 -0
  105. package/dist/NumberTheory.js.map +1 -0
  106. package/dist/Numerical.d.ts +85 -0
  107. package/dist/Numerical.d.ts.map +1 -0
  108. package/dist/Numerical.js +343 -0
  109. package/dist/Numerical.js.map +1 -0
  110. package/dist/Permutation.d.ts +43 -0
  111. package/dist/Permutation.d.ts.map +1 -0
  112. package/dist/Permutation.js +142 -0
  113. package/dist/Permutation.js.map +1 -0
  114. package/dist/Polygon.d.ts +64 -0
  115. package/dist/Polygon.d.ts.map +1 -0
  116. package/dist/Polygon.js +221 -0
  117. package/dist/Polygon.js.map +1 -0
  118. package/dist/PolynomialRing.d.ts +74 -0
  119. package/dist/PolynomialRing.d.ts.map +1 -0
  120. package/dist/PolynomialRing.js +231 -0
  121. package/dist/PolynomialRing.js.map +1 -0
  122. package/dist/Quaternion.d.ts +37 -0
  123. package/dist/Quaternion.d.ts.map +1 -0
  124. package/dist/Quaternion.js +106 -0
  125. package/dist/Quaternion.js.map +1 -0
  126. package/dist/Rational.d.ts +34 -0
  127. package/dist/Rational.d.ts.map +1 -0
  128. package/dist/Rational.js +136 -0
  129. package/dist/Rational.js.map +1 -0
  130. package/dist/RealMath.d.ts +112 -0
  131. package/dist/RealMath.d.ts.map +1 -0
  132. package/dist/RealMath.js +262 -0
  133. package/dist/RealMath.js.map +1 -0
  134. package/dist/Rotor4.d.ts +180 -0
  135. package/dist/Rotor4.d.ts.map +1 -0
  136. package/dist/Rotor4.js +388 -0
  137. package/dist/Rotor4.js.map +1 -0
  138. package/dist/SpecialFunctions.d.ts +32 -0
  139. package/dist/SpecialFunctions.d.ts.map +1 -0
  140. package/dist/SpecialFunctions.js +161 -0
  141. package/dist/SpecialFunctions.js.map +1 -0
  142. package/dist/SpecialOperator.d.ts +21 -0
  143. package/dist/SpecialOperator.d.ts.map +1 -0
  144. package/dist/SpecialOperator.js +26 -0
  145. package/dist/SpecialOperator.js.map +1 -0
  146. package/dist/Statistics.d.ts +50 -0
  147. package/dist/Statistics.d.ts.map +1 -0
  148. package/dist/Statistics.js +131 -0
  149. package/dist/Statistics.js.map +1 -0
  150. package/dist/StringEvaluator.d.ts +48 -0
  151. package/dist/StringEvaluator.d.ts.map +1 -0
  152. package/dist/StringEvaluator.js +190 -0
  153. package/dist/StringEvaluator.js.map +1 -0
  154. package/dist/Structure.d.ts +130 -0
  155. package/dist/Structure.d.ts.map +1 -0
  156. package/dist/Structure.js +394 -0
  157. package/dist/Structure.js.map +1 -0
  158. package/dist/Symbolic.d.ts +501 -0
  159. package/dist/Symbolic.d.ts.map +1 -0
  160. package/dist/Symbolic.js +4077 -0
  161. package/dist/Symbolic.js.map +1 -0
  162. package/dist/Type.d.ts +31 -0
  163. package/dist/Type.d.ts.map +1 -0
  164. package/dist/Type.js +47 -0
  165. package/dist/Type.js.map +1 -0
  166. package/dist/Utilities.d.ts +27 -0
  167. package/dist/Utilities.d.ts.map +1 -0
  168. package/dist/Utilities.js +47 -0
  169. package/dist/Utilities.js.map +1 -0
  170. package/dist/Vec4.d.ts +39 -0
  171. package/dist/Vec4.d.ts.map +1 -0
  172. package/dist/Vec4.js +82 -0
  173. package/dist/Vec4.js.map +1 -0
  174. package/dist/Vector.d.ts +83 -0
  175. package/dist/Vector.d.ts.map +1 -0
  176. package/dist/Vector.js +173 -0
  177. package/dist/Vector.js.map +1 -0
  178. package/dist/VectorCalculus.d.ts +42 -0
  179. package/dist/VectorCalculus.d.ts.map +1 -0
  180. package/dist/VectorCalculus.js +82 -0
  181. package/dist/VectorCalculus.js.map +1 -0
  182. package/dist/VectorUtils.d.ts +172 -0
  183. package/dist/VectorUtils.d.ts.map +1 -0
  184. package/dist/VectorUtils.js +706 -0
  185. package/dist/VectorUtils.js.map +1 -0
  186. package/dist/differentiation-rules.d.ts +21 -0
  187. package/dist/differentiation-rules.d.ts.map +1 -0
  188. package/dist/differentiation-rules.js +52 -0
  189. package/dist/differentiation-rules.js.map +1 -0
  190. package/dist/index.d.ts +52 -0
  191. package/dist/index.d.ts.map +1 -0
  192. package/dist/index.js +66 -0
  193. package/dist/index.js.map +1 -0
  194. package/package.json +68 -0
  195. package/src/Bivector4.ts +136 -0
  196. package/src/CellGraph.ts +669 -0
  197. package/src/Clifford4Internal.ts +90 -0
  198. package/src/Combinatorics.ts +183 -0
  199. package/src/CombinatoricsGenerators.ts +144 -0
  200. package/src/ComplexMath.ts +162 -0
  201. package/src/ComplexNumber.ts +491 -0
  202. package/src/Cycle.ts +126 -0
  203. package/src/Decimal.ts +145 -0
  204. package/src/Distributions.ts +269 -0
  205. package/src/DualNumber.ts +105 -0
  206. package/src/Environment.ts +127 -0
  207. package/src/Expression.ts +48 -0
  208. package/src/FFT.ts +146 -0
  209. package/src/GeometricAlgebra4.ts +157 -0
  210. package/src/Geometry.ts +173 -0
  211. package/src/Graph.ts +337 -0
  212. package/src/Graph3DUtils.ts +269 -0
  213. package/src/GraphUtils.ts +159 -0
  214. package/src/GroupTheory.ts +214 -0
  215. package/src/IntUtils.ts +200 -0
  216. package/src/Interval.ts +171 -0
  217. package/src/Lattice.ts +87 -0
  218. package/src/Logic.ts +38 -0
  219. package/src/MatrixMath.ts +326 -0
  220. package/src/NumberTheory.ts +228 -0
  221. package/src/Numerical.ts +390 -0
  222. package/src/Permutation.ts +148 -0
  223. package/src/Polygon.ts +229 -0
  224. package/src/PolynomialRing.ts +242 -0
  225. package/src/Quaternion.ts +125 -0
  226. package/src/Rational.ts +148 -0
  227. package/src/RealMath.ts +346 -0
  228. package/src/Rotor4.ts +440 -0
  229. package/src/SpecialFunctions.ts +152 -0
  230. package/src/SpecialOperator.ts +28 -0
  231. package/src/Statistics.ts +170 -0
  232. package/src/StringEvaluator.ts +208 -0
  233. package/src/Structure.ts +481 -0
  234. package/src/Symbolic.ts +4313 -0
  235. package/src/Type.ts +48 -0
  236. package/src/Utilities.ts +49 -0
  237. package/src/Vec4.ts +98 -0
  238. package/src/Vector.ts +189 -0
  239. package/src/VectorCalculus.ts +87 -0
  240. package/src/VectorUtils.ts +797 -0
  241. package/src/differentiation-rules.ts +73 -0
  242. package/src/index.ts +140 -0
package/src/Graph.ts ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Graph — a weighted (optionally directed) graph with the standard algorithms:
3
+ * breadth/depth-first traversal, Dijkstra shortest paths, minimum spanning tree
4
+ * (Kruskal), topological sort, connected components, cycle detection, and
5
+ * conversion to an adjacency matrix (feeding {@link MatrixMath}) with
6
+ * Floyd-Warshall all-pairs distances.
7
+ */
8
+
9
+ export interface Edge<T> {
10
+ from: T;
11
+ to: T;
12
+ weight: number;
13
+ }
14
+
15
+ /**
16
+ * Minimal binary min-heap keyed by a numeric priority, used by
17
+ * {@link Graph.dijkstra} / {@link Graph.shortestPath} to pick the next
18
+ * closest unvisited vertex in O(log n) instead of a linear scan. Supports
19
+ * duplicate/stale entries (an item can be pushed more than once when its
20
+ * distance improves); the caller is expected to skip entries whose stored
21
+ * priority no longer matches the current best-known distance ("lazy
22
+ * deletion"), which is cheaper than a decrease-key operation.
23
+ */
24
+ class MinHeap<T> {
25
+ private readonly items: Array<{ priority: number; value: T }> = [];
26
+
27
+ get size(): number {
28
+ return this.items.length;
29
+ }
30
+
31
+ push(value: T, priority: number): void {
32
+ this.items.push({ priority, value });
33
+ let i = this.items.length - 1;
34
+ while (i > 0) {
35
+ const parent = (i - 1) >> 1;
36
+ if (this.items[parent].priority <= this.items[i].priority) break;
37
+ [this.items[parent], this.items[i]] = [this.items[i], this.items[parent]];
38
+ i = parent;
39
+ }
40
+ }
41
+
42
+ pop(): { priority: number; value: T } | undefined {
43
+ const top = this.items[0];
44
+ if (top === undefined) return undefined;
45
+ const last = this.items.pop() as { priority: number; value: T };
46
+ if (this.items.length) {
47
+ this.items[0] = last;
48
+ let i = 0;
49
+ const n = this.items.length;
50
+ for (;;) {
51
+ const left = 2 * i + 1;
52
+ const right = 2 * i + 2;
53
+ let smallest = i;
54
+ if (left < n && this.items[left].priority < this.items[smallest].priority) smallest = left;
55
+ if (right < n && this.items[right].priority < this.items[smallest].priority) smallest = right;
56
+ if (smallest === i) break;
57
+ [this.items[smallest], this.items[i]] = [this.items[i], this.items[smallest]];
58
+ i = smallest;
59
+ }
60
+ }
61
+ return top;
62
+ }
63
+ }
64
+
65
+ export class Graph<T = string | number> {
66
+ readonly directed: boolean;
67
+ private readonly adjacency = new Map<T, Map<T, number>>();
68
+
69
+ constructor(directed = false) {
70
+ this.directed = directed;
71
+ }
72
+
73
+ addVertex(v: T): this {
74
+ if (!this.adjacency.has(v)) this.adjacency.set(v, new Map());
75
+ return this;
76
+ }
77
+
78
+ addEdge(u: T, v: T, weight = 1): this {
79
+ this.addVertex(u).addVertex(v);
80
+ (this.adjacency.get(u) as Map<T, number>).set(v, weight);
81
+ if (!this.directed) (this.adjacency.get(v) as Map<T, number>).set(u, weight);
82
+ return this;
83
+ }
84
+
85
+ vertices(): T[] {
86
+ return [...this.adjacency.keys()];
87
+ }
88
+
89
+ neighbors(v: T): T[] {
90
+ return [...(this.adjacency.get(v)?.keys() ?? [])];
91
+ }
92
+
93
+ edges(): Array<Edge<T>> {
94
+ const out: Array<Edge<T>> = [];
95
+ const seen = new Set<string>();
96
+ for (const [from, nbrs] of this.adjacency) {
97
+ for (const [to, weight] of nbrs) {
98
+ if (!this.directed) {
99
+ const key = JSON.stringify([from, to].map(String).sort());
100
+ if (seen.has(key)) continue;
101
+ seen.add(key);
102
+ }
103
+ out.push({ from, to, weight });
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+
109
+ /** Breadth-first traversal order from `start`. */
110
+ bfs(start: T): T[] {
111
+ const visited = new Set<T>([start]);
112
+ const order: T[] = [];
113
+ const queue: T[] = [start];
114
+ while (queue.length) {
115
+ const v = queue.shift() as T;
116
+ order.push(v);
117
+ for (const n of this.neighbors(v))
118
+ if (!visited.has(n)) {
119
+ visited.add(n);
120
+ queue.push(n);
121
+ }
122
+ }
123
+ return order;
124
+ }
125
+
126
+ /** Depth-first traversal order from `start`. */
127
+ dfs(start: T): T[] {
128
+ const visited = new Set<T>();
129
+ const order: T[] = [];
130
+ const visit = (v: T) => {
131
+ visited.add(v);
132
+ order.push(v);
133
+ for (const n of this.neighbors(v)) if (!visited.has(n)) visit(n);
134
+ };
135
+ visit(start);
136
+ return order;
137
+ }
138
+
139
+ /**
140
+ * Shared Dijkstra core used by {@link dijkstra} and {@link shortestPath}:
141
+ * a binary min-heap keyed on tentative distance replaces the O(V) linear
142
+ * scan for "closest unvisited vertex", bringing the algorithm from
143
+ * O(V²) down to the standard O((V+E) log V). Stale heap entries (pushed
144
+ * when a shorter distance was later found) are skipped lazily instead of
145
+ * removed, which is cheaper than a decrease-key operation.
146
+ */
147
+ private runDijkstra(start: T): { dist: Map<T, number>; prev: Map<T, T> } {
148
+ const dist = new Map<T, number>();
149
+ const prev = new Map<T, T>();
150
+ for (const v of this.vertices()) dist.set(v, Number.POSITIVE_INFINITY);
151
+ dist.set(start, 0);
152
+ const visited = new Set<T>();
153
+ const heap = new MinHeap<T>();
154
+ heap.push(start, 0);
155
+ while (heap.size) {
156
+ const top = heap.pop() as { priority: number; value: T };
157
+ const u = top.value;
158
+ if (visited.has(u)) continue;
159
+ if (top.priority > (dist.get(u) as number)) continue; // stale entry
160
+ visited.add(u);
161
+ for (const [n, w] of this.adjacency.get(u) ?? []) {
162
+ if (visited.has(n)) continue;
163
+ const nd = (dist.get(u) as number) + w;
164
+ if (nd < (dist.get(n) as number)) {
165
+ dist.set(n, nd);
166
+ prev.set(n, u);
167
+ heap.push(n, nd);
168
+ }
169
+ }
170
+ }
171
+ return { dist, prev };
172
+ }
173
+
174
+ /** Dijkstra shortest-path distances from `start` (non-negative weights). */
175
+ dijkstra(start: T): Map<T, number> {
176
+ return this.runDijkstra(start).dist;
177
+ }
178
+
179
+ /** The shortest path from `start` to `end` as `{ distance, path }`. */
180
+ shortestPath(start: T, end: T): { distance: number; path: T[] } {
181
+ const { dist, prev } = this.runDijkstra(start);
182
+ if (dist.get(end) === Number.POSITIVE_INFINITY) return { distance: Number.POSITIVE_INFINITY, path: [] };
183
+ const path: T[] = [];
184
+ let cur: T | undefined = end;
185
+ while (cur !== undefined) {
186
+ path.unshift(cur);
187
+ cur = cur === start ? undefined : prev.get(cur);
188
+ }
189
+ return { distance: dist.get(end) as number, path };
190
+ }
191
+
192
+ /** Connected components (undirected graphs). */
193
+ connectedComponents(): T[][] {
194
+ const seen = new Set<T>();
195
+ const components: T[][] = [];
196
+ for (const v of this.vertices()) {
197
+ if (seen.has(v)) continue;
198
+ const comp = this.bfs(v);
199
+ for (const c of comp) seen.add(c);
200
+ components.push(comp);
201
+ }
202
+ return components;
203
+ }
204
+
205
+ /** Topological order for a DAG, or `null` if the graph has a cycle. */
206
+ topologicalSort(): T[] | null {
207
+ const inDegree = new Map<T, number>();
208
+ for (const v of this.vertices()) inDegree.set(v, 0);
209
+ for (const [, nbrs] of this.adjacency) for (const [to] of nbrs) inDegree.set(to, (inDegree.get(to) as number) + 1);
210
+ const queue = this.vertices().filter((v) => inDegree.get(v) === 0);
211
+ const order: T[] = [];
212
+ while (queue.length) {
213
+ const v = queue.shift() as T;
214
+ order.push(v);
215
+ for (const n of this.neighbors(v)) {
216
+ inDegree.set(n, (inDegree.get(n) as number) - 1);
217
+ if (inDegree.get(n) === 0) queue.push(n);
218
+ }
219
+ }
220
+ return order.length === this.vertices().length ? order : null;
221
+ }
222
+
223
+ /** True when the graph contains a cycle. */
224
+ hasCycle(): boolean {
225
+ if (this.directed) return this.topologicalSort() === null;
226
+ const seen = new Set<T>();
227
+ const visit = (v: T, parent: T | null): boolean => {
228
+ seen.add(v);
229
+ for (const n of this.neighbors(v)) {
230
+ if (!seen.has(n)) {
231
+ if (visit(n, v)) return true;
232
+ } else if (n !== parent) return true;
233
+ }
234
+ return false;
235
+ };
236
+ for (const v of this.vertices()) if (!seen.has(v) && visit(v, null)) return true;
237
+ return false;
238
+ }
239
+
240
+ /** Minimum spanning tree (Kruskal) of an undirected graph, as a list of edges. */
241
+ minimumSpanningTree(): Array<Edge<T>> {
242
+ const parent = new Map<T, T>();
243
+ const find = (x: T): T => {
244
+ while (parent.get(x) !== x) {
245
+ parent.set(x, parent.get(parent.get(x) as T) as T);
246
+ x = parent.get(x) as T;
247
+ }
248
+ return x;
249
+ };
250
+ for (const v of this.vertices()) parent.set(v, v);
251
+ const mst: Array<Edge<T>> = [];
252
+ for (const e of this.edges().sort((a, b) => a.weight - b.weight)) {
253
+ const ra = find(e.from);
254
+ const rb = find(e.to);
255
+ if (ra !== rb) {
256
+ parent.set(ra, rb);
257
+ mst.push(e);
258
+ }
259
+ }
260
+ return mst;
261
+ }
262
+
263
+ /** Adjacency matrix (rows/cols follow the returned `order`; `Infinity` = no edge). */
264
+ toAdjacencyMatrix(): { matrix: number[][]; order: T[] } {
265
+ const order = this.vertices();
266
+ const index = new Map(order.map((v, i) => [v, i]));
267
+ const n = order.length;
268
+ const matrix = Array.from({ length: n }, (_, i) =>
269
+ Array.from({ length: n }, (_, j) => (i === j ? 0 : Number.POSITIVE_INFINITY)),
270
+ );
271
+ for (const [from, nbrs] of this.adjacency) {
272
+ for (const [to, w] of nbrs) matrix[index.get(from) as number][index.get(to) as number] = w;
273
+ }
274
+ return { matrix, order };
275
+ }
276
+
277
+ /**
278
+ * Strongly connected components (Tarjan's algorithm, single DFS pass,
279
+ * O(V+E)) — maximal vertex sets where every vertex can reach every
280
+ * other via directed edges. On an undirected graph this always reduces
281
+ * to {@link connectedComponents} (every edge is bidirectional, so
282
+ * "strongly connected" and "connected" coincide) — `connectedComponents`
283
+ * is the more direct choice there; this method exists for directed
284
+ * graphs, e.g. pruning a compatibility digraph down to the vertices
285
+ * that can actually participate in a cycle.
286
+ */
287
+ stronglyConnectedComponents(): T[][] {
288
+ let index = 0;
289
+ const indices = new Map<T, number>();
290
+ const lowlink = new Map<T, number>();
291
+ const onStack = new Set<T>();
292
+ const stack: T[] = [];
293
+ const components: T[][] = [];
294
+
295
+ const strongconnect = (v: T): void => {
296
+ indices.set(v, index);
297
+ lowlink.set(v, index);
298
+ index++;
299
+ stack.push(v);
300
+ onStack.add(v);
301
+
302
+ for (const w of this.neighbors(v)) {
303
+ if (!indices.has(w)) {
304
+ strongconnect(w);
305
+ lowlink.set(v, Math.min(lowlink.get(v) as number, lowlink.get(w) as number));
306
+ } else if (onStack.has(w)) {
307
+ lowlink.set(v, Math.min(lowlink.get(v) as number, indices.get(w) as number));
308
+ }
309
+ }
310
+
311
+ if (lowlink.get(v) === indices.get(v)) {
312
+ const component: T[] = [];
313
+ let w: T;
314
+ do {
315
+ w = stack.pop() as T;
316
+ onStack.delete(w);
317
+ component.push(w);
318
+ } while (w !== v);
319
+ components.push(component);
320
+ }
321
+ };
322
+
323
+ for (const v of this.vertices()) if (!indices.has(v)) strongconnect(v);
324
+ return components;
325
+ }
326
+
327
+ /** All-pairs shortest distances by Floyd-Warshall. */
328
+ floydWarshall(): { distances: number[][]; order: T[] } {
329
+ const { matrix, order } = this.toAdjacencyMatrix();
330
+ const n = order.length;
331
+ const d = matrix.map((row) => [...row]);
332
+ for (let k = 0; k < n; k++)
333
+ for (let i = 0; i < n; i++)
334
+ for (let j = 0; j < n; j++) if (d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];
335
+ return { distances: d, order };
336
+ }
337
+ }
@@ -0,0 +1,269 @@
1
+ import type { Polygon } from "./Polygon.ts";
2
+ import type { Vector } from "./Vector.ts";
3
+ import { VectorUtils } from "./VectorUtils.ts";
4
+
5
+ /**
6
+ * Graph3DUtils — helpers for turning functions and points into 3D mesh data.
7
+ *
8
+ * Ported from Mallory's ActionScript `Graph3DUtils`, which produced
9
+ * papervision3d `Mesh3D`/`Vertex3D`/`Face3D` objects. Those Flash libraries do
10
+ * not exist here, so the geometry is emitted as plain, renderer-agnostic data
11
+ * ({@link Mesh} = a coloured list of triangular {@link Face}s). The underlying
12
+ * geometry math is preserved.
13
+ *
14
+ * Bug fixes from the AS3 original:
15
+ * - `nRangeVector` only worked in one dimension (it returned `null` otherwise)
16
+ * and mutated its `mins`/`maxes`/`steps` arguments via `shift()`; it now
17
+ * handles any dimensionality without mutating its inputs.
18
+ * - `pointMatrixToMesh3D` used `alpha2` for the first material (should be
19
+ * `alpha1`) and had unreachable dead code after its `return`.
20
+ *
21
+ * NOTE: `create3DCurveSegment`/`create3DPrism` reproduce the original ribbon
22
+ * geometry, but with one fix: the AS3 original computed `phi` by converting
23
+ * an `atan2` result (radians) to degrees, then passed that degrees value
24
+ * straight into `Math.cos`/`Math.sin` (which expect radians) — a unit
25
+ * mismatch, not a deliberate choice. `phi` is now kept in radians throughout.
26
+ * The on-screen appearance still cannot be pixel-verified against the
27
+ * original Flash renderer (papervision3d no longer runs anywhere), so this is
28
+ * a best-effort mathematical correction, not a confirmed visual match.
29
+ */
30
+
31
+ export interface Vec3 {
32
+ x: number;
33
+ y: number;
34
+ z: number;
35
+ }
36
+
37
+ /** A triangular face. */
38
+ export type Face = [Vec3, Vec3, Vec3];
39
+
40
+ export interface Material {
41
+ color: number;
42
+ alpha: number;
43
+ oneSide: boolean;
44
+ }
45
+
46
+ export interface Mesh {
47
+ material: Material;
48
+ faces: Face[];
49
+ }
50
+
51
+ export interface Placement3D {
52
+ x: number;
53
+ y: number;
54
+ z: number;
55
+ scaleX: number;
56
+ scaleY: number;
57
+ scaleZ: number;
58
+ }
59
+
60
+ const v3 = (x: number, y: number, z = 0): Vec3 => ({ x, y, z });
61
+ const asPoint = (p: Vector<number>): Vec3 => ({ x: p.x as number, y: p.y as number, z: p.z as number });
62
+
63
+ export class Graph3DUtils {
64
+ /** Sample `binaryOperation` over a 2D grid, returning a matrix of results. */
65
+ static dualRangeVector<T>(
66
+ binaryOperation: (x: number, y: number) => T,
67
+ xMin = -10,
68
+ xMax = 10,
69
+ xStep = 1,
70
+ yMin = -10,
71
+ yMax = 10,
72
+ yStep = 1,
73
+ ): Vector<Vector<T>> {
74
+ const xValues = VectorUtils.arithmeticSequence(xMin, xMax, xStep);
75
+ // yValues is loop-invariant across the x loop — compute it once, not per x.
76
+ const yValues = VectorUtils.arithmeticSequence(yMin, yMax, yStep);
77
+ return VectorUtils.transform(xValues, (x) => VectorUtils.transform(yValues, (y) => binaryOperation(x, y)));
78
+ }
79
+
80
+ /**
81
+ * Sample `nOperation` over an N-dimensional grid (bug fix: the AS3 version
82
+ * only handled N=1 and mutated its arguments). `nOperation` receives the
83
+ * coordinate array for each grid point.
84
+ */
85
+ static nRangeVector<T>(
86
+ nOperation: (coords: number[]) => T,
87
+ mins: readonly number[],
88
+ maxes: readonly number[],
89
+ steps: readonly number[],
90
+ ): Vector<unknown> {
91
+ // Each dimension's sequence of values is loop-invariant across the recursion
92
+ // (it depends only on `dim`, not on `prefix`) — compute all of them once
93
+ // up front instead of recomputing per recursive call.
94
+ const dimValues = mins.map((min, dim) =>
95
+ VectorUtils.arithmeticSequence(min, maxes[dim] as number, steps[dim] as number),
96
+ );
97
+ const recurse = (dim: number, prefix: number[]): unknown => {
98
+ if (dim >= mins.length) return nOperation(prefix);
99
+ return VectorUtils.transform(dimValues[dim] as Vector<number>, (v) => recurse(dim + 1, [...prefix, v]));
100
+ };
101
+ return recurse(0, []) as Vector<unknown>;
102
+ }
103
+
104
+ /** Placement descriptor for a point in 3D (position + optional uniform scale from `t`). */
105
+ static placement3D(p: Vector<number>, altProperties: string[] | null = null): Placement3D | Record<string, number> {
106
+ if (altProperties) {
107
+ const out: Record<string, number> = {};
108
+ const max = Math.max(p.length, altProperties.length);
109
+ for (let i = 0; i < max; i++) out[altProperties[i] as string] = p[i] as number;
110
+ return out;
111
+ }
112
+ const scale = (p.t as number) !== 0 ? (p.t as number) : 1;
113
+ return { x: p.x as number, y: p.y as number, z: p.z as number, scaleX: scale, scaleY: scale, scaleZ: scale };
114
+ }
115
+
116
+ static placement3DBarGraph(p: Vector<number>): Placement3D {
117
+ return { x: p.x as number, y: p.y as number, z: p.z as number, scaleX: 1, scaleY: 1, scaleZ: 1 };
118
+ }
119
+
120
+ /** A flat two-triangle ribbon between two points (see the class NOTE). */
121
+ static create3DCurveSegment(
122
+ firstPoint: Vector<number>,
123
+ secondPoint: Vector<number>,
124
+ color = 0x000000,
125
+ alpha = 1,
126
+ planeThickness = 1,
127
+ planeAngle = 0,
128
+ ): Mesh {
129
+ const t = planeThickness / 2;
130
+ const a = asPoint(firstPoint);
131
+ const b = asPoint(secondPoint);
132
+ const phi = Math.PI / 2 - Math.atan2(b.y - a.y, b.x - a.x);
133
+ const a1 = v3(a.x - Math.cos(phi) * t * Math.cos(planeAngle), a.y + Math.sin(phi) * t * Math.sin(planeAngle));
134
+ const a2 = v3(a.x + Math.cos(phi) * t * Math.cos(planeAngle), a.y - Math.sin(phi) * t * Math.sin(planeAngle));
135
+ const b1 = v3(b.x - Math.cos(phi) * t * Math.cos(planeAngle), b.y + Math.sin(phi) * t * Math.sin(planeAngle));
136
+ const b2 = v3(b.x + Math.cos(phi) * t * Math.cos(planeAngle), b.y - Math.sin(phi) * t * Math.sin(planeAngle));
137
+ return {
138
+ material: { color, alpha, oneSide: false },
139
+ faces: [
140
+ [a1, b1, a2],
141
+ [b1, b2, a2],
142
+ ],
143
+ };
144
+ }
145
+
146
+ /** A rectangular prism (tube) between two points (see the class NOTE). */
147
+ static create3DPrism(
148
+ firstPoint: Vector<number>,
149
+ secondPoint: Vector<number>,
150
+ color = 0x000000,
151
+ alpha = 1,
152
+ planeThickness = 1,
153
+ ): Mesh {
154
+ const t = planeThickness / 2;
155
+ const a = asPoint(firstPoint);
156
+ const b = asPoint(secondPoint);
157
+ const phi = Math.PI / 2 - Math.atan2(b.y - a.y, b.x - a.x);
158
+
159
+ const a1 = v3(a.x - Math.cos(phi), a.y + Math.sin(phi) * t, a.z);
160
+ const a11 = v3(a1.x, a1.y, a1.z + t);
161
+ const a12 = v3(a1.x, a1.y, a1.z - t);
162
+ const a2 = v3(a.x + Math.cos(phi), a.y - Math.sin(phi) * t, a.z);
163
+ const a21 = v3(a2.x, a2.y, a2.z + t);
164
+ const a22 = v3(a2.x, a2.y, a2.z - t);
165
+ const b1 = v3(b.x - Math.cos(phi), b.y + Math.sin(phi) * t, a.z);
166
+ const b11 = v3(b1.x, b1.y, b1.z + t);
167
+ const b12 = v3(b1.x, b1.y, b1.z - t);
168
+ const b2 = v3(b.x + Math.cos(phi), b.y - Math.sin(phi) * t, a.z);
169
+ const b21 = v3(b2.x, b2.y, b2.z + t);
170
+ const b22 = v3(b2.x, b2.y, b2.z - t);
171
+
172
+ const faces: Face[] = [
173
+ [a11, a12, a22],
174
+ [a11, a21, a22],
175
+ [b11, b12, b22],
176
+ [b11, b21, b22],
177
+ [a11, a21, b21],
178
+ [a11, b11, b21],
179
+ [a21, b21, b22],
180
+ [a21, a22, b22],
181
+ [a22, b22, b12],
182
+ [a22, a12, b12],
183
+ [a12, a11, b11],
184
+ [a12, b12, b11],
185
+ ];
186
+ return { material: { color, alpha, oneSide: false }, faces };
187
+ }
188
+
189
+ /** A tube following a poly-line, as one prism mesh per segment. */
190
+ static vectorTo3DPrism(points: Vector<Vector<number>>, color = 0, alpha = 1, planeThickness = 1): Mesh[] {
191
+ const out: Mesh[] = [];
192
+ for (let i = 0; i < points.length - 1; i++) {
193
+ out.push(
194
+ Graph3DUtils.create3DPrism(
195
+ points[i] as Vector<number>,
196
+ points[i + 1] as Vector<number>,
197
+ color,
198
+ alpha,
199
+ planeThickness,
200
+ ),
201
+ );
202
+ }
203
+ return out;
204
+ }
205
+
206
+ /** Alias of {@link vectorTo3DPrism} (the AS3 `VectorTo3DCurve` was identical). */
207
+ static vectorTo3DCurve(points: Vector<Vector<number>>, color = 0, alpha = 1, planeThickness = 1): Mesh[] {
208
+ return Graph3DUtils.vectorTo3DPrism(points, color, alpha, planeThickness);
209
+ }
210
+
211
+ /** Triangulate a polygon into a mesh (fan from vertex 0). */
212
+ static polygonToMesh3D(polygon: Polygon, color = 0, alpha = 1): Mesh {
213
+ const faces: Face[] = [];
214
+ const degenerate = polygon.clone();
215
+ while (degenerate.length > 2) {
216
+ faces.push([asPoint(degenerate.vertex(0)), asPoint(degenerate.vertex(1)), asPoint(degenerate.vertex(2))]);
217
+ degenerate.splice(1, 1);
218
+ }
219
+ return { material: { color, alpha, oneSide: false }, faces };
220
+ }
221
+
222
+ /**
223
+ * Build a surface mesh from a grid (matrix) of points, as two triangle
224
+ * sweeps with distinct materials (bug fix: the first sweep now uses `alpha1`).
225
+ */
226
+ static pointMatrixToMesh3D(
227
+ vector: Vector<Vector<Vector<number>>>,
228
+ color1 = 0,
229
+ alpha1 = 1,
230
+ color2 = 0xffffff,
231
+ alpha2 = 1,
232
+ ): Mesh[] {
233
+ const width = VectorUtils.width(vector as never);
234
+ const height = VectorUtils.height(vector as never);
235
+ const at = (i: number, j: number): Vector<number> | undefined =>
236
+ (vector[i] as Vector<Vector<number>> | undefined)?.[j];
237
+
238
+ const faces1: Face[] = [];
239
+ for (let i = 0; i < width - 1; i++) {
240
+ for (let j = 0; j < height - 1; j++) {
241
+ if (at(i, j) && vector[i + 1] && at(i, j + 1)) {
242
+ faces1.push([
243
+ asPoint(at(i, j) as Vector<number>),
244
+ asPoint(at(i + 1, j) as Vector<number>),
245
+ asPoint(at(i, j + 1) as Vector<number>),
246
+ ]);
247
+ }
248
+ }
249
+ }
250
+
251
+ const faces2: Face[] = [];
252
+ for (let i = 1; i < width; i++) {
253
+ for (let j = 1; j < height; j++) {
254
+ if (at(i, j) && vector[i - 1] && at(i, j - 1)) {
255
+ faces2.push([
256
+ asPoint(at(i, j) as Vector<number>),
257
+ asPoint(at(i - 1, j) as Vector<number>),
258
+ asPoint(at(i, j - 1) as Vector<number>),
259
+ ]);
260
+ }
261
+ }
262
+ }
263
+
264
+ return [
265
+ { material: { color: color1, alpha: alpha1, oneSide: false }, faces: faces1 },
266
+ { material: { color: color2, alpha: alpha2, oneSide: false }, faces: faces2 },
267
+ ];
268
+ }
269
+ }