@ohtaman/washi 0.1.2 → 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 (104) hide show
  1. package/README.ja.md +195 -105
  2. package/README.md +195 -105
  3. package/README.zh-CN.md +197 -107
  4. package/dist/browser/highs-worker.js +2 -2
  5. package/dist/browser/{highs-loader-T47YOyos.js → loader-CVt4CloI.js} +2 -2
  6. package/dist/browser/solvers/glpk.js +1065 -0
  7. package/dist/browser/solvers/glpk.min.js +1 -0
  8. package/dist/browser/solvers/z3-built.js +8245 -0
  9. package/dist/browser/solvers/z3-built.wasm +4 -0
  10. package/dist/browser/solvers/z3.js +7550 -0
  11. package/dist/browser/solvers/z3.min.js +5 -0
  12. package/dist/browser/washi.js +370 -642
  13. package/dist/browser/washi.min.js +4 -4
  14. package/dist/lib/async-mutex-Duo4jwzn.js +195 -0
  15. package/dist/lib/constraint-BRuB3ooC.js +90 -0
  16. package/dist/lib/highs-worker.js +2 -2
  17. package/dist/lib/{highs-loader-Blb99785.js → loader-Dti0v9yC.js} +1 -1
  18. package/dist/lib/registry-DksqCSjI.js +32 -0
  19. package/dist/lib/solvers/glpk.js +161 -2
  20. package/dist/lib/solvers/z3-assets.js +1 -0
  21. package/dist/lib/solvers/z3-built.js +8245 -0
  22. package/dist/lib/solvers/z3-built.wasm +4 -0
  23. package/dist/lib/solvers/z3.js +476 -2
  24. package/dist/lib/washi.js +604 -248
  25. package/dist/types/index.d.ts +7 -10
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/modeler/constraint.d.ts +0 -3
  28. package/dist/types/modeler/constraint.d.ts.map +1 -1
  29. package/dist/types/modeler/expression.d.ts +16 -10
  30. package/dist/types/modeler/expression.d.ts.map +1 -1
  31. package/dist/types/modeler/index.d.ts +3 -5
  32. package/dist/types/modeler/index.d.ts.map +1 -1
  33. package/dist/types/modeler/model.d.ts +91 -12
  34. package/dist/types/modeler/model.d.ts.map +1 -1
  35. package/dist/types/modeler/solvers/glpk/index.d.ts +3 -0
  36. package/dist/types/modeler/solvers/glpk/index.d.ts.map +1 -0
  37. package/dist/types/modeler/solvers/glpk/loader.d.ts +16 -0
  38. package/dist/types/modeler/solvers/glpk/loader.d.ts.map +1 -0
  39. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -0
  40. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -0
  41. package/dist/types/modeler/solvers/glpk.d.ts +1 -21
  42. package/dist/types/modeler/solvers/glpk.d.ts.map +1 -1
  43. package/dist/types/{highs-assets.d.ts → modeler/solvers/highs/assets.d.ts} +1 -1
  44. package/dist/types/modeler/solvers/highs/assets.d.ts.map +1 -0
  45. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -0
  46. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -0
  47. package/dist/types/modeler/solvers/highs/index.d.ts +6 -0
  48. package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -0
  49. package/dist/types/{highs-loader.d.ts → modeler/solvers/highs/loader.d.ts} +2 -2
  50. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -0
  51. package/dist/types/{highs-node-runtime.d.ts → modeler/solvers/highs/node-runtime.d.ts} +1 -1
  52. package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -0
  53. package/dist/types/modeler/solvers/highs/solver.d.ts +33 -0
  54. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -0
  55. package/dist/types/modeler/solvers/highs/worker-client.d.ts +13 -0
  56. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -0
  57. package/dist/types/{highs-worker-factory.d.ts → modeler/solvers/highs/worker-factory.d.ts} +1 -1
  58. package/dist/types/modeler/solvers/highs/worker-factory.d.ts.map +1 -0
  59. package/dist/types/modeler/solvers/highs/worker.d.ts +2 -0
  60. package/dist/types/modeler/solvers/highs/worker.d.ts.map +1 -0
  61. package/dist/types/modeler/solvers/highs.d.ts +1 -31
  62. package/dist/types/modeler/solvers/highs.d.ts.map +1 -1
  63. package/dist/types/modeler/solvers/index.d.ts +0 -2
  64. package/dist/types/modeler/solvers/index.d.ts.map +1 -1
  65. package/dist/types/modeler/solvers/mock.d.ts +1 -0
  66. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  67. package/dist/types/modeler/solvers/registry.d.ts +1 -2
  68. package/dist/types/modeler/solvers/registry.d.ts.map +1 -1
  69. package/dist/types/modeler/solvers/types.d.ts +1 -1
  70. package/dist/types/modeler/solvers/z3/assets.d.ts +11 -0
  71. package/dist/types/modeler/solvers/z3/assets.d.ts.map +1 -0
  72. package/dist/types/modeler/solvers/z3/index.d.ts +3 -0
  73. package/dist/types/modeler/solvers/z3/index.d.ts.map +1 -0
  74. package/dist/types/modeler/solvers/z3/loader.d.ts +56 -0
  75. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -0
  76. package/dist/types/modeler/solvers/z3/solver.d.ts +34 -0
  77. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -0
  78. package/dist/types/modeler/solvers/z3.d.ts +1 -35
  79. package/dist/types/modeler/solvers/z3.d.ts.map +1 -1
  80. package/dist/types/modeler/types.d.ts +8 -3
  81. package/dist/types/modeler/types.d.ts.map +1 -1
  82. package/dist/types/modeler/variable.d.ts +1 -6
  83. package/dist/types/modeler/variable.d.ts.map +1 -1
  84. package/package.json +28 -6
  85. package/dist/browser/_washi-optional-engine-282Ug7Gw.js +0 -7
  86. package/dist/lib/glpk-5cUpjyb9.js +0 -135
  87. package/dist/lib/z3-C6GeIicJ.js +0 -509
  88. package/dist/types/glpk-loader.d.ts +0 -8
  89. package/dist/types/glpk-loader.d.ts.map +0 -1
  90. package/dist/types/highs-assets.d.ts.map +0 -1
  91. package/dist/types/highs-browser-runtime.d.ts +0 -2
  92. package/dist/types/highs-browser-runtime.d.ts.map +0 -1
  93. package/dist/types/highs-loader.d.ts.map +0 -1
  94. package/dist/types/highs-node-runtime.d.ts.map +0 -1
  95. package/dist/types/highs-worker-client.d.ts +0 -12
  96. package/dist/types/highs-worker-client.d.ts.map +0 -1
  97. package/dist/types/highs-worker-factory.d.ts.map +0 -1
  98. package/dist/types/highs-worker.d.ts +0 -2
  99. package/dist/types/highs-worker.d.ts.map +0 -1
  100. package/dist/types/modeler/mip-compat.d.ts +0 -43
  101. package/dist/types/modeler/mip-compat.d.ts.map +0 -1
  102. package/dist/types/modeler/pulp-compat.d.ts +0 -67
  103. package/dist/types/modeler/pulp-compat.d.ts.map +0 -1
  104. /package/dist/browser/{highs-browser-runtime-D13gHOT5.js → browser-runtime-D13gHOT5.js} +0 -0
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 提供的功能
49
51
 
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 秒内高效求解。
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 调查冲突约束。返回的是一个冲突约束子集,不一定是最小子集。
56
+
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,8 @@ 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";
206
+ import "@ohtaman/washi/solvers/z3";
203
207
 
