@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/README.md CHANGED
@@ -24,24 +24,24 @@
24
24
 
25
25
  ---
26
26
 
27
- **WaSHI** is an algebraic modeler for 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 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.
28
28
 
29
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.
30
30
 
31
- [Try the browser demo](https://ohtaman.github.io/washi/demo/) · [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
+ [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)
32
32
 
33
33
  ```ts
34
- import { Model, expr } from "@ohtaman/washi";
34
+ import { Model, expr, maximize } from "@ohtaman/washi";
35
35
 
36
36
  const m = new Model("WoodworkingProfit");
37
37
  const x = m.integer("desks", { lb: 0 });
38
38
  const y = m.integer("chairs", { lb: 0 });
39
39
 
40
- m.maximize(expr`30 * ${x} + 50 * ${y}`);
41
- m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
42
- m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
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
43
 
44
- await m.optimize();
44
+ await m.solve();
45
45
  console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
46
46
  ```
47
47
 
@@ -49,10 +49,10 @@ console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`
49
49
 
50
50
  ## 🎯 What WaSHI provides
51
51
 
52
- 1. **Algebraic model construction**: Formulate optimization models using domain-specific variable constructors (`m.binary`, `m.integer`, `m.continuous`), tagged template literals (`expr`), vector aggregations (`dot`, `sum`), and first-class constraints (`m.constrain`).
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/custom-solver-guide.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. Browser deployments must include the generated JS, Worker, and WASM assets. Offline use also requires those resources to be available locally or cached. Z3 has additional browser isolation requirements.
55
- 4. **Named constraints and SMT results**: With Z3, explicitly check satisfiability and inspect an unsatisfiable core to investigate conflicting constraints. A returned core is a conflicting subset, not necessarily the smallest one.
52
+ 1. **Algebraic model construction**: Formulate optimization models using domain-specific variable constructors (`m.binary`, `m.integer`, `m.continuous`), unified tagged template literals (`expr`), vector aggregations (`dot`, `sum`), and first-class constraints (`m.constraint`). Because JavaScript lacks operator overloading, `` expr`...` `` parses infix math (`+`, `-`, `*`, `^2`, `<=`, `>=`, `==`) while binding interpolated `${...}` values directly as typed `Var` or expression references—similar to parameterized SQL templates—preserving static type checking, IDE refactoring, and indexed variable lookups (`${flow[i][j]}`).
53
+ 2. **Solver adapters**: Use a common modeling API with HiGHS, Z3, or GLPK, or [register your own adapter](https://github.com/ohtaman/washi/blob/main/docs/solvers/custom-solver.md). Choose an engine that supports the model's problem class; the adapters do not make every solver support every feature.
54
+ 3. **Local execution and WebAssembly distribution**: With the local solver adapters, solving runs in the application without a separate Python service or native C/C++ build—removing backend provisioning as a bottleneck when shipping interactive tools or combining mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) on the client. Browser deployments must include the generated JS, Worker, and WASM assets. Offline use also requires those resources to be available locally or cached. Z3 has additional browser isolation requirements.
55
+ 4. **Named constraints and SMT results**: With Z3, explicitly check satisfiability and inspect conflicting constraints (`conflicts` / `unsatCore`) to investigate infeasibilities. A returned core is a conflicting subset, not necessarily the smallest one.
56
56
 
57
57
  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.
58
58
 
@@ -92,61 +92,54 @@ npm install z3-solver # For SMT and unsat core diagnosis
92
92
  npm install glpk.js # For GLPK solver (GPL-3.0)
93
93
  ```
94
94
 
95
+ *(Optional)* For AI coding agents (Claude Code, Antigravity, Cursor, Codex, etc.), `@ohtaman/washi` ships a portable [`skills/washi/SKILL.md`](skills/washi/SKILL.md) workflow alongside the [AI Agent Quick-Reference](docs/getting-started/ai-agents.md):
96
+
97
+ ```bash
98
+ mkdir -p .agents/skills/washi
99
+ cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
100
+ ```
101
+
95
102
  ---
96
103
 
97
104
  ## 🚀 Usage Guide
98
105
 
99
- ### 1. Mixed-Integer Linear Programming (MIP)
106
+ ### 1. Multidimensional Variables, Indicator Constraints & Solution Validation
100
107
 
101
- Formulate production scheduling, resource allocation, and facility location problems:
108
+ 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()`:
102
109
 
103
110
  ```ts
104
111
  import { Model, expr } from "@ohtaman/washi";
105
112
 
106
- const m = new Model("FactoryPlanning");
107
-
108
- // Decision variables (Integer, non-negative)
109
- const chairs = m.integer("chairs", { lb: 0 });
110
- const tables = m.integer("tables", { lb: 0 });
111
-
112
- // Natural formula objective & constraints via Tagged Template Literals
113
- m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
114
- m.constrain(expr`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
115
- m.constrain(expr`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
116
-
117
- // Solve using default HiGHS WASM engine
118
- const status = await m.optimize();
119
-
120
- console.log(`Status: ${status}`); // "Optimal"
121
- console.log(`Objective: $${m.objectiveValue}`); // 1100
122
- console.log(`Chairs: ${chairs.value}, Tables: ${tables.value}`); // 20 chairs, 10 tables
123
- ```
113
+ const m = new Model("FacilityPlanning");
124
114
 
125
- #### 🗂 Multidimensional Variables Grid
126
-
127
- Construct variable arrays or multi-index matrices in one call:
128
-
129
- ```ts
130
115
  // 1D dictionary of binary variables
131
116
  const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
132
117
 
133
118
  // 2D transport flow matrix: [Factory] x [Store]
134
- const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
119
+ const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0, ub: 1000 });
135
120
 
