document-compute.js 1.0.1 → 1.1.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 +29 -23
- package/dist/index.cjs +13 -6
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -6
- package/package.json +2 -11
package/README.md
CHANGED
|
@@ -24,34 +24,37 @@ To run a single test file, pass its path to vitest directly, e.g. `pnpm exec vit
|
|
|
24
24
|
|
|
25
25
|
## What it provides
|
|
26
26
|
|
|
27
|
-
| Module
|
|
28
|
-
|
|
29
|
-
| `compute/rational`
|
|
30
|
-
| `compute/dimensions` | `dimensionExponent`, `dimensionsEqual`, `isDimensionless`, `multiplyDimensions`, `divideDimensions`, `scaleDimension`, `dimensionToString`
|
|
31
|
-
| `compute/quantity`
|
|
32
|
-
| `compute/interval`
|
|
33
|
-
| `compute/evaluate`
|
|
34
|
-
| `compute/solve`
|
|
35
|
-
| `compute/errors`
|
|
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
36
|
|
|
37
37
|
Every module in the table is re-exported from the package root, so its exports import from `'document-compute.js'` directly.
|
|
38
38
|
|
|
39
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
40
|
|
|
41
41
|
```ts
|
|
42
|
-
import { evaluate } from
|
|
43
|
-
import type { FormulaBindings, MathExpression } from
|
|
42
|
+
import { evaluate } from "document-compute.js";
|
|
43
|
+
import type { FormulaBindings, MathExpression } from "document-schema.js";
|
|
44
44
|
|
|
45
45
|
// F = m * a
|
|
46
46
|
const force: MathExpression = {
|
|
47
|
-
kind:
|
|
48
|
-
operator:
|
|
49
|
-
args: [
|
|
47
|
+
kind: "app",
|
|
48
|
+
operator: "math:multiply",
|
|
49
|
+
args: [
|
|
50
|
+
{ kind: "sym", id: "m" },
|
|
51
|
+
{ kind: "sym", id: "a" },
|
|
52
|
+
],
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
const bindings: FormulaBindings = {
|
|
53
|
-
m: { kind:
|
|
54
|
-
a: { kind:
|
|
56
|
+
m: { kind: "quantity", magnitude: 2, dimension: { mass: 1 } },
|
|
57
|
+
a: { kind: "quantity", magnitude: 3, dimension: { length: 1, time: -2 } },
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
const result = evaluate(force, bindings);
|
|
@@ -80,18 +83,21 @@ The issue's own example is a compliance region: `0.87 <= cos(phi) <= 1`. Rather
|
|
|
80
83
|
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
84
|
|
|
82
85
|
```ts
|
|
83
|
-
import { solveFor } from
|
|
84
|
-
import type { MathExpression } from
|
|
86
|
+
import { solveFor } from "document-compute.js";
|
|
87
|
+
import type { MathExpression } from "document-schema.js";
|
|
85
88
|
|
|
86
89
|
// x^2 = 4, solve for x
|
|
87
90
|
const xSquared: MathExpression = {
|
|
88
|
-
kind:
|
|
89
|
-
operator:
|
|
90
|
-
args: [
|
|
91
|
+
kind: "app",
|
|
92
|
+
operator: "math:pow",
|
|
93
|
+
args: [
|
|
94
|
+
{ kind: "sym", id: "x" },
|
|
95
|
+
{ kind: "num", numerator: "2", denominator: "1" },
|
|
96
|
+
],
|
|
91
97
|
};
|
|
92
98
|
|
|
93
|
-
solveFor(xSquared, 4,
|
|
94
|
-
solveFor(xSquared, 4,
|
|
99
|
+
solveFor(xSquared, 4, "x", {}, { bracket: [0, 3] }); // 2, via bisection
|
|
100
|
+
solveFor(xSquared, 4, "x", {}, { method: "newton", initialGuess: 3 }); // 2, via Newton
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
## Deviations from the issue
|
package/dist/index.cjs
CHANGED
|
@@ -346,19 +346,27 @@ function evaluateQty(node, context) {
|
|
|
346
346
|
if (unit.offsetToSi !== void 0) siValue = addRational(siValue, toRational(unit.offsetToSi));
|
|
347
347
|
return quantity(rationalToNumber(siValue), unit.dimension);
|
|
348
348
|
}
|
|
349
|
+
function expectTwoArgs(args, subject) {
|
|
350
|
+
const [left, right] = args;
|
|
351
|
+
if (args.length !== 2 || left === void 0 || right === void 0) throw new UnsupportedExpressionError("evaluate", `${subject} takes exactly 2 arguments, got ${args.length}`);
|
|
352
|
+
return [left, right];
|
|
353
|
+
}
|
|
354
|
+
function expectOneArg(args, subject) {
|
|
355
|
+
const [only] = args;
|
|
356
|
+
if (args.length !== 1 || only === void 0) throw new UnsupportedExpressionError("evaluate", `${subject} takes exactly 1 argument, got ${args.length}`);
|
|
357
|
+
return only;
|
|
358
|
+
}
|
|
349
359
|
function evaluateApp(node, bindings, context) {
|
|
350
360
|
const args = node.args.map((arg) => evaluate(arg, bindings, context));
|
|
351
361
|
const binary = BINARY_OPERATORS[node.operator];
|
|
352
362
|
if (binary !== void 0) {
|
|
353
|
-
|
|
354
|
-
const [left, right] = args;
|
|
363
|
+
const [left, right] = expectTwoArgs(args, `operator '${node.operator}'`);
|
|
355
364
|
if (isInterval(left) || isInterval(right)) return binary.interval(toInterval(left), toInterval(right));
|
|
356
365
|
return binary.quantity(left, right);
|
|
357
366
|
}
|
|
358
367
|
const unary = UNARY_OPERATORS[node.operator];
|
|
359
368
|
if (unary !== void 0) {
|
|
360
|
-
|
|
361
|
-
const [only] = args;
|
|
369
|
+
const only = expectOneArg(args, `operator '${node.operator}'`);
|
|
362
370
|
if (isInterval(only)) {
|
|
363
371
|
if (unary.interval === void 0) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' has no interval rule in this pass`);
|
|
364
372
|
return unary.interval(only);
|
|
@@ -366,8 +374,7 @@ function evaluateApp(node, bindings, context) {
|
|
|
366
374
|
return unary.quantity(only);
|
|
367
375
|
}
|
|
368
376
|
if (node.operator === "math:pow") {
|
|
369
|
-
|
|
370
|
-
const [base, exponent] = args;
|
|
377
|
+
const [base, exponent] = expectTwoArgs(args, "'math:pow'");
|
|
371
378
|
return powQuantity(asQuantity(base, "evaluate"), asQuantity(exponent, "evaluate"));
|
|
372
379
|
}
|
|
373
380
|
throw new UnsupportedExpressionError("evaluate", `unknown operator '${node.operator}'`);
|
package/dist/index.d.cts
CHANGED
|
@@ -49,9 +49,9 @@ declare class NumericDomainError extends Error {
|
|
|
49
49
|
constructor(operation: string, detail: string);
|
|
50
50
|
}
|
|
51
51
|
declare class NonConvergentSolveError extends Error {
|
|
52
|
-
readonly method:
|
|
52
|
+
readonly method: "bisection" | "newton";
|
|
53
53
|
readonly iterations: number;
|
|
54
|
-
constructor(method:
|
|
54
|
+
constructor(method: "bisection" | "newton", iterations: number, detail: string);
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
57
|
//#region src/compute/quantity.d.ts
|
|
@@ -84,7 +84,7 @@ declare function isInterval(value: EvaluationResult): value is Interval;
|
|
|
84
84
|
declare function evaluate(expression: MathExpression, bindings: FormulaBindings, context?: SymbolTable): EvaluationResult;
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/compute/solve.d.ts
|
|
87
|
-
type SolveMethod =
|
|
87
|
+
type SolveMethod = "bisection" | "newton";
|
|
88
88
|
interface SolveForOptions {
|
|
89
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
90
|
method?: SolveMethod;
|
package/dist/index.d.ts
CHANGED
|
@@ -49,9 +49,9 @@ declare class NumericDomainError extends Error {
|
|
|
49
49
|
constructor(operation: string, detail: string);
|
|
50
50
|
}
|
|
51
51
|
declare class NonConvergentSolveError extends Error {
|
|
52
|
-
readonly method:
|
|
52
|
+
readonly method: "bisection" | "newton";
|
|
53
53
|
readonly iterations: number;
|
|
54
|
-
constructor(method:
|
|
54
|
+
constructor(method: "bisection" | "newton", iterations: number, detail: string);
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
57
|
//#region src/compute/quantity.d.ts
|
|
@@ -84,7 +84,7 @@ declare function isInterval(value: EvaluationResult): value is Interval;
|
|
|
84
84
|
declare function evaluate(expression: MathExpression, bindings: FormulaBindings, context?: SymbolTable): EvaluationResult;
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/compute/solve.d.ts
|
|
87
|
-
type SolveMethod =
|
|
87
|
+
type SolveMethod = "bisection" | "newton";
|
|
88
88
|
interface SolveForOptions {
|
|
89
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
90
|
method?: SolveMethod;
|
package/dist/index.js
CHANGED
|
@@ -345,19 +345,27 @@ function evaluateQty(node, context) {
|
|
|
345
345
|
if (unit.offsetToSi !== void 0) siValue = addRational(siValue, toRational(unit.offsetToSi));
|
|
346
346
|
return quantity(rationalToNumber(siValue), unit.dimension);
|
|
347
347
|
}
|
|
348
|
+
function expectTwoArgs(args, subject) {
|
|
349
|
+
const [left, right] = args;
|
|
350
|
+
if (args.length !== 2 || left === void 0 || right === void 0) throw new UnsupportedExpressionError("evaluate", `${subject} takes exactly 2 arguments, got ${args.length}`);
|
|
351
|
+
return [left, right];
|
|
352
|
+
}
|
|
353
|
+
function expectOneArg(args, subject) {
|
|
354
|
+
const [only] = args;
|
|
355
|
+
if (args.length !== 1 || only === void 0) throw new UnsupportedExpressionError("evaluate", `${subject} takes exactly 1 argument, got ${args.length}`);
|
|
356
|
+
return only;
|
|
357
|
+
}
|
|
348
358
|
function evaluateApp(node, bindings, context) {
|
|
349
359
|
const args = node.args.map((arg) => evaluate(arg, bindings, context));
|
|
350
360
|
const binary = BINARY_OPERATORS[node.operator];
|
|
351
361
|
if (binary !== void 0) {
|
|
352
|
-
|
|
353
|
-
const [left, right] = args;
|
|
362
|
+
const [left, right] = expectTwoArgs(args, `operator '${node.operator}'`);
|
|
354
363
|
if (isInterval(left) || isInterval(right)) return binary.interval(toInterval(left), toInterval(right));
|
|
355
364
|
return binary.quantity(left, right);
|
|
356
365
|
}
|
|
357
366
|
const unary = UNARY_OPERATORS[node.operator];
|
|
358
367
|
if (unary !== void 0) {
|
|
359
|
-
|
|
360
|
-
const [only] = args;
|
|
368
|
+
const only = expectOneArg(args, `operator '${node.operator}'`);
|
|
361
369
|
if (isInterval(only)) {
|
|
362
370
|
if (unary.interval === void 0) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' has no interval rule in this pass`);
|
|
363
371
|
return unary.interval(only);
|
|
@@ -365,8 +373,7 @@ function evaluateApp(node, bindings, context) {
|
|
|
365
373
|
return unary.quantity(only);
|
|
366
374
|
}
|
|
367
375
|
if (node.operator === "math:pow") {
|
|
368
|
-
|
|
369
|
-
const [base, exponent] = args;
|
|
376
|
+
const [base, exponent] = expectTwoArgs(args, "'math:pow'");
|
|
370
377
|
return powQuantity(asQuantity(base, "evaluate"), asQuantity(exponent, "evaluate"));
|
|
371
378
|
}
|
|
372
379
|
throw new UnsupportedExpressionError("evaluate", `unknown operator '${node.operator}'`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-compute.js",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Units-typed, tree-walking evaluator for document-schema.js's MathExpression -- exact-rational unit conversion, interval arithmetic, and bisection/Newton numeric solve-for, the compute package for the documents.js family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -65,27 +65,18 @@
|
|
|
65
65
|
},
|
|
66
66
|
"packageManager": "pnpm@11.6.0",
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"document-schema.js": "^5.
|
|
68
|
+
"document-schema.js": "^5.1.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
72
72
|
"@cloudflare/vitest-pool-workers": "^0.20.1",
|
|
73
|
-
"@commitlint/cli": "^21.2.1",
|
|
74
|
-
"@commitlint/config-conventional": "^21.2.0",
|
|
75
|
-
"@eslint/js": "^10.0.1",
|
|
76
|
-
"@semantic-release/changelog": "^7.0.0",
|
|
77
|
-
"@semantic-release/git": "^11.0.1",
|
|
78
73
|
"@types/node": "^26.1.2",
|
|
79
74
|
"eslint": "^10.8.0",
|
|
80
|
-
"globals": "^17.8.0",
|
|
81
75
|
"husky": "^9.1.7",
|
|
82
|
-
"lint-staged": "^17.2.0",
|
|
83
76
|
"publint": "^0.3.21",
|
|
84
|
-
"semantic-release": "^25.0.8",
|
|
85
77
|
"tsdown": "^0.22.13",
|
|
86
78
|
"turbo": "^2.10.8",
|
|
87
79
|
"typescript": "^6.0.3",
|
|
88
|
-
"typescript-eslint": "^8.65.0",
|
|
89
80
|
"vitest": "^4.1.10"
|
|
90
81
|
},
|
|
91
82
|
"lint-staged": {
|