@ohtaman/washi 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.ja.md +103 -421
  2. package/README.md +103 -421
  3. package/README.zh-CN.md +103 -421
  4. package/THIRD_PARTY_LICENSES.md +5 -3
  5. package/dist/browser/solvers/glpk.js +2 -2
  6. package/dist/browser/solvers/glpk.min.js +1 -1
  7. package/dist/browser/washi.js +777 -261
  8. package/dist/browser/washi.min.js +3 -3
  9. package/dist/lib/{registry-C20sxTwm.js → registry-D7TZ3Wh_.js} +88 -18
  10. package/dist/lib/solvers/glpk.js +3 -3
  11. package/dist/lib/solvers/z3.js +1 -1
  12. package/dist/lib/washi.js +864 -421
  13. package/dist/types/index.d.ts +1 -1
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/modeler/expression.d.ts +47 -26
  16. package/dist/types/modeler/expression.d.ts.map +1 -1
  17. package/dist/types/modeler/index.d.ts +2 -2
  18. package/dist/types/modeler/index.d.ts.map +1 -1
  19. package/dist/types/modeler/model.d.ts +12 -6
  20. package/dist/types/modeler/model.d.ts.map +1 -1
  21. package/dist/types/modeler/parser.d.ts +25 -5
  22. package/dist/types/modeler/parser.d.ts.map +1 -1
  23. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  24. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  25. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  26. package/dist/types/modeler/solvers/types.d.ts +2 -0
  27. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  28. package/docs/examples/diet-sensitivity.md +15 -14
  29. package/docs/examples/ecommerce-coupons.md +15 -22
  30. package/docs/examples/index.md +14 -12
  31. package/docs/examples/knapsack.md +8 -7
  32. package/docs/examples/portfolio-qp.md +11 -4
  33. package/docs/examples/production.md +4 -4
  34. package/docs/examples/shift-scheduling.md +20 -7
  35. package/docs/examples/smt-security.md +16 -4
  36. package/docs/examples/transportation.md +46 -19
  37. package/docs/examples/traveling-salesperson.md +28 -44
  38. package/docs/examples/unsat-core.md +15 -5
  39. package/docs/examples/vehicle-routing.md +10 -8
  40. package/docs/getting-started/ai-agents.md +25 -14
  41. package/docs/getting-started/cross-origin.md +56 -28
  42. package/docs/getting-started/index.md +40 -69
  43. package/docs/getting-started/runtimes.md +66 -61
  44. package/docs/guide/constraints.md +46 -40
  45. package/docs/guide/decomposition.md +127 -259
  46. package/docs/guide/expressions.md +90 -39
  47. package/docs/guide/formulation-tips.md +212 -0
  48. package/docs/guide/objectives.md +7 -4
  49. package/docs/guide/unsat-core.md +18 -6
  50. package/docs/guide/variables.md +33 -38
  51. package/docs/ja/getting-started/ai-agents.md +24 -14
  52. package/docs/solvers/highs.md +27 -6
  53. package/docs/solvers/index.md +1 -1
  54. package/docs/solvers/z3.md +3 -0
  55. package/docs/zh/getting-started/ai-agents.md +24 -14
  56. package/llms-full.txt +929 -687
  57. package/llms.txt +21 -19
  58. package/package.json +3 -3
  59. package/skills/washi/SKILL.md +14 -10
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Problem Class | Solver | Key APIs | Links |
4
4
  | :--- | :--- | :--- | :--- |
5
- | Combinatorial OR / Dantzig-Wolfe | HiGHS (default) | `m.solveColumnGeneration()`, `pricing`, `sum()`, `m.binary()`, `m.constraint()`, `expr` | <ul><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/13-vehicle-routing-column-generation.ts">Source Code</a></li><li><a href="../guide/decomposition.md">Decomposition Guide</a></li></ul> |
5
+ | Combinatorial OR / Dantzig-Wolfe | HiGHS (default) | `m.solveColumnGeneration()`, `pricing`, `m.binary()`, `m.constraint()`, `expr` (`sum`) | <ul><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/13-vehicle-routing-column-generation.ts">Source Code</a></li><li><a href="/guide/decomposition">Decomposition Guide</a></li></ul> |
6
6
 
