@ohtaman/washi 0.2.0 → 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 (38) hide show
  1. package/README.ja.md +131 -115
  2. package/README.md +131 -115
  3. package/README.zh-CN.md +133 -117
  4. package/dist/browser/solvers/glpk.js +10 -2
  5. package/dist/browser/solvers/glpk.min.js +1 -1
  6. package/dist/browser/solvers/z3.js +34 -12
  7. package/dist/browser/solvers/z3.min.js +2 -2
  8. package/dist/browser/washi.js +368 -306
  9. package/dist/browser/washi.min.js +4 -4
  10. package/dist/lib/{constraint-XWdn0ID9.js → constraint-BRuB3ooC.js} +0 -3
  11. package/dist/lib/solvers/glpk.js +10 -2
  12. package/dist/lib/solvers/z3.js +35 -10
  13. package/dist/lib/washi.js +368 -321
  14. package/dist/types/index.d.ts +5 -7
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/modeler/constraint.d.ts +0 -3
  17. package/dist/types/modeler/constraint.d.ts.map +1 -1
  18. package/dist/types/modeler/expression.d.ts +16 -10
  19. package/dist/types/modeler/expression.d.ts.map +1 -1
  20. package/dist/types/modeler/index.d.ts +2 -4
  21. package/dist/types/modeler/index.d.ts.map +1 -1
  22. package/dist/types/modeler/model.d.ts +90 -11
  23. package/dist/types/modeler/model.d.ts.map +1 -1
  24. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  25. package/dist/types/modeler/solvers/highs/solver.d.ts +1 -1
  26. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  27. package/dist/types/modeler/solvers/types.d.ts +1 -1
  28. package/dist/types/modeler/solvers/z3/loader.d.ts +12 -0
  29. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  30. package/dist/types/modeler/types.d.ts +8 -3
  31. package/dist/types/modeler/types.d.ts.map +1 -1
  32. package/dist/types/modeler/variable.d.ts +1 -6
  33. package/dist/types/modeler/variable.d.ts.map +1 -1
  34. package/package.json +10 -2
  35. package/dist/types/modeler/mip-compat.d.ts +0 -43
  36. package/dist/types/modeler/mip-compat.d.ts.map +0 -1
  37. package/dist/types/modeler/pulp-compat.d.ts +0 -67
  38. package/dist/types/modeler/pulp-compat.d.ts.map +0 -1
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 が提供するもの
51
+
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 を利用できます。返されるのは矛盾する制約の部分集合であり、最小の集合とは限りません。
49
56
 
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秒で高速に求解します。
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,7 @@ 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";
203
206
  import "@ohtaman/washi/solvers/z3";
204
207
 
205
208
  const m = new Model({ name: "SmartSecurityRules" });
@@ -208,16 +211,16 @@ const isNight = m.addBoolVar("isNight");
208
211
  const motion = m.addBoolVar("motion");
209
212
  const alarm = m.addBoolVar("alarm");
210
213
  const siren = m.addBoolVar("siren");
211
- const lux = m.addVar({ name: "lux", type: "Integer", lb: 0 });
214
+ const lux = m.integer("lux", { lb: 0 });
212
215
 
213
216
  // 論理ルール
214
- m.addConstraint(lux.le(50).implies(isNight), "dark_implies_night");
215
- 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");
216
219
 
217
220
  // 観測されたセンサー値
218
- m.addConstraint(lux.le(10), "sensor_dark");
219
- m.addConstraint(motion, "sensor_motion");
220
- 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");
221
224
 
222
225
  // 目的関数なしで制約充足可能性(SAT)を判定
223
226
  const res = await m.check({ solver: "z3" });
@@ -233,28 +236,28 @@ console.log(`サイレン作動? ${m.values.get("siren")}`); // true
233
236
  業務ルールや制約群が互いに矛盾している場合、**矛盾を引き起こしている制約の部分集合**(Unsat Core。最小性は保証しません)を特定してデバッグ(Z3 が必要):
234
237
 
