@ohtaman/washi 0.6.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.
Files changed (59) hide show
  1. package/README.ja.md +103 -421
  2. package/README.md +103 -421
  3. package/README.zh-CN.md +103 -421
  4. package/THIRD_PARTY_LICENSES.md +5 -3
  5. package/dist/browser/solvers/glpk.js +2 -2
  6. package/dist/browser/solvers/glpk.min.js +1 -1
  7. package/dist/browser/washi.js +777 -261
  8. package/dist/browser/washi.min.js +3 -3
  9. package/dist/lib/{registry-C20sxTwm.js → registry-D7TZ3Wh_.js} +88 -18
  10. package/dist/lib/solvers/glpk.js +3 -3
  11. package/dist/lib/solvers/z3.js +1 -1
  12. package/dist/lib/washi.js +864 -421
  13. package/dist/types/index.d.ts +1 -1
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/modeler/expression.d.ts +47 -26
  16. package/dist/types/modeler/expression.d.ts.map +1 -1
  17. package/dist/types/modeler/index.d.ts +2 -2
  18. package/dist/types/modeler/index.d.ts.map +1 -1
  19. package/dist/types/modeler/model.d.ts +12 -6
  20. package/dist/types/modeler/model.d.ts.map +1 -1
  21. package/dist/types/modeler/parser.d.ts +25 -5
  22. package/dist/types/modeler/parser.d.ts.map +1 -1
  23. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  24. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  25. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  26. package/dist/types/modeler/solvers/types.d.ts +2 -0
  27. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  28. package/docs/examples/diet-sensitivity.md +15 -14
  29. package/docs/examples/ecommerce-coupons.md +15 -22
  30. package/docs/examples/index.md +14 -12
  31. package/docs/examples/knapsack.md +8 -7
  32. package/docs/examples/portfolio-qp.md +11 -4
  33. package/docs/examples/production.md +4 -4
  34. package/docs/examples/shift-scheduling.md +20 -7
  35. package/docs/examples/smt-security.md +16 -4
  36. package/docs/examples/transportation.md +46 -19
  37. package/docs/examples/traveling-salesperson.md +28 -44
  38. package/docs/examples/unsat-core.md +15 -5
  39. package/docs/examples/vehicle-routing.md +10 -8
  40. package/docs/getting-started/ai-agents.md +25 -14
  41. package/docs/getting-started/cross-origin.md +56 -28
  42. package/docs/getting-started/index.md +40 -69
  43. package/docs/getting-started/runtimes.md +66 -61
  44. package/docs/guide/constraints.md +46 -40
  45. package/docs/guide/decomposition.md +127 -259
  46. package/docs/guide/expressions.md +90 -39
  47. package/docs/guide/formulation-tips.md +212 -0
  48. package/docs/guide/objectives.md +7 -4
  49. package/docs/guide/unsat-core.md +18 -6
  50. package/docs/guide/variables.md +33 -38
  51. package/docs/ja/getting-started/ai-agents.md +24 -14
  52. package/docs/solvers/highs.md +27 -6
  53. package/docs/solvers/index.md +1 -1
  54. package/docs/solvers/z3.md +3 -0
  55. package/docs/zh/getting-started/ai-agents.md +24 -14
  56. package/llms-full.txt +929 -687
  57. package/llms.txt +21 -19
  58. package/package.json +3 -3
  59. package/skills/washi/SKILL.md +14 -10
package/README.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 - Algebraic Modeler for Mathematical Optimization &amp; Constraint Programming" src="./assets/washi-header-light.png" width="420" />
5
+ <img alt="WaSHI - Mathematical Optimization Modeler in TypeScript" src="./assets/washi-header-light.png" width="420" />
6
6
  </picture>
7
- <p><strong>Algebraic Modeler for Mathematical Optimization &amp; Constraint Programming</strong></p>
7
+ <p><strong>Mathematical Optimization Modeler in TypeScript</strong></p>
8
8
  <p>
