@ohtaman/washi 0.4.0 → 0.6.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 (130) hide show
  1. package/README.ja.md +149 -121
  2. package/README.md +149 -121
  3. package/README.zh-CN.md +150 -122
  4. package/THIRD_PARTY_LICENSES.md +3 -4
  5. package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
  6. package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
  7. package/dist/browser/highs-worker.js +21 -3
  8. package/dist/browser/highs.wasm +0 -0
  9. package/dist/browser/highs.wasm.LICENSE +1 -2
  10. package/dist/browser/loader-DewlSho8.js +643 -0
  11. package/dist/browser/solvers/glpk.js +433 -286
  12. package/dist/browser/solvers/glpk.min.js +1 -1
  13. package/dist/browser/solvers/z3.js +1008 -981
  14. package/dist/browser/solvers/z3.min.js +2 -2
  15. package/dist/browser/washi.js +2778 -673
  16. package/dist/browser/washi.min.js +3 -4
  17. package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
  18. package/dist/lib/highs-browser-runtime.js +2120 -4124
  19. package/dist/lib/highs-node-runtime.js +24 -1
  20. package/dist/lib/highs-runtime.js +2 -0
  21. package/dist/lib/highs-worker.js +21 -3
  22. package/dist/lib/highs.wasm +0 -0
  23. package/dist/lib/highs.wasm.LICENSE +1 -2
  24. package/dist/lib/loader-BZUQNvh4.js +649 -0
  25. package/dist/lib/registry-C20sxTwm.js +964 -0
  26. package/dist/lib/solvers/glpk.js +201 -90
  27. package/dist/lib/solvers/z3.js +399 -259
  28. package/dist/lib/washi.js +2363 -1056
  29. package/dist/types/decomposition/benders.d.ts +32 -0
  30. package/dist/types/decomposition/benders.d.ts.map +1 -0
  31. package/dist/types/decomposition/column-generation.d.ts +34 -0
  32. package/dist/types/decomposition/column-generation.d.ts.map +1 -0
  33. package/dist/types/decomposition/index.d.ts +4 -0
  34. package/dist/types/decomposition/index.d.ts.map +1 -0
  35. package/dist/types/decomposition/types.d.ts +230 -0
  36. package/dist/types/decomposition/types.d.ts.map +1 -0
  37. package/dist/types/index.d.ts +5 -4
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/modeler/big-m.d.ts +75 -0
  40. package/dist/types/modeler/big-m.d.ts.map +1 -0
  41. package/dist/types/modeler/boolean-expr.d.ts +103 -14
  42. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  43. package/dist/types/modeler/constraint.d.ts +47 -9
  44. package/dist/types/modeler/constraint.d.ts.map +1 -1
  45. package/dist/types/modeler/expression.d.ts +123 -28
  46. package/dist/types/modeler/expression.d.ts.map +1 -1
  47. package/dist/types/modeler/index.d.ts +11 -6
  48. package/dist/types/modeler/index.d.ts.map +1 -1
  49. package/dist/types/modeler/linearizer.d.ts +15 -0
  50. package/dist/types/modeler/linearizer.d.ts.map +1 -0
  51. package/dist/types/modeler/lp-writer.d.ts +30 -3
  52. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  53. package/dist/types/modeler/model.d.ts +623 -131
  54. package/dist/types/modeler/model.d.ts.map +1 -1
  55. package/dist/types/modeler/objective.d.ts +59 -0
  56. package/dist/types/modeler/objective.d.ts.map +1 -0
  57. package/dist/types/modeler/parser.d.ts +34 -10
  58. package/dist/types/modeler/parser.d.ts.map +1 -1
  59. package/dist/types/modeler/quad-expr.d.ts +26 -7
  60. package/dist/types/modeler/quad-expr.d.ts.map +1 -1
  61. package/dist/types/modeler/solution-validator.d.ts +93 -0
  62. package/dist/types/modeler/solution-validator.d.ts.map +1 -0
  63. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
  64. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  65. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
  66. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
  67. package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
  68. package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
  69. package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
  70. package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
  71. package/dist/types/modeler/solvers/highs/loader.d.ts +38 -13
  72. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  73. package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
  74. package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -1
  75. package/dist/types/modeler/solvers/highs/solver.d.ts +12 -5
  76. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  77. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  78. package/dist/types/modeler/solvers/mock.d.ts +14 -5
  79. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  80. package/dist/types/modeler/solvers/types.d.ts +73 -11
  81. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  82. package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
  83. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  84. package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
  85. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
  86. package/dist/types/modeler/types.d.ts +86 -1
  87. package/dist/types/modeler/types.d.ts.map +1 -1
  88. package/dist/types/modeler/validation.d.ts +56 -0
  89. package/dist/types/modeler/validation.d.ts.map +1 -0
  90. package/dist/types/modeler/variable.d.ts +48 -14
  91. package/dist/types/modeler/variable.d.ts.map +1 -1
  92. package/docs/examples/diet-sensitivity.md +90 -0
  93. package/docs/examples/ecommerce-coupons.md +106 -0
  94. package/docs/examples/index.md +57 -0
  95. package/docs/examples/knapsack.md +60 -0
  96. package/docs/examples/portfolio-qp.md +83 -0
  97. package/docs/examples/production.md +51 -0
  98. package/docs/examples/shift-scheduling.md +175 -0
  99. package/docs/examples/smt-security.md +74 -0
  100. package/docs/examples/transportation.md +90 -0
  101. package/docs/examples/traveling-salesperson.md +173 -0
  102. package/docs/examples/unsat-core.md +64 -0
  103. package/docs/examples/vehicle-routing.md +206 -0
  104. package/docs/getting-started/ai-agents.md +271 -0
  105. package/docs/getting-started/cross-origin.md +87 -0
  106. package/docs/getting-started/index.md +116 -0
  107. package/docs/getting-started/runtimes.md +227 -0
  108. package/docs/guide/constraints.md +177 -0
  109. package/docs/guide/decomposition.md +399 -0
  110. package/docs/guide/expressions.md +204 -0
  111. package/docs/guide/objectives.md +170 -0
  112. package/docs/guide/unsat-core.md +154 -0
  113. package/docs/guide/variables.md +188 -0
  114. package/docs/ja/getting-started/ai-agents.md +270 -0
  115. package/docs/solvers/custom-solver.md +525 -0
  116. package/docs/solvers/glpk.md +66 -0
  117. package/docs/solvers/highs.md +115 -0
  118. package/docs/solvers/index.md +42 -0
  119. package/docs/solvers/z3.md +78 -0
  120. package/docs/zh/getting-started/ai-agents.md +270 -0
  121. package/llms-full.txt +4065 -0
  122. package/llms.txt +201 -0
  123. package/package.json +32 -16
  124. package/skills/washi/SKILL.md +197 -0
  125. package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
  126. package/dist/browser/loader-CVt4CloI.js +0 -58
  127. package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
  128. package/dist/lib/constraint-BRuB3ooC.js +0 -90
  129. package/dist/lib/loader-Dti0v9yC.js +0 -50
  130. package/dist/lib/registry-DksqCSjI.js +0 -32
