@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/Decimal.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal — an arbitrary-precision decimal number `digits × 10^exponent`,
|
|
3
|
+
* backed by `bigint`. Addition/subtraction/multiplication are exact (unlike
|
|
4
|
+
* `number`, which drifts on values like `0.1 + 0.2`); division is inherently
|
|
5
|
+
* approximate and rounds to a configurable number of significant digits — an
|
|
6
|
+
* honest limitation analogous to floats, just with configurable precision
|
|
7
|
+
* instead of a fixed 53 bits.
|
|
8
|
+
*
|
|
9
|
+
* Unlike {@link Rational}, no canonical/normalized form is maintained —
|
|
10
|
+
* equality and comparison work by aligning exponents rather than requiring
|
|
11
|
+
* reduced digits, which keeps arithmetic simple at the cost of `toString()`
|
|
12
|
+
* not collapsing trailing zeros introduced by intermediate computations.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const digitCount = (n: bigint): number => (n < 0n ? -n : n).toString().length;
|
|
16
|
+
|
|
17
|
+
/** Round `num / den` to the nearest integer (half away from zero), for any sign combination. */
|
|
18
|
+
function divRound(num: bigint, den: bigint): bigint {
|
|
19
|
+
const q = num / den;
|
|
20
|
+
const r = num % den;
|
|
21
|
+
if (r === 0n) return q;
|
|
22
|
+
const doubledR = (r < 0n ? -r : r) * 2n;
|
|
23
|
+
const absDen = den < 0n ? -den : den;
|
|
24
|
+
if (doubledR >= absDen) {
|
|
25
|
+
const sameSign = num < 0n === den < 0n;
|
|
26
|
+
return sameSign ? q + 1n : q - 1n;
|
|
27
|
+
}
|
|
28
|
+
return q;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class Decimal {
|
|
32
|
+
static readonly DEFAULT_PRECISION = 20;
|
|
33
|
+
|
|
34
|
+
readonly digits: bigint;
|
|
35
|
+
readonly exponent: number;
|
|
36
|
+
|
|
37
|
+
static readonly Zero = new Decimal(0n, 0);
|
|
38
|
+
static readonly One = new Decimal(1n, 0);
|
|
39
|
+
|
|
40
|
+
constructor(digits: bigint = 0n, exponent = 0) {
|
|
41
|
+
this.digits = digits;
|
|
42
|
+
this.exponent = exponent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static fromString(value: string): Decimal {
|
|
46
|
+
const match = /^\s*([+-]?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?\s*$/.exec(value);
|
|
47
|
+
if (!match) throw new Error(`Invalid Decimal string: ${value}`);
|
|
48
|
+
const [, sign, intPart, fracPart = "", expPart] = match;
|
|
49
|
+
const digits = BigInt((sign === "-" ? "-" : "") + intPart + fracPart);
|
|
50
|
+
const exponent = -fracPart.length + (expPart ? Number(expPart) : 0);
|
|
51
|
+
return new Decimal(digits, exponent);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static from(value: Decimal | bigint | number | string): Decimal {
|
|
55
|
+
if (value instanceof Decimal) return value;
|
|
56
|
+
if (typeof value === "bigint") return new Decimal(value, 0);
|
|
57
|
+
if (typeof value === "number") {
|
|
58
|
+
if (!Number.isFinite(value)) throw new Error("Cannot make a Decimal from a non-finite number.");
|
|
59
|
+
return Decimal.fromString(value.toString());
|
|
60
|
+
}
|
|
61
|
+
return Decimal.fromString(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Rescale `a` and `b` to a shared exponent (the smaller of the two). */
|
|
65
|
+
private static align(a: Decimal, b: Decimal): { ad: bigint; bd: bigint; exponent: number } {
|
|
66
|
+
const exponent = Math.min(a.exponent, b.exponent);
|
|
67
|
+
const ad = a.digits * 10n ** BigInt(a.exponent - exponent);
|
|
68
|
+
const bd = b.digits * 10n ** BigInt(b.exponent - exponent);
|
|
69
|
+
return { ad, bd, exponent };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
add(other: Decimal): Decimal {
|
|
73
|
+
const { ad, bd, exponent } = Decimal.align(this, other);
|
|
74
|
+
return new Decimal(ad + bd, exponent);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
subtract(other: Decimal): Decimal {
|
|
78
|
+
const { ad, bd, exponent } = Decimal.align(this, other);
|
|
79
|
+
return new Decimal(ad - bd, exponent);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
multiply(other: Decimal): Decimal {
|
|
83
|
+
return new Decimal(this.digits * other.digits, this.exponent + other.exponent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Division is approximate: the result is rounded to `precision` significant digits. */
|
|
87
|
+
divide(other: Decimal, precision = Decimal.DEFAULT_PRECISION): Decimal {
|
|
88
|
+
if (other.digits === 0n) throw new Error("Division by zero Decimal.");
|
|
89
|
+
if (this.digits === 0n) return Decimal.Zero;
|
|
90
|
+
const shift = Math.max(0, precision - digitCount(this.digits) + digitCount(other.digits));
|
|
91
|
+
const scaledNumerator = this.digits * 10n ** BigInt(shift);
|
|
92
|
+
const quotient = divRound(scaledNumerator, other.digits);
|
|
93
|
+
return new Decimal(quotient, this.exponent - other.exponent - shift);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
negate(): Decimal {
|
|
97
|
+
return new Decimal(-this.digits, this.exponent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
abs(): Decimal {
|
|
101
|
+
return this.digits < 0n ? this.negate() : this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
equals(other: Decimal): boolean {
|
|
105
|
+
const { ad, bd } = Decimal.align(this, other);
|
|
106
|
+
return ad === bd;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** -1, 0 or 1 depending on ordering against `other`. */
|
|
110
|
+
compare(other: Decimal): number {
|
|
111
|
+
const { ad, bd } = Decimal.align(this, other);
|
|
112
|
+
return ad < bd ? -1 : ad > bd ? 1 : 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lessThan(other: Decimal): boolean {
|
|
116
|
+
return this.compare(other) < 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isZero(): boolean {
|
|
120
|
+
return this.digits === 0n;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
toNumber(): number {
|
|
124
|
+
return Number(this.digits) * 10 ** this.exponent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Renders in plain decimal notation. Trailing fractional zeros are trimmed
|
|
129
|
+
* for readability — this is purely presentational; no canonical form is
|
|
130
|
+
* maintained internally, so `digits`/`exponent` are untouched.
|
|
131
|
+
*/
|
|
132
|
+
toString(): string {
|
|
133
|
+
if (this.digits === 0n) return "0";
|
|
134
|
+
const negative = this.digits < 0n;
|
|
135
|
+
let s = (negative ? -this.digits : this.digits).toString();
|
|
136
|
+
if (this.exponent >= 0) {
|
|
137
|
+
s = s + "0".repeat(this.exponent);
|
|
138
|
+
} else {
|
|
139
|
+
const pointPos = s.length + this.exponent;
|
|
140
|
+
s = pointPos <= 0 ? `0.${"0".repeat(-pointPos)}${s}` : `${s.slice(0, pointPos)}.${s.slice(pointPos)}`;
|
|
141
|
+
if (s.includes(".")) s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
142
|
+
}
|
|
143
|
+
return negative ? `-${s}` : s;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { SpecialFunctions as SF } from "./SpecialFunctions.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Distributions — common probability distributions (continuous and discrete),
|
|
5
|
+
* each exposing `pdf`/`pmf`, `cdf`, `mean`, `variance` and a random `sample`,
|
|
6
|
+
* plus a small suite of {@link HypothesisTests}. CDFs use the exact incomplete
|
|
7
|
+
* gamma/beta functions from {@link SpecialFunctions} rather than numeric
|
|
8
|
+
* integration, so they are accurate in the tails.
|
|
9
|
+
*
|
|
10
|
+
* Every factory takes an optional trailing `rng: () => number` (default
|
|
11
|
+
* `Math.random`), threaded through to every nested distribution it samples
|
|
12
|
+
* from — pass a seeded generator to make `sample()` deterministic in tests.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ContinuousDistribution {
|
|
16
|
+
pdf(x: number): number;
|
|
17
|
+
cdf(x: number): number;
|
|
18
|
+
mean(): number;
|
|
19
|
+
variance(): number;
|
|
20
|
+
sample(): number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DiscreteDistribution {
|
|
24
|
+
pmf(k: number): number;
|
|
25
|
+
cdf(k: number): number;
|
|
26
|
+
mean(): number;
|
|
27
|
+
variance(): number;
|
|
28
|
+
sample(): number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The inverse standard-normal CDF (Acklam's rational approximation). */
|
|
32
|
+
function normInv(p: number): number {
|
|
33
|
+
if (p <= 0) return Number.NEGATIVE_INFINITY;
|
|
34
|
+
if (p >= 1) return Number.POSITIVE_INFINITY;
|
|
35
|
+
const a = [
|
|
36
|
+
-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.38357751867269e2, -3.066479806614716e1,
|
|
37
|
+
2.506628277459239,
|
|
38
|
+
];
|
|
39
|
+
const b = [
|
|
40
|
+
-5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1, -1.328068155288572e1,
|
|
41
|
+
];
|
|
42
|
+
const c = [
|
|
43
|
+
-7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838, -2.549732539343734, 4.374664141464968,
|
|
44
|
+
2.938163982698783,
|
|
45
|
+
];
|
|
46
|
+
const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996, 3.754408661907416];
|
|
47
|
+
const plow = 0.02425;
|
|
48
|
+
const phigh = 1 - plow;
|
|
49
|
+
if (p < plow) {
|
|
50
|
+
const q = Math.sqrt(-2 * Math.log(p));
|
|
51
|
+
return (
|
|
52
|
+
(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
53
|
+
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (p <= phigh) {
|
|
57
|
+
const q = p - 0.5;
|
|
58
|
+
const r = q * q;
|
|
59
|
+
return (
|
|
60
|
+
((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q) /
|
|
61
|
+
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const q = Math.sqrt(-2 * Math.log(1 - p));
|
|
65
|
+
return (
|
|
66
|
+
-(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
67
|
+
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class Distributions {
|
|
72
|
+
/** The inverse standard-normal CDF (quantile / probit function). */
|
|
73
|
+
static normalQuantile(p: number, mean = 0, sd = 1): number {
|
|
74
|
+
return mean + sd * normInv(p);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static normal(mean = 0, sd = 1, rng: () => number = Math.random): ContinuousDistribution {
|
|
78
|
+
return {
|
|
79
|
+
pdf: (x) => Math.exp(-0.5 * ((x - mean) / sd) ** 2) / (sd * Math.sqrt(2 * Math.PI)),
|
|
80
|
+
cdf: (x) => 0.5 * SF.erfc(-(x - mean) / (sd * Math.SQRT2)),
|
|
81
|
+
mean: () => mean,
|
|
82
|
+
variance: () => sd * sd,
|
|
83
|
+
sample: () => mean + sd * Math.sqrt(-2 * Math.log(1 - rng())) * Math.cos(2 * Math.PI * rng()),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static exponential(rate = 1, rng: () => number = Math.random): ContinuousDistribution {
|
|
88
|
+
return {
|
|
89
|
+
pdf: (x) => (x < 0 ? 0 : rate * Math.exp(-rate * x)),
|
|
90
|
+
cdf: (x) => (x < 0 ? 0 : 1 - Math.exp(-rate * x)),
|
|
91
|
+
mean: () => 1 / rate,
|
|
92
|
+
variance: () => 1 / (rate * rate),
|
|
93
|
+
sample: () => -Math.log(1 - rng()) / rate,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static uniform(a = 0, b = 1, rng: () => number = Math.random): ContinuousDistribution {
|
|
98
|
+
return {
|
|
99
|
+
pdf: (x) => (x >= a && x <= b ? 1 / (b - a) : 0),
|
|
100
|
+
cdf: (x) => (x < a ? 0 : x > b ? 1 : (x - a) / (b - a)),
|
|
101
|
+
mean: () => (a + b) / 2,
|
|
102
|
+
variance: () => (b - a) ** 2 / 12,
|
|
103
|
+
sample: () => a + rng() * (b - a),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static gamma(shape: number, scale = 1, rng: () => number = Math.random): ContinuousDistribution {
|
|
108
|
+
return {
|
|
109
|
+
pdf: (x) => (x <= 0 ? 0 : (x ** (shape - 1) * Math.exp(-x / scale)) / (SF.gamma(shape) * scale ** shape)),
|
|
110
|
+
cdf: (x) => (x <= 0 ? 0 : SF.regularizedGammaP(shape, x / scale)),
|
|
111
|
+
mean: () => shape * scale,
|
|
112
|
+
variance: () => shape * scale * scale,
|
|
113
|
+
sample: () => Distributions.sampleGamma(shape, rng) * scale,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static chiSquare(df: number, rng: () => number = Math.random): ContinuousDistribution {
|
|
118
|
+
const g = Distributions.gamma(df / 2, 2, rng);
|
|
119
|
+
return { ...g, mean: () => df, variance: () => 2 * df };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static studentT(df: number, rng: () => number = Math.random): ContinuousDistribution {
|
|
123
|
+
const c = SF.gamma((df + 1) / 2) / (Math.sqrt(df * Math.PI) * SF.gamma(df / 2));
|
|
124
|
+
return {
|
|
125
|
+
pdf: (x) => c * (1 + (x * x) / df) ** (-(df + 1) / 2),
|
|
126
|
+
cdf: (x) => {
|
|
127
|
+
const ib = SF.regularizedIncompleteBeta(df / (df + x * x), df / 2, 0.5);
|
|
128
|
+
return x >= 0 ? 1 - 0.5 * ib : 0.5 * ib;
|
|
129
|
+
},
|
|
130
|
+
mean: () => (df > 1 ? 0 : Number.NaN),
|
|
131
|
+
variance: () => (df > 2 ? df / (df - 2) : Number.POSITIVE_INFINITY),
|
|
132
|
+
sample: () => {
|
|
133
|
+
const z = Distributions.normal(0, 1, rng).sample();
|
|
134
|
+
const chi = Distributions.chiSquare(df, rng).sample();
|
|
135
|
+
return z / Math.sqrt(chi / df);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static binomial(n: number, p: number, rng: () => number = Math.random): DiscreteDistribution {
|
|
141
|
+
const logChoose = (k: number) => SF.lnGamma(n + 1) - SF.lnGamma(k + 1) - SF.lnGamma(n - k + 1);
|
|
142
|
+
const pmf = (k: number): number =>
|
|
143
|
+
k < 0 || k > n || !Number.isInteger(k) ? 0 : Math.exp(logChoose(k) + k * Math.log(p) + (n - k) * Math.log(1 - p));
|
|
144
|
+
return {
|
|
145
|
+
pmf,
|
|
146
|
+
cdf: (k) => {
|
|
147
|
+
// Reuse the single pmf closure above instead of rebuilding a whole new
|
|
148
|
+
// binomial distribution (and its logChoose closure) on every step.
|
|
149
|
+
let s = 0;
|
|
150
|
+
for (let i = 0; i <= Math.floor(k); i++) s += pmf(i);
|
|
151
|
+
return Math.min(1, s);
|
|
152
|
+
},
|
|
153
|
+
mean: () => n * p,
|
|
154
|
+
variance: () => n * p * (1 - p),
|
|
155
|
+
sample: () => {
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (let i = 0; i < n; i++) if (rng() < p) count++;
|
|
158
|
+
return count;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static poisson(lambda: number, rng: () => number = Math.random): DiscreteDistribution {
|
|
164
|
+
return {
|
|
165
|
+
pmf: (k) => (k < 0 || !Number.isInteger(k) ? 0 : Math.exp(k * Math.log(lambda) - lambda - SF.lnGamma(k + 1))),
|
|
166
|
+
cdf: (k) => (k < 0 ? 0 : SF.regularizedGammaQ(Math.floor(k) + 1, lambda)),
|
|
167
|
+
mean: () => lambda,
|
|
168
|
+
variance: () => lambda,
|
|
169
|
+
sample: () => {
|
|
170
|
+
const L = Math.exp(-lambda);
|
|
171
|
+
let k = 0;
|
|
172
|
+
let prod = 1;
|
|
173
|
+
do {
|
|
174
|
+
k++;
|
|
175
|
+
prod *= rng();
|
|
176
|
+
} while (prod > L);
|
|
177
|
+
return k - 1;
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Marsaglia-Tsang gamma sampler (shape ≥ 0). */
|
|
183
|
+
private static sampleGamma(shape: number, rng: () => number = Math.random): number {
|
|
184
|
+
if (shape < 1) return Distributions.sampleGamma(shape + 1, rng) * rng() ** (1 / shape);
|
|
185
|
+
const d = shape - 1 / 3;
|
|
186
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
187
|
+
for (;;) {
|
|
188
|
+
let x: number;
|
|
189
|
+
let v: number;
|
|
190
|
+
do {
|
|
191
|
+
x = Distributions.normal(0, 1, rng).sample();
|
|
192
|
+
v = 1 + c * x;
|
|
193
|
+
} while (v <= 0);
|
|
194
|
+
v = v * v * v;
|
|
195
|
+
const u = rng();
|
|
196
|
+
if (u < 1 - 0.0331 * x ** 4) return d * v;
|
|
197
|
+
if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) return d * v;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface TestResult {
|
|
203
|
+
statistic: number;
|
|
204
|
+
df: number;
|
|
205
|
+
pValue: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export class HypothesisTests {
|
|
209
|
+
/** One-sample two-tailed Student t-test against a hypothesised mean `mu0`. */
|
|
210
|
+
static tTestOneSample(sample: number[], mu0 = 0): TestResult {
|
|
211
|
+
const n = sample.length;
|
|
212
|
+
const mean = sample.reduce((a, b) => a + b, 0) / n;
|
|
213
|
+
const variance = sample.reduce((acc, x) => acc + (x - mean) ** 2, 0) / (n - 1);
|
|
214
|
+
const se = Math.sqrt(variance / n);
|
|
215
|
+
const t = (mean - mu0) / se;
|
|
216
|
+
const df = n - 1;
|
|
217
|
+
const p = 2 * (1 - Distributions.studentT(df).cdf(Math.abs(t)));
|
|
218
|
+
return { statistic: t, df, pValue: p };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Welch's two-sample t-test (unequal variances). */
|
|
222
|
+
static tTestTwoSample(a: number[], b: number[]): TestResult {
|
|
223
|
+
const na = a.length;
|
|
224
|
+
const nb = b.length;
|
|
225
|
+
const ma = a.reduce((s, x) => s + x, 0) / na;
|
|
226
|
+
const mb = b.reduce((s, x) => s + x, 0) / nb;
|
|
227
|
+
const va = a.reduce((s, x) => s + (x - ma) ** 2, 0) / (na - 1);
|
|
228
|
+
const vb = b.reduce((s, x) => s + (x - mb) ** 2, 0) / (nb - 1);
|
|
229
|
+
const se = Math.sqrt(va / na + vb / nb);
|
|
230
|
+
const t = (ma - mb) / se;
|
|
231
|
+
const df = (va / na + vb / nb) ** 2 / ((va / na) ** 2 / (na - 1) + (vb / nb) ** 2 / (nb - 1));
|
|
232
|
+
const p = 2 * (1 - Distributions.studentT(df).cdf(Math.abs(t)));
|
|
233
|
+
return { statistic: t, df, pValue: p };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Pearson chi-square goodness-of-fit test. */
|
|
237
|
+
static chiSquareGoodnessOfFit(observed: number[], expected: number[]): TestResult {
|
|
238
|
+
const chi2 = observed.reduce((s, o, i) => s + (o - expected[i]) ** 2 / expected[i], 0);
|
|
239
|
+
const df = observed.length - 1;
|
|
240
|
+
const p = 1 - Distributions.chiSquare(df).cdf(chi2);
|
|
241
|
+
return { statistic: chi2, df, pValue: p };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Confidence interval for the mean of a sample at the given level (t-based). */
|
|
245
|
+
static confidenceIntervalMean(sample: number[], level = 0.95): [number, number] {
|
|
246
|
+
const n = sample.length;
|
|
247
|
+
const m = sample.reduce((a, b) => a + b, 0) / n;
|
|
248
|
+
const variance = sample.reduce((acc, x) => acc + (x - m) ** 2, 0) / (n - 1);
|
|
249
|
+
const se = Math.sqrt(variance / n);
|
|
250
|
+
const alpha = 1 - level;
|
|
251
|
+
const tCrit = tQuantile(1 - alpha / 2, n - 1);
|
|
252
|
+
return [m - tCrit * se, m + tCrit * se];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// -- small internal helpers -------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/** Quantile of Student-t via bisection on its CDF. */
|
|
259
|
+
function tQuantile(p: number, df: number): number {
|
|
260
|
+
const dist = Distributions.studentT(df);
|
|
261
|
+
let lo = -100;
|
|
262
|
+
let hi = 100;
|
|
263
|
+
for (let i = 0; i < 200; i++) {
|
|
264
|
+
const mid = (lo + hi) / 2;
|
|
265
|
+
if (dist.cdf(mid) < p) lo = mid;
|
|
266
|
+
else hi = mid;
|
|
267
|
+
}
|
|
268
|
+
return (lo + hi) / 2;
|
|
269
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DualNumber — a number of the form `value + deriv·ε` where `ε² = 0`. Evaluating
|
|
3
|
+
* a function on a dual number carries its derivative along automatically (the
|
|
4
|
+
* `ε` component), giving exact forward-mode automatic differentiation — more
|
|
5
|
+
* accurate than the finite differences in {@link RealMath.differentiateN}.
|
|
6
|
+
*
|
|
7
|
+
* It is also a commutative ring (a valid {@link Structure}).
|
|
8
|
+
*/
|
|
9
|
+
export class DualNumber {
|
|
10
|
+
readonly value: number;
|
|
11
|
+
readonly deriv: number;
|
|
12
|
+
|
|
13
|
+
constructor(value = 0, deriv = 0) {
|
|
14
|
+
this.value = value;
|
|
15
|
+
this.deriv = deriv;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A constant (zero derivative). */
|
|
19
|
+
static constant(x: number): DualNumber {
|
|
20
|
+
return new DualNumber(x, 0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** An independent variable at `x` (unit derivative). */
|
|
24
|
+
static variable(x: number): DualNumber {
|
|
25
|
+
return new DualNumber(x, 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static lift(x: DualNumber | number): DualNumber {
|
|
29
|
+
return x instanceof DualNumber ? x : new DualNumber(x, 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
add(o: DualNumber | number): DualNumber {
|
|
33
|
+
const d = DualNumber.lift(o);
|
|
34
|
+
return new DualNumber(this.value + d.value, this.deriv + d.deriv);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
subtract(o: DualNumber | number): DualNumber {
|
|
38
|
+
const d = DualNumber.lift(o);
|
|
39
|
+
return new DualNumber(this.value - d.value, this.deriv - d.deriv);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
multiply(o: DualNumber | number): DualNumber {
|
|
43
|
+
const d = DualNumber.lift(o);
|
|
44
|
+
return new DualNumber(this.value * d.value, this.value * d.deriv + this.deriv * d.value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
divide(o: DualNumber | number): DualNumber {
|
|
48
|
+
const d = DualNumber.lift(o);
|
|
49
|
+
return new DualNumber(this.value / d.value, (this.deriv * d.value - this.value * d.deriv) / (d.value * d.value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
negate(): DualNumber {
|
|
53
|
+
return new DualNumber(-this.value, -this.deriv);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Raise to a constant power (chain rule). */
|
|
57
|
+
pow(n: number): DualNumber {
|
|
58
|
+
return new DualNumber(this.value ** n, n * this.value ** (n - 1) * this.deriv);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static sin(x: DualNumber): DualNumber {
|
|
62
|
+
return new DualNumber(Math.sin(x.value), Math.cos(x.value) * x.deriv);
|
|
63
|
+
}
|
|
64
|
+
static cos(x: DualNumber): DualNumber {
|
|
65
|
+
return new DualNumber(Math.cos(x.value), -Math.sin(x.value) * x.deriv);
|
|
66
|
+
}
|
|
67
|
+
static tan(x: DualNumber): DualNumber {
|
|
68
|
+
const c = Math.cos(x.value);
|
|
69
|
+
return new DualNumber(Math.tan(x.value), x.deriv / (c * c));
|
|
70
|
+
}
|
|
71
|
+
static exp(x: DualNumber): DualNumber {
|
|
72
|
+
const e = Math.exp(x.value);
|
|
73
|
+
return new DualNumber(e, e * x.deriv);
|
|
74
|
+
}
|
|
75
|
+
static log(x: DualNumber): DualNumber {
|
|
76
|
+
return new DualNumber(Math.log(x.value), x.deriv / x.value);
|
|
77
|
+
}
|
|
78
|
+
static sqrt(x: DualNumber): DualNumber {
|
|
79
|
+
const s = Math.sqrt(x.value);
|
|
80
|
+
return new DualNumber(s, x.deriv / (2 * s));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The derivative of `f` at `x`, evaluated exactly via dual numbers. */
|
|
84
|
+
static derivative(f: (x: DualNumber) => DualNumber, x: number): number {
|
|
85
|
+
return f(DualNumber.variable(x)).deriv;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The value and derivative of `f` at `x` together. */
|
|
89
|
+
static valueAndDerivative(f: (x: DualNumber) => DualNumber, x: number): { value: number; derivative: number } {
|
|
90
|
+
const r = f(DualNumber.variable(x));
|
|
91
|
+
return { value: r.value, derivative: r.deriv };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The gradient of a multivariate `f` at `point` (one dual seed per coordinate). */
|
|
95
|
+
static gradient(f: (xs: DualNumber[]) => DualNumber, point: number[]): number[] {
|
|
96
|
+
return point.map((_, i) => {
|
|
97
|
+
const seeded = point.map((v, j) => new DualNumber(v, i === j ? 1 : 0));
|
|
98
|
+
return f(seeded).deriv;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
toString(): string {
|
|
103
|
+
return `${this.value} + ${this.deriv}ε`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment — a symbol table mapping string keys to values, with a separate
|
|
3
|
+
* pool of immutable bindings. Used by {@link StringEvaluator} and
|
|
4
|
+
* {@link Expression} to resolve variables and functions.
|
|
5
|
+
*
|
|
6
|
+
* Ported from Mallory's ActionScript `Enviornment` (the original class name was
|
|
7
|
+
* misspelled; corrected here). Deep-clone via Flash `ByteArray` serialisation is
|
|
8
|
+
* replaced with an ordinary entry copy — values are treated as immutable data.
|
|
9
|
+
*/
|
|
10
|
+
export class Environment {
|
|
11
|
+
private _items = new Map<string, unknown>();
|
|
12
|
+
private _immutableItems = new Map<string, unknown>();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Each consecutive pair of arguments `(key, value)` becomes an immutable
|
|
16
|
+
* binding. A trailing unpaired argument is ignored.
|
|
17
|
+
*/
|
|
18
|
+
constructor(...rest: unknown[]) {
|
|
19
|
+
if (rest.length % 2 === 1) rest.pop();
|
|
20
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
21
|
+
if (typeof rest[i] === "string") this.assignImmutable(rest[i] as string, rest[i + 1]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
existKey(request: string): boolean {
|
|
26
|
+
return this._immutableItems.has(request) || this._items.has(request);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
existImmutableKey(request: string): boolean {
|
|
30
|
+
return this._immutableItems.has(request);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
existValue(request: unknown): boolean {
|
|
34
|
+
for (const v of this._immutableItems.values()) if (v === request) return true;
|
|
35
|
+
for (const v of this._items.values()) if (v === request) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toString(showImmutable = true, showRest = true): string {
|
|
40
|
+
const result: string[] = [];
|
|
41
|
+
if (showImmutable) for (const [k, v] of this._immutableItems) result.push(`${k} : ${v}`);
|
|
42
|
+
if (showRest) for (const [k, v] of this._items) result.push(`${k} : ${v}`);
|
|
43
|
+
return `{${result.join(" ; ")}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A copy of the environment (bindings are shared by reference). */
|
|
47
|
+
clone(): Environment {
|
|
48
|
+
const out = new Environment();
|
|
49
|
+
out._items = new Map(this._items);
|
|
50
|
+
out._immutableItems = new Map(this._immutableItems);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get size(): number {
|
|
55
|
+
return this._items.size + this._immutableItems.size;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get keys(): string[] {
|
|
59
|
+
return [...this._immutableItems.keys(), ...this._items.keys()];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get immutableKeys(): string[] {
|
|
63
|
+
return [...this._immutableItems.keys()];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get values(): unknown[] {
|
|
67
|
+
return [...this._immutableItems.values(), ...this._items.values()];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Bind `key` to `value`. Assigning `undefined` (or the key to itself) removes it. */
|
|
71
|
+
assign(key: string, value?: unknown): boolean {
|
|
72
|
+
if (value === undefined || (key as unknown) === value) return this.remove(key);
|
|
73
|
+
if (this.existImmutableKey(key)) return false;
|
|
74
|
+
this._items.set(key, value);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Bind `key` immutably. Fails if the key already exists in either pool. */
|
|
79
|
+
assignImmutable(key: string, value?: unknown): boolean {
|
|
80
|
+
if (value === undefined || (key as unknown) === value) return this.remove(key);
|
|
81
|
+
if (this.existKey(key)) return false;
|
|
82
|
+
this._immutableItems.set(key, value);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Bind `key` to `value` after first resolving `value` in this environment. */
|
|
87
|
+
assignDeep(key: string, value: string): boolean {
|
|
88
|
+
return this.assign(key, this.retrieveDeep(value));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
assignImmutableDeep(key: string, value: string): boolean {
|
|
92
|
+
return this.assignImmutable(key, this.retrieveDeep(value));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Look up `key`; an unknown key resolves to itself (symbolic passthrough). */
|
|
96
|
+
retrieve(key: string): unknown {
|
|
97
|
+
if (!this.existKey(key)) return key;
|
|
98
|
+
if (this.existImmutableKey(key)) return this._immutableItems.get(key);
|
|
99
|
+
return this._items.get(key);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Follow chains of string references to the deepest concrete value. */
|
|
103
|
+
retrieveDeep(key: string): unknown {
|
|
104
|
+
let previous = this.retrieve(key);
|
|
105
|
+
// <= (not <): the initial `retrieve` above already consumes one lookup
|
|
106
|
+
// that isn't tied to a loop iteration, so even an empty environment
|
|
107
|
+
// (size 0) must still get one pass to notice `previous === key` and
|
|
108
|
+
// return the symbolic passthrough instead of falling through below.
|
|
109
|
+
for (let i = 0; i <= this.size; i++) {
|
|
110
|
+
if (previous === key) return key;
|
|
111
|
+
if (typeof previous !== "string" || previous === this.retrieve(previous)) return previous;
|
|
112
|
+
previous = this.retrieve(previous);
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Remove a mutable binding (immutable ones cannot be removed). */
|
|
118
|
+
remove(key: string): boolean {
|
|
119
|
+
if (this.existImmutableKey(key)) return false;
|
|
120
|
+
return this._items.delete(key);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Remove an immutable binding (for debugging; normally you should not). */
|
|
124
|
+
removeImmutable(key: string): boolean {
|
|
125
|
+
return this._immutableItems.delete(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Environment } from "./Environment.ts";
|
|
2
|
+
import { StringEvaluator } from "./StringEvaluator.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Expression — a named formula over a list of independent variables, evaluated
|
|
6
|
+
* by substituting values and delegating to {@link StringEvaluator}. Ported from
|
|
7
|
+
* Mallory's ActionScript `Expression`. (The dead `mx.messaging` import is dropped.)
|
|
8
|
+
*/
|
|
9
|
+
export class Expression {
|
|
10
|
+
private readonly _representation: string;
|
|
11
|
+
private readonly _indeVars: string[];
|
|
12
|
+
|
|
13
|
+
constructor(representation: unknown = "", independentVariables: string[] | null = null) {
|
|
14
|
+
this._representation = String(representation);
|
|
15
|
+
this._indeVars = independentVariables ?? [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
indeVarString(): string {
|
|
19
|
+
return this._indeVars.join(",");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toString(expanded = true, name = "function"): string {
|
|
23
|
+
if (expanded) return `${name}(${this.indeVarString()})=${this._representation}`;
|
|
24
|
+
return this._representation;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Evaluate the expression. `values` are bound to the independent variables
|
|
29
|
+
* (in order); an outer `environment` supplies everything else.
|
|
30
|
+
*/
|
|
31
|
+
evaluate(values: unknown = null, environment: Environment | null = null): unknown {
|
|
32
|
+
const args = Array.isArray(values) ? values : [values];
|
|
33
|
+
|
|
34
|
+
if (environment === null) {
|
|
35
|
+
const subEnvironment = new Environment();
|
|
36
|
+
for (let i = 0; i < this._indeVars.length; i++) {
|
|
37
|
+
subEnvironment.assign(this._indeVars[i] as string, args[i]);
|
|
38
|
+
}
|
|
39
|
+
return StringEvaluator.evaluate(this._representation, subEnvironment);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const subEnvironment = environment.clone();
|
|
43
|
+
for (let i = 0; i < this._indeVars.length; i++) {
|
|
44
|
+
subEnvironment.assign(this._indeVars[i] as string, args[i]);
|
|
45
|
+
}
|
|
46
|
+
return StringEvaluator.evaluate(StringEvaluator.evaluate(this._representation, subEnvironment), environment);
|
|
47
|
+
}
|
|
48
|
+
}
|