9
9
  <em>Linear Programming (LP) • Mixed-Integer Programming (MIP) • Convex Quadratic Programming (QP) • SMT &amp; Conflict Diagnosis</em>
10
10
  </p>
@@ -16,512 +16,194 @@
16
16
  <p>
17
17
  <a href="https://www.npmjs.com/package/@ohtaman/washi"><img src="https://img.shields.io/npm/v/@ohtaman/washi?color=crimson&label=npm" alt="npm version" /></a>
18
18
  <a href="https://github.com/ohtaman/washi/actions/workflows/distribution.yml"><img src="https://github.com/ohtaman/washi/actions/workflows/distribution.yml/badge.svg" alt="CI Status" /></a>
19
- <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
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://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 mathematical optimization and constraint programming in TypeScript and JavaScript, inspired by Python's **PuLP** and Julia's **JuMP**. Define variables, objectives, and arithmetic or logical constraints in application code. WaSHI constructs the model, translates it for a solver adapter, and maps results back to your variables. **HiGHS**, **Z3**, and **GLPK** perform the optimization or satisfiability checks.
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
- HiGHS is the default solver; Z3 and GLPK are optional plugins. The package provides Node.js and browser distributions, including WebAssembly assets. See the [browser integration guide](#-browser--web-worker-usage) for deployment requirements.
29
+ ## ✨ Key Features
30
30
 
31
- [Documentation](https://ohtaman.github.io/washi/) · [Try the browser demo](https://ohtaman.github.io/washi/demo/) · [AI Agent Quick-Reference](docs/getting-started/ai-agents.md) · [Agent Skill (`SKILL.md`)](skills/washi/SKILL.md) · [Browse example source code](https://github.com/ohtaman/washi/blob/main/examples/README.md) · [ESM / script / CodePen examples](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)
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.
32
36
 
33
- ```ts
34
- import { Model, expr, maximize } from "@ohtaman/washi";
35
-
36
- const m = new Model("WoodworkingProfit");
37
- const x = m.integer("desks", { lb: 0 });
38
- const y = m.integer("chairs", { lb: 0 });
39
-
40
- m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
41
- m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
42
- m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
43
-
44
- await m.solve();
45
- console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
46
- ```
47
-
48
- ---
49
-
50
- ## 🎯 What WaSHI provides
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 conflict diagnosis**: Explicitly check satisfiability (`m.check()`) and inspect conflicting constraints (`conflicts` / `unsatCore`) to investigate infeasibilities: built-in HiGHS isolates an **elastic repair set** (`conflicts`, constraints requiring relaxation to restore feasibility), while Z3 extracts an **unsatisfiable core** (`unsatCore` / `conflicts`, a mutually contradictory constraint subset; minimality is not guaranteed).
56
- 5. **Advanced decomposition methods**: Overcome combinatorial bottlenecks in monolithic MIP formulations (cutting stock, vehicle routing/VRP, traveling salesperson/TSP, and two-stage facility planning) using first-class decomposition orchestrators (`master.solveColumnGeneration`, `master.solveBenders`). WaSHI coordinates dual shadow prices, pricing subproblems, and Benders cut generation via clean callback orchestrators and linking constraints (`sub.link(...)`).
57
-
58
- Model construction and translation are WaSHI's responsibility. Supported problem classes, numerical behavior, and solve time depend on the selected solver, its settings, and the problem. Modeling overhead and solver execution time should be measured separately; a result for one model is not a general performance guarantee.
37
+ ## 📚 Documentation & Links
59
38
 
60
- ---
61
-
62
- ## ⚙️ Solver adapters and capabilities
63
-
64
- | Adapter | Engine license | Supported problem classes | Engine role | Integration |
65
- | :--- | :--- | :--- | :--- | :--- |
66
- | **[HiGHS](https://github.com/ERGO-Code/HiGHS)** | **MIT** | **LP, MIP, convex QP** | Numerical optimization | Built-in default |
67
- | **[Z3](https://github.com/Z3Prover/z3)** (via [`z3-solver`](https://www.npmjs.com/package/z3-solver)) | **MIT** | **SMT, SAT, logic, MIP** | Satisfiability checks, unsatisfiable cores, optimization | Optional plugin (`@ohtaman/washi/solvers/z3`) |
68
- | **[GLPK](https://www.gnu.org/software/glpk/)** (via [`glpk.js`](https://www.npmjs.com/package/glpk.js)) | **GPL-3.0** | **LP, MIP** | Linear and mixed-integer optimization | Optional plugin (`@ohtaman/washi/solvers/glpk`) |
69
- | **Mock** | **MIT** | — | Simulated solver responses for integration tests | Built-in; does not solve the model |
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
70
43
 
71
44
  ---
72
45
 
73
- ## 📦 Installation
46
+ ## 📦 Installation & Quick Start
74
47
 
75
48
  ```bash
76
- # npm
77
49
  npm install @ohtaman/washi
78
50
 
79
- # pnpm
80
- pnpm add @ohtaman/washi
81
-
82
- # yarn
83
- yarn add @ohtaman/washi
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)
84
54
 
85
- # bun
86
- bun add @ohtaman/washi
87
- ```
88
-
89
- *(Optional)* To use Z3 or GLPK solvers, install their respective packages:
90
-
91
- ```bash
92
- npm install z3-solver # For SMT and unsat core diagnosis
93
- npm install glpk.js # For GLPK solver (GPL-3.0)
94
- ```
95
-
96
- *(Optional)* For AI coding agents (Claude Code, Antigravity, Cursor, Codex, etc.), install the portable [`skills/washi/SKILL.md`](skills/washi/SKILL.md) workflow directly via the `skills` CLI or copy it from the installed package:
97
-
98
- ```bash
99
- # Install via the open skills CLI (skills.sh / vercel-labs/skills)
55
+ # Optional skill for AI coding agents (Claude Code, Cursor, Codex, etc.)
100
56
  npx skills add ohtaman/washi
101
-
102
- # Or manually copy the packaged skill into your project's agent directory
103
- mkdir -p .agents/skills/washi
104
- cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
105
57
  ```
106
58
 
107
- ---
108
-
109
- ## 🚀 Usage Guide
110
-
111
- ### 1. Multidimensional Variables, Indicator Constraints & Solution Validation
112
-
113
- Construct 1D dictionaries or 2D matrices of variables in a single call, express conditional Big-M logic with `m.indicator()`, monitor live progress (`onProgress`), and independently verify solutions with `m.validateSolution()`:
114
-
115
59
  ```ts
116
- import { Model, expr } from "@ohtaman/washi";
117
-
118
- const m = new Model("FacilityPlanning");
119
-
120
- // 1D dictionary of binary variables
121
- const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
122
-
123
- // 2D transport flow matrix: [Factory] x [Store]
124
- const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0, ub: 1000 });
125
-
126
- // 2D coordinate grid (e.g. 8x8 chessboard)
127
- const board = m.binary(8, 8, { prefix: "queen" });
128
-
129
- // Conditional Big-M constraint: if openCity["Tokyo"] == 0, flow["F1"]["StoreA"] <= 0
130
- // (Omit M to let WaSHI automatically derive a tight Big-M from finite variable bounds)
131
- m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
132
- activeValue: 0,
133
- name: "tokyo_closed_no_flow",
134
- });
135
-
136
- // Solve with optional progress callback & AbortSignal cancellation
137
- const controller = new AbortController();
138
- await m.solve({
139
- signal: controller.signal,
140
- onProgress: (ev) => {
141
- // Live branch-and-bound metrics: ev.elapsedTime, ev.objectiveValue, ev.objectiveBound, ev.mipGap
142
- },
143
- });
144
-
145
- // Independently verify variable bounds, integrality, and linear/logical constraints
146
- const report = m.validateSolution();
147
- console.log("Solution valid?", report.valid);
148
- ```
149
-
150
- ---
151
-
152
- ### 2. Portfolio Risk Minimization (Convex Quadratic Programming)
60
+ import { Model, expr, maximize } from "@ohtaman/washi";
153
61
 
154
- Minimize investment risk based on a covariance matrix subject to return and budget constraints:
62
+ const m = new Model("WoodworkingProfit");
63
+ const x = m.integer("desks", { lb: 0 });
64
+ const y = m.integer("chairs", { lb: 0 });
155
65
 
156
- ```ts
157
- import { Model, QuadExpr, sum, dot, minimize, expr } from "@ohtaman/washi";
158
-
159
- const m = new Model("MarkowitzPortfolio");
160
- const assets = ["Tech", "Bonds", "RealEstate"];
161
- const returns = [0.14, 0.04, 0.08];
162
-
163
- // Decision variables: portfolio allocation weights (0 to 1)
164
- const w = m.continuous(assets, { lb: 0, ub: 1 });
165
-
166
- // 1. Fully invested budget: sum(w) == 1.0
167
- m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
168
-
169
- // 2. Minimum target return: portfolio return >= 8.5%
170
- const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
171
- m.constraint(expr`${dot(retMap, w)} >= 0.085`, "min_return");
172
-
173
- // 3. Covariance risk matrix
174
- const cov = [
175
- [0.040, 0.002, 0.015],
176
- [0.002, 0.005, 0.002],
177
- [0.015, 0.002, 0.025],
178
- ];
179
-
180
- // Quadratic objective: w^T * Cov * w
181
- let risk = new QuadExpr();
182
- for (let i = 0; i < assets.length; i++) {
183
- for (let j = 0; j < assets.length; j++) {
184
- risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
185
- }
186
- }
187
- m.objective(minimize(risk));
66
+ m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
67
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
68
+ m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
188
69
 
189
70
  await m.solve();
190
- console.log(`Minimal Variance: ${m.objectiveValue?.toFixed(6)}`);
191
- assets.forEach((name) => {
192
- console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
193
- });
194
- ```
195
-
196
- ---
197
-
198
- ### 3. SMT Logic & Rule Engines (with Z3)
199
-
200
- Express boolean variables, propositional connectives (`and`, `or`, `implies`, `not`, `xor`), and mixed arithmetic rules:
201
-
202
- ```ts
203
- import { Model, not, expr } from "@ohtaman/washi";
204
- import "@ohtaman/washi/solvers/z3";
205
-
206
- const m = new Model({ name: "SmartSecurityRules" });
207
-
208
- const isNight = m.bool("isNight");
209
- const motion = m.bool("motion");
210
- const alarm = m.bool("alarm");
211
- const siren = m.bool("siren");
212
- const lux = m.integer("lux", { lb: 0 });
213
-
214
- // Logical rules
215
- m.constraint(lux.le(50).implies(isNight), "dark_implies_night");
216
- m.constraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
217
-
218
- // Current sensor observations
219
- m.constraint(expr`${lux} <= 10`, "sensor_dark");
220
- m.constraint(motion, "sensor_motion");
221
- m.constraint(alarm, "alarm_on");
222
-
223
- // Check satisfiability and deduce state without an objective function
224
- const res = await m.check({ solver: "z3" });
225
-
226
- console.log(`Satisfiable? ${res.status === "Sat"}`); // true
227
- console.log(`Siren active? ${m.values.get("siren")}`); // true
71
+ console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
228
72
  ```
229
73
 
230
74
  ---
231
75
 
232
- ### 4. Conflict Diagnosis (`conflicts` / `unsatCore`)
233
-
234
- When business rules or constraints contradict each other, diagnose infeasibilities to pinpoint where to intervene:
235
- - **Built-in HiGHS & GLPK**: `await m.check()` uses a single-solve elastic slack formulation ($\min \sum s_i$) to isolate `res.conflicts`—the **repair set** of constraints whose relaxation restores feasibility, without requiring extra dependencies.
236
- - **Z3**: `await m.check({ solver: "z3" })` provides exact SMT **unsatisfiable-core** extraction (`res.unsatCore` and `res.conflicts`), returning a mutually contradictory constraint subset.
237
-
238
- ```ts
239
- import { Model, expr } from "@ohtaman/washi";
240
-
241
- const m = new Model("ConflictDiagnosis");
242
- const engineers = m.integer("engineers", { lb: 0, ub: 20 });
243
- const budget = m.continuous("budget", { lb: 0, ub: 100000 });
244
- const rushDelivery = m.bool("rushDelivery");
245
-
246
- // Rule 1: Each engineer costs $8,000
247
- m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
248
-
249
- // Rule 2: Customer requires rush delivery
250
- m.constraint(rushDelivery, "rush_required");
251
-
252
- // Rule 3: Rush delivery requires at least 8 engineers
253
- m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
254
-
255
- // Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
256
- m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
257
-
258
- // Rule 5: Innocent bystander rule
259
- m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
76
+ ## ⚙️ Solvers
260
77
 
261
- // 1. Elastic repair diagnosis with default HiGHS
262
- const res = await m.check();
263
-
264
- console.log(`Status: ${res.status}`); // "Unsat"
265
- console.log("Repair set (relaxed constraints):", res.conflicts);
266
- // Output with HiGHS: ["rush_required"]
267
- // (Relaxing rush_required restores feasibility; innocent rules like minimum_one_employee are untouched)
78
+ | Solver | License | Problem Classes | Primary Use Case | Setup |
79
+ | :--- | :--- | :--- | :--- | :--- |
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`) |
268
84
 
269
- // 2. Or exact SMT unsatisfiable core with Z3 (import "@ohtaman/washi/solvers/z3")
270
- // const z3Res = await m.check({ solver: "z3" });
271
- // console.log("Contradictory core:", z3Res.unsatCore);
272
- // Output with Z3: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
273
- ```
85
+ Pass `{ solver: "z3" }` or `{ solver: "glpk" }` to `new Model(...)`, `m.solve(...)`, or `m.check(...)` after importing the plugin.
274
86
 
275
87
  ---
276
88
 
277
- ### 5. Solving with GLPK (GPL-3.0)
278
-
279
- To use the GNU Linear Programming Kit (GLPK) solver plugin:
280
-
281
- ```ts
282
- import { Model, expr, maximize } from "@ohtaman/washi";
283
- import "@ohtaman/washi/solvers/glpk";
89
+ ## 🚀 Usage Examples
284
90
 
285
- const m = new Model({ solver: "glpk" });
286
- const x = m.integer("x", { lb: 0 });
287
- const y = m.integer("y", { lb: 0 });
91
+ ### 1. Sums, Dot Products (`sum` / `dot`) & Conditional Constraints
288
92
 
289
- m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
290
- m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
291
- m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
292
-
293
- const status = await m.solve();
294
- console.log(`Status: ${status}`); // "Optimal"
295
- console.log(`Objective: ${m.objectiveValue}`); // 1100
296
- ```
297
-
298
- ---
299
-
300
- ### 6. Vector Operations & Fast Dot Product
301
-
302
- Cleanly compute 1D/2D linear combinations without nested loops:
93
+ Combine indexed variables with `dot` / `sum` and express conditional rules with `m.indicator()`:
303
94
 
304
95
  ```ts
305
- import { Model, dot, minimize } from "@ohtaman/washi";
96
+ import { Model, expr, minimize } from "@ohtaman/washi";
306
97
 
307
- const m = new Model();
98
+ const m = new Model("TransportPlanning");
308
99
  const factories = ["Tokyo", "Osaka"];
309
100
  const stores = ["StoreA", "StoreB"];
310
-
311
101
  const costs = {
312
102
  Tokyo: { StoreA: 4, StoreB: 7 },
313
103
  Osaka: { StoreA: 8, StoreB: 3 },
314
104
  };
315
105
 
316
- const ship = m.continuous(factories, stores);
106
+ const open = m.binary(factories);
107
+ const ship = m.continuous(factories, stores, { lb: 0, ub: 500 });
317
108
 
318
- // Total transportation cost in one line:
319
- m.objective(minimize(dot(costs, ship)));
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");
112
+
113
+ m.objective(minimize(expr`dot(${costs}, ${ship})`));
320
114
  await m.solve();
321
115
 
322
- // Extract all optimal variable values as a structured object
323
- const solution = m.getValues(ship);
324
- console.log(solution);
116
+ console.log("Tokyo -> StoreA:", ship["Tokyo"]["StoreA"].value);
325
117
  ```
326
118
 
327
- ---
119
+ ### 2. Convex Quadratic Programming (QP)
328
120
 
329
- ## 🌐 Browser & Web Worker Usage
121
+ Minimize a quadratic objective subject to linear constraints:
330
122
 
331
- The default HiGHS engine uses the package's WASM asset. There is **no automatic CDN fallback**. Node.js uses the installed HiGHS runtime; Vite and webpack 5 can discover the package-relative WASM and Worker references when building a browser application.
123
+ ```ts
124
+ import { Model, expr, minimize } from "@ohtaman/washi";
332
125
 
333
- ### npm with a bundler
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 });
334
129
 
335
- ```typescript
336
- import { Model } from "@ohtaman/washi";
337
- const model = new Model();
338
- // Define variables, objective and constraints.
339
- await model.solve();
340
- ```
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`));
341
133
 
342
- 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.
134
+ await m.solve();
135
+ console.log(`Tech: ${x.value?.toFixed(2)}, Bonds: ${y.value?.toFixed(2)}`);
136
+ ```
343
137
 
344
- ### Browser ESM without an application build
138
+ ### 3. SMT Logic & Conflict Diagnosis (`m.check`)
345
139
 
346
- Serve the **entire `dist/browser` directory**, including its JS chunks, `highs.wasm` and Worker. No import map is needed:
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.
347
143
 
348
- ```html
349
- <script type="module">
350
- import { Model } from "./washi/washi.js";
351
- const model = new Model();
352
- // Define variables, objective and constraints.
353
- await model.solve({ useWorker: false });
354
- </script>
355
- ```
144
+ ```ts
145
+ import { Model, implies, expr } from "@ohtaman/washi";
146
+ // import "@ohtaman/washi/solvers/z3"; // Optional: for exact SMT unsatCore
356
147
 
357
- ### Plain script / CodePen
148
+ const m = new Model("StaffingRules");
149
+ const engineers = m.integer("engineers", { lb: 0, ub: 20 });
150
+ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
151
+ const rush = m.bool("rush");
358
152
 
359
- The package also includes `dist/browser/washi.min.js`, which exports the `Washi` global. Put that file's URL in CodePen's External Scripts, or use a script tag:
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
360
158
 
361
- ```html
362
- <script src="./washi/washi.min.js"></script>
363
- <script>
364
- const model = new Washi.Model();
365
- // Define variables, objective and constraints.
366
- model.solve({ useWorker: false }).then(status => console.log(status));
367
- </script>
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"]
368
162
  ```
369
163
 
370
- 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.
164
+ ---
371
165
 
372
- ### Solver Plugins in the Browser (GLPK & Z3)
166
+ ## 🌐 Browser & Web Worker Usage
373
167
 
374
- The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/`:
375
- - **GLPK**: `dist/browser/solvers/glpk.js` (ESM) and `dist/browser/solvers/glpk.min.js` (IIFE, registers global `WashiGLPK`)
376
- - **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`
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.
377
170
 
378
- #### Browser ESM with GLPK
379
171
  ```html
172
+ <!-- Browser ESM -->
380
173
  <script type="module">
381
- import { Model, maximize } from "./washi/washi.js";
382
- import "./washi/solvers/glpk.js";
383
-
384
- const model = new Model({ solver: "glpk" });
385
- const x = model.continuous("x", { lb: 0, ub: 10 });
386
- model.objective(maximize(x));
387
- await model.solve();
388
- console.log("GLPK status:", model.status);
174
+ import { Model } from "./washi/washi.js";
175
+ // Optional solver plugins: import "./washi/solvers/glpk.js" or "./washi/solvers/z3.js"
176
+ const m = new Model();
177
+ await m.solve();
389
178
  </script>
390
- ```
391
179
 
392
- #### Plain Script with GLPK
393
- ```html
394
- <script src="./washi/washi.min.js"></script>
395
- <script src="./washi/solvers/glpk.min.js"></script>
396
- <script>
397
- const model = new Washi.Model({ solver: "glpk" });
398
- const x = model.continuous("x", { lb: 0, ub: 10 });
399
- model.objective(Washi.maximize(x));
400
- model.solve().then(status => console.log("GLPK status:", status));
401
- </script>
180
+ <!-- Classic Script / CodePen (exposes global Washi) -->
181
+ <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi/dist/browser/washi.min.js"></script>
402
182
  ```
403
183
 
404
184
  > [!NOTE]
405
- > **Z3 in Browser Environments & Cross-Origin Isolation**:
406
- > 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`).
407
-
408
- Complete standalone examples for ESM, script tags, and CodePen are available in [`examples/distribution/`](examples/distribution/README.md).
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).
409
186
 
