@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
@@ -1,12 +1,14 @@
1
1
  # Runtime Environments & Deployment
2
2
 
3
- 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.
3
+ WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, static hosting, and vanilla browser `<script>` tags—without native C/C++ compilation.
4
4
 
5
5
  ---
6
6
 
7
- ## 🟢 Node.js
7
+ ## 🟢 Node.js, Bun & Deno
8
8
 
9
- WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package.
9
+ ### Node.js
10
+
11
+ WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package:
10
12
 
11
13
  ```typescript
12
14
  import { Model, expr, maximize } from "@ohtaman/washi";
@@ -19,8 +21,10 @@ m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
19
21
  m.constraint(expr`${x} + ${y} <= 15`, "capacity");
20
22
 
21
23
  await m.solve();
22
- console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
23
- console.log(`x = ${x.value}, y = ${y.value}`);
24
+ if (m.hasPrimalSolution) {
25
+ console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
26
+ console.log(`x = ${x.value}, y = ${y.value}`);
27
+ }
24
28
  ```
25
29
 
26
30
  Run directly with `tsx` or `node`:
@@ -29,11 +33,33 @@ Run directly with `tsx` or `node`:
29
33
  npx tsx app.ts
30
34
  ```
31
35
 
36
+ ### Bun & Deno
37
+
38
+ ```bash
39
+ # Bun
40
+ bun add @ohtaman/washi
41
+ bun run app.ts
42
+ ```
43
+
44
+ In Deno (v1.30+ with `npm:` specifiers):
45
+
46
+ ```typescript
47
+ import { Model, maximize } from "npm:@ohtaman/washi";
48
+
49
+ const m = new Model();
50
+ const x = m.continuous("x", { lb: 0, ub: 10 });
51
+ m.objective(maximize(x));
52
+ await m.solve();
53
+ if (m.hasPrimalSolution) {
54
+ console.log("x =", x.value);
55
+ }
56
+ ```
57
+
32
58
  ---
33
59
 
34
- ## 🌐 Bundlers (Vite, Webpack, Rollup, Next.js)
60
+ ## 🌐 Bundlers & Static Hosting (Vite, Next.js, Webpack, Rollup)
35
61
 
36
- ### Modern Bundlers (Vite / Rollup / Webpack)
62
+ ### Modern Bundlers & Static Deployment
37
63
 
38
64
  When importing `@ohtaman/washi` in a browser bundler, the `browser` export condition resolves automatically:
39
65
 
@@ -45,14 +71,16 @@ export async function runOptimization() {
45
71
  const x = m.continuous("x", { lb: 0, ub: 100 });
46
72
  m.objective(maximize(x));
47
73
  await m.solve();
48
- return x.value;
74
+ return m.hasPrimalSolution ? x.value : null;
49
75
  }
50
76
  ```
51
77
 
