@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/Interval.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const F64 = new Float64Array(1);
|
|
2
|
+
const U64 = new BigUint64Array(F64.buffer);
|
|
3
|
+
|
|
4
|
+
/** The next representable double strictly greater than `value` (IEEE-754 nextafter toward +Infinity) -- JS has no built-in `Math.nextafter`, so this steps the raw 64-bit pattern by one ULP via a shared scratch buffer. */
|
|
5
|
+
function nextUp(value: number): number {
|
|
6
|
+
if (Number.isNaN(value) || value === Infinity) return value;
|
|
7
|
+
if (value === 0) return Number.MIN_VALUE;
|
|
8
|
+
F64[0] = value;
|
|
9
|
+
U64[0] = (U64[0] as bigint) + (value > 0 ? 1n : -1n);
|
|
10
|
+
return F64[0] as number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The next representable double strictly less than `value` -- `-nextUp(-value)`, since stepping toward -Infinity from `x` is the mirror of stepping toward +Infinity from `-x`. */
|
|
14
|
+
function nextDown(value: number): number {
|
|
15
|
+
return -nextUp(-value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Widens `x` by one ULP on each side -- the outward-rounding every non-exact
|
|
20
|
+
* `Interval` operation below applies to its result. `Math.*` computes to
|
|
21
|
+
* the *nearest* representable double (`Math.sqrt` is correctly rounded to
|
|
22
|
+
* 0.5 ULP per IEEE-754; the other transcendentals/arithmetic are within
|
|
23
|
+
* ~1 ULP on every mainstream engine), which breaks the containment
|
|
24
|
+
* guarantee interval arithmetic exists to provide: `Interval.point(2).sqrt()`
|
|
25
|
+
* would otherwise return a degenerate `[s, s]` that provably does NOT
|
|
26
|
+
* contain the irrational `sqrt(2)`. One ULP of outward slack restores
|
|
27
|
+
* rigor, at the cost of a deliberately loose bound when a result happens
|
|
28
|
+
* to be exactly representable (e.g. `sqrt(4)` reports a width-2-ULP
|
|
29
|
+
* interval around 2 instead of the point `[2, 2]`).
|
|
30
|
+
*/
|
|
31
|
+
function outward(x: Interval): Interval {
|
|
32
|
+
return new Interval(nextDown(x.lo), nextUp(x.hi));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Interval — rigorous interval arithmetic `[lo, hi]`. Operations return an
|
|
37
|
+
* interval guaranteed to contain every result of the corresponding operation on
|
|
38
|
+
* any pair of members, so a final interval bounds the true answer despite
|
|
39
|
+
* rounding — useful for reliable numerics and error tracking. Every
|
|
40
|
+
* operation that isn't provably exact (`negate`/`abs`/`hull`/`intersect`
|
|
41
|
+
* only move or compare existing endpoints) outward-rounds its result by
|
|
42
|
+
* one ULP per side via `outward()` above.
|
|
43
|
+
*/
|
|
44
|
+
export class Interval {
|
|
45
|
+
readonly lo: number;
|
|
46
|
+
readonly hi: number;
|
|
47
|
+
|
|
48
|
+
constructor(lo: number, hi: number = lo) {
|
|
49
|
+
if (lo > hi) throw new Error(`Invalid interval: [${lo}, ${hi}]`);
|
|
50
|
+
this.lo = lo;
|
|
51
|
+
this.hi = hi;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static of(lo: number, hi: number): Interval {
|
|
55
|
+
return new Interval(lo, hi);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A degenerate interval `[x, x]`. */
|
|
59
|
+
static point(x: number): Interval {
|
|
60
|
+
return new Interval(x, x);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get width(): number {
|
|
64
|
+
return this.hi - this.lo;
|
|
65
|
+
}
|
|
66
|
+
get midpoint(): number {
|
|
67
|
+
return (this.lo + this.hi) / 2;
|
|
68
|
+
}
|
|
69
|
+
get radius(): number {
|
|
70
|
+
return this.width / 2;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
contains(x: number): boolean {
|
|
74
|
+
return this.lo <= x && x <= this.hi;
|
|
75
|
+
}
|
|
76
|
+
overlaps(o: Interval): boolean {
|
|
77
|
+
return this.lo <= o.hi && o.lo <= this.hi;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
add(o: Interval): Interval {
|
|
81
|
+
return outward(new Interval(this.lo + o.lo, this.hi + o.hi));
|
|
82
|
+
}
|
|
83
|
+
subtract(o: Interval): Interval {
|
|
84
|
+
return outward(new Interval(this.lo - o.hi, this.hi - o.lo));
|
|
85
|
+
}
|
|
86
|
+
/** Exact: IEEE-754 negation only flips the sign bit, no rounding. */
|
|
87
|
+
negate(): Interval {
|
|
88
|
+
return new Interval(-this.hi, -this.lo);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
multiply(o: Interval): Interval {
|
|
92
|
+
const p = [this.lo * o.lo, this.lo * o.hi, this.hi * o.lo, this.hi * o.hi];
|
|
93
|
+
return outward(new Interval(Math.min(...p), Math.max(...p)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
divide(o: Interval): Interval {
|
|
97
|
+
if (o.contains(0)) throw new Error("Interval division by an interval containing zero.");
|
|
98
|
+
// The reciprocal bounds are themselves a rounded operation -- outward
|
|
99
|
+
// it before feeding into `multiply` (which outward-rounds its own
|
|
100
|
+
// result too), so neither step's rounding can silently eat the other's
|
|
101
|
+
// slack.
|
|
102
|
+
const reciprocal = outward(new Interval(1 / o.hi, 1 / o.lo));
|
|
103
|
+
return this.multiply(reciprocal);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The tightest interval containing both (the convex hull / union). */
|
|
107
|
+
hull(o: Interval): Interval {
|
|
108
|
+
return new Interval(Math.min(this.lo, o.lo), Math.max(this.hi, o.hi));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The overlap of two intervals, or `null` if they are disjoint. */
|
|
112
|
+
intersect(o: Interval): Interval | null {
|
|
113
|
+
const lo = Math.max(this.lo, o.lo);
|
|
114
|
+
const hi = Math.min(this.hi, o.hi);
|
|
115
|
+
return lo <= hi ? new Interval(lo, hi) : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
abs(): Interval {
|
|
119
|
+
if (this.lo >= 0) return this;
|
|
120
|
+
if (this.hi <= 0) return this.negate();
|
|
121
|
+
return new Interval(0, Math.max(-this.lo, this.hi));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
sqrt(): Interval {
|
|
125
|
+
if (this.lo < 0) throw new Error("sqrt of an interval with negative values.");
|
|
126
|
+
return outward(new Interval(Math.sqrt(this.lo), Math.sqrt(this.hi)));
|
|
127
|
+
}
|
|
128
|
+
exp(): Interval {
|
|
129
|
+
return outward(new Interval(Math.exp(this.lo), Math.exp(this.hi)));
|
|
130
|
+
}
|
|
131
|
+
log(): Interval {
|
|
132
|
+
if (this.lo <= 0) throw new Error("log of an interval with non-positive values.");
|
|
133
|
+
return outward(new Interval(Math.log(this.lo), Math.log(this.hi)));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Integer power (handles even powers of intervals straddling zero). Exact when `n === 0` (every interval to the 0th power is the exact point 1). */
|
|
137
|
+
pow(n: number): Interval {
|
|
138
|
+
if (!Number.isInteger(n) || n < 0) throw new Error("Interval.pow requires a non-negative integer.");
|
|
139
|
+
if (n === 0) return Interval.point(1);
|
|
140
|
+
const a = this.lo ** n;
|
|
141
|
+
const b = this.hi ** n;
|
|
142
|
+
if (n % 2 === 0 && this.contains(0)) return outward(new Interval(0, Math.max(a, b)));
|
|
143
|
+
return outward(new Interval(Math.min(a, b), Math.max(a, b)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private extrema(f: (x: number) => number, criticalPeriodStart: number, period: number): Interval {
|
|
147
|
+
const candidates = [f(this.lo), f(this.hi)];
|
|
148
|
+
// include critical points of the form `criticalPeriodStart + k·period` in range
|
|
149
|
+
const kStart = Math.ceil((this.lo - criticalPeriodStart) / period);
|
|
150
|
+
const kEnd = Math.floor((this.hi - criticalPeriodStart) / period);
|
|
151
|
+
for (let k = kStart; k <= kEnd; k++) candidates.push(f(criticalPeriodStart + k * period));
|
|
152
|
+
return outward(new Interval(Math.min(...candidates), Math.max(...candidates)));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
sin(): Interval {
|
|
156
|
+
if (this.width >= 2 * Math.PI) return new Interval(-1, 1);
|
|
157
|
+
return this.extrema(Math.sin, Math.PI / 2, Math.PI);
|
|
158
|
+
}
|
|
159
|
+
cos(): Interval {
|
|
160
|
+
if (this.width >= 2 * Math.PI) return new Interval(-1, 1);
|
|
161
|
+
return this.extrema(Math.cos, 0, Math.PI);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
equals(o: Interval): boolean {
|
|
165
|
+
return this.lo === o.lo && this.hi === o.hi;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
toString(): string {
|
|
169
|
+
return `[${this.lo}, ${this.hi}]`;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/Lattice.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lattice — axial-coordinate neighbor helpers for hexagonal and triangular
|
|
3
|
+
* grids (part of johnhenry/math#30's "lattice-geometry helpers" item,
|
|
4
|
+
* upstream for the generalized Wang tile laboratory,
|
|
5
|
+
* johnhenry/mallory#92). Both the solver (adjacency for constraint
|
|
6
|
+
* propagation) and the app (drawing) need a shared, tested "direction d's
|
|
7
|
+
* neighbor" definition per lattice — this is that definition, kept purely
|
|
8
|
+
* combinatorial (integer coordinate offsets), independent of any pixel
|
|
9
|
+
* geometry (see {@link Polygon.regular} for that side).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** One of the 6 axial directions on a hex grid, in the standard pointy-top ordering (E, NE, NW, W, SW, SE). */
|
|
13
|
+
export type HexDirection = 0 | 1 | 2 | 3 | 4 | 5;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The 6 axial-coordinate offsets, indexed by {@link HexDirection}. Opposite
|
|
17
|
+
* directions are `d` and `(d+3) % 6` — verified: `hexNeighbor` in direction
|
|
18
|
+
* `d` followed by `hexNeighbor` in direction `(d+3) % 6` returns to the
|
|
19
|
+
* start cell, for every `d`.
|
|
20
|
+
*/
|
|
21
|
+
export const HEX_AXIAL_DIRECTIONS: ReadonlyArray<readonly [number, number]> = [
|
|
22
|
+
[1, 0],
|
|
23
|
+
[1, -1],
|
|
24
|
+
[0, -1],
|
|
25
|
+
[-1, 0],
|
|
26
|
+
[-1, 1],
|
|
27
|
+
[0, 1],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** The axial-coordinate neighbor of `(q, r)` in direction `d`. */
|
|
31
|
+
export function hexNeighbor(q: number, r: number, d: HexDirection): readonly [number, number] {
|
|
32
|
+
const [dq, dr] = HEX_AXIAL_DIRECTIONS[d] as readonly [number, number];
|
|
33
|
+
return [q + dq, r + dr];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Every axial neighbor of `(q, r)`, in direction order. */
|
|
37
|
+
export function hexNeighbors(q: number, r: number): Array<readonly [number, number]> {
|
|
38
|
+
return [0, 1, 2, 3, 4, 5].map((d) => hexNeighbor(q, r, d as HexDirection));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A triangular lattice cell's orientation: `(x, y)` with `x + y` even is
|
|
43
|
+
* "up" (a triangle pointing up, vertex at the top edge), odd is "down" —
|
|
44
|
+
* a lattice of alternating up/down triangles tiling the plane, indexed by
|
|
45
|
+
* integer `(x, y)` (NOT pixel coordinates; see {@link Polygon.regular} for
|
|
46
|
+
* that side).
|
|
47
|
+
*/
|
|
48
|
+
export type TriOrientation = "up" | "down";
|
|
49
|
+
|
|
50
|
+
export function triOrientation(x: number, y: number): TriOrientation {
|
|
51
|
+
return (((x + y) % 2) + 2) % 2 === 0 ? "up" : "down";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** An "up" triangle's 3 edge-sharing neighbors (all "down"): its left/right neighbors in the same row, and the one above sharing its top edge. */
|
|
55
|
+
export type UpTriDirection = "left" | "right" | "top";
|
|
56
|
+
/** A "down" triangle's 3 edge-sharing neighbors (all "up"): its left/right neighbors in the same row, and the one below sharing its bottom edge. */
|
|
57
|
+
export type DownTriDirection = "left" | "right" | "bottom";
|
|
58
|
+
export type TriDirection = UpTriDirection | DownTriDirection;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The neighbor of triangular cell `(x, y)` in `direction` — reciprocal
|
|
62
|
+
* pairs: an "up" cell's `"right"` neighbor's `"left"` is itself, and an
|
|
63
|
+
* "up" cell's `"top"` neighbor's `"bottom"` is itself (verified before
|
|
64
|
+
* writing tests). `direction` must match `(x, y)`'s own orientation
|
|
65
|
+
* (`"top"` is only valid on an "up" cell, `"bottom"` only on "down").
|
|
66
|
+
*/
|
|
67
|
+
export function triNeighbor(x: number, y: number, direction: TriDirection): readonly [number, number] {
|
|
68
|
+
const orientation = triOrientation(x, y);
|
|
69
|
+
if (direction === "left") return [x - 1, y];
|
|
70
|
+
if (direction === "right") return [x + 1, y];
|
|
71
|
+
if (direction === "top") {
|
|
72
|
+
if (orientation !== "up")
|
|
73
|
+
throw new Error(`triNeighbor: "top" is only valid on an "up" cell, (${x},${y}) is "down".`);
|
|
74
|
+
return [x, y + 1];
|
|
75
|
+
}
|
|
76
|
+
if (orientation !== "down")
|
|
77
|
+
throw new Error(`triNeighbor: "bottom" is only valid on a "down" cell, (${x},${y}) is "up".`);
|
|
78
|
+
return [x, y - 1];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Every edge-sharing neighbor of triangular cell `(x, y)`, in the direction order valid for its own orientation. */
|
|
82
|
+
export function triNeighbors(x: number, y: number): Array<readonly [number, number]> {
|
|
83
|
+
const orientation = triOrientation(x, y);
|
|
84
|
+
const directions: readonly TriDirection[] =
|
|
85
|
+
orientation === "up" ? ["left", "right", "top"] : ["left", "right", "bottom"];
|
|
86
|
+
return directions.map((d) => triNeighbor(x, y, d));
|
|
87
|
+
}
|
package/src/Logic.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logic — boolean helper predicates.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Mallory's ActionScript `Logic`. The AS3 source did not actually
|
|
5
|
+
* compile (`And(a*;b:*)` and friends are syntactically invalid); the intent is
|
|
6
|
+
* clear from the comments and reproduced here as ordinary boolean functions.
|
|
7
|
+
*/
|
|
8
|
+
export class Logic {
|
|
9
|
+
/** Always true, whatever the arguments. */
|
|
10
|
+
static truth(..._anything: unknown[]): boolean {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Always false, whatever the arguments. */
|
|
15
|
+
static falsehood(..._anything: unknown[]): boolean {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Logical AND. */
|
|
20
|
+
static and(a: unknown, b: unknown): boolean {
|
|
21
|
+
return Boolean(a) && Boolean(b);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Logical (inclusive) OR. */
|
|
25
|
+
static or(a: unknown, b: unknown): boolean {
|
|
26
|
+
return Boolean(a) || Boolean(b);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Logical exclusive OR. */
|
|
30
|
+
static xor(a: unknown, b: unknown): boolean {
|
|
31
|
+
return Boolean(a) !== Boolean(b);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Logical NOT. */
|
|
35
|
+
static not(a: unknown): boolean {
|
|
36
|
+
return !a;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Vector } from "./Vector.ts";
|
|
2
|
+
import type { Matrix } from "./VectorUtils.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MatrixMath — numerical linear algebra: decompositions (LU, QR, Cholesky,
|
|
6
|
+
* eigen, SVD), direct solvers, rank/RREF/null space, least squares, the
|
|
7
|
+
* pseudo-inverse, and matrix norms. Complements the elementary matrix ops in
|
|
8
|
+
* {@link RealMath} (determinant, inverse, multiply) with the higher-level
|
|
9
|
+
* factorisations they lacked.
|
|
10
|
+
*
|
|
11
|
+
* Matrices are the library's `Matrix<number>` (a {@link Vector} of row Vectors);
|
|
12
|
+
* plain `number[][]` is also accepted as input.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type Mat = Matrix<number>;
|
|
16
|
+
type MatrixInput = Mat | number[][];
|
|
17
|
+
|
|
18
|
+
const EPS = 1e-12;
|
|
19
|
+
|
|
20
|
+
const raw = (m: MatrixInput): number[][] => [...(m as Iterable<Iterable<number>>)].map((r) => [...r]);
|
|
21
|
+
const wrap = (a: number[][]): Mat => Vector.fromArray(a.map((r) => Vector.fromArray(r)));
|
|
22
|
+
const vec = (a: number[]): Vector<number> => Vector.fromArray(a);
|
|
23
|
+
const identity = (n: number): number[][] =>
|
|
24
|
+
Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)));
|
|
25
|
+
const zeros = (m: number, n: number): number[][] => Array.from({ length: m }, () => new Array(n).fill(0));
|
|
26
|
+
const transposeRaw = (a: number[][]): number[][] => a[0].map((_, j) => a.map((row) => row[j]));
|
|
27
|
+
const matmul = (a: number[][], b: number[][]): number[][] => {
|
|
28
|
+
const m = a.length;
|
|
29
|
+
const n = b[0].length;
|
|
30
|
+
const k = b.length;
|
|
31
|
+
const out = zeros(m, n);
|
|
32
|
+
for (let i = 0; i < m; i++) for (let p = 0; p < k; p++) for (let j = 0; j < n; j++) out[i][j] += a[i][p] * b[p][j];
|
|
33
|
+
return out;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface LUResult {
|
|
37
|
+
L: Mat;
|
|
38
|
+
U: Mat;
|
|
39
|
+
P: Mat;
|
|
40
|
+
sign: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface QRResult {
|
|
44
|
+
Q: Mat;
|
|
45
|
+
R: Mat;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface EigenResult {
|
|
49
|
+
values: Vector<number>;
|
|
50
|
+
vectors: Mat;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SVDResult {
|
|
54
|
+
U: Mat;
|
|
55
|
+
S: Vector<number>;
|
|
56
|
+
V: Mat;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class MatrixMath {
|
|
60
|
+
/** LU decomposition with partial pivoting: `P·A = L·U`. */
|
|
61
|
+
static lu(A: MatrixInput): LUResult {
|
|
62
|
+
const U = raw(A);
|
|
63
|
+
const n = U.length;
|
|
64
|
+
const L = identity(n);
|
|
65
|
+
const P = identity(n);
|
|
66
|
+
let sign = 1;
|
|
67
|
+
for (let k = 0; k < n; k++) {
|
|
68
|
+
let pivot = k;
|
|
69
|
+
for (let i = k + 1; i < n; i++) if (Math.abs(U[i][k]) > Math.abs(U[pivot][k])) pivot = i;
|
|
70
|
+
if (pivot !== k) {
|
|
71
|
+
[U[k], U[pivot]] = [U[pivot], U[k]];
|
|
72
|
+
[P[k], P[pivot]] = [P[pivot], P[k]];
|
|
73
|
+
for (let j = 0; j < k; j++) [L[k][j], L[pivot][j]] = [L[pivot][j], L[k][j]];
|
|
74
|
+
sign = -sign;
|
|
75
|
+
}
|
|
76
|
+
if (Math.abs(U[k][k]) < EPS) continue; // singular column
|
|
77
|
+
for (let i = k + 1; i < n; i++) {
|
|
78
|
+
const f = U[i][k] / U[k][k];
|
|
79
|
+
L[i][k] = f;
|
|
80
|
+
for (let j = k; j < n; j++) U[i][j] -= f * U[k][j];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { L: wrap(L), U: wrap(U), P: wrap(P), sign };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Solve the linear system `A·x = b` via LU decomposition. */
|
|
87
|
+
static solve(A: MatrixInput, b: number[] | Vector<number>): Vector<number> {
|
|
88
|
+
const a = raw(A);
|
|
89
|
+
const n = a.length;
|
|
90
|
+
const rhs = [...(b as Iterable<number>)];
|
|
91
|
+
const { L, U, P } = MatrixMath.lu(a);
|
|
92
|
+
const Lr = raw(L);
|
|
93
|
+
const Ur = raw(U);
|
|
94
|
+
const Pr = raw(P);
|
|
95
|
+
// Pb
|
|
96
|
+
const pb = Pr.map((row) => row.reduce((s, v, j) => s + v * rhs[j], 0));
|
|
97
|
+
// forward solve L y = Pb
|
|
98
|
+
const y = new Array(n).fill(0);
|
|
99
|
+
for (let i = 0; i < n; i++) {
|
|
100
|
+
let s = pb[i];
|
|
101
|
+
for (let j = 0; j < i; j++) s -= Lr[i][j] * y[j];
|
|
102
|
+
y[i] = s; // L has unit diagonal
|
|
103
|
+
}
|
|
104
|
+
// back solve U x = y
|
|
105
|
+
const x = new Array(n).fill(0);
|
|
106
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
107
|
+
let s = y[i];
|
|
108
|
+
for (let j = i + 1; j < n; j++) s -= Ur[i][j] * x[j];
|
|
109
|
+
x[i] = Math.abs(Ur[i][i]) < EPS ? 0 : s / Ur[i][i];
|
|
110
|
+
}
|
|
111
|
+
return vec(x);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reduced row echelon form (Gauss-Jordan elimination). */
|
|
115
|
+
static rref(A: MatrixInput): Mat {
|
|
116
|
+
const a = raw(A);
|
|
117
|
+
const rows = a.length;
|
|
118
|
+
const cols = a[0].length;
|
|
119
|
+
let lead = 0;
|
|
120
|
+
for (let r = 0; r < rows && lead < cols; r++, lead++) {
|
|
121
|
+
let i = r;
|
|
122
|
+
while (lead < cols && Math.abs(a[i][lead]) < EPS) {
|
|
123
|
+
i++;
|
|
124
|
+
if (i === rows) {
|
|
125
|
+
i = r;
|
|
126
|
+
lead++;
|
|
127
|
+
if (lead === cols) return wrap(a);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
[a[i], a[r]] = [a[r], a[i]];
|
|
131
|
+
const pivot = a[r][lead];
|
|
132
|
+
for (let j = 0; j < cols; j++) a[r][j] /= pivot;
|
|
133
|
+
for (let k = 0; k < rows; k++) {
|
|
134
|
+
if (k === r) continue;
|
|
135
|
+
const factor = a[k][lead];
|
|
136
|
+
for (let j = 0; j < cols; j++) a[k][j] -= factor * a[r][j];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return wrap(a);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Rank of a matrix (number of nonzero rows in its RREF). */
|
|
143
|
+
static rank(A: MatrixInput): number {
|
|
144
|
+
const r = raw(MatrixMath.rref(A));
|
|
145
|
+
return r.filter((row) => row.some((v) => Math.abs(v) > 1e-9)).length;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A basis for the null space (kernel) of `A`, as a matrix whose rows are basis vectors. */
|
|
149
|
+
static nullSpace(A: MatrixInput): Mat {
|
|
150
|
+
const R = raw(MatrixMath.rref(A));
|
|
151
|
+
const rows = R.length;
|
|
152
|
+
const cols = R[0].length;
|
|
153
|
+
const pivotCols: number[] = [];
|
|
154
|
+
for (let r = 0; r < rows; r++) {
|
|
155
|
+
const lead = R[r].findIndex((v) => Math.abs(v) > 1e-9);
|
|
156
|
+
if (lead !== -1) pivotCols.push(lead);
|
|
157
|
+
}
|
|
158
|
+
const freeCols: number[] = [];
|
|
159
|
+
for (let c = 0; c < cols; c++) if (!pivotCols.includes(c)) freeCols.push(c);
|
|
160
|
+
|
|
161
|
+
const basis: number[][] = [];
|
|
162
|
+
for (const free of freeCols) {
|
|
163
|
+
const v = new Array(cols).fill(0);
|
|
164
|
+
v[free] = 1;
|
|
165
|
+
for (let r = 0; r < pivotCols.length; r++) v[pivotCols[r]] = -R[r][free];
|
|
166
|
+
basis.push(v);
|
|
167
|
+
}
|
|
168
|
+
return wrap(basis.length ? basis : [new Array(cols).fill(0)]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** QR decomposition via modified Gram-Schmidt: `A = Q·R` (Q orthonormal). */
|
|
172
|
+
static qr(A: MatrixInput): QRResult {
|
|
173
|
+
const a = raw(A);
|
|
174
|
+
const m = a.length;
|
|
175
|
+
const n = a[0].length;
|
|
176
|
+
const Q = zeros(m, n);
|
|
177
|
+
const R = zeros(n, n);
|
|
178
|
+
const cols = transposeRaw(a); // columns of A
|
|
179
|
+
for (let j = 0; j < n; j++) {
|
|
180
|
+
const v = [...cols[j]];
|
|
181
|
+
for (let i = 0; i < j; i++) {
|
|
182
|
+
let dot = 0;
|
|
183
|
+
for (let r = 0; r < m; r++) dot += Q[r][i] * v[r];
|
|
184
|
+
R[i][j] = dot;
|
|
185
|
+
for (let r = 0; r < m; r++) v[r] -= dot * Q[r][i];
|
|
186
|
+
}
|
|
187
|
+
let norm = Math.hypot(...v);
|
|
188
|
+
R[j][j] = norm;
|
|
189
|
+
if (norm < EPS) norm = 1;
|
|
190
|
+
for (let r = 0; r < m; r++) Q[r][j] = v[r] / norm;
|
|
191
|
+
}
|
|
192
|
+
return { Q: wrap(Q), R: wrap(R) };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Cholesky decomposition of a symmetric positive-definite matrix: `A = L·Lᵀ`. */
|
|
196
|
+
static cholesky(A: MatrixInput): Mat {
|
|
197
|
+
const a = raw(A);
|
|
198
|
+
const n = a.length;
|
|
199
|
+
const L = zeros(n, n);
|
|
200
|
+
for (let j = 0; j < n; j++) {
|
|
201
|
+
let d = a[j][j];
|
|
202
|
+
for (let k = 0; k < j; k++) d -= L[j][k] * L[j][k];
|
|
203
|
+
if (d <= 0) throw new Error("Matrix is not positive-definite.");
|
|
204
|
+
L[j][j] = Math.sqrt(d);
|
|
205
|
+
for (let i = j + 1; i < n; i++) {
|
|
206
|
+
let s = a[i][j];
|
|
207
|
+
for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
|
|
208
|
+
L[i][j] = s / L[j][j];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return wrap(L);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Eigenvalues and orthonormal eigenvectors of a symmetric matrix via the
|
|
216
|
+
* cyclic Jacobi rotation algorithm. Values are returned in descending order.
|
|
217
|
+
*/
|
|
218
|
+
static eigenSymmetric(A: MatrixInput, maxSweeps = 100): EigenResult {
|
|
219
|
+
const a = raw(A);
|
|
220
|
+
const n = a.length;
|
|
221
|
+
const V = identity(n);
|
|
222
|
+
for (let sweep = 0; sweep < maxSweeps; sweep++) {
|
|
223
|
+
let off = 0;
|
|
224
|
+
for (let p = 0; p < n; p++) for (let q = p + 1; q < n; q++) off += a[p][q] * a[p][q];
|
|
225
|
+
if (off < EPS) break;
|
|
226
|
+
for (let p = 0; p < n; p++) {
|
|
227
|
+
for (let q = p + 1; q < n; q++) {
|
|
228
|
+
if (Math.abs(a[p][q]) < EPS) continue;
|
|
229
|
+
const theta = (a[q][q] - a[p][p]) / (2 * a[p][q]);
|
|
230
|
+
const t = Math.sign(theta || 1) / (Math.abs(theta) + Math.sqrt(theta * theta + 1));
|
|
231
|
+
const c = 1 / Math.sqrt(t * t + 1);
|
|
232
|
+
const s = t * c;
|
|
233
|
+
for (let i = 0; i < n; i++) {
|
|
234
|
+
const aip = a[i][p];
|
|
235
|
+
const aiq = a[i][q];
|
|
236
|
+
a[i][p] = c * aip - s * aiq;
|
|
237
|
+
a[i][q] = s * aip + c * aiq;
|
|
238
|
+
}
|
|
239
|
+
for (let i = 0; i < n; i++) {
|
|
240
|
+
const api = a[p][i];
|
|
241
|
+
const aqi = a[q][i];
|
|
242
|
+
a[p][i] = c * api - s * aqi;
|
|
243
|
+
a[q][i] = s * api + c * aqi;
|
|
244
|
+
}
|
|
245
|
+
for (let i = 0; i < n; i++) {
|
|
246
|
+
const vip = V[i][p];
|
|
247
|
+
const viq = V[i][q];
|
|
248
|
+
V[i][p] = c * vip - s * viq;
|
|
249
|
+
V[i][q] = s * vip + c * viq;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const pairs = a.map((row, i) => ({ value: row[i], vec: V.map((r) => r[i]) }));
|
|
255
|
+
pairs.sort((x, y) => y.value - x.value);
|
|
256
|
+
return {
|
|
257
|
+
values: vec(pairs.map((p) => p.value)),
|
|
258
|
+
vectors: wrap(transposeRaw(pairs.map((p) => p.vec))),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Singular value decomposition `A = U·Σ·Vᵀ` (via the eigendecomposition of
|
|
264
|
+
* `AᵀA`). Returns `U`, the singular values `S` (descending), and `V`.
|
|
265
|
+
*/
|
|
266
|
+
static svd(A: MatrixInput): SVDResult {
|
|
267
|
+
const a = raw(A);
|
|
268
|
+
const m = a.length;
|
|
269
|
+
const n = a[0].length;
|
|
270
|
+
const at = transposeRaw(a);
|
|
271
|
+
const ata = matmul(at, a); // n×n
|
|
272
|
+
const { values, vectors } = MatrixMath.eigenSymmetric(ata);
|
|
273
|
+
const V = raw(vectors); // columns are eigenvectors of AᵀA
|
|
274
|
+
const sv = [...values].map((v) => Math.sqrt(Math.max(v, 0)));
|
|
275
|
+
// U columns: A·v_i / σ_i
|
|
276
|
+
const U = zeros(m, n);
|
|
277
|
+
for (let i = 0; i < n; i++) {
|
|
278
|
+
const vi = V.map((r) => r[i]);
|
|
279
|
+
const avi = a.map((row) => row.reduce((s, val, j) => s + val * vi[j], 0));
|
|
280
|
+
const sigma = sv[i];
|
|
281
|
+
for (let r = 0; r < m; r++) U[r][i] = sigma > EPS ? avi[r] / sigma : 0;
|
|
282
|
+
}
|
|
283
|
+
return { U: wrap(U), S: vec(sv), V: wrap(V) };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Least-squares solution of an overdetermined system `A·x ≈ b` (normal equations). */
|
|
287
|
+
static leastSquares(A: MatrixInput, b: number[] | Vector<number>): Vector<number> {
|
|
288
|
+
const a = raw(A);
|
|
289
|
+
const at = transposeRaw(a);
|
|
290
|
+
const ata = matmul(at, a);
|
|
291
|
+
const rhs = [...(b as Iterable<number>)];
|
|
292
|
+
const atb = at.map((row) => row.reduce((s, v, j) => s + v * rhs[j], 0));
|
|
293
|
+
return MatrixMath.solve(wrap(ata), atb);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The Moore-Penrose pseudo-inverse `A⁺` (via SVD). */
|
|
297
|
+
static pseudoInverse(A: MatrixInput): Mat {
|
|
298
|
+
const { U, S, V } = MatrixMath.svd(A);
|
|
299
|
+
const Ur = raw(U);
|
|
300
|
+
const Vr = raw(V);
|
|
301
|
+
const s = [...S];
|
|
302
|
+
const n = s.length;
|
|
303
|
+
// A⁺ = V · Σ⁺ · Uᵀ
|
|
304
|
+
const sInv = s.map((v) => (v > 1e-9 ? 1 / v : 0));
|
|
305
|
+
const sigmaPlus = zeros(n, n);
|
|
306
|
+
for (let i = 0; i < n; i++) sigmaPlus[i][i] = sInv[i];
|
|
307
|
+
return wrap(matmul(matmul(Vr, sigmaPlus), transposeRaw(Ur)));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** The Frobenius norm (root of the sum of squared entries). */
|
|
311
|
+
static frobeniusNorm(A: MatrixInput): number {
|
|
312
|
+
return Math.hypot(...raw(A).flat());
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The spectral (2-)norm: the largest singular value. */
|
|
316
|
+
static spectralNorm(A: MatrixInput): number {
|
|
317
|
+
return Math.max(...MatrixMath.svd(A).S);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The 2-norm condition number `σ_max / σ_min`. */
|
|
321
|
+
static conditionNumber(A: MatrixInput): number {
|
|
322
|
+
const s = [...MatrixMath.svd(A).S];
|
|
323
|
+
const min = Math.min(...s);
|
|
324
|
+
return min < EPS ? Number.POSITIVE_INFINITY : Math.max(...s) / min;
|
|
325
|
+
}
|
|
326
|
+
}
|