235
238
  ```ts
236
- import { Model } from "@ohtaman/washi";
239
+ import { Model, expr } from "@ohtaman/washi";
237
240
  import "@ohtaman/washi/solvers/z3";
238
241
 
239
242
  const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
240
- const engineers = m.addVar({ name: "engineers", type: "Integer", lb: 0, ub: 20 });
241
- 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 });
242
245
  const rushDelivery = m.addBoolVar("rushDelivery");
243
246
 
244
247
  // ルール 1: エンジニア1人あたり $8,000
245
- m.addConstraint(engineers.mul(8000).le(budget), "budget_limit");
248
+ m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
246
249
 
247
250
  // ルール 2: 特急納品が必須
248
- m.addConstraint(rushDelivery, "rush_required");
251
+ m.constrain(rushDelivery, "rush_required");
249
252
 
250
253
  // ルール 3: 特急納品には最低 8 人のエンジニアが必要
251
- m.addConstraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
254
+ m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
252
255
 
253
256
  // ルール 4: 役員会の予算上限 $50,000 (矛盾: 8人 * $8000 = $64,000 > $50,000!)
254
- m.addConstraint(budget.le(50000), "strict_board_budget_cap");
257
+ m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
255
258
 
256
259
  // ルール 5: 最低 1 人雇用の労働基準(無関係なルール)
257
- m.addConstraint(engineers.ge(1), "minimum_one_employee");
260
+ m.constrain(expr`${engineers} >= 1`, "minimum_one_employee");
258
261
 
259
262
  const res = await m.check({ solver: "z3" });
260
263
 
@@ -271,53 +274,49 @@ console.log("矛盾している制約:", res.unsatCore);
271
274
  GNU Linear Programming Kit (GLPK) ソルバープラグインを利用する場合:
272
275
 
273
276
  ```ts
274
- import { Model, expr, c } from "@ohtaman/washi";
277
+ import { Model, expr } from "@ohtaman/washi";
275
278
  import "@ohtaman/washi/solvers/glpk";
276
279
 
277
- const m = new Model({ sense: "Maximize", solver: "glpk" });
278
- const x = m.addVar({ type: "Integer", lb: 0 });
279
- const y = m.addVar({ type: "Integer", lb: 0 });
280
+ const m = new Model({ solver: "glpk" });
281
+ const x = m.integer("x", { lb: 0 });
282
+ const y = m.integer("y", { lb: 0 });
280
283
 
281
284
  m.maximize(expr`30 * ${x} + 50 * ${y}`);
282
- m.addConstraint(c`${x} + 2 * ${y} <= 40`, "wood_limit");
283
- m.addConstraint(c`2 * ${x} + ${y} <= 50`, "labor_limit");
285
+ m.constrain(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
286
+ m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
284
287
 
285
- const status = await m.solve();
288
+ const status = await m.optimize();
286
289
  console.log(`ステータス: ${status}`); // "Optimal"
287
290
  console.log(`目的関数値: ${m.objectiveValue}`); // 1100
288
291
  ```
289
292
 
290
293
  ---
291
294
 
292
- ### 6. Python PuLP 互換レイヤー
295
+ ### 6. ベクトル演算と高速内積 (`dot`)
293
296
 
294
- 既存の Python PuLP 最適化スクリプトをスムーズに移植:
297
+ ネストしたループを書くことなく、1次元・2次元の線形結合を1行で記述:
295
298
 
296
299
  ```ts
297
- import {
298
- LpProblem,
299
- LpVariable,
300
- LpMaximize,
301
- LpInteger,
302
- HiGHS,
303
- value,
304
- } from "@ohtaman/washi";
305
-
306
- const prob = new LpProblem("PuLPDemo", LpMaximize);
307
-
308
- const x = new LpVariable("x", 0, undefined, LpInteger);
309
- const y = new LpVariable("y", 0, undefined, LpInteger);
310
-
311
- // PuLP の += 相当
312
- prob.add(x.mul(30).add(y.mul(50)));
313
- prob.add(x.add(y.mul(2)).le(40));
314
- prob.add(x.mul(2).add(y).le(50));
315
-
316
- await prob.solve(new HiGHS());
317
-
318
- console.log(`ステータス: ${prob.status}`);
319
- console.log(`目的関数値: ${value(prob.objective)}`);
320
- 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);
321
320
  ```
322
321
 
323
322
  ---
@@ -398,13 +397,17 @@ CDN から直接読み込む場合は、URL を `https://cdn.jsdelivr.net/npm/@o
398
397
  ```
399
398
 
400
399
  > [!NOTE]
401
- > **ブラウザ環境での Z3 利用について**:
402
- > Z3 の WebAssembly エンジンはマルチスレッド(`SharedArrayBuffer`)を必要とするため、配信 Web サーバーがドキュメントレスポンスに以下の Cross-Origin-Isolation ヘッダーを送信する必要があります:
400
+ > **ブラウザ環境での Z3 利用と Cross-Origin Isolation**:
401
+ > Z3 の WebAssembly エンジンはマルチスレッド(`SharedArrayBuffer`)を必要とするため、ブラウザのセキュリティ仕様により Cross-Origin Isolation が必須となります。ドキュメントレスポンスに以下のヘッダーが必要です:
403
402
  > ```http