7
7
  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.
8
8
 
@@ -24,7 +24,7 @@ We consider a distribution network with a central depot at $(0, 0)$ and **50 cus
24
24
  <CodeRunner>
25
25
 
26
26
  ```typescript
27
- import { Model, expr, minimize, sum } from "@ohtaman/washi";
27
+ import { Model, expr, minimize } from "@ohtaman/washi";
28
28
 
29
29
  // --- Extracted Routing Helpers ---
30
30
 
@@ -141,13 +141,12 @@ async function runCVRP() {
141
141
 
142
142
  // Initial naive routes: 1 dedicated vehicle per customer
143
143
  const initialRoutes = customers.map((c) => master.binary(`route_init_${c.id}`));
144
- master.objective(minimize(sum(initialRoutes)));
144
+ master.objective(minimize(expr`sum(${initialRoutes})`));
145
145
 
146
146
  // Each customer must be visited by exactly 1 vehicle
147
- for (let i = 0; i < customers.length; i++) {
148
- master.constraint(expr`${initialRoutes[i]} == 1`, `visit_${customers[i].id}`);
149
- }
150
- const visitConstrs = master.constraints;
147
+ const visitConstrs = customers.map((c, i) =>
148
+ master.constraint(expr`${initialRoutes[i]} == 1`, `visit_${c.id}`)
149
+ );
151
150
 
152
151
  console.log(`Initial state: 50 naive single-customer routes (50 vehicles).`);
153
152
  console.log("Solving via Column Generation with dual-directed heuristic pricing...");
@@ -187,7 +186,7 @@ async function runCVRP() {
187
186
  console.log(`Total Columns Generated: ${result.columnsAdded}`);
188
187
 
189
188
  console.log("\nSample routes selected in fleet:");
190
- const activeRoutes = result.generatedColumns.filter((c) => (c.variable.value ?? 0) > 0.5);
189
+ const activeRoutes = result.generatedColumns.filter((c) => c.variable.value === 1);
191
190
  for (const r of activeRoutes.slice(0, 5)) {
192
191
  const meta = r.proposal.meta;
193
192
  console.log(` ${meta?.tour.join(" -> ")} (Transit: ${meta?.travelTime}m, Total: ${meta?.totalTime}m)`);
@@ -204,3 +203,6 @@ runCVRP();
204
203
  - **Master Problem and Pricing Separation**: The master problem solves the LP relaxation and final set-partitioning MIP, while the `pricing` callback searches for negative-reduced-cost routes using cheapest insertion and 2-opt local search.
205
204
  - **Dual Value Access**: Dual values from the master LP relaxation are read from `visitConstrs[i].dual` to prioritize customers with higher shadow prices during route construction.
206
205
  - **Route Metadata**: Each proposed column attaches `{ tour, travelTime, totalTime }` under `meta`, allowing the ordered stop sequence to be inspected from `result.generatedColumns` after the solve completes.
206
+ - **Set Partitioning (`== 1`) vs. Set Covering (`>= 1`)**: This example uses `== 1` so that the final MIP never covers a customer twice. In large-scale column generation, formulating coverage as `>= 1` restricts the dual prices to $\pi_i \ge 0$, avoiding negative dual oscillations; if the final MIP selects overlapping routes, duplicate visits can be removed by post-processing whenever distances satisfy the triangle inequality.
207
+ - **Heuristic Pricing vs. Exact ESPPRC**: Because `findImprovingRoute` uses a fast greedy heuristic (cheapest insertion + 2-opt) instead of an exact Elementary Shortest Path Problem with Resource Constraints (ESPPRC) solver, returning `null` means no further improving columns were found by the heuristic rather than proving global LP optimality.
208
+ - **Price-and-Branch (`finalizeAsMIP: true`)**: Solving the Restricted Master Problem as a MIP over the columns generated at the root node is known as the *Restricted Master Heuristic* (Price-and-Branch). It produces near-optimal integer fleets quickly without requiring full Branch-and-Price tree search.
@@ -57,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
57
57
  1. **Bundled WASM by default (no implicit CDN fallback)**:
58
58
  - In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
59
59
  - 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.
60
- - 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).
60
+ - 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).
61
61
  2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
62
62
  - 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:
63
63
  - `Cross-Origin-Opener-Policy: same-origin` (on the document)
@@ -66,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
66
66
  - 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.
67
67
  3. **Web Workers & Cancellation (`AbortSignal`)**:
68
68
  - Pre-solve cancellation (`signal.aborted` before execution starts) is supported across all solvers.
69
- - 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).
69
+ - 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).
70
70
  - 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.
71
71
 
72
72
  ---
@@ -82,6 +82,7 @@ const m = new Model("ExampleModel");
82
82
 
83
83
  // Scalar variables
84
84
  const x = m.continuous("x", { lb: 0, ub: 100 });
85
+ const y = m.continuous("y", { lb: 0, ub: 100 });
85
86
  const n = m.integer("n", { lb: 0, ub: 10 });
86
87
  const b = m.binary("b");
87
88
  const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
@@ -103,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
103
104
  ### Step 2: Build Expressions (`expr`, `sum`, `dot`)
104
105
 
105
106
  - **Unified `expr` template tag**:
106
- - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
107
- - Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${n} + ${n}^2` `` (variable-by-variable products and squares are supported for convex QP objectives).
108
- - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
109
- - **Aggregations**:
110
- - `sum(iterable, mapper?)` sums an `Iterable` of `Var` / `LinExpr` / `number` (e.g., `Var[]`, `grid.flat()`, or `Object.values(record)`).
111
- - `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
+ - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``, `` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``, `` expr`sum((${price} - ${cost}) * ${byItem})` ``
108
+ - 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).
109
+ - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``, `` expr`sum(${byItem}) <= 100` `` (supports `<=`, `>=`, `==`, and `=`).
110
+ - **Aggregations & Element-Wise Container Arithmetic**:
111
+ - `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.
112
+ - `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.
112
113
  - **Fluent methods**:
113
114
  - Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
114
115
  - Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
@@ -122,8 +123,8 @@ Constructing an `expr` or `.le()` object does **not** attach it to the model unt
122
123
  // Set objective using maximize() or minimize()
123
124
  m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
124
125
 
125
- // Register constraints on the model
126
- m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
126
+ // Register constraints on the model (returns the registered Constraint / BoolExpr or array)
127
+ const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
127
128
 
128
129
  // Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
129
130
  m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
@@ -161,10 +162,13 @@ export async function runMipExample() {
161
162
  const qty = m.integer(products, { lb: 0, ub: 100 });
162
163
  const setup = m.binary("setup");
163
164
 
164
- m.objective(maximize(expr`${dot(profit, qty)} - 20 * ${setup}`));
165
- m.constraint(expr`${dot(woodPerUnit, qty)} <= 40`, "wood_cap");
166
- m.constraint(expr`${dot(laborPerUnit, qty)} <= 50`, "labor_cap");
167
- m.constraint(expr`${sum(Object.values(qty))} <= 30 * ${setup}`, "require_setup");
165
+ m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${setup}`));
166
+ m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_cap");
167
+ m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_cap");
168
+ m.indicator(setup, expr`sum(${qty}) <= 0`, {
169
+ activeValue: 0,
170
+ name: "require_setup",
171
+ });
168
172
 
