@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.
- package/README.md +165 -0
- package/dist/Bivector4.d.ts +52 -0
- package/dist/Bivector4.d.ts.map +1 -0
- package/dist/Bivector4.js +105 -0
- package/dist/Bivector4.js.map +1 -0
- package/dist/CellGraph.d.ts +259 -0
- package/dist/CellGraph.d.ts.map +1 -0
- package/dist/CellGraph.js +637 -0
- package/dist/CellGraph.js.map +1 -0
- package/dist/Clifford4Internal.d.ts +35 -0
- package/dist/Clifford4Internal.d.ts.map +1 -0
- package/dist/Clifford4Internal.js +86 -0
- package/dist/Clifford4Internal.js.map +1 -0
- package/dist/Combinatorics.d.ts +61 -0
- package/dist/Combinatorics.d.ts.map +1 -0
- package/dist/Combinatorics.js +186 -0
- package/dist/Combinatorics.js.map +1 -0
- package/dist/CombinatoricsGenerators.d.ts +44 -0
- package/dist/CombinatoricsGenerators.d.ts.map +1 -0
- package/dist/CombinatoricsGenerators.js +148 -0
- package/dist/CombinatoricsGenerators.js.map +1 -0
- package/dist/ComplexMath.d.ts +46 -0
- package/dist/ComplexMath.d.ts.map +1 -0
- package/dist/ComplexMath.js +98 -0
- package/dist/ComplexMath.js.map +1 -0
- package/dist/ComplexNumber.d.ts +174 -0
- package/dist/ComplexNumber.d.ts.map +1 -0
- package/dist/ComplexNumber.js +443 -0
- package/dist/ComplexNumber.js.map +1 -0
- package/dist/Cycle.d.ts +42 -0
- package/dist/Cycle.d.ts.map +1 -0
- package/dist/Cycle.js +120 -0
- package/dist/Cycle.js.map +1 -0
- package/dist/Decimal.d.ts +45 -0
- package/dist/Decimal.d.ts.map +1 -0
- package/dist/Decimal.js +135 -0
- package/dist/Decimal.js.map +1 -0
- package/dist/Distributions.d.ts +55 -0
- package/dist/Distributions.d.ts.map +1 -0
- package/dist/Distributions.js +222 -0
- package/dist/Distributions.js.map +1 -0
- package/dist/DualNumber.d.ts +42 -0
- package/dist/DualNumber.d.ts.map +1 -0
- package/dist/DualNumber.js +91 -0
- package/dist/DualNumber.js.map +1 -0
- package/dist/Environment.d.ts +44 -0
- package/dist/Environment.d.ts.map +1 -0
- package/dist/Environment.js +129 -0
- package/dist/Environment.js.map +1 -0
- package/dist/Expression.d.ts +19 -0
- package/dist/Expression.d.ts.map +1 -0
- package/dist/Expression.js +43 -0
- package/dist/Expression.js.map +1 -0
- package/dist/FFT.d.ts +21 -0
- package/dist/FFT.d.ts.map +1 -0
- package/dist/FFT.js +136 -0
- package/dist/FFT.js.map +1 -0
- package/dist/GeometricAlgebra4.d.ts +73 -0
- package/dist/GeometricAlgebra4.d.ts.map +1 -0
- package/dist/GeometricAlgebra4.js +115 -0
- package/dist/GeometricAlgebra4.js.map +1 -0
- package/dist/Geometry.d.ts +41 -0
- package/dist/Geometry.d.ts.map +1 -0
- package/dist/Geometry.js +167 -0
- package/dist/Geometry.js.map +1 -0
- package/dist/Graph.d.ts +72 -0
- package/dist/Graph.d.ts.map +1 -0
- package/dist/Graph.js +337 -0
- package/dist/Graph.js.map +1 -0
- package/dist/Graph3DUtils.d.ts +80 -0
- package/dist/Graph3DUtils.d.ts.map +1 -0
- package/dist/Graph3DUtils.js +156 -0
- package/dist/Graph3DUtils.js.map +1 -0
- package/dist/GraphUtils.d.ts +67 -0
- package/dist/GraphUtils.d.ts.map +1 -0
- package/dist/GraphUtils.js +72 -0
- package/dist/GraphUtils.js.map +1 -0
- package/dist/GroupTheory.d.ts +73 -0
- package/dist/GroupTheory.d.ts.map +1 -0
- package/dist/GroupTheory.js +194 -0
- package/dist/GroupTheory.js.map +1 -0
- package/dist/IntUtils.d.ts +33 -0
- package/dist/IntUtils.d.ts.map +1 -0
- package/dist/IntUtils.js +185 -0
- package/dist/IntUtils.js.map +1 -0
- package/dist/Interval.d.ts +44 -0
- package/dist/Interval.d.ts.map +1 -0
- package/dist/Interval.js +165 -0
- package/dist/Interval.js.map +1 -0
- package/dist/Lattice.d.ts +48 -0
- package/dist/Lattice.d.ts.map +1 -0
- package/dist/Lattice.js +65 -0
- package/dist/Lattice.js.map +1 -0
- package/dist/Logic.d.ts +22 -0
- package/dist/Logic.d.ts.map +1 -0
- package/dist/Logic.js +34 -0
- package/dist/Logic.js.map +1 -0
- package/dist/MatrixMath.d.ts +71 -0
- package/dist/MatrixMath.d.ts.map +1 -0
- package/dist/MatrixMath.js +302 -0
- package/dist/MatrixMath.js.map +1 -0
- package/dist/NumberTheory.d.ts +49 -0
- package/dist/NumberTheory.d.ts.map +1 -0
- package/dist/NumberTheory.js +230 -0
- package/dist/NumberTheory.js.map +1 -0
- package/dist/Numerical.d.ts +85 -0
- package/dist/Numerical.d.ts.map +1 -0
- package/dist/Numerical.js +343 -0
- package/dist/Numerical.js.map +1 -0
- package/dist/Permutation.d.ts +43 -0
- package/dist/Permutation.d.ts.map +1 -0
- package/dist/Permutation.js +142 -0
- package/dist/Permutation.js.map +1 -0
- package/dist/Polygon.d.ts +64 -0
- package/dist/Polygon.d.ts.map +1 -0
- package/dist/Polygon.js +221 -0
- package/dist/Polygon.js.map +1 -0
- package/dist/PolynomialRing.d.ts +74 -0
- package/dist/PolynomialRing.d.ts.map +1 -0
- package/dist/PolynomialRing.js +231 -0
- package/dist/PolynomialRing.js.map +1 -0
- package/dist/Quaternion.d.ts +37 -0
- package/dist/Quaternion.d.ts.map +1 -0
- package/dist/Quaternion.js +106 -0
- package/dist/Quaternion.js.map +1 -0
- package/dist/Rational.d.ts +34 -0
- package/dist/Rational.d.ts.map +1 -0
- package/dist/Rational.js +136 -0
- package/dist/Rational.js.map +1 -0
- package/dist/RealMath.d.ts +112 -0
- package/dist/RealMath.d.ts.map +1 -0
- package/dist/RealMath.js +262 -0
- package/dist/RealMath.js.map +1 -0
- package/dist/Rotor4.d.ts +180 -0
- package/dist/Rotor4.d.ts.map +1 -0
- package/dist/Rotor4.js +388 -0
- package/dist/Rotor4.js.map +1 -0
- package/dist/SpecialFunctions.d.ts +32 -0
- package/dist/SpecialFunctions.d.ts.map +1 -0
- package/dist/SpecialFunctions.js +161 -0
- package/dist/SpecialFunctions.js.map +1 -0
- package/dist/SpecialOperator.d.ts +21 -0
- package/dist/SpecialOperator.d.ts.map +1 -0
- package/dist/SpecialOperator.js +26 -0
- package/dist/SpecialOperator.js.map +1 -0
- package/dist/Statistics.d.ts +50 -0
- package/dist/Statistics.d.ts.map +1 -0
- package/dist/Statistics.js +131 -0
- package/dist/Statistics.js.map +1 -0
- package/dist/StringEvaluator.d.ts +48 -0
- package/dist/StringEvaluator.d.ts.map +1 -0
- package/dist/StringEvaluator.js +190 -0
- package/dist/StringEvaluator.js.map +1 -0
- package/dist/Structure.d.ts +130 -0
- package/dist/Structure.d.ts.map +1 -0
- package/dist/Structure.js +394 -0
- package/dist/Structure.js.map +1 -0
- package/dist/Symbolic.d.ts +501 -0
- package/dist/Symbolic.d.ts.map +1 -0
- package/dist/Symbolic.js +4077 -0
- package/dist/Symbolic.js.map +1 -0
- package/dist/Type.d.ts +31 -0
- package/dist/Type.d.ts.map +1 -0
- package/dist/Type.js +47 -0
- package/dist/Type.js.map +1 -0
- package/dist/Utilities.d.ts +27 -0
- package/dist/Utilities.d.ts.map +1 -0
- package/dist/Utilities.js +47 -0
- package/dist/Utilities.js.map +1 -0
- package/dist/Vec4.d.ts +39 -0
- package/dist/Vec4.d.ts.map +1 -0
- package/dist/Vec4.js +82 -0
- package/dist/Vec4.js.map +1 -0
- package/dist/Vector.d.ts +83 -0
- package/dist/Vector.d.ts.map +1 -0
- package/dist/Vector.js +173 -0
- package/dist/Vector.js.map +1 -0
- package/dist/VectorCalculus.d.ts +42 -0
- package/dist/VectorCalculus.d.ts.map +1 -0
- package/dist/VectorCalculus.js +82 -0
- package/dist/VectorCalculus.js.map +1 -0
- package/dist/VectorUtils.d.ts +172 -0
- package/dist/VectorUtils.d.ts.map +1 -0
- package/dist/VectorUtils.js +706 -0
- package/dist/VectorUtils.js.map +1 -0
- package/dist/differentiation-rules.d.ts +21 -0
- package/dist/differentiation-rules.d.ts.map +1 -0
- package/dist/differentiation-rules.js +52 -0
- package/dist/differentiation-rules.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/src/Bivector4.ts +136 -0
- package/src/CellGraph.ts +669 -0
- package/src/Clifford4Internal.ts +90 -0
- package/src/Combinatorics.ts +183 -0
- package/src/CombinatoricsGenerators.ts +144 -0
- package/src/ComplexMath.ts +162 -0
- package/src/ComplexNumber.ts +491 -0
- package/src/Cycle.ts +126 -0
- package/src/Decimal.ts +145 -0
- package/src/Distributions.ts +269 -0
- package/src/DualNumber.ts +105 -0
- package/src/Environment.ts +127 -0
- package/src/Expression.ts +48 -0
- package/src/FFT.ts +146 -0
- package/src/GeometricAlgebra4.ts +157 -0
- package/src/Geometry.ts +173 -0
- package/src/Graph.ts +337 -0
- package/src/Graph3DUtils.ts +269 -0
- package/src/GraphUtils.ts +159 -0
- package/src/GroupTheory.ts +214 -0
- package/src/IntUtils.ts +200 -0
- package/src/Interval.ts +171 -0
- package/src/Lattice.ts +87 -0
- package/src/Logic.ts +38 -0
- package/src/MatrixMath.ts +326 -0
- package/src/NumberTheory.ts +228 -0
- package/src/Numerical.ts +390 -0
- package/src/Permutation.ts +148 -0
- package/src/Polygon.ts +229 -0
- package/src/PolynomialRing.ts +242 -0
- package/src/Quaternion.ts +125 -0
- package/src/Rational.ts +148 -0
- package/src/RealMath.ts +346 -0
- package/src/Rotor4.ts +440 -0
- package/src/SpecialFunctions.ts +152 -0
- package/src/SpecialOperator.ts +28 -0
- package/src/Statistics.ts +170 -0
- package/src/StringEvaluator.ts +208 -0
- package/src/Structure.ts +481 -0
- package/src/Symbolic.ts +4313 -0
- package/src/Type.ts +48 -0
- package/src/Utilities.ts +49 -0
- package/src/Vec4.ts +98 -0
- package/src/Vector.ts +189 -0
- package/src/VectorCalculus.ts +87 -0
- package/src/VectorUtils.ts +797 -0
- package/src/differentiation-rules.ts +73 -0
- 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
|
+
}
|