136
121
  // 2D coordinate grid (e.g. 8x8 chessboard)
137
122
  const board = m.binary(8, 8, { prefix: "queen" });
138
- ```
139
-
140
- #### 🔀 Big-M Indicator Constraints (`m.indicator`)
141
123
 
142
- Model conditional activation rules and fixed-charge economics without manual Big-M algebra:
143
-
144
- ```ts
145
- // If factory is closed (openCity['Tokyo'] == 0), its production capacity must be 0
124
+ // Conditional Big-M constraint: if openCity["Tokyo"] == 0, flow["F1"]["StoreA"] <= 0
125
+ // (Omit M to let WaSHI automatically derive a tight Big-M from finite variable bounds)
146
126
  m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
147
127
  activeValue: 0,
148
- M: 1000,
128
+ name: "tokyo_closed_no_flow",
149
129
  });
130
+
131
+ // Solve with optional progress callback & AbortSignal cancellation
132
+ const controller = new AbortController();
133
+ await m.solve({
134
+ signal: controller.signal,
135
+ onProgress: (ev) => {
136
+ // Live branch-and-bound metrics: ev.elapsedTime, ev.objectiveValue, ev.objectiveBound, ev.mipGap
137
+ },
138
+ });
139
+
140
+ // Independently verify variable bounds, integrality, and linear/logical constraints
141
+ const report = m.validateSolution();
142
+ console.log("Solution valid?", report.valid);
150
143
  ```
151
144
 
152
145
  ---
@@ -156,7 +149,7 @@ m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
156
149
  Minimize investment risk based on a covariance matrix subject to return and budget constraints:
157
150
 
158
151
  ```ts
159
- import { Model, QuadExpr, sum, dot, expr } from "@ohtaman/washi";
152
+ import { Model, QuadExpr, sum, dot, minimize, expr } from "@ohtaman/washi";
160
153
 
161
154
  const m = new Model("MarkowitzPortfolio");
162
155
  const assets = ["Tech", "Bonds", "RealEstate"];
@@ -166,11 +159,11 @@ const returns = [0.14, 0.04, 0.08];
166
159
  const w = m.continuous(assets, { lb: 0, ub: 1 });
167
160
 
168
161
  // 1. Fully invested budget: sum(w) == 1.0
169
- m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
162
+ m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
170
163
 
171
164
  // 2. Minimum target return: portfolio return >= 8.5%
172
165
  const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
173
- m.constrain(expr`${dot(retMap, w)} >= 0.085`, "min_return");
166
+ m.constraint(expr`${dot(retMap, w)} >= 0.085`, "min_return");
174
167
 
175
168
  // 3. Covariance risk matrix
176
169
  const cov = [
@@ -186,9 +179,9 @@ for (let i = 0; i < assets.length; i++) {
186
179
  risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
187
180
  }
188
181
  }
189
- m.minimize(risk);
182
+ m.objective(minimize(risk));
190
183
 