169
173
  const status = await m.solve();
170
174
  const report = m.validateSolution();
@@ -269,3 +273,10 @@ export function verifyNegativePatterns() {
269
273
  }
270
274
  ```
271
275
 
276
+ ### Practical OR Modeling Guardrails
277
+
278
+ - **Prefer inequalities (`>=` / `<=`) over exact equalities (`==`)** for demand coverage and resource limits when objective costs are positive; reserve `==` for physical conservation laws (e.g., flow balance, portfolio weights summing to `1`).
279
+ - **Always provide finite `lb` and `ub` bounds** on variables referenced inside `m.indicator(...)` and omit `M` so WaSHI automatically derives the tightest valid Big-M instead of falling back to `1e6`.
280
+ - **Use soft constraints (elastic slack variables)** for non-rigid business targets (e.g., preferred staffing, safety stock, day-off requests) by adding a non-negative slack variable (`lb: 0`) with a tiered objective penalty, reserving hard constraints for physical or legal invariants.
281
+
282
+
@@ -1,47 +1,70 @@
1
1
  # Cross-Origin Isolation (Z3 & Browsers)
2
2
 
3
- When running the **Z3** solver engine inside a web browser, special HTTP headers are required due to browser security models.
3
+ When running the **Z3** solver inside a web browser, Cross-Origin Isolation HTTP headers are required so the browser enables `SharedArrayBuffer`.
4
+
5
+ > [!TIP]
6
+ > **Not needed for HiGHS, GLPK, or Node.js**: The built-in **HiGHS** solver and **GLPK** plugin run in standard browser contexts without Cross-Origin Isolation or `SharedArrayBuffer`. Running Z3 in **Node.js** also requires no HTTP headers. You only need this page when using `@ohtaman/washi/solvers/z3` in the browser.
4
7
 
5
8
  ---
6
9
 
7
10
  ## Why is Cross-Origin Isolation Required?
8
11
 
9
- The Z3 WebAssembly engine utilizes multithreading backed by `SharedArrayBuffer` and Web Workers.
10
-
11
- In modern browsers (Chrome, Firefox, Safari, Edge), `SharedArrayBuffer` is disabled by default to mitigate Spectre-style timing side-channel attacks. To re-enable it, the hosting page must be **Cross-Origin Isolated**.
12
+ Z3's WebAssembly build uses multithreading (pthreads) backed by `SharedArrayBuffer` and Web Workers. Modern browsers (Chrome, Firefox, Safari, Edge) disable `SharedArrayBuffer` by default to mitigate Spectre-style side-channel attacks unless the top-level document opts into **Cross-Origin Isolation** (`window.crossOriginIsolated === true`).
12
13
 
13
14
  ---
14
15
 
15
16
  ## Required HTTP Response Headers
16
17
 
17
- To establish cross-origin isolation, your web server must serve the top-level HTML document with the following HTTP headers:
18
+ Serve the top-level HTML document with:
18
19
 
19
20
  ```http
