@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/llms.txt
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# WaSHI (@ohtaman/washi)
|
|
2
2
|
|
|
3
|
-
> WaSHI: WebAssembly-backed
|
|
3
|
+
> WaSHI: WebAssembly-backed Algebraic Modeler for Mathematical Optimization & Constraint Programming (LP, MIP, QP, SMT)
|
|
4
4
|
|
|
5
|
-
WaSHI brings the algebraic modeling style of Python's **PuLP**
|
|
5
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) brings the algebraic modeling style of Python's **PuLP** to the TypeScript and JavaScript ecosystem.
|
|
6
6
|
|
|
7
7
|
- Full single-file documentation reference: [llms-full.txt](llms-full.txt)
|
|
8
8
|
- Canonical AI agent modeling quick-reference: [docs/getting-started/ai-agents.md](docs/getting-started/ai-agents.md)
|
|
@@ -52,7 +52,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
52
52
|
1. **Bundled WASM by default (no implicit CDN fallback)**:
|
|
53
53
|
- In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
|
|
54
54
|
- In browser deployments, deploy the full `dist/browser/` directory (JS, Worker, and `.wasm` files) together. If a local WASM URL is missing or returns an HTML SPA fallback, initialization fails immediately and reports the attempted URL—it **never** silently switches to a public CDN.
|
|
55
|
-
- In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads
|
|
55
|
+
- In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads WaSHI's bundled local `highs.wasm` runtime).
|
|
56
56
|
2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
|
|
57
57
|
- Z3 WebAssembly uses `SharedArrayBuffer` multithreading. Browser pages running Z3 must run in a secure context (`window.isSecureContext === true`, i.e., HTTPS or `localhost`) and be cross-origin isolated (`window.crossOriginIsolated === true`) by serving:
|
|
58
58
|
- `Cross-Origin-Opener-Policy: same-origin` (on the document)
|
|
@@ -61,7 +61,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
61
61
|
- Importing `@ohtaman/washi` never auto-injects a Service Worker. When a secure context or cross-origin isolation is absent in a browser, Z3 rejects immediately with a descriptive error.
|
|
62
62
|
3. **Web Workers & Cancellation (`AbortSignal`)**:
|
|
63
63
|
- Pre-solve cancellation (`signal.aborted` before execution starts) is supported across all solvers.
|
|
64
|
-
- In browsers where `Worker` is available, HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
|
|
64
|
+
- In browsers where `Worker` is available on a non-opaque effective origin (`window.origin !== "null"`), HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). On opaque effective origins (`window.origin === "null"`, such as `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), HiGHS defaults to main-thread execution (`useWorker: false`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
|
|
65
65
|
- On main-thread HiGHS (`useWorker: false` or in Node.js), cooperative mid-solve cancellation is supported when `abort()` is triggered synchronously inside an `onProgress` callback at a solver interrupt checkpoint; arbitrary asynchronous aborts from outside cannot preempt a blocked synchronous WASM thread until a callback checkpoint or completion. Single-threaded WASM solvers without interrupt hooks (GLPK, Z3) check `signal` before execution starts.
|
|
66
66
|
|
|
67
67
|
---
|
|
@@ -77,6 +77,7 @@ const m = new Model("ExampleModel");
|
|
|
77
77
|
|
|
78
78
|
// Scalar variables
|
|
79
79
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
80
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
80
81
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
81
82
|
const b = m.binary("b");
|
|
82
83
|
const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
|
|
@@ -98,12 +99,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
98
99
|
### Step 2: Build Expressions (`expr`, `sum`, `dot`)
|
|
99
100
|
|
|
100
101
|
- **Unified `expr` template tag**:
|
|
101
|
-
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
102
|
-
- Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
103
|
-
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
|
|
104
|
-
- **Aggregations**:
|
|
105
|
-
- `sum(
|
|
106
|
-
- `dot(a, b)` computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]
|
|
102
|
+
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``, `` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``, `` expr`sum((${price} - ${cost}) * ${byItem})` ``
|
|
103
|
+
- Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``, `` expr`sum((${vec} - ${target})^2)` `` (supported for convex QP objectives on purely continuous models using `m.continuous`; built-in HiGHS does not support MIQP models containing integer, binary, or boolean variables).
|
|
104
|
+
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``, `` expr`sum(${byItem}) <= 100` `` (supports `<=`, `>=`, `==`, and `=`).
|
|
105
|
+
- **Aggregations & Element-Wise Container Arithmetic**:
|
|
106
|
+
- `sum(containerOrItems, mapper?)` (usable inside `` expr`sum(...)` `` or standalone `sum(...)`) sums a 1D/2D `Array`, `Iterable`, or plain `Record` of `Var` / `LinExpr` / `number` (without needing `Object.values(record)`), or maps over items/counts via `sum(items, (item, i) => ...)`. Inside `` expr`sum(...)` ``, element-wise container arithmetic (`+`, `-`, `*`, `/`, `^2`) is also supported.
|
|
107
|
+
- `dot(a, b)` (usable inside `` expr`dot(${a}, ${b})` `` or standalone `dot(a, b)`) computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`, computing the Frobenius inner product $\sum_{i,j} A_{i,j} B_{i,j}$), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
|
|
107
108
|
- **Fluent methods**:
|
|
108
109
|
- Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
|
|
109
110
|
- Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
|
|
@@ -117,8 +118,8 @@ Constructing an `expr` or `.le()` object does **not** attach it to the model unt
|
|
|
117
118
|
// Set objective using maximize() or minimize()
|
|
118
119
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
119
120
|
|
|
120
|
-
// Register constraints on the model
|
|
121
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
121
|
+
// Register constraints on the model (returns the registered Constraint / BoolExpr or array)
|
|
122
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
122
123
|
|
|
123
124
|
// Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
|
|
124
125
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -130,9 +131,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
130
131
|
- Returns `SolverStatus` (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`, `"Sat"`, or `"Unsat"`). Built-in optimization solvers return the optimization statuses (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`), where a time-, iteration-, or solution-limited run (or an interrupted solve) that found a valid primal incumbent returns `"Feasible"`; `"Sat"` and `"Unsat"` are included in `SolverStatus` for satisfiability/custom solver adapters (such as `MockSolver`).
|
|
131
132
|
- Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
|
|
132
133
|
- Call `m.validateSolution()` after solving to independently verify variable finiteness, variable bounds (`lb`/`ub`), integer/binary/boolean domains, linear constraint residuals, and logical constraints (`SolutionValidationReport`: `{ valid, complete, maxViolation, violations, ... }`). Note that `validateSolution()` checks primal feasibility of variables and constraints; it does not recompute or compare `m.objectiveValue`.
|
|
133
|
-
- **Satisfiability &
|
|
134
|
-
- Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts`
|
|
135
|
-
- For models containing
|
|
134
|
+
- **Satisfiability & Conflict Diagnosis (`await m.check(options?)`)**:
|
|
135
|
+
- Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` is populated when the model is proven `"Unsat"`.
|
|
136
|
+
- For models containing linear arithmetic constraints or logical constraints (`BoolVar`, `and`, `or`, `implies`, `xor`), `m.check()` works with any solver (including the default HiGHS and GLPK via single-solve elastic slack relaxation, automatically linearizing logical constraints into 0-1 MIP formulation) and diagnoses conflicting constraints as `conflicts` (leaving `unsatCore: undefined` for semantic rigor).
|
|
137
|
+
- Exact SMT unsatisfiable-core extraction (`result.unsatCore` and `result.conflicts`) is provided when using an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `unsatCore` reports a subset of registered constraint names that conflicts *together with the model's fixed variable bounds/domains* (and is not guaranteed minimal).
|
|
136
138
|
- **No concurrent mutation during async solve/check**:
|
|
137
139
|
- Mutating variables, constraints, or the objective while `await m.solve()` or `await m.check()` is pending invalidates the in-flight execution and rejects the stale promise.
|
|
138
140
|
|
|
@@ -163,22 +165,24 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
163
165
|
### Getting Started
|
|
164
166
|
|
|
165
167
|
- [AI Coding Agent Quick-Reference Guide](docs/getting-started/ai-agents.md): This document is the **consumer modeling quick-reference** for AI coding agents and developers building optimization or satisfiability models with `@ohtaman/washi`.
|
|
166
|
-
- [Introduction & Installation](docs/getting-started/index.md): WaSHI brings the algebraic modeling style of Python's **PuLP**
|
|
167
|
-
- [Runtime Environments & Deployment](docs/getting-started/runtimes.md): WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, vanilla browser `<script>` tags
|
|
168
|
-
- [Cross-Origin Isolation (Z3 & Browsers)](docs/getting-started/cross-origin.md): When running the **Z3** solver
|
|
168
|
+
- [Introduction & Installation](docs/getting-started/index.md): **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) brings the algebraic modeling style of Python's **PuLP** to the TypeScript and JavaScript ecosystem.
|
|
169
|
+
- [Runtime Environments & Deployment](docs/getting-started/runtimes.md): WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, static hosting, and vanilla browser `<script>` tags—without native C/C++ compilation.
|
|
170
|
+
- [Cross-Origin Isolation (Z3 & Browsers)](docs/getting-started/cross-origin.md): When running the **Z3** solver inside a web browser, Cross-Origin Isolation HTTP headers are required so the browser enables `SharedArrayBuffer`.
|
|
169
171
|
|
|
170
172
|
### Modeling Guide
|
|
171
173
|
|
|
172
|
-
- [Variables & Domains](docs/guide/variables.md): Variables represent the mathematical decision variables in your optimization model.
|
|
174
|
+
- [Variables & Domains](docs/guide/variables.md): Variables represent the mathematical decision variables in your optimization or logical model.
|
|
173
175
|
- [Expressions (`expr`, `sum`, `dot`)](docs/guide/expressions.md): Mathematical expressions are linear or quadratic combinations of decision variables and numeric coefficients.
|
|
174
176
|
- [Constraints (`m.constraint` & `m.indicator`)](docs/guide/constraints.md): Constraints restrict the feasible solution space of your mathematical or logical model.
|
|
175
177
|
- [Objectives & Solving (`m.solve` & `m.check`)](docs/guide/objectives.md): The objective function defines the goal of mathematical optimization—whether to maximize profits, minimize costs, or minimize quadratic risk variance.
|
|
176
|
-
- [Conflict Diagnosis (`unsatCore`) & Solution Validation](docs/guide/unsat-core.md): WaSHI provides built-in tools for both: **
|
|
178
|
+
- [Conflict Diagnosis (`unsatCore`) & Solution Validation](docs/guide/unsat-core.md): WaSHI provides built-in tools for both: **conflict diagnosis** via built-in HiGHS elastic relaxation or Z3 Unsat Core (`m.check()`), **independent solution validation** (`m.validateSolution()`), **LP formulation export** (`m.toLpString()`), and **fast unit testing** (`MockSolver`).
|
|
179
|
+
- [Decomposition Methods (`solveColumnGeneration` & `solveBenders`)](docs/guide/decomposition.md): When monolithic Mixed-Integer Programming (MIP) models have exponentially many candidate patterns or large multi-stage subproblems, WaSHI provides two high-level decomposition orchestrators—`master.solveColumnGeneration()` and `master.solveBenders()`—along with low-level primitives for dual inspection and dynamic model updates.
|
|
180
|
+
- [Formulation Patterns & Optimization Tips](docs/guide/formulation-tips.md): While the preceding Modeling Guide pages cover WaSHI's APIs (`Model`, `expr`, `m.constraint`, `m.solve`, etc.), this page collects standard mathematical optimization formulation patterns and solver tuning techniques frequently used in linear and mixed-integer programming (LP/MIP).
|
|
177
181
|
|
|
178
182
|
### Solvers
|
|
179
183
|
|
|
180
184
|
- [Solvers Overview](docs/solvers/index.md): WaSHI is designed around a pluggable, decoupled solver architecture.
|
|
181
|
-
- [HiGHS Solver Engine](docs/solvers/highs.md): **HiGHS** is high-performance mathematical optimization software
|
|
185
|
+
- [HiGHS Solver Engine](docs/solvers/highs.md): **HiGHS** is high-performance mathematical optimization software initiated at the University of Edinburgh (highs.dev · ERGO-Code/HiGHS).
|
|
182
186
|
- [Z3 SMT Solver Engine](docs/solvers/z3.md): **Z3** is a theorem prover and Satisfiability Modulo Theories (SMT) solver developed by Microsoft Research.
|
|
183
187
|
- [GLPK Solver Engine](docs/solvers/glpk.md): The **GNU Linear Programming Kit (GLPK)** is a long-standing, robust simplex and interior-point solver package intended for solving large-scale linear programming (LP) and mixed integer programming (MIP).
|
|
184
188
|
- [WaSHI Custom Solver Developer Guide](docs/solvers/custom-solver.md): This guide describes how to build, test, and distribute custom solver plugins for **WaSHI** (`@ohtaman/washi`).
|
|
@@ -194,4 +198,6 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
194
198
|
- [E-Commerce Cart & Coupon Optimizer (Indicator MIP)](docs/examples/ecommerce-coupons.md): Modern e-commerce checkouts often feature complex promotional rules: minimum spend thresholds, category-specific discounts, stackable vs. mutually exclusive coupons, and maximum coupon limits per order.
|
|
195
199
|
- [Portfolio Optimization (QP)](docs/examples/portfolio-qp.md): The Markowitz mean-variance portfolio optimization problem is the foundation of modern quantitative finance: allocate capital across risky assets to minimize overall portfolio variance (risk) subject to achieving a minimum required expected return.
|
|
196
200
|
- [SMT Security Policies](docs/examples/smt-security.md): Satisfiability Modulo Theories (SMT) combines propositional logic (Boolean SAT) with first-order theories (linear integer arithmetic, real inequalities).
|
|
197
|
-
- [Conflict Diagnosis (Unsat Core)](docs/examples/unsat-core.md): When business rules, budgets, and operational policies grow complex, constraints
|
|
201
|
+
- [Conflict Diagnosis (Unsat Core)](docs/examples/unsat-core.md): When business rules, budgets, and operational policies grow complex, constraints can easily conflict.
|
|
202
|
+
- [Capacitated Vehicle Routing Problem (CVRP, 50 Customers via Column Generation)](docs/examples/vehicle-routing.md): The **Capacitated Vehicle Routing Problem (CVRP)** determines the minimum number of delivery vehicles needed to visit a set of customers, subject to vehicle cargo capacity, maximum customer stops per shift, and route duration limits.
|
|
203
|
+
- [Traveling Salesperson Problem (TSP via Benders Decomposition)](docs/examples/traveling-salesperson.md): The **Traveling Salesperson Problem (TSP)** seeks the shortest closed tour that visits every city in a given set once and returns to the starting city.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ohtaman/washi",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "WaSHI: WebAssembly-backed
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "WaSHI: WebAssembly-backed Algebraic Modeler for Mathematical Optimization & Constraint Programming (LP, MIP, QP, SMT)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/lib/washi.js",
|
|
7
7
|
"module": "./dist/lib/washi.js",
|
|
@@ -66,35 +66,39 @@
|
|
|
66
66
|
],
|
|
67
67
|
"scripts": {
|
|
68
68
|
"dev": "vite",
|
|
69
|
+
"ensure:highs": "node scripts/ensure-highs-wasm.mjs",
|
|
70
|
+
"build:highs-wasm": "bash scripts/build-highs-wasm.sh",
|
|
69
71
|
"build:demo": "vite build",
|
|
70
72
|
"build:types": "node -e \"import('node:fs').then(fs => fs.rmSync('dist/types', { recursive: true, force: true }))\" && tsc -p tsconfig.lib.json",
|
|
71
|
-
"build:lib": "node scripts/build-library.mjs",
|
|
73
|
+
"build:lib": "node scripts/ensure-highs-wasm.mjs && node scripts/build-library.mjs",
|
|
72
74
|
"build:llms": "node scripts/generate-llms.mjs",
|
|
73
|
-
"build": "npm run build:types && npm run build:lib && npm run build:browser && npm run build:demo",
|
|
74
|
-
"build:site": "node scripts/build-site.mjs",
|
|
75
|
+
"build": "node scripts/ensure-highs-wasm.mjs && npm run build:types && npm run build:lib && npm run build:browser && npm run build:demo",
|
|
76
|
+
"build:site": "node scripts/ensure-highs-wasm.mjs && node scripts/build-site.mjs",
|
|
75
77
|
"docs:dev": "vitepress dev docs",
|
|
76
78
|
"docs:build": "vitepress build docs && node scripts/generate-llms.mjs",
|
|
77
79
|
"docs:preview": "vitepress preview docs",
|
|
78
80
|
"preview": "vite preview",
|
|
79
81
|
"test": "vitest run",
|
|
80
82
|
"test:e2e": "RUN_E2E=1 vitest run test/e2e.test.ts",
|
|
81
|
-
"prepublishOnly": "npm run build:types && npm run build:lib && npm run build:browser && npm run build:llms",
|
|
82
|
-
"build:browser": "node scripts/build-browser.mjs",
|
|
83
|
-
"test:distribution": "npm run build:types && npm run build:lib && npm run build:browser && npm run build:llms && node --experimental-vm-modules scripts/check-distribution.mjs",
|
|
84
|
-
"test:demo": "BASE_URL=/
|
|
83
|
+
"prepublishOnly": "node scripts/ensure-highs-wasm.mjs && npm run build:types && npm run build:lib && npm run build:browser && npm run build:llms",
|
|
84
|
+
"build:browser": "node scripts/ensure-highs-wasm.mjs && node scripts/build-browser.mjs",
|
|
85
|
+
"test:distribution": "node scripts/ensure-highs-wasm.mjs && npm run build:types && npm run build:lib && npm run build:browser && npm run build:llms && node --experimental-vm-modules scripts/check-distribution.mjs",
|
|
86
|
+
"test:demo": "BASE_URL=/ npm run build:demo && node scripts/check-demo-browser.mjs",
|
|
85
87
|
"bench": "node scripts/run-benchmarks.mjs"
|
|
86
88
|
},
|
|
87
89
|
"keywords": [
|
|
88
90
|
"optimization",
|
|
89
91
|
"mathematical-programming",
|
|
92
|
+
"constraint-programming",
|
|
90
93
|
"linear-programming",
|
|
91
94
|
"mixed-integer-programming",
|
|
92
95
|
"quadratic-programming",
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"qp",
|
|
96
|
+
"smt",
|
|
97
|
+
"z3",
|
|
96
98
|
"highs",
|
|
97
99
|
"pulp",
|
|
100
|
+
"jump",
|
|
101
|
+
"operations-research",
|
|
98
102
|
"wasm",
|
|
99
103
|
"webassembly",
|
|
100
104
|
"typescript"
|
|
@@ -112,10 +116,8 @@
|
|
|
112
116
|
"bugs": {
|
|
113
117
|
"url": "https://github.com/ohtaman/washi/issues"
|
|
114
118
|
},
|
|
115
|
-
"homepage": "https://
|
|
116
|
-
"dependencies": {
|
|
117
|
-
"highs": "^1.15.3"
|
|
118
|
-
},
|
|
119
|
+
"homepage": "https://washi.ohtaman.dev/",
|
|
120
|
+
"dependencies": {},
|
|
119
121
|
"peerDependencies": {
|
|
120
122
|
"glpk.js": "^5.0.0",
|
|
121
123
|
"z3-solver": "^5.2.0"
|
|
@@ -132,6 +134,7 @@
|
|
|
132
134
|
"@types/node": "^26.6.3",
|
|
133
135
|
"async-mutex": "^0.3.2",
|
|
134
136
|
"glpk.js": "^5.0.0",
|
|
137
|
+
"katex": "^0.16.21",
|
|
135
138
|
"mermaid": "^11.17.2",
|
|
136
139
|
"playwright": "^1.63.0",
|
|
137
140
|
"typescript": "^7.0.2",
|
package/skills/washi/SKILL.md
CHANGED
|
@@ -16,9 +16,9 @@ Select the solver engine and execution method that match the problem class:
|
|
|
16
16
|
| Problem Class | Recommended Solver | Setup & Import | Execution Method |
|
|
17
17
|
| :--- | :--- | :--- | :--- |
|
|
18
18
|
| **LP / MIP / Convex QP** (including `m.indicator()` Big-M and `initialValue` warm starts) | **HiGHS** (default) | Built-in (`import { Model } from "@ohtaman/washi"`) | `await m.solve()` |
|
|
19
|
-
| **SMT Propositional Logic** (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`) | **Z3**
|
|
20
|
-
| **Infeasibility / Conflict Diagnosis** (`conflicts` / `unsatCore`) | **
|
|
21
|
-
| **LP / MIP with GLPK** (GPL-3.0; no QP
|
|
19
|
+
| **SMT Propositional Logic** (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`) | **HiGHS** (auto 0-1 MIP) or **Z3** (native SMT) | Built-in for HiGHS; `npm install z3-solver` + `import "@ohtaman/washi/solvers/z3"` for Z3 | `await m.solve()` or `await m.check()` |
|
|
20
|
+
| **Infeasibility / Conflict Diagnosis** (`conflicts` / `unsatCore`) | **HiGHS** (elastic slack `conflicts`) or **Z3** (`unsatCore`) | Built-in for HiGHS; `import "@ohtaman/washi/solvers/z3"` for Z3 | `await m.check()` |
|
|
21
|
+
| **LP / MIP with GLPK** (GPL-3.0; no QP or `initialValue`) | **GLPK** plugin | `npm install glpk.js` + `import "@ohtaman/washi/solvers/glpk"` | `await m.solve({ solver: "glpk" })` |
|
|
22
22
|
|
|
23
23
|
- **Browser runtime notes**:
|
|
24
24
|
- HiGHS and GLPK load local `.wasm` and Worker assets relative to the bundle (never falling back to an external CDN).
|
|
@@ -30,19 +30,19 @@ Select the solver engine and execution method that match the problem class:
|
|
|
30
30
|
|
|
31
31
|
1. **Define decision variables on `Model`**:
|
|
32
32
|
- Numeric (`Var`): `m.continuous(nameOrDims, { lb, ub, initialValue })`, `m.integer(...)`, `m.binary(...)`
|
|
33
|
-
- Propositional boolean (`BoolVar
|
|
33
|
+
- Propositional boolean (`BoolVar`): `m.bool(nameOrDims)`
|
|
34
34
|
- Indexed containers: pass a count (`number` -> `Var[]` / `Var[][]`) or key array (`readonly string[]` -> `Record<K, Var>` / `Record<R, Record<C, Var>>`).
|
|
35
35
|
- Provide finite `lb` and `ub` bounds whenever known—especially on variables used in `m.indicator()` so WaSHI can automatically derive a tight Big-M when `M` is omitted.
|
|
36
36
|
2. **Build expressions (`expr`, `sum`, `dot`)**:
|
|
37
|
-
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${y} - 10` ``
|
|
38
|
-
- Quadratic expression (`QuadExpr`, **objectives only**): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``
|
|
39
|
-
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${y} <= 40` `` (supports `<=`, `>=`, `==`, `=`)
|
|
40
|
-
- Vector aggregations: `sum(
|
|
41
|
-
- Propositional logic
|
|
37
|
+
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${y} - 10` ``, `` expr`dot(${profit}, ${qty}) - 20 * ${lineOpen}` ``
|
|
38
|
+
- Quadratic expression (`QuadExpr`, **objectives only**): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``, `` expr`sum((${xVec} - ${target})^2)` `` (purely continuous models on HiGHS; MIQP with integer/binary/boolean variables is not supported)
|
|
39
|
+
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${y} <= 40` ``, `` expr`sum(${qty}) <= 100` `` (supports `<=`, `>=`, `==`, `=`)
|
|
40
|
+
- Vector & Record aggregations: `sum(containerOrItems, mapper?)` and `dot(a, b)` (both usable inside `expr` or standalone; `sum` accepts 1D/2D `Array`, `Iterable`, and `Record` directly without `Object.values()`, plus element-wise container arithmetic `+`, `-`, `*`, `/`, `^2` inside `expr`; `dot` computes the 1D inner product or 2D Frobenius inner product)
|
|
41
|
+
- Propositional logic: `and(...)`, `or(...)`, `not(...)`, `implies(a, b)`, `xor(a, b)` (or fluent `.and()`, `.or()`, `.not()`, `.implies()`, `.xor()`). Automatically linearized into 0-1 MIP binary variables and linear constraints on HiGHS/GLPK, or solved natively on Z3.
|
|
42
42
|
3. **Register objective and named constraints**:
|
|
43
43
|
- Constructing an `expr` or `.le()` / `.ge()` / `.eq()` object does **not** attach it to the model.
|
|
44
44
|
- Register the objective with `m.objective(maximize(e))` or `m.objective(minimize(e))`.
|
|
45
|
-
- Register every constraint with a descriptive name: `m.constraint(c, "resource_limit")` or `m.indicator(binVar, c, { activeValue: 1, name: "cap_when_open" })`. Descriptive names are essential for LP inspection and
|
|
45
|
+
- Register every constraint with a descriptive name: `const limit = m.constraint(c, "resource_limit")` (returns the registered `Constraint` / `BoolExpr`, or array for batch inputs) or `m.indicator(binVar, c, { activeValue: 1, name: "cap_when_open" })`. Descriptive names are essential for LP inspection and conflict diagnosis (`conflicts` / `unsatCore`).
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -65,8 +65,8 @@ Never report a model solution as final without executing the verification step:
|
|
|
65
65
|
- If `report.valid === false`, inspect `report.violations`, `report.missingVariables`, `report.uncheckedConstraints`, and `report.maxViolation`.
|
|
66
66
|
- Note: `m.validateSolution()` independently checks variable finiteness, variable bounds (`lb`/`ub`), integrality (`Integer`, `Binary`, `BoolVar`), linear constraint residuals, and logical constraints. It does **not** recompute `m.objectiveValue` or verify natural-language problem coverage.
|
|
67
67
|
2. **When a model is infeasible (`"Infeasible"` or `"Unsat"`)**:
|
|
68
|
-
- Run `const res = await m.check({ solver: "z3" })
|
|
69
|
-
- When `res.status === "Unsat"`, inspect `res.conflicts` (
|
|
68
|
+
- Run `const res = await m.check()` (diagnoses with built-in HiGHS via single-solve elastic slack relaxation) or `const res = await m.check({ solver: "z3" })` (for exact SMT unsat core).
|
|
69
|
+
- When `res.status === "Unsat"`, inspect `res.conflicts` (also stored on `m.conflicts`). On HiGHS, `conflicts` reports the subset of constraints requiring relaxation to restore feasibility (with `unsatCore: undefined` for semantic rigor). On Z3, both `unsatCore` and `conflicts` report the contradictory constraint subset.
|
|
70
70
|
- Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking named constraints, `res.conflicts` returns a subset of registered constraint names that conflicts *together with the model's fixed variable bounds/domains* (not guaranteed minimal).
|
|
71
71
|
|
|
72
72
|
---
|
|
@@ -88,10 +88,10 @@ export async function solveAndVerifyMip() {
|
|
|
88
88
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
89
89
|
const lineOpen = m.binary("lineOpen");
|
|
90
90
|
|
|
91
|
-
m.objective(maximize(expr
|
|
92
|
-
m.constraint(expr
|
|
93
|
-
m.constraint(expr
|
|
94
|
-
m.indicator(lineOpen, expr
|
|
91
|
+
m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${lineOpen}`));
|
|
92
|
+
m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_limit");
|
|
93
|
+
m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_limit");
|
|
94
|
+
m.indicator(lineOpen, expr`sum(${qty}) <= 0`, {
|
|
95
95
|
activeValue: 0,
|
|
96
96
|
name: "closed_line_zero_output",
|
|
97
97
|
});
|
|
@@ -191,7 +191,11 @@ export async function diagnoseAndVerifyWithZ3() {
|
|
|
191
191
|
|
|
192
192
|
## 6. Guardrails & Common Pitfalls
|
|
193
193
|
|
|
194
|
-
- **No quadratic constraints**: Quadratic terms (`${x}^2`, `${x} * ${y}`) are only valid in `m.objective(...)
|
|
194
|
+
- **No quadratic constraints or MIQP**: Quadratic terms (`${x}^2`, `${x} * ${y}`) are only valid in `m.objective(...)` (using them in `m.constraint(...)` throws `"Quadratic constraints are not supported"`), and built-in HiGHS requires Convex QP models to contain only continuous variables (`m.continuous`).
|
|
195
195
|
- **Do not confuse `LinExpr` and `Constraint`**: `m.objective(maximize(...))` requires a `LinExpr`, `QuadExpr`, `Var`, or `number` (never a `Constraint`). `m.constraint(...)` requires a `Constraint` (with `<=`, `>=`, `==`, or `=`) or `BoolExpr` (never a bare `LinExpr`).
|
|
196
196
|
- **Do not mutate during async solve/check**: Modifying variables, bounds, constraints, or the objective while `await m.solve()` or `await m.check()` is pending invalidates the in-flight run and rejects the promise.
|
|
197
197
|
- **Do not use retired aliases**: Always use `expr` (not `c`), `m.constraint(...)` (not `m.constrain` or `m.add`), `m.objective(maximize(...))` (not `m.maximize` or `m.setObjective`), `await m.solve()` (not `m.optimize`), `.le()` / `.ge()` / `.eq()` (not `.lessThanOrEqual()`), `.value` / `m.getValues(...)` (not `.getValue()`), and `m.variables` / `m.constraints` (not `m.vars` / `m.constrs`).
|
|
198
|
+
- **Practical OR formulation guardrails**:
|
|
199
|
+
- **Prefer inequalities (`>=` / `<=`) over `==`** for demand coverage and capacity limits when costs are non-negative; reserve `==` for flow conservation or normalization identities.
|
|
200
|
+
- **Provide finite `lb` and `ub` bounds** on variables inside `m.indicator(...)` and omit `M` so WaSHI automatically derives the tightest valid Big-M instead of a loose `1e6` fallback.
|
|
201
|
+
- **Use soft constraints (non-negative slack variables)** with tiered objective penalties for non-rigid business targets (preferred staffing, safety stock, day-off requests) so minor data contention produces a prioritized trade-off rather than `"Infeasible"`.
|