78
+ Because both the modeler and solver run on WebAssembly, the static output of `vite build` (or your bundler's production build) can be deployed directly to **GitHub Pages, Cloudflare Pages, Vercel, or Netlify** with no backend server required. (The default HiGHS and GLPK solvers require no special HTTP headers; if you use Z3 in the browser, see [Cross-Origin Isolation](./cross-origin.md).)
79
+
52
80
  #### Background Web Worker Execution (Default)
53
- In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so long-running LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (`onProgress`) and cancellation (`AbortSignal`).
81
+ In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (`onProgress`) and cancellation (`AbortSignal`).
54
82
 
55
- If your environment prohibits Web Workers or you explicitly want synchronous main-thread execution, pass `useWorker: false`:
83
+ If your environment prohibits Web Workers or you explicitly want main-thread execution, pass `useWorker: false`:
56
84
 
57
85
  ```typescript
58
86
  await m.solve({ useWorker: false });
@@ -91,7 +119,9 @@ export default function OptimizerComponent() {
91
119
  const x = m.continuous("x", { lb: 0, ub: 50 });
92
120
  m.objective(maximize(x));
93
121
  await m.solve();
94
- setVal(x.value);
122
+ if (m.hasPrimalSolution) {
123
+ setVal(x.value);
124
+ }
95
125
  };
96
126
 
97
127
  return (
@@ -107,36 +137,36 @@ export default function OptimizerComponent() {
107
137
 
108
138
  ## 📦 Browser ESM (`<script type="module">`)
109
139
 
110
- You can import the pre-bundled browser ESM modules directly from a local static server (or from an npm CDN once a compatible release is published) without a build step:
140
+ Without a bundler, you can import the pre-built browser ESM module directly from your own static server or an npm CDN:
111
141
 
112
142
  ```html
113
143
  <script type="module">
114
- // Serve from your local static server / dist:
115
- import { Model, expr, maximize } from "./dist/browser/washi.js";
116
- // Or from an npm CDN once a compatible release is published:
117
- // import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.js";
144
+ // From an npm CDN (or "./dist/browser/washi.js" when self-hosted):
145
+ import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.js";
118
146
  // Optional solver plugins:
119
- // import "./dist/browser/solvers/glpk.js";
120
- // import "./dist/browser/solvers/z3.js";
147
+ // import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.js";
148
+ // import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.js";
121
149
 
122
150
  const m = new Model("BrowserESM");
123
- const x = m.integer("x", { lb: 0 });
124
- const y = m.integer("y", { lb: 0 });
151
+ const x = m.integer("x", { lb: 0, ub: 100 });
152
+ const y = m.integer("y", { lb: 0, ub: 100 });
125
153
 
126
154
  m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
127
155
  m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
128
156
  m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor");
129
157
 
130
158
  await m.solve();
131
- console.log("Optimal Objective:", m.objectiveValue);
159
+ if (m.hasPrimalSolution) {
160
+ console.log("Optimal Objective:", m.objectiveValue);
161
+ }
132
162
  </script>
133
163
  ```
134
164
 
135
165
  ---
136
166
 
137
- ## 🟨 Classic `<script>` Tags (IIFE / Standalone Build / CDN)
167
+ ## 🟨 Classic `<script>` Tags (IIFE / Standalone HTML / CDN)
138
168
 
139
- For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard `<script>` tag. All exports are attached to the global `window.Washi` object:
169
+ For single-file HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard `<script>` tag. All exports are attached to the global `window.Washi` object:
140
170
 
141
171
  ```html
142
172
  <!DOCTYPE html>
@@ -149,13 +179,11 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
149
179
  <h1>WaSHI Browser Optimization</h1>
150
180
  <pre id="output">Solving...</pre>
151
181
 
152
- <!-- Core WaSHI + HiGHS (MIT) from local static build / assets -->
153
- <script src="./dist/browser/washi.min.js"></script>
154
- <!-- Or via CDN once a compatible release is published: -->
155
- <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js"></script> -->
182
+ <!-- Core WaSHI + HiGHS (MIT) via CDN (or "./dist/browser/washi.min.js" when self-hosted) -->
183
+ <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.min.js"></script>
156
184
  <!-- Optional plugins (auto-register on window.Washi): -->
157
- <!-- <script src="./dist/browser/solvers/glpk.min.js"></script> -->
158
- <!-- <script src="./dist/browser/solvers/z3.min.js"></script> -->
185
+ <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.min.js"></script> -->
186
+ <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.min.js"></script> -->
159
187
 
160
188
  <script>
161
189
  (async () => {
@@ -170,10 +198,12 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
170
198
 
171
199
  await m.solve();
172
200
 
173
- document.getElementById("output").textContent =
174
- `Status: ${m.status}\n` +
175
- `Objective: ${m.objectiveValue}\n` +
176
- `x = ${x.value}, y = ${y.value}`;
201
+ if (m.hasPrimalSolution) {
202
+ document.getElementById("output").textContent =
203
+ `Status: ${m.status}\n` +
204
+ `Objective: ${m.objectiveValue}\n` +
205
+ `x = ${x.value}, y = ${y.value}`;
206
+ }
177
207
  })();
178
208
  </script>
179
209
  </body>
@@ -181,7 +211,7 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
181
211
  ```
182
212
 
183
213
  > [!NOTE]
184
- > When loading `washi.min.js` from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct `new Worker("https://cdn...")` construction due to the same-origin policy. WaSHI automatically bridges cross-origin workers via a lightweight `Blob` wrapper using `importScripts()`, so Web Worker execution works out of the box from CDNs.
214
+ > When loading from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct `new Worker("https://cdn...")` construction under the same-origin policy. On standard web origins (`https://` / `http://`), WaSHI automatically bridges cross-origin workers via a lightweight ES module `Blob` bootstrap (`import "https://..."`). On opaque effective origins (`window.origin === "null"`, such as local `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), WaSHI automatically runs HiGHS on the main thread when `useWorker` is omitted.
185
215
 
186
216
  ---
187
217
 
@@ -195,33 +225,8 @@ WaSHI never phones home or falls back to public CDNs. To self-host in an air-gap
195
225
  - Optional Z3: `solvers/z3.js` / `solvers/z3.min.js`, `solvers/z3-built.js`, `solvers/z3-built.wasm`
196
226
  2. **Content Security Policy (CSP) Headers**:
197
227
  - Allow WebAssembly compilation (`'wasm-unsafe-eval'` in `script-src`).
198
- - If using the default Web Worker or cross-origin Blob worker fallback, allow `worker-src 'self' blob:;`.
228
+ - If using the default Web Worker or cross-origin Blob worker bootstrap, allow `worker-src 'self' blob:;`.
199
229
  - If `blob:` workers are prohibited by policy, either serve `highs-worker.js` from `'self'` or run on the main thread with `await m.solve({ useWorker: false })`.
200
230
  3. **Cross-Origin Isolation (Z3 Only)**:
201
231
  - HiGHS and GLPK run in standard browser contexts without special headers.
202
- - Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (or `credentialless`). See the [Cross-Origin Isolation Guide](./cross-origin.md) for setup details.
203
-
204
- ---
205
-
206
- ## ⚡ Bun & Deno
207
-
208
- ### Bun
209
-
210
- ```bash
211
- bun add @ohtaman/washi
212
- bun run app.ts
213
- ```
214
-
215
- ### Deno
216
-
217
- In Deno (v1.30+ with `npm:` specifiers):
218
-
219
- ```typescript
220
- import { Model, maximize } from "npm:@ohtaman/washi";
221
-
222
- const m = new Model();
223
- const x = m.continuous("x", { lb: 0, ub: 10 });
224
- m.objective(maximize(x));
225
- await m.solve();
226
- console.log("x =", x.value);
227
- ```
232
+ - Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (`credentialless` works in Chromium/Firefox, whereas Safari requires `require-corp`). See the [Cross-Origin Isolation Guide](./cross-origin.md) for setup details.
@@ -1,17 +1,17 @@
1
1
  # Constraints (`m.constraint` & `m.indicator`)
2
2
 
3
3
  Constraints restrict the feasible solution space of your mathematical or logical model.
4
- In WaSHI, both linear arithmetic constraints and propositional SMT formulas are registered using **`m.constraint()`**:
4
+ In WaSHI, both linear arithmetic constraints and propositional logic formulas are registered using **`m.constraint()`**:
5
5
 
6
6
  ```typescript
7
- m.constraint(expr`${desks} + 2 * ${chairs} <= 40`, "wood_limit");
7
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
8
8
  ```
9
9
 
10
10
  ---
11
11
 
12
- ## ✍️ Algebraic Linear Constraints (`expr`)
12
+ ## Algebraic Linear Constraints (`expr`)
13
13
 
14
- The `expr` tagged template literal syntax allows writing equations and inequalities with natural mathematical notation:
14
+ The `expr` tagged template literal allows writing equations and inequalities with standard mathematical notation:
15
15
 
16
16
  ```typescript
17
17
  import { Model, expr } from "@ohtaman/washi";
@@ -30,13 +30,15 @@ m.constraint(expr`3 * ${x} + ${y} >= 20`, "min_demand");
30
30
  m.constraint(expr`${x} + ${y} == 30`, "exact_quota");
31
31
  ```
32
32
 
33
+ `m.constraint()` returns the registered `Constraint` (or `BoolExpr` for logical formulas), or an array when passed a batch array.
34
+
33
35
  ### Supported Comparison Operators
34
36
  - `<=`: Upper bound constraint
35
37
  - `>=`: Lower bound constraint
36
38
  - `==` or `=`: Exact equality constraint
37
39
 
38
40
  ### Batch Registration
39
- You can pass an array of constraints to `m.constraint([...])` to register them transactionally in a single call:
41
+ Pass an array of constraints to `m.constraint([...])` to register them transactionally in a single call:
40
42
 
41
43
  ```typescript
42
44
  m.constraint([expr`${x} <= 10`, expr`${y} <= 20`]);
@@ -44,50 +46,51 @@ m.constraint([expr`${x} <= 10`, expr`${y} <= 20`]);
44
46
 
45
47
  ---
46
48
 
47
- ## 🏷️ Naming Constraints
49
+ ## Naming Constraints
48
50
 
49
- Every constraint can be given a unique, descriptive name as the second argument of `m.constraint(c, name)`:
51
+ Pass a unique, descriptive name as the second argument of `m.constraint(c, name)`:
50
52
 
51
53
  ```typescript
52
54
  m.constraint(expr`${x} + ${y} <= 100`, "budget_cap");
53
55
  ```
54
56
 
55
57
  ### Why Name Constraints?
56
- 1. **Model Inspection (`m.toLpString()`)**: Exported CPLEX LP strings retain these descriptive names (sanitized if needed) so you can inspect the exact generated formulation.
57
- 2. **Conflict Diagnosis (`conflicts` / `unsatCore`)**: When constraints contradict each other, Z3 `m.check()` returns the exact constraint names forming the conflicting subset.
58
+ 1. **Model Inspection (`m.toLpString()`)**: Exported CPLEX LP strings retain these descriptive names (sanitized if needed) so you can inspect the generated formulation.
59
+ 2. **Conflict Diagnosis (`conflicts` / `unsatCore`)**: When constraints contradict each other, `m.check()` returns the conflicting constraint names (via built-in HiGHS elastic relaxation or Z3 `unsatCore`).
58
60
  3. **Direct Lookup (`m.getConstraintByName()`)**: Retrieve a constraint by name after solving to inspect its `dual` or `slack` value.
59
61
 
60
62
  ---
61
63
 
62
- ## 🔁 Adding Constraints over Iterables & Sums
64
+ ## Aggregated Constraints (`sum` & `dot`)
63
65
 
64
- Use `sum()` or `dot()` inside `expr` to express linear aggregations cleanly:
66
+ Use `sum(...)` and `dot(..., ...)` directly inside `expr` to express linear aggregations over arrays and dictionaries cleanly:
65
67
 
66
68
  ```typescript
67
- import { Model, sum, expr } from "@ohtaman/washi";
69
+ import { Model, expr } from "@ohtaman/washi";
68
70
 
69
- const items = ["A", "B", "C"];
71
+ const m = new Model();
72
+ const items = ["A", "B", "C"] as const;
70
73
  const weights = { A: 5, B: 10, C: 15 };
71
74
  const selected = m.binary(items);
72
75
 
76
+ // Select at most 2 items
77
+ m.constraint(expr`sum(${selected}) <= 2`, "max_items");
78
+
73
79
  // Total weight cannot exceed capacity 25
74
- m.constraint(
75
- expr`${sum(items, (i) => selected[i].mul(weights[i]))} <= 25`,
76
- "capacity_limit"
77
- );
80
+ m.constraint(expr`dot(${weights}, ${selected}) <= 25`, "capacity_limit");
78
81
  ```
79
82
 
80
83
  ---
81
84
 
82
- ## 🎯 Conditional / Indicator Constraints (`m.indicator`)
85
+ ## Conditional / Indicator Constraints (`m.indicator`)
83
86
 
84
- When a linear constraint should only take effect when a binary decision variable is active (`1` or `0`), use `m.indicator()`:
87
+ When a linear constraint should only take effect when a binary decision variable (`m.binary()`) equals `1` (or `0`), use `m.indicator()`:
85
88
 
86
89
  ```typescript
87
90
  const useDiscount = m.binary("use_discount");
88
91
  const cartTotal = m.continuous("cart_total", { lb: 0, ub: 10_000 });
89
92
 
90
- // If useDiscount == 1, then cartTotal >= 100
93
+ // If useDiscount == 1, enforce cartTotal >= 100
91
94
  // WaSHI automatically derives a safe, tight Big-M from the variable bounds
92
95
  m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
93
96
  activeValue: 1,
@@ -97,16 +100,16 @@ m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
97
100
 
98
101
  ### Safe Automatic Big-M Derivation & Bounds
99
102
 
100
- When `options.M` is omitted, WaSHI automatically derives a safe, minimal Big-M value from the finite variable bounds:
103
+ When `options.M` is omitted, WaSHI automatically derives a minimal valid Big-M value from finite variable bounds (`lb` and `ub`):
101
104
  - For `<=` constraints, variables with positive coefficients require finite upper bounds (`ub`), and negative coefficients require finite lower bounds (`lb`).
102
105
  - For `>=` constraints, variables with positive coefficients require finite lower bounds (`lb`), and negative coefficients require finite upper bounds (`ub`).
103
- - For `==` constraints, safe bounds are derived separately for both the `<=` and `>=` directions (generating `name_le` and `name_ge`).
106
+ - For `==` constraints, bounds are derived separately for both the `<=` and `>=` directions (generating `name_le` and `name_ge`).
104
107
 
105
- If any referenced variable lacks the required finite bounds, `m.indicator()` throws an actionable error prompting for finite bounds or an explicit `options.M`.
108
+ If any referenced variable lacks the required finite bound, `m.indicator()` throws an error prompting for finite bounds or an explicit `options.M`.
106
109
 
107
- ### Explicit Override & Numerical Stability
110
+ ### Explicit `M` Override
108
111
 
109
- You can override the derived Big-M by providing `{ M: number }`:
112
+ You can override the derived Big-M by passing `{ M: number }`:
110
113
 
111
114
  ```typescript
112
115
  m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
@@ -116,15 +119,13 @@ m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
116
119
  ```
117
120
 
118
121
  > [!TIP]
119
- > **Numerical Stability vs. Correctness**:
120
- > An $M$ value that is too small can inadvertently cut off valid solutions. Conversely, an excessively large $M$ (e.g., $10^9$) causes poor matrix conditioning and numerical instability in MIP solvers.
121
- > By setting explicit, realistic variable bounds (`lb` and `ub`), WaSHI derives the tightest possible safe Big-M automatically.
122
+ > **Prefer Finite Variable Bounds Over Arbitrary Large `M`**: An $M$ value that is too small can cut off valid feasible solutions, whereas an excessively large $M$ (e.g., $10^9$) degrades numerical conditioning in MIP solvers. Setting realistic `lb` and `ub` bounds on variables lets WaSHI compute the tightest safe Big-M automatically.
122
123
 
123
124
  ---
124
125
 
125
- ## 🧠 Propositional Logic & SMT Constraints (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`)
126
+ ## Propositional Logic & SMT Constraints (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`)
126
127
 
127
- When solving with the **Z3** SMT engine (`@ohtaman/washi/solvers/z3`), you can combine propositional boolean variables (`m.bool()`) and linear constraints (`Constraint`) using first-class logical operators:
128
+ WaSHI provides first-class logical operators to combine propositional boolean variables (`m.bool()`) and linear constraints (`Constraint`). Logical formulas are solved natively by the **Z3** SMT adapter (`@ohtaman/washi/solvers/z3`) and automatically linearized to 0-1 MIP constraints on **HiGHS** and **GLPK**:
128
129
 
129
130
  ```typescript
130
131
  import { Model, expr, and, or, not, implies, xor } from "@ohtaman/washi";
@@ -133,14 +134,14 @@ import "@ohtaman/washi/solvers/z3";
133
134
  const m = new Model({ solver: "z3" });
134
135
  const isAdmin = m.bool("isAdmin");
135
136
  const hasMfa = m.bool("hasMfa");
136
- const Clearance = m.integer("clearance", { lb: 0, ub: 5 });
137
+ const clearance = m.integer("clearance", { lb: 0, ub: 5 });
137
138
 
138
139
  // Method syntax on BoolVar / BoolOp:
139
140
  m.constraint(isAdmin.implies(hasMfa), "admin_requires_mfa");
140
141
 
141
142
  // Mixing boolean propositions with linear arithmetic comparisons:
142
143
  m.constraint(
143
- implies(isAdmin, expr`${Clearance} >= 4`),
144
+ implies(isAdmin, expr`${clearance} >= 4`),
144
145
  "admin_clearance_level"
145
146
  );
146
147
 
@@ -155,23 +156,28 @@ m.constraint(and(or(isAdmin, hasMfa), not(xor(isAdmin, hasMfa))), "policy");
155
156
  | **Trigger variable** | Numeric `Binary` variable (`m.binary()`) | `BoolVar` (`m.bool()`), `BoolOp`, or `Constraint` |
156
157
  | **Consequent** | Linear `Constraint` (`<=`, `>=`, `==`) | `BoolVar`, `BoolOp`, or linear `Constraint` |
157
158
  | **Target Solvers** | **All MIP/LP solvers** (HiGHS, GLPK, Z3) via Big-M linearization | **All solvers** (HiGHS and GLPK via automatic 0-1 MIP linearization, Z3 natively) |
158
- | **Best For** | Operations research & MIP optimization with an objective (`m.solve()`) | Rule engines, policy verification, and SMT feasibility (`m.check()`) |
159
+ | **Primary Use Case** | MIP optimization models with arithmetic binary variables (`m.solve()`) | Logical rule systems and SMT feasibility (`m.check()`) |
160
+
161
+ > [!NOTE]
162
+ > **General Inequality Antecedents in `implies()`**: On **Z3** (`solver: "z3"`), the antecedent `a` in `implies(a, b)` can be any linear inequality—for example, ``implies(expr`${clearance} >= 4`, hasMfa)`` using `clearance` and `hasMfa` above. On MIP solvers (**HiGHS** / **GLPK**), conditional constraints should use a `BoolVar` (`m.bool()`) antecedent or `m.indicator(binVar, constraint)` with a `Binary` trigger variable.
159
163
 
160
164
  ---
161
165
 
162
- ## 📐 Dual Values, Shadow Prices & Lookups
166
+ ## Dual Values, Shadow Prices & Lookups
163
167
 
164
- For continuous Linear Programming (LP) problems, query the economic shadow price (`dual`) and surplus (`slack`) on each constraint:
168
+ For continuous Linear Programming (LP) problems, query the shadow price (`dual`) and surplus (`slack`) on each constraint (returned by `m.constraint()` or looked up via `m.getConstraintByName()`):
165
169
 
166
170
  ```typescript
167
- m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
171
+ const woodConstraint = m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
168
172
 
169
173
  await m.solve();
170
174
 
171
- const woodConstraint = m.getConstraintByName("wood");
172
- console.log("Shadow price (dual):", woodConstraint?.dual);
173
- console.log("Slack surplus:", woodConstraint?.slack);
175
+ console.log("Shadow price (dual):", woodConstraint.dual);
176
+ console.log("Slack surplus:", woodConstraint.slack);
174
177
  console.log("Total linear constraints:", m.numConstrs);
175
178
  ```
176
179
 
177
- For logical constraints registered with a name, use `m.getLogicalConstraintByName(name)` to look up the registered `LogicalConstraintItem`.
180
+ For named logical constraints, use `m.getLogicalConstraintByName(name)` to look up the registered `LogicalConstraintItem`.
181
+
182
+ > [!TIP]
183
+ > For modeling techniques such as **soft constraints with slack variables** and **symmetry breaking**, see [Formulation Patterns & Optimization Tips](./formulation-tips.md#_3-constraint-design-numerical-conditioning).