204
208
  const m = new Model({ name: "SmartSecurityRules" });
205
209
 
@@ -207,16 +211,16 @@ const isNight = m.addBoolVar("isNight");
207
211
  const motion = m.addBoolVar("motion");
208
212
  const alarm = m.addBoolVar("alarm");
209
213
  const siren = m.addBoolVar("siren");
210
- const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
214
+ const lux = m.integer("lux", { lb: 0 });
211
215
 
212
216
  // 规则定义
213
- m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
214
- 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");
215
219
 
216
220
  // 传感器当前输入数据
217
- m.addConstraint(lux.le(10), "sensor_dark");
218
- m.addConstraint(motion, "sensor_motion");
219
- 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");
220
224
 
221
225
  // 无目标函数直接判定可满足性 (SAT)
222
226
  const res = await m.check({ solver: "z3" });
@@ -232,28 +236,28 @@ console.log(`警报响起? ${m.values.get("siren")}`); // true
232
236
  当规则或约束之间存在矛盾时,Z3 能够提取引发冲突的**矛盾约束子集**(Unsat Core,不保证最小性;需使用 Z3):
233
237
 
234
238
  ```ts
235
- import { Model } from "@ohtaman/washi";
239
+ import { Model, expr } from "@ohtaman/washi";
236
240
  import "@ohtaman/washi/solvers/z3";
237
241
 
238
242
  const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
239
- const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
240
- 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 });
241
245
  const rushDelivery = m.addBoolVar("rushDelivery");
242
246
 
243
247
  // 规则 1: 每位工程师成本 $8,000
244
- m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
248
+ m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
245
249
 
246
250
  // 规则 2: 客户要求加急交付
247
- m.addConstraint(rushDelivery, "rush_required");
251
+ m.constrain(rushDelivery, "rush_required");
248
252
 
249
253
  // 规则 3: 加急交付需至少配备 8 名工程师
250
- m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
254
+ m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
251
255
 
252
256
  // 规则 4: 董事会严格预算上限 $50,000 (冲突点: 8人 * $8,000 = $64,000 > $50,000!)
253
- m.addConstraint(budget.le(50000), "strict_board_budget_cap");
257
+ m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
254
258
 
255
259
  // 规则 5: 最低 1 名员工用工底线(无关规则)
256
- m.addConstraint(engineers.ge(1), "minimum_one_employee");
260
+ m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
257
261
 
258
262
  const res = await m.check({ solver: "z3" });
259
263
 
@@ -265,35 +269,54 @@ console.log("冲突约束子集:", res.unsatCore);
265
269
 
266
270
  ---
267
271
 
268
- ### 5. Python PuLP 兼容层
272
+ ### 5. 使用 GLPK 求解器 (GPL-3.0)
273
+
274
+ 使用 GNU Linear Programming Kit (GLPK) 求解器插件:
275
+
276
+ ```ts
277
+ import { Model, expr } from "@ohtaman/washi";
278
+ import "@ohtaman/washi/solvers/glpk";
279
+
280
+ const m = new Model({ solver: "glpk" });
281
+ const x = m.integer("x", { lb: 0 });
282
+ const y = m.integer("y", { lb: 0 });
283
+
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");
287
+
288
+ const status = await m.optimize();
289
+ console.log(`求解状态: ${status}`); // "Optimal"
290
+ console.log(`目标函数值: ${m.objectiveValue}`); // 1100
291
+ ```
292
+
293
+ ---
294
+
295
+ ### 6. 向量运算与高效点积 (`dot`)
269
296
 
270
- 若你已有 Python PuLP 代码资产,可零摩擦迁移:
297
+ 无需嵌套循环,一行代码完成一维和二维变量的线性组合:
271
298
 
272
299
  ```ts