404
403
  > Cross-Origin-Opener-Policy: same-origin
405
404
  > Cross-Origin-Embedder-Policy: require-corp
406
405
  > ```
407
- > また、別オリジン(CDN など)からアセットを読み込む場合は `Cross-Origin-Resource-Policy: cross-origin` の返却が必要です。
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 が利用できない環境や隔離の確立に失敗した環境では、原因を明示して安全に実行を中断します。
408
411
 
409
412
  完全なスタンドアロンサンプル(ESM、スクリプトタグ、CodePen)は `examples/distribution/` に用意されています。
410
413
 
@@ -437,33 +440,46 @@ WaSHI は疎結合なプラグイン方式のソルバーアーキテクチャ
437
440
 
438
441
  ---
439
442
 
440
- ## 💻 サンプルスクリプト & インタラクティブ Web デモ
443
+ ## 💻 サンプルとブラウザデモ
441
444
 
442
- `examples/node/` 配下に実行可能なスタンドアロンスクリプトを用意しています:
445
+ [サンプル一覧](https://github.com/ohtaman/washi/blob/main/examples/README.md)および[オンライン実践レシピ集・API逆引き索引](https://ohtaman.github.io/washi/ja/examples/)では、目的別・WaSHI の API 機能別にサンプルを探せます。
443
446
 
444
- ```bash
445
- npx tsx examples/node/01-production-planning.ts
446
- npx tsx examples/node/02-knapsack-problem.ts
447
- npx tsx examples/node/03-portfolio-qp.ts
448
- npx tsx examples/node/04-smt-security-rules.ts
449
- npx tsx examples/node/05-unsat-core-diagnosis.ts
450
- npx tsx examples/node/06-pulp-compatibility.ts
451
- npx tsx examples/node/08-glpk-solver-plugin.ts
452
- ```
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 |
453
457
 
454
- ブラウザで動く Web デモ (`examples/web-demo`) を起動するには:
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
+ ローカルで実行する場合は、リポジトリを取得して開発用依存をインストールします。
455
461
 
456
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/
457
473
  npm run dev
458
- # ドキュメント: http://localhost:5173/
459
- # インタラクティブデモ: http://localhost:5173/demo/
460
474
  ```
461
475
 
476
+ サンプルはリポジトリの `src/` モジュールを import しています。公開パッケージを使うアプリでは、`@ohtaman/washi` と必要なソルバープラグインから import してください。
477
+
462
478
  ---
463
479
 
464
480
  ## 🧪 テスト & 信頼性
465
481
 
466
- 厳格なテスト駆動開発 (TDD) により、120 以上の自動テストを実施しています:
482
+ リポジトリのユニット・統合テストを実行し、配布物をビルドできます:
467
483
 
468
484
  ```bash
469
485
  # すべてのユニット・統合テストを実行