@ohtaman/washi 0.2.0 → 0.4.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 +138 -122
- package/README.md +138 -122
- package/README.zh-CN.md +140 -124
- package/dist/browser/solvers/glpk.js +10 -2
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +34 -12
- package/dist/browser/solvers/z3.min.js +2 -2
- package/dist/browser/washi.js +562 -397
- package/dist/browser/washi.min.js +4 -4
- package/dist/lib/{constraint-XWdn0ID9.js → constraint-BRuB3ooC.js} +0 -3
- package/dist/lib/solvers/glpk.js +10 -2
- package/dist/lib/solvers/z3.js +35 -10
- package/dist/lib/washi.js +562 -412
- package/dist/types/index.d.ts +5 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +0 -3
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +23 -12
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +2 -4
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +123 -40
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/parser.d.ts +9 -8
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/quad-expr.d.ts +3 -1
- package/dist/types/modeler/quad-expr.d.ts.map +1 -1
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +1 -1
- package/dist/types/modeler/solvers/z3/loader.d.ts +12 -0
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +8 -3
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/dist/types/modeler/variable.d.ts +7 -6
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/package.json +11 -3
- package/dist/types/modeler/mip-compat.d.ts +0 -43
- package/dist/types/modeler/mip-compat.d.ts.map +0 -1
- package/dist/types/modeler/pulp-compat.d.ts +0 -67
- 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
|
|
5
|
+
<img alt="WaSHI - 面向 TypeScript 与 JavaScript 的数理优化代数建模器 (Modeler)" src="./assets/washi-header-light.png" width="420" />
|
|
6
6
|
</picture>
|
|
7
|
-
<p><strong>面向 TypeScript 与 JavaScript
|
|
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**
|
|
27
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) 是面向 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
|
|
34
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
33
35
|
|
|
34
|
-
const m = new Model(
|
|
35
|
-
const x = m.
|
|
36
|
-
const y = m.
|
|
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.
|
|
40
|
-
m.
|
|
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.
|
|
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
|
-
## 🎯
|
|
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
|
-
|
|
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
|
-
|
|
|
63
|
+
| 适配器 | 引擎许可证 | 支持的问题类型 | 引擎作用 | 集成方式 |
|
|
62
64
|
| :--- | :--- | :--- | :--- | :--- |
|
|
63
|
-
| **HiGHS** | **MIT** | **LP, MIP, 凸QP** |
|
|
64
|
-
| **Z3** | **MIT** | **SMT, SAT,
|
|
65
|
-
| **GLPK** | **GPL-3.0** | **LP, MIP** |
|
|
66
|
-
| **Mock** | **MIT** |
|
|
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,
|
|
104
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
103
105
|
|
|
104
|
-
const m = new Model(
|
|
106
|
+
const m = new Model("FactoryPlanning");
|
|
105
107
|
|
|
106
108
|
// 决策变量(非负整数)
|
|
107
|
-
const chairs = m.
|
|
108
|
-
const tables = m.
|
|
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.
|
|
113
|
-
m.
|
|
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.
|
|
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
|
-
#### 🗂 多维变量网格
|
|
125
|
+
#### 🗂 多维变量网格
|
|
124
126
|
|
|
125
|
-
|
|
127
|
+
一行代码声明变量字典或多维变量矩阵:
|
|
126
128
|
|
|
127
129
|
```ts
|
|
128
130
|
// 1D 字典形式的布尔变量
|
|
129
|
-
const openCity = m.
|
|
131
|
+
const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
|
|
130
132
|
|
|
131
133
|
// 2D 运输量矩阵: [工厂] x [门店]
|
|
132
|
-
const flow = m.
|
|
134
|
+
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
|
|
133
135
|
|
|
134
136
|
// 2D 网格坐标 (例如 8x8 棋盘)
|
|
135
|
-
const board = m.
|
|
137
|
+
const board = m.binary(8, 8, { prefix: "queen" });
|
|
136
138
|
```
|
|
137
139
|
|
|
138
|
-
#### 🔀 Big-M
|
|
140
|
+
#### 🔀 Big-M 条件指示约束 (`m.indicator`)
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
轻松建模条件触发规则与固定成本启动,无需繁琐的手工 Big-M 代数推导:
|
|
141
143
|
|
|
142
144
|
```ts
|
|
143
145
|
// "若工厂关闭 (openCity['Tokyo'] == 0),则产量 flow 必须为 0"
|
|
144
|
-
m.
|
|
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(
|
|
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 =
|
|
166
|
+
const w = m.continuous(assets, { lb: 0, ub: 1 });
|
|
165
167
|
|
|
166
168
|
// 1. 预算约束: 资金分配总额 100%
|
|
167
|
-
m.
|
|
169
|
+
m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
|
|
168
170
|
|
|
169
171
|
// 2. 目标回报率: 组合最低回报率 >= 8.5%
|
|
170
|
-
|
|
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.
|
|
191
|
+
await m.optimize();
|
|
189
192
|
console.log(`最小方差: ${m.objectiveValue?.toFixed(6)}`);
|
|
190
|
-
assets.forEach((name
|
|
191
|
-
console.log(` ${name}: ${((w[
|
|
193
|
+
assets.forEach((name) => {
|
|
194
|
+
console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
|
|
192
195
|
});
|
|
193
196
|
```
|
|
194
197
|
|
|
@@ -199,25 +202,25 @@ 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" });
|
|
206
209
|
|
|
207
|
-
const isNight = m.
|
|
208
|
-
const motion = m.
|
|
209
|
-
const alarm = m.
|
|
210
|
-
const siren = m.
|
|
211
|
-
const lux = m.
|
|
210
|
+
const isNight = m.bool("isNight");
|
|
211
|
+
const motion = m.bool("motion");
|
|
212
|
+
const alarm = m.bool("alarm");
|
|
213
|
+
const siren = m.bool("siren");
|
|
214
|
+
const lux = m.integer("lux", { lb: 0 });
|
|
212
215
|
|
|
213
216
|
// 规则定义
|
|
214
|
-
m.
|
|
215
|
-
m.
|
|
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.
|
|
219
|
-
m.
|
|
220
|
-
m.
|
|
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.
|
|
241
|
-
const budget = m.
|
|
242
|
-
const rushDelivery = m.
|
|
243
|
+
const engineers = m.integer("engineers", { lb: 0, ub: 20 });
|
|
244
|
+
const budget = m.continuous("budget", { lb: 0, ub: 100000 });
|
|
245
|
+
const rushDelivery = m.bool("rushDelivery");
|
|
243
246
|
|
|
244
247
|
// 规则 1: 每位工程师成本 $8,000
|
|
245
|
-
m.
|
|
248
|
+
m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
246
249
|
|
|
247
250
|
// 规则 2: 客户要求加急交付
|
|
248
|
-
m.
|
|
251
|
+
m.constrain(rushDelivery, "rush_required");
|
|
249
252
|
|
|
250
253
|
// 规则 3: 加急交付需至少配备 8 名工程师
|
|
251
|
-
m.
|
|
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.
|
|
257
|
+
m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
255
258
|
|
|
256
259
|
// 规则 5: 最低 1 名员工用工底线(无关规则)
|
|
257
|
-
m.
|
|
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
|
|
277
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
275
278
|
import "@ohtaman/washi/solvers/glpk";
|
|
276
279
|
|
|
277
|
-
const m = new Model({
|
|
278
|
-
const x = m.
|
|
279
|
-
const y = m.
|
|
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.
|
|
283
|
-
m.
|
|
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.
|
|
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.
|
|
295
|
+
### 6. 向量运算与高效点积 (`dot`)
|
|
293
296
|
|
|
294
|
-
|
|
297
|
+
无需嵌套循环,一行代码完成一维和二维变量的线性组合:
|
|
295
298
|
|
|
296
299
|
```ts
|
|
297
|
-
import {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
---
|
|
@@ -378,7 +377,7 @@ await model.solve();
|
|
|
378
377
|
import "./washi/solvers/glpk.js";
|
|
379
378
|
|
|
380
379
|
const model = new Model({ solver: "glpk" });
|
|
381
|
-
const x = model.
|
|
380
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
382
381
|
model.maximize(x);
|
|
383
382
|
await model.solve();
|
|
384
383
|
console.log("GLPK 求解状态:", model.status);
|
|
@@ -391,20 +390,24 @@ await model.solve();
|
|
|
391
390
|
<script src="./washi/solvers/glpk.min.js"></script>
|
|
392
391
|
<script>
|
|
393
392
|
const model = new Washi.Model({ solver: "glpk" });
|
|
394
|
-
const x = model.
|
|
393
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
395
394
|
model.maximize(x);
|
|
396
395
|
model.solve().then(status => console.log("GLPK 求解状态:", status));
|
|
397
396
|
</script>
|
|
398
397
|
```
|
|
399
398
|
|
|
400
399
|
> [!NOTE]
|
|
401
|
-
> **浏览器环境中使用 Z3
|
|
402
|
-
> 由于 Z3 的 WebAssembly 引擎依托多线程 (`SharedArrayBuffer`)
|
|
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
|
-
>
|
|
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
|
-
## 💻
|
|
443
|
+
## 💻 示例与浏览器演示
|
|
441
444
|
|
|
442
|
-
|
|
445
|
+
[示例目录](https://github.com/ohtaman/washi/blob/main/examples/README.md)与[在线案例与 API 索引](https://ohtaman.github.io/washi/zh/examples/)按问题类别和 WaSHI API 功能汇总了源码链接及本地运行方法。
|
|
443
446
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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) | 二维矩阵变量 (`continuous(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) | 满减门槛条件约束 (`indicator`)、互斥规则与凑单推荐 | 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) | 算术与逻辑约束 (`bool`, `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
|
-
|
|
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
|
-
|
|
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 === "
|
|
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;
|