@ohtaman/washi 0.0.0-stage → 0.1.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +341 -0
  3. package/README.md +340 -2
  4. package/README.zh-CN.md +341 -0
  5. package/THIRD_PARTY_LICENSES.md +83 -0
  6. package/dist/lib/assets/highs-worker-D57nd9KC.js +3 -0
  7. package/dist/lib/glpk-BC32ZaPG.js +126 -0
  8. package/dist/lib/highs.wasm +0 -0
  9. package/dist/lib/highs.wasm.LICENSE +22 -0
  10. package/dist/lib/solvers/glpk.js +2 -0
  11. package/dist/lib/solvers/z3.js +2 -0
  12. package/dist/lib/washi.js +908 -0
  13. package/dist/lib/z3-C5DfllfD.js +562 -0
  14. package/dist/types/glpk-loader.d.ts +8 -0
  15. package/dist/types/glpk-loader.d.ts.map +1 -0
  16. package/dist/types/highs-loader.d.ts +39 -0
  17. package/dist/types/highs-loader.d.ts.map +1 -0
  18. package/dist/types/highs-worker-client.d.ts +12 -0
  19. package/dist/types/highs-worker-client.d.ts.map +1 -0
  20. package/dist/types/highs-worker.d.ts +2 -0
  21. package/dist/types/highs-worker.d.ts.map +1 -0
  22. package/dist/types/index.d.ts +14 -0
  23. package/dist/types/index.d.ts.map +1 -0
  24. package/dist/types/modeler/boolean-expr.d.ts +38 -0
  25. package/dist/types/modeler/boolean-expr.d.ts.map +1 -0
  26. package/dist/types/modeler/constraint.d.ts +28 -0
  27. package/dist/types/modeler/constraint.d.ts.map +1 -0
  28. package/dist/types/modeler/expression.d.ts +79 -0
  29. package/dist/types/modeler/expression.d.ts.map +1 -0
  30. package/dist/types/modeler/index.d.ts +14 -0
  31. package/dist/types/modeler/index.d.ts.map +1 -0
  32. package/dist/types/modeler/lp-writer.d.ts +30 -0
  33. package/dist/types/modeler/lp-writer.d.ts.map +1 -0
  34. package/dist/types/modeler/mip-compat.d.ts +43 -0
  35. package/dist/types/modeler/mip-compat.d.ts.map +1 -0
  36. package/dist/types/modeler/model.d.ts +156 -0
  37. package/dist/types/modeler/model.d.ts.map +1 -0
  38. package/dist/types/modeler/parser.d.ts +27 -0
  39. package/dist/types/modeler/parser.d.ts.map +1 -0
  40. package/dist/types/modeler/pulp-compat.d.ts +67 -0
  41. package/dist/types/modeler/pulp-compat.d.ts.map +1 -0
  42. package/dist/types/modeler/quad-expr.d.ts +55 -0
  43. package/dist/types/modeler/quad-expr.d.ts.map +1 -0
  44. package/dist/types/modeler/solvers/glpk.d.ts +22 -0
  45. package/dist/types/modeler/solvers/glpk.d.ts.map +1 -0
  46. package/dist/types/modeler/solvers/highs.d.ts +26 -0
  47. package/dist/types/modeler/solvers/highs.d.ts.map +1 -0
  48. package/dist/types/modeler/solvers/index.d.ts +7 -0
  49. package/dist/types/modeler/solvers/index.d.ts.map +1 -0
  50. package/dist/types/modeler/solvers/mock.d.ts +25 -0
  51. package/dist/types/modeler/solvers/mock.d.ts.map +1 -0
  52. package/dist/types/modeler/solvers/registry.d.ts +20 -0
  53. package/dist/types/modeler/solvers/registry.d.ts.map +1 -0
  54. package/dist/types/modeler/solvers/types.d.ts +86 -0
  55. package/dist/types/modeler/solvers/types.d.ts.map +1 -0
  56. package/dist/types/modeler/solvers/z3.d.ts +37 -0
  57. package/dist/types/modeler/solvers/z3.d.ts.map +1 -0
  58. package/dist/types/modeler/types.d.ts +27 -0
  59. package/dist/types/modeler/types.d.ts.map +1 -0
  60. package/dist/types/modeler/variable.d.ts +52 -0
  61. package/dist/types/modeler/variable.d.ts.map +1 -0
  62. package/package.json +97 -4
