@ohtaman/washi 0.1.2 → 0.3.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 (104) hide show
  1. package/README.ja.md +195 -105
  2. package/README.md +195 -105
  3. package/README.zh-CN.md +197 -107
  4. package/dist/browser/highs-worker.js +2 -2
  5. package/dist/browser/{highs-loader-T47YOyos.js → loader-CVt4CloI.js} +2 -2
  6. package/dist/browser/solvers/glpk.js +1065 -0
  7. package/dist/browser/solvers/glpk.min.js +1 -0
  8. package/dist/browser/solvers/z3-built.js +8245 -0
  9. package/dist/browser/solvers/z3-built.wasm +4 -0
  10. package/dist/browser/solvers/z3.js +7550 -0
  11. package/dist/browser/solvers/z3.min.js +5 -0
  12. package/dist/browser/washi.js +370 -642
  13. package/dist/browser/washi.min.js +4 -4
  14. package/dist/lib/async-mutex-Duo4jwzn.js +195 -0
  15. package/dist/lib/constraint-BRuB3ooC.js +90 -0
  16. package/dist/lib/highs-worker.js +2 -2
  17. package/dist/lib/{highs-loader-Blb99785.js → loader-Dti0v9yC.js} +1 -1
  18. package/dist/lib/registry-DksqCSjI.js +32 -0
  19. package/dist/lib/solvers/glpk.js +161 -2
  20. package/dist/lib/solvers/z3-assets.js +1 -0
  21. package/dist/lib/solvers/z3-built.js +8245 -0
  22. package/dist/lib/solvers/z3-built.wasm +4 -0
  23. package/dist/lib/solvers/z3.js +476 -2
  24. package/dist/lib/washi.js +604 -248
  25. package/dist/types/index.d.ts +7 -10
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/modeler/constraint.d.ts +0 -3
  28. package/dist/types/modeler/constraint.d.ts.map +1 -1
  29. package/dist/types/modeler/expression.d.ts +16 -10
  30. package/dist/types/modeler/expression.d.ts.map +1 -1
  31. package/dist/types/modeler/index.d.ts +3 -5
  32. package/dist/types/modeler/index.d.ts.map +1 -1
  33. package/dist/types/modeler/model.d.ts +91 -12
  34. package/dist/types/modeler/model.d.ts.map +1 -1
  35. package/dist/types/modeler/solvers/glpk/index.d.ts +3 -0
  36. package/dist/types/modeler/solvers/glpk/index.d.ts.map +1 -0
  37. package/dist/types/modeler/solvers/glpk/loader.d.ts +16 -0
  38. package/dist/types/modeler/solvers/glpk/loader.d.ts.map +1 -0
  39. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -0
  40. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -0
  41. package/dist/types/modeler/solvers/glpk.d.ts +1 -21
  42. package/dist/types/modeler/solvers/glpk.d.ts.map +1 -1
  43. package/dist/types/{highs-assets.d.ts → modeler/solvers/highs/assets.d.ts} +1 -1
  44. package/dist/types/modeler/solvers/highs/assets.d.ts.map +1 -0
  45. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -0
  46. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -0
  47. package/dist/types/modeler/solvers/highs/index.d.ts +6 -0
  48. package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -0
  49. package/dist/types/{highs-loader.d.ts → modeler/solvers/highs/loader.d.ts} +2 -2
  50. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -0
  51. package/dist/types/{highs-node-runtime.d.ts → modeler/solvers/highs/node-runtime.d.ts} +1 -1
  52. package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -0
  53. package/dist/types/modeler/solvers/highs/solver.d.ts +33 -0
  54. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -0
  55. package/dist/types/modeler/solvers/highs/worker-client.d.ts +13 -0
  56. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -0
  57. package/dist/types/{highs-worker-factory.d.ts → modeler/solvers/highs/worker-factory.d.ts} +1 -1
  58. package/dist/types/modeler/solvers/highs/worker-factory.d.ts.map +1 -0
  59. package/dist/types/modeler/solvers/highs/worker.d.ts +2 -0
  60. package/dist/types/modeler/solvers/highs/worker.d.ts.map +1 -0
  61. package/dist/types/modeler/solvers/highs.d.ts +1 -31
  62. package/dist/types/modeler/solvers/highs.d.ts.map +1 -1
  63. package/dist/types/modeler/solvers/index.d.ts +0 -2
  64. package/dist/types/modeler/solvers/index.d.ts.map +1 -1
  65. package/dist/types/modeler/solvers/mock.d.ts +1 -0
  66. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  67. package/dist/types/modeler/solvers/registry.d.ts +1 -2
  68. package/dist/types/modeler/solvers/registry.d.ts.map +1 -1
  69. package/dist/types/modeler/solvers/types.d.ts +1 -1
  70. package/dist/types/modeler/solvers/z3/assets.d.ts +11 -0
  71. package/dist/types/modeler/solvers/z3/assets.d.ts.map +1 -0
  72. package/dist/types/modeler/solvers/z3/index.d.ts +3 -0
  73. package/dist/types/modeler/solvers/z3/index.d.ts.map +1 -0
  74. package/dist/types/modeler/solvers/z3/loader.d.ts +56 -0
  75. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -0
  76. package/dist/types/modeler/solvers/z3/solver.d.ts +34 -0
  77. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -0
  78. package/dist/types/modeler/solvers/z3.d.ts +1 -35
  79. package/dist/types/modeler/solvers/z3.d.ts.map +1 -1
  80. package/dist/types/modeler/types.d.ts +8 -3
  81. package/dist/types/modeler/types.d.ts.map +1 -1
  82. package/dist/types/modeler/variable.d.ts +1 -6
  83. package/dist/types/modeler/variable.d.ts.map +1 -1
  84. package/package.json +28 -6
  85. package/dist/browser/_washi-optional-engine-282Ug7Gw.js +0 -7
  86. package/dist/lib/glpk-5cUpjyb9.js +0 -135
  87. package/dist/lib/z3-C6GeIicJ.js +0 -509
  88. package/dist/types/glpk-loader.d.ts +0 -8
  89. package/dist/types/glpk-loader.d.ts.map +0 -1
  90. package/dist/types/highs-assets.d.ts.map +0 -1
  91. package/dist/types/highs-browser-runtime.d.ts +0 -2
  92. package/dist/types/highs-browser-runtime.d.ts.map +0 -1
  93. package/dist/types/highs-loader.d.ts.map +0 -1
  94. package/dist/types/highs-node-runtime.d.ts.map +0 -1
  95. package/dist/types/highs-worker-client.d.ts +0 -12
  96. package/dist/types/highs-worker-client.d.ts.map +0 -1
  97. package/dist/types/highs-worker-factory.d.ts.map +0 -1
  98. package/dist/types/highs-worker.d.ts +0 -2
  99. package/dist/types/highs-worker.d.ts.map +0 -1
  100. package/dist/types/modeler/mip-compat.d.ts +0 -43
  101. package/dist/types/modeler/mip-compat.d.ts.map +0 -1
  102. package/dist/types/modeler/pulp-compat.d.ts +0 -67
  103. package/dist/types/modeler/pulp-compat.d.ts.map +0 -1
  104. /package/dist/browser/{highs-browser-runtime-D13gHOT5.js → browser-runtime-D13gHOT5.js} +0 -0