273
- import {
274
- LpProblem,
275
- LpVariable,
276
- LpMaximize,
277
- LpInteger,
278
- HiGHS,
279
- value,
280
- } from "@ohtaman/washi";
281
-
282
- const prob = new LpProblem("PuLPDemo", LpMaximize);
283
-
284
- const x = new LpVariable("x", 0, undefined, LpInteger);
285
- const y = new LpVariable("y", 0, undefined, LpInteger);
286
-
287
- // 等同于 PuLP 中的 += 语法
288
- prob.add(x.mul(30).add(y.mul(50)));
289
- prob.add(x.add(y.mul(2)).le(40));
290
- prob.add(x.mul(2).add(y).le(50));
291
-
292
- await prob.solve(new HiGHS());
293
-
294
- console.log(`状态: ${prob.status}`);
295
- console.log(`目标函数值: ${value(prob.objective)}`);
296
- 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);
297
320
  ```
298
321
 
299
322
  ---
@@ -341,7 +364,52 @@ await model.solve();
341
364
 
342
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。
343
366
 
344
- 浏览器构建包含 HiGHS 引擎,不包含可选的 GLPK / Z3 引擎。其他引擎集成请使用 npm 包及可选依赖。完整示例位于 `examples/distribution/`。
367
+ ### 浏览器环境中的求解器插件(GLPK & Z3)
368
+
369
+ 浏览器发布包在 `dist/browser/solvers/` 下提供了开箱即用的独立求解器插件(通过 CDN 引入时适用于 `>= 0.2.0` 或 `@latest`):
370
+ - **GLPK**: `dist/browser/solvers/glpk.js` (ESM) 与 `dist/browser/solvers/glpk.min.js` (IIFE,挂载全局 `WashiGLPK`)
371
+ - **Z3**: `dist/browser/solvers/z3.js` (ESM) 与 `dist/browser/solvers/z3.min.js` (IIFE,挂载全局 `WashiZ3`),附带 `z3-built.js` 和 `z3-built.wasm`
372
+
373
+ #### 浏览器 ESM 使用 GLPK
374
+ ```html
375
+ <script type="module">
376
+ import { Model } from "./washi/washi.js";
377
+ import "./washi/solvers/glpk.js";
378
+
379
+ const model = new Model({ solver: "glpk" });
380
+ const x = model.addVar({ lb: 0, ub: 10 });
381
+ model.maximize(x);
382
+ await model.solve();
383
+ console.log("GLPK 求解状态:", model.status);
384
+ </script>
385
+ ```
386
+
387
+ #### 原生脚本标签使用 GLPK
388
+ ```html
389
+ <script src="./washi/washi.min.js"></script>
390
+ <script src="./washi/solvers/glpk.min.js"></script>
391
+ <script>
392
+ const model = new Washi.Model({ solver: "glpk" });
393
+ const x = model.addVar({ lb: 0, ub: 10 });
394
+ model.maximize(x);
395
+ model.solve().then(status => console.log("GLPK 求解状态:", status));
396
+ </script>
397
+ ```
398
+
399
+ > [!NOTE]
400
+ > **浏览器环境中使用 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/`。
345
413
 
