@ohtaman/washi 0.3.0 → 0.5.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 (97) hide show
  1. package/README.ja.md +111 -118
  2. package/README.md +109 -116
  3. package/README.zh-CN.md +112 -119
  4. package/dist/browser/highs-worker.js +21 -3
  5. package/dist/browser/loader-CtaDHef_.js +273 -0
  6. package/dist/browser/solvers/glpk.js +433 -286
  7. package/dist/browser/solvers/glpk.min.js +1 -1
  8. package/dist/browser/solvers/z3.js +1002 -981
  9. package/dist/browser/solvers/z3.min.js +2 -2
  10. package/dist/browser/washi.js +2178 -622
  11. package/dist/browser/washi.min.js +4 -4
  12. package/dist/lib/highs-worker.js +21 -3
  13. package/dist/lib/loader-DmNC_UNk.js +279 -0
  14. package/dist/lib/registry-BEfvpZW3.js +951 -0
  15. package/dist/lib/solvers/glpk.js +201 -90
  16. package/dist/lib/solvers/z3.js +399 -259
  17. package/dist/lib/washi.js +1674 -932
  18. package/dist/types/index.d.ts +4 -4
  19. package/dist/types/index.d.ts.map +1 -1
  20. package/dist/types/modeler/big-m.d.ts +75 -0
  21. package/dist/types/modeler/big-m.d.ts.map +1 -0
  22. package/dist/types/modeler/boolean-expr.d.ts +103 -14
  23. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  24. package/dist/types/modeler/constraint.d.ts +36 -9
  25. package/dist/types/modeler/constraint.d.ts.map +1 -1
  26. package/dist/types/modeler/expression.d.ts +117 -27
  27. package/dist/types/modeler/expression.d.ts.map +1 -1
  28. package/dist/types/modeler/index.d.ts +10 -6
  29. package/dist/types/modeler/index.d.ts.map +1 -1
  30. package/dist/types/modeler/lp-writer.d.ts +27 -2
  31. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  32. package/dist/types/modeler/model.d.ts +491 -140
  33. package/dist/types/modeler/model.d.ts.map +1 -1
  34. package/dist/types/modeler/objective.d.ts +59 -0
  35. package/dist/types/modeler/objective.d.ts.map +1 -0
  36. package/dist/types/modeler/parser.d.ts +39 -14
  37. package/dist/types/modeler/parser.d.ts.map +1 -1
  38. package/dist/types/modeler/quad-expr.d.ts +28 -7
  39. package/dist/types/modeler/quad-expr.d.ts.map +1 -1
  40. package/dist/types/modeler/solution-validator.d.ts +93 -0
  41. package/dist/types/modeler/solution-validator.d.ts.map +1 -0
  42. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
  43. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  44. package/dist/types/modeler/solvers/highs/loader.d.ts +36 -11
  45. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  46. package/dist/types/modeler/solvers/highs/solver.d.ts +11 -4
  47. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  48. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  49. package/dist/types/modeler/solvers/mock.d.ts +14 -5
  50. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  51. package/dist/types/modeler/solvers/types.d.ts +71 -11
  52. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  53. package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
  54. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  55. package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
  56. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
  57. package/dist/types/modeler/types.d.ts +60 -2
  58. package/dist/types/modeler/types.d.ts.map +1 -1
  59. package/dist/types/modeler/validation.d.ts +56 -0
  60. package/dist/types/modeler/validation.d.ts.map +1 -0
  61. package/dist/types/modeler/variable.d.ts +53 -13
  62. package/dist/types/modeler/variable.d.ts.map +1 -1
  63. package/docs/examples/diet-sensitivity.md +90 -0
  64. package/docs/examples/ecommerce-coupons.md +106 -0
  65. package/docs/examples/index.md +53 -0
  66. package/docs/examples/knapsack.md +60 -0
  67. package/docs/examples/portfolio-qp.md +83 -0
  68. package/docs/examples/production.md +51 -0
  69. package/docs/examples/shift-scheduling.md +175 -0
  70. package/docs/examples/smt-security.md +74 -0
  71. package/docs/examples/transportation.md +90 -0
  72. package/docs/examples/unsat-core.md +64 -0
  73. package/docs/getting-started/ai-agents.md +267 -0
  74. package/docs/getting-started/cross-origin.md +87 -0
  75. package/docs/getting-started/index.md +116 -0
  76. package/docs/getting-started/runtimes.md +227 -0
  77. package/docs/guide/constraints.md +177 -0
  78. package/docs/guide/expressions.md +204 -0
  79. package/docs/guide/objectives.md +170 -0
  80. package/docs/guide/unsat-core.md +147 -0
  81. package/docs/guide/variables.md +188 -0
  82. package/docs/ja/getting-started/ai-agents.md +266 -0
  83. package/docs/solvers/custom-solver.md +525 -0
  84. package/docs/solvers/glpk.md +66 -0
  85. package/docs/solvers/highs.md +93 -0
  86. package/docs/solvers/index.md +42 -0
  87. package/docs/solvers/z3.md +78 -0
  88. package/docs/zh/getting-started/ai-agents.md +266 -0
  89. package/llms-full.txt +3249 -0
  90. package/llms.txt +197 -0
  91. package/package.json +19 -6
  92. package/skills/washi/SKILL.md +197 -0
  93. package/dist/browser/loader-CVt4CloI.js +0 -58
  94. package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
  95. package/dist/lib/constraint-BRuB3ooC.js +0 -90
  96. package/dist/lib/loader-Dti0v9yC.js +0 -50
  97. package/dist/lib/registry-DksqCSjI.js +0 -32
