@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
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
|
```
|
|
@@ -57,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
57
57
|
1. **デフォルトでパッケージ同梱 WASM を使用(暗黙の CDN フォールバックなし)**:
|
|
58
58
|
- Node.js および最新のバンドラーでは、WebAssembly アセットはモジュールからの相対パスで自動的に解決されます。
|
|
59
59
|
- ブラウザ環境へデプロイする際は、`dist/browser/` ディレクトリ一式(JS、Worker、`.wasm` ファイル)をまとめて配置してください。ローカルの WASM URL が欠落している場合や HTML の SPA フォールバックが返された場合、初期化は試行した URL を明示して即座に失敗し、**外部パブリック CDN へ勝手に切り替わることは決してありません**。
|
|
60
|
-
- ブラウザランタイムで `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` ランタイムを読み込むため無視されます)。
|
|
61
61
|
2. **ブラウザでの Z3 実行にはセキュアコンテキスト・Cross-Origin Isolation・CORS が必要**:
|
|
62
62
|
- Z3 WebAssembly は `SharedArrayBuffer` によるマルチスレッドを使用します。Z3 を実行するブラウザページは、セキュアコンテキスト(`window.isSecureContext === true`、すなわち HTTPS または `localhost`)かつ Cross-Origin Isolated(`window.crossOriginIsolated === true`)である必要があり、以下の HTTP ヘッダーを配信する必要があります:
|
|
63
63
|
- `Cross-Origin-Opener-Policy: same-origin`(ドキュメント応答)
|
|
@@ -66,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
66
66
|
- `@ohtaman/washi` のインポートによって Service Worker が自動注入されることはありません。ブラウザでセキュアコンテキストや Cross-Origin Isolation が欠けている場合、Z3 は原因を示すエラーで即座に reject します。
|
|
67
67
|
3. **Web Worker とキャンセル制御 (`AbortSignal`)**:
|
|
68
68
|
- 求解開始前のキャンセル(実行開始時点で `signal.aborted`)は、すべてのソルバーでサポートされています。
|
|
69
|
-
- `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 リクエストをキャンセル)できます。
|
|
70
70
|
- メインスレッド実行の HiGHS(`useWorker: false` または Node.js)では、ソルバーの割り込みチェックポイントで呼ばれる `onProgress` コールバック内で同期的に `abort()` をトリガーした場合に協調的な途中中断が機能します。外部からの非同期 abort は、同期実行中の WASM スレッドがチェックポイントまたは完了に達するまで割り込むことはできません。割り込みフックを持たないシングルスレッド WASM ソルバー(GLPK、Z3)は実行開始前に `signal` を確認します。
|
|
71
71
|
|
|
72
72
|
---
|
|
@@ -82,6 +82,7 @@ const m = new Model("ExampleModel");
|
|
|
82
82
|
|
|
83
83
|
// スカラー変数
|
|
84
84
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
85
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
85
86
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
86
87
|
const b = m.binary("b");
|
|
87
88
|
const flag = m.bool("flag"); // 命題論理変数(Z3 / 論理制約用)
|
|
@@ -103,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
103
104
|
### ステップ 2: 数式の構築 (`expr`, `sum`, `dot`)
|
|
104
105
|
|
|
105
106
|
- **統一 `expr` テンプレートタグ**:
|
|
106
|
-
- 1次式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
107
|
-
- 2次式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
108
|
-
- 線形制約(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(`<=`, `>=`, `==`, `=` をサポート)。
|
|
109
|
-
-
|
|
110
|
-
- `sum(
|
|
111
|
-
- `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, ...>`)、または配列と辞書の混合コンテナの内積を、引数の順序を問わず計算します。
|
|
112
113
|
- **メソッドチェーン(Fluent API)**:
|
|
113
114
|
- 算術演算(`LinExpr` と `QuadExpr` で利用可能。`Var` は `.add()`, `.sub()`, `.mul()`, `.neg()` をサポート): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
|
|
114
115
|
- 比較演算(`Var` と `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
|
|
@@ -122,8 +123,8 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
122
123
|
// maximize() または minimize() を用いて目的関数を設定
|
|
123
124
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
124
125
|
|
|
125
|
-
//
|
|
126
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
126
|
+
// モデルに制約条件を登録(登録された Constraint / BoolExpr またはその配列を返す)
|
|
127
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
127
128
|
|
|
128
129
|
// 条件付き(Indicator)制約 (MIP Big-M 変換: b === 1 => x <= 50)
|
|
129
130
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -161,10 +162,13 @@ export async function runMipExample() {
|
|
|
161
162
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
162
163
|
const setup = m.binary("setup");
|
|
163
164
|
|
|
164
|
-
m.objective(maximize(expr
|
|
165
|
-
m.constraint(expr
|
|
166
|
-
m.constraint(expr
|
|
167
|
-
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
|
+
});
|
|
168
172
|
|
|
169
173
|
const status = await m.solve();
|
|
170
174
|
const report = m.validateSolution();
|
|
@@ -268,3 +272,9 @@ export function verifyNegativePatterns() {
|
|
|
268
272
|
return { m, x, y, maximize };
|
|
269
273
|
}
|
|
270
274
|
```
|
|
275
|
+
|
|
276
|
+
### 実務モデリングのベストプラクティス
|
|
277
|
+
|
|
278
|
+
- **等式制約(`==`)よりも不等式制約(`>=` / `<=`)を優先する**: 需要充足やリソース上限など、コストが正である限り不等式で自然に等号成立する条件には `>=` / `<=` を使い、`==` は流量保存則や重みの合計($\sum w_i = 1$)など物理的・定義的な恒等式に限定してください。
|
|
279
|
+
- **`m.indicator(...)` 内の変数には有限の `lb` / `ub` を設定し `M` を省略する**: 変数に現実的な上下限を設定して `M` オプションを省略することで、`1e6` のような過大なデフォルト値ではなく、変数境界から最もタイトな Big-M が自動導出されます。
|
|
280
|
+
- **絶対条件以外のビジネス目標はソフト制約(スラック変数)にする**: 希望休や目標在庫などの「可能な限り守りたい条件」には非負スラック変数(`lb: 0`)を導入して目的関数でペナルティを課し、データ変動時にモデル全体が `Infeasible` で停止するのを防いでください。
|
package/docs/solvers/highs.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
4
|
|
|
5
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.
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ In `@ohtaman/washi`, HiGHS runs entirely in WebAssembly compiled directly from o
|
|
|
10
10
|
|
|
11
11
|
- **Built-in & Zero-Config**: Bundled directly with `@ohtaman/washi` with zero external runtime dependencies. No separate peer dependency or C++ compiler required.
|
|
12
12
|
- **Permissive MIT License**: 100% commercially friendly.
|
|
13
|
-
- **Web Worker
|
|
13
|
+
- **Background Web Worker Execution**: Automatically offloads solving to a dedicated Web Worker in browser environments to avoid freezing the UI thread.
|
|
14
14
|
- **Warm-Start Support (`initialValue`)**: Supports passing variable primal initial value hints (`initialValue`) to HiGHS `setSolution` for warm starting.
|
|
15
15
|
- **Fast WASM Execution**: Runs the directly compiled 32-bit WebAssembly binary at near-native speed.
|
|
16
16
|
|
|
@@ -24,14 +24,14 @@ WaSHI compiles official HiGHS C++ source directly to WebAssembly (`highs.wasm`)
|
|
|
24
24
|
|
|
25
25
|
- **Progress Monitoring & Cancellation (Supported)**:
|
|
26
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
|
|
28
|
-
|
|
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
29
|
|
|
30
30
|
### WebAssembly Asset Lifecycle & On-Demand Builds
|
|
31
31
|
|
|
32
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
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
|
|
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
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`).
|
|
36
36
|
|
|
37
37
|
---
|
|
@@ -60,7 +60,7 @@ const count = model.integer("count", { lb: 0, ub: 50 });
|
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
### 3. Convex Quadratic Programming (QP)
|
|
63
|
-
Solves quadratic objective functions subject to linear constraints:
|
|
63
|
+
Solves continuous models with convex quadratic objective functions subject to linear constraints:
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
66
|
// Minimize risk/variance uniformly using expr:
|
|
@@ -71,6 +71,9 @@ model.objective(minimize(expr`0.5 * ${x}^2 + 0.5 * ${y}^2`));
|
|
|
71
71
|
// model.objective(minimize(quadObj));
|
|
72
72
|
```
|
|
73
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
|
+
|
|
74
77
|
---
|
|
75
78
|
|
|
76
79
|
## Solver Options
|
|
@@ -91,6 +94,14 @@ await model.solve({
|
|
|
91
94
|
console.log(`Elapsed: ${ev.elapsedTime.toFixed(1)}s, Best: ${ev.objectiveValue}`);
|
|
92
95
|
},
|
|
93
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
|
+
}
|
|
94
105
|
```
|
|
95
106
|
|
|
96
107
|
---
|
|
@@ -113,3 +124,13 @@ const syncModel = new Model({
|
|
|
113
124
|
});
|
|
114
125
|
```
|
|
115
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
|
@@ -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
|
|
@@ -57,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
57
57
|
1. **默认使用随包内置的 WASM(无隐式 CDN 回退)**:
|
|
58
58
|
- 在 Node.js 和现代构建工具中,WebAssembly 资源会自动相对于模块路径解析。
|
|
59
59
|
- 在浏览器部署中,请将完整的 `dist/browser/` 目录(包含 JS、Worker 和 `.wasm` 文件)一起部署。如果本地 WASM URL 缺失或返回了 HTML SPA 回退页面,初始化会立即失败并报告所尝试的 URL——**绝不会**静默切换到公共 CDN。
|
|
60
|
-
- 在浏览器运行时中,若需从自定义位置加载 `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` 运行时)。
|
|
61
61
|
2. **浏览器中运行 Z3 需要安全上下文、跨源隔离(Cross-Origin Isolation)与 CORS**:
|
|
62
62
|
- Z3 WebAssembly 依赖 `SharedArrayBuffer` 多线程。运行 Z3 的浏览器页面必须处于安全上下文(`window.isSecureContext === true`,即 HTTPS 或 `localhost`)且开启跨源隔离(`window.crossOriginIsolated === true`),服务端需配置以下响应头:
|
|
63
63
|
- `Cross-Origin-Opener-Policy: same-origin`(主文档响应)
|
|
@@ -66,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
66
66
|
- 导入 `@ohtaman/washi` 绝不会自动注入 Service Worker。当浏览器缺少安全上下文或跨源隔离时,Z3 会立即以明确说明原因的错误 reject。
|
|
67
67
|
3. **Web Worker 与取消控制 (`AbortSignal`)**:
|
|
68
68
|
- 求解前取消(执行开始前 `signal.aborted` 已为真)在所有求解器中均受支持。
|
|
69
|
-
-
|
|
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 请求)。
|
|
70
70
|
- 在主线程运行 HiGHS 时(`useWorker: false` 或在 Node.js 中),支持在求解器中断检查点触发的 `onProgress` 回调内同步调用 `abort()` 实现协作式中途取消;来自外部的任意异步取消在同步 WASM 线程到达检查点或执行完成前无法抢占阻塞线程。无中断钩子的单线程 WASM 求解器(GLPK、Z3)会在执行开始前检查 `signal`。
|
|
71
71
|
|
|
72
72
|
---
|
|
@@ -82,6 +82,7 @@ const m = new Model("ExampleModel");
|
|
|
82
82
|
|
|
83
83
|
// 标量变量
|
|
84
84
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
85
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
85
86
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
86
87
|
const b = m.binary("b");
|
|
87
88
|
const flag = m.bool("flag"); // 命题布尔变量(用于 Z3 / 逻辑约束)
|
|
@@ -103,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
103
104
|
### 步骤 2:构建表达式 (`expr`, `sum`, `dot`)
|
|
104
105
|
|
|
105
106
|
- **统一 `expr` 模板标签**:
|
|
106
|
-
- 线性表达式(`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
107
|
-
- 二次表达式(`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
108
|
-
- 线性约束(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(支持 `<=`、`>=`、`==` 和 `=`)。
|
|
109
|
-
-
|
|
110
|
-
- `sum(
|
|
111
|
-
- `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 容器的内积,操作数顺序不限。
|
|
112
113
|
- **链式方法**:
|
|
113
114
|
- 算术运算(在 `LinExpr` 和 `QuadExpr` 上支持;`Var` 支持 `.add()`、`.sub()`、`.mul()`、`.neg()`): `.add(other)`、`.sub(other)`、`.mul(other)`、`.div(scalar)`、`.neg()`
|
|
114
115
|
- 比较运算(在 `Var` 和 `LinExpr` 上支持): `.le(rhs, name?)`、`.ge(rhs, name?)`、`.eq(rhs, name?)`
|
|
@@ -122,8 +123,8 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
122
123
|
// 使用 maximize() 或 minimize() 设置目标函数
|
|
123
124
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
124
125
|
|
|
125
|
-
//
|
|
126
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
126
|
+
// 将约束注册到模型(返回已注册的 Constraint / BoolExpr 或其数组)
|
|
127
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
127
128
|
|
|
128
129
|
// 指示约束 (MIP Big-M 转化: b === 1 => x <= 50)
|
|
129
130
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -161,10 +162,13 @@ export async function runMipExample() {
|
|
|
161
162
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
162
163
|
const setup = m.binary("setup");
|
|
163
164
|
|
|
164
|
-
m.objective(maximize(expr
|
|
165
|
-
m.constraint(expr
|
|
166
|
-
m.constraint(expr
|
|
167
|
-
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
|
+
});
|
|
168
172
|
|
|
169
173
|
const status = await m.solve();
|
|
170
174
|
const report = m.validateSolution();
|
|
@@ -268,3 +272,9 @@ export function verifyNegativePatterns() {
|
|
|
268
272
|
return { m, x, y, maximize };
|
|
269
273
|
}
|
|
270
274
|
```
|
|
275
|
+
|
|
276
|
+
### 实战运筹建模守则
|
|
277
|
+
|
|
278
|
+
- **优先使用不等式(`>=` / `<=`)而非严格等式(`==`)**:当目标成本为正时,需求覆盖与资源上限应优先采用 `>=` / `<=`;仅将 `==` 用于流量守恒或投资权重归一化($\sum w_i = 1$)等严格恒等关系。
|
|
279
|
+
- **为 `m.indicator(...)` 涉及的变量提供有限 `lb` 与 `ub` 并省略 `M`**:设置合理的有限变量边界并省略 `M`,可让 WaSHI 自动推导最紧凑的 Big-M,避免退化为松散的 `1e6` 默认值。
|
|
280
|
+
- **将非刚性业务规则建模为软约束(弹性松弛变量)**:对排班偏好、安全库存等非绝对硬性规则,引入非负松弛变量(`lb: 0`)并在目标函数中施加分级惩罚,避免真实业务数据微小波动导致整个模型返回 `"Infeasible"`。
|