191
- await m.optimize();
184
+ await m.solve();
192
185
  console.log(`Minimal Variance: ${m.objectiveValue?.toFixed(6)}`);
193
186
  assets.forEach((name) => {
194
187
  console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
@@ -207,20 +200,20 @@ import "@ohtaman/washi/solvers/z3";
207
200
 
208
201
  const m = new Model({ name: "SmartSecurityRules" });
209
202
 
210
- const isNight = m.addBoolVar("isNight");
211
- const motion = m.addBoolVar("motion");
212
- const alarm = m.addBoolVar("alarm");
213
- const siren = m.addBoolVar("siren");
203
+ const isNight = m.bool("isNight");
204
+ const motion = m.bool("motion");
205
+ const alarm = m.bool("alarm");
206
+ const siren = m.bool("siren");
214
207
  const lux = m.integer("lux", { lb: 0 });
215
208
 
216
209
  // Logical rules
217
- m.constrain(lux.le(50).implies(isNight), "dark_implies_night");
218
- m.constrain(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
210
+ m.constraint(lux.le(50).implies(isNight), "dark_implies_night");
211
+ m.constraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
219
212
 
220
213
  // Current sensor observations
221
- m.constrain(expr`${lux} <= 10`, "sensor_dark");
222
- m.constrain(motion, "sensor_motion");
223
- m.constrain(alarm, "alarm_on");
214
+ m.constraint(expr`${lux} <= 10`, "sensor_dark");
215
+ m.constraint(motion, "sensor_motion");
216
+ m.constraint(alarm, "alarm_on");
224
217
 
225
218
  // Check satisfiability and deduce state without an objective function
226
219
  const res = await m.check({ solver: "z3" });
@@ -231,7 +224,7 @@ console.log(`Siren active? ${m.values.get("siren")}`); // true
231
224
 
232
225
  ---
233
226
 
234
- ### 4. Conflict Diagnosis (`unsatCore`)
227
+ ### 4. Conflict Diagnosis (`conflicts` / `unsatCore`)
235
228
 
236
229
  When business rules or constraints contradict each other, extract a **conflicting constraint subset** (unsatisfiable core; minimality is not guaranteed) for troubleshooting (requires Z3):
237
230
 
@@ -242,27 +235,27 @@ import "@ohtaman/washi/solvers/z3";
242
235
  const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
243
236
  const engineers = m.integer("engineers", { lb: 0, ub: 20 });
244
237
  const budget = m.continuous("budget", { lb: 0, ub: 100000 });
245
- const rushDelivery = m.addBoolVar("rushDelivery");
238
+ const rushDelivery = m.bool("rushDelivery");
246
239
 
247
240
  // Rule 1: Each engineer costs $8,000
248
- m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
241
+ m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
249
242
 
250
243
  // Rule 2: Customer requires rush delivery
251
- m.constrain(rushDelivery, "rush_required");
244
+ m.constraint(rushDelivery, "rush_required");
252
245
 
253
246
  // Rule 3: Rush delivery requires at least 8 engineers
254
- m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
247
+ m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
255
248
 
256
249
  // Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
257
- m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
250
+ m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
258
251
 
259
252
  // Rule 5: Innocent bystander rule
260
- m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
253
+ m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
261
254
 
262
255
  const res = await m.check({ solver: "z3" });
263
256
 
264
257
  console.log(`Status: ${res.status}`); // "Unsat"
265
- console.log("Conflicting constraints:", res.unsatCore);
258
+ console.log("Conflicting constraints:", res.conflicts);
266
259
  // Output: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
267
260
  // Note: "minimum_one_employee" is correctly excluded!
268
261
  ```
@@ -274,18 +267,18 @@ console.log("Conflicting constraints:", res.unsatCore);
274
267
  To use the GNU Linear Programming Kit (GLPK) solver plugin:
275
268
 
276
269
  ```ts
277
- import { Model, expr } from "@ohtaman/washi";
270
+ import { Model, expr, maximize } from "@ohtaman/washi";
278
271
  import "@ohtaman/washi/solvers/glpk";
279
272
 
280
273
  const m = new Model({ solver: "glpk" });
281
274
  const x = m.integer("x", { lb: 0 });
282
275
  const y = m.integer("y", { lb: 0 });
283
276
 
284
- m.maximize(expr`30 * ${x} + 50 * ${y}`);
285
- m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
286
- m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
277
+ m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
278
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
279
+ m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
287
280
 
288
- const status = await m.optimize();
281
+ const status = await m.solve();
289
282
  console.log(`Status: ${status}`); // "Optimal"
290
283
  console.log(`Objective: ${m.objectiveValue}`); // 1100
291
284
  ```
@@ -297,7 +290,7 @@ console.log(`Objective: ${m.objectiveValue}`); // 1100
297
290
  Cleanly compute 1D/2D linear combinations without nested loops:
298
291
 
299
292
  ```ts
300
- import { Model, dot } from "@ohtaman/washi";
293
+ import { Model, dot, minimize } from "@ohtaman/washi";
301
294
 
302
295
  const m = new Model();
303
296
  const factories = ["Tokyo", "Osaka"];
@@ -311,8 +304,8 @@ const costs = {
311
304
  const ship = m.continuous(factories, stores);
312
305
 
313
306
  // Total transportation cost in one line:
314
- m.minimize(dot(costs, ship));
315
- await m.optimize();
307
+ m.objective(minimize(dot(costs, ship)));
308
+ await m.solve();
316
309
 
317
310
  // Extract all optimal variable values as a structured object
318
311
  const solution = m.getValues(ship);
@@ -362,23 +355,23 @@ The package also includes `dist/browser/washi.min.js`, which exports the `Washi`
362
355
  </script>
363
356
  ```
364
357
 
365
- For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.1.2/dist/browser/washi.min.js` (or pin your desired release version `>= 0.1.2`). 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.
358
+ For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js` (specifying a compatible release version once published). For ESM use the corresponding `dist/browser/washi.js` URL. Selecting the CDN URL is an explicit choice; local copies never switch to a CDN.
366
359
 
367
360
  ### Solver Plugins in the Browser (GLPK & Z3)
368
361
 
369
- The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/` (available from `>= 0.2.0` or `@latest` on CDN):
362
+ The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/`:
370
363
  - **GLPK**: `dist/browser/solvers/glpk.js` (ESM) and `dist/browser/solvers/glpk.min.js` (IIFE, registers global `WashiGLPK`)
371
364
  - **Z3**: `dist/browser/solvers/z3.js` (ESM) and `dist/browser/solvers/z3.min.js` (IIFE, registers global `WashiZ3`), accompanied by `z3-built.js` and `z3-built.wasm`
372
365
 
373
366
  #### Browser ESM with GLPK
374
367
  ```html
375
368
  <script type="module">
376
- import { Model } from "./washi/washi.js";
369
+ import { Model, maximize } from "./washi/washi.js";
377
370
  import "./washi/solvers/glpk.js";
378
371
 
379
372
  const model = new Model({ solver: "glpk" });
380
- const x = model.addVar({ lb: 0, ub: 10 });
381
- model.maximize(x);
373
+ const x = model.continuous("x", { lb: 0, ub: 10 });
374
+ model.objective(maximize(x));
382
375
  await model.solve();
383
376
  console.log("GLPK status:", model.status);
384
377
  </script>
@@ -390,53 +383,35 @@ The browser distribution includes pre-bundled, standalone solver plugins under `
390
383
  <script src="./washi/solvers/glpk.min.js"></script>
391
384
  <script>
392
385
  const model = new Washi.Model({ solver: "glpk" });
393
- const x = model.addVar({ lb: 0, ub: 10 });
394
- model.maximize(x);
386
+ const x = model.continuous("x", { lb: 0, ub: 10 });
387
+ model.objective(Washi.maximize(x));
395
388
  model.solve().then(status => console.log("GLPK status:", status));
396
389
  </script>
397
390
  ```
398
391
 
399
392
  > [!NOTE]
400
393
  > **Z3 in Browser Environments & Cross-Origin Isolation**:
401
- > Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation. The HTML document response must send:
402
- > ```http
403
- > Cross-Origin-Opener-Policy: same-origin
404
- > Cross-Origin-Embedder-Policy: require-corp
405
- > ```
406
- > And any cross-origin assets must serve `Cross-Origin-Resource-Policy: cross-origin`.
407
- >
408
- > General static sites without custom header control (e.g. standard GitHub Pages or preview platforms) and third-party iframe embeds (e.g. CodePen / JSFiddle) do not satisfy these requirements without extra setup. Importing `@ohtaman/washi` never automatically injects or registers a Service Worker into consumer applications.
409
- >
410
- > To enable Z3 on GitHub Pages, the official WaSHI Web Demo bundles a dedicated `coi-serviceworker.js` strictly scoped to `/washi/`. On fresh browser sessions, it registers the worker and reloads once to establish `window.crossOriginIsolated === true` before running Z3. Environments lacking Service Workers or failing isolation abort Z3 execution with a descriptive explanation rather than hanging.
411
-
412
- Complete standalone examples for ESM, script tags, and CodePen are available in `examples/distribution/`.
413
-
414
- ### Offline distribution, Workers and custom hosting
415
-
416
- Deploy the full application build or the entire browser distribution to a local HTTP / intranet server. With all assets locally available, an empty browser cache does not require access to the internet. Saving only the HTML or the first JS file is insufficient. `file://` execution and automatic PWA caching are not supported by this distribution.
417
-
418
- ESM assets resolve relative to their module URL; the script build captures its own script URL at load time. CDN Workers use a Blob module bootstrap. The server must permit cross-origin JS / WASM loading; CSP must permit the selected script / connection origins and `worker-src blob:` for CDN Workers. If that is unavailable, use `useWorker: false`. Worker load / message failures reject pending solves rather than leaving them unresolved.
394
+ > Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`). See the [Cross-Origin Isolation Guide](docs/getting-started/cross-origin.md) and [Runtime Environments Guide](docs/getting-started/runtimes.md) for full details on static hosting, `coi-serviceworker.js`, offline/intranet deployment, CSP (`worker-src blob:`), and custom WASM URLs (`setHighsWasmUrl`).
419
395
 
420
- For custom hosting, `setHighsWasmUrl(url)` and `options.wasmUrl` remain available. To explicitly use the existing HiGHS CDN:
396
+ Complete standalone examples for ESM, script tags, and CodePen are available in [`examples/distribution/`](examples/distribution/README.md).
421
397
 
422
- ```typescript
423
- import { setHighsWasmUrl, OFFICIAL_HIGHS_CDN_WASM_URL } from "@ohtaman/washi";
424
- setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
425
- ```
398
+ ---
426
399
 
427
- Failed initialization reports the attempted URL and does not try a different host. This changes the previous local-root/CDN fallback behavior: deploy the generated assets or configure an explicit URL.
400
+ ## 🛠️ Custom Solvers & Extensibility
428
401
 
429
- ### Verify the distributable
402
+ WaSHI features a decoupled, pluggable solver architecture. You can easily build and register your own custom solver adapter (e.g., for cloud REST optimizers, WebAssembly engines, or proprietary solvers):
430
403
 
431
- `npm run test:distribution` packs and installs the actual npm tarball, builds Vite / webpack consumers without optional peers, and runs their emitted JS with real WASM in a VM. This is not a browser/CORS test. For Playwright Chromium checks, run `npx playwright install chromium` and `RUN_DISTRIBUTION_BROWSER=1 npm run test:distribution`. CI also runs that browser mode, including separate-origin browser bundles with external network requests blocked.
404
+ - See the complete [Custom Solver Developer Guide](docs/solvers/custom-solver.md) for step-by-step tutorials, complete API contracts (`Solver`, `SolveModelData`, `CheckModelData`, `SolverResult`), and testing best practices.
432
405
 
433
406
  ---
434
407
 
435
- ## 🛠️ Custom Solvers & Extensibility
408
+ ## 📚 Documentation
436
409
 
437
- 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):
410
+ Full documentation is available at **[ohtaman.github.io/washi](https://ohtaman.github.io/washi/)** and in the repository's [`docs/`](docs/) directory:
438
411
 
439
- - See the complete [Custom Solver Developer Guide](docs/custom-solver-guide.md) for step-by-step tutorials, complete API contracts (`Solver`, `LpModelData`, `SolverResult`), and testing best practices.
412
+ - **Getting Started**: [Quick Start](docs/getting-started/index.md) · [AI Agent Quick-Reference](docs/getting-started/ai-agents.md) · [Runtime Environments](docs/getting-started/runtimes.md) · [Cross-Origin Isolation (Z3)](docs/getting-started/cross-origin.md)
413
+ - **Modeling Guide**: [Variables (`Var`, `BoolVar`)](docs/guide/variables.md) · [Expressions (`expr`, `sum`, `dot`)](docs/guide/expressions.md) · [Constraints & Logic (`indicator`, SMT)](docs/guide/constraints.md) · [Objectives & Solving (`solve`, `validateSolution`)](docs/guide/objectives.md) · [Feasibility & Unsat Core (`check`)](docs/guide/unsat-core.md)
414
+ - **Solvers**: [Solver Overview](docs/solvers/index.md) · [HiGHS](docs/solvers/highs.md) · [Z3](docs/solvers/z3.md) · [GLPK](docs/solvers/glpk.md) · [Custom Solvers](docs/solvers/custom-solver.md)
440
415
 
441
416
  ---
442
417
 
@@ -446,13 +421,13 @@ Start with the [example gallery](https://github.com/ohtaman/washi/blob/main/exam
446
421
 
447
422
  | Category | Example | What it demonstrates | Solver |
448
423
  | :--- | :--- | :--- | :--- |
449
- | **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`, `c`) | HiGHS |
450
- | **Fundamentals** | [Transportation network](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 2D origin-destination variable matrix (`addVars(rows, cols)`) and flow balance | HiGHS |
424
+ | **Fundamentals** | [Production planning](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | Integer variables, a profit objective, and resource constraints (`expr`) | HiGHS |
425
+ | **Fundamentals** | [Transportation network](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 2D origin-destination variable matrix (`continuous(rows, cols)`) and flow balance | HiGHS |
451
426
  | **Fundamentals** | [Diet & sensitivity analysis](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | Shadow prices (`constr.dual`), surpluses (`constr.slack`), and reduced costs (`var.reducedCost`) | HiGHS |
452
427
  | **Practical** | [Staff shift scheduling](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | Weekly roster optimization + diagnosing conflicting Saturday vacation requests | HiGHS + Z3 |
453
- | **Practical** | [E-commerce coupon optimizer](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | Conditional spend thresholds (`addIndicator`), coupon exclusivity, and add-on recommendations | HiGHS |
428
+ | **Practical** | [E-commerce coupon optimizer](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | Conditional spend thresholds (`indicator`), coupon exclusivity, and add-on recommendations | HiGHS |
454
429
  | **Practical** | [Portfolio allocation](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) | A convex quadratic objective (`QuadExpr`) with allocation constraints | HiGHS |
455
- | **Logic / SMT** | [Business rule checks](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | Arithmetic and Boolean constraints (`addBoolVar`, `implies`) with satisfiability checking | Z3 |
430
+ | **Logic / SMT** | [Business rule checks](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | Arithmetic and Boolean constraints (`bool`, `implies`) with satisfiability checking | Z3 |
456
431
  | **Logic / SMT** | [Conflict diagnosis](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | Named constraints and an unsatisfiable core (`unsatCore`) | Z3 |
457
432
 
458
433
  [Open the hosted demo](https://ohtaman.github.io/washi/demo/) to edit and run models in your browser. For browser ESM, classic script tags, and CodePen, see the [distribution examples](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md). Z3 requires cross-origin isolation; see the browser requirements above.
@@ -477,7 +452,7 @@ The example sources import the repository's `src/` modules. In an application us
477
452
 
478
453
  ---
479
454
 
480
- ## 🧪 Testing
455
+ ## 🧪 Testing & Reproducible Benchmarks
481
456
 
482
457
  Run the repository's unit and integration tests, and build the distributions:
483
458
 
@@ -489,6 +464,24 @@ npm test
489
464
  npm run build
490
465
  ```
491
466
 
467
+ ### Optional Reproducible Benchmarks
468
+
469
+ To measure modeling and solver phases on deterministic `small-lp` (24 variables, 18 constraints) and `scheduling-mip` (315 variables, 231 constraints, including rebuilt `initialValue` warm starts) scenarios without mixing performance timings into correctness test thresholds:
470
+
471
+ ```bash
472
+ # Run Node.js in-process WASM benchmarks (HiGHS and GLPK, 5 warm iterations by default)
473
+ npm run bench
474
+
475
+ # Customize iteration count or emit structured JSON
476
+ node scripts/run-benchmarks.mjs --iterations=10 --json
477
+
478
+ # Also measure headless Chromium Web Worker execution (requires Playwright Chromium)
479
+ node scripts/run-benchmarks.mjs --browser
480
+ ```
481
+
482
+ - **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".
483
+ - **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.
484
+
492
485
  ---
493
486
 
494
487
  ## 📄 License