@@ -0,0 +1,227 @@
1
+ # Runtime Environments & Deployment
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.
4
+
5
+ ---
6
+
7
+ ## 🟢 Node.js
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.
10
+
11
+ ```typescript
12
+ import { Model, expr, maximize } from "@ohtaman/washi";
13
+
14
+ const m = new Model("NodeExample");
15
+ const x = m.continuous("x", { lb: 0, ub: 10 });
16
+ const y = m.continuous("y", { lb: 0, ub: 20 });
17
+
18
+ m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
19
+ m.constraint(expr`${x} + ${y} <= 15`, "capacity");
20
+
21
+ await m.solve();
22
+ console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
23
+ console.log(`x = ${x.value}, y = ${y.value}`);
24
+ ```
25
+
26
+ Run directly with `tsx` or `node`:
27
+
28
+ ```bash
29
+ npx tsx app.ts
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 🌐 Bundlers (Vite, Webpack, Rollup, Next.js)
35
+
36
+ ### Modern Bundlers (Vite / Rollup / Webpack)
37
+
38
+ When importing `@ohtaman/washi` in a browser bundler, the `browser` export condition resolves automatically:
39
+
40
+ ```typescript
41
+ import { Model, expr, maximize } from "@ohtaman/washi";
42
+
43
+ export async function runOptimization() {
44
+ const m = new Model({ solver: "highs" });
45
+ const x = m.continuous("x", { lb: 0, ub: 100 });
46
+ m.objective(maximize(x));
47
+ await m.solve();
48
+ return x.value;
49
+ }
50
+ ```
51
+
52
+ #### 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`).
54
+
55
+ If your environment prohibits Web Workers or you explicitly want synchronous main-thread execution, pass `useWorker: false`:
56
+
57
+ ```typescript
58
+ await m.solve({ useWorker: false });
59
+ ```
60
+
61
+ #### Customizing the HiGHS WASM Asset URL
62
+ By default, WaSHI resolves `highs.wasm` relative to the bundle (`new URL("./highs.wasm", import.meta.url)`). It **never** makes implicit requests to external CDNs (`jsdelivr`, `unpkg`, etc.).
63
+
64
+ If your bundler or static server hosts `.wasm` files under a custom path, configure the URL globally or per solve:
65
+
66
+ ```typescript
67
+ import { setHighsWasmUrl } from "@ohtaman/washi";
68
+
69
+ // Option A: Configure globally before solving
70
+ setHighsWasmUrl("/assets/wasm/highs.wasm");
71
+
72
+ // Option B: Configure per solve call
73
+ await m.solve({ wasmUrl: "/assets/wasm/highs.wasm" });
74
+ ```
75
+
76
+ ### Next.js & Server-Side Rendering (SSR)
77
+
78
+ In Next.js App Router or Pages Router, optimization models can run either in Server Actions / API Routes (Node.js runtime) or on the client inside `"use client"` components:
79
+
80
+ ```tsx
81
+ "use client";
82
+
83
+ import { useState } from "react";
84
+ import { Model, maximize } from "@ohtaman/washi";
85
+
86
+ export default function OptimizerComponent() {
87
+ const [val, setVal] = useState<number | null>(null);
88
+
89
+ const handleSolve = async () => {
90
+ const m = new Model();
91
+ const x = m.continuous("x", { lb: 0, ub: 50 });
92
+ m.objective(maximize(x));
93
+ await m.solve();
94
+ setVal(x.value);
95
+ };
96
+
97
+ return (
98
+ <div>
99
+ <button onClick={handleSolve}>Run Optimization</button>
100
+ {val !== null && <p>Result: {val}</p>}
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 📦 Browser ESM (`<script type="module">`)
109
+
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:
111
+
112
+ ```html
113
+ <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";
118
+ // Optional solver plugins:
119
+ // import "./dist/browser/solvers/glpk.js";
120
+ // import "./dist/browser/solvers/z3.js";
121
+
122
+ const m = new Model("BrowserESM");
123
+ const x = m.integer("x", { lb: 0 });
124
+ const y = m.integer("y", { lb: 0 });
125
+
126
+ m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
127
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
128
+ m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor");
129
+
130
+ await m.solve();
131
+ console.log("Optimal Objective:", m.objectiveValue);
132
+ </script>
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 🟨 Classic `<script>` Tags (IIFE / Standalone Build / CDN)
138
+
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:
140
+
141
+ ```html
142
+ <!DOCTYPE html>
143
+ <html>
144
+ <head>
145
+ <meta charset="utf-8" />
146
+ <title>WaSHI Browser Demo</title>
147
+ </head>
148
+ <body>
149
+ <h1>WaSHI Browser Optimization</h1>
150
+ <pre id="output">Solving...</pre>
151
+
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> -->
156
+ <!-- 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> -->
159
+
160
+ <script>
161
+ (async () => {
162
+ const { Model, expr, maximize } = Washi;
163
+
164
+ const m = new Model("BrowserDemo");
165
+ const x = m.continuous("x", { lb: 0, ub: 10 });
166
+ const y = m.continuous("y", { lb: 0, ub: 20 });
167
+
168
+ m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
169
+ m.constraint(expr`${x} + ${y} <= 15`);
170
+
171
+ await m.solve();
172
+
173
+ document.getElementById("output").textContent =
174
+ `Status: ${m.status}\n` +
175
+ `Objective: ${m.objectiveValue}\n` +
176
+ `x = ${x.value}, y = ${y.value}`;
177
+ })();
178
+ </script>
179
+ </body>
180
+ </html>
181
+ ```
182
+
183
+ > [!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.
185
+
186
+ ---
187
+
188
+ ## 🔒 Offline, Intranet & Strict CSP Environments
189
+
190
+ WaSHI never phones home or falls back to public CDNs. To self-host in an air-gapped intranet or under a strict Content Security Policy (CSP):
191
+
192
+ 1. **Copy Static Assets**: Serve the files from `node_modules/@ohtaman/washi/dist/browser/` on your own origin:
193
+ - `washi.js` / `washi.min.js`, `highs.wasm`, and `highs-worker.js`
194
+ - Optional GLPK: `solvers/glpk.js` / `solvers/glpk.min.js`
195
+ - Optional Z3: `solvers/z3.js` / `solvers/z3.min.js`, `solvers/z3-built.js`, `solvers/z3-built.wasm`
196
+ 2. **Content Security Policy (CSP) Headers**:
197
+ - 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:;`.
199
+ - 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
+ 3. **Cross-Origin Isolation (Z3 Only)**:
201
+ - 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
+ ```
@@ -0,0 +1,177 @@
1
+ # Constraints (`m.constraint` & `m.indicator`)
2
+
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()`**:
5
+
6
+ ```typescript
7
+ m.constraint(expr`${desks} + 2 * ${chairs} <= 40`, "wood_limit");
8
+ ```
9
+
10
+ ---
11
+
12
+ ## ✍️ Algebraic Linear Constraints (`expr`)
13
+
14
+ The `expr` tagged template literal syntax allows writing equations and inequalities with natural mathematical notation:
15
+
16
+ ```typescript
17
+ import { Model, expr } from "@ohtaman/washi";
18
+
19
+ const m = new Model();
20
+ const x = m.continuous("x", { lb: 0 });
21
+ const y = m.continuous("y", { lb: 0 });
22
+
23
+ // Upper bound inequality (<=)
24
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
25
+
26
+ // Lower bound inequality (>=)
27
+ m.constraint(expr`3 * ${x} + ${y} >= 20`, "min_demand");
28
+
29
+ // Exact equality (==)
30
+ m.constraint(expr`${x} + ${y} == 30`, "exact_quota");
31
+ ```
32
+
33
+ ### Supported Comparison Operators
34
+ - `<=`: Upper bound constraint
35
+ - `>=`: Lower bound constraint
36
+ - `==` or `=`: Exact equality constraint
37
+
38
+ ### Batch Registration
39
+ You can pass an array of constraints to `m.constraint([...])` to register them transactionally in a single call:
40
+
41
+ ```typescript
42
+ m.constraint([expr`${x} <= 10`, expr`${y} <= 20`]);
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🏷️ Naming Constraints
48
+
49
+ Every constraint can be given a unique, descriptive name as the second argument of `m.constraint(c, name)`:
50
+
51
+ ```typescript
52
+ m.constraint(expr`${x} + ${y} <= 100`, "budget_cap");
53
+ ```
54
+
55
+ ### 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
+ 3. **Direct Lookup (`m.getConstraintByName()`)**: Retrieve a constraint by name after solving to inspect its `dual` or `slack` value.
59
+
60
+ ---
61
+
62
+ ## 🔁 Adding Constraints over Iterables & Sums
63
+
64
+ Use `sum()` or `dot()` inside `expr` to express linear aggregations cleanly:
65
+
66
+ ```typescript
67
+ import { Model, sum, expr } from "@ohtaman/washi";
68
+
69
+ const items = ["A", "B", "C"];
70
+ const weights = { A: 5, B: 10, C: 15 };
71
+ const selected = m.binary(items);
72
+
73
+ // Total weight cannot exceed capacity 25
74
+ m.constraint(
75
+ expr`${sum(items, (i) => selected[i].mul(weights[i]))} <= 25`,
76
+ "capacity_limit"
77
+ );
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 🎯 Conditional / Indicator Constraints (`m.indicator`)
83
+
84
+ When a linear constraint should only take effect when a binary decision variable is active (`1` or `0`), use `m.indicator()`:
85
+
86
+ ```typescript
87
+ const useDiscount = m.binary("use_discount");
88
+ const cartTotal = m.continuous("cart_total", { lb: 0, ub: 10_000 });
89
+
90
+ // If useDiscount == 1, then cartTotal >= 100
91
+ // WaSHI automatically derives a safe, tight Big-M from the variable bounds
92
+ m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
93
+ activeValue: 1,
94
+ name: "min_spend_for_discount",
95
+ });
96
+ ```
97
+
98
+ ### Safe Automatic Big-M Derivation & Bounds
99
+
100
+ When `options.M` is omitted, WaSHI automatically derives a safe, minimal Big-M value from the finite variable bounds:
101
+ - For `<=` constraints, variables with positive coefficients require finite upper bounds (`ub`), and negative coefficients require finite lower bounds (`lb`).
102
+ - 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`).
104
+
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`.
106
+
107
+ ### Explicit Override & Numerical Stability
108
+
109
+ You can override the derived Big-M by providing `{ M: number }`:
110
+
111
+ ```typescript
112
+ m.indicator(useDiscount, expr`${cartTotal} >= 100`, {
113
+ activeValue: 1,
114
+ M: 10_000, // Explicit override
115
+ });
116
+ ```
117
+
118
+ > [!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
+
123
+ ---
124
+
125
+ ## 🧠 Propositional Logic & SMT Constraints (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`)
126
+
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
+
129
+ ```typescript
130
+ import { Model, expr, and, or, not, implies, xor } from "@ohtaman/washi";
131
+ import "@ohtaman/washi/solvers/z3";
132
+
133
+ const m = new Model({ solver: "z3" });
134
+ const isAdmin = m.bool("isAdmin");
135
+ const hasMfa = m.bool("hasMfa");
136
+ const Clearance = m.integer("clearance", { lb: 0, ub: 5 });
137
+
138
+ // Method syntax on BoolVar / BoolOp:
139
+ m.constraint(isAdmin.implies(hasMfa), "admin_requires_mfa");
140
+
141
+ // Mixing boolean propositions with linear arithmetic comparisons:
142
+ m.constraint(
143
+ implies(isAdmin, expr`${Clearance} >= 4`),
144
+ "admin_clearance_level"
145
+ );
146
+
147
+ // Top-level helper functions (and, or, not, implies, xor):
148
+ m.constraint(and(or(isAdmin, hasMfa), not(xor(isAdmin, hasMfa))), "policy");
149
+ ```
150
+
151
+ ### Choosing Between `m.indicator()` and `implies()`
152
+
153
+ | Feature | `m.indicator(binVar, constraint, opts)` | `implies(a, b)` / `a.implies(b)` |
154
+ | :--- | :--- | :--- |
155
+ | **Trigger variable** | Numeric `Binary` variable (`m.binary()`) | `BoolVar` (`m.bool()`), `BoolOp`, or `Constraint` |
156
+ | **Consequent** | Linear `Constraint` (`<=`, `>=`, `==`) | `BoolVar`, `BoolOp`, or linear `Constraint` |
157
+ | **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
+
160
+ ---
161
+
162
+ ## 📐 Dual Values, Shadow Prices & Lookups
163
+
164
+ For continuous Linear Programming (LP) problems, query the economic shadow price (`dual`) and surplus (`slack`) on each constraint:
165
+
166
+ ```typescript
167
+ m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
168
+
169
+ await m.solve();
170
+
171
+ const woodConstraint = m.getConstraintByName("wood");
172
+ console.log("Shadow price (dual):", woodConstraint?.dual);
173
+ console.log("Slack surplus:", woodConstraint?.slack);
174
+ console.log("Total linear constraints:", m.numConstrs);
175
+ ```
176
+
177
+ For logical constraints registered with a name, use `m.getLogicalConstraintByName(name)` to look up the registered `LogicalConstraintItem`.