20
21
  Cross-Origin-Opener-Policy: same-origin
21
22
  Cross-Origin-Embedder-Policy: require-corp
22
23
  ```
23
24
 
24
- In addition, any cross-origin assets (e.g. scripts or WASM files loaded from external domains) must send:
25
+ If your page loads cross-origin subresources (such as scripts, images, or WASM files from another domain), those responses must also include `Cross-Origin-Resource-Policy: cross-origin` (or CORS headers).
25
26
 
26
- ```http
27
- Cross-Origin-Resource-Policy: cross-origin
28
- ```
27
+ > [!NOTE]
28
+ > Chromium and Firefox also support `Cross-Origin-Embedder-Policy: credentialless`, which relaxes subresource CORP requirements, **but Safari (WebKit) does not support `credentialless`**. Use `require-corp` if you support Safari / iOS.
29
29
 
30
30
  ---
31
31
 
32
- ## Solutions by Hosting Environment
32
+ ## Setup by Environment
33
33
 
34
- ### 1. Custom Node.js / Express Server
34
+ ### 1. Vite (`vite.config.ts`)
35
35
 
36
- ```javascript
37
- app.use((req, res, next) => {
38
- res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
39
- res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
40
- next();
36
+ Configure both the local dev server (`vite`) and production preview server (`vite preview`):
37
+
38
+ ```typescript
39
+ import { defineConfig } from "vite";
40
+
41
+ export default defineConfig({
42
+ server: {
43
+ headers: {
44
+ "Cross-Origin-Opener-Policy": "same-origin",
45
+ "Cross-Origin-Embedder-Policy": "require-corp",
46
+ },
47
+ },
48
+ preview: {
49
+ headers: {
50
+ "Cross-Origin-Opener-Policy": "same-origin",
51
+ "Cross-Origin-Embedder-Policy": "require-corp",
52
+ },
53
+ },
41
54
  });
42
55
  ```
43
56
 
44
- ### 2. Next.js (`next.config.js`)
57
+ ### 2. Cloudflare Pages / Netlify (`public/_headers`)
58
+
59
+ Place a `_headers` file in `public/` so it is copied to the root of your build output:
60
+
61
+ ```text
62
+ /*
63
+ Cross-Origin-Opener-Policy: same-origin
64
+ Cross-Origin-Embedder-Policy: require-corp
65
+ ```
66
+
67
+ ### 3. Next.js (`next.config.js`)
45
68
 
46
69
  ```javascript
47
70
  /** @type {import('next').NextConfig} */
@@ -62,26 +85,31 @@ const nextConfig = {
62
85
  module.exports = nextConfig;
63
86
  ```
64
87
 
65
- ### 3. Static Hosting without Header Control (e.g., GitHub Pages)
88
+ ### 4. Custom Node.js / Express Server
89
+
90
+ ```javascript
91
+ app.use((req, res, next) => {
92
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
93
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
94
+ next();
95
+ });
96
+ ```
66
97
 
67
- Platforms like GitHub Pages or preview environments do not allow customizing response headers directly.
98
+ ### 5. Static Hosting without Header Control (e.g., GitHub Pages)
68
99
 
69
- For these environments, you can use **`coi-serviceworker`** (a lightweight Service Worker that injects isolation headers on the client side):
100
+ Platforms like GitHub Pages do not allow customizing HTTP response headers directly. You can use **`coi-serviceworker`** to inject the isolation headers via a client-side Service Worker:
70
101
 
71
- 1. Place `coi-serviceworker.js` in your website root.
72
- 2. In your HTML `<head>`, include:
102
+ 1. Place `coi-serviceworker.js` at your site root.
103
+ 2. Load it in your HTML `<head>`:
73
104
 
74
105
  ```html
75
106
  <script src="/coi-serviceworker.js"></script>
76
107
  ```
77
108
 
78
- When a user visits for the first time, the service worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
109
+ On the first visit, the Service Worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
79
110
 
80
111
  ---
81
112
 
82
- ## Graceful Degradation in WaSHI
83
-
84
- WaSHI's Z3 solver adapter automatically inspects the environment at runtime. If Z3 is invoked in a browser lacking cross-origin isolation, it immediately rejects with a clear, informative error message explaining that COOP/COEP headers or a service worker are required, rather than hanging silently.
113
+ ## Runtime Check in WaSHI
85
114
 
86
- > [!TIP]
87
- > The default **HiGHS** solver engine does not require cross-origin isolation or `SharedArrayBuffer`. It works out of the box in standard browser contexts.
115
+ WaSHI's Z3 adapter checks `crossOriginIsolated` before initializing Z3 in the browser. If Cross-Origin Isolation is missing, `solve()` / `check()` immediately rejects with an actionable error explaining that COOP/COEP headers or a Service Worker are required, rather than hanging silently.
@@ -1,19 +1,35 @@
1
1
  # Introduction & Installation
2
2
 
3
- Welcome to **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface).
3
+ **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) brings the algebraic modeling style of Python's **PuLP** to the TypeScript and JavaScript ecosystem. Write expressions naturally with tagged templates (`expr`) and solve mathematical optimization problems directly in **Node.js and the browser** using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**).
4
4
 