package/README.ja.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <source media="(prefers-color-scheme: light)" srcset="./assets/washi-header-light.png">
5
5
  <img alt="WaSHI - TypeScript / JavaScript のための数理最適化・代数モデラー" src="./assets/washi-header-light.png" width="420" />
6
6
  </picture>
7
- <p><strong>TypeScript / JavaScript のための直感的な数理最適化・代数モデラー</strong></p>
7
+ <p><strong>TypeScript / JavaScript のための代数モデラー</strong></p>
8
8
  <p>
9
9
  <em>線形計画 (LP) • 混合整数計画 (MIP) • 凸二次計画 (QP) • SMT論理制約 &amp; 矛盾診断</em>
10
10
  </p>
@@ -14,56 +14,58 @@
14
14
  <a href="./README.zh-CN.md">简体中文</a>
15
15
  </p>
16
16
  <p>
17
+ <a href="https://www.npmjs.com/package/@ohtaman/washi"><img src="https://img.shields.io/npm/v/@ohtaman/washi?color=crimson&label=npm" alt="npm version" /></a>
18
+ <a href="https://github.com/ohtaman/washi/actions/workflows/distribution.yml"><img src="https://github.com/ohtaman/washi/actions/workflows/distribution.yml/badge.svg" alt="CI Status" /></a>
17
19
  <a href="https://ohtaman.github.io/washi/"><img src="https://img.shields.io/badge/docs-online-blue" alt="docs" /></a>
18
20
  <a href="https://ohtaman.github.io/washi/demo/"><img src="https://img.shields.io/badge/demo-playground-brightgreen" alt="live demo" /></a>
19
- <a href="https://www.npmjs.com/package/@ohtaman/washi"><img src="https://img.shields.io/npm/v/@ohtaman/washi?color=crimson&label=npm" alt="npm version" /></a>
20
- <a href="https://bundlephobia.com/package/@ohtaman/washi"><img src="https://img.shields.io/badge/bundle%20size-7.2%20kB%20(gzip)-blue" alt="bundle size" /></a>
21
21
  <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
22
- <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="TypeScript types" />
23
- <img src="https://img.shields.io/badge/tests-120%2B%20passing-brightgreen" alt="tests" />
24
22
  </p>
25
23
  </div>
26
24
 
27
25
  ---
28
26
 