410
187
  ---
411
188
 
412
- ## 🛠️ Custom Solvers & Extensibility
413
-
414
- 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):
189
+ ## 🤝 Contributing
415
190
 
416
- - 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.
191
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development setup, running tests and benchmarks, rebuilding WebAssembly assets, and the release workflow.
417
192
 
418
193
  ---
419
194
 
420
- ## 📚 Documentation
195
+ ## 🙏 Acknowledgments
421
196
 
422
- Full documentation is available at **[ohtaman.github.io/washi](https://ohtaman.github.io/washi/)** and in the repository's [`docs/`](docs/) directory:
197
+ WaSHI stands on the shoulders of the open-source mathematical optimization and automated reasoning communities:
423
198
 
424
- - **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)
425
- - **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) · [Decomposition (`solveColumnGeneration`, `solveBenders`)](docs/guide/decomposition.md)
426
- - **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)
427
-
428
- ---
429
-
430
- ## 💻 Examples and browser demo
431
-
432
- 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.
433
-
434
- | Category | Example | What it demonstrates | Solver |
435
- | :--- | :--- | :--- | :--- |
436
- | **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 |
437
- | **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 |
438
- | **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 |
439
- | **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 |
440
- | **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 |
441
- | **Decomposition** | [Vehicle routing (CVRP)](https://github.com/ohtaman/washi/blob/main/examples/node/13-vehicle-routing-column-generation.ts) | Column Generation (Dantzig-Wolfe): pricing feasible vehicle routes from dual shadow prices | HiGHS |
442
- | **Decomposition** | [Traveling salesperson (TSP)](https://github.com/ohtaman/washi/blob/main/examples/node/14-traveling-salesperson-benders.ts) | Benders Decomposition (DFJ): lazy subtour elimination constraints via feasibility cuts | HiGHS |
443
- | **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 |
444
- | **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 |
445
- | **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 |
446
-
447
- [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.
448
-
449
- To run examples locally, clone the repository and install its development dependencies first:
450
-
451
- ```bash
452
- git clone https://github.com/ohtaman/washi.git
453
- cd washi
454
- npm ci
455
- npx tsx examples/node/01-production-planning.ts
456
- npx tsx examples/node/09-transportation-problem.ts
457
- npx tsx examples/node/10-diet-sensitivity.ts
458
- npx tsx examples/node/11-shift-scheduling.ts
459
- npx tsx examples/node/12-ecommerce-coupon-optimizer.ts
460
- npx tsx examples/node/13-vehicle-routing-column-generation.ts
461
- npx tsx examples/node/14-traveling-salesperson-benders.ts
462
-
463
- # Local browser demo: http://localhost:5173/demo/
464
- npm run dev
465
- ```
466
-
467
- 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.
468
-
469
- ---
470
-
471
- ## 🧪 Testing & Reproducible Benchmarks
472
-
473
- Run the repository's unit and integration tests, and build the distributions:
474
-
475
- ```bash
476
- # Run all unit and integration tests
477
- npm test
478
-
479
- # Build production bundle and TypeScript declarations
480
- npm run build
481
- ```
482
-
483
- ### Optional Reproducible Benchmarks
484
-
485
- 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:
486
-
487
- ```bash
488
- # Run Node.js in-process WASM benchmarks (HiGHS and GLPK, 5 warm iterations by default)
489
- npm run bench
490
-
491
- # Customize iteration count or emit structured JSON
492
- node scripts/run-benchmarks.mjs --iterations=10 --json
493
-
494
- # Also measure headless Chromium Web Worker execution (requires Playwright Chromium)
495
- node scripts/run-benchmarks.mjs --browser
496
- ```
497
-
498
- - **Explicit phase boundaries**: Reports cold runtime initialization (`coldRuntimeInitMs`, `coldFirstSolveMs`) separately from repeated warm-runtime measurements (`modelBuildMs`, `validationMs`, `lpSerializationMs` for LP-string adapters or `symbolMapMs` for structured adapters, `resultMappingMs` via `MockSolver`, `adapterSolveMs`, and `endToEndSolveMs`). Timings are measured directly per phase and are never subtracted to estimate "native solver time".
499
- - **Distinct execution environments**: Node.js in-process WASM (`usesBrowserWorker: false`) and Browser Web Worker execution (`usesBrowserWorker: true`, which includes Worker lifecycle and `postMessage` structured-clone transfer) are reported as separate environments, not interchangeable baselines.
500
-
501
- ### Rebuilding HiGHS WebAssembly & Release Workflow
502
-
503
- 1. **Rebuilding / Updating HiGHS WebAssembly Assets (`highs.config.json`)**:
504
- ```bash
505
- # Compile ERGO-Code/HiGHS to public/highs.wasm + runtime.js and update SHA-256 hashes in highs.config.json
506
- npm run build:highs-wasm
507
-
508
- # After merging to main, upload the updated assets to the highs-<version> GitHub Release tag
509
- # so on-demand downloads (scripts/ensure-highs-wasm.mjs) match highs.config.json SHA-256 hashes:
510
- gh release upload highs-v1.15.1 public/highs.wasm src/modeler/solvers/highs/runtime.js --clobber
511
- ```
512
- Alternatively, trigger the **Build HiGHS WebAssembly** workflow (`.github/workflows/build-wasm.yml`) via `workflow_dispatch` with `create_release: true`, then download the `highs-wasm-assets` workflow artifact (`public/highs.wasm`, `src/modeler/solvers/highs/runtime.js`, and `highs.config.json`) and commit those updated files to `main` so repository checksums stay synchronized with the published Release assets.
513
-
514
- 2. **Publishing `@ohtaman/washi` to npm**:
515
- - Merging to `main` automatically deploys the updated documentation and web demo to GitHub Pages (`.github/workflows/deploy-demo.yml`), but does **not** publish to npm automatically.
516
- - To publish a new version to npm, bump `"version"` in `package.json` on `main` and publish a GitHub Release (e.g., `gh release create v0.5.1 --generate-notes`). Publishing the GitHub Release triggers `.github/workflows/publish-npm.yml`, which runs unit and Playwright distribution checks before publishing to npm via OIDC Trusted Publishing.
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.
517
203
 
518
204
  ---
519
205
 
520
206
  ## 📄 License
521
207
 
522
208
  - **`@ohtaman/washi`**: [MIT License](./LICENSE)
523
- - **HiGHS** (default engine, [ERGO-Code/HiGHS](https://github.com/ERGO-Code/HiGHS)): MIT License
524
- - **Z3** (optional plugin via [`z3-solver`](https://www.npmjs.com/package/z3-solver)): MIT License
525
- - **GLPK** (optional plugin via [`glpk.js`](https://www.npmjs.com/package/glpk.js)): GPL-3.0 License
526
-
527
- See [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) for full attribution and license details of bundled/peer optimization engines.
209
+ - **Bundled / Optional Engines**: HiGHS (MIT), Z3 (MIT), GLPK (GPL-3.0). See [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) for full details.