@ohtaman/washi 0.2.0 → 0.3.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 (38) hide show
  1. package/README.ja.md +131 -115
  2. package/README.md +131 -115
  3. package/README.zh-CN.md +133 -117
  4. package/dist/browser/solvers/glpk.js +10 -2
  5. package/dist/browser/solvers/glpk.min.js +1 -1
  6. package/dist/browser/solvers/z3.js +34 -12
  7. package/dist/browser/solvers/z3.min.js +2 -2
  8. package/dist/browser/washi.js +368 -306
  9. package/dist/browser/washi.min.js +4 -4
  10. package/dist/lib/{constraint-XWdn0ID9.js → constraint-BRuB3ooC.js} +0 -3
  11. package/dist/lib/solvers/glpk.js +10 -2
  12. package/dist/lib/solvers/z3.js +35 -10
  13. package/dist/lib/washi.js +368 -321
  14. package/dist/types/index.d.ts +5 -7
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/modeler/constraint.d.ts +0 -3
  17. package/dist/types/modeler/constraint.d.ts.map +1 -1
  18. package/dist/types/modeler/expression.d.ts +16 -10
  19. package/dist/types/modeler/expression.d.ts.map +1 -1
  20. package/dist/types/modeler/index.d.ts +2 -4
  21. package/dist/types/modeler/index.d.ts.map +1 -1
  22. package/dist/types/modeler/model.d.ts +90 -11
  23. package/dist/types/modeler/model.d.ts.map +1 -1
  24. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  25. package/dist/types/modeler/solvers/highs/solver.d.ts +1 -1
  26. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  27. package/dist/types/modeler/solvers/types.d.ts +1 -1
  28. package/dist/types/modeler/solvers/z3/loader.d.ts +12 -0
  29. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  30. package/dist/types/modeler/types.d.ts +8 -3
  31. package/dist/types/modeler/types.d.ts.map +1 -1
  32. package/dist/types/modeler/variable.d.ts +1 -6
  33. package/dist/types/modeler/variable.d.ts.map +1 -1
  34. package/package.json +10 -2
  35. package/dist/types/modeler/mip-compat.d.ts +0 -43
  36. package/dist/types/modeler/mip-compat.d.ts.map +0 -1
  37. package/dist/types/modeler/pulp-compat.d.ts +0 -67
  38. package/dist/types/modeler/pulp-compat.d.ts.map +0 -1
package/README.zh-CN.md CHANGED
@@ -2,9 +2,9 @@
2
2
  <picture>
3
3
  <source media="(prefers-color-scheme: dark)" srcset="./assets/washi-header-dark.png">
4
4
  <source media="(prefers-color-scheme: light)" srcset="./assets/washi-header-light.png">
5
- <img alt="WaSHI - 面向 TypeScript 与 JavaScript 的数学规划与代数建模库" src="./assets/washi-header-light.png" width="420" />
5
+ <img alt="WaSHI - 面向 TypeScript 与 JavaScript 的数理优化代数建模器 (Modeler)" src="./assets/washi-header-light.png" width="420" />
6
6
  </picture>
7
- <p><strong>面向 TypeScript 与 JavaScript 的直观数学规划与代数建模库</strong></p>
7
+ <p><strong>面向 TypeScript 与 JavaScript 的代数建模器</strong></p>
8
8
  <p>
9
9
  <em>线性规划 (LP) • 混合整数规划 (MIP) • 凸二次规划 (QP) • SMT逻辑约束与冲突诊断</em>
10
10
  </p>
@@ -14,56 +14,58 @@
14
14
  <a href="./README.zh-CN.md">简体中文</a>
15
15
  </p>
16
16
  <p>
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
+ <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>
17
19
  <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
18
20
  <a href="https://ohtaman.github.io/washi/demo/"><img src="https://img.shields.io/badge/demo-playground-brightgreen" alt="live demo" /></a>
19
- <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>
20
- <a href="https://bundlephobia.com/package/@ohtaman/washi"><img src="https://img.shields.io/badge/bundle%20size-7.2%20kB%20(gzip)-blue" alt="bundle size" /></a>
21
21
  <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
22
- <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="TypeScript types" />
23
- <img src="https://img.shields.io/badge/tests-120%2B%20passing-brightgreen" alt="tests" />
24
22
  </p>
25
23
  </div>
26
24
 
27
25
  ---
28
26
 
