@ohtaman/washi 0.5.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 -386
- package/README.md +104 -387
- package/README.zh-CN.md +103 -386
- package/THIRD_PARTY_LICENSES.md +5 -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 +1 -1
- 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 +3 -3
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +7 -1
- package/dist/browser/solvers/z3.min.js +1 -1
- package/dist/browser/washi.js +1708 -540
- 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 +1 -1
- 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-BEfvpZW3.js → registry-D7TZ3Wh_.js} +142 -59
- package/dist/lib/solvers/glpk.js +4 -4
- package/dist/lib/solvers/z3.js +1 -1
- package/dist/lib/washi.js +1576 -465
- 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 +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +11 -0
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +57 -26
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +4 -3
- 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 +3 -1
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +159 -8
- 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/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 +4 -4
- 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 +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 +4 -0
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +27 -0
- package/dist/types/modeler/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 +18 -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 +157 -0
- package/docs/examples/unsat-core.md +15 -5
- package/docs/examples/vehicle-routing.md +208 -0
- package/docs/getting-started/ai-agents.md +33 -18
- 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 +47 -41
- package/docs/guide/decomposition.md +267 -0
- 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 +28 -9
- package/docs/guide/variables.md +33 -38
- package/docs/ja/getting-started/ai-agents.md +32 -18
- package/docs/solvers/custom-solver.md +1 -1
- package/docs/solvers/highs.md +48 -5
- package/docs/solvers/index.md +4 -4
- package/docs/solvers/z3.md +3 -0
- package/docs/zh/getting-started/ai-agents.md +32 -18
- package/llms-full.txt +1536 -478
- package/llms.txt +28 -22
- package/package.json +19 -16
- package/skills/washi/SKILL.md +21 -17
- package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
- package/dist/browser/loader-CtaDHef_.js +0 -273
- package/dist/lib/loader-DmNC_UNk.js +0 -279
package/docs/guide/variables.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Variables & Domains
|
|
2
2
|
|
|
3
|
-
Variables represent the mathematical decision variables in your optimization model.
|
|
3
|
+
Variables represent the mathematical decision variables in your optimization or logical model.
|
|
4
4
|
WaSHI provides domain-specific factory methods on the `Model` instance: `m.continuous()`, `m.integer()`, `m.binary()`, and `m.bool()`.
|
|
5
5
|
|
|
6
6
|
---
|
|
@@ -8,7 +8,7 @@ WaSHI provides domain-specific factory methods on the `Model` instance: `m.conti
|
|
|
8
8
|
## Decision Variable Types
|
|
9
9
|
|
|
10
10
|
### 1. Continuous Variables (`m.continuous`)
|
|
11
|
-
Can take any real number value within `[lb, ub]`.
|
|
11
|
+
Can take any real number value within `[lb, ub]`. Used for continuous quantities such as flow rates, weights, budgets, and portfolio allocations:
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Model } from "@ohtaman/washi";
|
|
@@ -20,7 +20,7 @@ const fuel = m.continuous("fuel", { lb: 0, ub: 1000 });
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
### 2. Integer Variables (`m.integer`)
|
|
23
|
-
Restricted to
|
|
23
|
+
Restricted to integer values within `[lb, ub]`. Used for discrete counts such as machines, vehicles, or headcount:
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
26
|
// Single integer variable bounded in [0, 15]
|
|
@@ -28,48 +28,43 @@ const trucks = m.integer("trucks", { lb: 0, ub: 15 });
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
### 3. Binary Variables (`m.binary`)
|
|
31
|
-
Restricted to $\{0, 1\}$ as
|
|
31
|
+
Restricted to $\{0, 1\}$ as numeric values (`lb = 0`, `ub = 1`). Used for 0-1 MIP selection and yes/no decisions that appear in linear sums or objective costs:
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
// Single binary variable
|
|
34
|
+
// Single binary variable (lb = 0, ub = 1 automatically)
|
|
35
35
|
const openWarehouse = m.binary("open_warehouse");
|
|
36
|
-
// lb is automatically 0, ub is automatically 1
|
|
37
36
|
```
|
|
38
37
|
|
|
39
38
|
### 4. Propositional Boolean Variables (`m.bool`)
|
|
40
|
-
Pure boolean
|
|
39
|
+
Pure boolean propositions (`true` / `false`) for logical and SMT constraints:
|
|
41
40
|
|
|
42
41
|
```typescript
|
|
43
|
-
// Single propositional boolean
|
|
44
|
-
const
|
|
45
|
-
const
|
|
42
|
+
// Single propositional boolean variables
|
|
43
|
+
const isAdmin = m.bool("isAdmin");
|
|
44
|
+
const hasMfa = m.bool("hasMfa");
|
|
46
45
|
|
|
47
46
|
// First-class boolean operators: and, or, not, implies, xor
|
|
48
|
-
m.constraint(
|
|
47
|
+
m.constraint(isAdmin.implies(hasMfa), "admin_requires_mfa");
|
|
49
48
|
```
|
|
50
49
|
|
|
51
|
-
###
|
|
52
|
-
Semi-continuous variables take values in $\{0\} \cup [L, U]$ (either zero or between a positive lower and upper bound, useful for minimum production batches or operational thresholds).
|
|
50
|
+
### Choosing Between `m.binary()` and `m.bool()`
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
| Feature | `m.binary()` (`Var`) | `m.bool()` (`BoolVar`) |
|
|
53
|
+
| :--- | :--- | :--- |
|
|
54
|
+
| **Solved `.value` type** | `number \| null` (`0` or `1`) | `boolean \| null` (`true` or `false`) |
|
|
55
|
+
| **Linear arithmetic (`expr`, `sum`, `dot`, objective costs)** | ✅ Native (`` expr`30 * ${b}` ``) | ❌ Cannot be multiplied by numbers or summed directly |
|
|
56
|
+
| **Indicator constraints (`m.indicator(b, ...)` )** | ✅ Supported as trigger variable | ❌ Use `` implies(flag, expr`...`) `` instead |
|
|
57
|
+
| **Propositional logic (`and`, `or`, `not`, `implies`, `xor`)** | Via comparison (`` expr`${b} == 1` ``) | ✅ Native first-class methods (`.implies()`, `.and()`) |
|
|
58
|
+
| **Solver execution** | Native 0-1 MIP on HiGHS/GLPK; bounded integer on Z3 | Native boolean proposition on Z3; auto-linearized to 0-1 MIP on HiGHS/GLPK |
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const m = new Model();
|
|
60
|
-
const batch = m.continuous("batch", { lb: 0, ub: 100 });
|
|
61
|
-
const isActive = m.binary("is_active");
|
|
62
|
-
|
|
63
|
-
// If isActive === 0, batch is forced to 0; if 1, batch is within [10, 100]
|
|
64
|
-
m.constraint(expr`${batch} >= 10 * ${isActive}`, "min_batch_run");
|
|
65
|
-
m.constraint(expr`${batch} <= 100 * ${isActive}`, "max_batch_run");
|
|
66
|
-
```
|
|
60
|
+
> [!TIP]
|
|
61
|
+
> Use **`m.binary()`** when a decision participates in arithmetic expressions—such as linear sums ($\sum x_i \ge 3$), objective cost terms ($c_i x_i$), or `m.indicator()`. Use **`m.bool()`** when expressing logical conditions with `implies()`, `and()`, and `or()` without numeric coefficients.
|
|
67
62
|
|
|
68
63
|
---
|
|
69
64
|
|
|
70
65
|
## Multi-Dimensional & Array Creation
|
|
71
66
|
|
|
72
|
-
The domain factory methods (`continuous`, `integer`, `binary`, `bool`)
|
|
67
|
+
The domain factory methods (`continuous`, `integer`, `binary`, `bool`) accept a numeric count or an array of string keys to create 1D arrays, typed dictionaries, and 2D matrices in a single call.
|
|
73
68
|
|
|
74
69
|
### By Count
|
|
75
70
|
Pass a number to create an array of variables:
|
|
@@ -80,15 +75,15 @@ const flags = m.bool(3); // BoolVar[] (p_0, p_1, p_2)
|
|
|
80
75
|
```
|
|
81
76
|
|
|
82
77
|
### 1D Variable Dictionaries
|
|
83
|
-
Pass an array of keys to get a typed dictionary `Record<
|
|
78
|
+
Pass an array of keys to get a typed dictionary `Record<K, Var>` or `Record<K, BoolVar>`:
|
|
84
79
|
|
|
85
80
|
```typescript
|
|
86
|
-
const items = ["laptop", "phone", "tablet"];
|
|
81
|
+
const items = ["laptop", "phone", "tablet"] as const;
|
|
87
82
|
|
|
88
83
|
// Record<"laptop" | "phone" | "tablet", Var>
|
|
89
84
|
const selected = m.binary(items);
|
|
90
85
|
|
|
91
|
-
const perms = m.bool(["read", "write", "execute"]);
|
|
86
|
+
const perms = m.bool(["read", "write", "execute"] as const);
|
|
92
87
|
// perms.read, perms.write, perms.execute
|
|
93
88
|
```
|
|
94
89
|
|
|
@@ -122,16 +117,16 @@ const schedule = m.binary(factories, 7, { prefix: "open_day" });
|
|
|
122
117
|
|
|
123
118
|
| Option | Type | Default | Description |
|
|
124
119
|
| :--- | :--- | :--- | :--- |
|
|
125
|
-
| `lb` | `number` | `0` | Lower bound. Defaults to 0 (non-negative). |
|
|
120
|
+
| `lb` | `number` | `0` | Lower bound. Defaults to `0` (non-negative). |
|
|
126
121
|
| `ub` | `number` | `Infinity` (or `1` for binary) | Upper bound. |
|
|
127
122
|
| `type` | `"Continuous"` \| `"Integer"` \| `"Binary"` | Inferred from method | Underlying variable domain category. |
|
|
128
|
-
| `initialValue` | `number` | `undefined` | Optional primal solution hint (MIP start), kept distinct from solved `variable.value`. |
|
|
123
|
+
| `initialValue` | `number` | `undefined` | Optional primal solution hint (MIP warm start), kept distinct from solved `variable.value`. |
|
|
129
124
|
|
|
130
125
|
---
|
|
131
126
|
|
|
132
127
|
## Primal Initial Values (Warm-Start Hints)
|
|
133
128
|
|
|
134
|
-
When rebuilding a model across iterations
|
|
129
|
+
When rebuilding a model across iterations, you can supply optional primal initial values (`initialValue`) on a subset or all numeric variables (`m.continuous`, `m.integer`, `m.binary`, or `v.initialValue = ...`):
|
|
135
130
|
|
|
136
131
|
```typescript
|
|
137
132
|
const prevValues: Record<string, number | null> = { trucks: 8, open_warehouse: 1 };
|
|
@@ -148,15 +143,15 @@ const openWarehouse2 = m2.binary("open_warehouse", {
|
|
|
148
143
|
const extraCapacity = m2.continuous("extra_capacity", { lb: 0 }); // Partial start: no initialValue needed
|
|
149
144
|
```
|
|
150
145
|
|
|
151
|
-
- **Hint vs. Hard Constraint**: `initialValue` is
|
|
152
|
-
- **Validation & Tolerance (`1e-6`)**: Supplied values must be finite numbers within `[lb - 1e-6, ub + 1e-6]` (and within `1e-6` of an integer
|
|
146
|
+
- **Hint vs. Hard Constraint**: `initialValue` is an optional solver hint (passed to HiGHS via `setSolution`). It does **not** fix the variable or add a constraint, and `variable.value` remains `null` until solved. *(To penalize deviations from a previous plan, see [Formulation Patterns & Tips](./formulation-tips.md#minimal-perturbation-rescheduling-vs-warm-starting).)*
|
|
147
|
+
- **Validation & Tolerance (`1e-6`)**: Supplied values must be finite numbers within `[lb - 1e-6, ub + 1e-6]` (and within `1e-6` of an integer for `"Integer"` and `"Binary"` variables). Partial starts are accepted without needing to satisfy every model constraint, though supplying a complete constraint-feasible assignment gives MIP solvers an immediate incumbent bound.
|
|
153
148
|
- **Solver Support**: Supported by **HiGHS** (`capabilities.supportsWarmStart === true`). Solvers without warm-start support (**GLPK**, **Z3**, or custom adapters without `supportsWarmStart: true`) reject `solve()` and `check()` with a descriptive error rather than silently ignoring `initialValue`.
|
|
154
149
|
|
|
155
150
|
---
|
|
156
151
|
|
|
157
152
|
## Accessing Solution Values & Reduced Costs
|
|
158
153
|
|
|
159
|
-
After calling `await m.solve()` or `await m.check()`, assigned variable values
|
|
154
|
+
After calling `await m.solve()` or `await m.check()`, assigned variable values are accessible via `variable.value` (returns `number | null` for `Var`, or `boolean | null` for `BoolVar`) or the bulk accessor `m.getValues()`:
|
|
160
155
|
|
|
161
156
|
```typescript
|
|
162
157
|
await m.solve();
|
|
@@ -164,7 +159,7 @@ await m.solve();
|
|
|
164
159
|
// Single variable primal value
|
|
165
160
|
console.log(`trucks: ${trucks.value}`);
|
|
166
161
|
|
|
167
|
-
// Reduced cost (LP sensitivity analysis
|
|
162
|
+
// Reduced cost (LP sensitivity analysis)
|
|
168
163
|
console.log(`fuel reduced cost: ${fuel.reducedCost}`);
|
|
169
164
|
|
|
170
165
|
// Bulk extraction for 1D or 2D collections (works for both Var and BoolVar collections)
|
|
@@ -179,10 +174,10 @@ const flowValues = m.getValues(ship);
|
|
|
179
174
|
|
|
180
175
|
## Looking Up Variables by Name
|
|
181
176
|
|
|
182
|
-
You can retrieve registered decision variables by name or inspect the total variable count on the model:
|
|
177
|
+
You can retrieve registered decision variables by name or inspect the total numeric variable count on the model:
|
|
183
178
|
|
|
184
179
|
```typescript
|
|
185
180
|
const v = m.getVarByName("trucks"); // Var | undefined
|
|
186
|
-
const b = m.getBoolVarByName("
|
|
181
|
+
const b = m.getBoolVarByName("isAdmin"); // BoolVar | undefined
|
|
187
182
|
console.log("Total numeric variables:", m.numVars);
|
|
188
183
|
```
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
> **スコープに関する注記**: このドキュメントは、`@ohtaman/washi` を用いて数理最適化モデルや充足可能性(SAT/SMT)モデルを構築する AI コーディングエージェントおよび開発者向けの**利用者向けモデリング・クイックリファレンス**です。
|
|
4
4
|
> - **ポータブル Agent Skill (`SKILL.md`)**: モデリング・`toLpString()` による式検査・`validateSolution()` による解検証・Z3 `unsatCore` による矛盾診断までを一貫して実行するワークフロースキル(Claude Code, Antigravity, Cursor, Codex 等に対応)として、[`skills/washi/SKILL.md`](https://github.com/ohtaman/washi/blob/main/skills/washi/SKILL.md)(npm パッケージ内の `node_modules/@ohtaman/washi/skills/washi/SKILL.md` にも同梱)を利用できます:
|
|
5
5
|
> ```bash
|
|
6
|
-
> #
|
|
6
|
+
> # skills CLI (skills.sh / vercel-labs/skills) 経由で導入
|
|
7
|
+
> npx skills add ohtaman/washi
|
|
8
|
+
>
|
|
9
|
+
> # またはパッケージ同梱のスキルをプロジェクトのエージェントスキルディレクトリへ配置
|
|
7
10
|
> mkdir -p .agents/skills/washi
|
|
8
11
|
> cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
|
|
9
12
|
> ```
|
|
@@ -54,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
54
57
|
1. **デフォルトでパッケージ同梱 WASM を使用(暗黙の CDN フォールバックなし)**:
|
|
55
58
|
- Node.js および最新のバンドラーでは、WebAssembly アセットはモジュールからの相対パスで自動的に解決されます。
|
|
56
59
|
- ブラウザ環境へデプロイする際は、`dist/browser/` ディレクトリ一式(JS、Worker、`.wasm` ファイル)をまとめて配置してください。ローカルの WASM URL が欠落している場合や HTML の SPA フォールバックが返された場合、初期化は試行した URL を明示して即座に失敗し、**外部パブリック CDN へ勝手に切り替わることは決してありません**。
|
|
57
|
-
- ブラウザランタイムで `highs.wasm` をカスタム URL から読み込む場合は、`setHighsWasmUrl("https://.../highs.wasm")` を呼び出すか、`await m.solve({ wasmUrl: "https://.../highs.wasm" })` を渡します(Node.js
|
|
60
|
+
- ブラウザランタイムで `highs.wasm` をカスタム URL から読み込む場合は、`setHighsWasmUrl("https://.../highs.wasm")` を呼び出すか、`await m.solve({ wasmUrl: "https://.../highs.wasm" })` を渡します(Node.js では常にパッケージ同梱のローカル `highs.wasm` ランタイムを読み込むため無視されます)。
|
|
58
61
|
2. **ブラウザでの Z3 実行にはセキュアコンテキスト・Cross-Origin Isolation・CORS が必要**:
|
|
59
62
|
- Z3 WebAssembly は `SharedArrayBuffer` によるマルチスレッドを使用します。Z3 を実行するブラウザページは、セキュアコンテキスト(`window.isSecureContext === true`、すなわち HTTPS または `localhost`)かつ Cross-Origin Isolated(`window.crossOriginIsolated === true`)である必要があり、以下の HTTP ヘッダーを配信する必要があります:
|
|
60
63
|
- `Cross-Origin-Opener-Policy: same-origin`(ドキュメント応答)
|
|
@@ -63,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
63
66
|
- `@ohtaman/washi` のインポートによって Service Worker が自動注入されることはありません。ブラウザでセキュアコンテキストや Cross-Origin Isolation が欠けている場合、Z3 は原因を示すエラーで即座に reject します。
|
|
64
67
|
3. **Web Worker とキャンセル制御 (`AbortSignal`)**:
|
|
65
68
|
- 求解開始前のキャンセル(実行開始時点で `signal.aborted`)は、すべてのソルバーでサポートされています。
|
|
66
|
-
- `Worker` が利用可能なブラウザ環境では、HiGHS はデフォルトで専用 Web Worker 内で実行されます(`useWorker: true
|
|
69
|
+
- 実効オリジンが不透明でない(`window.origin !== "null"`)かつ `Worker` が利用可能なブラウザ環境では、HiGHS はデフォルトで専用 Web Worker 内で実行されます(`useWorker: true`)。実効オリジンが不透明な環境(`window.origin === "null"`、`file:` URL や `allow-same-origin` なしの `sandbox="allow-scripts"` `<iframe>` 埋め込み等)ではデフォルトでメインスレッド実行(`useWorker: false`)になります。`m.solve({ signal })` に `{ signal }` を渡すと、実行中の Worker 求解をプリエンプティブに強制終了(または待機中の Worker リクエストをキャンセル)できます。
|
|
67
70
|
- メインスレッド実行の HiGHS(`useWorker: false` または Node.js)では、ソルバーの割り込みチェックポイントで呼ばれる `onProgress` コールバック内で同期的に `abort()` をトリガーした場合に協調的な途中中断が機能します。外部からの非同期 abort は、同期実行中の WASM スレッドがチェックポイントまたは完了に達するまで割り込むことはできません。割り込みフックを持たないシングルスレッド WASM ソルバー(GLPK、Z3)は実行開始前に `signal` を確認します。
|
|
68
71
|
|
|
69
72
|
---
|
|
@@ -79,6 +82,7 @@ const m = new Model("ExampleModel");
|
|
|
79
82
|
|
|
80
83
|
// スカラー変数
|
|
81
84
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
85
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
82
86
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
83
87
|
const b = m.binary("b");
|
|
84
88
|
const flag = m.bool("flag"); // 命題論理変数(Z3 / 論理制約用)
|
|
@@ -100,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
100
104
|
### ステップ 2: 数式の構築 (`expr`, `sum`, `dot`)
|
|
101
105
|
|
|
102
106
|
- **統一 `expr` テンプレートタグ**:
|
|
103
|
-
- 1次式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
104
|
-
- 2次式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
105
|
-
- 線形制約(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(`<=`, `>=`, `==`, `=` をサポート)。
|
|
106
|
-
-
|
|
107
|
-
- `sum(
|
|
108
|
-
- `dot(a, b)`
|
|
107
|
+
- 1次式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``, `` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``, `` expr`sum((${price} - ${cost}) * ${byItem})` ``
|
|
108
|
+
- 2次式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``, `` expr`sum((${vec} - ${target})^2)` ``(連続変数 `m.continuous` のみで構成される凸 QP モデルの目的関数としてサポート。標準搭載の HiGHS は整数・バイナリ・論理変数を含む MIQP には非対応です)。
|
|
109
|
+
- 線形制約(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``, `` expr`sum(${byItem}) <= 100` ``(`<=`, `>=`, `==`, `=` をサポート)。
|
|
110
|
+
- **集計ヘルパーと要素ごとの四則演算**:
|
|
111
|
+
- `sum(containerOrItems, mapper?)`(`` expr`sum(...)` `` の内部またはスタンドアロン `sum(...)` で利用可能)は、1次元・2次元の `Array`, `Iterable`, またはプレーンな `Record`(`Object.values(record)` 不要)の総和を計算します。`` expr`sum(...)` `` 内ではコンテナの要素ごとの四則演算・二乗(`+`, `-`, `*`, `/`, `^2`)も記述可能です。
|
|
112
|
+
- `dot(a, b)`(`` expr`dot(${a}, ${b})` `` の内部またはスタンドアロン `dot(a, b)` で利用可能)は、対応する 1次元配列、2次元配列(`number[][]` / `Var[][]`、フロベニウス内積 $\sum_{i,j} A_{i,j} B_{i,j}$)、プレーンな辞書(`Record<string, ...>`)、または配列と辞書の混合コンテナの内積を、引数の順序を問わず計算します。
|
|
109
113
|
- **メソッドチェーン(Fluent API)**:
|
|
110
114
|
- 算術演算(`LinExpr` と `QuadExpr` で利用可能。`Var` は `.add()`, `.sub()`, `.mul()`, `.neg()` をサポート): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
|
|
111
115
|
- 比較演算(`Var` と `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
|
|
@@ -119,8 +123,8 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
119
123
|
// maximize() または minimize() を用いて目的関数を設定
|
|
120
124
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
121
125
|
|
|
122
|
-
//
|
|
123
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
126
|
+
// モデルに制約条件を登録(登録された Constraint / BoolExpr またはその配列を返す)
|
|
127
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
124
128
|
|
|
125
129
|
// 条件付き(Indicator)制約 (MIP Big-M 変換: b === 1 => x <= 50)
|
|
126
130
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -132,9 +136,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
132
136
|
- `SolverStatus`(`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`, `"Sat"`, または `"Unsat"`)を返します。組み込みの最適化ソルバーは最適化ステータス(`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`)を返し、時間・反復・解数制限や中断で停止した際に有効な実行可能解(Incumbent)が見つかっている場合は `"Feasible"` を返します。`"Sat"` と `"Unsat"` は充足可能性ソルバーやカスタムソルバーアダプター(`MockSolver` など)向けに `SolverStatus` に含まれています。
|
|
133
137
|
- 解の値は `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, `x.reducedCost` から読み取ります。
|
|
134
138
|
- 求解後に `m.validateSolution()` を呼び出すことで、変数の有限性、境界条件(`lb`/`ub`)、整数・バイナリ・真偽値領域、線形制約の残差、および論理制約を独立して検証できます(`SolutionValidationReport`: `{ valid, complete, maxViolation, violations, ... }`)。なお、`validateSolution()` は変数および制約条件の主実行可能性を検査するものであり、`m.objectiveValue` の再計算や比較は行いません。
|
|
135
|
-
-
|
|
136
|
-
- `CheckResult`(`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`)を返します。`conflicts`
|
|
137
|
-
-
|
|
139
|
+
- **充足可能性判定と矛盾診断 (`await m.check(options?)`)**:
|
|
140
|
+
- `CheckResult`(`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`)を返します。`conflicts` はモデルが `"Unsat"` と証明されたときに設定されます。
|
|
141
|
+
- 線形算術制約または論理制約(`BoolVar`, `and`, `or`, `implies`, `xor`)を含むモデルでは、`m.check()` はデフォルトの HiGHS や GLPK を含む任意のソルバーで動作します(単一 solve の弾性スラック緩和を用い、論理制約は 0-1 MIP へ自動線形化)。矛盾時は緩和が必要な制約名を `conflicts` として診断します(意味論の厳密さのため `unsatCore: undefined`)。
|
|
142
|
+
- 厳密な SMT Unsat Core 抽出(`result.unsatCore` および `result.conflicts`)は SMT 対応ソルバー(`{ solver: "z3" }`)使用時に提供されます。Z3 は登録された制約を追跡しつつ、変数の上下限(`lb`/`ub`)およびバイナリ領域 `{0, 1}` を直接アサートするため、`unsatCore` は*モデルの固定された変数境界・領域と合わせたときに*矛盾する登録済み制約名の部分集合を返します(最小性は保証されません)。
|
|
138
143
|
- **非同期 `solve` / `check` 実行中のモデル変更禁止**:
|
|
139
144
|
- `await m.solve()` または `await m.check()` の完了待機中に変数・制約・目的関数を変更すると、進行中の実行は無効化され、古い Promise は reject されます。
|
|
140
145
|
|
|
@@ -157,10 +162,13 @@ export async function runMipExample() {
|
|
|
157
162
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
158
163
|
const setup = m.binary("setup");
|
|
159
164
|
|
|
160
|
-
m.objective(maximize(expr
|
|
161
|
-
m.constraint(expr
|
|
162
|
-
m.constraint(expr
|
|
163
|
-
m.
|
|
165
|
+
m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${setup}`));
|
|
166
|
+
m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_cap");
|
|
167
|
+
m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_cap");
|
|
168
|
+
m.indicator(setup, expr`sum(${qty}) <= 0`, {
|
|
169
|
+
activeValue: 0,
|
|
170
|
+
name: "require_setup",
|
|
171
|
+
});
|
|
164
172
|
|
|
165
173
|
const status = await m.solve();
|
|
166
174
|
const report = m.validateSolution();
|
|
@@ -264,3 +272,9 @@ export function verifyNegativePatterns() {
|
|
|
264
272
|
return { m, x, y, maximize };
|
|
265
273
|
}
|
|
266
274
|
```
|
|
275
|
+
|
|
276
|
+
### 実務モデリングのベストプラクティス
|
|
277
|
+
|
|
278
|
+
- **等式制約(`==`)よりも不等式制約(`>=` / `<=`)を優先する**: 需要充足やリソース上限など、コストが正である限り不等式で自然に等号成立する条件には `>=` / `<=` を使い、`==` は流量保存則や重みの合計($\sum w_i = 1$)など物理的・定義的な恒等式に限定してください。
|
|
279
|
+
- **`m.indicator(...)` 内の変数には有限の `lb` / `ub` を設定し `M` を省略する**: 変数に現実的な上下限を設定して `M` オプションを省略することで、`1e6` のような過大なデフォルト値ではなく、変数境界から最もタイトな Big-M が自動導出されます。
|
|
280
|
+
- **絶対条件以外のビジネス目標はソフト制約(スラック変数)にする**: 希望休や目標在庫などの「可能な限り守りたい条件」には非負スラック変数(`lb: 0`)を導入して目的関数でペナルティを課し、データ変動時にモデル全体が `Infeasible` で停止するのを防いでください。
|
|
@@ -119,7 +119,7 @@ export interface SolveModelData extends CheckModelData {
|
|
|
119
119
|
|
|
120
120
|
Custom solvers declare their supported features using `capabilities`. Before dispatching `solve()` or `check()`, `Model` validates:
|
|
121
121
|
- `supportsQP === false` when the objective contains quadratic terms (`QuadExpr`),
|
|
122
|
-
-
|
|
122
|
+
- when the model contains logical constraints (`m.logicalConstraints`) and the solver does not support SMT (`supportsSMT !== true`), `Model` automatically linearizes them into equivalent 0-1 MIP binary variables and linear constraints via `linearizeModelForMip`, and
|
|
123
123
|
- `supportsWarmStart !== true` when any variable has a primal `initialValue` set.
|
|
124
124
|
|
|
125
125
|
```typescript
|
package/docs/solvers/highs.md
CHANGED
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
# HiGHS Solver Engine
|
|
2
2
|
|
|
3
|
-
**HiGHS** is high-performance mathematical optimization software
|
|
3
|
+
**HiGHS** is high-performance mathematical optimization software initiated at the University of Edinburgh ([highs.dev](https://highs.dev/) · [ERGO-Code/HiGHS](https://github.com/ERGO-Code/HiGHS)). It is widely recognized as the state-of-the-art open-source solver for Linear Programming (LP), Mixed-Integer Programming (MIP), and Convex Quadratic Programming (QP).
|
|
4
|
+
|
|
5
|
+
In `@ohtaman/washi`, HiGHS runs entirely in WebAssembly compiled directly from official upstream source ([ERGO-Code/HiGHS](https://github.com/ERGO-Code/HiGHS)) using its native C API (`highs_c_api.h`), providing zero-config execution across Node.js, Web Workers, and browsers without third-party wrapper dependencies.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## Key Features in WaSHI
|
|
8
10
|
|
|
9
|
-
- **Built-in & Zero-Config**: Bundled directly with `@ohtaman/washi
|
|
11
|
+
- **Built-in & Zero-Config**: Bundled directly with `@ohtaman/washi` with zero external runtime dependencies. No separate peer dependency or C++ compiler required.
|
|
10
12
|
- **Permissive MIT License**: 100% commercially friendly.
|
|
11
|
-
- **Web Worker
|
|
13
|
+
- **Background Web Worker Execution**: Automatically offloads solving to a dedicated Web Worker in browser environments to avoid freezing the UI thread.
|
|
12
14
|
- **Warm-Start Support (`initialValue`)**: Supports passing variable primal initial value hints (`initialValue`) to HiGHS `setSolution` for warm starting.
|
|
13
|
-
- **Fast WASM Execution**:
|
|
15
|
+
- **Fast WASM Execution**: Runs the directly compiled 32-bit WebAssembly binary at near-native speed.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Architecture & WebAssembly Runtime
|
|
20
|
+
|
|
21
|
+
WaSHI compiles official HiGHS C++ source directly to WebAssembly (`highs.wasm`) with `-fwasm-exceptions` and single-threaded universal portability (`-DHIGHS_NO_DEFAULT_THREADS=ON`). By interfacing directly with HiGHS's native C API (`highs_c_api.h`), WaSHI achieves zero-copy solution extraction via typed array views on WebAssembly linear memory, rapid upstream release tracking, and a self-contained zero-dependency distribution.
|
|
22
|
+
|
|
23
|
+
### Callbacks & Solver Interactivity
|
|
24
|
+
|
|
25
|
+
- **Progress Monitoring & Cancellation (Supported)**:
|
|
26
|
+
Through HiGHS's internal logging and interrupt hooks, WaSHI provides real-time progress callbacks (`onProgress`) and cooperative mid-solve cancellation via standard `AbortSignal`.
|
|
27
|
+
- **Dynamic Cuts / Lazy Constraints (Not Exposed in WASM Driver)**:
|
|
28
|
+
WaSHI's HiGHS WebAssembly C API driver currently binds logging, progress, interrupt, and incumbent callbacks and does not expose in-tree lazy constraint or user cut callbacks across the WASM-to-JS boundary. For problems requiring dynamic subtour elimination or Benders decomposition, use [`await m.solveBenders({ subproblem })`](../guide/decomposition.md#benders-decomposition-solvebenders) (or a manual outer cutting-plane loop: solve $\to$ detect violation $\to$ `model.constraint(...)` $\to$ re-solve), which iteratively separates cuts and converges to the same exact optimal solution.
|
|
29
|
+
|
|
30
|
+
### WebAssembly Asset Lifecycle & On-Demand Builds
|
|
31
|
+
|
|
32
|
+
HiGHS's WebAssembly binary (`public/highs.wasm`) and runtime driver (`runtime.js`) are managed via `highs.config.json` with cryptographic SHA-256 verification:
|
|
33
|
+
- **Zero-Friction Verification**: Build and distribution pipelines run `node scripts/ensure-highs-wasm.mjs`, verifying local assets in milliseconds before bundling.
|
|
34
|
+
- **On-Demand Release Download**: When assets are missing or updated, `ensure-highs-wasm.mjs` automatically downloads verified binaries from WaSHI's GitHub Release assets.
|
|
35
|
+
- **Compiling from Source**: When updating HiGHS or modifying compiler flags, run `npm run build:highs-wasm` with the Emscripten SDK (`emcc`), CMake, and Ninja installed, and upload the rebuilt assets to the `highs-<version>` GitHub Release tag (`gh release upload highs-v1.15.1 public/highs.wasm src/modeler/solvers/highs/runtime.js --clobber`) so on-demand downloads match `highs.config.json` (or run `.github/workflows/build-wasm.yml` and commit the `highs-wasm-assets` artifact including `highs.config.json`).
|
|
14
36
|
|
|
15
37
|
---
|
|
16
38
|
|
|
@@ -38,7 +60,7 @@ const count = model.integer("count", { lb: 0, ub: 50 });
|
|
|
38
60
|
```
|
|
39
61
|
|
|
40
62
|
### 3. Convex Quadratic Programming (QP)
|
|
41
|
-
Solves quadratic objective functions subject to linear constraints:
|
|
63
|
+
Solves continuous models with convex quadratic objective functions subject to linear constraints:
|
|
42
64
|
|
|
43
65
|
```typescript
|
|
44
66
|
// Minimize risk/variance uniformly using expr:
|
|
@@ -49,6 +71,9 @@ model.objective(minimize(expr`0.5 * ${x}^2 + 0.5 * ${y}^2`));
|
|
|
49
71
|
// model.objective(minimize(quadObj));
|
|
50
72
|
```
|
|
51
73
|
|
|
74
|
+
> [!NOTE]
|
|
75
|
+
> **Continuous Convex QP Only**: HiGHS solves Convex QP models where **all** model variables are continuous (`m.continuous`), with a positive semi-definite quadratic matrix $\mathbf{Q}$ when minimizing (or negative semi-definite when maximizing). Mixed-Integer Quadratic Programming (**MIQP**—any model combining a quadratic objective with `Integer`, `Binary`, or `BoolVar` variables) and quadratic constraints (**QCP**) are not supported; to model products of binary decisions in MILP, see [Formulation Patterns & Tips](../guide/formulation-tips.md).
|
|
76
|
+
|
|
52
77
|
---
|
|
53
78
|
|
|
54
79
|
## Solver Options
|
|
@@ -69,6 +94,14 @@ await model.solve({
|
|
|
69
94
|
console.log(`Elapsed: ${ev.elapsedTime.toFixed(1)}s, Best: ${ev.objectiveValue}`);
|
|
70
95
|
},
|
|
71
96
|
});
|
|
97
|
+
|
|
98
|
+
// Always check hasPrimalSolution when timeLimit or AbortSignal is used:
|
|
99
|
+
if (model.hasPrimalSolution) {
|
|
100
|
+
console.log(
|
|
101
|
+
`Usable solution (${model.status}, termination: ${model.terminationStatus}):`,
|
|
102
|
+
`obj = ${model.objectiveValue}, gap = ${((model.relGap ?? 0) * 100).toFixed(2)}%`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
72
105
|
```
|
|
73
106
|
|
|
74
107
|
---
|
|
@@ -91,3 +124,13 @@ const syncModel = new Model({
|
|
|
91
124
|
});
|
|
92
125
|
```
|
|
93
126
|
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## References & Upstream Links
|
|
130
|
+
|
|
131
|
+
- **Official Website**: [https://highs.dev/](https://highs.dev/)
|
|
132
|
+
- **Official Documentation**: [https://ergo-code.github.io/HiGHS/](https://ergo-code.github.io/HiGHS/)
|
|
133
|
+
- **Source Repository**: [ERGO-Code/HiGHS](https://github.com/ERGO-Code/HiGHS) (MIT License)
|
|
134
|
+
- **Academic Citation**: If you cite HiGHS in academic or technical publications, please reference the foundational paper requested by the HiGHS authors:
|
|
135
|
+
> Q. Huangfu and J. A. J. Hall, *"Parallelizing the dual revised simplex method"*, Mathematical Programming Computation, 10 (1), 119–142, 2018. DOI: [10.1007/s12532-017-0130-5](https://doi.org/10.1007/s12532-017-0130-5)
|
|
136
|
+
|
package/docs/solvers/index.md
CHANGED
|
@@ -8,9 +8,9 @@ WaSHI is designed around a pluggable, decoupled solver architecture. You formula
|
|
|
8
8
|
|
|
9
9
|
| Solver | Engine License | Problem Classes | Best For | Status in WaSHI |
|
|
10
10
|
| :--- | :--- | :--- | :--- | :--- |
|
|
11
|
-
| **HiGHS** | **MIT** | **LP, MIP, Convex QP** | Industrial-scale optimization, operations research, finance | **Built-in default** (zero configuration) |
|
|
12
|
-
| **Z3** | **MIT** | **SMT, SAT, Logic, MIP** | Propositional logic, conflict diagnosis (`unsatCore`), rule engines | Optional plugin (`@ohtaman/washi/solvers/z3`) |
|
|
13
|
-
| **GLPK** | **GPL-3.0** | **LP, MIP** | Academic benchmarks, legacy GLPK compatibility | Optional plugin (`@ohtaman/washi/solvers/glpk`) |
|
|
11
|
+
| **[HiGHS](https://github.com/ERGO-Code/HiGHS)** | **MIT** | **LP, MIP, Convex QP** | Industrial-scale optimization, operations research, finance | **Built-in default** (zero configuration) |
|
|
12
|
+
| **[Z3](https://github.com/Z3Prover/z3)** (via [`z3-solver`](https://www.npmjs.com/package/z3-solver)) | **MIT** | **SMT, SAT, Logic, MIP** | Propositional logic, conflict diagnosis (`unsatCore`), rule engines | Optional plugin (`@ohtaman/washi/solvers/z3`) |
|
|
13
|
+
| **[GLPK](https://www.gnu.org/software/glpk/)** (via [`glpk.js`](https://www.npmjs.com/package/glpk.js)) | **GPL-3.0** | **LP, MIP** | Academic benchmarks, legacy GLPK compatibility | Optional plugin (`@ohtaman/washi/solvers/glpk`) |
|
|
14
14
|
| **Mock** | **MIT** | *Any* | Ultra-fast unit testing in CI without loading WebAssembly | Built-in |
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -36,7 +36,7 @@ const m3 = new Model({ solver: "glpk" });
|
|
|
36
36
|
|
|
37
37
|
## Solver Guides
|
|
38
38
|
|
|
39
|
-
- [HiGHS Guide](./highs.md): Web Worker
|
|
39
|
+
- [HiGHS Guide](./highs.md): Background Web Worker execution, MIP tolerances, and Convex QP solving.
|
|
40
40
|
- [Z3 Guide](./z3.md): SMT assertions, logic modeling, and Unsat Core analysis.
|
|
41
41
|
- [GLPK Guide](./glpk.md): GLPK adapter, GPL-3.0 licensing considerations, and browser usage.
|
|
42
42
|
- [Custom Solvers](./custom-solver.md): Step-by-step developer tutorial for integrating proprietary or cloud REST solvers.
|
package/docs/solvers/z3.md
CHANGED
|
@@ -23,6 +23,9 @@ npm install z3-solver
|
|
|
23
23
|
3. **Exact Mathematical Guarantees**:
|
|
24
24
|
Z3 can perform exact rational arithmetic without floating-point inaccuracies.
|
|
25
25
|
|
|
26
|
+
> [!TIP]
|
|
27
|
+
> **Z3 (`m.check()`) vs. HiGHS (`m.solve()`)**: Z3 excels at satisfiability checking (`m.check()`), formal policy verification, general inequality implications (`` implies(expr`${x} <= 50`, y) ``), and scale-invariant `unsatCore` extraction. For numerical LP, MIP, or Convex QP optimization with objective functions over tens or hundreds of variables, use the built-in **HiGHS** solver (`m.solve()`), whose simplex relaxations and branch-and-bound cutting planes are orders of magnitude faster than `Z3.Optimize`. Or combine both in a hybrid pipeline: verify/diagnose rules with Z3 `m.check()`, then optimize costs with HiGHS `m.solve()`.
|
|
28
|
+
|
|
26
29
|
---
|
|
27
30
|
|
|
28
31
|
## Basic Usage
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
> **范围说明**:本文档是面向使用 `@ohtaman/washi` 构建数理优化或可满足性(SAT/SMT)模型的 AI 编程助手与开发者的**使用端建模快速参考**。
|
|
4
4
|
> - **可移植 Agent Skill (`SKILL.md`)**:如需涵盖端到端建模、`toLpString()` 导出检查、`validateSolution()` 解验证以及 Z3 `unsatCore` 冲突诊断的完整工作流技能(兼容 Claude Code、Antigravity、Cursor、Codex 等支持 `SKILL.md` 的代理),请使用 [`skills/washi/SKILL.md`](https://github.com/ohtaman/washi/blob/main/skills/washi/SKILL.md)(同时随 npm 包分发于 `node_modules/@ohtaman/washi/skills/washi/SKILL.md`):
|
|
5
5
|
> ```bash
|
|
6
|
-
> #
|
|
6
|
+
> # 通过 skills CLI (skills.sh / vercel-labs/skills) 直接安装
|
|
7
|
+
> npx skills add ohtaman/washi
|
|
8
|
+
>
|
|
9
|
+
> # 或将随包附带的技能复制到项目的代理技能目录中
|
|
7
10
|
> mkdir -p .agents/skills/washi
|
|
8
11
|
> cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
|
|
9
12
|
> ```
|
|
@@ -54,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
54
57
|
1. **默认使用随包内置的 WASM(无隐式 CDN 回退)**:
|
|
55
58
|
- 在 Node.js 和现代构建工具中,WebAssembly 资源会自动相对于模块路径解析。
|
|
56
59
|
- 在浏览器部署中,请将完整的 `dist/browser/` 目录(包含 JS、Worker 和 `.wasm` 文件)一起部署。如果本地 WASM URL 缺失或返回了 HTML SPA 回退页面,初始化会立即失败并报告所尝试的 URL——**绝不会**静默切换到公共 CDN。
|
|
57
|
-
- 在浏览器运行时中,若需从自定义位置加载 `highs.wasm`,请调用 `setHighsWasmUrl("https://.../highs.wasm")` 或传入 `await m.solve({ wasmUrl: "https://.../highs.wasm" })`(在 Node.js 中会被忽略,Node.js
|
|
60
|
+
- 在浏览器运行时中,若需从自定义位置加载 `highs.wasm`,请调用 `setHighsWasmUrl("https://.../highs.wasm")` 或传入 `await m.solve({ wasmUrl: "https://.../highs.wasm" })`(在 Node.js 中会被忽略,Node.js 始终加载随包内置的本地 `highs.wasm` 运行时)。
|
|
58
61
|
2. **浏览器中运行 Z3 需要安全上下文、跨源隔离(Cross-Origin Isolation)与 CORS**:
|
|
59
62
|
- Z3 WebAssembly 依赖 `SharedArrayBuffer` 多线程。运行 Z3 的浏览器页面必须处于安全上下文(`window.isSecureContext === true`,即 HTTPS 或 `localhost`)且开启跨源隔离(`window.crossOriginIsolated === true`),服务端需配置以下响应头:
|
|
60
63
|
- `Cross-Origin-Opener-Policy: same-origin`(主文档响应)
|
|
@@ -63,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
63
66
|
- 导入 `@ohtaman/washi` 绝不会自动注入 Service Worker。当浏览器缺少安全上下文或跨源隔离时,Z3 会立即以明确说明原因的错误 reject。
|
|
64
67
|
3. **Web Worker 与取消控制 (`AbortSignal`)**:
|
|
65
68
|
- 求解前取消(执行开始前 `signal.aborted` 已为真)在所有求解器中均受支持。
|
|
66
|
-
-
|
|
69
|
+
- 在有效源非不透明(`window.origin !== "null"`)且支持 `Worker` 的浏览器环境中,HiGHS 默认在专用 Web Worker 中运行(`useWorker: true`);在有效源为不透明源(`window.origin === "null"`,如 `file:` URL 或未开启 `allow-same-origin` 的 `sandbox="allow-scripts"` `<iframe>` 嵌入)下则默认在主线程运行(`useWorker: false`)。向 `m.solve({ signal })` 传入 `{ signal }` 可以抢占式终止正在运行的 Worker 求解(或取消排队中的 Worker 请求)。
|
|
67
70
|
- 在主线程运行 HiGHS 时(`useWorker: false` 或在 Node.js 中),支持在求解器中断检查点触发的 `onProgress` 回调内同步调用 `abort()` 实现协作式中途取消;来自外部的任意异步取消在同步 WASM 线程到达检查点或执行完成前无法抢占阻塞线程。无中断钩子的单线程 WASM 求解器(GLPK、Z3)会在执行开始前检查 `signal`。
|
|
68
71
|
|
|
69
72
|
---
|
|
@@ -79,6 +82,7 @@ const m = new Model("ExampleModel");
|
|
|
79
82
|
|
|
80
83
|
// 标量变量
|
|
81
84
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
85
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
82
86
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
83
87
|
const b = m.binary("b");
|
|
84
88
|
const flag = m.bool("flag"); // 命题布尔变量(用于 Z3 / 逻辑约束)
|
|
@@ -100,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
100
104
|
### 步骤 2:构建表达式 (`expr`, `sum`, `dot`)
|
|
101
105
|
|
|
102
106
|
- **统一 `expr` 模板标签**:
|
|
103
|
-
- 线性表达式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
104
|
-
- 二次表达式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
105
|
-
- 线性约束(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(支持 `<=`、`>=`、`==` 和 `=`)。
|
|
106
|
-
-
|
|
107
|
-
- `sum(
|
|
108
|
-
- `dot(a, b)`
|
|
107
|
+
- 线性表达式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``、`` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``、`` expr`sum((${price} - ${cost}) * ${byItem})` ``
|
|
108
|
+
- 二次表达式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``、`` expr`sum((${vec} - ${target})^2)` ``(支持仅含连续变量 `m.continuous` 的凸 QP 目标函数;内置 HiGHS 不支持包含整数、二元或布尔变量的混合整数二次规划 MIQP)。
|
|
109
|
+
- 线性约束(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``、`` expr`sum(${byItem}) <= 100` ``(支持 `<=`、`>=`、`==` 和 `=`)。
|
|
110
|
+
- **聚合函数与逐元素算术运算**:
|
|
111
|
+
- `sum(containerOrItems, mapper?)`(可在 `` expr`sum(...)` `` 内部或独立函数 `sum(...)` 中使用)对一维/二维 `Array`、`Iterable` 或普通字典 `Record`(无需 `Object.values(record)`)求和。在 `` expr`sum(...)` `` 内部还支持容器逐元素四则运算与平方(`+`、`-`、`*`、`/`、`^2`)。
|
|
112
|
+
- `dot(a, b)`(可在 `` expr`dot(${a}, ${b})` `` 内部或独立函数 `dot(a, b)` 中使用)计算匹配的一维数组、二维数组(`number[][]` / `Var[][]`,计算 Frobenius 内积 $\sum_{i,j} A_{i,j} B_{i,j}$)、普通字典(`Record<string, ...>`)或混合 Array/Record 容器的内积,操作数顺序不限。
|
|
109
113
|
- **链式方法**:
|
|
110
114
|
- 算术运算(在 `LinExpr` 和 `QuadExpr` 上支持;`Var` 支持 `.add()`、`.sub()`、`.mul()`、`.neg()`): `.add(other)`、`.sub(other)`、`.mul(other)`、`.div(scalar)`、`.neg()`
|
|
111
115
|
- 比较运算(在 `Var` 和 `LinExpr` 上支持): `.le(rhs, name?)`、`.ge(rhs, name?)`、`.eq(rhs, name?)`
|
|
@@ -119,8 +123,8 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
119
123
|
// 使用 maximize() 或 minimize() 设置目标函数
|
|
120
124
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
121
125
|
|
|
122
|
-
//
|
|
123
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
126
|
+
// 将约束注册到模型(返回已注册的 Constraint / BoolExpr 或其数组)
|
|
127
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
124
128
|
|
|
125
129
|
// 指示约束 (MIP Big-M 转化: b === 1 => x <= 50)
|
|
126
130
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -132,9 +136,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
132
136
|
- 返回 `SolverStatus`(`"Optimal"`、`"Feasible"`、`"Infeasible"`、`"Unbounded"`、`"TimeLimit"`、`"IterationLimit"`、`"Error"`、`"Unknown"`、`"NotSolved"`、`"Sat"` 或 `"Unsat"`)。内置优化求解器返回优化状态(`"Optimal"`、`"Feasible"`、`"Infeasible"`、`"Unbounded"`、`"TimeLimit"`、`"IterationLimit"`、`"Error"`、`"Unknown"`、`"NotSolved"`),其中因时间、迭代或解数量上限(或中断)停止但已找到合法可行解(Incumbent)的运行会返回 `"Feasible"`;`"Sat"` 与 `"Unsat"` 包含在 `SolverStatus` 中以供可满足性/自定义求解器适配器(如 `MockSolver`)使用。
|
|
133
137
|
- 通过 `x.value`、`expr`...`.value`、`m.getValues(x)`、`m.getValues(container)`、`m.objectiveValue`、`constr.dual`、`constr.slack` 和 `x.reducedCost` 读取结果。
|
|
134
138
|
- 求解后调用 `m.validateSolution()` 可独立校验变量有限性、变量上下界(`lb`/`ub`)、整数/二元/布尔域、线性约束残差以及逻辑约束(`SolutionValidationReport`:`{ valid, complete, maxViolation, violations, ... }`)。注意:`validateSolution()` 校验变量与约束的原问题可行性,不会重新计算或比对 `m.objectiveValue`。
|
|
135
|
-
-
|
|
136
|
-
- 返回 `CheckResult`(`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }
|
|
137
|
-
-
|
|
139
|
+
- **可满足性检查与冲突诊断 (`await m.check(options?)`)**:
|
|
140
|
+
- 返回 `CheckResult`(`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`),其中在模型证明为 `"Unsat"` 时填充 `conflicts`。
|
|
141
|
+
- 对于包含线性算术约束或逻辑约束(`BoolVar`、`and`、`or`、`implies`、`xor`)的模型,`m.check()` 均可在任意求解器(包括默认的 HiGHS 与 GLPK,通过单次求解弹性松弛模型,并将逻辑约束自动线性化为 0-1 MIP)上运行,并在不可行时将需要松弛的冲突约束诊断为 `conflicts`(为保证语义严谨,`unsatCore: undefined`)。
|
|
142
|
+
- 精确的 SMT 不满足核提取(`result.unsatCore` 与 `result.conflicts`)在选用 SMT 求解器(`{ solver: "z3" }`)时提供。由于 Z3 在跟踪已注册约束的同时直接断言变量边界(`lb`/`ub`)和二元 `{0, 1}` 域,`unsatCore` 报告的是*结合模型固定变量边界/域时*发生冲突的已注册约束名称子集(不保证最小性)。
|
|
138
143
|
- **异步 `solve` / `check` 期间禁止并发修改模型**:
|
|
139
144
|
- 在 `await m.solve()` 或 `await m.check()` 挂起期间修改变量、约束或目标函数会使当前执行失效,并使过期的 Promise 被 reject。
|
|
140
145
|
|
|
@@ -157,10 +162,13 @@ export async function runMipExample() {
|
|
|
157
162
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
158
163
|
const setup = m.binary("setup");
|
|
159
164
|
|
|
160
|
-
m.objective(maximize(expr
|
|
161
|
-
m.constraint(expr
|
|
162
|
-
m.constraint(expr
|
|
163
|
-
m.
|
|
165
|
+
m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${setup}`));
|
|
166
|
+
m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_cap");
|
|
167
|
+
m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_cap");
|
|
168
|
+
m.indicator(setup, expr`sum(${qty}) <= 0`, {
|
|
169
|
+
activeValue: 0,
|
|
170
|
+
name: "require_setup",
|
|
171
|
+
});
|
|
164
172
|
|
|
165
173
|
const status = await m.solve();
|
|
166
174
|
const report = m.validateSolution();
|
|
@@ -264,3 +272,9 @@ export function verifyNegativePatterns() {
|
|
|
264
272
|
return { m, x, y, maximize };
|
|
265
273
|
}
|
|
266
274
|
```
|
|
275
|
+
|
|
276
|
+
### 实战运筹建模守则
|
|
277
|
+
|
|
278
|
+
- **优先使用不等式(`>=` / `<=`)而非严格等式(`==`)**:当目标成本为正时,需求覆盖与资源上限应优先采用 `>=` / `<=`;仅将 `==` 用于流量守恒或投资权重归一化($\sum w_i = 1$)等严格恒等关系。
|
|
279
|
+
- **为 `m.indicator(...)` 涉及的变量提供有限 `lb` 与 `ub` 并省略 `M`**:设置合理的有限变量边界并省略 `M`,可让 WaSHI 自动推导最紧凑的 Big-M,避免退化为松散的 `1e6` 默认值。
|
|
280
|
+
- **将非刚性业务规则建模为软约束(弹性松弛变量)**:对排班偏好、安全库存等非绝对硬性规则,引入非负松弛变量(`lb: 0`)并在目标函数中施加分级惩罚,避免真实业务数据微小波动导致整个模型返回 `"Infeasible"`。
|