document-compute.js
的单元类型的树式求值器
document-schema.js的MathExpression- - - - - -evaluate()对于同一解释器上的点值和有界区间,solveFor()用于在一个未知数上的数值寻根(平分法和牛顿法)。单位转换因子的精确有理运算,因此注册表转换链永远不会累积浮点漂移。的计算包 documents.js家庭. worker -同构:相同的代码在Node和Cloudflare worker隔离中运行。
创建 ExaDev / documents.js # 573:文档的公式存储为 MathExpression 树(document-schema.js的 src/math.ts, ExaDev / document-schema.js # 15)——a的语义部分 ContentFormula,与LaTeX一起,呈现器逐字序列化。储存那棵树本身并不能买到什么;陈述公式并报告其计算结果的文档需要实际遍历树并生成一个数字,而不会将不属于一起的维度悄悄地混合在一起。这个包就是这样的东西:一个解释器,在相同问题的三种形式(一个点值、一个有界区间和(通过寻根)一个未知的解)中不变地重用。
开始
需要node . js >=20 和规划 11.6.0。
pnpm install
pnpm build # tsdown -> dist/ (ESM + CJS + .d.ts)
pnpm typecheck # tsc -p tsconfig.json && tsc -p tsconfig.node.json (dual tsconfig)
pnpm lint # eslint . --fix --cache --max-warnings 0
pnpm test # vitest run
pnpm test:watch # vitest
pnpm test:workers # vitest run --config vitest.workers.config.ts, inside a real Cloudflare Workers (workerd) isolate
要运行单个测试文件,将其路径直接传递给vittest,例如: pnpm exec vitest run src/compute/evaluate.test.ts。
它提供了什么
| 模块 | 出口 |
|---|---|
compute/rational | Rational, toRational, toExactRational, addRational, subtractRational, multiplyRational, divideRational, rationalToNumber |
compute/dimensions | dimensionExponent, dimensionsEqual, isDimensionless, multiplyDimensions, divideDimensions, scaleDimension, dimensionToString |
compute/quantity | quantity, addQuantities, subtractQuantities, multiplyQuantities, divideQuantities, negateQuantity, absQuantity, powQuantity, sqrtQuantity, sinQuantity, cosQuantity, tanQuantity |
compute/interval | interval, pointInterval, addIntervals, subtractIntervals, multiplyIntervals, divideIntervals, negateInterval, absInterval |
compute/evaluate | evaluate, EvaluationResult, isInterval |
compute/solve | solveFor, SolveMethod, SolveForOptions |
compute/errors | IncompatibleDimensionsError, UnboundSymbolError, UnknownUnitError, DivisionByZeroError, UnsupportedExpressionError, NumericDomainError, NonConvergentSolveError |
表中的每个模块都是从包根重新导出的,所以它的导出是从 'document-compute.js' 直接。
此求值器使用并产生的值类型- Quantity, Interval, EvaluationValue, FormulaBindings,以及它们的Zod模式——在这里没有定义:它们是键入的契约 document-schema.js 本身(src/math.ts旁边, MathExpression),因此评估输入是模式验证的形状,就像该包中的其他所有东西一样。从 'document-schema.js' 和这个包一样:
import { evaluate } from "document-compute.js";
import type { FormulaBindings, MathExpression } from "document-schema.js";
// F = m * a
const force: MathExpression = {
kind: "app",
operator: "math:multiply",
args: [
{ kind: "sym", id: "m" },
{ kind: "sym", id: "a" },
],
};
const bindings: FormulaBindings = {
m: { kind: "quantity", magnitude: 2, dimension: { mass: 1 } },
a: { kind: "quantity", magnitude: 3, dimension: { length: 1, time: -2 } },
};
const result = evaluate(force, bindings);
// { kind: 'quantity', magnitude: 6, dimension: { mass: 1, length: 1, time: -2 } }
evaluate 绝不在其结果中返回失败:成功调用返回plain Quantity | Interval (请参见下面为什么返回类型不是字面上的 Quantity | Interval | error),而每一次真正的失败都会抛出一个错误 compute/errors’自己的类——同样的习语 document-schema.js的 schema-io.ts 和 archive-codec的 CompoundFileFormatError/ArchiveWalkLimitError 已经使用:有名字的,可捕捉的 Error 子类,从来没有 { ok, error } 包装折叠成返回类型。
单位为类型系统
每一个 Quantity 这个求值器产生携带a dimension -一个SI指数向量重用 document-schema.js的自己的 DimensionVector ({ length: 1, time: -1 } 的速度, {} 对于无量纲)-在平原旁边 magnitude. 添加或减去两个尺寸不匹配的量不是这个包将生成的数字: addQuantities/subtractQuantities (里面也有相同的规则 evaluate 为 math:add/math:subtract)把 IncompatibleDimensionsError 而不是返回一个碰巧错误的值。乘法和除法总是维度上定义的-它们通过添加或减去指数来组合维度向量(compute/dimensions.ts的 multiplyDimensions/divideDimensions) -所以这里没有什么可拒绝的,只有一个结果维度需要计算。 powQuantity/sqrtQuantity 将同样的规则扩展到指数:无量纲的基数允许任何实数指数,有量纲的基数只能是整数幂,其缩放后的指数保持整数(DimensionVectorSchema 需要整数,所以 sqrt 的 length^1 没有回答,扔—— IncompatibleDimensionsError 同样,不是没有人要求的分数维度)。
一个 MathExpression的 'qty' Leaf携带一个精确值加上一个单元注册表id (document-schema.js的 MathQty/MathUnit,决心反对 SymbolTable 通过 evaluate(第三个论点); evaluateQty (compute/evaluate.ts)解析该id,然后进行计算 si_value = value * factorToSi + offsetToSi - - - - - - 完全用精确的BigInt有理数运算 (compute/rational.ts),转换为纯JS number 只有一次,在那一刻,分解si相干幅度作为a进入评估器 Quantity. 这是该方案中一个经过深思熟虑的精确边界:一个单位注册表转换链(英尺到米,仿射温标,每单位归一化的电力系统数量)从不重复浮点四舍五入的方式 Number 乘法,因为这个转换的上游每一步都是位精确的BigInt算术,在每个操作中都减少到最低项。边界的下游——普通 +/-/*// already-resolved之间 Quantity 大小, sin/cos/sqrt,一个 solveFor 根-是普通的浮点数,因为这些结果通常不是精确的(没有精确的有理数) sin(1)),让它们保持位精确将是错误的精确,而不是更有力的保证。 QuantitySchema我自己的现场评论 magnitude 说明了这种权衡;这是该计划有意做出的判断,而不是疏忽。
区间算术,在相同的求值器上
这个问题本身的例子是一个合规区域: 0.87 <= cos(phi) <= 1. 而不是为“一个公式,但是有范围”添加第二个求值器, FormulaBindings 让任何符号绑定到一个 Interval ({ kind: 'interval', min, max, dimension })而不是一个点 Quantity, evaluate的 'app' 调度促进平原 Quantity 简并点区间的操作数(pointInterval),当二元运算符的两边都是an时 Interval -相同的树遍历,相同的操作符id,只是在范围上运行,而不是在注意到一个点时运行。 addIntervals/subtractIntervals 直接合并端点; multiplyIntervals/divideIntervals 执行标准规则,即产品或商的极值总是在两个区间端点的四个角组合之一处达到(min*min, min*max, max*min, max*max(或通过除法的倒数等效)-这实际上是将教科书中的符号-大小写表(positive×positive, negative×negative, straddling×straddling和所有混合情况)分解成一个公式,无论间隔位于零的哪一边都是正确的; interval.test.ts 直接练习每个符号的组合,而不是相信封闭的形式。除以接近零或跨越零的间隔没有确定的结果(它将通过±无穷大)并抛出 DivisionByZeroError 而不是让 Infinity/NaN 静默地进入计算的其余部分。只有四个算术运算符加上负/abs在此传递中有间隔规则 pow/sqrt/三角函数是 Quantity-only和throw UnsupportedExpressionError 在一个 Interval 操作数,因为非单调函数或符号相关函数的正确一般区间范围需要更多的分析,而不是本文的范围所涵盖的(见下文)。
数值求解
solveFor(expression, targetValue, unknownSymbol, bindings, options?, context?) 求出的大小 unknownSymbol 使 expression 评估, targetValue,通过对相同的根进行查找 evaluate -永远不要用代数方法重新排列表达式。它实现了问题要求和允许的算法 options.method 在他们之间做出选择('bisection',默认值,或 'newton'):
- 二等分的 需要
options.bracket: [low, high]其残差具有相反的符号(中间值定理是其正确性的全部参数),每次迭代都将括号减半,并且不能发散-这是安全的默认值。 - 牛顿法 需要
options.initialGuess通过中心差估计导数,(f(x+h) - f(x-h)) / (2h)(options.derivativeStep、默认1e-6) -选择单侧正向/向后差分,因为它的截断误差为O(h²)而不是O(h). 如果没有符号层,就没有符号衍生品可用(参见超出范围),所以这里的全部内容是数字层。
两把 NonConvergentSolveError 而不是返回一个他们不能保证的数字:bissection,当它的括号没有跨越根或迭代预算(options.maxIterations(默认值为100)在剩余值降至以下之前耗尽 options.tolerance (默认 1e-9); 当数值导数消失或发散,或相同的预算/公差耗尽时,牛顿。 options.unknownDimension 设置 DimensionVector 未知在每个试验点(默认为无量纲)下被绑定,因此物理上的未知量纲(长度,质量)可以根据沿途检查量纲的公式正确解决。
import { solveFor } from "document-compute.js";
import type { MathExpression } from "document-schema.js";
// x^2 = 4, solve for x
const xSquared: MathExpression = {
kind: "app",
operator: "math:pow",
args: [
{ kind: "sym", id: "x" },
{ kind: "num", numerator: "2", denominator: "1" },
],
};
solveFor(xSquared, 4, "x", {}, { bracket: [0, 3] }); // 2, via bisection
solveFor(xSquared, 4, "x", {}, { method: "newton", initialGuess: 3 }); // 2, via Newton
偏离问题
#573要求的一件事是在收养时关闭的,而不是在这里建造的: Quantity 和 FormulaBindings (与 Interval 和 EvaluationValue)是打印出来的合同 document-schema.js的 src/math.ts旁边, MathExpression 本身,正如问题所提出的那样——评估输入是经过验证的模式,就像包中的其他所有内容一样,这个包以同样的方式从那里导入它们 MathExpression, DimensionVector, ExactRational,而不是携带包局部定义。
工作实例差分线束
ExaDev / documents.js # 794 将#573自己声明的微分器(度量真实文档的公式的部分,该公式的计算再现了文档自己声明的答案)拆分为自己的包一次 evaluate/solveFor 它们是用来衡量的。 src/harness/worked-example.ts 和 src/harness/corpus.ts 那是挽具吗?
runWorkedExampleSequence(formulas, symbolTable?, options?)遍历一个按文档顺序排列的已降低的序列ContentFormula重视和认可“给定的、公式的、陈述的结果”形状的工作示例实际上具有:a 定义 (F = m \times a-右边仍然提到一个符号),a 绑定 (m = 2 kg-一个完全封闭的“给定”),和a 表示结果 (F = 6 N-结构上与绑定相同,但重申定义等待的符号)。定义本身的右侧是在达到其所述结果时根据当前绑定进行计算的,而不是在定义行首次出现时进行快照—常见的真实文档首先声明一般规律,然后是具体数字,然后是答案。每一个结果都是其中之一match,mismatch,gap(命名一个特定的WorkedExampleGap- - - - - -unbound-symbol,unknown-unit,incompatible-dimensions,division-by-zero,unsupported-construct,numeric-domain,non-convergent-solve-一人compute/errors.ts类),或unresolved(该文件从未重申对这个定义的回答)。比较是相对容忍度(1e-3(默认),而不是完全相等,因为一个工作示例自己陈述的答案通常是四舍五入的。collectFormulas(document)/runCorpus(documents, options?)从文字处理中提取公式序列ContentDocument的块流(包括表格单元格),并立即在整个语料库上运行控制装置,聚合一个组合覆盖率分数加上每个文档自己的结果;formatCorpusReport将结果作为纯文本呈现给CLI/控制台调用者。
作用域为点值(Quantity)回答:这个工具计算的每个值都来自于对没有绑定的封闭语句求值 evaluate 不能变成 Interval (一个 Interval 只有在将一个符号绑定到一个符号时才会出现)-一个真正的间隔值工作示例(0.87 <= cos(phi) <= 1(#573自己对区间算术的说明)在这个“symbol = expression”相等语法中根本没有表示,因为两者都没有 MathExpression 也不 documents.jsLaTeX的降低具有复合不等到范围的读数,并且超出了此传递的范围,而不是无声地错误处理。
src/harness/corpus.test.ts 证明了整个管道端到端标记文本通过 markdown-codec的 $$ 块识别和 documents.js的 lowerMarkdownMath (“LaTeX降低”#794将其命名为工作示例的自然来源)放入这个工具中—针对一个小的、手工编写的初学者语料库。 markdown-codec 和 documents.js 是 devDependencies只:它们都位于这个包的家族依赖顺序之上(参见monorepo根README的包表),所以在没有循环的情况下,它们都不能是运行时依赖。不过,这个线束本身并不是只能测试的 src/index.ts 将其导出为真正的公共API (runWorkedExampleSequence, collectFormulas, runCorpus, formatCorpusReport),与…相同 evaluate/solveFor;真正的事实是:它在家族中的任何其他地方都没有运行时消费者,不像 evaluate/solveFor 在它下面 document-mcp的 compute_formula 工具直接依赖于(参见约定)。现在存在一个大规模语料库层: pnpm test:corpus 运行被忽略的错误 test/corpus/ 套件, scripts/generate-corpus.mjs 通过机械降低的算术语法(明确的\times / \frac / \sqrt组成,从不并列)重新生成- 300个确定性工作示例文档,每个声明的答案由生成器自己的并行评估计算-测量300/300个匹配,0个空白,0个未解决,并将测量报告写入 test/corpus/report.txt. 它的第一次运行立即得到了回报:每个带有NEGATIVE声明答案的文档在降序的只带负号的一元读取下降低了其整个相等性,现在一般化了(在关系或操作符后面加一个负号,为下面的操作数签名)。真正的教科书语料库仍然是一个局部的添加在上面:点相同 test/corpus/files/ 目录在真实的标记文件和线束测量他们相同。
在构建这个线束自己的装置时,一个真正的bug浮出水面 documents.js乳胶降低: F = m \times a (这是教科书上几乎所有公式的标准写法)降低到 (F = m) \times a 而不是 F = (m \times a),因为降低运算符以相同的优先级折叠关系运算符和算术运算符,而没有概念 = 应该把松散线绑起来吗 ExaDev / documents.js # 812. 这个包自己的fixture通过显式的右括号(F = {m \times a}(它正确降低),因为修复降低本身超出了这个包的范围。
超出范围
直接引用issue自己的scope行: 这不是Mathematica意义上的CAS -单位类型的评估和数值求解是“从文档中计算公式结果”的90%,并且现在可以本地构建。 具体地说,这个传递不尝试,并且这个包不携带指向:
- 象征性的代数 -精确的表达式重排作为LaTeX发出,简化,集成。
solveFor用数值方法找到一个根;它从不从代数上孤立未知。 - SymPy sidecar或任何其他符号引擎适配器。 这个问题将它命名为符号工作的最终归宿,在这个包没有定义或存根的求值器接口后面。
- 矩阵值评估。
MathExpression的'matrix'节点存在于语法中document-schema.js定义,但此求值器只生成标量Quantity/Interval值;一个'matrix'节点将UnsupportedExpressionError而不是默默地被错误评估。 - 的一般间隔规则
pow/sqrt/三角算子。 这些是为Quantity只有;适用于Interval他们抛出的操作数UnsupportedExpressionError而不是猜测一个范围,一个非单调或符号相关的函数需要真正的分析才能得到正确的结果。
约定
- 工人同构(参见 family-wide公约):运行时
src/不得进口node:*,或使用Buffer全局-由a强制执行no-restricted-imports/no-restricted-globalsESLint规则,并在CI中通过在实际的workerd隔离(pnpm test:workers). 精确有理的算术很简单BigInt,从来没有node:crypto或任何其他仅限节点的原语,正是如此。 - 只有
src/index.ts可以命名为index.*一个自定义ESLint规则(local/no-non-barrel-index)拒绝任何其他模块indexBasename,因为这将是一个隐藏的入口点exports地图package.json不做广告。 - 失败总是被抛,被命名
Error子类(compute/errors.ts),从来没有{ ok, error }结果包装器-匹配document-schema.js的schema-io.ts和archive-codec而不是单独为这个包发明第二个约定。 - 连接到
document-mcp自 ExaDev / documents.js # 928:其compute_formula工具是一个真正依赖于运行时的工具,它读取文档的嵌入公式并对每个公式进行计算evaluate(). 仍然不是依赖documents.js,document-cli,或web-这个包裹在上面document-schema.js独自一人在家庭的抚养秩序中,而且document-mcp是第一个具有实际计算文档公式的自然的、面向代理的需求的表面;将其连接到任何其他表面仍然是一个单独的,深思熟虑的决定。 - 发布、CI和提交消息约定都是工作空间范围的,而不是包本地的——请参阅 monorepo root README 对于机制(每包拓扑)
semantic-release通过@exadev/semantic-release-workspace、OIDC信任的npm发布,以及发布后的重新发布/认证作业)。
安装
pnpm add document-compute.js
# or
npm install document-compute.js
许可证
麻省理工学院