29
- **WaSHI** 是一个高层代数建模库,将 Python **PuLP** 与 Julia **JuMP** 的优雅表现力引入现代 TypeScript / JavaScript 生态系统。你可以直接使用符合数学直觉的代数公式编写数学模型,并在 Node.js、现代浏览器、Bun、Deno 及 Next.js 中秒级求解——完全依托 WebAssembly 求解器(**HiGHS**、**Z3**、**GLPK**),无需任何本地 C++ 编译环境。
27
+ **WaSHI** 是面向 TypeScript / JavaScript 的代数建模器,受到 Python **PuLP** 和 Julia **JuMP** 的启发。你可以在应用代码中定义变量、目标函数、算术约束和逻辑约束。WaSHI 负责构建模型、转换为求解器输入,并将结果映射回变量;优化与可满足性检查由 **HiGHS**、**Z3** 和 **GLPK** 完成。
28
+
29
+ HiGHS 是默认求解器;Z3 和 GLPK 是可选插件。软件包提供 Node.js 和浏览器分发版本,并包含 WebAssembly 资源。部署要求请参阅[浏览器使用说明](#-浏览器与-web-worker-使用指南)。
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)
30
32
 
31
33
  ```ts
32
- import { Model, expr, c } from "@ohtaman/washi";
34
+ import { Model, expr } from "@ohtaman/washi";
33
35
 
34
- const m = new Model({ sense: "Maximize" });
35
- const x = m.addVar({ type: "Integer", lb: 0 });
36
- const y = m.addVar({ type: "Integer", lb: 0 });
36
+ const m = new Model("木工产品利润最大化");
37
+ const x = m.integer("桌子", { lb: 0 });
38
+ const y = m.integer("椅子", { lb: 0 });
37
39
 
38
40
  m.maximize(expr`30 * ${x} + 50 * ${y}`);
39
- m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
40
- m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
41
+ m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
42
+ m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
41
43
 
42
- await m.solve();
44
+ await m.optimize();
43
45
  console.log(`生产计划: x=${x.value}, y=${y.value} (最大利润: $${m.objectiveValue})`);
44
46
  ```
45
47
 
46
48
  ---
47
49
 
48
- ## 🎯 为什么选择 WaSHI?
50
+ ## 🎯 WaSHI 提供的功能
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 调查冲突约束。返回的是一个冲突约束子集,不一定是最小子集。
49
56
 
50
- 1. 🌐 **100% 客户端运行与零服务器成本**:直接在用户的浏览器、边缘节点(Edge Worker)或 Electron 桌面端运行工业级优化。后端服务器运维成本为零,支持离线运行,且机密业务数据(如排班表、成本数据)永不离开客户端。
51
- 2. 📦 **零依赖・单一包分发**:告别 Python 环境配置与繁重的跨平台 C/C++ 原生求解器编译绑定。只需 `npm install @ohtaman/washi`,包括 WebAssembly 求解器在内的所有组件即可开箱即用。
52
- 3. ✍️ **极简现代代数建模 DX(兼容 PuLP / python-mip)**:无需编写晦涩低级的矩阵索引。使用直观的模板字面量(`c`${x} + 2 * ${y} <= 40``)或熟悉的 Python 风格抽象(`LpProblem`, `lpSum`),像在草稿纸上写公式一样自然。
53
- 4. 🔄 **统一多求解器接口(HiGHS / Z3 / GLPK)**:模型代码一次编写,即可通过统一 API 自由切换:擅长工业级 MIP/QP 的 **HiGHS**、擅长逻辑求解与冲突分析的 **Z3**,以及经典学术求解器 **GLPK**。
54
- 5. 🔍 **不可行模型(Infeasible)冲突根因诊断**:当业务规则出现矛盾导致无解时,可通过 Z3 引擎快速定位并提取冲突约束子集(Unsat Core)。
55
- 6. ⚡ **WebAssembly 原生工业级大规模性能**:搭载 $O(N)$ 线性时间高速表达式构建器,仅需约 100ms 即可构建 20,000+ 变量的大规模 MIP,并在浏览器 WebAssembly 中由 HiGHS 在约 15 秒内高效求解。
57
+ 模型构建与转换由 WaSHI 负责。支持的问题类型、数值行为和求解时间取决于所选求解器、配置及具体问题。建模开销与求解器执行时间应分别测量;某个模型上的结果并不构成通用性能保证。
56
58
 
57
59
  ---
58
60
 
