@ohtaman/washi 0.4.0 → 0.5.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.
Files changed (97) hide show
  1. package/README.ja.md +100 -107
  2. package/README.md +98 -105
  3. package/README.zh-CN.md +101 -108
  4. package/dist/browser/highs-worker.js +21 -3
  5. package/dist/browser/loader-CtaDHef_.js +273 -0
  6. package/dist/browser/solvers/glpk.js +433 -286
  7. package/dist/browser/solvers/glpk.min.js +1 -1
  8. package/dist/browser/solvers/z3.js +1002 -981
  9. package/dist/browser/solvers/z3.min.js +2 -2
  10. package/dist/browser/washi.js +2129 -676
  11. package/dist/browser/washi.min.js +4 -4
  12. package/dist/lib/highs-worker.js +21 -3
  13. package/dist/lib/loader-DmNC_UNk.js +279 -0
  14. package/dist/lib/registry-BEfvpZW3.js +951 -0
  15. package/dist/lib/solvers/glpk.js +201 -90
  16. package/dist/lib/solvers/z3.js +399 -259
  17. package/dist/lib/washi.js +1627 -988
  18. package/dist/types/index.d.ts +4 -4
  19. package/dist/types/index.d.ts.map +1 -1
  20. package/dist/types/modeler/big-m.d.ts +75 -0
  21. package/dist/types/modeler/big-m.d.ts.map +1 -0
  22. package/dist/types/modeler/boolean-expr.d.ts +103 -14
  23. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  24. package/dist/types/modeler/constraint.d.ts +36 -9
  25. package/dist/types/modeler/constraint.d.ts.map +1 -1
  26. package/dist/types/modeler/expression.d.ts +113 -28
  27. package/dist/types/modeler/expression.d.ts.map +1 -1
  28. package/dist/types/modeler/index.d.ts +10 -6
  29. package/dist/types/modeler/index.d.ts.map +1 -1
  30. package/dist/types/modeler/lp-writer.d.ts +27 -2
  31. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  32. package/dist/types/modeler/model.d.ts +478 -131
  33. package/dist/types/modeler/model.d.ts.map +1 -1
  34. package/dist/types/modeler/objective.d.ts +59 -0
  35. package/dist/types/modeler/objective.d.ts.map +1 -0
  36. package/dist/types/modeler/parser.d.ts +34 -10
  37. package/dist/types/modeler/parser.d.ts.map +1 -1
  38. package/dist/types/modeler/quad-expr.d.ts +26 -7
  39. package/dist/types/modeler/quad-expr.d.ts.map +1 -1
  40. package/dist/types/modeler/solution-validator.d.ts +93 -0
  41. package/dist/types/modeler/solution-validator.d.ts.map +1 -0
  42. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
  43. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  44. package/dist/types/modeler/solvers/highs/loader.d.ts +36 -11
  45. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  46. package/dist/types/modeler/solvers/highs/solver.d.ts +11 -4
  47. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  48. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  49. package/dist/types/modeler/solvers/mock.d.ts +14 -5
  50. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  51. package/dist/types/modeler/solvers/types.d.ts +71 -11
  52. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  53. package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
  54. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  55. package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
  56. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
  57. package/dist/types/modeler/types.d.ts +59 -1
  58. package/dist/types/modeler/types.d.ts.map +1 -1
  59. package/dist/types/modeler/validation.d.ts +56 -0
  60. package/dist/types/modeler/validation.d.ts.map +1 -0
  61. package/dist/types/modeler/variable.d.ts +48 -14
  62. package/dist/types/modeler/variable.d.ts.map +1 -1
  63. package/docs/examples/diet-sensitivity.md +90 -0
  64. package/docs/examples/ecommerce-coupons.md +106 -0
  65. package/docs/examples/index.md +53 -0
  66. package/docs/examples/knapsack.md +60 -0
  67. package/docs/examples/portfolio-qp.md +83 -0
  68. package/docs/examples/production.md +51 -0
  69. package/docs/examples/shift-scheduling.md +175 -0
  70. package/docs/examples/smt-security.md +74 -0
  71. package/docs/examples/transportation.md +90 -0
  72. package/docs/examples/unsat-core.md +64 -0
  73. package/docs/getting-started/ai-agents.md +267 -0
  74. package/docs/getting-started/cross-origin.md +87 -0
  75. package/docs/getting-started/index.md +116 -0
  76. package/docs/getting-started/runtimes.md +227 -0
  77. package/docs/guide/constraints.md +177 -0
  78. package/docs/guide/expressions.md +204 -0
  79. package/docs/guide/objectives.md +170 -0
  80. package/docs/guide/unsat-core.md +147 -0
  81. package/docs/guide/variables.md +188 -0
  82. package/docs/ja/getting-started/ai-agents.md +266 -0
  83. package/docs/solvers/custom-solver.md +525 -0
  84. package/docs/solvers/glpk.md +66 -0
  85. package/docs/solvers/highs.md +93 -0
  86. package/docs/solvers/index.md +42 -0
  87. package/docs/solvers/z3.md +78 -0
  88. package/docs/zh/getting-started/ai-agents.md +266 -0
  89. package/llms-full.txt +3249 -0
  90. package/llms.txt +197 -0
  91. package/package.json +18 -5
  92. package/skills/washi/SKILL.md +197 -0
  93. package/dist/browser/loader-CVt4CloI.js +0 -58
  94. package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
  95. package/dist/lib/constraint-BRuB3ooC.js +0 -90
  96. package/dist/lib/loader-Dti0v9yC.js +0 -50
  97. package/dist/lib/registry-DksqCSjI.js +0 -32