package/llms.txt ADDED
@@ -0,0 +1,197 @@
1
+ # WaSHI (@ohtaman/washi)
2
+
3
+ > WaSHI: WebAssembly-backed, Solver-independent, High-level modeling Interface (LP, MIP, QP, SMT)
4
+
5
+ WaSHI brings the algebraic modeling style of Python's **PuLP** and Julia's **JuMP** to the TypeScript and JavaScript ecosystem.
6
+
7
+ - Full single-file documentation reference: [llms-full.txt](llms-full.txt)
8
+ - Canonical AI agent modeling quick-reference: [docs/getting-started/ai-agents.md](docs/getting-started/ai-agents.md)
9
+
10
+ ---
11
+
12
+ ## 1. Package Imports & Deployment
13
+
14
+ ### Node.js / Bundlers (Vite, Webpack, Next.js, Bun)
15
+
16
+ ```ts
17
+ import {
18
+ Model,
19
+ expr,
20
+ sum,
21
+ dot,
22
+ maximize,
23
+ minimize,
24
+ and,
25
+ or,
26
+ not,
27
+ implies,
28
+ xor,
29
+ setHighsWasmUrl,
30
+ } from "@ohtaman/washi";
31
+
32
+ // Optional solver plugins (require installing peer dependencies first):
33
+ // npm install z3-solver
34
+ import "@ohtaman/washi/solvers/z3";
35
+ // npm install glpk.js
36
+ import "@ohtaman/washi/solvers/glpk";
37
+ ```
38
+
39
+ ### Browser ESM & Classic Script Tags
40
+
41
+ - **Browser ESM**:
42
+ - Core (HiGHS built-in): `import { Model, expr, maximize } from "./washi/washi.js";`
43
+ - GLPK plugin: `import "./washi/solvers/glpk.js";`
44
+ - Z3 plugin: `import "./washi/solvers/z3.js";`
45
+ - **Classic `<script>` / CodePen (IIFE)**:
46
+ - Core: `<script src="./washi/washi.min.js"></script>` (exposes global `Washi`)
47
+ - GLPK plugin: `<script src="./washi/solvers/glpk.min.js"></script>` (registers `"glpk"`, exposes `WashiGLPK`)
48
+ - Z3 plugin: `<script src="./washi/solvers/z3.min.js"></script>` (registers `"z3"`, exposes `WashiZ3`)
49
+
50
+ ### WebAssembly Asset & Runtime Rules
51
+
52
+ 1. **Bundled WASM by default (no implicit CDN fallback)**:
53
+ - In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
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 the local Node `highs` package runtime).
56
+ 2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
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
+ - `Cross-Origin-Opener-Policy: same-origin` (on the document)
59
+ - `Cross-Origin-Embedder-Policy: require-corp` (on the document)
60
+ - `Cross-Origin-Resource-Policy: cross-origin` and `Access-Control-Allow-Origin` (on cross-origin script/WASM asset responses)
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
+ 3. **Web Workers & Cancellation (`AbortSignal`)**:
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).
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
+
67
+ ---
68
+
69
+ ## 2. Canonical Modeling Workflow & Key Rules
70
+
71
+ Formulating and solving a model consists of four distinct steps:
72
+
73
+ ### Step 1: Create Model & Decision Variables
74
+
75
+ ```ts
76
+ const m = new Model("ExampleModel");
77
+
78
+ // Scalar variables
79
+ const x = m.continuous("x", { lb: 0, ub: 100 });
80
+ const n = m.integer("n", { lb: 0, ub: 10 });
81
+ const b = m.binary("b");
82
+ const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
83
+
84
+ // Indexed variable containers:
85
+ // - Numeric dimension -> Array
86
+ // - Key-array dimension -> Record
87
+ const vec = m.continuous(4, { lb: 0, prefix: "v" }); // Var[] (length 4)
88
+ const grid = m.binary(2, 3, { prefix: "g" }); // Var[][] (2x3)
89
+ const byItem = m.integer(["desk", "chair"] as const, { lb: 0 }); // Record<"desk" | "chair", Var>
90
+ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
91
+ ```
92
+
93
+ - **Optional Primal Initial Values (`initialValue?: number`)**:
94
+ - Numeric variable constructors (`m.continuous`, `m.integer`, `m.binary`, and `new Var`) and the mutable `v.initialValue` property accept an optional finite primal hint (validated with tolerance `1e-6` against bounds `[lb, ub]` and integer/binary domains), kept distinct from the solved `v.value`.
95
+ - A start may be complete or partial (e.g., transferred by stable variable names when rebuilding a MIP model). Supplying `initialValue` is a hint only—it does **not** fix the variable, add a hard constraint, or guarantee solver acceptance or a runtime speedup.
96
+ - Supported by **HiGHS** (`supportsWarmStart: true`); solvers without warm-start support (**GLPK**, **Z3**) throw an error if any variable has `initialValue !== undefined`.
97
+
98
+ ### Step 2: Build Expressions (`expr`, `sum`, `dot`)
99
+
100
+ - **Unified `expr` template tag**:
101
+ - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
102
+ - Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${n} + ${n}^2` `` (variable-by-variable products and squares are supported for convex QP objectives).
103
+ - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
104
+ - **Aggregations**:
105
+ - `sum(iterable, mapper?)` sums an `Iterable` of `Var` / `LinExpr` / `number` (e.g., `Var[]`, `grid.flat()`, or `Object.values(record)`).
106
+ - `dot(a, b)` computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
107
+ - **Fluent methods**:
108
+ - Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
109
+ - Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
110
+ - Boolean logic (on `BoolVar`, `BoolExpr`, and `Constraint` for Z3): standalone `and(...)`, `or(...)`, `not(...)`, `implies(a, b)`, `xor(a, b)` and fluent `.and(...)`, `.or(...)`, `.not()`, `.implies(...)`, `.xor(...)`
111
+
112
+ ### Step 3: Register Objective & Constraints on the Model
113
+
114
+ Constructing an `expr` or `.le()` object does **not** attach it to the model until you pass it to `m.constraint(...)` or `m.objective(...)`:
115
+
116
+ ```ts
117
+ // Set objective using maximize() or minimize()
118
+ m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
119
+
120
+ // Register constraints on the model
121
+ m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
122
+
123
+ // Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
124
+ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
125
+ ```
126
+
127
+ ### Step 4: Execute (`solve` or `check`) & Verify (`validateSolution`)
128
+
129
+ - **Optimization (`await m.solve(options?)`)**:
130
+ - 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
+ - Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
132
+ - 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 & Unsat Core (`await m.check(options?)`)**:
134
+ - Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` and `unsatCore` are optional and populated only when an unsat-core-capable solver (such as Z3) returns `"Unsat"`.
135
+ - For models containing only linear arithmetic constraints, `m.check()` works with any solver (including the default HiGHS and GLPK) by falling back to a feasibility `solve()`. Logical constraints (`BoolVar`, `and`, `or`, `implies`, etc.) and unsatisfiable-core extraction (`result.conflicts` / `result.unsatCore`) require an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `conflicts` / `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
+ - **No concurrent mutation during async solve/check**:
137
+ - 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
+
139
+ ---
140
+
141
+ ## 3. Common Pitfalls & Retired Aliases
142
+
143
+ 1. DO NOT pass a Constraint to m.objective() or arithmetic methods:
144
+ - `` WRONG: m.objective(maximize(expr`${x} <= 10` as any)); -> throws Error ``
145
+ - `` WRONG: expr`${x} + 1`.add(expr`${y} <= 5` as any); -> throws Error ``
146
+ 2. DO NOT pass an arithmetic expression without (<=, >=, ==) to m.constraint():
147
+ - `` WRONG: m.constraint(expr`${x} + ${y}` as any); -> throws Error ``
148
+ 3. DO NOT use quadratic terms inside linear constraints:
149
+ - `` WRONG: expr`${x}^2 <= 10`; -> throws Error ("Quadratic constraints are not supported") ``
150
+ 4. DO NOT use retired aliases (removed in #59 / #81):
151
+ - Use `expr` instead of retired `c` template tag
152
+ - Use `m.constraint(...)` instead of `m.constrain(...)` or `m.add(...)`
153
+ - Use `m.objective(maximize(...))` / `m.objective(minimize(...))` instead of `m.maximize(...)` / `m.minimize(...)` / `m.setObjective(...)`
154
+ - Use `await m.solve()` instead of `await m.optimize()`
155
+ - Use `.le()`, `.ge()`, `.eq()` instead of `.lessThanOrEqual()`, `.greaterThanOrEqual()`, `.equalTo()`
156
+ - Use `.value` or `m.getValues(...)` instead of `expr.getValue()`
157
+ - Use `m.variables` / `m.constraints` instead of `m.vars` / `m.constrs`
158
+
159
+ ---
160
+
161
+ ## 4. Canonical English Documentation Index
162
+
163
+ ### Getting Started
164
+
165
+ - [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** and Julia's **JuMP** to the TypeScript and JavaScript ecosystem.
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, and air-gapped intranets—without native C/C++ compilation.
168
+ - [Cross-Origin Isolation (Z3 & Browsers)](docs/getting-started/cross-origin.md): When running the **Z3** solver engine inside a web browser, special HTTP headers are required due to browser security models.
169
+
170
+ ### Modeling Guide
171
+
172
+ - [Variables & Domains](docs/guide/variables.md): Variables represent the mathematical decision variables in your optimization model.
173
+ - [Expressions (`expr`, `sum`, `dot`)](docs/guide/expressions.md): Mathematical expressions are linear or quadratic combinations of decision variables and numeric coefficients.
174
+ - [Constraints (`m.constraint` & `m.indicator`)](docs/guide/constraints.md): Constraints restrict the feasible solution space of your mathematical or logical model.
175
+ - [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: **Unsat Core conflict diagnosis** via Z3 (`m.check()`), **independent solution validation** (`m.validateSolution()`), **LP formulation export** (`m.toLpString()`), and **fast unit testing** (`MockSolver`).
177
+
178
+ ### Solvers
179
+
180
+ - [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 developed at the University of Edinburgh.
182
+ - [Z3 SMT Solver Engine](docs/solvers/z3.md): **Z3** is a theorem prover and Satisfiability Modulo Theories (SMT) solver developed by Microsoft Research.
183
+ - [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
+ - [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`).
185
+
186
+ ### Examples
187
+
188
+ - [Examples & API Finder](docs/examples/index.md): Explore runnable optimization and logic formulations built with WaSHI.
189
+ - [Production Planning (LP / MIP)](docs/examples/production.md): Production planning is a classic Linear Programming problem: determine the optimal quantity of each product to manufacture in order to maximize total profit while respecting resource constraints (raw materials, machine time, labor hours).
190
+ - [0-1 Knapsack Problem (Binary MIP)](docs/examples/knapsack.md): The 0-1 Knapsack problem is an essential combinatorial optimization problem: given a set of items, each with a weight and a value, determine which items to include in a collection so that the total weight does not exceed a given limit and the total value is maximized.
191
+ - [Transportation & Allocation Network (LP)](docs/examples/transportation.md): The transportation problem is one of the most fundamental network optimization models: determine how many units to ship from each supply origin (factory) to each destination (store) to minimize total shipping cost while respecting factory supply limits and meeting store demands.
192
+ - [Diet Problem & Sensitivity Analysis (LP Duals & Slacks)](docs/examples/diet-sensitivity.md): When solving a Linear Programming model, the optimal variable values (`var.value`) are only half the story.
193
+ - [Staff Shift Scheduling & Conflict Diagnosis (MIP + Z3)](docs/examples/shift-scheduling.md): Staff shift scheduling is a classic real-world application of Mixed-Integer Programming in SaaS and workforce management tools.
194
+ - [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
+ - [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
+ - [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 inevitably conflict.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ohtaman/washi",
3
- "version": "0.3.0",
4
- "description": "WaSHI: WebAssembly Algebraic System for Hybrid & Integer modeling (LP, MIP, QP, SMT)",
3
+ "version": "0.5.0",
4
+ "description": "WaSHI: WebAssembly-backed, Solver-independent, High-level modeling Interface (LP, MIP, QP, SMT)",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/washi.js",
7
7
  "module": "./dist/lib/washi.js",
@@ -48,6 +48,16 @@
48
48
  "dist/lib",
49
49
  "dist/browser",
50
50
  "dist/types",
51
+ "docs/getting-started/ai-agents.md",
52
+ "docs/ja/getting-started/ai-agents.md",
53
+ "docs/zh/getting-started/ai-agents.md",
54
+ "docs/getting-started",
55
+ "docs/guide",
56
+ "docs/solvers",
57
+ "docs/examples",
58
+ "skills/washi/SKILL.md",
59
+ "llms.txt",
60
+ "llms-full.txt",
51
61
  "README.md",
52
62
  "README.ja.md",
53
63
  "README.zh-CN.md",
@@ -59,18 +69,20 @@
59
69
  "build:demo": "vite build",
60
70
  "build:types": "node -e \"import('node:fs').then(fs => fs.rmSync('dist/types', { recursive: true, force: true }))\" && tsc -p tsconfig.lib.json",
61
71
  "build:lib": "node scripts/build-library.mjs",
72
+ "build:llms": "node scripts/generate-llms.mjs",
62
73
  "build": "npm run build:types && npm run build:lib && npm run build:browser && npm run build:demo",
63
74
  "build:site": "node scripts/build-site.mjs",
64
75
  "docs:dev": "vitepress dev docs",
65
- "docs:build": "vitepress build docs",
76
+ "docs:build": "vitepress build docs && node scripts/generate-llms.mjs",
66
77
  "docs:preview": "vitepress preview docs",
67
78
  "preview": "vite preview",
68
79
  "test": "vitest run",
69
80
  "test:e2e": "RUN_E2E=1 vitest run test/e2e.test.ts",
70
- "prepublishOnly": "npm run build:types && npm run build:lib && npm run build:browser",
81
+ "prepublishOnly": "npm run build:types && npm run build:lib && npm run build:browser && npm run build:llms",
71
82
  "build:browser": "node scripts/build-browser.mjs",
72
- "test:distribution": "npm run build:types && npm run build:lib && npm run build:browser && node --experimental-vm-modules scripts/check-distribution.mjs",
73
- "test:demo": "BASE_URL=/washi/ npm run build:demo && node scripts/check-demo-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=/washi/ npm run build:demo && node scripts/check-demo-browser.mjs",
85
+ "bench": "node scripts/run-benchmarks.mjs"
74
86
  },
75
87
  "keywords": [
76
88
  "optimization",
@@ -118,6 +130,7 @@
118
130
  },
119
131
  "devDependencies": {
120
132
  "@types/node": "^26.6.3",
133
+ "async-mutex": "^0.3.2",
121
134
  "glpk.js": "^5.0.0",
122
135
  "mermaid": "^11.17.2",
123
136
  "playwright": "^1.63.0",
@@ -0,0 +1,197 @@
1
+ ---
2
+ name: washi
3
+ description: Formulate, inspect, solve, and verify mathematical optimization (LP, MIP, convex QP) and SMT satisfiability or unsat-core models in TypeScript and JavaScript using @ohtaman/washi (HiGHS, Z3, GLPK).
4
+ ---
5
+
6
+ # WaSHI Modeling & Verification Skill
7
+
8
+ Use this skill when building, debugging, or verifying mathematical optimization (LP, MIP, convex QP) or satisfiability (SMT / SAT) models with `@ohtaman/washi`. For the full API reference and browser/bundler deployment rules, see [`docs/getting-started/ai-agents.md`](https://github.com/ohtaman/washi/blob/main/docs/getting-started/ai-agents.md) (available locally at `node_modules/@ohtaman/washi/docs/getting-started/ai-agents.md` in consumer projects or `docs/getting-started/ai-agents.md` in the repository).
9
+
10
+ ---
11
+
12
+ ## 1. Problem & Solver Selection
13
+
14
+ Select the solver engine and execution method that match the problem class:
15
+
16
+ | Problem Class | Recommended Solver | Setup & Import | Execution Method |
17
+ | :--- | :--- | :--- | :--- |
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** plugin | `npm install z3-solver` + `import "@ohtaman/washi/solvers/z3"` | `await m.check({ solver: "z3" })` or `await m.solve({ solver: "z3" })` |
20
+ | **Infeasibility / Conflict Diagnosis** (`conflicts` / `unsatCore`) | **Z3** plugin | `npm install z3-solver` + `import "@ohtaman/washi/solvers/z3"` | `await m.check({ solver: "z3" })` |
21
+ | **LP / MIP with GLPK** (GPL-3.0; no QP, SMT, or `initialValue`) | **GLPK** plugin | `npm install glpk.js` + `import "@ohtaman/washi/solvers/glpk"` | `await m.solve({ solver: "glpk" })` |
22
+
23
+ - **Browser runtime notes**:
24
+ - HiGHS and GLPK load local `.wasm` and Worker assets relative to the bundle (never falling back to an external CDN).
25
+ - Z3 uses `SharedArrayBuffer` pthreads and requires `window.isSecureContext === true` and `window.crossOriginIsolated === true` (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`).
26
+
27
+ ---
28
+
29
+ ## 2. Algebraic Formulation Workflow
30
+
31
+ 1. **Define decision variables on `Model`**:
32
+ - Numeric (`Var`): `m.continuous(nameOrDims, { lb, ub, initialValue })`, `m.integer(...)`, `m.binary(...)`
33
+ - Propositional boolean (`BoolVar`, for Z3): `m.bool(nameOrDims)`
34
+ - Indexed containers: pass a count (`number` -> `Var[]` / `Var[][]`) or key array (`readonly string[]` -> `Record<K, Var>` / `Record<R, Record<C, Var>>`).
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
+ 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(iterable, mapper?)` and `dot(a, b)`
41
+ - Propositional logic (for Z3): `and(...)`, `or(...)`, `not(...)`, `implies(a, b)`, `xor(a, b)` (or fluent `.and()`, `.or()`, `.not()`, `.implies()`, `.xor()`)
42
+ 3. **Register objective and named constraints**:
43
+ - Constructing an `expr` or `.le()` / `.ge()` / `.eq()` object does **not** attach it to the model.
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 Z3 `unsatCore` conflict diagnosis.
46
+
47
+ ---
48
+
49
+ ## 3. Model Inspection (`validateIntegrity` & `toLpString`)
50
+
51
+ Before solving or when debugging formulation bugs:
52
+
53
+ - **`m.validateIntegrity()`**: Validates variable ownership (variables and constraints cannot be shared across `Model` instances), bound sanity (`lb <= ub`, finite `initialValue` within bounds/integrality tolerance `1e-6`), and finite objective/constraint coefficients.
54
+ - **`m.toLpString()`**: Calls `validateIntegrity()` and exports the numeric LP / MIP / QP formulation in standard CPLEX LP format (`Maximize` / `Minimize`, `Subject To`, `Bounds`, `General`, `Binary`, `End`). Inspect this string to confirm objective sense, signs, coefficients, and right-hand sides match the mathematical specification.
55
+
56
+ ---
57
+
58
+ ## 4. Execution & Mandatory Self-Verification
59
+
60
+ Never report a model solution as final without executing the verification step:
61
+
62
+ 1. **After `const status = await m.solve(options?)`**:
63
+ - Verify `status === "Optimal"` (or `status === "Feasible"` / `m.hasPrimalSolution === true` when solver limits apply).
64
+ - Call `const report = m.validateSolution()` and assert `report.valid === true`.
65
+ - If `report.valid === false`, inspect `report.violations`, `report.missingVariables`, `report.uncheckedConstraints`, and `report.maxViolation`.
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
+ 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` (alias for `res.unsatCore`, also stored on `m.conflicts` / `m.unsatCore`).
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
+
72
+ ---
73
+
74
+ ## 5. Executable Workflow Recipes
75
+
76
+ ### Recipe 1: MIP Formulation, `toLpString()` Inspection & `validateSolution()` Verification
77
+
78
+ ```ts
79
+ import { Model, expr, dot, sum, maximize } from "@ohtaman/washi";
80
+
81
+ export async function solveAndVerifyMip() {
82
+ const m = new Model("ProductionMIP");
83
+ const products = ["desk", "chair"] as const;
84
+ const profit = { desk: 30, chair: 50 };
85
+ const woodPerUnit = { desk: 1, chair: 2 };
86
+ const laborPerUnit = { desk: 2, chair: 1 };
87
+
88
+ const qty = m.integer(products, { lb: 0, ub: 100 });
89
+ const lineOpen = m.binary("lineOpen");
90
+
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(Object.values(qty))} <= 0`, {
95
+ activeValue: 0,
96
+ name: "closed_line_zero_output",
97
+ });
98
+
99
+ // 1. Pre-solve integrity & LP formulation inspection
100
+ m.validateIntegrity();
101
+ const lpText = m.toLpString();
102
+
103
+ // 2. Solve with default HiGHS solver
104
+ const status = await m.solve();
105
+
106
+ // 3. Mandatory independent solution verification
107
+ const report = m.validateSolution();
108
+
109
+ return {
110
+ status,
111
+ lpText,
112
+ objectiveValue: m.objectiveValue,
113
+ quantities: m.getValues(qty),
114
+ lineOpen: lineOpen.value,
115
+ valid: report.valid,
116
+ maxViolation: report.maxViolation,
117
+ };
118
+ }
119
+ ```
120
+
121
+ ### Recipe 2: Convex Quadratic Programming (QP) & Verification
122
+
123
+ ```ts
124
+ import { Model, expr, minimize } from "@ohtaman/washi";
125
+
126
+ export async function solveAndVerifyQp() {
127
+ const m = new Model("AllocationQP");
128
+ const x = m.continuous("x", { lb: 0 });
129
+ const y = m.continuous("y", { lb: 0 });
130
+
131
+ m.objective(minimize(expr`${x}^2 + ${y}^2`));
132
+ m.constraint(expr`${x} + ${y} >= 4`, "target_total");
133
+
134
+ const lpText = m.toLpString();
135
+ const status = await m.solve();
136
+ const report = m.validateSolution();
137
+
138
+ return {
139
+ status,
140
+ lpText,
141
+ objectiveValue: m.objectiveValue,
142
+ x: x.value,
143
+ y: y.value,
144
+ valid: report.valid,
145
+ };
146
+ }
147
+ ```
148
+
149
+ ### Recipe 3: Z3 Propositional Logic, `unsatCore` Conflict Diagnosis & Verification
150
+
151
+ ```ts
152
+ import { Model, expr, implies } from "@ohtaman/washi";
153
+ import "@ohtaman/washi/solvers/z3";
154
+
155
+ export async function diagnoseAndVerifyWithZ3() {
156
+ // Part A: Diagnose conflicting rules with Z3 unsatCore / conflicts
157
+ const unsatModel = new Model("PolicyConflict", { solver: "z3" });
158
+ const vip = unsatModel.bool("vip");
159
+ const discount = unsatModel.integer("discount", { lb: 0, ub: 50 });
160
+
161
+ unsatModel.constraint(vip, "customer_is_vip");
162
+ unsatModel.constraint(implies(vip, expr`${discount} >= 20`), "vip_min_discount");
163
+ unsatModel.constraint(expr`${discount} <= 10`, "campaign_cap");
164
+
165
+ const unsatRes = await unsatModel.check();
166
+
167
+ // Part B: Relax the conflicting cap and verify the satisfiable assignment
168
+ const satModel = new Model("PolicyResolved", { solver: "z3" });
169
+ const vipSat = satModel.bool("vip");
170
+ const discountSat = satModel.integer("discount", { lb: 0, ub: 50 });
171
+
172
+ satModel.constraint(vipSat, "customer_is_vip");
173
+ satModel.constraint(implies(vipSat, expr`${discountSat} >= 20`), "vip_min_discount");
174
+ satModel.constraint(expr`${discountSat} <= 25`, "campaign_cap");
175
+
176
+ const satRes = await satModel.check();
177
+ const report = satModel.validateSolution();
178
+
179
+ return {
180
+ unsatStatus: unsatRes.status,
181
+ conflicts: unsatRes.conflicts,
182
+ unsatCore: unsatRes.unsatCore,
183
+ satStatus: satRes.status,
184
+ resolvedDiscount: discountSat.value,
185
+ valid: report.valid,
186
+ };
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 6. Guardrails & Common Pitfalls
193
+
194
+ - **No quadratic constraints**: Quadratic terms (`${x}^2`, `${x} * ${y}`) are only valid in `m.objective(...)`. Using them in `m.constraint(...)` throws `"Quadratic constraints are not supported"`.
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
+ - **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
+ - **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`).
@@ -1,58 +0,0 @@
1
- //#region \0washi-browser-assets
2
- var e = import.meta.url;
3
- function t(t) {
4
- if (!e) throw Error("[WaSHI] Load the browser IIFE using an external script src, or use the browser ESM build.");
5
- return new URL(t, e).href;
6
- }
7
- function n() {
8
- return t("highs.wasm");
9
- }
10
- //#endregion
11
- //#region src/modeler/solvers/highs/loader.ts
12
- var r = "https://cdn.jsdelivr.net/npm/highs@1.15.3/build/highs.wasm", i = null, a = null, o = null;
13
- function s(e) {
14
- o = e, i = null, a = null;
15
- }
16
- function c() {
17
- return o;
18
- }
19
- function l(e) {
20
- return e.length >= 4 && e[0] === 0 && e[1] === 97 && e[2] === 115 && e[3] === 109;
21
- }
22
- async function u(e) {
23
- try {
24
- let t = await fetch(e, { headers: { Range: "bytes=0-3" } });
25
- if (!t.ok || (t.headers.get("content-type") || "").includes("text/html")) return !1;
26
- if (!t.body) return !0;
27
- let n = t.body.getReader(), { value: r, done: i } = await n.read();
28
- return await n.cancel(), i || !r || r.length < 4 ? !1 : l(r);
29
- } catch {
30
- return !1;
31
- }
32
- }
33
- async function d(e) {
34
- return e || o || n();
35
- }
36
- async function f(e = {}) {
37
- let t = await d(e.wasmUrl), n = t ?? "node";
38
- if (i && a === n) return i;
39
- a = n;
40
- let r = (async () => {
41
- let e = (await import("./browser-runtime-D13gHOT5.js")).default;
42
- return await e({ locateFile: (e) => e.endsWith(".wasm") ? t : e });
43
- })().catch((e) => {
44
- i === r && (i = null, a = null);
45
- let n = e instanceof Error ? e.message : String(e);
46
- throw Error(`[WaSHI] Failed to initialize HiGHS${t ? ` from "${t}"` : ""}. Deploy the package's WASM asset or explicitly set wasmUrl; no CDN fallback is attempted. ${n}`, { cause: e });
47
- });
48
- return i = r, r;
49
- }
50
- async function p(e, t = {}) {
51
- let n = await f(t), r = performance.now(), i = { output_flag: t.outputFlag ?? !1 };
52
- return t.timeLimit !== void 0 && (i.time_limit = t.timeLimit), t.presolve !== void 0 && (i.presolve = t.presolve), t.solver !== void 0 && (i.solver = t.solver), t.relGap !== void 0 && (i.mip_rel_gap = t.relGap), t.absGap !== void 0 && (i.mip_abs_gap = t.absGap), {
53
- result: n.solve(e, i),
54
- runTime: (performance.now() - r) / 1e3
55
- };
56
- }
57
- //#endregion
58
- export { u as a, p as c, l as i, t as l, f as n, d as o, c as r, s, r as t };