59
- ## ⚡ 求解器矩阵
61
+ ## ⚙️ 求解器适配器与支持范围
60
62
 
61
- | 求解器 | 核心许可证 | 支持的问题类型 | 最佳应用场景 | 在 WaSHI 中的提供方式 |
63
+ | 适配器 | 引擎许可证 | 支持的问题类型 | 引擎作用 | 集成方式 |
62
64
  | :--- | :--- | :--- | :--- | :--- |
63
- | **HiGHS** | **MIT** | **LP, MIP, 凸QP** | 工业级生产排程、运筹规划、金融组合优化 | **内置默认**(零配置) |
64
- | **Z3** | **MIT** | **SMT, SAT, 命题逻辑, MIP** | 业务规则验证、约束冲突诊断 (`unsatCore`)、规则引擎 | 可选插件 (`@ohtaman/washi/solvers/z3`) |
65
- | **GLPK** | **GPL-3.0** | **LP, MIP** | 学术基准测试、存量 GLPK 模型迁移 | 可选插件 (`@ohtaman/washi/solvers/glpk`) |
66
- | **Mock** | **MIT** | *任意* | CI/CD 环境下无需加载 WASM 的极速单元测试 | 内置 |
65
+ | **HiGHS** | **MIT** | **LP, MIP, 凸QP** | 数值优化 | 默认内置 |
66
+ | **Z3** | **MIT** | **SMT, SAT, 逻辑, MIP** | 可满足性检查、Unsat Core、优化 | 可选插件 (`@ohtaman/washi/solvers/z3`) |
67
+ | **GLPK** | **GPL-3.0** | **LP, MIP** | 线性与混合整数优化 | 可选插件 (`@ohtaman/washi/solvers/glpk`) |
68
+ | **Mock** | **MIT** | — | 为集成测试提供模拟响应 | 内置;不实际求解模型 |
67
69
 
68
70
  ---
69
71
 
@@ -99,49 +101,49 @@ npm install glpk.js # 用于 GLPK 求解器 (GPL-3.0)
99
101
  在有限资源约束下实现利润最大化:
100
102
 
