document-compute.js 1.0.0 → 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 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 | 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` |
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 'document-compute.js';
43
- import type { FormulaBindings, MathExpression } from 'document-schema.js';
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: 'app',
48
- operator: 'math:multiply',
49
- args: [{ kind: 'sym', id: 'm' }, { kind: 'sym', id: 'a' }],
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: 'quantity', magnitude: 2, dimension: { mass: 1 } },
54
- a: { kind: 'quantity', magnitude: 3, dimension: { length: 1, time: -2 } },
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 'document-compute.js';
84
- import type { MathExpression } from 'document-schema.js';
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: 'app',
89
- operator: 'math:pow',
90
- args: [{ kind: 'sym', id: 'x' }, { kind: 'num', numerator: '2', denominator: '1' }],
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, 'x', {}, { bracket: [0, 3] }); // 2, via bisection
94
- solveFor(xSquared, 4, 'x', {}, { method: 'newton', initialGuess: 3 }); // 2, via Newton
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
- if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 2 arguments, got ${args.length}`);
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
- if (args.length !== 1) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 1 argument, got ${args.length}`);
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
- if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `'math:pow' takes exactly 2 arguments, got ${args.length}`);
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: 'bisection' | 'newton';
52
+ readonly method: "bisection" | "newton";
53
53
  readonly iterations: number;
54
- constructor(method: 'bisection' | 'newton', iterations: number, detail: string);
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 = 'bisection' | 'newton';
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: 'bisection' | 'newton';
52
+ readonly method: "bisection" | "newton";
53
53
  readonly iterations: number;
54
- constructor(method: 'bisection' | 'newton', iterations: number, detail: string);
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 = 'bisection' | 'newton';
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
- if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 2 arguments, got ${args.length}`);
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
- if (args.length !== 1) throw new UnsupportedExpressionError("evaluate", `operator '${node.operator}' takes exactly 1 argument, got ${args.length}`);
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
- if (args.length !== 2) throw new UnsupportedExpressionError("evaluate", `'math:pow' takes exactly 2 arguments, got ${args.length}`);
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.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": "^4.10.0"
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": {