@ohtaman/washi 0.5.0 → 0.7.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 +103 -386
- package/README.md +104 -387
- package/README.zh-CN.md +103 -386
- package/THIRD_PARTY_LICENSES.md +5 -4
- package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
- package/dist/browser/highs-worker.js +1 -1
- package/dist/browser/highs.wasm +0 -0
- package/dist/browser/highs.wasm.LICENSE +1 -2
- package/dist/browser/loader-DewlSho8.js +643 -0
- package/dist/browser/solvers/glpk.js +3 -3
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +7 -1
- package/dist/browser/solvers/z3.min.js +1 -1
- package/dist/browser/washi.js +1708 -540
- package/dist/browser/washi.min.js +3 -4
- package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/lib/highs-browser-runtime.js +2120 -4124
- package/dist/lib/highs-node-runtime.js +24 -1
- package/dist/lib/highs-runtime.js +2 -0
- package/dist/lib/highs-worker.js +1 -1
- package/dist/lib/highs.wasm +0 -0
- package/dist/lib/highs.wasm.LICENSE +1 -2
- package/dist/lib/loader-BZUQNvh4.js +649 -0
- package/dist/lib/{registry-BEfvpZW3.js → registry-D7TZ3Wh_.js} +142 -59
- package/dist/lib/solvers/glpk.js +4 -4
- package/dist/lib/solvers/z3.js +1 -1
- package/dist/lib/washi.js +1576 -465
- package/dist/types/decomposition/benders.d.ts +32 -0
- package/dist/types/decomposition/benders.d.ts.map +1 -0
- package/dist/types/decomposition/column-generation.d.ts +34 -0
- package/dist/types/decomposition/column-generation.d.ts.map +1 -0
- package/dist/types/decomposition/index.d.ts +4 -0
- package/dist/types/decomposition/index.d.ts.map +1 -0
- package/dist/types/decomposition/types.d.ts +230 -0
- package/dist/types/decomposition/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +11 -0
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +57 -26
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +4 -3
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/linearizer.d.ts +15 -0
- package/dist/types/modeler/linearizer.d.ts.map +1 -0
- package/dist/types/modeler/lp-writer.d.ts +3 -1
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +159 -8
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/parser.d.ts +25 -5
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
- package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/loader.d.ts +4 -4
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/node-runtime.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/highs/worker-client.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +4 -0
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +27 -0
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +15 -14
- package/docs/examples/ecommerce-coupons.md +15 -22
- package/docs/examples/index.md +18 -12
- package/docs/examples/knapsack.md +8 -7
- package/docs/examples/portfolio-qp.md +11 -4
- package/docs/examples/production.md +4 -4
- package/docs/examples/shift-scheduling.md +20 -7
- package/docs/examples/smt-security.md +16 -4
- package/docs/examples/transportation.md +46 -19
- package/docs/examples/traveling-salesperson.md +157 -0
- package/docs/examples/unsat-core.md +15 -5
- package/docs/examples/vehicle-routing.md +208 -0
- package/docs/getting-started/ai-agents.md +33 -18
- package/docs/getting-started/cross-origin.md +56 -28
- package/docs/getting-started/index.md +40 -69
- package/docs/getting-started/runtimes.md +66 -61
- package/docs/guide/constraints.md +47 -41
- package/docs/guide/decomposition.md +267 -0
- package/docs/guide/expressions.md +90 -39
- package/docs/guide/formulation-tips.md +212 -0
- package/docs/guide/objectives.md +7 -4
- package/docs/guide/unsat-core.md +28 -9
- package/docs/guide/variables.md +33 -38
- package/docs/ja/getting-started/ai-agents.md +32 -18
- package/docs/solvers/custom-solver.md +1 -1
- package/docs/solvers/highs.md +48 -5
- package/docs/solvers/index.md +4 -4
- package/docs/solvers/z3.md +3 -0
- package/docs/zh/getting-started/ai-agents.md +32 -18
- package/llms-full.txt +1536 -478
- package/llms.txt +28 -22
- package/package.json +19 -16
- package/skills/washi/SKILL.md +21 -17
- package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
- package/dist/browser/loader-CtaDHef_.js +0 -273
- package/dist/lib/loader-DmNC_UNk.js +0 -279
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
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 -
|
|
5
|
+
<img alt="WaSHI - Mathematical Optimization Modeler in TypeScript" src="./assets/washi-header-light.png" width="420" />
|
|
6
6
|
</picture>
|
|
7
|
-
<p><strong>
|
|
7
|
+
<p><strong>Mathematical Optimization Modeler in TypeScript</strong></p>
|
|
8
8
|
<p>
|
|
9
|
-
<em>Linear Programming (LP) • Mixed-Integer Programming (MIP) • Quadratic Programming (QP) • SMT & Conflict Diagnosis</em>
|
|
9
|
+
<em>Linear Programming (LP) • Mixed-Integer Programming (MIP) • Convex Quadratic Programming (QP) • SMT & Conflict Diagnosis</em>
|
|
10
10
|
</p>
|
|
11
11
|
<p>
|
|
12
12
|
<a href="./README.md">English</a> •
|
|
@@ -16,19 +16,45 @@
|
|
|
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.
|
|
20
|
-
<a href="https://ohtaman.
|
|
19
|
+
<a href="https://washi.ohtaman.dev/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
|
|
20
|
+
<a href="https://washi.ohtaman.dev/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>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) is an algebraic modeler for TypeScript and JavaScript, inspired by Python's **PuLP
|
|
27
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) is an algebraic modeler for mathematical optimization and constraint programming in TypeScript and JavaScript, inspired by Python's **PuLP**. Define variables, objectives, and constraints in application code, and solve them locally in Node.js or the browser using **HiGHS** (default), **Z3**, or **GLPK**.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
## ✨ Key Features
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
- **Natural Algebraic Syntax**: Write expressions and constraints directly with `` expr`30 * ${x} + 50 * ${y} <= 40` ``, `sum`, and `dot`.
|
|
32
|
+
- **Runs in Node.js & Browsers**: Powered by WebAssembly—no Python service or native C/C++ build required.
|
|
33
|
+
- **Solver-Independent**: Use built-in **HiGHS**, optional **Z3** and **GLPK** plugins, or custom solver adapters.
|
|
34
|
+
- **Satisfiability & Conflict Diagnosis**: Check logical/arithmetic feasibility and pinpoint contradictory constraints when a model is infeasible.
|
|
35
|
+
- **Decomposition Methods**: Built-in support for Column Generation and Benders Decomposition on large-scale problems.
|
|
36
|
+
|
|
37
|
+
## 📚 Documentation & Links
|
|
38
|
+
|
|
39
|
+
- **[Documentation](https://washi.ohtaman.dev/)**: Modeling guide, solver references, and deployment guides
|
|
40
|
+
- **[Interactive Showcase](https://washi.ohtaman.dev/showcase/) & [Browser Playground](https://washi.ohtaman.dev/demo/)**: Try live optimization apps and edit models in the browser
|
|
41
|
+
- **[Examples](https://washi.ohtaman.dev/examples/)**: Problem recipes and runnable Node.js / browser scripts ([`examples/`](examples/README.md))
|
|
42
|
+
- **[AI Agent Guide](docs/getting-started/ai-agents.md) · [Agent Skill (`SKILL.md`)](skills/washi/SKILL.md) · [`llms.txt`](https://washi.ohtaman.dev/llms.txt) / [`llms-full.txt`](https://washi.ohtaman.dev/llms-full.txt)**: Compact reference, workflow skill, and machine-readable docs for AI coding agents
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📦 Installation & Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @ohtaman/washi
|
|
50
|
+
|
|
51
|
+
# Optional solver plugins
|
|
52
|
+
npm install z3-solver # For SMT logic & unsat core diagnosis
|
|
53
|
+
npm install glpk.js # For GLPK solver (GPL-3.0)
|
|
54
|
+
|
|
55
|
+
# Optional skill for AI coding agents (Claude Code, Cursor, Codex, etc.)
|
|
56
|
+
npx skills add ohtaman/washi
|
|
57
|
+
```
|
|
32
58
|
|
|
33
59
|
```ts
|
|
34
60
|
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
@@ -47,446 +73,137 @@ console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`
|
|
|
47
73
|
|
|
48
74
|
---
|
|
49
75
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
1. **Algebraic model construction**: Formulate optimization models using domain-specific variable constructors (`m.binary`, `m.integer`, `m.continuous`), unified tagged template literals (`expr`), vector aggregations (`dot`, `sum`), and first-class constraints (`m.constraint`). Because JavaScript lacks operator overloading, `` expr`...` `` parses infix math (`+`, `-`, `*`, `^2`, `<=`, `>=`, `==`) while binding interpolated `${...}` values directly as typed `Var` or expression references—similar to parameterized SQL templates—preserving static type checking, IDE refactoring, and indexed variable lookups (`${flow[i][j]}`).
|
|
53
|
-
2. **Solver adapters**: Use a common modeling API with HiGHS, Z3, or GLPK, or [register your own adapter](https://github.com/ohtaman/washi/blob/main/docs/solvers/custom-solver.md). Choose an engine that supports the model's problem class; the adapters do not make every solver support every feature.
|
|
54
|
-
3. **Local execution and WebAssembly distribution**: With the local solver adapters, solving runs in the application without a separate Python service or native C/C++ build—removing backend provisioning as a bottleneck when shipping interactive tools or combining mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) on the client. Browser deployments must include the generated JS, Worker, and WASM assets. Offline use also requires those resources to be available locally or cached. Z3 has additional browser isolation requirements.
|
|
55
|
-
4. **Named constraints and SMT results**: With Z3, explicitly check satisfiability and inspect conflicting constraints (`conflicts` / `unsatCore`) to investigate infeasibilities. A returned core is a conflicting subset, not necessarily the smallest one.
|
|
76
|
+
## ⚙️ Solvers
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## ⚙️ Solver adapters and capabilities
|
|
62
|
-
|
|
63
|
-
| Adapter | Engine license | Supported problem classes | Engine role | Integration |
|
|
78
|
+
| Solver | License | Problem Classes | Primary Use Case | Setup |
|
|
64
79
|
| :--- | :--- | :--- | :--- | :--- |
|
|
65
|
-
| **HiGHS** |
|
|
66
|
-
| **Z3** |
|
|
67
|
-
| **GLPK** |
|
|
68
|
-
| **Mock** |
|
|
69
|
-
|
|
70
|
-
---
|
|
80
|
+
| **[HiGHS](https://github.com/ERGO-Code/HiGHS)** | MIT | LP, MIP, Convex QP | Numerical optimization & elastic conflict repair | Built-in default |
|
|
81
|
+
| **[Z3](https://github.com/Z3Prover/z3)** | MIT | SMT, SAT, Logic, MIP | Logical rules, satisfiability & exact `unsatCore` | `import "@ohtaman/washi/solvers/z3"` |
|
|
82
|
+
| **[GLPK](https://www.gnu.org/software/glpk/)** | GPL-3.0 | LP, MIP | Linear & mixed-integer optimization | `import "@ohtaman/washi/solvers/glpk"` |
|
|
83
|
+
| **Mock** | MIT | — | Deterministic unit & integration testing | Built-in (`MockSolver`) |
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# npm
|
|
76
|
-
npm install @ohtaman/washi
|
|
77
|
-
|
|
78
|
-
# pnpm
|
|
79
|
-
pnpm add @ohtaman/washi
|
|
80
|
-
|
|
81
|
-
# yarn
|
|
82
|
-
yarn add @ohtaman/washi
|
|
83
|
-
|
|
84
|
-
# bun
|
|
85
|
-
bun add @ohtaman/washi
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
*(Optional)* To use Z3 or GLPK solvers, install their respective packages:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
npm install z3-solver # For SMT and unsat core diagnosis
|
|
92
|
-
npm install glpk.js # For GLPK solver (GPL-3.0)
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
*(Optional)* For AI coding agents (Claude Code, Antigravity, Cursor, Codex, etc.), `@ohtaman/washi` ships a portable [`skills/washi/SKILL.md`](skills/washi/SKILL.md) workflow alongside the [AI Agent Quick-Reference](docs/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
|
-
```
|
|
85
|
+
Pass `{ solver: "z3" }` or `{ solver: "glpk" }` to `new Model(...)`, `m.solve(...)`, or `m.check(...)` after importing the plugin.
|
|
101
86
|
|
|
102
87
|
---
|
|
103
88
|
|
|
104
|
-
## 🚀 Usage
|
|
89
|
+
## 🚀 Usage Examples
|
|
105
90
|
|
|
106
|
-
### 1.
|
|
91
|
+
### 1. Sums, Dot Products (`sum` / `dot`) & Conditional Constraints
|
|
107
92
|
|
|
108
|
-
|
|
93
|
+
Combine indexed variables with `dot` / `sum` and express conditional rules with `m.indicator()`:
|
|
109
94
|
|
|
110
95
|
```ts
|
|
111
|
-
import { Model, expr } from "@ohtaman/washi";
|
|
112
|
-
|
|
113
|
-
const m = new Model("FacilityPlanning");
|
|
114
|
-
|
|
115
|
-
// 1D dictionary of binary variables
|
|
116
|
-
const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
|
|
117
|
-
|
|
118
|
-
// 2D transport flow matrix: [Factory] x [Store]
|
|
119
|
-
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0, ub: 1000 });
|
|
120
|
-
|
|
121
|
-
// 2D coordinate grid (e.g. 8x8 chessboard)
|
|
122
|
-
const board = m.binary(8, 8, { prefix: "queen" });
|
|
123
|
-
|
|
124
|
-
// Conditional Big-M constraint: if openCity["Tokyo"] == 0, flow["F1"]["StoreA"] <= 0
|
|
125
|
-
// (Omit M to let WaSHI automatically derive a tight Big-M from finite variable bounds)
|
|
126
|
-
m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
|
|
127
|
-
activeValue: 0,
|
|
128
|
-
name: "tokyo_closed_no_flow",
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Solve with optional progress callback & AbortSignal cancellation
|
|
132
|
-
const controller = new AbortController();
|
|
133
|
-
await m.solve({
|
|
134
|
-
signal: controller.signal,
|
|
135
|
-
onProgress: (ev) => {
|
|
136
|
-
// Live branch-and-bound metrics: ev.elapsedTime, ev.objectiveValue, ev.objectiveBound, ev.mipGap
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Independently verify variable bounds, integrality, and linear/logical constraints
|
|
141
|
-
const report = m.validateSolution();
|
|
142
|
-
console.log("Solution valid?", report.valid);
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
96
|
+
import { Model, expr, minimize } from "@ohtaman/washi";
|
|
146
97
|
|
|
147
|
-
|
|
98
|
+
const m = new Model("TransportPlanning");
|
|
99
|
+
const factories = ["Tokyo", "Osaka"];
|
|
100
|
+
const stores = ["StoreA", "StoreB"];
|
|
101
|
+
const costs = {
|
|
102
|
+
Tokyo: { StoreA: 4, StoreB: 7 },
|
|
103
|
+
Osaka: { StoreA: 8, StoreB: 3 },
|
|
104
|
+
};
|
|
148
105
|
|
|
149
|
-
|
|
106
|
+
const open = m.binary(factories);
|
|
107
|
+
const ship = m.continuous(factories, stores, { lb: 0, ub: 500 });
|
|
150
108
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const m = new Model("MarkowitzPortfolio");
|
|
155
|
-
const assets = ["Tech", "Bonds", "RealEstate"];
|
|
156
|
-
const returns = [0.14, 0.04, 0.08];
|
|
157
|
-
|
|
158
|
-
// Decision variables: portfolio allocation weights (0 to 1)
|
|
159
|
-
const w = m.continuous(assets, { lb: 0, ub: 1 });
|
|
160
|
-
|
|
161
|
-
// 1. Fully invested budget: sum(w) == 1.0
|
|
162
|
-
m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
|
|
163
|
-
|
|
164
|
-
// 2. Minimum target return: portfolio return >= 8.5%
|
|
165
|
-
const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
|
|
166
|
-
m.constraint(expr`${dot(retMap, w)} >= 0.085`, "min_return");
|
|
167
|
-
|
|
168
|
-
// 3. Covariance risk matrix
|
|
169
|
-
const cov = [
|
|
170
|
-
[0.040, 0.002, 0.015],
|
|
171
|
-
[0.002, 0.005, 0.002],
|
|
172
|
-
[0.015, 0.002, 0.025],
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
// Quadratic objective: w^T * Cov * w
|
|
176
|
-
let risk = new QuadExpr();
|
|
177
|
-
for (let i = 0; i < assets.length; i++) {
|
|
178
|
-
for (let j = 0; j < assets.length; j++) {
|
|
179
|
-
risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
m.objective(minimize(risk));
|
|
109
|
+
// If Tokyo is closed (open["Tokyo"] == 0), total shipment from Tokyo must be 0
|
|
110
|
+
m.indicator(open["Tokyo"], expr`sum(${ship["Tokyo"]}) <= 0`, { activeValue: 0 });
|
|
111
|
+
m.constraint(expr`${ship["Tokyo"]["StoreA"]} + ${ship["Osaka"]["StoreA"]} >= 100`, "demand_A");
|
|
183
112
|
|
|
113
|
+
m.objective(minimize(expr`dot(${costs}, ${ship})`));
|
|
184
114
|
await m.solve();
|
|
185
|
-
console.log(`Minimal Variance: ${m.objectiveValue?.toFixed(6)}`);
|
|
186
|
-
assets.forEach((name) => {
|
|
187
|
-
console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
|
|
188
|
-
});
|
|
189
|
-
```
|
|
190
115
|
|
|
191
|
-
|
|
116
|
+
console.log("Tokyo -> StoreA:", ship["Tokyo"]["StoreA"].value);
|
|
117
|
+
```
|
|
192
118
|
|
|
193
|
-
###
|
|
119
|
+
### 2. Convex Quadratic Programming (QP)
|
|
194
120
|
|
|
195
|
-
|
|
121
|
+
Minimize a quadratic objective subject to linear constraints:
|
|
196
122
|
|
|
197
123
|
```ts
|
|
198
|
-
import { Model,
|
|
199
|
-
import "@ohtaman/washi/solvers/z3";
|
|
200
|
-
|
|
201
|
-
const m = new Model({ name: "SmartSecurityRules" });
|
|
202
|
-
|
|
203
|
-
const isNight = m.bool("isNight");
|
|
204
|
-
const motion = m.bool("motion");
|
|
205
|
-
const alarm = m.bool("alarm");
|
|
206
|
-
const siren = m.bool("siren");
|
|
207
|
-
const lux = m.integer("lux", { lb: 0 });
|
|
124
|
+
import { Model, expr, minimize } from "@ohtaman/washi";
|
|
208
125
|
|
|
209
|
-
|
|
210
|
-
m.
|
|
211
|
-
m.
|
|
126
|
+
const m = new Model("PortfolioQP");
|
|
127
|
+
const x = m.continuous("tech", { lb: 0, ub: 1 });
|
|
128
|
+
const y = m.continuous("bonds", { lb: 0, ub: 1 });
|
|
212
129
|
|
|
213
|
-
|
|
214
|
-
m.constraint(expr
|
|
215
|
-
m.
|
|
216
|
-
m.constraint(alarm, "alarm_on");
|
|
130
|
+
m.constraint(expr`${x} + ${y} == 1`, "budget");
|
|
131
|
+
m.constraint(expr`0.14 * ${x} + 0.04 * ${y} >= 0.08`, "min_return");
|
|
132
|
+
m.objective(minimize(expr`0.04 * ${x}^2 + 0.005 * ${y}^2`));
|
|
217
133
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.log(`Satisfiable? ${res.status === "Sat"}`); // true
|
|
222
|
-
console.log(`Siren active? ${m.values.get("siren")}`); // true
|
|
134
|
+
await m.solve();
|
|
135
|
+
console.log(`Tech: ${x.value?.toFixed(2)}, Bonds: ${y.value?.toFixed(2)}`);
|
|
223
136
|
```
|
|
224
137
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
### 4. Conflict Diagnosis (`conflicts` / `unsatCore`)
|
|
138
|
+
### 3. SMT Logic & Conflict Diagnosis (`m.check`)
|
|
228
139
|
|
|
229
|
-
|
|
140
|
+
Express logical implications and diagnose why a model is infeasible:
|
|
141
|
+
- **HiGHS (default)**: Isolates an **elastic repair set** (`res.conflicts`)—constraints whose relaxation restores feasibility.
|
|
142
|
+
- **Z3**: Extracts an **unsatisfiable core** (`res.unsatCore` / `res.conflicts`)—a mutually contradictory subset of constraints.
|
|
230
143
|
|
|
231
144
|
```ts
|
|
232
|
-
import { Model, expr } from "@ohtaman/washi";
|
|
233
|
-
import "@ohtaman/washi/solvers/z3";
|
|
145
|
+
import { Model, implies, expr } from "@ohtaman/washi";
|
|
146
|
+
// import "@ohtaman/washi/solvers/z3"; // Optional: for exact SMT unsatCore
|
|
234
147
|
|
|
235
|
-
const m = new Model(
|
|
148
|
+
const m = new Model("StaffingRules");
|
|
236
149
|
const engineers = m.integer("engineers", { lb: 0, ub: 20 });
|
|
237
150
|
const budget = m.continuous("budget", { lb: 0, ub: 100000 });
|
|
238
|
-
const
|
|
151
|
+
const rush = m.bool("rush");
|
|
239
152
|
|
|
240
|
-
// Rule 1: Each engineer costs $8,000
|
|
241
153
|
m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
154
|
+
m.constraint(rush, "rush_required");
|
|
155
|
+
m.constraint(implies(rush, expr`${engineers} >= 8`), "rush_staffing");
|
|
156
|
+
m.constraint(expr`${budget} <= 50000`, "budget_cap"); // Conflict: 8 * $8,000 > $50,000
|
|
157
|
+
m.constraint(expr`${engineers} >= 1`, "min_staff"); // Unrelated rule
|
|
242
158
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// Rule 3: Rush delivery requires at least 8 engineers
|
|
247
|
-
m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
|
|
248
|
-
|
|
249
|
-
// Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
|
|
250
|
-
m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
251
|
-
|
|
252
|
-
// Rule 5: Innocent bystander rule
|
|
253
|
-
m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
|
|
254
|
-
|
|
255
|
-
const res = await m.check({ solver: "z3" });
|
|
256
|
-
|
|
257
|
-
console.log(`Status: ${res.status}`); // "Unsat"
|
|
258
|
-
console.log("Conflicting constraints:", res.conflicts);
|
|
259
|
-
// Output: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
|
|
260
|
-
// Note: "minimum_one_employee" is correctly excluded!
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
### 5. Solving with GLPK (GPL-3.0)
|
|
266
|
-
|
|
267
|
-
To use the GNU Linear Programming Kit (GLPK) solver plugin:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
271
|
-
import "@ohtaman/washi/solvers/glpk";
|
|
272
|
-
|
|
273
|
-
const m = new Model({ solver: "glpk" });
|
|
274
|
-
const x = m.integer("x", { lb: 0 });
|
|
275
|
-
const y = m.integer("y", { lb: 0 });
|
|
276
|
-
|
|
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");
|
|
280
|
-
|
|
281
|
-
const status = await m.solve();
|
|
282
|
-
console.log(`Status: ${status}`); // "Optimal"
|
|
283
|
-
console.log(`Objective: ${m.objectiveValue}`); // 1100
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
### 6. Vector Operations & Fast Dot Product
|
|
289
|
-
|
|
290
|
-
Cleanly compute 1D/2D linear combinations without nested loops:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
import { Model, dot, minimize } from "@ohtaman/washi";
|
|
294
|
-
|
|
295
|
-
const m = new Model();
|
|
296
|
-
const factories = ["Tokyo", "Osaka"];
|
|
297
|
-
const stores = ["StoreA", "StoreB"];
|
|
298
|
-
|
|
299
|
-
const costs = {
|
|
300
|
-
Tokyo: { StoreA: 4, StoreB: 7 },
|
|
301
|
-
Osaka: { StoreA: 8, StoreB: 3 },
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
const ship = m.continuous(factories, stores);
|
|
305
|
-
|
|
306
|
-
// Total transportation cost in one line:
|
|
307
|
-
m.objective(minimize(dot(costs, ship)));
|
|
308
|
-
await m.solve();
|
|
309
|
-
|
|
310
|
-
// Extract all optimal variable values as a structured object
|
|
311
|
-
const solution = m.getValues(ship);
|
|
312
|
-
console.log(solution);
|
|
159
|
+
const res = await m.check(); // Or: await m.check({ solver: "z3" })
|
|
160
|
+
console.log(res.status); // "Unsat"
|
|
161
|
+
console.log(res.conflicts); // HiGHS: ["rush_required"]
|
|
313
162
|
```
|
|
314
163
|
|
|
315
164
|
---
|
|
316
165
|
|
|
317
166
|
## 🌐 Browser & Web Worker Usage
|
|
318
167
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
### npm with a bundler
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
import { Model } from "@ohtaman/washi";
|
|
325
|
-
const model = new Model();
|
|
326
|
-
// Define variables, objective and constraints.
|
|
327
|
-
await model.solve();
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
No manual WASM copy or URL configuration is needed in the tested Vite / webpack builds. Other bundlers and SSR need their own integration checks; call the browser solver on the client. Worker execution is the browser default. Use `model.solve({ useWorker: false })` for a simple main-thread demo; a long solve will block the UI.
|
|
331
|
-
|
|
332
|
-
### Browser ESM without an application build
|
|
333
|
-
|
|
334
|
-
Serve the **entire `dist/browser` directory**, including its JS chunks, `highs.wasm` and Worker. No import map is needed:
|
|
168
|
+
- **Bundlers (Vite / webpack 5)**: `import { Model } from "@ohtaman/washi"` works out of the box. Solves run in a Web Worker by default; pass `{ useWorker: false }` for main-thread execution.
|
|
169
|
+
- **Standalone ESM / Script Tag / CDN**: Serve `dist/browser/` (including `highs.wasm` and worker chunks) or load from a CDN.
|
|
335
170
|
|
|
336
171
|
```html
|
|
172
|
+
<!-- Browser ESM -->
|
|
337
173
|
<script type="module">
|
|
338
174
|
import { Model } from "./washi/washi.js";
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
await
|
|
175
|
+
// Optional solver plugins: import "./washi/solvers/glpk.js" or "./washi/solvers/z3.js"
|
|
176
|
+
const m = new Model();
|
|
177
|
+
await m.solve();
|
|
342
178
|
</script>
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Plain script / CodePen
|
|
346
179
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
```html
|
|
350
|
-
<script src="./washi/washi.min.js"></script>
|
|
351
|
-
<script>
|
|
352
|
-
const model = new Washi.Model();
|
|
353
|
-
// Define variables, objective and constraints.
|
|
354
|
-
model.solve({ useWorker: false }).then(status => console.log(status));
|
|
355
|
-
</script>
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js` (specifying a compatible release version once published). For ESM use the corresponding `dist/browser/washi.js` URL. Selecting the CDN URL is an explicit choice; local copies never switch to a CDN.
|
|
359
|
-
|
|
360
|
-
### Solver Plugins in the Browser (GLPK & Z3)
|
|
361
|
-
|
|
362
|
-
The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/`:
|
|
363
|
-
- **GLPK**: `dist/browser/solvers/glpk.js` (ESM) and `dist/browser/solvers/glpk.min.js` (IIFE, registers global `WashiGLPK`)
|
|
364
|
-
- **Z3**: `dist/browser/solvers/z3.js` (ESM) and `dist/browser/solvers/z3.min.js` (IIFE, registers global `WashiZ3`), accompanied by `z3-built.js` and `z3-built.wasm`
|
|
365
|
-
|
|
366
|
-
#### Browser ESM with GLPK
|
|
367
|
-
```html
|
|
368
|
-
<script type="module">
|
|
369
|
-
import { Model, maximize } from "./washi/washi.js";
|
|
370
|
-
import "./washi/solvers/glpk.js";
|
|
371
|
-
|
|
372
|
-
const model = new Model({ solver: "glpk" });
|
|
373
|
-
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
374
|
-
model.objective(maximize(x));
|
|
375
|
-
await model.solve();
|
|
376
|
-
console.log("GLPK status:", model.status);
|
|
377
|
-
</script>
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
#### Plain Script with GLPK
|
|
381
|
-
```html
|
|
382
|
-
<script src="./washi/washi.min.js"></script>
|
|
383
|
-
<script src="./washi/solvers/glpk.min.js"></script>
|
|
384
|
-
<script>
|
|
385
|
-
const model = new Washi.Model({ solver: "glpk" });
|
|
386
|
-
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
387
|
-
model.objective(Washi.maximize(x));
|
|
388
|
-
model.solve().then(status => console.log("GLPK status:", status));
|
|
389
|
-
</script>
|
|
180
|
+
<!-- Classic Script / CodePen (exposes global Washi) -->
|
|
181
|
+
<script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi/dist/browser/washi.min.js"></script>
|
|
390
182
|
```
|
|
391
183
|
|
|
392
184
|
> [!NOTE]
|
|
393
|
-
> **Z3 in Browser
|
|
394
|
-
> Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`). See the [Cross-Origin Isolation Guide](docs/getting-started/cross-origin.md) and [Runtime Environments Guide](docs/getting-started/runtimes.md) for full details on static hosting, `coi-serviceworker.js`, offline/intranet deployment, CSP (`worker-src blob:`), and custom WASM URLs (`setHighsWasmUrl`).
|
|
395
|
-
|
|
396
|
-
Complete standalone examples for ESM, script tags, and CodePen are available in [`examples/distribution/`](examples/distribution/README.md).
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## 🛠️ Custom Solvers & Extensibility
|
|
401
|
-
|
|
402
|
-
WaSHI features a decoupled, pluggable solver architecture. You can easily build and register your own custom solver adapter (e.g., for cloud REST optimizers, WebAssembly engines, or proprietary solvers):
|
|
403
|
-
|
|
404
|
-
- See the complete [Custom Solver Developer Guide](docs/solvers/custom-solver.md) for step-by-step tutorials, complete API contracts (`Solver`, `SolveModelData`, `CheckModelData`, `SolverResult`), and testing best practices.
|
|
185
|
+
> **Z3 in the Browser**: Because Z3's WebAssembly build uses `SharedArrayBuffer`, browsers require Cross-Origin Isolation (`COOP: same-origin` and `COEP: require-corp`). See the [Cross-Origin Isolation Guide](docs/getting-started/cross-origin.md) and [`examples/distribution/`](examples/distribution/README.md).
|
|
405
186
|
|
|
406
187
|
---
|
|
407
188
|
|
|
408
|
-
##
|
|
189
|
+
## 🤝 Contributing
|
|
409
190
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
- **Getting Started**: [Quick Start](docs/getting-started/index.md) · [AI Agent Quick-Reference](docs/getting-started/ai-agents.md) · [Runtime Environments](docs/getting-started/runtimes.md) · [Cross-Origin Isolation (Z3)](docs/getting-started/cross-origin.md)
|
|
413
|
-
- **Modeling Guide**: [Variables (`Var`, `BoolVar`)](docs/guide/variables.md) · [Expressions (`expr`, `sum`, `dot`)](docs/guide/expressions.md) · [Constraints & Logic (`indicator`, SMT)](docs/guide/constraints.md) · [Objectives & Solving (`solve`, `validateSolution`)](docs/guide/objectives.md) · [Feasibility & Unsat Core (`check`)](docs/guide/unsat-core.md)
|
|
414
|
-
- **Solvers**: [Solver Overview](docs/solvers/index.md) · [HiGHS](docs/solvers/highs.md) · [Z3](docs/solvers/z3.md) · [GLPK](docs/solvers/glpk.md) · [Custom Solvers](docs/solvers/custom-solver.md)
|
|
191
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development setup, running tests and benchmarks, rebuilding WebAssembly assets, and the release workflow.
|
|
415
192
|
|
|
416
193
|
---
|
|
417
194
|
|
|
418
|
-
##
|
|
419
|
-
|
|
420
|
-
Start with the [example gallery](https://github.com/ohtaman/washi/blob/main/examples/README.md) or the [online Example & Feature Finder](https://ohtaman.github.io/washi/examples/), which organize examples by problem category and by WaSHI API feature.
|
|
195
|
+
## 🙏 Acknowledgments
|
|
421
196
|
|
|
422
|
-
|
|
423
|
-
| :--- | :--- | :--- | :--- |
|
|
424
|
-
| **Fundamentals** | [Production planning](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | Integer variables, a profit objective, and resource constraints (`expr`) | HiGHS |
|
|
425
|
-
| **Fundamentals** | [Transportation network](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 2D origin-destination variable matrix (`continuous(rows, cols)`) and flow balance | HiGHS |
|
|
426
|
-
| **Fundamentals** | [Diet & sensitivity analysis](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | Shadow prices (`constr.dual`), surpluses (`constr.slack`), and reduced costs (`var.reducedCost`) | HiGHS |
|
|
427
|
-
| **Practical** | [Staff shift scheduling](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | Weekly roster optimization + diagnosing conflicting Saturday vacation requests | HiGHS + Z3 |
|
|
428
|
-
| **Practical** | [E-commerce coupon optimizer](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | Conditional spend thresholds (`indicator`), coupon exclusivity, and add-on recommendations | HiGHS |
|
|
429
|
-
| **Practical** | [Portfolio allocation](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) | A convex quadratic objective (`QuadExpr`) with allocation constraints | HiGHS |
|
|
430
|
-
| **Logic / SMT** | [Business rule checks](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | Arithmetic and Boolean constraints (`bool`, `implies`) with satisfiability checking | Z3 |
|
|
431
|
-
| **Logic / SMT** | [Conflict diagnosis](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | Named constraints and an unsatisfiable core (`unsatCore`) | Z3 |
|
|
432
|
-
|
|
433
|
-
[Open the hosted demo](https://ohtaman.github.io/washi/demo/) to edit and run models in your browser. For browser ESM, classic script tags, and CodePen, see the [distribution examples](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md). Z3 requires cross-origin isolation; see the browser requirements above.
|
|
434
|
-
|
|
435
|
-
To run examples locally, clone the repository and install its development dependencies first:
|
|
436
|
-
|
|
437
|
-
```bash
|
|
438
|
-
git clone https://github.com/ohtaman/washi.git
|
|
439
|
-
cd washi
|
|
440
|
-
npm ci
|
|
441
|
-
npx tsx examples/node/01-production-planning.ts
|
|
442
|
-
npx tsx examples/node/09-transportation-problem.ts
|
|
443
|
-
npx tsx examples/node/10-diet-sensitivity.ts
|
|
444
|
-
npx tsx examples/node/11-shift-scheduling.ts
|
|
445
|
-
npx tsx examples/node/12-ecommerce-coupon-optimizer.ts
|
|
446
|
-
|
|
447
|
-
# Local browser demo: http://localhost:5173/demo/
|
|
448
|
-
npm run dev
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
The example sources import the repository's `src/` modules. In an application using the published package, import `@ohtaman/washi` and the required solver plugin instead.
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
## 🧪 Testing & Reproducible Benchmarks
|
|
456
|
-
|
|
457
|
-
Run the repository's unit and integration tests, and build the distributions:
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
# Run all unit and integration tests
|
|
461
|
-
npm test
|
|
462
|
-
|
|
463
|
-
# Build production bundle and TypeScript declarations
|
|
464
|
-
npm run build
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### Optional Reproducible Benchmarks
|
|
468
|
-
|
|
469
|
-
To measure modeling and solver phases on deterministic `small-lp` (24 variables, 18 constraints) and `scheduling-mip` (315 variables, 231 constraints, including rebuilt `initialValue` warm starts) scenarios without mixing performance timings into correctness test thresholds:
|
|
470
|
-
|
|
471
|
-
```bash
|
|
472
|
-
# Run Node.js in-process WASM benchmarks (HiGHS and GLPK, 5 warm iterations by default)
|
|
473
|
-
npm run bench
|
|
474
|
-
|
|
475
|
-
# Customize iteration count or emit structured JSON
|
|
476
|
-
node scripts/run-benchmarks.mjs --iterations=10 --json
|
|
477
|
-
|
|
478
|
-
# Also measure headless Chromium Web Worker execution (requires Playwright Chromium)
|
|
479
|
-
node scripts/run-benchmarks.mjs --browser
|
|
480
|
-
```
|
|
197
|
+
WaSHI stands on the shoulders of the open-source mathematical optimization and automated reasoning communities:
|
|
481
198
|
|
|
482
|
-
- **
|
|
483
|
-
- **
|
|
199
|
+
- **[HiGHS](https://highs.dev/)** ([`ERGO-Code/HiGHS`](https://github.com/ERGO-Code/HiGHS)) — High-performance open-source optimization software for LP, MIP, and Convex QP initiated at the University of Edinburgh by Julian Hall, Qi Huangfu, Ivet Galabova, Leona Gottwald, Michael Feldmeier, and contributors, which powers WaSHI's built-in default solver (*Huangfu & Hall, Mathematical Programming Computation, 2018*, [DOI: 10.1007/s12532-017-0130-5](https://doi.org/10.1007/s12532-017-0130-5)).
|
|
200
|
+
- **[Z3 Theorem Prover](https://github.com/Z3Prover/z3)** ([`z3-solver`](https://www.npmjs.com/package/z3-solver)) — Developed at Microsoft Research, powering WaSHI's optional SMT satisfiability and exact `unsatCore` conflict diagnosis plugin.
|
|
201
|
+
- **[GLPK](https://www.gnu.org/software/glpk/) & [`glpk.js`](https://github.com/jvail/glpk.js)** — Powering WaSHI's optional GPL-3.0 LP/MIP solver plugin.
|
|
202
|
+
- **[PuLP](https://github.com/coin-or/pulp)** — Inspiring WaSHI's declarative algebraic modeling ergonomics.
|
|
484
203
|
|
|
485
204
|
---
|
|
486
205
|
|
|
487
206
|
## 📄 License
|
|
488
207
|
|
|
489
208
|
- **`@ohtaman/washi`**: [MIT License](./LICENSE)
|
|
490
|
-
- **HiGHS
|
|
491
|
-
- **Z3** (optional plugin): MIT License
|
|
492
|
-
- **GLPK** (optional plugin): GPL-3.0 License
|
|
209
|
+
- **Bundled / Optional Engines**: HiGHS (MIT), Z3 (MIT), GLPK (GPL-3.0). See [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) for full details.
|