@ohtaman/washi 0.3.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.
- package/README.ja.md +111 -118
- package/README.md +109 -116
- package/README.zh-CN.md +112 -119
- package/dist/browser/highs-worker.js +21 -3
- package/dist/browser/loader-CtaDHef_.js +273 -0
- package/dist/browser/solvers/glpk.js +433 -286
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +1002 -981
- package/dist/browser/solvers/z3.min.js +2 -2
- package/dist/browser/washi.js +2178 -622
- package/dist/browser/washi.min.js +4 -4
- package/dist/lib/highs-worker.js +21 -3
- package/dist/lib/loader-DmNC_UNk.js +279 -0
- package/dist/lib/registry-BEfvpZW3.js +951 -0
- package/dist/lib/solvers/glpk.js +201 -90
- package/dist/lib/solvers/z3.js +399 -259
- package/dist/lib/washi.js +1674 -932
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/big-m.d.ts +75 -0
- package/dist/types/modeler/big-m.d.ts.map +1 -0
- package/dist/types/modeler/boolean-expr.d.ts +103 -14
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +36 -9
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +117 -27
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +10 -6
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/lp-writer.d.ts +27 -2
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +491 -140
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/objective.d.ts +59 -0
- package/dist/types/modeler/objective.d.ts.map +1 -0
- package/dist/types/modeler/parser.d.ts +39 -14
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/quad-expr.d.ts +28 -7
- package/dist/types/modeler/quad-expr.d.ts.map +1 -1
- package/dist/types/modeler/solution-validator.d.ts +93 -0
- package/dist/types/modeler/solution-validator.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/loader.d.ts +36 -11
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts +11 -4
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
- package/dist/types/modeler/solvers/mock.d.ts +14 -5
- package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +71 -11
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
- package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +60 -2
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/dist/types/modeler/validation.d.ts +56 -0
- package/dist/types/modeler/validation.d.ts.map +1 -0
- package/dist/types/modeler/variable.d.ts +53 -13
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +90 -0
- package/docs/examples/ecommerce-coupons.md +106 -0
- package/docs/examples/index.md +53 -0
- package/docs/examples/knapsack.md +60 -0
- package/docs/examples/portfolio-qp.md +83 -0
- package/docs/examples/production.md +51 -0
- package/docs/examples/shift-scheduling.md +175 -0
- package/docs/examples/smt-security.md +74 -0
- package/docs/examples/transportation.md +90 -0
- package/docs/examples/unsat-core.md +64 -0
- package/docs/getting-started/ai-agents.md +267 -0
- package/docs/getting-started/cross-origin.md +87 -0
- package/docs/getting-started/index.md +116 -0
- package/docs/getting-started/runtimes.md +227 -0
- package/docs/guide/constraints.md +177 -0
- package/docs/guide/expressions.md +204 -0
- package/docs/guide/objectives.md +170 -0
- package/docs/guide/unsat-core.md +147 -0
- package/docs/guide/variables.md +188 -0
- package/docs/ja/getting-started/ai-agents.md +266 -0
- package/docs/solvers/custom-solver.md +525 -0
- package/docs/solvers/glpk.md +66 -0
- package/docs/solvers/highs.md +93 -0
- package/docs/solvers/index.md +42 -0
- package/docs/solvers/z3.md +78 -0
- package/docs/zh/getting-started/ai-agents.md +266 -0
- package/llms-full.txt +3249 -0
- package/llms.txt +197 -0
- package/package.json +19 -6
- package/skills/washi/SKILL.md +197 -0
- package/dist/browser/loader-CVt4CloI.js +0 -58
- package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
- package/dist/lib/constraint-BRuB3ooC.js +0 -90
- package/dist/lib/loader-Dti0v9yC.js +0 -50
- 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>
|
|
@@ -24,24 +24,24 @@
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
**WaSHI** 是面向 TypeScript / JavaScript 的代数建模器,受到 Python **PuLP** 和 Julia **JuMP** 的启发。你可以在应用代码中定义变量、目标函数、算术约束和逻辑约束。WaSHI 负责构建模型、转换为求解器输入,并将结果映射回变量;优化与可满足性检查由 **HiGHS**、**Z3** 和 **GLPK** 完成。
|
|
27
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) 是面向 TypeScript / JavaScript 的代数建模器,受到 Python **PuLP** 和 Julia **JuMP** 的启发。你可以在应用代码中定义变量、目标函数、算术约束和逻辑约束。WaSHI 负责构建模型、转换为求解器输入,并将结果映射回变量;优化与可满足性检查由 **HiGHS**、**Z3** 和 **GLPK** 完成。
|
|
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.
|
|
42
|
-
m.
|
|
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.
|
|
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
|
|
53
|
-
2. **求解器适配器**:通过共同的建模 API 使用 HiGHS、Z3 或 GLPK,也可以[注册自己的适配器](https://github.com/ohtaman/washi/blob/main/docs/custom-solver
|
|
54
|
-
3. **本地执行与 WebAssembly
|
|
55
|
-
4. **命名约束与 SMT 结果**:显式使用 Z3
|
|
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.
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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)}%`);
|
|
@@ -207,20 +200,20 @@ import "@ohtaman/washi/solvers/z3";
|
|
|
207
200
|
|
|
208
201
|
const m = new Model({ name: "SmartSecurityRules" });
|
|
209
202
|
|
|
210
|
-
const isNight = m.
|
|
211
|
-
const motion = m.
|
|
212
|
-
const alarm = m.
|
|
213
|
-
const siren = m.
|
|
203
|
+
const isNight = m.bool("isNight");
|
|
204
|
+
const motion = m.bool("motion");
|
|
205
|
+
const alarm = m.bool("alarm");
|
|
206
|
+
const siren = m.bool("siren");
|
|
214
207
|
const lux = m.integer("lux", { lb: 0 });
|
|
215
208
|
|
|
216
209
|
// 规则定义
|
|
217
|
-
m.
|
|
218
|
-
m.
|
|
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.
|
|
222
|
-
m.
|
|
223
|
-
m.
|
|
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";
|
|
@@ -242,27 +235,27 @@ import "@ohtaman/washi/solvers/z3";
|
|
|
242
235
|
const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
|
|
243
236
|
const engineers = m.integer("engineers", { lb: 0, ub: 20 });
|
|
244
237
|
const budget = m.continuous("budget", { lb: 0, ub: 100000 });
|
|
245
|
-
const rushDelivery = m.
|
|
238
|
+
const rushDelivery = m.bool("rushDelivery");
|
|
246
239
|
|
|
247
240
|
// 规则 1: 每位工程师成本 $8,000
|
|
248
|
-
m.
|
|
241
|
+
m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
249
242
|
|
|
250
243
|
// 规则 2: 客户要求加急交付
|
|
251
|
-
m.
|
|
244
|
+
m.constraint(rushDelivery, "rush_required");
|
|
252
245
|
|
|
253
246
|
// 规则 3: 加急交付需至少配备 8 名工程师
|
|
254
|
-
m.
|
|
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.
|
|
250
|
+
m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
258
251
|
|
|
259
252
|
// 规则 5: 最低 1 名员工用工底线(无关规则)
|
|
260
|
-
m.
|
|
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.
|
|
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.
|
|
286
|
-
m.
|
|
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.
|
|
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.
|
|
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
|
|
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/`
|
|
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
|
-
const x = model.
|
|
381
|
-
model.maximize(x);
|
|
373
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
374
|
+
model.objective(maximize(x));
|
|
382
375
|
await model.solve();
|
|
383
376
|
console.log("GLPK 求解状态:", model.status);
|
|
384
377
|
</script>
|
|
@@ -390,53 +383,35 @@ await model.solve();
|
|
|
390
383
|
<script src="./washi/solvers/glpk.min.js"></script>
|
|
391
384
|
<script>
|
|
392
385
|
const model = new Washi.Model({ solver: "glpk" });
|
|
393
|
-
const x = model.
|
|
394
|
-
model.maximize(x);
|
|
386
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
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`)
|
|
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
|
-
|
|
396
|
+
完整独立的 ESM、Script 标签与 CodePen 示例请参阅 [`examples/distribution/`](examples/distribution/README.md)。
|
|
421
397
|
|
|
422
|
-
|
|
423
|
-
import { setHighsWasmUrl, OFFICIAL_HIGHS_CDN_WASM_URL } from "@ohtaman/washi";
|
|
424
|
-
setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
|
|
425
|
-
```
|
|
398
|
+
---
|
|
426
399
|
|
|
427
|
-
|
|
400
|
+
## 🛠️ 自定义求解器开发与架构扩展
|
|
428
401
|
|
|
429
|
-
|
|
402
|
+
WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并注册自定义求解器适配器(如云端 REST 求解服务、定制 WebAssembly 引擎或商业求解器):
|
|
430
403
|
|
|
431
|
-
|
|
404
|
+
- 完整开发指南、接口契约(`Solver`, `SolveModelData`, `CheckModelData`, `SolverResult`)与测试实践详见 [自定义求解器开发指南](docs/zh/solvers/custom-solver.md)。
|
|
432
405
|
|
|
433
406
|
---
|
|
434
407
|
|
|
435
|
-
##
|
|
408
|
+
## 📚 文档指南
|
|
436
409
|
|
|
437
|
-
|
|
410
|
+
完整文档可在 **[ohtaman.github.io/washi/zh/](https://ohtaman.github.io/washi/zh/)** 及仓库的 [`docs/zh/`](docs/zh/)([英文版 `docs/`](docs/))中查阅:
|
|
438
411
|
|
|
439
|
-
-
|
|
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,13 +421,13 @@ WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并
|
|
|
446
421
|
|
|
447
422
|
| 类别 | 示例 | 展示内容 | 求解器 |
|
|
448
423
|
| :--- | :--- | :--- | :--- |
|
|
449
|
-
| **基础模型** | [生产计划](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数变量、利润目标与资源约束 (`expr
|
|
450
|
-
| **基础模型** | [运输与分配网络](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 二维矩阵变量 (`
|
|
424
|
+
| **基础模型** | [生产计划](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数变量、利润目标与资源约束 (`expr`) | HiGHS |
|
|
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 |
|
|
453
|
-
| **实战应用** | [购物车优惠券最优组合](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | 满减门槛条件约束 (`
|
|
428
|
+
| **实战应用** | [购物车优惠券最优组合](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | 满减门槛条件约束 (`indicator`)、互斥规则与凑单推荐 | HiGHS |
|
|
454
429
|
| **实战应用** | [投资组合分配](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) | 算术与逻辑约束 (`
|
|
430
|
+
| **逻辑 / SMT** | [业务规则检查](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | 算术与逻辑约束 (`bool`, `implies`) 的可满足性检查 | Z3 |
|
|
456
431
|
| **逻辑 / SMT** | [冲突诊断](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | 命名约束与 Unsat Core (`unsatCore`) | Z3 |
|
|
457
432
|
|
|
458
433
|
在[在线浏览器演示](https://ohtaman.github.io/washi/demo/)中可以编辑并运行模型。浏览器 ESM、script 标签和 CodePen 的用法请参阅[分发示例](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)。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 {
|
|
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,
|
|
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,
|