@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.
- package/README.ja.md +103 -421
- package/README.md +103 -421
- package/README.zh-CN.md +103 -421
- package/THIRD_PARTY_LICENSES.md +5 -3
- package/dist/browser/solvers/glpk.js +2 -2
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/washi.js +777 -261
- package/dist/browser/washi.min.js +3 -3
- package/dist/lib/{registry-C20sxTwm.js → registry-D7TZ3Wh_.js} +88 -18
- package/dist/lib/solvers/glpk.js +3 -3
- package/dist/lib/solvers/z3.js +1 -1
- package/dist/lib/washi.js +864 -421
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +47 -26
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +2 -2
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +12 -6
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/parser.d.ts +25 -5
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +2 -0
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +15 -14
- package/docs/examples/ecommerce-coupons.md +15 -22
- package/docs/examples/index.md +14 -12
- package/docs/examples/knapsack.md +8 -7
- package/docs/examples/portfolio-qp.md +11 -4
- package/docs/examples/production.md +4 -4
- package/docs/examples/shift-scheduling.md +20 -7
- package/docs/examples/smt-security.md +16 -4
- package/docs/examples/transportation.md +46 -19
- package/docs/examples/traveling-salesperson.md +28 -44
- package/docs/examples/unsat-core.md +15 -5
- package/docs/examples/vehicle-routing.md +10 -8
- package/docs/getting-started/ai-agents.md +25 -14
- package/docs/getting-started/cross-origin.md +56 -28
- package/docs/getting-started/index.md +40 -69
- package/docs/getting-started/runtimes.md +66 -61
- package/docs/guide/constraints.md +46 -40
- package/docs/guide/decomposition.md +127 -259
- package/docs/guide/expressions.md +90 -39
- package/docs/guide/formulation-tips.md +212 -0
- package/docs/guide/objectives.md +7 -4
- package/docs/guide/unsat-core.md +18 -6
- package/docs/guide/variables.md +33 -38
- package/docs/ja/getting-started/ai-agents.md +24 -14
- package/docs/solvers/highs.md +27 -6
- package/docs/solvers/index.md +1 -1
- package/docs/solvers/z3.md +3 -0
- package/docs/zh/getting-started/ai-agents.md +24 -14
- package/llms-full.txt +929 -687
- package/llms.txt +21 -19
- package/package.json +3 -3
- 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
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
console.log(`
|
|
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
|
|
60
|
+
## 🌐 Bundlers & Static Hosting (Vite, Next.js, Webpack, Rollup)
|
|
35
61
|
|
|
36
|
-
### Modern Bundlers
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
115
|
-
import { Model, expr, maximize } from "
|
|
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 "
|
|
120
|
-
// import "
|
|
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
|
-
|
|
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
|
|
167
|
+
## 🟨 Classic `<script>` Tags (IIFE / Standalone HTML / CDN)
|
|
138
168
|
|
|
139
|
-
For
|
|
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)
|
|
153
|
-
<script src="
|
|
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="
|
|
158
|
-
<!-- <script src="
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
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
|
|
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` (
|
|
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
|
|
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`${
|
|
7
|
+
m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Algebraic Linear Constraints (`expr`)
|
|
13
13
|
|
|
14
|
-
The `expr` tagged template literal
|
|
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
|
-
|
|
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
|
-
##
|
|
49
|
+
## Naming Constraints
|
|
48
50
|
|
|
49
|
-
|
|
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
|
|
57
|
-
2. **Conflict Diagnosis (`conflicts` / `unsatCore`)**: When constraints contradict each other,
|
|
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
|
-
##
|
|
64
|
+
## Aggregated Constraints (`sum` & `dot`)
|
|
63
65
|
|
|
64
|
-
Use `sum()`
|
|
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,
|
|
69
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
68
70
|
|
|
69
|
-
const
|
|
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
|
-
##
|
|
85
|
+
## Conditional / Indicator Constraints (`m.indicator`)
|
|
83
86
|
|
|
84
|
-
When a linear constraint should only take effect when a binary decision variable
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
110
|
+
### Explicit `M` Override
|
|
108
111
|
|
|
109
|
-
You can override the derived Big-M by
|
|
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
|
-
> **
|
|
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
|
-
##
|
|
126
|
+
## Propositional Logic & SMT Constraints (`BoolVar`, `and`, `or`, `not`, `implies`, `xor`)
|
|
126
127
|
|
|
127
|
-
|
|
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
|
|
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`${
|
|
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
|
-
| **
|
|
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
|
-
##
|
|
166
|
+
## Dual Values, Shadow Prices & Lookups
|
|
163
167
|
|
|
164
|
-
For continuous Linear Programming (LP) problems, query the
|
|
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
|
-
|
|
172
|
-
console.log("
|
|
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
|
|
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).
|