5
- WaSHI brings the algebraic modeling style of Python's **PuLP** and Julia's **JuMP** to the TypeScript and JavaScript ecosystem. You can formulate mathematical optimization problems using algebraic expressions and solve them locally using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**)—without a separate Python service or native C/C++ build steps.
5
+ ```typescript
6
+ import { Model, expr, maximize } from "@ohtaman/washi";
7
+
8
+ const m = new Model("production");
6
9
 
7
- ### Why TypeScript & WebAssembly?
10
+ // 1. Define decision variables with realistic finite bounds (lb / ub)
11
+ const desks = m.integer("desks", { lb: 0, ub: 100 });
12
+ const chairs = m.integer("chairs", { lb: 0, ub: 100 });
8
13
 
9
- - **Removing the deployment bottleneck**: In traditional optimization workflows (e.g., Python with PuLP or Pyomo), turning a model into an interactive application requires provisioning a backend service and managing native C/C++ solver binaries. As AI-assisted development accelerates UI and application prototyping, backend infrastructure often becomes the primary bottleneck to shipping. Running both the modeler and WebAssembly solvers on standard Web technologies allows an optimization tool to be deployed as static assets (e.g., on GitHub Pages or Cloudflare Pages, serving the generated JS, Worker, and WASM files; with COOP/COEP headers when using Z3)—and makes it straightforward to pair mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) entirely on the client.
10
- - **Algebraic syntax without operator overloading (`expr`)**: Because JavaScript lacks operator overloading, libraries usually trade off between verbose method chaining (`x.mul(30).add(y.mul(50))`) and untyped strings (`"30 * x + 50 * y"`). WaSHI uses tagged template literals (`` expr`30 * ${x} + 50 * ${y} <= 40` ``) similar to parameterized SQL queries—parsing infix operators while binding `${x}` directly as typed TypeScript object references for static type checking, IDE symbol renaming, and multi-dimensional indexing (`${flow[i][j]}`).
14
+ // 2. Set the objective function and constraints
15
+ m.objective(maximize(expr`30 * ${desks} + 50 * ${chairs}`));
16
+ m.constraint(expr`${desks} + 2 * ${chairs} <= 40`, "wood_capacity");
17
+ m.constraint(expr`2 * ${desks} + ${chairs} <= 50`, "labor_capacity");
18
+
19
+ // 3. Solve (built-in HiGHS WebAssembly) and validate the solution
20
+ const status = await m.solve();
21
+ if (m.hasPrimalSolution) {
22
+ const report = m.validateSolution();
23
+ console.log(status, report.valid); // "Optimal", true
24
+ console.log(`Profit: ${m.objectiveValue} (desks=${desks.value}, chairs=${chairs.value})`); // 1100 (desks=20, chairs=10)
25
+ }
26
+ ```
11
27
 