package/README.zh-CN.md CHANGED
@@ -16,7 +16,7 @@
16
16
  <p>
17
17
  <a href="https://www.npmjs.com/package/@ohtaman/washi"><img src="https://img.shields.io/npm/v/@ohtaman/washi?color=crimson&label=npm" alt="npm version" /></a>
18
18
  <a href="https://github.com/ohtaman/washi/actions/workflows/distribution.yml"><img src="https://github.com/ohtaman/washi/actions/workflows/distribution.yml/badge.svg" alt="CI Status" /></a>
19
- <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
19
+ <a href="https://ohtaman.github.io/washi/zh/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
20
20
  <a href="https://ohtaman.github.io/washi/demo/"><img src="https://img.shields.io/badge/demo-playground-brightgreen" alt="live demo" /></a>
21
21
  <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
22
22
  </p>
@@ -28,20 +28,20 @@
28
28
 
29
29
  HiGHS 是默认求解器;Z3 和 GLPK 是可选插件。软件包提供 Node.js 和浏览器分发版本,并包含 WebAssembly 资源。部署要求请参阅[浏览器使用说明](#-浏览器与-web-worker-使用指南)。
30
30
 
31
- [试用浏览器演示](https://ohtaman.github.io/washi/demo/) · [示例源码与运行方法](https://github.com/ohtaman/washi/blob/main/examples/README.md) · [ESM / script / CodePen 示例](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)
31
+ [在线文档](https://ohtaman.github.io/washi/zh/) · [试用浏览器演示](https://ohtaman.github.io/washi/demo/) · [AI 编程助手快速参考](docs/zh/getting-started/ai-agents.md) · [Agent Skill (`SKILL.md`)](skills/washi/SKILL.md) · [示例源码与运行方法](https://github.com/ohtaman/washi/blob/main/examples/README.md) · [ESM / script / CodePen 示例](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)
32
32
 
33
33
  ```ts
34
- import { Model, expr } from "@ohtaman/washi";
34
+ import { Model, expr, maximize } from "@ohtaman/washi";
35
35
 
36
36
  const m = new Model("木工产品利润最大化");
37
37
  const x = m.integer("桌子", { lb: 0 });
38
38
  const y = m.integer("椅子", { lb: 0 });
39
39
 
40
- m.maximize(expr`30 * ${x} + 50 * ${y}`);
41
- m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
42
- m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
40
+ m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
41
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
42
+ m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
43
43
 
44
- await m.optimize();
44
+ await m.solve();
45
45
  console.log(`生产计划: x=${x.value}, y=${y.value} (最大利润: $${m.objectiveValue})`);
46
46
  ```
47
47
 
@@ -49,10 +49,10 @@ console.log(`生产计划: x=${x.value}, y=${y.value} (最大利润: $${m.object
49
49
 
50
50
  ## 🎯 WaSHI 提供的功能
51
51
 
52
- 1. **代数模型构建**:使用领域变量构造函数(`m.binary`、`m.integer`、`m.continuous`)、标签模板字面量(`expr`)、向量聚合函数(`dot`、`sum`)以及首选的 `m.constrain()` 约束方法,精确且优雅地描述数学模型。
53
- 2. **求解器适配器**:通过共同的建模 API 使用 HiGHS、Z3 或 GLPK,也可以[注册自己的适配器](https://github.com/ohtaman/washi/blob/main/docs/custom-solver-guide.md)。需要选择支持相应问题类型的求解器;不同求解器的能力并不相同。
54
- 3. **本地执行与 WebAssembly 分发**:使用本地求解器适配器时,可在应用内求解,无需单独的 Python 服务或本地 C/C++ 编译。浏览器部署需要包含生成的 JS、Worker 和 WASM 资源。离线使用还需要这些资源可在本地访问或已缓存。Z3 有额外的浏览器隔离要求。
55
- 4. **命名约束与 SMT 结果**:显式使用 Z3 检查可满足性,并通过 Unsat Core 调查冲突约束。返回的是一个冲突约束子集,不一定是最小子集。
52
+ 1. **代数模型构建**:使用领域变量构造函数(`m.binary`、`m.integer`、`m.continuous`)、标签模板字面量(`expr`)、向量聚合函数(`dot`、`sum`)以及一等约束(`m.constraint`)构建优化模型。由于 JavaScript 不支持运算符重载,`` expr`...` `` 采用类似参数化 SQL 模板的方式,在解析中缀数学运算符(`+`、`-`、`*`、`^2`、`<=`、`>=`、`==`)的同时,将 `${...}` 插值直接绑定为强类型的 `Var` 或表达式对象引用,从而保留静态类型检查、IDE 重命名支持以及多维索引访问(`${flow[i][j]}`)。
53
+ 2. **求解器适配器**:通过共同的建模 API 使用 HiGHS、Z3 或 GLPK,也可以[注册自己的适配器](https://github.com/ohtaman/washi/blob/main/docs/zh/solvers/custom-solver.md)。需要选择支持相应问题类型的求解器;不同求解器的能力并不相同。
54
+ 3. **本地执行与 WebAssembly 分发**:使用本地求解器适配器时,可在应用内直接求解,无需单独的 Python 服务或本地 C/C++ 编译——这消除了在快速开发和交付交互式工具时的后端基础设施瓶颈,也便于在客户端直接与浏览器内 LLM(如 WebGPU / WebLLM)协同工作。浏览器部署需要包含生成的 JS、Worker 和 WASM 资源。离线使用还需要这些资源可在本地访问或已缓存。Z3 有额外的浏览器隔离要求。
55
+ 4. **命名约束与 SMT 结果**:显式使用 Z3 检查可满足性,并通过冲突约束(`conflicts` / `unsatCore`)调查冲突约束。返回的是一个冲突约束子集,不一定是最小子集。
56
56
 
57
57
  模型构建与转换由 WaSHI 负责。支持的问题类型、数值行为和求解时间取决于所选求解器、配置及具体问题。建模开销与求解器执行时间应分别测量;某个模型上的结果并不构成通用性能保证。
58
58
 
@@ -92,61 +92,54 @@ npm install z3-solver # 用于 SMT 与冲突诊断
92
92
  npm install glpk.js # 用于 GLPK 求解器 (GPL-3.0)
93
93
  ```
94
94
 
95
+ *(可选)* 面向 AI 编程助手(Claude Code、Antigravity、Cursor、Codex 等),`@ohtaman/washi` 随包附带了可复用的 [`skills/washi/SKILL.md`](skills/washi/SKILL.md) 建模与校验技能及 [AI 编程助手快速参考](docs/zh/getting-started/ai-agents.md):
96
+
97
+ ```bash
98
+ mkdir -p .agents/skills/washi
99
+ cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
100
+ ```
101
+
95
102
  ---
96
103
 
97
104
  ## 🚀 快速上手指南
98
105
 
99
- ### 1. 混合整数线性规划 (MIP: 生产计划问题)
106
+ ### 1. 多维变量、条件指示约束 (`m.indicator`) 与解验证 (`m.validateSolution`)
100
107
 
101
- 在有限资源约束下实现利润最大化:
108
+ 一行代码声明一维字典或二维变量矩阵,通过 `m.indicator()` 表达 Big-M 条件规则,结合 `onProgress` / `AbortSignal` 控制求解过程,并通过 `m.validateSolution()` 独立校验解的合法性:
102
109
 
103
110
  ```ts
104
111
  import { Model, expr } from "@ohtaman/washi";
105
112
 
106
- const m = new Model("FactoryPlanning");
107
-
108
- // 决策变量(非负整数)
109
- const chairs = m.integer("chairs", { lb: 0 });
110
- const tables = m.integer("tables", { lb: 0 });
111
-
112
- // 借助标签模板字面量自然表达目标函数与约束
113
- m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
114
- m.constrain(expr`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
115
- m.constrain(expr`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
116
-
117
- // 使用默认的 HiGHS WASM 引擎求解
118
- const status = await m.optimize();
119
-
120
- console.log(`求解状态: ${status}`); // "Optimal"
121
- console.log(`最大利润: $${m.objectiveValue}`); // 1100
122
- console.log(`生产量: 椅子=${chairs.value}, 桌子=${tables.value}`); // 椅子 20, 桌子 10
123
- ```
113
+ const m = new Model("FacilityPlanning");
124
114
 
125
- #### 🗂 多维变量网格
126
-
127
- 一行代码声明变量字典或多维变量矩阵:
128
-
129
- ```ts
130
- // 1D 字典形式的布尔变量
115
+ // 1D 字典形式的二元变量
131
116
  const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
132
117
 
133
118
  // 2D 运输量矩阵: [工厂] x [门店]
134
- const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
119
+ const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0, ub: 1000 });
135
120
 
136
121
  // 2D 网格坐标 (例如 8x8 棋盘)
137
122
  const board = m.binary(8, 8, { prefix: "queen" });
138
- ```
139
-
140
- #### 🔀 Big-M 条件指示约束 (`m.indicator`)
141
123
 
142
- 轻松建模条件触发规则与固定成本启动,无需繁琐的手工 Big-M 代数推导:
143
-
144
- ```ts
145
- // "若工厂关闭 (openCity['Tokyo'] == 0),则产量 flow 必须为 0"
124
+ // Big-M 条件约束: 若工厂关闭 (openCity['Tokyo'] == 0),则 flow <= 0
125
+ // (省略 M 时,WaSHI 会根据变量的有限上下界自动推导紧凑安全的 Big-M)
146
126
  m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
147
127
  activeValue: 0,
148
- M: 1000,
128
+ name: "tokyo_closed_no_flow",
149
129
  });
130
+
131
+ // 支持实时进度回调 (onProgress) 与 AbortSignal 取消
132
+ const controller = new AbortController();
133
+ await m.solve({
134
+ signal: controller.signal,
135
+ onProgress: (ev) => {
136
+ // 实时指标: ev.elapsedTime, ev.objectiveValue, ev.objectiveBound, ev.mipGap
137
+ },
138
+ });
139
+
140
+ // 独立校验变量边界、整数性以及线性/逻辑约束
141
+ const report = m.validateSolution();
142
+ console.log("解验证通过?", report.valid);
150
143
  ```
151
144
 
152
145
  ---
@@ -156,7 +149,7 @@ m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
156
149
  在满足预期收益与预算约束的前提下,基于协方差矩阵最小化投资组合风险:
157
150
 
158
151
  ```ts
159
- import { Model, QuadExpr, sum, dot, expr } from "@ohtaman/washi";
152
+ import { Model, QuadExpr, sum, dot, minimize, expr } from "@ohtaman/washi";
160
153
 
161
154
  const m = new Model("MarkowitzPortfolio");
162
155
  const assets = ["Tech", "Bonds", "RealEstate"];
@@ -166,11 +159,11 @@ const returns = [0.14, 0.04, 0.08];
166
159
  const w = m.continuous(assets, { lb: 0, ub: 1 });
167
160
 
168
161
  // 1. 预算约束: 资金分配总额 100%
169
- m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
162
+ m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
170
163
 
171
164
  // 2. 目标回报率: 组合最低回报率 >= 8.5%
172
165
  const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
173
- m.constrain(expr`${dot(retMap, w)} >= 0.085`, "min_return");
166
+ m.constraint(expr`${dot(retMap, w)} >= 0.085`, "min_return");
174
167
 
175
168
  // 3. 资产协方差矩阵 (风险与相关性)
176
169
  const cov = [
@@ -186,9 +179,9 @@ for (let i = 0; i < assets.length; i++) {
186
179
  risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
187
180
  }
188
181
  }
189
- m.minimize(risk);
182
+ m.objective(minimize(risk));
190
183
 
191
- await m.optimize();
184
+ await m.solve();
192
185
  console.log(`最小方差: ${m.objectiveValue?.toFixed(6)}`);
193
186
  assets.forEach((name) => {
194
187
  console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
@@ -214,13 +207,13 @@ const siren = m.bool("siren");
214
207
  const lux = m.integer("lux", { lb: 0 });
215
208
 
216
209
  // 规则定义
217
- m.constrain(lux.le(50).implies(isNight), "dark_implies_night");
218
- m.constrain(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
210
+ m.constraint(lux.le(50).implies(isNight), "dark_implies_night");
211
+ m.constraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
219
212
 
220
213
  // 传感器当前输入数据
221
- m.constrain(expr`${lux} <= 10`, "sensor_dark");
222
- m.constrain(motion, "sensor_motion");
223
- m.constrain(alarm, "alarm_on");
214
+ m.constraint(expr`${lux} <= 10`, "sensor_dark");
215
+ m.constraint(motion, "sensor_motion");
216
+ m.constraint(alarm, "alarm_on");
224
217
 
225
218
  // 无目标函数直接判定可满足性 (SAT)
226
219
  const res = await m.check({ solver: "z3" });
@@ -231,9 +224,9 @@ console.log(`警报响起? ${m.values.get("siren")}`); // true
231
224
 
232
225
  ---
233
226
 
234
- ### 4. 约束冲突根因诊断 (`unsatCore`)
227
+ ### 4. 约束冲突根因诊断 (`conflicts` / `unsatCore`)
235
228
 
236
- 当规则或约束之间存在矛盾时,Z3 能够提取引发冲突的**矛盾约束子集**(Unsat Core,不保证最小性;需使用 Z3):
229
+ 当规则或约束之间存在矛盾时,Z3 能够提取引发冲突的**矛盾约束子集**(Unsat Core / Conflicts,不保证最小性;需使用 Z3):
237
230
 
238
231
  ```ts
239
232
  import { Model, expr } from "@ohtaman/washi";
@@ -245,24 +238,24 @@ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
245
238
  const rushDelivery = m.bool("rushDelivery");
246
239
 
247
240
  // 规则 1: 每位工程师成本 $8,000
248
- m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
241
+ m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
249
242
 
250
243
  // 规则 2: 客户要求加急交付
251
- m.constrain(rushDelivery, "rush_required");
244
+ m.constraint(rushDelivery, "rush_required");
252
245
 
253
246
  // 规则 3: 加急交付需至少配备 8 名工程师
254
- m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
247
+ m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
255
248
 
256
249
  // 规则 4: 董事会严格预算上限 $50,000 (冲突点: 8人 * $8,000 = $64,000 > $50,000!)
257
- m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
250
+ m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
258
251
 
259
252
  // 规则 5: 最低 1 名员工用工底线(无关规则)
260
- m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
253
+ m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
261
254
 
262
255
  const res = await m.check({ solver: "z3" });
263
256
 
264
257
  console.log(`求解状态: ${res.status}`); // "Unsat"
265
- console.log("冲突约束子集:", res.unsatCore);
258
+ console.log("冲突约束子集:", res.conflicts);
266
259
  // 输出: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
267
260
  // 注:无关的 "minimum_one_employee" 被准确排除在外!
268
261
  ```
@@ -274,18 +267,18 @@ console.log("冲突约束子集:", res.unsatCore);
274
267
  使用 GNU Linear Programming Kit (GLPK) 求解器插件:
275
268
 
276
269
  ```ts
277
- import { Model, expr } from "@ohtaman/washi";
270
+ import { Model, expr, maximize } from "@ohtaman/washi";
278
271
  import "@ohtaman/washi/solvers/glpk";
279
272
 
280
273
  const m = new Model({ solver: "glpk" });
281
274
  const x = m.integer("x", { lb: 0 });
282
275
  const y = m.integer("y", { lb: 0 });
283
276
 
284
- m.maximize(expr`30 * ${x} + 50 * ${y}`);
285
- m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
286
- m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
277
+ m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
278
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
279
+ m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
287
280
 
288
- const status = await m.optimize();
281
+ const status = await m.solve();
289
282
  console.log(`求解状态: ${status}`); // "Optimal"
290
283
  console.log(`目标函数值: ${m.objectiveValue}`); // 1100
291
284
  ```
@@ -297,7 +290,7 @@ console.log(`目标函数值: ${m.objectiveValue}`); // 1100
297
290
  无需嵌套循环,一行代码完成一维和二维变量的线性组合:
298
291
 
299
292
  ```ts
300
- import { Model, dot } from "@ohtaman/washi";
293
+ import { Model, dot, minimize } from "@ohtaman/washi";
301
294
 
302
295
  const m = new Model();
303
296
  const factories = ["Tokyo", "Osaka"];
@@ -311,8 +304,8 @@ const costs = {
311
304
  const ship = m.continuous(factories, stores);
312
305
 
313
306
  // 一行代码最小化总运输成本:
314
- m.minimize(dot(costs, ship));
315
- await m.optimize();
307
+ m.objective(minimize(dot(costs, ship)));
308
+ await m.solve();
316
309
 
317
310
  // 获取所有变量的最优取值结构
318
311
  const solution = m.getValues(ship);
@@ -362,23 +355,23 @@ await model.solve();
362
355
  </script>
363
356
  ```
364
357
 
365
- 若通过 CDN 加载,可使用 `https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.1.2/dist/browser/washi.min.js`(请固定 `0.1.2` 及以上版本号)。ESM 使用同版本的 `dist/browser/washi.js`。选择 CDN URL 就是显式使用 CDN;本地部署不会自动改用外部 CDN。
358
+ 若通过 CDN 加载,可使用 `https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js`(待包含相应 API 的版本发布后指定该版本号)。ESM 使用同版本的 `dist/browser/washi.js`。选择 CDN URL 就是显式使用 CDN;本地部署不会自动改用外部 CDN。
366
359
 
367
360
  ### 浏览器环境中的求解器插件(GLPK & Z3)
368
361
 
369
- 浏览器发布包在 `dist/browser/solvers/` 下提供了开箱即用的独立求解器插件(通过 CDN 引入时适用于 `>= 0.2.0` 或 `@latest`):
362
+ 浏览器发布包在 `dist/browser/solvers/` 下提供了开箱即用的独立求解器插件:
370
363
  - **GLPK**: `dist/browser/solvers/glpk.js` (ESM) 与 `dist/browser/solvers/glpk.min.js` (IIFE,挂载全局 `WashiGLPK`)
371
364
  - **Z3**: `dist/browser/solvers/z3.js` (ESM) 与 `dist/browser/solvers/z3.min.js` (IIFE,挂载全局 `WashiZ3`),附带 `z3-built.js` 和 `z3-built.wasm`
372
365
 
373
366
  #### 浏览器 ESM 使用 GLPK
374
367
  ```html
375
368
  <script type="module">
376
- import { Model } from "./washi/washi.js";
369
+ import { Model, maximize } from "./washi/washi.js";
377
370
  import "./washi/solvers/glpk.js";
378
371
 
379
372
  const model = new Model({ solver: "glpk" });
380
373
  const x = model.continuous("x", { lb: 0, ub: 10 });
381
- model.maximize(x);
374
+ model.objective(maximize(x));
382
375
  await model.solve();
383
376
  console.log("GLPK 求解状态:", model.status);
384
377
  </script>
@@ -391,52 +384,34 @@ await model.solve();
391
384
  <script>
392
385
  const model = new Washi.Model({ solver: "glpk" });
393
386
  const x = model.continuous("x", { lb: 0, ub: 10 });
394
- model.maximize(x);
387
+ model.objective(Washi.maximize(x));
395
388
  model.solve().then(status => console.log("GLPK 求解状态:", status));
396
389
  </script>
397
390
  ```
398
391
 
399
392
  > [!NOTE]
400
393
  > **浏览器环境中使用 Z3 的配置要求与跨源隔离**:
401
- > 由于 Z3 的 WebAssembly 引擎依托多线程 (`SharedArrayBuffer`),现代浏览器强制要求 Cross-Origin Isolation。文档响应头必须包含:
402
- > ```http
403
- > Cross-Origin-Opener-Policy: same-origin
404
- > Cross-Origin-Embedder-Policy: require-corp
405
- > ```
406
- > 并且跨源资源必须包含 `Cross-Origin-Resource-Policy: cross-origin` 标头。
407
- >
408
- > 无法自定义响应标头的常见静态托管(如标准 GitHub Pages 或 CodePen 等 iframe 预览环境)在缺少额外配置时无法直接满足条件。引入 `@ohtaman/washi` 核心库绝不会自动向用户项目中注入或注册 Service Worker。
409
- >
410
- > 在 GitHub Pages 部署的 WaSHI 官方 Web Demo 中,通过在同源独立部署作用域严格限定为 `/washi/` 的 `coi-serviceworker.js`,在首次访问时安全自动重载一次以建立 `window.crossOriginIsolated === true` 环境。在不支持 Service Worker 或隔离失败的环境中,Z3 会清晰展示诊断原因并中止执行,防止无限重载或无响应挂起。
411
-
412
- 完整独立的 ESM、Script 标签与 CodePen 示例请参阅 `examples/distribution/`。
413
-
414
- ### 工厂内网、离线部署、Worker 与自定义 URL
415
-
416
- 将应用构建产物或整个 `dist/browser` 目录部署到本地 HTTP / 内网服务器。所有资源可在本地访问时,即使浏览器缓存为空也不需要外部网络。仅保存 HTML 或第一个 JS 文件是不够的。不保证 `file://` 执行,也不自动提供 PWA 缓存。
417
-
418
- ESM 根据模块 URL 解析资源;script 构建在加载时保存自身的 URL。CDN Worker 使用 Blob 模块启动,需要服务端的跨域资源许可及相应 CSP。CDN Worker 还需允许 `worker-src blob:`。不支持时可使用 `useWorker: false`。Worker 加载或消息处理失败会拒绝求解 Promise。
394
+ > 由于 Z3 的 WebAssembly 引擎依托多线程 (`SharedArrayBuffer`),现代浏览器强制要求跨源隔离(`Cross-Origin-Opener-Policy: same-origin` 与 `Cross-Origin-Embedder-Policy: require-corp`)。关于静态托管(`coi-serviceworker.js`)、离线/内网部署、CSP(`worker-src blob:`)与自定义 WASM URL(`setHighsWasmUrl`)的完整说明,请参阅 [跨源隔离指南](docs/zh/getting-started/cross-origin.md) 与 [运行时环境配置](docs/zh/getting-started/runtimes.md)。
419
395
 
420
- 自定义地址仍可通过 `setHighsWasmUrl(url)` 或 `options.wasmUrl` 指定。显式选择现有 HiGHS CDN:
396
+ 完整独立的 ESM、Script 标签与 CodePen 示例请参阅 [`examples/distribution/`](examples/distribution/README.md)。
421
397
 
422
- ```typescript
423
- import { setHighsWasmUrl, OFFICIAL_HIGHS_CDN_WASM_URL } from "@ohtaman/washi";
424
- setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
425
- ```
398
+ ---
426
399
 
427
- 初始化失败会返回包含请求地址的错误,不会尝试其他主机。依赖旧的域名根目录 / CDN 自动回退的应用需部署生成资源或显式配置 URL。
400
+ ## 🛠️ 自定义求解器开发与架构扩展
428
401
 
429
- ### 验证发布产物
402
+ WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并注册自定义求解器适配器(如云端 REST 求解服务、定制 WebAssembly 引擎或商业求解器):
430
403
 
431
- `npm run test:distribution` 安装实际 npm tarball,在没有可选依赖时构建 Vite / webpack 应用,并通过 VM 执行生成的 JS 和真实 WASM。这不等同于浏览器 / CORS 验证。安装 `npx playwright install chromium` 后,运行 `RUN_DISTRIBUTION_BROWSER=1 npm run test:distribution` 以验证 Chromium。CI 使用该模式,验证跨域部署及外部网络请求阻断。
404
+ - 完整开发指南、接口契约(`Solver`, `SolveModelData`, `CheckModelData`, `SolverResult`)与测试实践详见 [自定义求解器开发指南](docs/zh/solvers/custom-solver.md)。
432
405
 
433
406
  ---
434
407
 
435
- ## 🛠️ 自定义求解器开发与架构扩展
408
+ ## 📚 文档指南
436
409
 
437
- WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并注册自定义求解器适配器(如云端 REST 求解服务、定制 WebAssembly 引擎或商业求解器):
410
+ 完整文档可在 **[ohtaman.github.io/washi/zh/](https://ohtaman.github.io/washi/zh/)** 及仓库的 [`docs/zh/`](docs/zh/)([英文版 `docs/`](docs/))中查阅:
438
411
 
439
- - 完整开发指南、接口契约(`Solver`, `LpModelData`, `SolverResult`)与测试实践详见 [Custom Solver Developer Guide](docs/custom-solver-guide.md)。
412
+ - **快速上手**: [快速开始](docs/zh/getting-started/index.md) · [AI 编程助手快速参考](docs/zh/getting-started/ai-agents.md) · [运行时环境配置](docs/zh/getting-started/runtimes.md) · [跨源隔离指南 (Z3)](docs/zh/getting-started/cross-origin.md)
413
+ - **建模指南**: [决策变量 (`Var`, `BoolVar`)](docs/zh/guide/variables.md) · [表达式与聚合 (`expr`, `sum`, `dot`)](docs/zh/guide/expressions.md) · [约束与逻辑条件 (`indicator`, SMT)](docs/zh/guide/constraints.md) · [目标函数与求解 (`solve`, `validateSolution`)](docs/zh/guide/objectives.md) · [可满足性与 Unsat Core (`check`)](docs/zh/guide/unsat-core.md)
414
+ - **求解器**: [求解器概览](docs/zh/solvers/index.md) · [HiGHS](docs/zh/solvers/highs.md) · [Z3](docs/zh/solvers/z3.md) · [GLPK](docs/zh/solvers/glpk.md) · [自定义求解器](docs/zh/solvers/custom-solver.md)
440
415
 
441
416
  ---
442
417
 
@@ -446,7 +421,7 @@ WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并
446
421
 
447
422
  | 类别 | 示例 | 展示内容 | 求解器 |
448
423
  | :--- | :--- | :--- | :--- |
449
- | **基础模型** | [生产计划](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数变量、利润目标与资源约束 (`expr`, `c`) | HiGHS |
424
+ | **基础模型** | [生产计划](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数变量、利润目标与资源约束 (`expr`) | HiGHS |
450
425
  | **基础模型** | [运输与分配网络](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 二维矩阵变量 (`continuous(rows, cols)`) 与供需平衡 | HiGHS |
451
426
  | **基础模型** | [营养问题与灵敏度分析](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | 影子价格 (`constr.dual`)、松弛量 (`constr.slack`) 与缩减成本 (`var.reducedCost`) | HiGHS |
452
427
  | **实战应用** | [员工排班与冲突诊断](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | 周排班优化与周六休假冲突诊断 | HiGHS + Z3 |
@@ -477,7 +452,7 @@ npm run dev
477
452
 
478
453
  ---
479
454
 
480
- ## 🧪 自动化测试
455
+ ## 🧪 自动化测试与可复现基准测试
481
456
 
482
457
  运行仓库的单元测试与集成测试,并构建分发版本:
483
458
 
@@ -489,6 +464,24 @@ npm test
489
464
  npm run build
490
465
  ```
491
466
 
467
+ ### 可选的可复现基准测试
468
+
469
+ 在不将耗时阈值混入正确性测试断言的前提下,对确定性的 `small-lp`(24 个变量、18 条约束)和 `scheduling-mip`(315 个变量、231 条约束,含重建模型的 `initialValue` 热启动)进行分阶段性能测量:
470
+
471
+ ```bash
472
+ # 运行 Node.js 进程内 WASM 基准测试(HiGHS 与 GLPK,默认 5 次热重复)
473
+ npm run bench
474
+
475
+ # 自定义重复次数或输出 JSON
476
+ node scripts/run-benchmarks.mjs --iterations=10 --json
477
+
478
+ # 同时测量无头 Chromium Web Worker 执行(需要安装 Playwright Chromium)
479
+ node scripts/run-benchmarks.mjs --browser
480
+ ```
481
+
482
+ - **明确的测量边界**:将冷启动初始化(`coldRuntimeInitMs`、`coldFirstSolveMs`)与复用运行时的重复测量(`modelBuildMs`、`validationMs`、LP 字符串适配器的 `lpSerializationMs` 或结构化适配器的 `symbolMapMs`、基于 `MockSolver` 的 `resultMappingMs`、`adapterSolveMs` 以及 `endToEndSolveMs`)分开直接测量,不通过相减来臆造“原生求解器时间”。
483
+ - **区分不同执行环境**:Node.js 进程内 WASM(`usesBrowserWorker: false`)与浏览器 Web Worker 执行(`usesBrowserWorker: true`,包含 Worker 生命周期与 `postMessage` 结构化克隆传输)作为不同执行环境分别报告,不可直接互换作基线。
484
+
492
485
  ---
493
486
 
494
487
  ## 📄 许可证
@@ -1,9 +1,27 @@
1
- import { c as e } from "./loader-CVt4CloI.js";
1
+ import { a as e } from "./loader-CtaDHef_.js";
2
2
  //#region src/modeler/solvers/highs/worker.ts
3
3
  self.onmessage = async (t) => {
4
- let { type: n, id: r, lpString: i, options: a } = t.data;
4
+ let { type: n, id: r, lpString: i, options: a, reportProgress: o } = t.data;
5
5
  if (n === "SOLVE") try {
6
- let { result: t, runTime: n } = await e(i, a);
6
+ let { result: t, runTime: n } = await e(i, {
7
+ ...a,
8
+ ...o ? {
9
+ onProgress: (e) => {
10
+ self.postMessage({
11
+ type: "SOLVE_PROGRESS",
12
+ id: r,
13
+ progress: e
14
+ });
15
+ },
16
+ onIncumbent: (e) => {
17
+ self.postMessage({
18
+ type: "SOLVE_INCUMBENT",
19
+ id: r,
20
+ incumbent: e
21
+ });
22
+ }
23
+ } : {}
24
+ });
7
25
  self.postMessage({
8
26
  type: "SOLVE_SUCCESS",
9
27
  id: r,