101
103
  ```ts
102
- import { Model, c, expr } from "@ohtaman/washi";
104
+ import { Model, expr } from "@ohtaman/washi";
103
105
 
104
- const m = new Model({ name: "FactoryPlanning", sense: "Maximize" });
106
+ const m = new Model("FactoryPlanning");
105
107
 
106
108
  // 决策变量(非负整数)
107
- const chairs = m.addVar({ name: "chairs", type: "Integer", lb: 0 });
108
- const tables = m.addVar({ name: "tables", type: "Integer", lb: 0 });
109
+ const chairs = m.integer("chairs", { lb: 0 });
110
+ const tables = m.integer("tables", { lb: 0 });
109
111
 
110
112
  // 借助标签模板字面量自然表达目标函数与约束
111
113
  m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
112
- m.addConstraint(c`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
113
- m.addConstraint(c`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
114
+ m.constrain(expr`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
115
+ m.constrain(expr`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
114
116
 
115
117
  // 使用默认的 HiGHS WASM 引擎求解
116
- const status = await m.solve();
118
+ const status = await m.optimize();
117
119
 
118
120
  console.log(`求解状态: ${status}`); // "Optimal"
119
121
  console.log(`最大利润: $${m.objectiveValue}`); // 1100
120
122
  console.log(`生产量: 椅子=${chairs.value}, 桌子=${tables.value}`); // 椅子 20, 桌子 10
121
123
  ```
122
124
 
123
- #### 🗂 多维变量网格 (`addVars`)
125
+ #### 🗂 多维变量网格
124
126
 
125
- 一行代码声明变量字典或多维变量矩阵(类似于 PuLP 的 `LpVariable.dicts`):
127
+ 一行代码声明变量字典或多维变量矩阵:
126
128
 
127
129
  ```ts
128
130
  // 1D 字典形式的布尔变量
129
- const openCity = m.addVars(["Tokyo", "Osaka", "Nagoya"], { type: "Binary" });
131
+ const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
130
132
 
131
133
  // 2D 运输量矩阵: [工厂] x [门店]
132
- const flow = m.addVars(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
134
+ const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
133
135
 
134
136
  // 2D 网格坐标 (例如 8x8 棋盘)
135
- const board = m.addVars(8, 8, { type: "Binary", prefix: "queen" });
137
+ const board = m.binary(8, 8, { prefix: "queen" });
136
138
  ```
137
139
 
138
- #### 🔀 Big-M 指示约束 (`addIndicator`)
140
+ #### 🔀 Big-M 条件指示约束 (`m.indicator`)
139
141
 
140
- 直接表达条件激活与固定成本逻辑,无需手动进行繁琐的 Big-M 代数推导:
142
+ 轻松建模条件触发规则与固定成本启动,无需繁琐的手工 Big-M 代数推导:
141
143
 
142
144
  ```ts
143
145
  // "若工厂关闭 (openCity['Tokyo'] == 0),则产量 flow 必须为 0"
144
- m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
146
+ m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
145
147
  activeValue: 0,
146
148
  M: 1000,
147
149
  });
@@ -154,20 +156,21 @@ m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
154
156
  在满足预期收益与预算约束的前提下,基于协方差矩阵最小化投资组合风险:
155
157
 
156
158
  ```ts
157
- import { Model, QuadExpr, sum } from "@ohtaman/washi";
159
+ import { Model, QuadExpr, sum, dot, expr } from "@ohtaman/washi";
158
160
 
159
- const m = new Model({ name: "MarkowitzPortfolio", sense: "Minimize" });
161
+ const m = new Model("MarkowitzPortfolio");
160
162
  const assets = ["Tech", "Bonds", "RealEstate"];
161
163
  const returns = [0.14, 0.04, 0.08];
162
164
 
163
165
  // 决策变量: 各资产投资权重 (0.0 ~ 1.0)
164
- const w = assets.map((a) => m.addVar({ name: `w_${a}`, lb: 0, ub: 1 }));
166
+ const w = m.continuous(assets, { lb: 0, ub: 1 });
165
167
 
166
168
  // 1. 预算约束: 资金分配总额 100%
167
- m.addConstraint(sum(w).eq(1.0), "budget");
169
+ m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
168
170
 
169
171
  // 2. 目标回报率: 组合最低回报率 >= 8.5%
170
- m.addConstraint(sum(w.map((v, i) => v.mul(returns[i]))).ge(0.085), "min_return");
172
+ const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
173
+ m.constrain(expr`${dot(retMap, w)} >= 0.085`, "min_return");
171
174
 
172
175
  // 3. 资产协方差矩阵 (风险与相关性)
173
176
  const cov = [
@@ -180,15 +183,15 @@ const cov = [
180
183
  let risk = new QuadExpr();
181
184
  for (let i = 0; i < assets.length; i++) {
182
185
  for (let j = 0; j < assets.length; j++) {
183
- risk = risk.add(w[i].mul(w[j]).mul(cov[i][j]));
186
+ risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
184
187
  }
185
188
  }
186
189
  m.minimize(risk);
187
190
 
188
- await m.solve();
191
+ await m.optimize();
189
192
  console.log(`最小方差: ${m.objectiveValue?.toFixed(6)}`);
190
- assets.forEach((name, i) => {
191
- console.log(` ${name}: ${((w[i].value || 0) * 100).toFixed(2)}%`);
193
+ assets.forEach((name) => {
194
+ console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
192
195
  });
193
196
  ```
194
197
 
@@ -199,7 +202,7 @@ assets.forEach((name, i) => {
199
202
  原生支持布尔命题变量(`BoolVar`)、逻辑连接词(`and`, `or`, `implies`, `not`, `xor`)与数值算术约束的混合表达:
200
203
 
201
204
  ```ts
202
- import { Model, not } from "@ohtaman/washi";
205
+ import { Model, not, expr } from "@ohtaman/washi";
203
206
  import "@ohtaman/washi/solvers/z3";
204
207
 
205
208
  const m = new Model({ name: "SmartSecurityRules" });
@@ -208,16 +211,16 @@ const isNight = m.addBoolVar("isNight");
208
211
  const motion = m.addBoolVar("motion");
209
212
  const alarm = m.addBoolVar("alarm");
210
213
  const siren = m.addBoolVar("siren");
211
- const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
214
+ const lux = m.integer("lux", { lb: 0 });
212
215
 
213
216
  // 规则定义
214
- m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
215
- m.addConstraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
217
+ m.constrain(lux.le(50).implies(isNight), "dark_implies_night");
218
+ m.constrain(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
216
219
 
217
220
  // 传感器当前输入数据
218
- m.addConstraint(lux.le(10), "sensor_dark");
219
- m.addConstraint(motion, "sensor_motion");
220
- m.addConstraint(alarm, "alarm_on");
221
+ m.constrain(expr`${lux} <= 10`, "sensor_dark");
222
+ m.constrain(motion, "sensor_motion");
223
+ m.constrain(alarm, "alarm_on");
221
224
 
222
225
  // 无目标函数直接判定可满足性 (SAT)
223
226
  const res = await m.check({ solver: "z3" });
@@ -233,28 +236,28 @@ console.log(`警报响起? ${m.values.get("siren")}`); // true
233
236
  当规则或约束之间存在矛盾时,Z3 能够提取引发冲突的**矛盾约束子集**(Unsat Core,不保证最小性;需使用 Z3):
234
237
 
235
238
  ```ts
236
- import { Model } from "@ohtaman/washi";
239
+ import { Model, expr } from "@ohtaman/washi";
237
240
  import "@ohtaman/washi/solvers/z3";
238
241
 
239
242
  const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
240
- const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
241
- const budget = m.addVar({ name: "budget", type: "Continuous", lb: 0, ub: 100000 });
243
+ const engineers = m.integer("engineers", { lb: 0, ub: 20 });
244
+ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
242
245
  const rushDelivery = m.addBoolVar("rushDelivery");
243
246
 
244
247
  // 规则 1: 每位工程师成本 $8,000
245
- m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
248
+ m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
246
249
 
247
250
  // 规则 2: 客户要求加急交付
248
- m.addConstraint(rushDelivery, "rush_required");
251
+ m.constrain(rushDelivery, "rush_required");
249
252
 
250
253
  // 规则 3: 加急交付需至少配备 8 名工程师
251
- m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
254
+ m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
252
255
 
253
256
  // 规则 4: 董事会严格预算上限 $50,000 (冲突点: 8人 * $8,000 = $64,000 > $50,000!)
254
- m.addConstraint(budget.le(50000), "strict_board_budget_cap");
257
+ m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
255
258
 
256
259
  // 规则 5: 最低 1 名员工用工底线(无关规则)
257
- m.addConstraint(engineers.ge(1), "minimum_one_employee");
260
+ m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
258
261
 
259
262
  const res = await m.check({ solver: "z3" });
260
263
 
@@ -271,53 +274,49 @@ console.log("冲突约束子集:", res.unsatCore);
271
274
  使用 GNU Linear Programming Kit (GLPK) 求解器插件:
272
275
 
273
276
  ```ts
274
- import { Model, expr, c } from "@ohtaman/washi";
277
+ import { Model, expr } from "@ohtaman/washi";
275
278
  import "@ohtaman/washi/solvers/glpk";
276
279
 
277
- const m = new Model({ sense: "Maximize", solver: "glpk" });
278
- const x = m.addVar({ type: "Integer", lb: 0 });
279
- const y = m.addVar({ type: "Integer", lb: 0 });
280
+ const m = new Model({ solver: "glpk" });
281
+ const x = m.integer("x", { lb: 0 });
282
+ const y = m.integer("y", { lb: 0 });
280
283
 
281
284
  m.maximize(expr`30 * ${x} + 50 * ${y}`);
282
- m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
283
- m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
285
+ m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
286
+ m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
284
287
 
285
- const status = await m.solve();
288
+ const status = await m.optimize();
286
289
  console.log(`求解状态: ${status}`); // "Optimal"
287
290
  console.log(`目标函数值: ${m.objectiveValue}`); // 1100
288
291
  ```
289
292
 
290
293
  ---
291
294
 
292
- ### 6. Python PuLP 兼容层
295
+ ### 6. 向量运算与高效点积 (`dot`)
293
296
 
294
- 若你已有 Python PuLP 代码资产,可零摩擦迁移:
297
+ 无需嵌套循环,一行代码完成一维和二维变量的线性组合:
295
298
 
296
299
  ```ts
297
- import {
298
- LpProblem,
299
- LpVariable,
300
- LpMaximize,
301
- LpInteger,
302
- HiGHS,
303
- value,
304
- } from "@ohtaman/washi";
305
-
306
- const prob = new LpProblem("PuLPDemo", LpMaximize);
307
-
308
- const x = new LpVariable("x", 0, undefined, LpInteger);
309
- const y = new LpVariable("y", 0, undefined, LpInteger);
310
-
311
- // 等同于 PuLP 中的 += 语法
312
- prob.add(x.mul(30).add(y.mul(50)));
313
- prob.add(x.add(y.mul(2)).le(40));
314
- prob.add(x.mul(2).add(y).le(50));
315
-
316
- await prob.solve(new HiGHS());
317
-
318
- console.log(`状态: ${prob.status}`);
319
- console.log(`目标函数值: ${value(prob.objective)}`);
320
- console.log(`x = ${value(x)}, y = ${value(y)}`);
300
+ import { Model, dot } from "@ohtaman/washi";
301
+
302
+ const m = new Model();
303
+ const factories = ["Tokyo", "Osaka"];
304
+ const stores = ["StoreA", "StoreB"];
305
+
306
+ const costs = {
307
+ Tokyo: { StoreA: 4, StoreB: 7 },
308
+ Osaka: { StoreA: 8, StoreB: 3 },
309
+ };
310
+
311
+ const ship = m.continuous(factories, stores);
312
+
313
+ // 一行代码最小化总运输成本:
314
+ m.minimize(dot(costs, ship));
315
+ await m.optimize();
316
+
317
+ // 获取所有变量的最优取值结构
318
+ const solution = m.getValues(ship);
319
+ console.log(solution);
321
320
  ```
322
321
 
323
322
  ---
@@ -398,13 +397,17 @@ await model.solve();
398
397
  ```
399
398
 
400
399
  > [!NOTE]
401
- > **浏览器环境中使用 Z3 的配置要求**:
402
- > 由于 Z3 的 WebAssembly 引擎依托多线程 (`SharedArrayBuffer`),承载页面的 Web 服务器必须在文档响应头中附带以下 Cross-Origin-Isolation 标头:
400
+ > **浏览器环境中使用 Z3 的配置要求与跨源隔离**:
401
+ > 由于 Z3 的 WebAssembly 引擎依托多线程 (`SharedArrayBuffer`),现代浏览器强制要求 Cross-Origin Isolation。文档响应头必须包含:
403
402
  > ```http
404
403
  > Cross-Origin-Opener-Policy: same-origin
405
404
  > Cross-Origin-Embedder-Policy: require-corp
406
405
  > ```
407
- > 此外,任何跨源静态资源(如来自 CDN)都必须返回 `Cross-Origin-Resource-Policy: cross-origin` 响应头。
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 会清晰展示诊断原因并中止执行,防止无限重载或无响应挂起。
408
411
 
409
412
  完整独立的 ESM、Script 标签与 CodePen 示例请参阅 `examples/distribution/`。
410
413
 
@@ -437,33 +440,46 @@ WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并
437
440
 
438
441
  ---
439
442
 
440
- ## 💻 本地脚本与交互式 Web 演示
443
+ ## 💻 示例与浏览器演示
441
444
 
442
- 在 `examples/node/` 中提供了即开即用的独立脚本:
445
+ [示例目录](https://github.com/ohtaman/washi/blob/main/examples/README.md)与[在线案例与 API 索引](https://ohtaman.github.io/washi/zh/examples/)按问题类别和 WaSHI API 功能汇总了源码链接及本地运行方法。
443
446
 
444
- ```bash
445
- npx tsx examples/node/01-production-planning.ts
446
- npx tsx examples/node/02-knapsack-problem.ts
447
- npx tsx examples/node/03-portfolio-qp.ts
448
- npx tsx examples/node/04-smt-security-rules.ts
449
- npx tsx examples/node/05-unsat-core-diagnosis.ts
450
- npx tsx examples/node/06-pulp-compatibility.ts
451
- npx tsx examples/node/08-glpk-solver-plugin.ts
452
- ```
447
+ | 类别 | 示例 | 展示内容 | 求解器 |
448
+ | :--- | :--- | :--- | :--- |
449
+ | **基础模型** | [生产计划](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数变量、利润目标与资源约束 (`expr`, `c`) | HiGHS |
450
+ | **基础模型** | [运输与分配网络](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 二维矩阵变量 (`addVars(rows, cols)`) 与供需平衡 | HiGHS |
451
+ | **基础模型** | [营养问题与灵敏度分析](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | 影子价格 (`constr.dual`)、松弛量 (`constr.slack`) 与缩减成本 (`var.reducedCost`) | HiGHS |
452
+ | **实战应用** | [员工排班与冲突诊断](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | 周排班优化与周六休假冲突诊断 | HiGHS + Z3 |
453
+ | **实战应用** | [购物车优惠券最优组合](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | 满减门槛条件约束 (`addIndicator`)、互斥规则与凑单推荐 | HiGHS |
454
+ | **实战应用** | [投资组合分配](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) | 凸二次目标函数 (`QuadExpr`) 与分配约束 | HiGHS |
455
+ | **逻辑 / SMT** | [业务规则检查](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | 算术与逻辑约束 (`addBoolVar`, `implies`) 的可满足性检查 | Z3 |
456
+ | **逻辑 / SMT** | [冲突诊断](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | 命名约束与 Unsat Core (`unsatCore`) | Z3 |
453
457
 
454
- 启动浏览器端交互式可视化演示 (`examples/web-demo`):
458
+ 在[在线浏览器演示](https://ohtaman.github.io/washi/demo/)中可以编辑并运行模型。浏览器 ESM、script 标签和 CodePen 的用法请参阅[分发示例](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)。Z3 需要跨源隔离;具体要求见上方浏览器说明。
459
+
460
+ 本地运行前,请克隆仓库并安装开发依赖:
455
461
 
456
462
  ```bash
463
+ git clone https://github.com/ohtaman/washi.git
464
+ cd washi
465
+ npm ci
466
+ npx tsx examples/node/01-production-planning.ts
467
+ npx tsx examples/node/09-transportation-problem.ts
468
+ npx tsx examples/node/10-diet-sensitivity.ts
469
+ npx tsx examples/node/11-shift-scheduling.ts
470
+ npx tsx examples/node/12-ecommerce-coupon-optimizer.ts
471
+
472
+ # 本地浏览器演示: http://localhost:5173/demo/
457
473
  npm run dev
458
- # 文档主页: http://localhost:5173/
459
- # 交互式 Demo: http://localhost:5173/demo/
460
474
  ```
461
475
 
476
+ 示例导入仓库的 `src/` 模块。使用已发布软件包的应用应改为导入 `@ohtaman/washi` 及所需的求解器插件。
477
+
462
478
  ---
463
479
 
464
480
  ## 🧪 自动化测试
465
481
 
466
- 项目全面贯彻测试驱动开发 (TDD),涵盖 120+ 单元测试、集成测试与大规模基准测试:
482
+ 运行仓库的单元测试与集成测试,并构建分发版本:
467
483
 
468
484
  ```bash
469
485
  # 运行全部单元与集成测试
@@ -1013,7 +1013,7 @@ var Pe = class e {
1013
1013
  let h = {
1014
1014
  name: e.name || "GLPK_Model",
1015
1015
  objective: {
1016
- direction: e.sense === "Maximize" ? i.GLP_MAX : i.GLP_MIN,
1016
+ direction: e.sense && e.sense.toLowerCase() === "maximize" ? i.GLP_MAX : i.GLP_MIN,
1017
1017
  name: "obj",
1018
1018
  vars: l
1019
1019
  },
@@ -1026,7 +1026,15 @@ var Pe = class e {
1026
1026
  };
1027
1027
  typeof r.timeLimit == "number" && (g.tmlim = r.timeLimit), typeof r.mipGap == "number" && (g.mipgap = r.mipGap);
1028
1028
  let _ = performance.now(), v = await i.solve(h, g), y = (performance.now() - _) / 1e3, b = v?.result?.status, x;
1029
- x = b === i.GLP_OPT ? "Optimal" : b === i.GLP_FEAS ? "Feasible" : b === i.GLP_INFEAS || b === i.GLP_NOFEAS ? "Infeasible" : b === i.GLP_UNBND ? "Unbounded" : "Optimal";
1029
+ if (x = b === i.GLP_OPT ? "Optimal" : b === i.GLP_FEAS ? "Feasible" : b === i.GLP_INFEAS || b === i.GLP_NOFEAS ? "Infeasible" : b === i.GLP_UNBND ? "Unbounded" : "Optimal", x === "Infeasible" || x === "Unbounded") return {
1030
+ status: x,
1031
+ objectiveValue: null,
1032
+ values: {},
1033
+ duals: {},
1034
+ slacks: {},
1035
+ runTime: y,
1036
+ raw: v
1037
+ };
1030
1038
  let S = v?.result?.z, C = typeof S == "number" ? S + e.objective.constant : null, w = {}, T = v?.result?.vars || {};
1031
1039
  for (let [e, t] of Object.entries(T)) {
1032
1040
  let n = a.get(e), r = n ? n.name : e;