document-compute.js 0.0.0 → 1.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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/index.cjs +495 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +451 -0
- package/package.json +91 -2
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DimensionVector, ExactRational, FormulaBindings, Interval, MathExpression, Quantity, SiBaseDimension, SymbolTable } from "document-schema.js";
|
|
2
|
+
//#region src/compute/rational.d.ts
|
|
3
|
+
interface Rational {
|
|
4
|
+
readonly n: bigint;
|
|
5
|
+
readonly d: bigint;
|
|
6
|
+
}
|
|
7
|
+
declare function toRational(value: ExactRational): Rational;
|
|
8
|
+
declare function toExactRational(value: Rational): ExactRational;
|
|
9
|
+
declare function addRational(a: Rational, b: Rational): Rational;
|
|
10
|
+
declare function subtractRational(a: Rational, b: Rational): Rational;
|
|
11
|
+
declare function multiplyRational(a: Rational, b: Rational): Rational;
|
|
12
|
+
declare function divideRational(a: Rational, b: Rational): Rational;
|
|
13
|
+
declare function rationalToNumber(value: Rational): number;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/compute/dimensions.d.ts
|
|
16
|
+
declare function dimensionExponent(dimension: DimensionVector, base: SiBaseDimension): number;
|
|
17
|
+
declare function dimensionsEqual(a: DimensionVector, b: DimensionVector): boolean;
|
|
18
|
+
declare function isDimensionless(dimension: DimensionVector): boolean;
|
|
19
|
+
declare function multiplyDimensions(a: DimensionVector, b: DimensionVector): DimensionVector;
|
|
20
|
+
declare function divideDimensions(a: DimensionVector, b: DimensionVector): DimensionVector;
|
|
21
|
+
declare function scaleDimension(dimension: DimensionVector, k: number): DimensionVector;
|
|
22
|
+
declare function dimensionToString(dimension: DimensionVector): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/compute/errors.d.ts
|
|
25
|
+
declare class IncompatibleDimensionsError extends Error {
|
|
26
|
+
readonly operation: string;
|
|
27
|
+
readonly left: DimensionVector;
|
|
28
|
+
readonly right: DimensionVector;
|
|
29
|
+
constructor(operation: string, left: DimensionVector, right: DimensionVector, detail?: string);
|
|
30
|
+
}
|
|
31
|
+
declare class UnboundSymbolError extends Error {
|
|
32
|
+
readonly symbol: string;
|
|
33
|
+
constructor(symbol: string);
|
|
34
|
+
}
|
|
35
|
+
declare class UnknownUnitError extends Error {
|
|
36
|
+
readonly unit: string;
|
|
37
|
+
constructor(unit: string);
|
|
38
|
+
}
|
|
39
|
+
declare class DivisionByZeroError extends Error {
|
|
40
|
+
readonly operation: string;
|
|
41
|
+
constructor(operation: string, detail: string);
|
|
42
|
+
}
|
|
43
|
+
declare class UnsupportedExpressionError extends Error {
|
|
44
|
+
readonly context: string;
|
|
45
|
+
constructor(context: string, detail: string);
|
|
46
|
+
}
|
|
47
|
+
declare class NumericDomainError extends Error {
|
|
48
|
+
readonly operation: string;
|
|
49
|
+
constructor(operation: string, detail: string);
|
|
50
|
+
}
|
|
51
|
+
declare class NonConvergentSolveError extends Error {
|
|
52
|
+
readonly method: 'bisection' | 'newton';
|
|
53
|
+
readonly iterations: number;
|
|
54
|
+
constructor(method: 'bisection' | 'newton', iterations: number, detail: string);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/compute/quantity.d.ts
|
|
58
|
+
declare function quantity(magnitude: number, dimension?: DimensionVector): Quantity;
|
|
59
|
+
declare function addQuantities(a: Quantity, b: Quantity): Quantity;
|
|
60
|
+
declare function subtractQuantities(a: Quantity, b: Quantity): Quantity;
|
|
61
|
+
declare function multiplyQuantities(a: Quantity, b: Quantity): Quantity;
|
|
62
|
+
declare function divideQuantities(a: Quantity, b: Quantity): Quantity;
|
|
63
|
+
declare function negateQuantity(a: Quantity): Quantity;
|
|
64
|
+
declare function absQuantity(a: Quantity): Quantity;
|
|
65
|
+
declare function powQuantity(base: Quantity, exponent: Quantity): Quantity;
|
|
66
|
+
declare function sqrtQuantity(a: Quantity): Quantity;
|
|
67
|
+
declare function sinQuantity(a: Quantity): Quantity;
|
|
68
|
+
declare function cosQuantity(a: Quantity): Quantity;
|
|
69
|
+
declare function tanQuantity(a: Quantity): Quantity;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/compute/interval.d.ts
|
|
72
|
+
declare function interval(min: number, max: number, dimension?: DimensionVector): Interval;
|
|
73
|
+
declare function pointInterval(magnitude: number, dimension?: DimensionVector): Interval;
|
|
74
|
+
declare function addIntervals(a: Interval, b: Interval): Interval;
|
|
75
|
+
declare function subtractIntervals(a: Interval, b: Interval): Interval;
|
|
76
|
+
declare function multiplyIntervals(a: Interval, b: Interval): Interval;
|
|
77
|
+
declare function divideIntervals(a: Interval, b: Interval): Interval;
|
|
78
|
+
declare function negateInterval(a: Interval): Interval;
|
|
79
|
+
declare function absInterval(a: Interval): Interval;
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/compute/evaluate.d.ts
|
|
82
|
+
type EvaluationResult = Quantity | Interval;
|
|
83
|
+
declare function isInterval(value: EvaluationResult): value is Interval;
|
|
84
|
+
declare function evaluate(expression: MathExpression, bindings: FormulaBindings, context?: SymbolTable): EvaluationResult;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/compute/solve.d.ts
|
|
87
|
+
type SolveMethod = 'bisection' | 'newton';
|
|
88
|
+
interface SolveForOptions {
|
|
89
|
+
/** Which root-finding algorithm to use. Default: 'bisection' (needs no derivative and cannot diverge the way Newton can, so it is the safer default; Newton converges faster once it has a decent initialGuess). */
|
|
90
|
+
method?: SolveMethod;
|
|
91
|
+
/** Convergence threshold on |f(x)| = |evaluate(expression, ...) - targetValue|. Default 1e-9. */
|
|
92
|
+
tolerance?: number;
|
|
93
|
+
/** Iteration budget before giving up with NonConvergentSolveError. Default 100. */
|
|
94
|
+
maxIterations?: number;
|
|
95
|
+
/** Required for 'bisection': an [low, high] bracket whose residuals at the endpoints have opposite signs (the intermediate value theorem is bisection's entire correctness argument -- there is no bracket to fall back on if this does not hold, so a bad bracket throws rather than guessing one). */
|
|
96
|
+
bracket?: [number, number];
|
|
97
|
+
/** Required for 'newton': the starting point the iteration refines from. */
|
|
98
|
+
initialGuess?: number;
|
|
99
|
+
/** The dimension the unknown symbol is bound under at each trial point. Default {} (dimensionless) -- set this when the unknown is not dimensionless, e.g. { length: 1 } to solve for a length in SI-coherent metres. */
|
|
100
|
+
unknownDimension?: DimensionVector;
|
|
101
|
+
/** Step size h for Newton's central-difference derivative estimate (see newton() below). Default 1e-6. */
|
|
102
|
+
derivativeStep?: number;
|
|
103
|
+
}
|
|
104
|
+
declare function solveFor(expression: MathExpression, targetValue: number, unknownSymbol: string, bindings: FormulaBindings, options?: SolveForOptions, context?: SymbolTable): number;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { DivisionByZeroError, EvaluationResult, IncompatibleDimensionsError, NonConvergentSolveError, NumericDomainError, Rational, SolveForOptions, SolveMethod, UnboundSymbolError, UnknownUnitError, UnsupportedExpressionError, absInterval, absQuantity, addIntervals, addQuantities, addRational, cosQuantity, dimensionExponent, dimensionToString, dimensionsEqual, divideDimensions, divideIntervals, divideQuantities, divideRational, evaluate, interval, isDimensionless, isInterval, multiplyDimensions, multiplyIntervals, multiplyQuantities, multiplyRational, negateInterval, negateQuantity, pointInterval, powQuantity, quantity, rationalToNumber, scaleDimension, sinQuantity, solveFor, sqrtQuantity, subtractIntervals, subtractQuantities, subtractRational, tanQuantity, toExactRational, toRational };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DimensionVector, ExactRational, FormulaBindings, Interval, MathExpression, Quantity, SiBaseDimension, SymbolTable } from "document-schema.js";
|
|
2
|
+
//#region src/compute/rational.d.ts
|
|
3
|
+
interface Rational {
|
|
4
|
+
readonly n: bigint;
|
|
5
|
+
readonly d: bigint;
|
|
6
|
+
}
|
|
7
|
+
declare function toRational(value: ExactRational): Rational;
|
|
8
|
+
declare function toExactRational(value: Rational): ExactRational;
|
|
9
|
+
declare function addRational(a: Rational, b: Rational): Rational;
|
|
10
|
+
declare function subtractRational(a: Rational, b: Rational): Rational;
|
|
11
|
+
declare function multiplyRational(a: Rational, b: Rational): Rational;
|
|
12
|
+
declare function divideRational(a: Rational, b: Rational): Rational;
|
|
13
|
+
declare function rationalToNumber(value: Rational): number;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/compute/dimensions.d.ts
|
|
16
|
+
declare function dimensionExponent(dimension: DimensionVector, base: SiBaseDimension): number;
|
|
17
|
+
declare function dimensionsEqual(a: DimensionVector, b: DimensionVector): boolean;
|
|
18
|
+
declare function isDimensionless(dimension: DimensionVector): boolean;
|
|
19
|
+
declare function multiplyDimensions(a: DimensionVector, b: DimensionVector): DimensionVector;
|
|
20
|
+
declare function divideDimensions(a: DimensionVector, b: DimensionVector): DimensionVector;
|
|
21
|
+
declare function scaleDimension(dimension: DimensionVector, k: number): DimensionVector;
|
|
22
|
+
declare function dimensionToString(dimension: DimensionVector): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/compute/errors.d.ts
|
|
25
|
+
declare class IncompatibleDimensionsError extends Error {
|
|
26
|
+
readonly operation: string;
|
|
27
|
+
readonly left: DimensionVector;
|
|
28
|
+
readonly right: DimensionVector;
|
|
29
|
+
constructor(operation: string, left: DimensionVector, right: DimensionVector, detail?: string);
|
|
30
|
+
}
|
|
31
|
+
declare class UnboundSymbolError extends Error {
|
|
32
|
+
readonly symbol: string;
|
|
33
|
+
constructor(symbol: string);
|
|
34
|
+
}
|
|
35
|
+
declare class UnknownUnitError extends Error {
|
|
36
|
+
readonly unit: string;
|
|
37
|
+
constructor(unit: string);
|
|
38
|
+
}
|
|
39
|
+
declare class DivisionByZeroError extends Error {
|
|
40
|
+
readonly operation: string;
|
|
41
|
+
constructor(operation: string, detail: string);
|
|
42
|
+
}
|
|
43
|
+
declare class UnsupportedExpressionError extends Error {
|
|
44
|
+
readonly context: string;
|
|
45
|
+
constructor(context: string, detail: string);
|
|
46
|
+
}
|
|
47
|
+
declare class NumericDomainError extends Error {
|
|
48
|
+
readonly operation: string;
|
|
49
|
+
constructor(operation: string, detail: string);
|
|
50
|
+
}
|
|
51
|
+
declare class NonConvergentSolveError extends Error {
|
|
52
|
+
readonly method: 'bisection' | 'newton';
|
|
53
|
+
readonly iterations: number;
|
|
54
|
+
constructor(method: 'bisection' | 'newton', iterations: number, detail: string);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/compute/quantity.d.ts
|
|
58
|
+
declare function quantity(magnitude: number, dimension?: DimensionVector): Quantity;
|
|
59
|
+
declare function addQuantities(a: Quantity, b: Quantity): Quantity;
|
|
60
|
+
declare function subtractQuantities(a: Quantity, b: Quantity): Quantity;
|
|
61
|
+
declare function multiplyQuantities(a: Quantity, b: Quantity): Quantity;
|
|
62
|
+
declare function divideQuantities(a: Quantity, b: Quantity): Quantity;
|
|
63
|
+
declare function negateQuantity(a: Quantity): Quantity;
|
|
64
|
+
declare function absQuantity(a: Quantity): Quantity;
|
|
65
|
+
declare function powQuantity(base: Quantity, exponent: Quantity): Quantity;
|
|
66
|
+
declare function sqrtQuantity(a: Quantity): Quantity;
|
|
67
|
+
declare function sinQuantity(a: Quantity): Quantity;
|
|
68
|
+
declare function cosQuantity(a: Quantity): Quantity;
|
|
69
|
+
declare function tanQuantity(a: Quantity): Quantity;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/compute/interval.d.ts
|
|
72
|
+
declare function interval(min: number, max: number, dimension?: DimensionVector): Interval;
|
|
73
|
+
declare function pointInterval(magnitude: number, dimension?: DimensionVector): Interval;
|
|
74
|
+
declare function addIntervals(a: Interval, b: Interval): Interval;
|
|
75
|
+
declare function subtractIntervals(a: Interval, b: Interval): Interval;
|
|
76
|
+
declare function multiplyIntervals(a: Interval, b: Interval): Interval;
|
|
77
|
+
declare function divideIntervals(a: Interval, b: Interval): Interval;
|
|
78
|
+
declare function negateInterval(a: Interval): Interval;
|
|
79
|
+
declare function absInterval(a: Interval): Interval;
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/compute/evaluate.d.ts
|
|
82
|
+
type EvaluationResult = Quantity | Interval;
|
|
83
|
+
declare function isInterval(value: EvaluationResult): value is Interval;
|
|
84
|
+
declare function evaluate(expression: MathExpression, bindings: FormulaBindings, context?: SymbolTable): EvaluationResult;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/compute/solve.d.ts
|
|
87
|
+
type SolveMethod = 'bisection' | 'newton';
|
|
88
|
+
interface SolveForOptions {
|
|
89
|
+
/** Which root-finding algorithm to use. Default: 'bisection' (needs no derivative and cannot diverge the way Newton can, so it is the safer default; Newton converges faster once it has a decent initialGuess). */
|
|
90
|
+
method?: SolveMethod;
|
|
91
|
+
/** Convergence threshold on |f(x)| = |evaluate(expression, ...) - targetValue|. Default 1e-9. */
|
|
92
|
+
tolerance?: number;
|
|
93
|
+
/** Iteration budget before giving up with NonConvergentSolveError. Default 100. */
|
|
94
|
+
maxIterations?: number;
|
|
95
|
+
/** Required for 'bisection': an [low, high] bracket whose residuals at the endpoints have opposite signs (the intermediate value theorem is bisection's entire correctness argument -- there is no bracket to fall back on if this does not hold, so a bad bracket throws rather than guessing one). */
|
|
96
|
+
bracket?: [number, number];
|
|
97
|
+
/** Required for 'newton': the starting point the iteration refines from. */
|
|
98
|
+
initialGuess?: number;
|
|
99
|
+
/** The dimension the unknown symbol is bound under at each trial point. Default {} (dimensionless) -- set this when the unknown is not dimensionless, e.g. { length: 1 } to solve for a length in SI-coherent metres. */
|
|
100
|
+
unknownDimension?: DimensionVector;
|
|
101
|
+
/** Step size h for Newton's central-difference derivative estimate (see newton() below). Default 1e-6. */
|
|
102
|
+
derivativeStep?: number;
|
|
103
|
+
}
|
|
104
|
+
declare function solveFor(expression: MathExpression, targetValue: number, unknownSymbol: string, bindings: FormulaBindings, options?: SolveForOptions, context?: SymbolTable): number;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { DivisionByZeroError, EvaluationResult, IncompatibleDimensionsError, NonConvergentSolveError, NumericDomainError, Rational, SolveForOptions, SolveMethod, UnboundSymbolError, UnknownUnitError, UnsupportedExpressionError, absInterval, absQuantity, addIntervals, addQuantities, addRational, cosQuantity, dimensionExponent, dimensionToString, dimensionsEqual, divideDimensions, divideIntervals, divideQuantities, divideRational, evaluate, interval, isDimensionless, isInterval, multiplyDimensions, multiplyIntervals, multiplyQuantities, multiplyRational, negateInterval, negateQuantity, pointInterval, powQuantity, quantity, rationalToNumber, scaleDimension, sinQuantity, solveFor, sqrtQuantity, subtractIntervals, subtractQuantities, subtractRational, tanQuantity, toExactRational, toRational };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { SI_BASE_DIMENSIONS } from "document-schema.js";
|
|
2
|
+
//#region src/compute/rational.ts
|
|
3
|
+
function bigintOfCanonicalDigits(digits) {
|
|
4
|
+
return BigInt(digits);
|
|
5
|
+
}
|
|
6
|
+
function toRational(value) {
|
|
7
|
+
return {
|
|
8
|
+
n: bigintOfCanonicalDigits(value.numerator),
|
|
9
|
+
d: bigintOfCanonicalDigits(value.denominator)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function gcd(a, b) {
|
|
13
|
+
let x = a < 0n ? -a : a;
|
|
14
|
+
let y = b < 0n ? -b : b;
|
|
15
|
+
while (y !== 0n) [x, y] = [y, x % y];
|
|
16
|
+
return x === 0n ? 1n : x;
|
|
17
|
+
}
|
|
18
|
+
function reduce(n, d) {
|
|
19
|
+
if (d === 0n) throw new RangeError("rational.ts: denominator must not be zero");
|
|
20
|
+
if (n === 0n) return {
|
|
21
|
+
n: 0n,
|
|
22
|
+
d: 1n
|
|
23
|
+
};
|
|
24
|
+
const sign = d < 0n ? -1n : 1n;
|
|
25
|
+
const num = n * sign;
|
|
26
|
+
const den = d * sign;
|
|
27
|
+
const g = gcd(num, den);
|
|
28
|
+
return {
|
|
29
|
+
n: num / g,
|
|
30
|
+
d: den / g
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function toExactRational(value) {
|
|
34
|
+
const reduced = reduce(value.n, value.d);
|
|
35
|
+
return {
|
|
36
|
+
numerator: reduced.n.toString(),
|
|
37
|
+
denominator: reduced.d.toString()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function addRational(a, b) {
|
|
41
|
+
return reduce(a.n * b.d + b.n * a.d, a.d * b.d);
|
|
42
|
+
}
|
|
43
|
+
function subtractRational(a, b) {
|
|
44
|
+
return reduce(a.n * b.d - b.n * a.d, a.d * b.d);
|
|
45
|
+
}
|
|
46
|
+
function multiplyRational(a, b) {
|
|
47
|
+
return reduce(a.n * b.n, a.d * b.d);
|
|
48
|
+
}
|
|
49
|
+
function divideRational(a, b) {
|
|
50
|
+
if (b.n === 0n) throw new RangeError("rational.ts: division by zero");
|
|
51
|
+
return reduce(a.n * b.d, a.d * b.n);
|
|
52
|
+
}
|
|
53
|
+
function rationalToNumber(value) {
|
|
54
|
+
return Number(value.n) / Number(value.d);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/compute/dimensions.ts
|
|
58
|
+
function dimensionExponent(dimension, base) {
|
|
59
|
+
return dimension[base] ?? 0;
|
|
60
|
+
}
|
|
61
|
+
function dimensionsEqual(a, b) {
|
|
62
|
+
return SI_BASE_DIMENSIONS.every((base) => dimensionExponent(a, base) === dimensionExponent(b, base));
|
|
63
|
+
}
|
|
64
|
+
function isDimensionless(dimension) {
|
|
65
|
+
return SI_BASE_DIMENSIONS.every((base) => dimensionExponent(dimension, base) === 0);
|
|
66
|
+
}
|
|
67
|
+
function combine(a, b, op) {
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const base of SI_BASE_DIMENSIONS) {
|
|
70
|
+
const value = op(dimensionExponent(a, base), dimensionExponent(b, base));
|
|
71
|
+
if (value !== 0) result[base] = value;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function multiplyDimensions(a, b) {
|
|
76
|
+
return combine(a, b, (x, y) => x + y);
|
|
77
|
+
}
|
|
78
|
+
function divideDimensions(a, b) {
|
|
79
|
+
return combine(a, b, (x, y) => x - y);
|
|
80
|
+
}
|
|
81
|
+
function scaleDimension(dimension, k) {
|
|
82
|
+
const result = {};
|
|
83
|
+
for (const base of SI_BASE_DIMENSIONS) {
|
|
84
|
+
const exponent = dimensionExponent(dimension, base);
|
|
85
|
+
if (exponent === 0) continue;
|
|
86
|
+
const scaled = exponent * k;
|
|
87
|
+
if (!Number.isInteger(scaled)) throw new RangeError(`dimensions.ts: scaling '${base}' by ${k} does not land on an integer exponent`);
|
|
88
|
+
result[base] = scaled;
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function dimensionToString(dimension) {
|
|
93
|
+
const parts = SI_BASE_DIMENSIONS.filter((base) => dimensionExponent(dimension, base) !== 0).map((base) => `${base}^${dimensionExponent(dimension, base)}`);
|
|
94
|
+
return parts.length === 0 ? "dimensionless" : parts.join("·");
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/compute/errors.ts
|
|
98
|
+
var IncompatibleDimensionsError = class extends Error {
|
|
99
|
+
operation;
|
|
100
|
+
left;
|
|
101
|
+
right;
|
|
102
|
+
constructor(operation, left, right, detail) {
|
|
103
|
+
super(`'${operation}' requires compatible dimensions, got ${dimensionToString(left)} and ${dimensionToString(right)}` + (detail === void 0 ? "." : ` (${detail}).`));
|
|
104
|
+
this.name = "IncompatibleDimensionsError";
|
|
105
|
+
this.operation = operation;
|
|
106
|
+
this.left = left;
|
|
107
|
+
this.right = right;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var UnboundSymbolError = class extends Error {
|
|
111
|
+
symbol;
|
|
112
|
+
constructor(symbol) {
|
|
113
|
+
super(`symbol '${symbol}' has no entry in the supplied bindings.`);
|
|
114
|
+
this.name = "UnboundSymbolError";
|
|
115
|
+
this.symbol = symbol;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var UnknownUnitError = class extends Error {
|
|
119
|
+
unit;
|
|
120
|
+
constructor(unit) {
|
|
121
|
+
super(`unit '${unit}' is not registered in the supplied symbol table's units.`);
|
|
122
|
+
this.name = "UnknownUnitError";
|
|
123
|
+
this.unit = unit;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var DivisionByZeroError = class extends Error {
|
|
127
|
+
operation;
|
|
128
|
+
constructor(operation, detail) {
|
|
129
|
+
super(`'${operation}': division by zero (${detail}).`);
|
|
130
|
+
this.name = "DivisionByZeroError";
|
|
131
|
+
this.operation = operation;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var UnsupportedExpressionError = class extends Error {
|
|
135
|
+
context;
|
|
136
|
+
constructor(context, detail) {
|
|
137
|
+
super(`${context}: ${detail}.`);
|
|
138
|
+
this.name = "UnsupportedExpressionError";
|
|
139
|
+
this.context = context;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var NumericDomainError = class extends Error {
|
|
143
|
+
operation;
|
|
144
|
+
constructor(operation, detail) {
|
|
145
|
+
super(`'${operation}': ${detail}.`);
|
|
146
|
+
this.name = "NumericDomainError";
|
|
147
|
+
this.operation = operation;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var NonConvergentSolveError = class extends Error {
|
|
151
|
+
method;
|
|
152
|
+
iterations;
|
|
153
|
+
constructor(method, iterations, detail) {
|
|
154
|
+
super(`solveFor (${method}) did not converge after ${iterations} iteration(s): ${detail}.`);
|
|
155
|
+
this.name = "NonConvergentSolveError";
|
|
156
|
+
this.method = method;
|
|
157
|
+
this.iterations = iterations;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/compute/quantity.ts
|
|
162
|
+
function quantity(magnitude, dimension = {}) {
|
|
163
|
+
return {
|
|
164
|
+
kind: "quantity",
|
|
165
|
+
magnitude,
|
|
166
|
+
dimension
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function addQuantities(a, b) {
|
|
170
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:add", a.dimension, b.dimension);
|
|
171
|
+
return quantity(a.magnitude + b.magnitude, a.dimension);
|
|
172
|
+
}
|
|
173
|
+
function subtractQuantities(a, b) {
|
|
174
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:subtract", a.dimension, b.dimension);
|
|
175
|
+
return quantity(a.magnitude - b.magnitude, a.dimension);
|
|
176
|
+
}
|
|
177
|
+
function multiplyQuantities(a, b) {
|
|
178
|
+
return quantity(a.magnitude * b.magnitude, multiplyDimensions(a.dimension, b.dimension));
|
|
179
|
+
}
|
|
180
|
+
function divideQuantities(a, b) {
|
|
181
|
+
if (b.magnitude === 0) throw new DivisionByZeroError("math:divide", `divisor magnitude is exactly zero (dividend magnitude ${a.magnitude})`);
|
|
182
|
+
return quantity(a.magnitude / b.magnitude, divideDimensions(a.dimension, b.dimension));
|
|
183
|
+
}
|
|
184
|
+
function negateQuantity(a) {
|
|
185
|
+
return quantity(-a.magnitude, a.dimension);
|
|
186
|
+
}
|
|
187
|
+
function absQuantity(a) {
|
|
188
|
+
return quantity(Math.abs(a.magnitude), a.dimension);
|
|
189
|
+
}
|
|
190
|
+
function powQuantity(base, exponent) {
|
|
191
|
+
if (!isDimensionless(exponent.dimension)) throw new IncompatibleDimensionsError("math:pow", exponent.dimension, {}, "the exponent must be dimensionless");
|
|
192
|
+
if (isDimensionless(base.dimension)) return quantity(Math.pow(base.magnitude, exponent.magnitude), {});
|
|
193
|
+
if (!Number.isInteger(exponent.magnitude)) throw new IncompatibleDimensionsError("math:pow", base.dimension, {}, "a dimensioned base can only be raised to an integer power");
|
|
194
|
+
return quantity(Math.pow(base.magnitude, exponent.magnitude), scaleDimension(base.dimension, exponent.magnitude));
|
|
195
|
+
}
|
|
196
|
+
function sqrtQuantity(a) {
|
|
197
|
+
if (a.magnitude < 0) throw new NumericDomainError("math:sqrt", `magnitude must be non-negative, got ${a.magnitude}`);
|
|
198
|
+
let dimension;
|
|
199
|
+
try {
|
|
200
|
+
dimension = scaleDimension(a.dimension, .5);
|
|
201
|
+
} catch {
|
|
202
|
+
throw new IncompatibleDimensionsError("math:sqrt", a.dimension, {}, "every exponent must be even for the dimension to have an exact square root");
|
|
203
|
+
}
|
|
204
|
+
return quantity(Math.sqrt(a.magnitude), dimension);
|
|
205
|
+
}
|
|
206
|
+
function requireDimensionless(a, operator) {
|
|
207
|
+
if (!isDimensionless(a.dimension)) throw new IncompatibleDimensionsError(operator, a.dimension, {}, "trigonometric functions take a dimensionless (radian) argument");
|
|
208
|
+
}
|
|
209
|
+
function sinQuantity(a) {
|
|
210
|
+
requireDimensionless(a, "math:sin");
|
|
211
|
+
return quantity(Math.sin(a.magnitude), {});
|
|
212
|
+
}
|
|
213
|
+
function cosQuantity(a) {
|
|
214
|
+
requireDimensionless(a, "math:cos");
|
|
215
|
+
return quantity(Math.cos(a.magnitude), {});
|
|
216
|
+
}
|
|
217
|
+
function tanQuantity(a) {
|
|
218
|
+
requireDimensionless(a, "math:tan");
|
|
219
|
+
return quantity(Math.tan(a.magnitude), {});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/compute/interval.ts
|
|
223
|
+
function interval(min, max, dimension = {}) {
|
|
224
|
+
if (min > max) throw new RangeError(`interval: min (${min}) must not exceed max (${max})`);
|
|
225
|
+
return {
|
|
226
|
+
kind: "interval",
|
|
227
|
+
min,
|
|
228
|
+
max,
|
|
229
|
+
dimension
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function pointInterval(magnitude, dimension = {}) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "interval",
|
|
235
|
+
min: magnitude,
|
|
236
|
+
max: magnitude,
|
|
237
|
+
dimension
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function addIntervals(a, b) {
|
|
241
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:add", a.dimension, b.dimension);
|
|
242
|
+
return interval(a.min + b.min, a.max + b.max, a.dimension);
|
|
243
|
+
}
|
|
244
|
+
function subtractIntervals(a, b) {
|
|
245
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:subtract", a.dimension, b.dimension);
|
|
246
|
+
return interval(a.min - b.max, a.max - b.min, a.dimension);
|
|
247
|
+
}
|
|
248
|
+
function multiplyIntervals(a, b) {
|
|
249
|
+
const corners = [
|
|
250
|
+
a.min * b.min,
|
|
251
|
+
a.min * b.max,
|
|
252
|
+
a.max * b.min,
|
|
253
|
+
a.max * b.max
|
|
254
|
+
];
|
|
255
|
+
return interval(Math.min(...corners), Math.max(...corners), multiplyDimensions(a.dimension, b.dimension));
|
|
256
|
+
}
|
|
257
|
+
function divideIntervals(a, b) {
|
|
258
|
+
if (b.min <= 0 && b.max >= 0) throw new DivisionByZeroError("math:divide", `divisor interval [${b.min}, ${b.max}] contains zero`);
|
|
259
|
+
const reciprocalMin = 1 / b.max;
|
|
260
|
+
const reciprocalMax = 1 / b.min;
|
|
261
|
+
const corners = [
|
|
262
|
+
a.min * reciprocalMin,
|
|
263
|
+
a.min * reciprocalMax,
|
|
264
|
+
a.max * reciprocalMin,
|
|
265
|
+
a.max * reciprocalMax
|
|
266
|
+
];
|
|
267
|
+
return interval(Math.min(...corners), Math.max(...corners), divideDimensions(a.dimension, b.dimension));
|
|
268
|
+
}
|
|
269
|
+
function negateInterval(a) {
|
|
270
|
+
return interval(-a.max, -a.min, a.dimension);
|
|
271
|
+
}
|
|
272
|
+
function absInterval(a) {
|
|
273
|
+
if (a.min >= 0) return a;
|
|
274
|
+
if (a.max <= 0) return negateInterval(a);
|
|
275
|
+
return interval(0, Math.max(-a.min, a.max), a.dimension);
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/compute/evaluate.ts
|
|
279
|
+
const EMPTY_SYMBOL_TABLE$1 = {
|
|
280
|
+
symbols: [],
|
|
281
|
+
units: []
|
|
282
|
+
};
|
|
283
|
+
function isInterval(value) {
|
|
284
|
+
return value.kind === "interval";
|
|
285
|
+
}
|
|
286
|
+
function toInterval(value) {
|
|
287
|
+
return isInterval(value) ? value : pointInterval(value.magnitude, value.dimension);
|
|
288
|
+
}
|
|
289
|
+
function asQuantity(value, context) {
|
|
290
|
+
if (isInterval(value)) throw new UnsupportedExpressionError(context, "this position requires a plain Quantity, not an Interval");
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
const BINARY_OPERATORS = {
|
|
294
|
+
"math:add": {
|
|
295
|
+
quantity: addQuantities,
|
|
296
|
+
interval: addIntervals
|
|
297
|
+
},
|
|
298
|
+
"math:subtract": {
|
|
299
|
+
quantity: subtractQuantities,
|
|
300
|
+
interval: subtractIntervals
|
|
301
|
+
},
|
|
302
|
+
"math:multiply": {
|
|
303
|
+
quantity: multiplyQuantities,
|
|
304
|
+
interval: multiplyIntervals
|
|
305
|
+
},
|
|
306
|
+
"math:divide": {
|
|
307
|
+
quantity: divideQuantities,
|
|
308
|
+
interval: divideIntervals
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const UNARY_OPERATORS = {
|
|
312
|
+
"math:negate": {
|
|
313
|
+
quantity: negateQuantity,
|
|
314
|
+
interval: negateInterval
|
|
315
|
+
},
|
|
316
|
+
"math:abs": {
|
|
317
|
+
quantity: absQuantity,
|
|
318
|
+
interval: absInterval
|
|
319
|
+
},
|
|
320
|
+
"math:sqrt": { quantity: sqrtQuantity },
|
|
321
|
+
"math:sin": { quantity: sinQuantity },
|
|
322
|
+
"math:cos": { quantity: cosQuantity },
|
|
323
|
+
"math:tan": { quantity: tanQuantity }
|
|
324
|
+
};
|
|
325
|
+
function evaluate(expression, bindings, context = EMPTY_SYMBOL_TABLE$1) {
|
|
326
|
+
switch (expression.kind) {
|
|
327
|
+
case "num": return quantity(rationalToNumber(toRational(expression)), {});
|
|
328
|
+
case "qty": return evaluateQty(expression, context);
|
|
329
|
+
case "sym": {
|
|
330
|
+
const bound = bindings[expression.id];
|
|
331
|
+
if (bound === void 0) throw new UnboundSymbolError(expression.id);
|
|
332
|
+
return bound;
|
|
333
|
+
}
|
|
334
|
+
case "app": return evaluateApp(expression, bindings, context);
|
|
335
|
+
case "sum":
|
|
336
|
+
case "prod": return evaluateBinder(expression, bindings, context);
|
|
337
|
+
case "matrix": throw new UnsupportedExpressionError("evaluate", "matrix-valued expressions are out of scope for this pass -- document-compute.js evaluates scalar Quantity/Interval values only");
|
|
338
|
+
case "unparsed": throw new UnsupportedExpressionError("evaluate", `this node is source LaTeX ("${expression.latex}") document-schema.js's lowering could not represent structurally, so there is nothing to evaluate`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function evaluateQty(node, context) {
|
|
342
|
+
const unit = context.units.find((entry) => entry.id === node.unit);
|
|
343
|
+
if (unit === void 0) throw new UnknownUnitError(node.unit);
|
|
344
|
+
let siValue = multiplyRational(toRational(node.value), toRational(unit.factorToSi));
|
|
345
|
+
if (unit.offsetToSi !== void 0) siValue = addRational(siValue, toRational(unit.offsetToSi));
|
|
346
|
+
return quantity(rationalToNumber(siValue), unit.dimension);
|
|
347
|
+
}
|
|
348
|
+
function evaluateApp(node, bindings, context) {
|
|
349
|
+
const args = node.args.map((arg) => evaluate(arg, bindings, context));
|
|
350
|
+
const binary = BINARY_OPERATORS[node.operator];
|
|
351
|
+
if (binary !== void 0) {
|
|
352
|
+
if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 2 arguments, got ${args.length}`);
|
|
353
|
+
const [left, right] = args;
|
|
354
|
+
if (isInterval(left) || isInterval(right)) return binary.interval(toInterval(left), toInterval(right));
|
|
355
|
+
return binary.quantity(left, right);
|
|
356
|
+
}
|
|
357
|
+
const unary = UNARY_OPERATORS[node.operator];
|
|
358
|
+
if (unary !== void 0) {
|
|
359
|
+
if (args.length !== 1) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 1 argument, got ${args.length}`);
|
|
360
|
+
const [only] = args;
|
|
361
|
+
if (isInterval(only)) {
|
|
362
|
+
if (unary.interval === void 0) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' has no interval rule in this pass`);
|
|
363
|
+
return unary.interval(only);
|
|
364
|
+
}
|
|
365
|
+
return unary.quantity(only);
|
|
366
|
+
}
|
|
367
|
+
if (node.operator === "math:pow") {
|
|
368
|
+
if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `'math:pow' takes exactly 2 arguments, got ${args.length}`);
|
|
369
|
+
const [base, exponent] = args;
|
|
370
|
+
return powQuantity(asQuantity(base, "evaluate"), asQuantity(exponent, "evaluate"));
|
|
371
|
+
}
|
|
372
|
+
throw new UnsupportedExpressionError("evaluate", `unknown operator '${node.operator}'`);
|
|
373
|
+
}
|
|
374
|
+
function evaluateBinder(node, bindings, context) {
|
|
375
|
+
const lower = asQuantity(evaluate(node.lower, bindings, context), `evaluate:${node.kind}`);
|
|
376
|
+
const upper = asQuantity(evaluate(node.upper, bindings, context), `evaluate:${node.kind}`);
|
|
377
|
+
if (!isDimensionless(lower.dimension) || !isDimensionless(upper.dimension)) throw new IncompatibleDimensionsError(`math:${node.kind}`, lower.dimension, upper.dimension, "binder bounds must be dimensionless");
|
|
378
|
+
if (!Number.isInteger(lower.magnitude) || !Number.isInteger(upper.magnitude)) throw new UnsupportedExpressionError(`evaluate:${node.kind}`, "binder bounds must evaluate to integers");
|
|
379
|
+
let accumulator = node.kind === "sum" ? quantity(0, {}) : quantity(1, {});
|
|
380
|
+
for (let i = lower.magnitude; i <= upper.magnitude; i += 1) {
|
|
381
|
+
const bodyBindings = {
|
|
382
|
+
...bindings,
|
|
383
|
+
[node.binder]: quantity(i, {})
|
|
384
|
+
};
|
|
385
|
+
const bodyValue = asQuantity(evaluate(node.body, bodyBindings, context), `evaluate:${node.kind}`);
|
|
386
|
+
accumulator = node.kind === "sum" ? addQuantities(accumulator, bodyValue) : multiplyQuantities(accumulator, bodyValue);
|
|
387
|
+
}
|
|
388
|
+
return accumulator;
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/compute/solve.ts
|
|
392
|
+
const DEFAULT_TOLERANCE = 1e-9;
|
|
393
|
+
const DEFAULT_MAX_ITERATIONS = 100;
|
|
394
|
+
const DEFAULT_DERIVATIVE_STEP = 1e-6;
|
|
395
|
+
const EMPTY_SYMBOL_TABLE = {
|
|
396
|
+
symbols: [],
|
|
397
|
+
units: []
|
|
398
|
+
};
|
|
399
|
+
function residualFn(expression, targetValue, unknownSymbol, bindings, context, dimension) {
|
|
400
|
+
return (x) => {
|
|
401
|
+
const result = evaluate(expression, {
|
|
402
|
+
...bindings,
|
|
403
|
+
[unknownSymbol]: quantity(x, dimension)
|
|
404
|
+
}, context);
|
|
405
|
+
if (result.kind !== "quantity") throw new UnsupportedExpressionError("solveFor", "the expression must evaluate to a plain Quantity, not an Interval");
|
|
406
|
+
return result.magnitude - targetValue;
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function solveFor(expression, targetValue, unknownSymbol, bindings, options = {}, context = EMPTY_SYMBOL_TABLE) {
|
|
410
|
+
const method = options.method ?? "bisection";
|
|
411
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE;
|
|
412
|
+
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
413
|
+
const f = residualFn(expression, targetValue, unknownSymbol, bindings, context, options.unknownDimension ?? {});
|
|
414
|
+
if (method === "bisection") return bisection(f, options.bracket, tolerance, maxIterations);
|
|
415
|
+
return newton(f, options.initialGuess, tolerance, maxIterations, options.derivativeStep ?? DEFAULT_DERIVATIVE_STEP);
|
|
416
|
+
}
|
|
417
|
+
function bisection(f, bracket, tolerance, maxIterations) {
|
|
418
|
+
if (bracket === void 0) throw new UnsupportedExpressionError("solveFor", "method 'bisection' requires options.bracket: [low, high]");
|
|
419
|
+
let [low, high] = bracket;
|
|
420
|
+
let fLow = f(low);
|
|
421
|
+
const fHigh0 = f(high);
|
|
422
|
+
if (Math.abs(fLow) < tolerance) return low;
|
|
423
|
+
if (Math.abs(fHigh0) < tolerance) return high;
|
|
424
|
+
if (fLow > 0 === fHigh0 > 0) throw new NonConvergentSolveError("bisection", 0, `residual at the bracket endpoints does not change sign (f(${low})=${fLow}, f(${high})=${fHigh0}) -- bisection needs a bracket straddling the root`);
|
|
425
|
+
for (let i = 0; i < maxIterations; i += 1) {
|
|
426
|
+
const mid = (low + high) / 2;
|
|
427
|
+
const fMid = f(mid);
|
|
428
|
+
if (Math.abs(fMid) < tolerance) return mid;
|
|
429
|
+
if (fMid > 0 === fLow > 0) {
|
|
430
|
+
low = mid;
|
|
431
|
+
fLow = fMid;
|
|
432
|
+
} else high = mid;
|
|
433
|
+
}
|
|
434
|
+
throw new NonConvergentSolveError("bisection", maxIterations, `residual still exceeds tolerance ${tolerance} after ${maxIterations} iterations`);
|
|
435
|
+
}
|
|
436
|
+
function newton(f, initialGuess, tolerance, maxIterations, h) {
|
|
437
|
+
if (initialGuess === void 0) throw new UnsupportedExpressionError("solveFor", "method 'newton' requires options.initialGuess");
|
|
438
|
+
let x = initialGuess;
|
|
439
|
+
for (let i = 0; i < maxIterations; i += 1) {
|
|
440
|
+
const fx = f(x);
|
|
441
|
+
if (Math.abs(fx) < tolerance) return x;
|
|
442
|
+
const derivative = (f(x + h) - f(x - h)) / (2 * h);
|
|
443
|
+
if (!Number.isFinite(derivative) || Math.abs(derivative) < 1e-14) throw new NonConvergentSolveError("newton", i, `the numeric derivative vanished or diverged near x=${x}`);
|
|
444
|
+
const next = x - fx / derivative;
|
|
445
|
+
if (!Number.isFinite(next)) throw new NonConvergentSolveError("newton", i, `the iteration diverged to a non-finite value near x=${x}`);
|
|
446
|
+
x = next;
|
|
447
|
+
}
|
|
448
|
+
throw new NonConvergentSolveError("newton", maxIterations, `residual still exceeds tolerance ${tolerance} after ${maxIterations} iterations`);
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
export { DivisionByZeroError, IncompatibleDimensionsError, NonConvergentSolveError, NumericDomainError, UnboundSymbolError, UnknownUnitError, UnsupportedExpressionError, absInterval, absQuantity, addIntervals, addQuantities, addRational, cosQuantity, dimensionExponent, dimensionToString, dimensionsEqual, divideDimensions, divideIntervals, divideQuantities, divideRational, evaluate, interval, isDimensionless, isInterval, multiplyDimensions, multiplyIntervals, multiplyQuantities, multiplyRational, negateInterval, negateQuantity, pointInterval, powQuantity, quantity, rationalToNumber, scaleDimension, sinQuantity, solveFor, sqrtQuantity, subtractIntervals, subtractQuantities, subtractRational, tanQuantity, toExactRational, toRational };
|