@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.
Files changed (59) hide show
  1. package/README.ja.md +103 -421
  2. package/README.md +103 -421
  3. package/README.zh-CN.md +103 -421
  4. package/THIRD_PARTY_LICENSES.md +5 -3
  5. package/dist/browser/solvers/glpk.js +2 -2
  6. package/dist/browser/solvers/glpk.min.js +1 -1
  7. package/dist/browser/washi.js +777 -261
  8. package/dist/browser/washi.min.js +3 -3
  9. package/dist/lib/{registry-C20sxTwm.js → registry-D7TZ3Wh_.js} +88 -18
  10. package/dist/lib/solvers/glpk.js +3 -3
  11. package/dist/lib/solvers/z3.js +1 -1
  12. package/dist/lib/washi.js +864 -421
  13. package/dist/types/index.d.ts +1 -1
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/modeler/expression.d.ts +47 -26
  16. package/dist/types/modeler/expression.d.ts.map +1 -1
  17. package/dist/types/modeler/index.d.ts +2 -2
  18. package/dist/types/modeler/index.d.ts.map +1 -1
  19. package/dist/types/modeler/model.d.ts +12 -6
  20. package/dist/types/modeler/model.d.ts.map +1 -1
  21. package/dist/types/modeler/parser.d.ts +25 -5
  22. package/dist/types/modeler/parser.d.ts.map +1 -1
  23. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  24. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  25. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  26. package/dist/types/modeler/solvers/types.d.ts +2 -0
  27. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  28. package/docs/examples/diet-sensitivity.md +15 -14
  29. package/docs/examples/ecommerce-coupons.md +15 -22
  30. package/docs/examples/index.md +14 -12
  31. package/docs/examples/knapsack.md +8 -7
  32. package/docs/examples/portfolio-qp.md +11 -4
  33. package/docs/examples/production.md +4 -4
  34. package/docs/examples/shift-scheduling.md +20 -7
  35. package/docs/examples/smt-security.md +16 -4
  36. package/docs/examples/transportation.md +46 -19
  37. package/docs/examples/traveling-salesperson.md +28 -44
  38. package/docs/examples/unsat-core.md +15 -5
  39. package/docs/examples/vehicle-routing.md +10 -8
  40. package/docs/getting-started/ai-agents.md +25 -14
  41. package/docs/getting-started/cross-origin.md +56 -28
  42. package/docs/getting-started/index.md +40 -69
  43. package/docs/getting-started/runtimes.md +66 -61
  44. package/docs/guide/constraints.md +46 -40
  45. package/docs/guide/decomposition.md +127 -259
  46. package/docs/guide/expressions.md +90 -39
  47. package/docs/guide/formulation-tips.md +212 -0
  48. package/docs/guide/objectives.md +7 -4
  49. package/docs/guide/unsat-core.md +18 -6
  50. package/docs/guide/variables.md +33 -38
  51. package/docs/ja/getting-started/ai-agents.md +24 -14
  52. package/docs/solvers/highs.md +27 -6
  53. package/docs/solvers/index.md +1 -1
  54. package/docs/solvers/z3.md +3 -0
  55. package/docs/zh/getting-started/ai-agents.md +24 -14
  56. package/llms-full.txt +929 -687
  57. package/llms.txt +21 -19
  58. package/package.json +3 -3
  59. package/skills/washi/SKILL.md +14 -10
@@ -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]`. Ideal for physical measurements, flow rates, budgets, and weights:
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 integers. Ideal for countable assets (machines, trucks, staff count):
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 mathematical 0-1 values. Ideal for MIP selection, yes/no decisions, and facility selections:
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 logic propositions for SMT / SAT modeling with Z3:
39
+ Pure boolean propositions (`true` / `false`) for logical and SMT constraints:
41
40
 
42
41
  ```typescript