12
28
  ---
13
29
 
14
30
  ## 📦 Installation
15
31
 
16
- Install WaSHI using your preferred package manager:
32
+ The default **HiGHS** solver (LP / MIP / Convex QP) is bundled and works out of the box:
17
33
 
18
34
  ::: code-group
19
35
 
@@ -35,82 +51,37 @@ bun add @ohtaman/washi
35
51
 
36
52
  :::
37
53
 
38
- ### Optional Solver Dependencies
39
-
40
- WaSHI ships with **HiGHS** built-in with zero configuration. If you need SMT logic / Unsat Core diagnosis via **Z3**, or compatibility with **GLPK**, install their respective peer dependencies:
54
+ Install optional peer dependencies only if you need **Z3** (SMT logic & Unsat Core diagnosis) or **GLPK**:
41
55
 
42
56
  ```bash
43
- # For SMT logic and Unsat Core conflict diagnosis
44
- npm install z3-solver
45
-
46
- # For GLPK solver (GPL-3.0)
47
- npm install glpk.js
57
+ npm install z3-solver # For SMT logic and Unsat Core conflict diagnosis
58
+ npm install glpk.js # For GLPK solver (GPL-3.0)
48
59
  ```
49
60
 
50
61
  ---
51
62
 
52
- ## 🚀 Quickstart Walkthrough
53
-
54
- Here is a step-by-step example of solving a production allocation problem.
55
-
56
- ### 1. Import the Core Classes
57
-
58
- ```typescript
59
- import { Model, expr, maximize } from "@ohtaman/washi";
60
- ```
61
-
62
- ### 2. Instantiate a Model
63
-
64
- ```typescript
65
- const model = new Model("WoodworkingProfit");
66
- ```
67
-
68
- ### 3. Add Decision Variables
63
+ ## 💡 Why TypeScript & WebAssembly?
69
64
 