29
- **WaSHI** は、Python の **PuLP** や Julia の **JuMP** のような表現力豊かな代数モデリングの美しさを、モダンな TypeScript / JavaScript エコシステムにもたらす高水準モデラーライブラリです。数式そのままの自然な記法で数理最適化モデルを定式化し、Node.js、ブラウザ、Bun、Deno、Next.js 上でネイティブ C++ コンパイル不要で即座に求解できます(WebAssembly 版の **HiGHS**、**Z3**、**GLPK** ソルバーを統合サポート)。
27
+ **WaSHI** は、Python の **PuLP** や Julia の **JuMP** に着想を得た、TypeScript / JavaScript 向けの代数モデラーです。アプリケーションのコードで変数・目的関数・数値制約や論理制約を記述できます。WaSHI はモデルの構築、ソルバー向けの変換、変数への結果の反映を担当し、最適化や充足可能性の判定は **HiGHS**・**Z3**・**GLPK** が行います。
28
+
29
+ HiGHS が標準ソルバーで、Z3 と GLPK は任意で追加するプラグインです。Node.js 用とブラウザ用の配布物に WebAssembly アセットを含みます。配信に必要な設定は[ブラウザでの利用方法](#-ブラウザ--web-worker-での利用方法)を参照してください。
30
+
31
+ [ブラウザデモを試す](https://ohtaman.github.io/washi/demo/) · [サンプルのソースと実行方法](https://github.com/ohtaman/washi/blob/main/examples/README.md) · [ESM / script / CodePen のサンプル](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)
30
32
 
31
33
  ```ts
32
- import { Model, expr, c } from "@ohtaman/washi";
34
+ import { Model, expr } from "@ohtaman/washi";
33
35
 
34
- const m = new Model({ sense: "Maximize" });
35
- const x = m.addVar({ type: "Integer", lb: 0 });
36
- const y = m.addVar({ type: "Integer", lb: 0 });
36
+ const m = new Model("木工製品の利益最大化");
37
+ const x = m.integer("机", { lb: 0 });
38
+ const y = m.integer("椅子", { lb: 0 });
37
39
 
38
40
  m.maximize(expr`30 * ${x} + 50 * ${y}`);
39
- m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
40
- m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
41
+ m.constrain(expr`${x} + 2 * ${y} <= 40`, "木材上限");
42
+ m.constrain(expr`2 * ${x} + ${y} <= 50`, "労働力上限");
41
43
 
42
- await m.solve();
44
+ await m.optimize();
43
45
  console.log(`生産量: x=${x.value}, y=${y.value} (最大利益: $${m.objectiveValue})`);
44
46
  ```
45
47
 
46
48
  ---
47
49
 
48
- ## 🎯 なぜ WaSHI を選ぶのか?
50
+ ## 🎯 WaSHI が提供するもの
49
51
 
50
- 1. 🌐 **完全クライアントサイド実行 & サーバーコストゼロ**: ブラウザ、エッジワーカー、Electron アプリ内で本格的な数理最適化を完結。バックエンドサーバーの運用コストがゼロになり、オフライン環境でも動作可能。社外秘データ(シフト表や原価など)が外部に送信されないため強固なプライバシーを担保します。
51
- 2. 📦 **ゼロ依存・単一パッケージでの圧倒的な配布容易性**: Python のランタイム管理や仮想環境、OS 別の C/C++ ネイティブソルバーバイナリの同梱に悩まされることはありません。`npm install @ohtaman/washi` だけで WebAssembly ソルバーを含めて即座に動作します。
52
- 3. ✍️ **直感的な代数モデリング DX(PuLP / python-mip 互換)**: 泥臭い行列のインデックス操作は不要。テンプレートリテラル記法(`c`${x} + 2 * ${y} <= 40``)や、Python 最適化開発者におなじみの構文(`LpProblem`, `lpSum`)で、教科書そのままの数式を記述できます。
53
- 4. 🔄 **統一マルチソルバー API(HiGHS / Z3 / GLPK)**: 定式化したモデルコードはそのまま、産業用 MIP/QP に強い **HiGHS**、論理制約・SMT に強い **Z3**、学術用途の **GLPK** をオプション1つで柔軟に切り替え可能です。
54
- 5. 🔍 **実行不可能(Infeasible)時の矛盾原因自動診断**: ルール同士が矛盾して解なしになった場合、衝突している制約の部分集合(Unsat Core)を Z3 エンジン経由で特定・診断します。
55
- 6. ⚡ **WebAssembly 上での本格的な産業規模パフォーマンス**: 線形時間 $O(N)$ の高速式ビルダーにより、20,000 変数を超える大規模 MIP を約100msで定式化し、WebAssembly 版 HiGHS で約15秒で高速に求解します。
52
+ 1. **代数的なモデル記述**: 直感的なドメイン変数メソッド(`m.binary`, `m.integer`, `m.continuous`)、タグ付きテンプレート(`expr`)、ベクトル集計関数(`dot`, `sum`)、そして第1推奨の `m.constrain()` により、数学的定義に忠実なモデルを簡潔に記述できます。
53
+ 2. **ソルバーアダプター**: 共通のモデリング API から HiGHS・Z3・GLPK を利用でき、[独自アダプターも登録できます](https://github.com/ohtaman/washi/blob/main/docs/custom-solver-guide.md)。モデルの問題クラスを扱えるソルバーを選ぶ必要があり、対応機能はソルバーごとに異なります。
54
+ 3. **ローカル実行と WebAssembly の配布**: ローカルソルバーのアダプターでは、別途 Python サービスや C/C++ のネイティブビルドを用意せず、アプリケーション内で求解できます。ブラウザへの配布には生成された JS・Worker・WASM が必要です。オフライン利用には、これらをローカルに配置するかキャッシュしておく必要があります。Z3 には追加のブラウザ隔離設定があります。
55
+ 4. **名前付き制約と SMT の結果**: Z3 を明示的に使って充足可能性を確認し、矛盾する制約の調査に Unsat Core を利用できます。返されるのは矛盾する制約の部分集合であり、最小の集合とは限りません。
56
+
57
+ モデルの構築と変換は WaSHI が担当します。扱える問題クラス、数値計算の挙動、求解時間は、選択したソルバー・設定・問題に依存します。モデル構築のオーバーヘッドとソルバーの実行時間は分けて計測する必要があり、特定のモデルでの結果は一般的な性能保証ではありません。
56
58
 
57
59
  ---
58
60
 
59
- ## ⚡ ソルバー対応マトリクス
61
+ ## ⚙️ ソルバーアダプターと対応範囲
60
62
 
61
- | ソルバー | ライセンス | 解ける問題クラス | 最適な用途 | WaSHI での提供形態 |
63
+ | アダプター | エンジンのライセンス | 対応する問題クラス | エンジンの役割 | 提供形態 |
62
64
  | :--- | :--- | :--- | :--- | :--- |
63
- | **HiGHS** | **MIT** | **LP, MIP, 凸QP** | 産業規模の計画最適化、生産スケジューリング、金融ポートフォリオ | **標準ビルトイン**(ゼロ設定) |
64
- | **Z3** | **MIT** | **SMT, SAT, 命題論理, MIP** | 業務ルール検証、制約矛盾の診断 (`unsatCore`)、ルールエンジン | オプションプラグイン (`@ohtaman/washi/solvers/z3`) |
65
- | **GLPK** | **GPL-3.0** | **LP, MIP** | 学術的ベンチマーク、既存 GLPK モデル資産との照合 | オプションプラグイン (`@ohtaman/washi/solvers/glpk`) |
66
- | **Mock** | **MIT** | *任意* | WASM をロードしない CI/CD での高速単体テスト | 標準ビルトイン |
65
+ | **HiGHS** | **MIT** | **LP, MIP, 凸QP** | 数値最適化 | 標準ソルバー |
66
+ | **Z3** | **MIT** | **SMT, SAT, 命題論理, MIP** | 充足可能性の判定、Unsat Core、最適化 | オプションプラグイン (`@ohtaman/washi/solvers/z3`) |
67
+ | **GLPK** | **GPL-3.0** | **LP, MIP** | 線形・混合整数最適化 | オプションプラグイン (`@ohtaman/washi/solvers/glpk`) |
68
+ | **Mock** | **MIT** | — | 統合テスト用の模擬応答 | 標準搭載。モデルの求解は行わない |
67
69
 
68
70
  ---
69
71
 
@@ -99,49 +101,49 @@ npm install glpk.js # GLPK ソルバーを利用する場合 (GPL-3.0)
99
101
  リソース制約の下で利益を最大化する生産計画:
100
102
 
101
103
  ```ts
102
- import { Model, c, expr } from "@ohtaman/washi";
104
+ import { Model, expr } from "@ohtaman/washi";
103
105
 
104
- const m = new Model({ name: "FactoryPlanning", sense: "Maximize" });
106
+ const m = new Model("FactoryPlanning");
105
107
 
106
108
  // 決定変数(整数・非負)
107
- const chairs = m.addVar({ name: "chairs", type: "Integer", lb: 0 });
108
- const tables = m.addVar({ name: "tables", type: "Integer", lb: 0 });
109
+ const chairs = m.integer("chairs", { lb: 0 });
110
+ const tables = m.integer("tables", { lb: 0 });
109
111
 
110
112
  // テンプレートリテラル記法による自然な目的関数と制約
111
113
  m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
112
- m.addConstraint(c`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
113
- m.addConstraint(c`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
114
+ m.constrain(expr`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
115
+ m.constrain(expr`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
114
116
 
115
117
  // 標準の HiGHS WASM エンジンで求解
116
- const status = await m.solve();
118
+ const status = await m.optimize();
117
119
 
118
120
  console.log(`ステータス: ${status}`); // "Optimal"
119
121
  console.log(`最大利益: $${m.objectiveValue}`); // 1100
120
122
  console.log(`生産数: 椅子=${chairs.value}, 机=${tables.value}`); // 椅子 20, 机 10
121
123
  ```
122
124
 
123
- #### 🗂 多次元変数グリッド (`addVars`)
125
+ #### 🗂 多次元変数グリッド
124
126
 
125
- キー一覧や多次元インデックスから、1 行で変数群を定義(PuLP の `LpVariable.dicts` 相当):
127
+ キー一覧や多次元インデックスから、1 行で変数群を定義:
126
128
 
127
129
  ```ts
128
130
  // 1次元辞書形式のバイナリ変数群
129
- const openCity = m.addVars(["Tokyo", "Osaka", "Nagoya"], { type: "Binary" });
131
+ const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
130
132
 
131
133
  // 2次元輸送量行列: [工場] x [店舗]
132
- const flow = m.addVars(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
134
+ const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
133
135
 
134
136
  // 2次元グリッド (例: 8x8 チェス盤)
135
- const board = m.addVars(8, 8, { type: "Binary", prefix: "queen" });
137
+ const board = m.binary(8, 8, { prefix: "queen" });
136
138
  ```
137
139
 
138
- #### 🔀 Big-M インジケータ制約 (`addIndicator`)
140
+ #### 🔀 Big-M インジケータ制約 (`m.indicator`)
139
141
 
140
142
  条件分岐や固定費の発生ルールを、煩雑な手動 Big-M 計算なしに記述:
141
143
 
142
144
  ```ts
143
145
  // 「東京工場が稼働していない (openCity['Tokyo'] == 0) なら、生産量 flow は 0」
144
- m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
146
+ m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
145
147
  activeValue: 0,
146
148
  M: 1000,
147
149
  });
@@ -154,20 +156,21 @@ m.addIndicator(openCity["Tokyo"], flow["F1"]["StoreA"].le(0), {
154
156
  期待リターンと予算制約を満たしつつ、共分散行列に基づく投資リスクを最小化:
155
157
 
156
158
  ```ts
157
- import { Model, QuadExpr, sum } from "@ohtaman/washi";
159
+ import { Model, QuadExpr, sum, dot, expr } from "@ohtaman/washi";
158
160
 
159
- const m = new Model({ name: "MarkowitzPortfolio", sense: "Minimize" });
161
+ const m = new Model("MarkowitzPortfolio");
160
162
  const assets = ["Tech", "Bonds", "RealEstate"];
161
163
  const returns = [0.14, 0.04, 0.08];
162
164
 
163
165
  // 決定変数: 投資比率 (0.0 〜 1.0)
164
- const w = assets.map((a) => m.addVar({ name: `w_${a}`, lb: 0, ub: 1 }));
166
+ const w = m.continuous(assets, { lb: 0, ub: 1 });
165
167
 
166
168
  // 1. 予算制約: 合計 100%
167
- m.addConstraint(sum(w).eq(1.0), "budget");
169
+ m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
168
170
 
169
171
  // 2. 目標リターン: 最低 8.5%
170
- m.addConstraint(sum(w.map((v, i) => v.mul(returns[i]))).ge(0.085), "min_return");
172
+ const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
173
+ m.constrain(expr`${dot(retMap, w)} >= 0.085`, "min_return");
171
174
 
172
175
  // 3. 共分散行列 (リスク & 相関)
173
176
  const cov = [
@@ -180,15 +183,15 @@ const cov = [
180
183
  let risk = new QuadExpr();
181
184
  for (let i = 0; i < assets.length; i++) {
182
185
  for (let j = 0; j < assets.length; j++) {
183
- risk = risk.add(w[i].mul(w[j]).mul(cov[i][j]));
186
+ risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
184
187
  }
185
188
  }
186
189
  m.minimize(risk);
187
190
 
188
- await m.solve();
191
+ await m.optimize();
189
192
  console.log(`最小分散: ${m.objectiveValue?.toFixed(6)}`);
190
- assets.forEach((name, i) => {
191
- console.log(` ${name}: ${((w[i].value || 0) * 100).toFixed(2)}%`);
193
+ assets.forEach((name) => {
194
+ console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
192
195
  });
193
196
  ```
194
197
 
@@ -199,7 +202,8 @@ assets.forEach((name, i) => {
199
202
  命題変数(`BoolVar`)と論理結合子(`and`, `or`, `implies`, `not`, `xor`)、数値比較制約を混在させたルール定義:
200
203
 
201
204
  ```ts
202
- import { Model, not } from "@ohtaman/washi";
205
+ import { Model, not, expr } from "@ohtaman/washi";
206
+ import "@ohtaman/washi/solvers/z3";
203
207
 
204
208
  const m = new Model({ name: "SmartSecurityRules" });
205
209
 
@@ -207,16 +211,16 @@ const isNight = m.addBoolVar("isNight");
207
211
  const motion = m.addBoolVar("motion");
208
212
  const alarm = m.addBoolVar("alarm");
209
213
  const siren = m.addBoolVar("siren");
210
- const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
214
+ const lux = m.integer("lux", { lb: 0 });
211
215
 
212
216
  // 論理ルール
213
- m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
214
- m.addConstraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
217
+ m.constrain(lux.le(50).implies(isNight), "dark_implies_night");
218
+ m.constrain(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
215
219
 
216
220
  // 観測されたセンサー値
217
- m.addConstraint(lux.le(10), "sensor_dark");
218
- m.addConstraint(motion, "sensor_motion");
219
- m.addConstraint(alarm, "alarm_on");
221
+ m.constrain(expr`${lux} <= 10`, "sensor_dark");
222
+ m.constrain(motion, "sensor_motion");
223
+ m.constrain(alarm, "alarm_on");
220
224
 
221
225
  // 目的関数なしで制約充足可能性(SAT)を判定
222
226
  const res = await m.check({ solver: "z3" });
@@ -232,28 +236,28 @@ console.log(`サイレン作動? ${m.values.get("siren")}`); // true
232
236
  業務ルールや制約群が互いに矛盾している場合、**矛盾を引き起こしている制約の部分集合**(Unsat Core。最小性は保証しません)を特定してデバッグ(Z3 が必要):
233
237
 
234
238
  ```ts
235
- import { Model } from "@ohtaman/washi";
239
+ import { Model, expr } from "@ohtaman/washi";
236
240
  import "@ohtaman/washi/solvers/z3";
237
241
 
238
242
  const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
239
- const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
240
- const budget = m.addVar({ name: "budget", type: "Continuous", lb: 0, ub: 100000 });
243
+ const engineers = m.integer("engineers", { lb: 0, ub: 20 });
244
+ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
241
245
  const rushDelivery = m.addBoolVar("rushDelivery");
242
246
 
243
247
  // ルール 1: エンジニア1人あたり $8,000
244
- m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
248
+ m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
245
249
 
246
250
  // ルール 2: 特急納品が必須
247
- m.addConstraint(rushDelivery, "rush_required");
251
+ m.constrain(rushDelivery, "rush_required");
248
252
 
249
253
  // ルール 3: 特急納品には最低 8 人のエンジニアが必要
250
- m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
254
+ m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
251
255
 
252
256
  // ルール 4: 役員会の予算上限 $50,000 (矛盾: 8人 * $8000 = $64,000 > $50,000!)
253
- m.addConstraint(budget.le(50000), "strict_board_budget_cap");
257
+ m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
254
258
 
255
259
  // ルール 5: 最低 1 人雇用の労働基準(無関係なルール)
256
- m.addConstraint(engineers.ge(1), "minimum_one_employee");
260
+ m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
257
261
 
258
262
  const res = await m.check({ solver: "z3" });
259
263
 
@@ -265,35 +269,54 @@ console.log("矛盾している制約:", res.unsatCore);
265
269
 
266
270
  ---
267
271
 
268
- ### 5. Python PuLP 互換レイヤー
272
+ ### 5. GLPK ソルバーの利用 (GPL-3.0)
273
+
274
+ GNU Linear Programming Kit (GLPK) ソルバープラグインを利用する場合:
275
+
276
+ ```ts
277
+ import { Model, expr } from "@ohtaman/washi";
278
+ import "@ohtaman/washi/solvers/glpk";
279
+
280
+ const m = new Model({ solver: "glpk" });
281
+ const x = m.integer("x", { lb: 0 });
282
+ const y = m.integer("y", { lb: 0 });
283
+
284
+ m.maximize(expr`30 * ${x} + 50 * ${y}`);
285
+ m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
286
+ m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
287
+
288
+ const status = await m.optimize();
289
+ console.log(`ステータス: ${status}`); // "Optimal"
290
+ console.log(`目的関数値: ${m.objectiveValue}`); // 1100
291
+ ```
292
+
293
+ ---
294
+
295
+ ### 6. ベクトル演算と高速内積 (`dot`)
269
296
 
270
- 既存の Python PuLP 最適化スクリプトをスムーズに移植:
297
+ ネストしたループを書くことなく、1次元・2次元の線形結合を1行で記述:
271
298
 
272
299
  ```ts
273
- import {
274
- LpProblem,
275
- LpVariable,
276
- LpMaximize,
277
- LpInteger,
278
- HiGHS,
279
- value,
280
- } from "@ohtaman/washi";
281
-
282
- const prob = new LpProblem("PuLPDemo", LpMaximize);
283
-
284
- const x = new LpVariable("x", 0, undefined, LpInteger);
285
- const y = new LpVariable("y", 0, undefined, LpInteger);
286
-
287
- // PuLP の += 相当
288
- prob.add(x.mul(30).add(y.mul(50)));
289
- prob.add(x.add(y.mul(2)).le(40));
290
- prob.add(x.mul(2).add(y).le(50));
291
-
292
- await prob.solve(new HiGHS());
293
-
294
- console.log(`ステータス: ${prob.status}`);
295
- console.log(`目的関数値: ${value(prob.objective)}`);
296
- console.log(`x = ${value(x)}, y = ${value(y)}`);
300
+ import { Model, dot } from "@ohtaman/washi";
301
+
302
+ const m = new Model();
303
+ const factories = ["Tokyo", "Osaka"];
304
+ const stores = ["StoreA", "StoreB"];
305
+
306
+ const costs = {
307
+ Tokyo: { StoreA: 4, StoreB: 7 },
308
+ Osaka: { StoreA: 8, StoreB: 3 },
309
+ };
310
+
311
+ const ship = m.continuous(factories, stores);
312
+
313
+ // 総輸送コストを1行で最小化:
314
+ m.minimize(dot(costs, ship));
315
+ await m.optimize();
316
+
317
+ // 最適解の全変数値を取得
318
+ const solution = m.getValues(ship);
319
+ console.log(solution);
297
320
  ```
298
321
 
299
322
  ---
@@ -341,7 +364,52 @@ await model.solve();
341
364
 
342
365
  CDN から直接読み込む場合は、URL を `https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.1.2/dist/browser/washi.min.js`(`0.1.2` 以降のバージョンを固定)に指定できます。ESM は同じ版の `dist/browser/washi.js` を指定します。CDN の URL を選ぶことが明示的な CDN 利用になり、ローカル配信が勝手に CDN へ切り替わることはありません。
343
366
 
344
- ブラウザ用ビルドに含まれるエンジンは HiGHS です。GLPK / Z3 エンジンの連携には npm パッケージと任意依存を使ってください。完結した利用例は `examples/distribution/` にあります。
367
+ ### ブラウザ環境でのソルバープラグイン(GLPK & Z3)
368
+
369
+ ブラウザ配布物には、`dist/browser/solvers/` 配下に事前バンドルされたスタンドアロンソルバープラグインが含まれています(CDN 利用時は `>= 0.2.0` または `@latest` で利用可能):
370
+ - **GLPK**: `dist/browser/solvers/glpk.js` (ESM) および `dist/browser/solvers/glpk.min.js` (IIFE、グローバル `WashiGLPK` を登録)
371
+ - **Z3**: `dist/browser/solvers/z3.js` (ESM) および `dist/browser/solvers/z3.min.js` (IIFE、グローバル `WashiZ3` を登録)、ならびに `z3-built.js` / `z3-built.wasm`
372
+
373
+ #### ブラウザ ESM での GLPK 利用
374
+ ```html
375
+ <script type="module">
376
+ import { Model } from "./washi/washi.js";
377
+ import "./washi/solvers/glpk.js";
378
+
379
+ const model = new Model({ solver: "glpk" });
380
+ const x = model.addVar({ lb: 0, ub: 10 });
381
+ model.maximize(x);
382
+ await model.solve();
383
+ console.log("GLPK ステータス:", model.status);
384
+ </script>
385
+ ```
386
+
387
+ #### スクリプトタグでの GLPK 利用
388
+ ```html
389
+ <script src="./washi/washi.min.js"></script>
390
+ <script src="./washi/solvers/glpk.min.js"></script>
391
+ <script>
392
+ const model = new Washi.Model({ solver: "glpk" });
393
+ const x = model.addVar({ lb: 0, ub: 10 });
394
+ model.maximize(x);
395
+ model.solve().then(status => console.log("GLPK ステータス:", status));
396
+ </script>
397
+ ```
398
+
399
+ > [!NOTE]
400
+ > **ブラウザ環境での Z3 利用と Cross-Origin Isolation**:
401
+ > Z3 の WebAssembly エンジンはマルチスレッド(`SharedArrayBuffer`)を必要とするため、ブラウザのセキュリティ仕様により Cross-Origin Isolation が必須となります。ドキュメントレスポンスに以下のヘッダーが必要です:
402
+ > ```http
403
+ > Cross-Origin-Opener-Policy: same-origin
404
+ > Cross-Origin-Embedder-Policy: require-corp
405
+ > ```
406
+ > また、別オリジンからアセットを読み込む場合は `Cross-Origin-Resource-Policy: cross-origin` の返却が必要です。
407
+ >
408
+ > 任意のヘッダーを設定できない一般的な静的配信(GitHub Pages や CodePen 等の iframe プレビュー環境)では、追加対応なしで条件を満たすことはできません。なお、ライブラリ本体 `@ohtaman/washi` のインポートによって利用者のページに Service Worker が勝手に登録されることはありません。
409
+ >
410
+ > GitHub Pages で公開している公式 Web デモでは、`/washi/` スコープ限定の独立した `coi-serviceworker.js` を同オリジンに配置・登録し、初回アクセス時に1回だけ安全に再読み込みを行って `window.crossOriginIsolated === true` を確立してから Z3 を実行します。Service Worker が利用できない環境や隔離の確立に失敗した環境では、原因を明示して安全に実行を中断します。
411
+
412
+ 完全なスタンドアロンサンプル(ESM、スクリプトタグ、CodePen)は `examples/distribution/` に用意されています。
345
413
 
346
414
  ### 工場内・オフライン配布、Worker、自前配信
347
415
 
@@ -364,32 +432,54 @@ setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
364
432
 
365
433
  ---
366
434
 
367
- ## 💻 サンプルスクリプト & インタラクティブ Web デモ
435
+ ## 🛠️ カスタムソルバー開発と拡張性
368
436
 
369
- `examples/node/` 配下に実行可能なスタンドアロンスクリプトを用意しています:
437
+ WaSHI は疎結合なプラグイン方式のソルバーアーキテクチャを採用しています。独自の最適化エンジン(クラウド REST ソルバー、WebAssembly エンジン、商用ソルバーアダプターなど)を容易に構築・登録できます:
370
438
 
371
- ```bash
372
- npx tsx examples/node/01-production-planning.ts
373
- npx tsx examples/node/02-knapsack-problem.ts
374
- npx tsx examples/node/03-portfolio-qp.ts
375
- npx tsx examples/node/04-smt-security-rules.ts
376
- npx tsx examples/node/05-unsat-core-diagnosis.ts
377
- npx tsx examples/node/06-pulp-compatibility.ts
378
- ```
439
+ - 詳細な開発手順、API コントラクト仕様(`Solver`, `LpModelData`, `SolverResult`)、およびテスト手法については [Custom Solver Developer Guide](docs/custom-solver-guide.md) をご覧ください。
440
+
441
+ ---
442
+
443
+ ## 💻 サンプルとブラウザデモ
379
444
 
380
- ブラウザで動く Web デモ (`examples/web-demo`) を起動するには:
445
+ [サンプル一覧](https://github.com/ohtaman/washi/blob/main/examples/README.md)および[オンライン実践レシピ集・API逆引き索引](https://ohtaman.github.io/washi/ja/examples/)では、目的別・WaSHI の API 機能別にサンプルを探せます。
446
+
447
+ | カテゴリ | サンプル | 確認できること | ソルバー |
448
+ | :--- | :--- | :--- | :--- |
449
+ | **基本モデル** | [生産計画](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | 整数変数、利益の最大化、資源制約 (`expr`, `c`) | HiGHS |
450
+ | **基本モデル** | [輸送・物流ネットワーク](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 2次元マトリクス変数 (`addVars(rows, cols)`) と供給・需要バランス | HiGHS |
451
+ | **基本モデル** | [栄養問題と感度分析](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | 潜在価格 (`constr.dual`)、余剰 (`constr.slack`)、被約費用 (`var.reducedCost`) | HiGHS |
452
+ | **実践応用** | [スタッフのシフト作成](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | 週間シフト最適化と、土曜日の希望休が衝突した際の原因特定 | HiGHS + Z3 |
453
+ | **実践応用** | [ECカートのクーポン最適適用](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | 条件付き購入金額閾値 (`addIndicator`)、併用不可ルール、追加購入提案 | HiGHS |
454
+ | **実践応用** | [ポートフォリオ配分](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) | 凸二次目的関数 (`QuadExpr`) と配分の制約 | HiGHS |
455
+ | **論理 / SMT** | [業務ルールの検証](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | 数値・論理制約 (`addBoolVar`, `implies`) の充足可能性の確認 | Z3 |
456
+ | **論理 / SMT** | [矛盾する制約の診断](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | 名前付き制約と Unsat Core (`unsatCore`) | Z3 |
457
+
458
+ [公開ブラウザデモ](https://ohtaman.github.io/washi/demo/)では、モデルを編集して実行できます。ブラウザ標準 ESM、script タグ、CodePen での使い方は[配布形式のサンプル](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md)を参照してください。Z3 は Cross-Origin Isolation が必要です。詳細は上記のブラウザ要件に記載しています。
459
+
460
+ ローカルで実行する場合は、リポジトリを取得して開発用依存をインストールします。
381
461
 
382
462
  ```bash
463
+ git clone https://github.com/ohtaman/washi.git
464
+ cd washi
465
+ npm ci
466
+ npx tsx examples/node/01-production-planning.ts
467
+ npx tsx examples/node/09-transportation-problem.ts
468
+ npx tsx examples/node/10-diet-sensitivity.ts
469
+ npx tsx examples/node/11-shift-scheduling.ts
470
+ npx tsx examples/node/12-ecommerce-coupon-optimizer.ts
471
+
472
+ # ローカルのブラウザデモ: http://localhost:5173/demo/
383
473
  npm run dev
384
- # ドキュメント: http://localhost:5173/
385
- # インタラクティブデモ: http://localhost:5173/demo/
386
474
  ```
387
475
 
476
+ サンプルはリポジトリの `src/` モジュールを import しています。公開パッケージを使うアプリでは、`@ohtaman/washi` と必要なソルバープラグインから import してください。
477
+
388
478
  ---
389
479
 
390
480
  ## 🧪 テスト & 信頼性
391
481
 
392
- 厳格なテスト駆動開発 (TDD) により、120 以上の自動テストを実施しています:
482
+ リポジトリのユニット・統合テストを実行し、配布物をビルドできます:
393
483
 
394
484
  ```bash
395
485
  # すべてのユニット・統合テストを実行