346
414
  ### 工厂内网、离线部署、Worker 与自定义 URL
347
415
 
@@ -364,32 +432,54 @@ setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
364
432
 
365
433
  ---
366
434
 
367
- ## 💻 本地脚本与交互式 Web 演示
435
+ ## 🛠️ 自定义求解器开发与架构扩展
368
436
 
369
- 在 `examples/node/` 中提供了即开即用的独立脚本:
437
+ WaSHI 采用完全解耦的插件式求解器架构。你可以轻松编写并注册自定义求解器适配器(如云端 REST 求解服务、定制 WebAssembly 引擎或商业求解器):
370
438
 
371
- ```bash
372
- npx tsx examples/node/01-production-planning.ts
373
- npx tsx examples/node/02-knapsack-problem.ts
374
- npx tsx examples/node/03-portfolio-qp.ts
375
- npx tsx examples/node/04-smt-security-rules.ts
376
- npx tsx examples/node/05-unsat-core-diagnosis.ts
377
- npx tsx examples/node/06-pulp-compatibility.ts
378
- ```
439
+ - 完整开发指南、接口契约(`Solver`, `LpModelData`, `SolverResult`)与测试实践详见 [Custom Solver Developer Guide](docs/custom-solver-guide.md)。
440
+
441
+ ---
442
+
443
+ ## 💻 示例与浏览器演示
379
444
 
380
- 启动浏览器端交互式可视化演示 (`examples/web-demo`):
445
+ [示例目录](https://github.com/ohtaman/washi/blob/main/examples/README.md)与[在线案例与 API 索引](https://ohtaman.github.io/washi/zh/examples/)按问题类别和 WaSHI API 功能汇总了源码链接及本地运行方法。
446
+
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 |
457
+
458
+ 在[在线浏览器演示](https://ohtaman.github.io/washi/demo/)中可以编辑并运行模型。浏览器 ESM、script 标签和 CodePen 的用法请参阅[分发示例](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)。Z3 需要跨源隔离;具体要求见上方浏览器说明。
459
+
460
+ 本地运行前,请克隆仓库并安装开发依赖:
381
461
 
382
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/
383
473
  npm run dev
384
- # 文档主页: http://localhost:5173/
385
- # 交互式 Demo: http://localhost:5173/demo/
386
474
  ```
387
475
 
476
+ 示例导入仓库的 `src/` 模块。使用已发布软件包的应用应改为导入 `@ohtaman/washi` 及所需的求解器插件。
477
+
388
478
  ---
389
479
 
390
480
  ## 🧪 自动化测试
391
481
 
392
- 项目全面贯彻测试驱动开发 (TDD),涵盖 120+ 单元测试、集成测试与大规模基准测试:
482
+ 运行仓库的单元测试与集成测试,并构建分发版本:
393
483
 
394
484
  ```bash
395
485
  # 运行全部单元与集成测试
@@ -1,5 +1,5 @@
1
- import { c as e } from "./highs-loader-T47YOyos.js";
2
- //#region src/highs-worker.ts
1
+ import { c as e } from "./loader-CVt4CloI.js";
2
+ //#region src/modeler/solvers/highs/worker.ts
3
3
  self.onmessage = async (t) => {
4
4
  let { type: n, id: r, lpString: i, options: a } = t.data;
5
5
  if (n === "SOLVE") try {
@@ -8,7 +8,7 @@ function n() {
8
8
  return t("highs.wasm");
9
9
  }
10
10
  //#endregion
11
- //#region src/highs-loader.ts
11
+ //#region src/modeler/solvers/highs/loader.ts
12
12
  var r = "https://cdn.jsdelivr.net/npm/highs@1.15.3/build/highs.wasm", i = null, a = null, o = null;
13
13
  function s(e) {
14
14
  o = e, i = null, a = null;
@@ -38,7 +38,7 @@ async function f(e = {}) {
38
38
  if (i && a === n) return i;
39
39
  a = n;
40
40
  let r = (async () => {
41
- let e = (await import("./highs-browser-runtime-D13gHOT5.js")).default;
41
+ let e = (await import("./browser-runtime-D13gHOT5.js")).default;
42
42
  return await e({ locateFile: (e) => e.endsWith(".wasm") ? t : e });
43
43
  })().catch((e) => {
44
44
  i === r && (i = null, a = null);