70
- Variables represent the unknowns you want to determine. Factory methods (`m.continuous`, `m.integer`, `m.binary`) clearly define their domain:
65
+ - **Zero native setup across Node.js and the browser**: No platform-specific C/C++ builds or separate Python runtimes to manage. A single `npm install` runs the same modeling code across **Node.js APIs, CLIs, and scripts** as well as in the **browser**.
66
+ - **Lightweight embedding with standard Web APIs & UIs**: Go beyond local analysis scripts by shipping **interactive Web tools, mini-games, or Chrome extensions** that anyone can open via a URL with zero backend hosting cost. You can wire mathematical optimization directly into drag-and-drop UIs or **standard browser capabilities**—such as screen/camera capture and speech recognition (Web Speech API)—with no network round-trips or external data transfer.
67
+ - **End-to-end type safety from app data to model**: Prevent common indexing and dictionary-key mistakes with TypeScript's type system. Typed variable dictionaries (`Record<K, Var>`) and variable-reference interpolation (`` expr`30 * ${qty.desk} <= 40` ``) connect directly to your application schemas, preserving compile-time variable/key checks, IDE autocompletion, and symbol renaming.
68
+ - **Strong synergy with AI development & LLM workflows**: Keeping your application and optimization model in a **single TypeScript codebase** lets you validate everything in one test runner—without crossing a Python service boundary—and deploy easily as static assets or a single service. That tight test-and-deploy loop fits AI-assisted coding naturally, while at runtime you can pair flexible LLM extraction with exact solver feasibility and conflict diagnosis (`unsatCore`) in the same runtime.
71
69
 
72
- ```typescript
73
- const x = model.integer("x", { lb: 0 });
74
- const y = model.integer("y", { lb: 0 });
75
- ```
76
-
77
- ### 4. Set the Objective Function
78
-
79
- Use the `expr` tagged template literal along with `maximize(...)` or `minimize(...)` to define the goal:
80
-
81
- ```typescript
82
- // Maximize 30*x + 50*y
83
- model.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
84
- ```
85
-
86
- ### 5. Add Constraints
87
-
88
- Use `model.constraint()` with the `expr` tagged template to formulate equality and inequality constraints:
89
-
90
- ```typescript
91
- model.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_capacity");
92
- model.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_capacity");
93
- ```
94
-
95
- ### 6. Solve and Extract Results
70
+ ---
96
71
 
97
- Solve the model asynchronously via WebAssembly (HiGHS by default):
72
+ ## 🔍 Core Workflow Essentials
98
73
 
99
- ```typescript
100
- const status = await model.solve();
74
+ Three practical habits cover the essentials shown in the snippet above:
101
75
 
102
- console.log("Status:", status); // "Optimal"
103
- console.log("Optimal Objective Value:", model.objectiveValue); // 1100
104
- console.log(`Solution: x = ${x.value}, y = ${y.value}`); // x = 20, y = 10
105
- ```
76
+ 1. **Set realistic finite bounds (`lb`, `ub`)**: When defining variables with `m.continuous`, `m.integer`, or `m.binary`, finite bounds strengthen MIP presolve and enable automatic tight Big-M derivation for logical constraints.
77
+ 2. **Formulate objectives and constraints with `expr`**: Use `` m.objective(maximize(expr`...`)) `` and `` m.constraint(expr`... <= ...`, "name") ``. Naming constraints lets you pinpoint conflicting rules immediately if a model becomes infeasible.
78
+ 3. **Check `hasPrimalSolution` and call `validateSolution()`**: After `await m.solve()`, check `m.hasPrimalSolution` before reading `.value` on variables, and call `m.validateSolution()` to independently verify that bounds, integrality, and constraints hold within tolerance.
106
79
 
107
80
  ---
108
81
 
109
82
  ## 🧭 Next Steps
110
83
 
111
- - Give AI coding agents a concise API & deployment reference with the [AI Coding Agent Quick-Reference Guide](./ai-agents.md).
112
- - Learn how to run WaSHI across different environments in [Runtime Environments](./runtimes.md).
113
- - Understand how to formulate complex expressions in the [Modeling Guide](../guide/expressions.md).
114
- - Explore built-in and pluggable solvers in the [Solvers Matrix](../solvers/).
115
- - Browse 9 runnable optimization and SMT recipes in [Examples & API Finder](../examples/).
116
-
84
+ - **[Modeling Guide](../guide/variables.md)**: Array/dictionary variables, aggregations (`sum`, `dot`), logical constraints, and Unsat Core diagnosis
85
+ - **[Examples & API Finder](../examples/)**: 11 runnable optimization and SMT recipes—from shift scheduling and portfolio QP to vehicle routing
86
+ - **[Solvers Overview](../solvers/)**: Compare built-in HiGHS with optional Z3 and GLPK adapters
87
+ - **[Runtime Environments](./runtimes.md)**: Setup details for Node.js, Vite / Next.js bundlers, CDNs, and static hosting