document-compute.js 0.0.0 → 1.0.1
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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph Mearman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# document-compute.js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/documents.js/tree/main/packages/document-compute.js) [](https://www.npmjs.com/package/document-compute.js) [](https://www.npmjs.com/package/document-compute.js) [](https://github.com/ExaDev/documents.js/actions)
|
|
4
|
+
|
|
5
|
+
> A units-typed, tree-walking evaluator for `document-schema.js`'s `MathExpression` — `evaluate()` for point values and bounded intervals over the same interpreter, `solveFor()` for numeric root-finding (bisection and Newton's method) on one unknown. Exact-rational arithmetic for unit-conversion factors, so a chain of registry conversions never accumulates floating-point drift. The compute package for the [documents.js family](https://github.com/ExaDev). Worker-isomorphic: the same code runs under Node and inside a Cloudflare Workers isolate.
|
|
6
|
+
|
|
7
|
+
Created for [ExaDev/documents.js#573](https://github.com/ExaDev/documents.js/issues/573): a document's formula is stored as a `MathExpression` tree (`document-schema.js`'s `src/math.ts`, [ExaDev/document-schema.js#15](https://github.com/ExaDev/document-schema.js/issues/15)) — the semantic half of a `ContentFormula`, alongside the LaTeX a renderer serialises verbatim. Storing that tree buys nothing on its own; a document that states a formula and then reports its computed answer needs something that actually walks the tree and produces a number, unit-aware, without silently mixing dimensions that don't belong together. This package is that something: one interpreter, reused unchanged across three shapes of the same problem — a point value, a bounded interval, and (via root-finding) an unknown to solve for.
|
|
8
|
+
|
|
9
|
+
## Getting started
|
|
10
|
+
|
|
11
|
+
Requires Node.js `>=20` and pnpm `11.6.0`.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm install
|
|
15
|
+
pnpm build # tsdown -> dist/ (ESM + CJS + .d.ts)
|
|
16
|
+
pnpm typecheck # tsc -p tsconfig.json && tsc -p tsconfig.node.json (dual tsconfig)
|
|
17
|
+
pnpm lint # eslint . --fix --cache --max-warnings 0
|
|
18
|
+
pnpm test # vitest run
|
|
19
|
+
pnpm test:watch # vitest
|
|
20
|
+
pnpm test:workers # vitest run --config vitest.workers.config.ts, inside a real Cloudflare Workers (workerd) isolate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To run a single test file, pass its path to vitest directly, e.g. `pnpm exec vitest run src/compute/evaluate.test.ts`.
|
|
24
|
+
|
|
25
|
+
## What it provides
|
|
26
|
+
|
|
27
|
+
| Module | Exports |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `compute/rational` | `Rational`, `toRational`, `toExactRational`, `addRational`, `subtractRational`, `multiplyRational`, `divideRational`, `rationalToNumber` |
|
|
30
|
+
| `compute/dimensions` | `dimensionExponent`, `dimensionsEqual`, `isDimensionless`, `multiplyDimensions`, `divideDimensions`, `scaleDimension`, `dimensionToString` |
|
|
31
|
+
| `compute/quantity` | `quantity`, `addQuantities`, `subtractQuantities`, `multiplyQuantities`, `divideQuantities`, `negateQuantity`, `absQuantity`, `powQuantity`, `sqrtQuantity`, `sinQuantity`, `cosQuantity`, `tanQuantity` |
|
|
32
|
+
| `compute/interval` | `interval`, `pointInterval`, `addIntervals`, `subtractIntervals`, `multiplyIntervals`, `divideIntervals`, `negateInterval`, `absInterval` |
|
|
33
|
+
| `compute/evaluate` | `evaluate`, `EvaluationResult`, `isInterval` |
|
|
34
|
+
| `compute/solve` | `solveFor`, `SolveMethod`, `SolveForOptions` |
|
|
35
|
+
| `compute/errors` | `IncompatibleDimensionsError`, `UnboundSymbolError`, `UnknownUnitError`, `DivisionByZeroError`, `UnsupportedExpressionError`, `NumericDomainError`, `NonConvergentSolveError` |
|
|
36
|
+
|
|
37
|
+
Every module in the table is re-exported from the package root, so its exports import from `'document-compute.js'` directly.
|
|
38
|
+
|
|
39
|
+
The value types this evaluator consumes and produces — `Quantity`, `Interval`, `EvaluationValue`, `FormulaBindings`, and their Zod schemas — are not defined here: they are typed contracts in `document-schema.js` itself (`src/math.ts`, beside `MathExpression`), so evaluation inputs are schema-validated shapes like everything else in that package. Import them from `'document-schema.js'` the same way this package does:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { evaluate } from 'document-compute.js';
|
|
43
|
+
import type { FormulaBindings, MathExpression } from 'document-schema.js';
|
|
44
|
+
|
|
45
|
+
// F = m * a
|
|
46
|
+
const force: MathExpression = {
|
|
47
|
+
kind: 'app',
|
|
48
|
+
operator: 'math:multiply',
|
|
49
|
+
args: [{ kind: 'sym', id: 'm' }, { kind: 'sym', id: 'a' }],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const bindings: FormulaBindings = {
|
|
53
|
+
m: { kind: 'quantity', magnitude: 2, dimension: { mass: 1 } },
|
|
54
|
+
a: { kind: 'quantity', magnitude: 3, dimension: { length: 1, time: -2 } },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = evaluate(force, bindings);
|
|
58
|
+
// { kind: 'quantity', magnitude: 6, dimension: { mass: 1, length: 1, time: -2 } }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`evaluate` never returns a failure inside its result: a successful call returns a plain `Quantity | Interval` (see below on why the return type is not literally `Quantity | Interval | error`), and every real failure throws one of `compute/errors`' own classes — the same idiom `document-schema.js`'s `schema-io.ts` and `archive-codec`'s `CompoundFileFormatError`/`ArchiveWalkLimitError` already use: a named, catchable `Error` subclass, never a `{ ok, error }` wrapper folded into the return type.
|
|
62
|
+
|
|
63
|
+
## Units as the type system
|
|
64
|
+
|
|
65
|
+
Every `Quantity` this evaluator produces carries a `dimension` — an SI exponent vector reusing `document-schema.js`'s own `DimensionVector` (`{ length: 1, time: -1 }` for speed, `{}` for dimensionless) — alongside a plain `magnitude`. Adding or subtracting two quantities whose dimensions don't match is not a number this package will produce: `addQuantities`/`subtractQuantities` (and the identical rule inside `evaluate` for `math:add`/`math:subtract`) throw `IncompatibleDimensionsError` rather than returning a value that happens to be wrong. Multiplication and division are always dimensionally defined — they combine dimension vectors by adding or subtracting exponents (`compute/dimensions.ts`'s `multiplyDimensions`/`divideDimensions`) — so there is nothing to reject there, only a resulting dimension to compute. `powQuantity`/`sqrtQuantity` extend the same rule to exponents: a dimensionless base tolerates any real exponent, a dimensioned one only an integer power whose scaled exponents stay integers (`DimensionVectorSchema` requires integers, so `sqrt` of `length^1` has no answer and throws — `IncompatibleDimensionsError` again, not a fractional dimension nobody asked for).
|
|
66
|
+
|
|
67
|
+
A `MathExpression`'s `'qty'` leaf carries an exact value plus a unit-registry id (`document-schema.js`'s `MathQty`/`MathUnit`, resolved against the `SymbolTable` passed as `evaluate`'s third argument); `evaluateQty` (`compute/evaluate.ts`) resolves that id, then computes `si_value = value * factorToSi + offsetToSi` — **entirely in exact BigInt rational arithmetic** (`compute/rational.ts`), converting to a plain JS `number` exactly once, at the moment the resolved SI-coherent magnitude enters the evaluator as a `Quantity`. That is the one deliberate exactness boundary in this package: a chain of unit-registry conversions (feet to metres, an affine temperature scale, a per-unit-normalised power-system quantity) never compounds floating-point rounding the way repeated `Number` multiplication would, because every step upstream of that single conversion is bit-exact BigInt arithmetic, reduced to lowest terms at every operation. Downstream of that boundary — ordinary `+`/`-`/`*`/`/` between already-resolved `Quantity` magnitudes, `sin`/`cos`/`sqrt`, a `solveFor` root — is plain floating point, because those results are not exact in general (there is no exact rational `sin(1)`), and holding them to bit-exactness would be false precision, not a stronger guarantee. `QuantitySchema`'s own field comment on `magnitude` states this trade-off; it is a judgement call this package makes deliberately, not an oversight.
|
|
68
|
+
|
|
69
|
+
## Interval arithmetic, over the same evaluator
|
|
70
|
+
|
|
71
|
+
The issue's own example is a compliance region: `0.87 <= cos(phi) <= 1`. Rather than adding a second evaluator for "a formula, but with ranges," `FormulaBindings` lets any symbol be bound to an `Interval` (`{ kind: 'interval', min, max, dimension }`) instead of a point `Quantity`, and `evaluate`'s `'app'` dispatch promotes a plain `Quantity` operand to a degenerate point interval (`pointInterval`) the moment either side of a binary operator is an `Interval` — the same tree walk, the same operator ids, just running over ranges instead of points once it notices one. `addIntervals`/`subtractIntervals` combine endpoints directly; `multiplyIntervals`/`divideIntervals` implement the standard rule that a product's or quotient's extremes are always attained at one of the four corner combinations of the two intervals' endpoints (`min*min, min*max, max*min, max*max`, or the equivalent via the reciprocal for division) — which is what actually resolves the textbook sign-case table (positive×positive, negative×negative, straddling×straddling, and every mixed case) into one formula that is correct regardless of which side of zero either interval sits on; `interval.test.ts` exercises each sign combination directly rather than trusting the closed form on faith. Division by an interval that touches or straddles zero has no defined result (it would pass through ±Infinity) and throws `DivisionByZeroError` instead of letting `Infinity`/`NaN` flow silently into the rest of a computation. Only the four arithmetic operators plus negate/abs have interval rules in this pass — `pow`/`sqrt`/the trig functions are `Quantity`-only and throw `UnsupportedExpressionError` on an `Interval` operand, since a correct general interval range for a non-monotonic or sign-dependent function needs more analysis than this pass's scope covers (see below).
|
|
72
|
+
|
|
73
|
+
## Numeric solve-for
|
|
74
|
+
|
|
75
|
+
`solveFor(expression, targetValue, unknownSymbol, bindings, options?, context?)` finds the magnitude for `unknownSymbol` that makes `expression` evaluate to `targetValue`, by root-finding over the same `evaluate` — never by rearranging the expression algebraically. It implements both algorithms the issue asks for and lets `options.method` pick between them (`'bisection'`, the default, or `'newton'`):
|
|
76
|
+
|
|
77
|
+
- **Bisection** needs `options.bracket: [low, high]` whose residuals have opposite signs (the intermediate-value theorem is its entire correctness argument), halves the bracket every iteration, and cannot diverge — the safe default.
|
|
78
|
+
- **Newton's method** needs `options.initialGuess` and estimates the derivative by central difference, `(f(x+h) - f(x-h)) / (2h)` (`options.derivativeStep`, default `1e-6`) — chosen over a one-sided forward/backward difference because its truncation error is `O(h²)` rather than `O(h)`. No symbolic derivative is available without a symbolic layer this pass deliberately does not build (see Out of scope), so a numeric one is the whole story here.
|
|
79
|
+
|
|
80
|
+
Both throw `NonConvergentSolveError` rather than returning a number they cannot vouch for: bisection when its bracket doesn't straddle a root or the iteration budget (`options.maxIterations`, default 100) runs out before the residual drops under `options.tolerance` (default `1e-9`); Newton when the numeric derivative vanishes or diverges, or the same budget/tolerance is exhausted. `options.unknownDimension` sets the `DimensionVector` the unknown is bound under at each trial point (default dimensionless) so a physically dimensioned unknown (a length, a mass) solves correctly against a formula that checks dimensions along the way.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { solveFor } from 'document-compute.js';
|
|
84
|
+
import type { MathExpression } from 'document-schema.js';
|
|
85
|
+
|
|
86
|
+
// x^2 = 4, solve for x
|
|
87
|
+
const xSquared: MathExpression = {
|
|
88
|
+
kind: 'app',
|
|
89
|
+
operator: 'math:pow',
|
|
90
|
+
args: [{ kind: 'sym', id: 'x' }, { kind: 'num', numerator: '2', denominator: '1' }],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
solveFor(xSquared, 4, 'x', {}, { bracket: [0, 3] }); // 2, via bisection
|
|
94
|
+
solveFor(xSquared, 4, 'x', {}, { method: 'newton', initialGuess: 3 }); // 2, via Newton
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Deviations from the issue
|
|
98
|
+
|
|
99
|
+
Two things #573 asks for are not here in full. One was closed at adoption: `Quantity` and `FormulaBindings` (with `Interval` and `EvaluationValue`) are typed contracts in `document-schema.js`'s `src/math.ts`, beside `MathExpression` itself, exactly as the issue proposes — evaluation inputs are validated schemas like everything else in that package, and this package imports them from there the same way it imports `MathExpression`, `DimensionVector`, and `ExactRational`, rather than carrying package-local definitions.
|
|
100
|
+
|
|
101
|
+
The one that remains: **the worked-example test harness** the issue describes as its differentiator — measuring the fraction of a real document corpus's formulae whose evaluation reproduces the document's own stated answer. That harness needs real document corpora with stated formulae and answers, which this package has no access to build against; what ships here instead is thorough unit-level coverage of `evaluate`, the unit/dimension model, interval arithmetic, and `solveFor` in isolation (`src/compute/*.test.ts`). It is tracked as a follow-up to this package, not silently dropped.
|
|
102
|
+
|
|
103
|
+
## Out of scope
|
|
104
|
+
|
|
105
|
+
Quoting the issue's own scope line directly: **this is deliberately not a CAS in the Mathematica sense — units-typed evaluation and numeric solving are the 90% of "compute the result of a formula from a document" and are buildable natively now.** Concretely, this pass does not attempt, and this package carries no code toward:
|
|
106
|
+
|
|
107
|
+
- **Symbolic algebra** — exact rearrangement of an expression emitted back out as LaTeX, simplification, integration. `solveFor` finds a root numerically; it never isolates the unknown algebraically.
|
|
108
|
+
- **A SymPy sidecar or any other symbolic-engine adapter.** The issue names this as the eventual home for symbolic work, behind an evaluator interface this package does not define or stub.
|
|
109
|
+
- **The worked-example test harness** — see Deviations from the issue above.
|
|
110
|
+
- **Matrix-valued evaluation.** `MathExpression`'s `'matrix'` node exists in the grammar `document-schema.js` defines, but this evaluator only ever produces scalar `Quantity`/`Interval` values; a `'matrix'` node throws `UnsupportedExpressionError` rather than being silently misevaluated.
|
|
111
|
+
- **A general interval rule for `pow`/`sqrt`/the trigonometric operators.** These are implemented for `Quantity` only; applied to an `Interval` operand they throw `UnsupportedExpressionError` rather than guessing at a range a non-monotonic or sign-dependent function would need real analysis to get right.
|
|
112
|
+
|
|
113
|
+
## Conventions
|
|
114
|
+
|
|
115
|
+
- Worker-isomorphic (see the [family-wide convention](https://github.com/ExaDev/documents.js/blob/main/README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running a test suite inside an actual `workerd` isolate (`pnpm test:workers`). Exact-rational arithmetic is plain `BigInt`, never `node:crypto` or any other Node-only primitive, precisely so this holds.
|
|
116
|
+
- Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise.
|
|
117
|
+
- Failure is always a thrown, named `Error` subclass (`compute/errors.ts`), never a `{ ok, error }` result wrapper — matching `document-schema.js`'s `schema-io.ts` and `archive-codec`'s own error classes rather than inventing a second convention for this package alone.
|
|
118
|
+
- Not wired into the conversion pipeline. This package is a standalone evaluator: it is not a dependency of `documents.js`, `document-cli`, `document-mcp`, or `documents`, and adding it as one is a separate, deliberate decision for whichever of those surfaces first needs a document's formula actually computed.
|
|
119
|
+
- Release, CI, and commit-message conventions are all workspace-wide, not package-local — see the [monorepo root README](../../README.md#releases) for the mechanism (topological per-package `semantic-release` via `@exadev/semantic-release-workspace`, OIDC trusted npm publishing, and the post-release republish/attestation jobs).
|
|
120
|
+
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
pnpm add document-compute.js
|
|
125
|
+
# or
|
|
126
|
+
npm install document-compute.js
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let document_schema_js = require("document-schema.js");
|
|
3
|
+
//#region src/compute/rational.ts
|
|
4
|
+
function bigintOfCanonicalDigits(digits) {
|
|
5
|
+
return BigInt(digits);
|
|
6
|
+
}
|
|
7
|
+
function toRational(value) {
|
|
8
|
+
return {
|
|
9
|
+
n: bigintOfCanonicalDigits(value.numerator),
|
|
10
|
+
d: bigintOfCanonicalDigits(value.denominator)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function gcd(a, b) {
|
|
14
|
+
let x = a < 0n ? -a : a;
|
|
15
|
+
let y = b < 0n ? -b : b;
|
|
16
|
+
while (y !== 0n) [x, y] = [y, x % y];
|
|
17
|
+
return x === 0n ? 1n : x;
|
|
18
|
+
}
|
|
19
|
+
function reduce(n, d) {
|
|
20
|
+
if (d === 0n) throw new RangeError("rational.ts: denominator must not be zero");
|
|
21
|
+
if (n === 0n) return {
|
|
22
|
+
n: 0n,
|
|
23
|
+
d: 1n
|
|
24
|
+
};
|
|
25
|
+
const sign = d < 0n ? -1n : 1n;
|
|
26
|
+
const num = n * sign;
|
|
27
|
+
const den = d * sign;
|
|
28
|
+
const g = gcd(num, den);
|
|
29
|
+
return {
|
|
30
|
+
n: num / g,
|
|
31
|
+
d: den / g
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function toExactRational(value) {
|
|
35
|
+
const reduced = reduce(value.n, value.d);
|
|
36
|
+
return {
|
|
37
|
+
numerator: reduced.n.toString(),
|
|
38
|
+
denominator: reduced.d.toString()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function addRational(a, b) {
|
|
42
|
+
return reduce(a.n * b.d + b.n * a.d, a.d * b.d);
|
|
43
|
+
}
|
|
44
|
+
function subtractRational(a, b) {
|
|
45
|
+
return reduce(a.n * b.d - b.n * a.d, a.d * b.d);
|
|
46
|
+
}
|
|
47
|
+
function multiplyRational(a, b) {
|
|
48
|
+
return reduce(a.n * b.n, a.d * b.d);
|
|
49
|
+
}
|
|
50
|
+
function divideRational(a, b) {
|
|
51
|
+
if (b.n === 0n) throw new RangeError("rational.ts: division by zero");
|
|
52
|
+
return reduce(a.n * b.d, a.d * b.n);
|
|
53
|
+
}
|
|
54
|
+
function rationalToNumber(value) {
|
|
55
|
+
return Number(value.n) / Number(value.d);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/compute/dimensions.ts
|
|
59
|
+
function dimensionExponent(dimension, base) {
|
|
60
|
+
return dimension[base] ?? 0;
|
|
61
|
+
}
|
|
62
|
+
function dimensionsEqual(a, b) {
|
|
63
|
+
return document_schema_js.SI_BASE_DIMENSIONS.every((base) => dimensionExponent(a, base) === dimensionExponent(b, base));
|
|
64
|
+
}
|
|
65
|
+
function isDimensionless(dimension) {
|
|
66
|
+
return document_schema_js.SI_BASE_DIMENSIONS.every((base) => dimensionExponent(dimension, base) === 0);
|
|
67
|
+
}
|
|
68
|
+
function combine(a, b, op) {
|
|
69
|
+
const result = {};
|
|
70
|
+
for (const base of document_schema_js.SI_BASE_DIMENSIONS) {
|
|
71
|
+
const value = op(dimensionExponent(a, base), dimensionExponent(b, base));
|
|
72
|
+
if (value !== 0) result[base] = value;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function multiplyDimensions(a, b) {
|
|
77
|
+
return combine(a, b, (x, y) => x + y);
|
|
78
|
+
}
|
|
79
|
+
function divideDimensions(a, b) {
|
|
80
|
+
return combine(a, b, (x, y) => x - y);
|
|
81
|
+
}
|
|
82
|
+
function scaleDimension(dimension, k) {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const base of document_schema_js.SI_BASE_DIMENSIONS) {
|
|
85
|
+
const exponent = dimensionExponent(dimension, base);
|
|
86
|
+
if (exponent === 0) continue;
|
|
87
|
+
const scaled = exponent * k;
|
|
88
|
+
if (!Number.isInteger(scaled)) throw new RangeError(`dimensions.ts: scaling '${base}' by ${k} does not land on an integer exponent`);
|
|
89
|
+
result[base] = scaled;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function dimensionToString(dimension) {
|
|
94
|
+
const parts = document_schema_js.SI_BASE_DIMENSIONS.filter((base) => dimensionExponent(dimension, base) !== 0).map((base) => `${base}^${dimensionExponent(dimension, base)}`);
|
|
95
|
+
return parts.length === 0 ? "dimensionless" : parts.join("·");
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/compute/errors.ts
|
|
99
|
+
var IncompatibleDimensionsError = class extends Error {
|
|
100
|
+
operation;
|
|
101
|
+
left;
|
|
102
|
+
right;
|
|
103
|
+
constructor(operation, left, right, detail) {
|
|
104
|
+
super(`'${operation}' requires compatible dimensions, got ${dimensionToString(left)} and ${dimensionToString(right)}` + (detail === void 0 ? "." : ` (${detail}).`));
|
|
105
|
+
this.name = "IncompatibleDimensionsError";
|
|
106
|
+
this.operation = operation;
|
|
107
|
+
this.left = left;
|
|
108
|
+
this.right = right;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var UnboundSymbolError = class extends Error {
|
|
112
|
+
symbol;
|
|
113
|
+
constructor(symbol) {
|
|
114
|
+
super(`symbol '${symbol}' has no entry in the supplied bindings.`);
|
|
115
|
+
this.name = "UnboundSymbolError";
|
|
116
|
+
this.symbol = symbol;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var UnknownUnitError = class extends Error {
|
|
120
|
+
unit;
|
|
121
|
+
constructor(unit) {
|
|
122
|
+
super(`unit '${unit}' is not registered in the supplied symbol table's units.`);
|
|
123
|
+
this.name = "UnknownUnitError";
|
|
124
|
+
this.unit = unit;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var DivisionByZeroError = class extends Error {
|
|
128
|
+
operation;
|
|
129
|
+
constructor(operation, detail) {
|
|
130
|
+
super(`'${operation}': division by zero (${detail}).`);
|
|
131
|
+
this.name = "DivisionByZeroError";
|
|
132
|
+
this.operation = operation;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var UnsupportedExpressionError = class extends Error {
|
|
136
|
+
context;
|
|
137
|
+
constructor(context, detail) {
|
|
138
|
+
super(`${context}: ${detail}.`);
|
|
139
|
+
this.name = "UnsupportedExpressionError";
|
|
140
|
+
this.context = context;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var NumericDomainError = class extends Error {
|
|
144
|
+
operation;
|
|
145
|
+
constructor(operation, detail) {
|
|
146
|
+
super(`'${operation}': ${detail}.`);
|
|
147
|
+
this.name = "NumericDomainError";
|
|
148
|
+
this.operation = operation;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var NonConvergentSolveError = class extends Error {
|
|
152
|
+
method;
|
|
153
|
+
iterations;
|
|
154
|
+
constructor(method, iterations, detail) {
|
|
155
|
+
super(`solveFor (${method}) did not converge after ${iterations} iteration(s): ${detail}.`);
|
|
156
|
+
this.name = "NonConvergentSolveError";
|
|
157
|
+
this.method = method;
|
|
158
|
+
this.iterations = iterations;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/compute/quantity.ts
|
|
163
|
+
function quantity(magnitude, dimension = {}) {
|
|
164
|
+
return {
|
|
165
|
+
kind: "quantity",
|
|
166
|
+
magnitude,
|
|
167
|
+
dimension
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function addQuantities(a, b) {
|
|
171
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:add", a.dimension, b.dimension);
|
|
172
|
+
return quantity(a.magnitude + b.magnitude, a.dimension);
|
|
173
|
+
}
|
|
174
|
+
function subtractQuantities(a, b) {
|
|
175
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:subtract", a.dimension, b.dimension);
|
|
176
|
+
return quantity(a.magnitude - b.magnitude, a.dimension);
|
|
177
|
+
}
|
|
178
|
+
function multiplyQuantities(a, b) {
|
|
179
|
+
return quantity(a.magnitude * b.magnitude, multiplyDimensions(a.dimension, b.dimension));
|
|
180
|
+
}
|
|
181
|
+
function divideQuantities(a, b) {
|
|
182
|
+
if (b.magnitude === 0) throw new DivisionByZeroError("math:divide", `divisor magnitude is exactly zero (dividend magnitude ${a.magnitude})`);
|
|
183
|
+
return quantity(a.magnitude / b.magnitude, divideDimensions(a.dimension, b.dimension));
|
|
184
|
+
}
|
|
185
|
+
function negateQuantity(a) {
|
|
186
|
+
return quantity(-a.magnitude, a.dimension);
|
|
187
|
+
}
|
|
188
|
+
function absQuantity(a) {
|
|
189
|
+
return quantity(Math.abs(a.magnitude), a.dimension);
|
|
190
|
+
}
|
|
191
|
+
function powQuantity(base, exponent) {
|
|
192
|
+
if (!isDimensionless(exponent.dimension)) throw new IncompatibleDimensionsError("math:pow", exponent.dimension, {}, "the exponent must be dimensionless");
|
|
193
|
+
if (isDimensionless(base.dimension)) return quantity(Math.pow(base.magnitude, exponent.magnitude), {});
|
|
194
|
+
if (!Number.isInteger(exponent.magnitude)) throw new IncompatibleDimensionsError("math:pow", base.dimension, {}, "a dimensioned base can only be raised to an integer power");
|
|
195
|
+
return quantity(Math.pow(base.magnitude, exponent.magnitude), scaleDimension(base.dimension, exponent.magnitude));
|
|
196
|
+
}
|
|
197
|
+
function sqrtQuantity(a) {
|
|
198
|
+
if (a.magnitude < 0) throw new NumericDomainError("math:sqrt", `magnitude must be non-negative, got ${a.magnitude}`);
|
|
199
|
+
let dimension;
|
|
200
|
+
try {
|
|
201
|
+
dimension = scaleDimension(a.dimension, .5);
|
|
202
|
+
} catch {
|
|
203
|
+
throw new IncompatibleDimensionsError("math:sqrt", a.dimension, {}, "every exponent must be even for the dimension to have an exact square root");
|
|
204
|
+
}
|
|
205
|
+
return quantity(Math.sqrt(a.magnitude), dimension);
|
|
206
|
+
}
|
|
207
|
+
function requireDimensionless(a, operator) {
|
|
208
|
+
if (!isDimensionless(a.dimension)) throw new IncompatibleDimensionsError(operator, a.dimension, {}, "trigonometric functions take a dimensionless (radian) argument");
|
|
209
|
+
}
|
|
210
|
+
function sinQuantity(a) {
|
|
211
|
+
requireDimensionless(a, "math:sin");
|
|
212
|
+
return quantity(Math.sin(a.magnitude), {});
|
|
213
|
+
}
|
|
214
|
+
function cosQuantity(a) {
|
|
215
|
+
requireDimensionless(a, "math:cos");
|
|
216
|
+
return quantity(Math.cos(a.magnitude), {});
|
|
217
|
+
}
|
|
218
|
+
function tanQuantity(a) {
|
|
219
|
+
requireDimensionless(a, "math:tan");
|
|
220
|
+
return quantity(Math.tan(a.magnitude), {});
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/compute/interval.ts
|
|
224
|
+
function interval(min, max, dimension = {}) {
|
|
225
|
+
if (min > max) throw new RangeError(`interval: min (${min}) must not exceed max (${max})`);
|
|
226
|
+
return {
|
|
227
|
+
kind: "interval",
|
|
228
|
+
min,
|
|
229
|
+
max,
|
|
230
|
+
dimension
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function pointInterval(magnitude, dimension = {}) {
|
|
234
|
+
return {
|
|
235
|
+
kind: "interval",
|
|
236
|
+
min: magnitude,
|
|
237
|
+
max: magnitude,
|
|
238
|
+
dimension
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function addIntervals(a, b) {
|
|
242
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:add", a.dimension, b.dimension);
|
|
243
|
+
return interval(a.min + b.min, a.max + b.max, a.dimension);
|
|
244
|
+
}
|
|
245
|
+
function subtractIntervals(a, b) {
|
|
246
|
+
if (!dimensionsEqual(a.dimension, b.dimension)) throw new IncompatibleDimensionsError("math:subtract", a.dimension, b.dimension);
|
|
247
|
+
return interval(a.min - b.max, a.max - b.min, a.dimension);
|
|
248
|
+
}
|
|
249
|
+
function multiplyIntervals(a, b) {
|
|
250
|
+
const corners = [
|
|
251
|
+
a.min * b.min,
|
|
252
|
+
a.min * b.max,
|
|
253
|
+
a.max * b.min,
|
|
254
|
+
a.max * b.max
|
|
255
|
+
];
|
|
256
|
+
return interval(Math.min(...corners), Math.max(...corners), multiplyDimensions(a.dimension, b.dimension));
|
|
257
|
+
}
|
|
258
|
+
function divideIntervals(a, b) {
|
|
259
|
+
if (b.min <= 0 && b.max >= 0) throw new DivisionByZeroError("math:divide", `divisor interval [${b.min}, ${b.max}] contains zero`);
|
|
260
|
+
const reciprocalMin = 1 / b.max;
|
|
261
|
+
const reciprocalMax = 1 / b.min;
|
|
262
|
+
const corners = [
|
|
263
|
+
a.min * reciprocalMin,
|
|
264
|
+
a.min * reciprocalMax,
|
|
265
|
+
a.max * reciprocalMin,
|
|
266
|
+
a.max * reciprocalMax
|
|
267
|
+
];
|
|
268
|
+
return interval(Math.min(...corners), Math.max(...corners), divideDimensions(a.dimension, b.dimension));
|
|
269
|
+
}
|
|
270
|
+
function negateInterval(a) {
|
|
271
|
+
return interval(-a.max, -a.min, a.dimension);
|
|
272
|
+
}
|
|
273
|
+
function absInterval(a) {
|
|
274
|
+
if (a.min >= 0) return a;
|
|
275
|
+
if (a.max <= 0) return negateInterval(a);
|
|
276
|
+
return interval(0, Math.max(-a.min, a.max), a.dimension);
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/compute/evaluate.ts
|
|
280
|
+
const EMPTY_SYMBOL_TABLE$1 = {
|
|
281
|
+
symbols: [],
|
|
282
|
+
units: []
|
|
283
|
+
};
|
|
284
|
+
function isInterval(value) {
|
|
285
|
+
return value.kind === "interval";
|
|
286
|
+
}
|
|
287
|
+
function toInterval(value) {
|
|
288
|
+
return isInterval(value) ? value : pointInterval(value.magnitude, value.dimension);
|
|
289
|
+
}
|
|
290
|
+
function asQuantity(value, context) {
|
|
291
|
+
if (isInterval(value)) throw new UnsupportedExpressionError(context, "this position requires a plain Quantity, not an Interval");
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
const BINARY_OPERATORS = {
|
|
295
|
+
"math:add": {
|
|
296
|
+
quantity: addQuantities,
|
|
297
|
+
interval: addIntervals
|
|
298
|
+
},
|
|
299
|
+
"math:subtract": {
|
|
300
|
+
quantity: subtractQuantities,
|
|
301
|
+
interval: subtractIntervals
|
|
302
|
+
},
|
|
303
|
+
"math:multiply": {
|
|
304
|
+
quantity: multiplyQuantities,
|
|
305
|
+
interval: multiplyIntervals
|
|
306
|
+
},
|
|
307
|
+
"math:divide": {
|
|
308
|
+
quantity: divideQuantities,
|
|
309
|
+
interval: divideIntervals
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const UNARY_OPERATORS = {
|
|
313
|
+
"math:negate": {
|
|
314
|
+
quantity: negateQuantity,
|
|
315
|
+
interval: negateInterval
|
|
316
|
+
},
|
|
317
|
+
"math:abs": {
|
|
318
|
+
quantity: absQuantity,
|
|
319
|
+
interval: absInterval
|
|
320
|
+
},
|
|
321
|
+
"math:sqrt": { quantity: sqrtQuantity },
|
|
322
|
+
"math:sin": { quantity: sinQuantity },
|
|
323
|
+
"math:cos": { quantity: cosQuantity },
|
|
324
|
+
"math:tan": { quantity: tanQuantity }
|
|
325
|
+
};
|
|
326
|
+
function evaluate(expression, bindings, context = EMPTY_SYMBOL_TABLE$1) {
|
|
327
|
+
switch (expression.kind) {
|
|
328
|
+
case "num": return quantity(rationalToNumber(toRational(expression)), {});
|
|
329
|
+
case "qty": return evaluateQty(expression, context);
|
|
330
|
+
case "sym": {
|
|
331
|
+
const bound = bindings[expression.id];
|
|
332
|
+
if (bound === void 0) throw new UnboundSymbolError(expression.id);
|
|
333
|
+
return bound;
|
|
334
|
+
}
|
|
335
|
+
case "app": return evaluateApp(expression, bindings, context);
|
|
336
|
+
case "sum":
|
|
337
|
+
case "prod": return evaluateBinder(expression, bindings, context);
|
|
338
|
+
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");
|
|
339
|
+
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`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function evaluateQty(node, context) {
|
|
343
|
+
const unit = context.units.find((entry) => entry.id === node.unit);
|
|
344
|
+
if (unit === void 0) throw new UnknownUnitError(node.unit);
|
|
345
|
+
let siValue = multiplyRational(toRational(node.value), toRational(unit.factorToSi));
|
|
346
|
+
if (unit.offsetToSi !== void 0) siValue = addRational(siValue, toRational(unit.offsetToSi));
|
|
347
|
+
return quantity(rationalToNumber(siValue), unit.dimension);
|
|
348
|
+
}
|
|
349
|
+
function evaluateApp(node, bindings, context) {
|
|
350
|
+
const args = node.args.map((arg) => evaluate(arg, bindings, context));
|
|
351
|
+
const binary = BINARY_OPERATORS[node.operator];
|
|
352
|
+
if (binary !== void 0) {
|
|
353
|
+
if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 2 arguments, got ${args.length}`);
|
|
354
|
+
const [left, right] = args;
|
|
355
|
+
if (isInterval(left) || isInterval(right)) return binary.interval(toInterval(left), toInterval(right));
|
|
356
|
+
return binary.quantity(left, right);
|
|
357
|
+
}
|
|
358
|
+
const unary = UNARY_OPERATORS[node.operator];
|
|
359
|
+
if (unary !== void 0) {
|
|
360
|
+
if (args.length !== 1) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 1 argument, got ${args.length}`);
|
|
361
|
+
const [only] = args;
|
|
362
|
+
if (isInterval(only)) {
|
|
363
|
+
if (unary.interval === void 0) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' has no interval rule in this pass`);
|
|
364
|
+
return unary.interval(only);
|
|
365
|
+
}
|
|
366
|
+
return unary.quantity(only);
|
|
367
|
+
}
|
|
368
|
+
if (node.operator === "math:pow") {
|
|
369
|
+
if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `'math:pow' takes exactly 2 arguments, got ${args.length}`);
|
|
370
|
+
const [base, exponent] = args;
|
|
371
|
+
return powQuantity(asQuantity(base, "evaluate"), asQuantity(exponent, "evaluate"));
|
|
372
|
+
}
|
|
373
|
+
throw new UnsupportedExpressionError("evaluate", `unknown operator '${node.operator}'`);
|
|
374
|
+
}
|
|
375
|
+
function evaluateBinder(node, bindings, context) {
|
|
376
|
+
const lower = asQuantity(evaluate(node.lower, bindings, context), `evaluate:${node.kind}`);
|
|
377
|
+
const upper = asQuantity(evaluate(node.upper, bindings, context), `evaluate:${node.kind}`);
|
|
378
|
+
if (!isDimensionless(lower.dimension) || !isDimensionless(upper.dimension)) throw new IncompatibleDimensionsError(`math:${node.kind}`, lower.dimension, upper.dimension, "binder bounds must be dimensionless");
|
|
379
|
+
if (!Number.isInteger(lower.magnitude) || !Number.isInteger(upper.magnitude)) throw new UnsupportedExpressionError(`evaluate:${node.kind}`, "binder bounds must evaluate to integers");
|
|
380
|
+
let accumulator = node.kind === "sum" ? quantity(0, {}) : quantity(1, {});
|
|
381
|
+
for (let i = lower.magnitude; i <= upper.magnitude; i += 1) {
|
|
382
|
+
const bodyBindings = {
|
|
383
|
+
...bindings,
|
|
384
|
+
[node.binder]: quantity(i, {})
|
|
385
|
+
};
|
|
386
|
+
const bodyValue = asQuantity(evaluate(node.body, bodyBindings, context), `evaluate:${node.kind}`);
|
|
387
|
+
accumulator = node.kind === "sum" ? addQuantities(accumulator, bodyValue) : multiplyQuantities(accumulator, bodyValue);
|
|
388
|
+
}
|
|
389
|
+
return accumulator;
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/compute/solve.ts
|
|
393
|
+
const DEFAULT_TOLERANCE = 1e-9;
|
|
394
|
+
const DEFAULT_MAX_ITERATIONS = 100;
|
|
395
|
+
const DEFAULT_DERIVATIVE_STEP = 1e-6;
|
|
396
|
+
const EMPTY_SYMBOL_TABLE = {
|
|
397
|
+
symbols: [],
|
|
398
|
+
units: []
|
|
399
|
+
};
|
|
400
|
+
function residualFn(expression, targetValue, unknownSymbol, bindings, context, dimension) {
|
|
401
|
+
return (x) => {
|
|
402
|
+
const result = evaluate(expression, {
|
|
403
|
+
...bindings,
|
|
404
|
+
[unknownSymbol]: quantity(x, dimension)
|
|
405
|
+
}, context);
|
|
406
|
+
if (result.kind !== "quantity") throw new UnsupportedExpressionError("solveFor", "the expression must evaluate to a plain Quantity, not an Interval");
|
|
407
|
+
return result.magnitude - targetValue;
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function solveFor(expression, targetValue, unknownSymbol, bindings, options = {}, context = EMPTY_SYMBOL_TABLE) {
|
|
411
|
+
const method = options.method ?? "bisection";
|
|
412
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE;
|
|
413
|
+
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
414
|
+
const f = residualFn(expression, targetValue, unknownSymbol, bindings, context, options.unknownDimension ?? {});
|
|
415
|
+
if (method === "bisection") return bisection(f, options.bracket, tolerance, maxIterations);
|
|
416
|
+
return newton(f, options.initialGuess, tolerance, maxIterations, options.derivativeStep ?? DEFAULT_DERIVATIVE_STEP);
|
|
417
|
+
}
|
|
418
|
+
function bisection(f, bracket, tolerance, maxIterations) {
|
|
419
|
+
if (bracket === void 0) throw new UnsupportedExpressionError("solveFor", "method 'bisection' requires options.bracket: [low, high]");
|
|
420
|
+
let [low, high] = bracket;
|
|
421
|
+
let fLow = f(low);
|
|
422
|
+
const fHigh0 = f(high);
|
|
423
|
+
if (Math.abs(fLow) < tolerance) return low;
|
|
424
|
+
if (Math.abs(fHigh0) < tolerance) return high;
|
|
425
|
+
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`);
|
|
426
|
+
for (let i = 0; i < maxIterations; i += 1) {
|
|
427
|
+
const mid = (low + high) / 2;
|
|
428
|
+
const fMid = f(mid);
|
|
429
|
+
if (Math.abs(fMid) < tolerance) return mid;
|
|
430
|
+
if (fMid > 0 === fLow > 0) {
|
|
431
|
+
low = mid;
|
|
432
|
+
fLow = fMid;
|
|
433
|
+
} else high = mid;
|
|
434
|
+
}
|
|
435
|
+
throw new NonConvergentSolveError("bisection", maxIterations, `residual still exceeds tolerance ${tolerance} after ${maxIterations} iterations`);
|
|
436
|
+
}
|
|
437
|
+
function newton(f, initialGuess, tolerance, maxIterations, h) {
|
|
438
|
+
if (initialGuess === void 0) throw new UnsupportedExpressionError("solveFor", "method 'newton' requires options.initialGuess");
|
|
439
|
+
let x = initialGuess;
|
|
440
|
+
for (let i = 0; i < maxIterations; i += 1) {
|
|
441
|
+
const fx = f(x);
|
|
442
|
+
if (Math.abs(fx) < tolerance) return x;
|
|
443
|
+
const derivative = (f(x + h) - f(x - h)) / (2 * h);
|
|
444
|
+
if (!Number.isFinite(derivative) || Math.abs(derivative) < 1e-14) throw new NonConvergentSolveError("newton", i, `the numeric derivative vanished or diverged near x=${x}`);
|
|
445
|
+
const next = x - fx / derivative;
|
|
446
|
+
if (!Number.isFinite(next)) throw new NonConvergentSolveError("newton", i, `the iteration diverged to a non-finite value near x=${x}`);
|
|
447
|
+
x = next;
|
|
448
|
+
}
|
|
449
|
+
throw new NonConvergentSolveError("newton", maxIterations, `residual still exceeds tolerance ${tolerance} after ${maxIterations} iterations`);
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
exports.DivisionByZeroError = DivisionByZeroError;
|
|
453
|
+
exports.IncompatibleDimensionsError = IncompatibleDimensionsError;
|
|
454
|
+
exports.NonConvergentSolveError = NonConvergentSolveError;
|
|
455
|
+
exports.NumericDomainError = NumericDomainError;
|
|
456
|
+
exports.UnboundSymbolError = UnboundSymbolError;
|
|
457
|
+
exports.UnknownUnitError = UnknownUnitError;
|
|
458
|
+
exports.UnsupportedExpressionError = UnsupportedExpressionError;
|
|
459
|
+
exports.absInterval = absInterval;
|
|
460
|
+
exports.absQuantity = absQuantity;
|
|
461
|
+
exports.addIntervals = addIntervals;
|
|
462
|
+
exports.addQuantities = addQuantities;
|
|
463
|
+
exports.addRational = addRational;
|
|
464
|
+
exports.cosQuantity = cosQuantity;
|
|
465
|
+
exports.dimensionExponent = dimensionExponent;
|
|
466
|
+
exports.dimensionToString = dimensionToString;
|
|
467
|
+
exports.dimensionsEqual = dimensionsEqual;
|
|
468
|
+
exports.divideDimensions = divideDimensions;
|
|
469
|
+
exports.divideIntervals = divideIntervals;
|
|
470
|
+
exports.divideQuantities = divideQuantities;
|
|
471
|
+
exports.divideRational = divideRational;
|
|
472
|
+
exports.evaluate = evaluate;
|
|
473
|
+
exports.interval = interval;
|
|
474
|
+
exports.isDimensionless = isDimensionless;
|
|
475
|
+
exports.isInterval = isInterval;
|
|
476
|
+
exports.multiplyDimensions = multiplyDimensions;
|
|
477
|
+
exports.multiplyIntervals = multiplyIntervals;
|
|
478
|
+
exports.multiplyQuantities = multiplyQuantities;
|
|
479
|
+
exports.multiplyRational = multiplyRational;
|
|
480
|
+
exports.negateInterval = negateInterval;
|
|
481
|
+
exports.negateQuantity = negateQuantity;
|
|
482
|
+
exports.pointInterval = pointInterval;
|
|
483
|
+
exports.powQuantity = powQuantity;
|
|
484
|
+
exports.quantity = quantity;
|
|
485
|
+
exports.rationalToNumber = rationalToNumber;
|
|
486
|
+
exports.scaleDimension = scaleDimension;
|
|
487
|
+
exports.sinQuantity = sinQuantity;
|
|
488
|
+
exports.solveFor = solveFor;
|
|
489
|
+
exports.sqrtQuantity = sqrtQuantity;
|
|
490
|
+
exports.subtractIntervals = subtractIntervals;
|
|
491
|
+
exports.subtractQuantities = subtractQuantities;
|
|
492
|
+
exports.subtractRational = subtractRational;
|
|
493
|
+
exports.tanQuantity = tanQuantity;
|
|
494
|
+
exports.toExactRational = toExactRational;
|
|
495
|
+
exports.toRational = toRational;
|