package/README.md CHANGED
@@ -1,3 +1,341 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/washi-header-dark.png">
4
+ <source media="(prefers-color-scheme: light)" srcset="./assets/washi-header-light.png">
5
+ <img alt="WaSHI - Algebraic Modeler for TypeScript &amp; JavaScript" src="./assets/washi-header-light.png" width="420" />
6
+ </picture>
7
+ <p><strong>Intuitive Mathematical Optimization &amp; Algebraic Modeler for TypeScript &amp; JavaScript</strong></p>
8
+ <p>
9
+ <em>Linear Programming (LP) • Mixed-Integer Programming (MIP) • Quadratic Programming (QP) • SMT &amp; Conflict Diagnosis</em>
10
+ </p>
11
+ <p>
12
+ <a href="./README.md">English</a> •
13
+ <a href="./README.ja.md">日本語</a> •
14
+ <a href="./README.zh-CN.md">简体中文</a>
15
+ </p>
16
+ <p>
17
+ <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/demo-online-brightgreen" alt="live demo" /></a>
18
+ <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>
19
+ <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>
20
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
21
+ <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="TypeScript types" />
22
+ <img src="https://img.shields.io/badge/tests-120%2B%20passing-brightgreen" alt="tests" />
23
+ </p>
24
+ </div>
2
25
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
26
+ ---
27
+
28
+ **WaSHI** is a high-level algebraic modeling library that brings the expressive elegance of Python's **PuLP** and Julia's **JuMP** to the modern TypeScript and JavaScript ecosystem. Formulate mathematical models using natural algebraic equations and solve them instantly across Node.js, browsers, Bun, Deno, and Next.js—powered natively by WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**) with zero native compilation steps.
29
+
30
+ ```ts
31
+ import { Model, expr, c } from "@ohtaman/washi";
32
+
33
+ const m = new Model({ sense: "Maximize" });
34
+ const x = m.addVar({ type: "Integer", lb: 0 });
35
+ const y = m.addVar({ type: "Integer", lb: 0 });
36
+
37
+ m.maximize(expr`30 * ${x} + 50 * ${y}`);
38
+ m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
39
+ m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
40
+
41
+ await m.solve();
42
+ console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🎯 Why WaSHI?
48
+
49
+ 1. 🌐 **100% Client-Side & Zero Server Cost**: Run industrial-grade optimization entirely inside the user's browser, edge worker, or Electron app. Zero backend server costs, offline-ready, and 100% private (confidential business data never leaves the client device).
50
+ 2. 📦 **Frictionless Zero-Dependency Distribution**: No Python runtimes, no C/C++ compilation, and no OS-dependent native solver binaries to bundle. Just `npm install @ohtaman/washi`—everything (including WebAssembly solvers) works instantly out of the box.
51
+ 3. ✍️ **Familiar Algebraic Modeling (PuLP & python-mip DX)**: Never struggle with low-level matrix indices. Formulate models naturally using tagged template literals (`c`${x} + 2 * ${y} <= 40``) or drop-in PuLP/python-mip syntax (`LpProblem`, `lpSum`).
52
+ 4. 🔄 **Pluggable Solvers & Unified API**: Formulate your mathematical model once; solve with **HiGHS** (industrial MIP/QP), **Z3** (SMT logic & Unsat Core diagnosis), or **GLPK** with a single parameter.
53
+ 5. 🔍 **Automatic Conflict Diagnosis (`unsatCore`)**: When constraints are contradictory (Infeasible), diagnose root causes instantly by extracting the minimal conflicting constraint subset via the Z3 engine.
54
+ 6. ⚡ **Industrial Scale Performance in WebAssembly**: Built with an $O(N)$ linear-time expression builder. Formulate 20,000+ variable MIPs in ~100ms and solve them in ~15 seconds right inside WebAssembly via HiGHS.
55
+
56
+ ---
57
+
58
+ ## ⚡ Solvers Matrix
59
+
60
+ | Solver | Engine License | Problem Classes | Best For | Status in WaSHI |
61
+ | :--- | :--- | :--- | :--- | :--- |
62
+ | **HiGHS** | **MIT** | **LP, MIP, Convex QP** | Industrial-scale optimization, production planning, finance | **Built-in default** (zero-config) |
63
+ | **Z3** | **MIT** | **SMT, SAT, Logic, MIP** | Propositional logic, conflict diagnosis (`unsatCore`), rule engines | Optional plugin (`@ohtaman/washi/solvers/z3`) |
64
+ | **GLPK** | **GPL-3.0** | **LP, MIP** | Academic benchmarks, legacy GLPK compatibility | Optional plugin (`@ohtaman/washi/solvers/glpk`) |
65
+ | **Mock** | **MIT** | *Any* | Ultra-fast unit testing in CI without loading WebAssembly | Built-in |
66
+
67
+ ---
68
+
69
+ ## 📦 Installation
70
+
71
+ ```bash
72
+ # npm
73
+ npm install @ohtaman/washi
74
+
75
+ # pnpm
76
+ pnpm add @ohtaman/washi
77
+
78
+ # yarn
79
+ yarn add @ohtaman/washi
80
+
81
+ # bun
82
+ bun add @ohtaman/washi
83
+ ```
84
+
85
+ *(Optional)* To use Z3 or GLPK solvers, install their respective packages:
86
+
87
+ ```bash
88
+ npm install z3-solver # For SMT and unsat core diagnosis
89
+ npm install glpk.js # For GLPK solver (GPL-3.0)
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 🚀 Usage Guide
95
+
96
+ ### 1. Mixed-Integer Linear Programming (MIP)
97
+
98
+ Formulate production scheduling, resource allocation, and facility location problems:
99
+
100
+ ```ts
101
+ import { Model, c, expr } from "@ohtaman/washi";
102
+
103
+ const m = new Model({ name: "FactoryPlanning", sense: "Maximize" });
104
+
105
+ // Decision variables (Integer, non-negative)
106
+ const chairs = m.addVar({ name: "chairs", type: "Integer", lb: 0 });
107
+ const tables = m.addVar({ name: "tables", type: "Integer", lb: 0 });
108
+
109
+ // Natural formula objective & constraints via Tagged Template Literals
110
+ m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
111
+ m.addConstraint(c`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
112
+ m.addConstraint(c`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
113
+
114
+ // Solve using default HiGHS WASM engine
115
+ const status = await m.solve();
116
+
117
+ console.log(`Status: ${status}`); // "Optimal"
118
+ console.log(`Objective: $${m.objectiveValue}`); // 1100
119
+ console.log(`Chairs: ${chairs.value}, Tables: ${tables.value}`); // 20 chairs, 10 tables
120
+ ```
121
+
122
+ #### 🗂 Multidimensional Variables Grid (`addVars`)
123
+
124
+ Construct variable arrays or multi-index matrices in one line (similar to PuLP's `LpVariable.dicts`):
125
+
126
+ ```ts
127
+ // 1D dictionary of binary variables
128
+ const openCity = m.addVars(["Tokyo", "Osaka", "Nagoya"], { type: "Binary" });
129
+
130
+ // 2D transport flow matrix: [Factory] x [Store]
131
+ const flow = m.addVars(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
132
+
133
+ // 2D coordinate grid (e.g. 8x8 chessboard)
134
+ const board = m.addVars(8, 8, { type: "Binary", prefix: "queen" });
135
+ ```
136
+
137
+ #### 🔀 Big-M Indicator Constraints (`addIndicator`)
138
+
139
+ Model conditional activation rules and fixed-charge economics without manual Big-M algebra:
140
+
141
+ ```ts
142
+ // If factory is closed (openCity['Tokyo'] == 0), its production capacity must be 0
143
+ m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
144
+ activeValue: 0,
145
+ M: 1000,
146
+ });
147
+ ```
148
+
149
+ ---
150
+
151
+ ### 2. Portfolio Risk Minimization (Convex Quadratic Programming)
152
+
153
+ Minimize investment risk based on a covariance matrix subject to return and budget constraints:
154
+
155
+ ```ts
156
+ import { Model, QuadExpr, sum } from "@ohtaman/washi";
157
+
158
+ const m = new Model({ name: "MarkowitzPortfolio", sense: "Minimize" });
159
+ const assets = ["Tech", "Bonds", "RealEstate"];
160
+ const returns = [0.14, 0.04, 0.08];
161
+
162
+ // Decision variables: portfolio allocation weights (0 to 1)
163
+ const w = assets.map((a) => m.addVar({ name: `w_${a}`, lb: 0, ub: 1 }));
164
+
165
+ // 1. Fully invested budget: sum(w) == 1.0
166
+ m.addConstraint(sum(w).eq(1.0), "budget");
167
+
168
+ // 2. Minimum target return: portfolio return >= 8.5%
169
+ m.addConstraint(sum(w.map((v, i) => v.mul(returns[i]))).ge(0.085), "min_return");
170
+
171
+ // 3. Covariance risk matrix
172
+ const cov = [
173
+ [0.040, 0.002, 0.015],
174
+ [0.002, 0.005, 0.002],
175
+ [0.015, 0.002, 0.025],
176
+ ];
177
+
178
+ // Quadratic objective: w^T * Cov * w
179
+ let risk = new QuadExpr();
180
+ for (let i = 0; i < assets.length; i++) {
181
+ for (let j = 0; j < assets.length; j++) {
182
+ risk = risk.add(w[i].mul(w[j]).mul(cov[i][j]));
183
+ }
184
+ }
185
+ m.minimize(risk);
186
+
187
+ await m.solve();
188
+ console.log(`Minimal Variance: ${m.objectiveValue?.toFixed(6)}`);
189
+ assets.forEach((name, i) => {
190
+ console.log(` ${name}: ${((w[i].value || 0) * 100).toFixed(2)}%`);
191
+ });
192
+ ```
193
+
194
+ ---
195
+
196
+ ### 3. SMT Logic & Rule Engines (with Z3)
197
+
198
+ Express boolean variables, propositional connectives (`and`, `or`, `implies`, `not`, `xor`), and mixed arithmetic rules:
199
+
200
+ ```ts
201
+ import { Model, not } from "@ohtaman/washi";
202
+
203
+ const m = new Model({ name: "SmartSecurityRules" });
204
+
205
+ const isNight = m.addBoolVar("isNight");
206
+ const motion = m.addBoolVar("motion");
207
+ const alarm = m.addBoolVar("alarm");
208
+ const siren = m.addBoolVar("siren");
209
+ const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
210
+
211
+ // Logical rules
212
+ m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
213
+ m.addConstraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
214
+
215
+ // Current sensor observations
216
+ m.addConstraint(lux.le(10), "sensor_dark");
217
+ m.addConstraint(motion, "sensor_motion");
218
+ m.addConstraint(alarm, "alarm_on");
219
+
220
+ // Check satisfiability and deduce state without an objective function
221
+ const res = await m.check({ solver: "z3" });
222
+
223
+ console.log(`Satisfiable? ${res.status === "Sat"}`); // true
224
+ console.log(`Siren active? ${m.values.get("siren")}`); // true
225
+ ```
226
+
227
+ ---
228
+
229
+ ### 4. Conflict Diagnosis (`unsatCore`)
230
+
231
+ When business rules or constraints contradict each other, extract the **minimal conflicting subset** for instant troubleshooting (requires Z3):
232
+
233
+ ```ts
234
+ import { Model } from "@ohtaman/washi";
235
+ import "@ohtaman/washi/solvers/z3";
236
+
237
+ const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
238
+ const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
239
+ const budget = m.addVar({ name: "budget", type: "Continuous", lb: 0, ub: 100000 });
240
+ const rushDelivery = m.addBoolVar("rushDelivery");
241
+
242
+ // Rule 1: Each engineer costs $8,000
243
+ m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
244
+
245
+ // Rule 2: Customer requires rush delivery
246
+ m.addConstraint(rushDelivery, "rush_required");
247
+
248
+ // Rule 3: Rush delivery requires at least 8 engineers
249
+ m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
250
+
251
+ // Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
252
+ m.addConstraint(budget.le(50000), "strict_board_budget_cap");
253
+
254
+ // Rule 5: Innocent bystander rule
255
+ m.addConstraint(engineers.ge(1), "minimum_one_employee");
256
+
257
+ const res = await m.check({ solver: "z3" });
258
+
259
+ console.log(`Status: ${res.status}`); // "Unsat"
260
+ console.log("Conflicting constraints:", res.unsatCore);
261
+ // Output: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
262
+ // Note: "minimum_one_employee" is correctly excluded!
263
+ ```
264
+
265
+ ---
266
+
267
+ ### 5. Python PuLP Compatibility Layer
268
+
269
+ If you have existing Python PuLP code, port it with near zero friction:
270
+
271
+ ```ts
272
+ import {
273
+ LpProblem,
274
+ LpVariable,
275
+ LpMaximize,
276
+ LpInteger,
277
+ HiGHS,
278
+ value,
279
+ } from "@ohtaman/washi";
280
+
281
+ const prob = new LpProblem("PuLPDemo", LpMaximize);
282
+
283
+ const x = new LpVariable("x", 0, undefined, LpInteger);
284
+ const y = new LpVariable("y", 0, undefined, LpInteger);
285
+
286
+ // PuLP += equivalent
287
+ prob.add(x.mul(30).add(y.mul(50)));
288
+ prob.add(x.add(y.mul(2)).le(40));
289
+ prob.add(x.mul(2).add(y).le(50));
290
+
291
+ await prob.solve(new HiGHS());
292
+
293
+ console.log(`Status: ${prob.status}`);
294
+ console.log(`Objective: ${value(prob.objective)}`);
295
+ console.log(`x = ${value(x)}, y = ${value(y)}`);
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 💻 CLI & Interactive Demos
301
+
302
+ Ready-to-run standalone examples are available in `examples/node/`:
303
+
304
+ ```bash
305
+ npx tsx examples/node/01-production-planning.ts
306
+ npx tsx examples/node/02-knapsack-problem.ts
307
+ npx tsx examples/node/03-portfolio-qp.ts
308
+ npx tsx examples/node/04-smt-security-rules.ts
309
+ npx tsx examples/node/05-unsat-core-diagnosis.ts
310
+ npx tsx examples/node/06-pulp-compatibility.ts
311
+ ```
312
+
313
+ To run the interactive browser showcase (`examples/web-demo`):
314
+
315
+ ```bash
316
+ npm run dev
317
+ # Open http://localhost:5173/ in your browser
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 🧪 Testing
323
+
324
+ WaSHI is developed with strict Test-Driven Development (TDD) across 120+ unit, integration, and large-scale test cases:
325
+
326
+ ```bash
327
+ # Run all unit and integration tests
328
+ npm test
329
+
330
+ # Build production bundle and TypeScript declarations
331
+ npm run build
332
+ ```
333
+
334
+ ---
335
+
336
+ ## 📄 License
337
+
338
+ - **`@ohtaman/washi`**: [MIT License](./LICENSE)
339
+ - **HiGHS** (default engine): MIT License
340
+ - **Z3** (optional plugin): MIT License
341
+ - **GLPK** (optional plugin): GPL-3.0 License
@@ -0,0 +1,341 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/washi-header-dark.png">
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" />
6
+ </picture>
7
+ <p><strong>面向 TypeScript 与 JavaScript 的直观数学规划与代数建模库</strong></p>
8
+ <p>
9
+ <em>线性规划 (LP) • 混合整数规划 (MIP) • 凸二次规划 (QP) • SMT逻辑约束与冲突诊断</em>
10
+ </p>
11
+ <p>
12
+ <a href="./README.md">English</a> •
13
+ <a href="./README.ja.md">日本語</a> •
14
+ <a href="./README.zh-CN.md">简体中文</a>
15
+ </p>
16
+ <p>
17
+ <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/demo-online-brightgreen" alt="live demo" /></a>
18
+ <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>
19
+ <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>
20
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
21
+ <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="TypeScript types" />
22
+ <img src="https://img.shields.io/badge/tests-120%2B%20passing-brightgreen" alt="tests" />
23
+ </p>
24
+ </div>
25
+
26
+ ---
27
+
28
+ **WaSHI** 是一个高层代数建模库,将 Python **PuLP** 与 Julia **JuMP** 的优雅表现力引入现代 TypeScript / JavaScript 生态系统。你可以直接使用符合数学直觉的代数公式编写数学模型,并在 Node.js、现代浏览器、Bun、Deno 及 Next.js 中秒级求解——完全依托 WebAssembly 求解器(**HiGHS**、**Z3**、**GLPK**),无需任何本地 C++ 编译环境。
29
+
30
+ ```ts
31
+ import { Model, expr, c } from "@ohtaman/washi";
32
+
33
+ const m = new Model({ sense: "Maximize" });
34
+ const x = m.addVar({ type: "Integer", lb: 0 });
35
+ const y = m.addVar({ type: "Integer", lb: 0 });
36
+
37
+ m.maximize(expr`30 * ${x} + 50 * ${y}`);
38
+ m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
39
+ m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
40
+
41
+ await m.solve();
42
+ console.log(`生产计划: x=${x.value}, y=${y.value} (最大利润: $${m.objectiveValue})`);
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🎯 为什么选择 WaSHI?
48
+
49
+ 1. 🌐 **100% 客户端运行与零服务器成本**:直接在用户的浏览器、边缘节点(Edge Worker)或 Electron 桌面端运行工业级优化。后端服务器运维成本为零,支持离线运行,且机密业务数据(如排班表、成本数据)永不离开客户端。
50
+ 2. 📦 **零依赖・单一包分发**:告别 Python 环境配置与繁重的跨平台 C/C++ 原生求解器编译绑定。只需 `npm install @ohtaman/washi`,包括 WebAssembly 求解器在内的所有组件即可开箱即用。
51
+ 3. ✍️ **极简现代代数建模 DX(兼容 PuLP / python-mip)**:无需编写晦涩低级的矩阵索引。使用直观的模板字面量(`c`${x} + 2 * ${y} <= 40``)或熟悉的 Python 风格抽象(`LpProblem`, `lpSum`),像在草稿纸上写公式一样自然。
52
+ 4. 🔄 **统一多求解器接口(HiGHS / Z3 / GLPK)**:模型代码一次编写,即可通过统一 API 自由切换:擅长工业级 MIP/QP 的 **HiGHS**、擅长逻辑求解与冲突分析的 **Z3**,以及经典学术求解器 **GLPK**。
53
+ 5. 🔍 **不可行模型(Infeasible)冲突根因诊断**:当业务规则出现矛盾导致无解时,可通过 Z3 引擎快速定位并提取冲突约束的最小子集(Unsat Core)。
54
+ 6. ⚡ **WebAssembly 原生工业级大规模性能**:搭载 $O(N)$ 线性时间高速表达式构建器,仅需约 100ms 即可构建 20,000+ 变量的大规模 MIP,并在浏览器 WebAssembly 中由 HiGHS 在约 15 秒内高效求解。
55
+
56
+ ---
57
+
58
+ ## ⚡ 求解器矩阵
59
+
60
+ | 求解器 | 核心许可证 | 支持的问题类型 | 最佳应用场景 | 在 WaSHI 中的提供方式 |
61
+ | :--- | :--- | :--- | :--- | :--- |
62
+ | **HiGHS** | **MIT** | **LP, MIP, 凸QP** | 工业级生产排程、运筹规划、金融组合优化 | **内置默认**(零配置) |
63
+ | **Z3** | **MIT** | **SMT, SAT, 命题逻辑, MIP** | 业务规则验证、约束冲突诊断 (`unsatCore`)、规则引擎 | 可选插件 (`@ohtaman/washi/solvers/z3`) |
64
+ | **GLPK** | **GPL-3.0** | **LP, MIP** | 学术基准测试、存量 GLPK 模型迁移 | 可选插件 (`@ohtaman/washi/solvers/glpk`) |
65
+ | **Mock** | **MIT** | *任意* | CI/CD 环境下无需加载 WASM 的极速单元测试 | 内置 |
66
+
67
+ ---
68
+
69
+ ## 📦 安装
70
+
71
+ ```bash
72
+ # npm
73
+ npm install @ohtaman/washi
74
+
75
+ # pnpm
76
+ pnpm add @ohtaman/washi
77
+
78
+ # yarn
79
+ yarn add @ohtaman/washi
80
+
81
+ # bun
82
+ bun add @ohtaman/washi
83
+ ```
84
+
85
+ *(可选)* 若需使用 Z3 或 GLPK 求解器,可按需安装对应依赖包:
86
+
87
+ ```bash
88
+ npm install z3-solver # 用于 SMT 与冲突诊断
89
+ npm install glpk.js # 用于 GLPK 求解器 (GPL-3.0)
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 🚀 快速上手指南
95
+
96
+ ### 1. 混合整数线性规划 (MIP: 生产计划问题)
97
+
98
+ 在有限资源约束下实现利润最大化:
99
+
100
+ ```ts
101
+ import { Model, c, expr } from "@ohtaman/washi";
102
+
103
+ const m = new Model({ name: "FactoryPlanning", sense: "Maximize" });
104
+
105
+ // 决策变量(非负整数)
106
+ const chairs = m.addVar({ name: "chairs", type: "Integer", lb: 0 });
107
+ const tables = m.addVar({ name: "tables", type: "Integer", lb: 0 });
108
+
109
+ // 借助标签模板字面量自然表达目标函数与约束
110
+ m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
111
+ m.addConstraint(c`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
112
+ m.addConstraint(c`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
113
+
114
+ // 使用默认的 HiGHS WASM 引擎求解
115
+ const status = await m.solve();
116
+
117
+ console.log(`求解状态: ${status}`); // "Optimal"
118
+ console.log(`最大利润: $${m.objectiveValue}`); // 1100
119
+ console.log(`生产量: 椅子=${chairs.value}, 桌子=${tables.value}`); // 椅子 20, 桌子 10
120
+ ```
121
+
122
+ #### 🗂 多维变量网格 (`addVars`)
123
+
124
+ 一行代码声明变量字典或多维变量矩阵(类似于 PuLP 的 `LpVariable.dicts`):
125
+
126
+ ```ts
127
+ // 1D 字典形式的布尔变量
128
+ const openCity = m.addVars(["Tokyo", "Osaka", "Nagoya"], { type: "Binary" });
129
+
130
+ // 2D 运输量矩阵: [工厂] x [门店]
131
+ const flow = m.addVars(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
132
+
133
+ // 2D 网格坐标 (例如 8x8 棋盘)
134
+ const board = m.addVars(8, 8, { type: "Binary", prefix: "queen" });
135
+ ```
136
+
137
+ #### 🔀 Big-M 指示约束 (`addIndicator`)
138
+
139
+ 直接表达条件激活与固定成本逻辑,无需手动进行繁琐的 Big-M 代数推导:
140
+
141
+ ```ts
142
+ // "若工厂关闭 (openCity['Tokyo'] == 0),则产量 flow 必须为 0"
143
+ m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
144
+ activeValue: 0,
145
+ M: 1000,
146
+ });
147
+ ```
148
+
149
+ ---
150
+
151
+ ### 2. 投资组合风险最小化 (凸二次规划: QP)
152
+
153
+ 在满足预期收益与预算约束的前提下,基于协方差矩阵最小化投资组合风险:
154
+
155
+ ```ts
156
+ import { Model, QuadExpr, sum } from "@ohtaman/washi";
157
+
158
+ const m = new Model({ name: "MarkowitzPortfolio", sense: "Minimize" });
159
+ const assets = ["Tech", "Bonds", "RealEstate"];
160
+ const returns = [0.14, 0.04, 0.08];
161
+
162
+ // 决策变量: 各资产投资权重 (0.0 ~ 1.0)
163
+ const w = assets.map((a) => m.addVar({ name: `w_${a}`, lb: 0, ub: 1 }));
164
+
165
+ // 1. 预算约束: 资金分配总额 100%
166
+ m.addConstraint(sum(w).eq(1.0), "budget");
167
+
168
+ // 2. 目标回报率: 组合最低回报率 >= 8.5%
169
+ m.addConstraint(sum(w.map((v, i) => v.mul(returns[i]))).ge(0.085), "min_return");
170
+
171
+ // 3. 资产协方差矩阵 (风险与相关性)
172
+ const cov = [
173
+ [0.040, 0.002, 0.015],
174
+ [0.002, 0.005, 0.002],
175
+ [0.015, 0.002, 0.025],
176
+ ];
177
+
178
+ // 二次目标函数: w^T * Cov * w
179
+ let risk = new QuadExpr();
180
+ for (let i = 0; i < assets.length; i++) {
181
+ for (let j = 0; j < assets.length; j++) {
182
+ risk = risk.add(w[i].mul(w[j]).mul(cov[i][j]));
183
+ }
184
+ }
185
+ m.minimize(risk);
186
+
187
+ await m.solve();
188
+ console.log(`最小方差: ${m.objectiveValue?.toFixed(6)}`);
189
+ assets.forEach((name, i) => {
190
+ console.log(` ${name}: ${((w[i].value || 0) * 100).toFixed(2)}%`);
191
+ });
192
+ ```
193
+
194
+ ---
195
+
196
+ ### 3. SMT 逻辑与规则引擎 (集成 Z3)
197
+
198
+ 原生支持布尔命题变量(`BoolVar`)、逻辑连接词(`and`, `or`, `implies`, `not`, `xor`)与数值算术约束的混合表达:
199
+
200
+ ```ts
201
+ import { Model, not } from "@ohtaman/washi";
202
+
203
+ const m = new Model({ name: "SmartSecurityRules" });
204
+
205
+ const isNight = m.addBoolVar("isNight");
206
+ const motion = m.addBoolVar("motion");
207
+ const alarm = m.addBoolVar("alarm");
208
+ const siren = m.addBoolVar("siren");
209
+ const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
210
+
211
+ // 规则定义
212
+ m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
213
+ m.addConstraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
214
+
215
+ // 传感器当前输入数据
216
+ m.addConstraint(lux.le(10), "sensor_dark");
217
+ m.addConstraint(motion, "sensor_motion");
218
+ m.addConstraint(alarm, "alarm_on");
219
+
220
+ // 无目标函数直接判定可满足性 (SAT)
221
+ const res = await m.check({ solver: "z3" });
222
+
223
+ console.log(`系统状态满足? ${res.status === "Sat"}`); // true
224
+ console.log(`警报响起? ${m.values.get("siren")}`); // true
225
+ ```
226
+
227
+ ---
228
+
229
+ ### 4. 约束冲突根因诊断 (`unsatCore`)
230
+
231
+ 当规则或约束之间存在矛盾时,Z3 能够精准指出引发冲突的**最小冲突约束子集**(需使用 Z3):
232
+
233
+ ```ts
234
+ import { Model } from "@ohtaman/washi";
235
+ import "@ohtaman/washi/solvers/z3";
236
+
237
+ const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
238
+ const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
239
+ const budget = m.addVar({ name: "budget", type: "Continuous", lb: 0, ub: 100000 });
240
+ const rushDelivery = m.addBoolVar("rushDelivery");
241
+
242
+ // 规则 1: 每位工程师成本 $8,000
243
+ m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
244
+
245
+ // 规则 2: 客户要求加急交付
246
+ m.addConstraint(rushDelivery, "rush_required");
247
+
248
+ // 规则 3: 加急交付需至少配备 8 名工程师
249
+ m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
250
+
251
+ // 规则 4: 董事会严格预算上限 $50,000 (冲突点: 8人 * $8,000 = $64,000 > $50,000!)
252
+ m.addConstraint(budget.le(50000), "strict_board_budget_cap");
253
+
254
+ // 规则 5: 最低 1 名员工用工底线(无关规则)
255
+ m.addConstraint(engineers.ge(1), "minimum_one_employee");
256
+
257
+ const res = await m.check({ solver: "z3" });
258
+
259
+ console.log(`求解状态: ${res.status}`); // "Unsat"
260
+ console.log("冲突约束子集:", res.unsatCore);
261
+ // 输出: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
262
+ // 注:无关的 "minimum_one_employee" 被准确排除在外!
263
+ ```
264
+
265
+ ---
266
+
267
+ ### 5. Python PuLP 兼容层
268
+
269
+ 若你已有 Python PuLP 代码资产,可零摩擦迁移:
270
+
271
+ ```ts
272
+ import {
273
+ LpProblem,
274
+ LpVariable,
275
+ LpMaximize,
276
+ LpInteger,
277
+ HiGHS,
278
+ value,
279
+ } from "@ohtaman/washi";
280
+
281
+ const prob = new LpProblem("PuLPDemo", LpMaximize);
282
+
283
+ const x = new LpVariable("x", 0, undefined, LpInteger);
284
+ const y = new LpVariable("y", 0, undefined, LpInteger);
285
+
286
+ // 等同于 PuLP 中的 += 语法
287
+ prob.add(x.mul(30).add(y.mul(50)));
288
+ prob.add(x.add(y.mul(2)).le(40));
289
+ prob.add(x.mul(2).add(y).le(50));
290
+
291
+ await prob.solve(new HiGHS());
292
+
293
+ console.log(`状态: ${prob.status}`);
294
+ console.log(`目标函数值: ${value(prob.objective)}`);
295
+ console.log(`x = ${value(x)}, y = ${value(y)}`);
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 💻 本地脚本与交互式 Web 演示
301
+
302
+ 在 `examples/node/` 中提供了即开即用的独立脚本:
303
+
304
+ ```bash
305
+ npx tsx examples/node/01-production-planning.ts
306
+ npx tsx examples/node/02-knapsack-problem.ts
307
+ npx tsx examples/node/03-portfolio-qp.ts
308
+ npx tsx examples/node/04-smt-security-rules.ts
309
+ npx tsx examples/node/05-unsat-core-diagnosis.ts
310
+ npx tsx examples/node/06-pulp-compatibility.ts
311
+ ```
312
+
313
+ 启动浏览器端交互式可视化演示 (`examples/web-demo`):
314
+
315
+ ```bash
316
+ npm run dev
317
+ # 在浏览器中访问 http://localhost:5173/
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 🧪 自动化测试
323
+
324
+ 项目全面贯彻测试驱动开发 (TDD),涵盖 120+ 单元测试、集成测试与大规模基准测试:
325
+
326
+ ```bash
327
+ # 运行全部单元与集成测试
328
+ npm test
329
+
330
+ # 构建生产级依赖包与 TypeScript 类型定义
331
+ npm run build
332
+ ```
333
+
334
+ ---
335
+
336
+ ## 📄 许可证
337
+
338
+ - **`@ohtaman/washi`**: [MIT 许可证](./LICENSE)
339
+ - **HiGHS**(默认求解器): MIT 许可证
340
+ - **Z3**(可选插件): MIT 许可证
341
+ - **GLPK**(可选插件): GPL-3.0 许可证