43
- // Single propositional boolean variable
44
- const isNight = m.bool("isNight");
45
- const alarmTriggered = m.bool("alarmTriggered");
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(alarmTriggered.implies(isNight), "alarm_policy");
47
+ m.constraint(isAdmin.implies(hasMfa), "admin_requires_mfa");
49
48
  ```
50
49
 
51
- ### 💡 Modeling Semi-Continuous Decisions
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
- While not a primitive `VarType` in `Model`, semi-continuous behavior is cleanly modeled by combining a continuous variable with a binary indicator variable and linking constraints ($L \cdot b \le x \le U \cdot b$):
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
- ```typescript
57
- import { Model, expr } from "@ohtaman/washi";
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`) support count, arrays of keys, and row/column keys to create 1D arrays, typed dictionaries, and 2D matrices in a single call.
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<string, Var>` or `Record<string, BoolVar>`:
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 with stable variable names, you can supply optional primal initial values (`initialValue`) on a subset or all numeric variables (`m.continuous`, `m.integer`, `m.binary`, or `v.initialValue = ...`):
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 strictly an optional solver hint (passed to HiGHS via `setSolution`). It does **not** fix the variable, add a constraint, or guarantee either solver acceptance or a runtime speedup. It remains separate from `variable.value` (which is `null` until solved and populated with the solver's primal solution).
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 inside bounds for `"Integer"` and `"Binary"` variables). Partial starts are not required to satisfy every model constraint.
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 (optimal or incumbent primal values from `solve()`, or a satisfying assignment from `check()`) are accessible via `variable.value` (returns `number | null` for `Var`, or `boolean | null` for `BoolVar`) or the bulk accessor `m.getValues()`:
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 — how much an objective coefficient must improve before a variable at its bound enters the basis)
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("isNight"); // BoolVar | undefined
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 では常にローカルの `highs` パッケージランタイムを読み込むため無視されます)。
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`)。`m.solve({ signal })` に `{ signal }` を渡すと、実行中の Worker 求解をプリエンプティブに強制終了(または待機中の Worker リクエストをキャンセル)できます。
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} * ${n} + ${n}^2` ``(凸 QP 目的関数向けに変数同士の積や二乗をサポート)。
108
- - 線形制約(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(`<=`, `>=`, `==`, `=` をサポート)。
109
- - **集計ヘルパー**:
110
- - `sum(iterable, mapper?)` は `Var` / `LinExpr` / `number` の `Iterable`(例:`Var[]`, `grid.flat()`, `Object.values(record)`)の総和を計算します。
111
- - `dot(a, b)` は対応する 1次元配列、2次元配列(`number[][]` / `Var[][]`)、プレーンな辞書(`Record<string, ...>`)、または配列と辞書の混合コンテナの内積を、引数の順序を問わず計算します。
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`${dot(profit, qty)} - 20 * ${setup}`));
165
- m.constraint(expr`${dot(woodPerUnit, qty)} <= 40`, "wood_cap");
166
- m.constraint(expr`${dot(laborPerUnit, qty)} <= 50`, "labor_cap");
167
- m.constraint(expr`${sum(Object.values(qty))} <= 30 * ${setup}`, "require_setup");
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` で停止するのを防いでください。
@@ -1,6 +1,6 @@
1
1
  # HiGHS Solver Engine
2
2
 
3
- **HiGHS** is high-performance mathematical optimization software developed at the University of Edinburgh ([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).
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 Multithreading**: Automatically offloads intensive solving to background Web Workers in browser environments to avoid freezing the UI thread.
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 Supported in WASM Wrapper)**:
28
- The upstream WebAssembly wrapper intentionally excludes low-level native callback type 8 (`MIP lazy constraints`) due to WASM-to-JS boundary and synchronous memory safety constraints. For problems requiring subtour elimination or Benders decomposition, use an **iterative cutting-plane loop** (solve $\to$ detect violation $\to$ `model.constraint(...)` $\to$ re-solve).
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 official GitHub Release assets.
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
+
@@ -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 multithreading, MIP tolerances, and QP solving.
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.
@@ -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 始终加载本地安装的 `highs` 包运行时)。
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
- - 在支持 `Worker` 的浏览器环境中,HiGHS 默认在专用 Web Worker 中运行(`useWorker: true`)。向 `m.solve({ signal })` 传入 `{ signal }` 可以抢占式终止正在运行的 Worker 求解(或取消排队中的 Worker 请求)。
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} * ${n} + ${n}^2` ``(支持凸 QP 目标函数中的变量相乘与平方项)。
108
- - 线性约束(`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``(支持 `<=`、`>=`、`==` 和 `=`)。
109
- - **聚合函数**:
110
- - `sum(iterable, mapper?)` 对 `Var` / `LinExpr` / `number` 的 `Iterable`(例如 `Var[]`、`grid.flat()` 或 `Object.values(record)`)求和。
111
- - `dot(a, b)` 计算匹配的一维数组、二维数组(`number[][]` / `Var[][]`)、普通字典(`Record<string, ...>`)或混合 Array/Record 容器的内积,操作数顺序不限。
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`${dot(profit, qty)} - 20 * ${setup}`));
165
- m.constraint(expr`${dot(woodPerUnit, qty)} <= 40`, "wood_cap");
166
- m.constraint(expr`${dot(laborPerUnit, qty)} <= 50`, "labor_cap");
167
- m.constraint(expr`${sum(Object.values(qty))} <= 30 * ${setup}`, "require_setup");
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"`。