@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.
- package/README.ja.md +149 -121
- package/README.md +149 -121
- package/README.zh-CN.md +150 -122
- package/THIRD_PARTY_LICENSES.md +3 -4
- package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
- package/dist/browser/highs-worker.js +21 -3
- package/dist/browser/highs.wasm +0 -0
- package/dist/browser/highs.wasm.LICENSE +1 -2
- package/dist/browser/loader-DewlSho8.js +643 -0
- package/dist/browser/solvers/glpk.js +433 -286
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +1008 -981
- package/dist/browser/solvers/z3.min.js +2 -2
- package/dist/browser/washi.js +2778 -673
- package/dist/browser/washi.min.js +3 -4
- package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/lib/highs-browser-runtime.js +2120 -4124
- package/dist/lib/highs-node-runtime.js +24 -1
- package/dist/lib/highs-runtime.js +2 -0
- package/dist/lib/highs-worker.js +21 -3
- package/dist/lib/highs.wasm +0 -0
- package/dist/lib/highs.wasm.LICENSE +1 -2
- package/dist/lib/loader-BZUQNvh4.js +649 -0
- package/dist/lib/registry-C20sxTwm.js +964 -0
- package/dist/lib/solvers/glpk.js +201 -90
- package/dist/lib/solvers/z3.js +399 -259
- package/dist/lib/washi.js +2363 -1056
- package/dist/types/decomposition/benders.d.ts +32 -0
- package/dist/types/decomposition/benders.d.ts.map +1 -0
- package/dist/types/decomposition/column-generation.d.ts +34 -0
- package/dist/types/decomposition/column-generation.d.ts.map +1 -0
- package/dist/types/decomposition/index.d.ts +4 -0
- package/dist/types/decomposition/index.d.ts.map +1 -0
- package/dist/types/decomposition/types.d.ts +230 -0
- package/dist/types/decomposition/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/big-m.d.ts +75 -0
- package/dist/types/modeler/big-m.d.ts.map +1 -0
- package/dist/types/modeler/boolean-expr.d.ts +103 -14
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +47 -9
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +123 -28
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +11 -6
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/linearizer.d.ts +15 -0
- package/dist/types/modeler/linearizer.d.ts.map +1 -0
- package/dist/types/modeler/lp-writer.d.ts +30 -3
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +623 -131
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/objective.d.ts +59 -0
- package/dist/types/modeler/objective.d.ts.map +1 -0
- package/dist/types/modeler/parser.d.ts +34 -10
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/quad-expr.d.ts +26 -7
- package/dist/types/modeler/quad-expr.d.ts.map +1 -1
- package/dist/types/modeler/solution-validator.d.ts +93 -0
- package/dist/types/modeler/solution-validator.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
- package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/loader.d.ts +38 -13
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts +12 -5
- 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/mock.d.ts +14 -5
- package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +73 -11
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
- package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +86 -1
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/dist/types/modeler/validation.d.ts +56 -0
- package/dist/types/modeler/validation.d.ts.map +1 -0
- package/dist/types/modeler/variable.d.ts +48 -14
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +90 -0
- package/docs/examples/ecommerce-coupons.md +106 -0
- package/docs/examples/index.md +57 -0
- package/docs/examples/knapsack.md +60 -0
- package/docs/examples/portfolio-qp.md +83 -0
- package/docs/examples/production.md +51 -0
- package/docs/examples/shift-scheduling.md +175 -0
- package/docs/examples/smt-security.md +74 -0
- package/docs/examples/transportation.md +90 -0
- package/docs/examples/traveling-salesperson.md +173 -0
- package/docs/examples/unsat-core.md +64 -0
- package/docs/examples/vehicle-routing.md +206 -0
- package/docs/getting-started/ai-agents.md +271 -0
- package/docs/getting-started/cross-origin.md +87 -0
- package/docs/getting-started/index.md +116 -0
- package/docs/getting-started/runtimes.md +227 -0
- package/docs/guide/constraints.md +177 -0
- package/docs/guide/decomposition.md +399 -0
- package/docs/guide/expressions.md +204 -0
- package/docs/guide/objectives.md +170 -0
- package/docs/guide/unsat-core.md +154 -0
- package/docs/guide/variables.md +188 -0
- package/docs/ja/getting-started/ai-agents.md +270 -0
- package/docs/solvers/custom-solver.md +525 -0
- package/docs/solvers/glpk.md +66 -0
- package/docs/solvers/highs.md +115 -0
- package/docs/solvers/index.md +42 -0
- package/docs/solvers/z3.md +78 -0
- package/docs/zh/getting-started/ai-agents.md +270 -0
- package/llms-full.txt +4065 -0
- package/llms.txt +201 -0
- package/package.json +32 -16
- package/skills/washi/SKILL.md +197 -0
- package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
- package/dist/browser/loader-CVt4CloI.js +0 -58
- package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
- package/dist/lib/constraint-BRuB3ooC.js +0 -90
- package/dist/lib/loader-Dti0v9yC.js +0 -50
- 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`.
|