@ohtaman/washi 0.2.0 → 0.4.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 +138 -122
- package/README.md +138 -122
- package/README.zh-CN.md +140 -124
- package/dist/browser/solvers/glpk.js +10 -2
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +34 -12
- package/dist/browser/solvers/z3.min.js +2 -2
- package/dist/browser/washi.js +562 -397
- package/dist/browser/washi.min.js +4 -4
- package/dist/lib/{constraint-XWdn0ID9.js → constraint-BRuB3ooC.js} +0 -3
- package/dist/lib/solvers/glpk.js +10 -2
- package/dist/lib/solvers/z3.js +35 -10
- package/dist/lib/washi.js +562 -412
- package/dist/types/index.d.ts +5 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +0 -3
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +23 -12
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +2 -4
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +123 -40
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/parser.d.ts +9 -8
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/quad-expr.d.ts +3 -1
- package/dist/types/modeler/quad-expr.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 +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +1 -1
- package/dist/types/modeler/solvers/z3/loader.d.ts +12 -0
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +8 -3
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/dist/types/modeler/variable.d.ts +7 -6
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/package.json +11 -3
- package/dist/types/modeler/mip-compat.d.ts +0 -43
- package/dist/types/modeler/mip-compat.d.ts.map +0 -1
- package/dist/types/modeler/pulp-compat.d.ts +0 -67
- 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
|
|
7
|
+
<p><strong>TypeScript / JavaScript のための代数モデラー</strong></p>
|
|
8
8
|
<p>
|
|
9
9
|
<em>線形計画 (LP) • 混合整数計画 (MIP) • 凸二次計画 (QP) • SMT論理制約 & 矛盾診断</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
|
|
27
|
+
**WaSHI**(WebAssembly-backed, Solver-independent, High-level modeling Interface)は、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
|
|
34
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
33
35
|
|
|
34
|
-
const m = new Model(
|
|
35
|
-
const x = m.
|
|
36
|
-
const y = m.
|
|
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.
|
|
40
|
-
m.
|
|
41
|
+
m.constrain(expr`${x} + 2 * ${y} <= 40`, "木材上限");
|
|
42
|
+
m.constrain(expr`2 * ${x} + ${y} <= 50`, "労働力上限");
|
|
41
43
|
|
|
42
|
-
await m.
|
|
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
|
-
## 🎯
|
|
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
|
-
|
|
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
|
-
|
|
|
63
|
+
| アダプター | エンジンのライセンス | 対応する問題クラス | エンジンの役割 | 提供形態 |
|
|
62
64
|
| :--- | :--- | :--- | :--- | :--- |
|
|
63
|
-
| **HiGHS** | **MIT** | **LP, MIP, 凸QP** |
|
|
64
|
-
| **Z3** | **MIT** | **SMT, SAT, 命題論理, MIP** |
|
|
65
|
-
| **GLPK** | **GPL-3.0** | **LP, MIP** |
|
|
66
|
-
| **Mock** | **MIT** |
|
|
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,
|
|
104
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
103
105
|
|
|
104
|
-
const m = new Model(
|
|
106
|
+
const m = new Model("FactoryPlanning");
|
|
105
107
|
|
|
106
108
|
// 決定変数(整数・非負)
|
|
107
|
-
const chairs = m.
|
|
108
|
-
const tables = m.
|
|
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.
|
|
113
|
-
m.
|
|
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.
|
|
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
|
-
#### 🗂 多次元変数グリッド
|
|
125
|
+
#### 🗂 多次元変数グリッド
|
|
124
126
|
|
|
125
|
-
キー一覧や多次元インデックスから、1
|
|
127
|
+
キー一覧や多次元インデックスから、1 行で変数群を定義:
|
|
126
128
|
|
|
127
129
|
```ts
|
|
128
130
|
// 1次元辞書形式のバイナリ変数群
|
|
129
|
-
const openCity = m.
|
|
131
|
+
const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
|
|
130
132
|
|
|
131
133
|
// 2次元輸送量行列: [工場] x [店舗]
|
|
132
|
-
const flow = m.
|
|
134
|
+
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
|
|
133
135
|
|
|
134
136
|
// 2次元グリッド (例: 8x8 チェス盤)
|
|
135
|
-
const board = m.
|
|
137
|
+
const board = m.binary(8, 8, { prefix: "queen" });
|
|
136
138
|
```
|
|
137
139
|
|
|
138
|
-
#### 🔀 Big-M インジケータ制約 (`
|
|
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.
|
|
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(
|
|
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 =
|
|
166
|
+
const w = m.continuous(assets, { lb: 0, ub: 1 });
|
|
165
167
|
|
|
166
168
|
// 1. 予算制約: 合計 100%
|
|
167
|
-
m.
|
|
169
|
+
m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
|
|
168
170
|
|
|
169
171
|
// 2. 目標リターン: 最低 8.5%
|
|
170
|
-
|
|
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.
|
|
191
|
+
await m.optimize();
|
|
189
192
|
console.log(`最小分散: ${m.objectiveValue?.toFixed(6)}`);
|
|
190
|
-
assets.forEach((name
|
|
191
|
-
console.log(` ${name}: ${((w[
|
|
193
|
+
assets.forEach((name) => {
|
|
194
|
+
console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
|
|
192
195
|
});
|
|
193
196
|
```
|
|
194
197
|
|
|
@@ -199,25 +202,25 @@ 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" });
|
|
206
209
|
|
|
207
|
-
const isNight = m.
|
|
208
|
-
const motion = m.
|
|
209
|
-
const alarm = m.
|
|
210
|
-
const siren = m.
|
|
211
|
-
const lux = m.
|
|
210
|
+
const isNight = m.bool("isNight");
|
|
211
|
+
const motion = m.bool("motion");
|
|
212
|
+
const alarm = m.bool("alarm");
|
|
213
|
+
const siren = m.bool("siren");
|
|
214
|
+
const lux = m.integer("lux", { lb: 0 });
|
|
212
215
|
|
|
213
216
|
// 論理ルール
|
|
214
|
-
m.
|
|
215
|
-
m.
|
|
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.
|
|
219
|
-
m.
|
|
220
|
-
m.
|
|
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.
|
|
241
|
-
const budget = m.
|
|
242
|
-
const rushDelivery = m.
|
|
243
|
+
const engineers = m.integer("engineers", { lb: 0, ub: 20 });
|
|
244
|
+
const budget = m.continuous("budget", { lb: 0, ub: 100000 });
|
|
245
|
+
const rushDelivery = m.bool("rushDelivery");
|
|
243
246
|
|
|
244
247
|
// ルール 1: エンジニア1人あたり $8,000
|
|
245
|
-
m.
|
|
248
|
+
m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
246
249
|
|
|
247
250
|
// ルール 2: 特急納品が必須
|
|
248
|
-
m.
|
|
251
|
+
m.constrain(rushDelivery, "rush_required");
|
|
249
252
|
|
|
250
253
|
// ルール 3: 特急納品には最低 8 人のエンジニアが必要
|
|
251
|
-
m.
|
|
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.
|
|
257
|
+
m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
255
258
|
|
|
256
259
|
// ルール 5: 最低 1 人雇用の労働基準(無関係なルール)
|
|
257
|
-
m.
|
|
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
|
|
277
|
+
import { Model, expr } from "@ohtaman/washi";
|
|
275
278
|
import "@ohtaman/washi/solvers/glpk";
|
|
276
279
|
|
|
277
|
-
const m = new Model({
|
|
278
|
-
const x = m.
|
|
279
|
-
const y = m.
|
|
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.
|
|
283
|
-
m.
|
|
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.
|
|
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.
|
|
295
|
+
### 6. ベクトル演算と高速内積 (`dot`)
|
|
293
296
|
|
|
294
|
-
|
|
297
|
+
ネストしたループを書くことなく、1次元・2次元の線形結合を1行で記述:
|
|
295
298
|
|
|
296
299
|
```ts
|
|
297
|
-
import {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
---
|
|
@@ -378,7 +377,7 @@ CDN から直接読み込む場合は、URL を `https://cdn.jsdelivr.net/npm/@o
|
|
|
378
377
|
import "./washi/solvers/glpk.js";
|
|
379
378
|
|
|
380
379
|
const model = new Model({ solver: "glpk" });
|
|
381
|
-
const x = model.
|
|
380
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
382
381
|
model.maximize(x);
|
|
383
382
|
await model.solve();
|
|
384
383
|
console.log("GLPK ステータス:", model.status);
|
|
@@ -391,20 +390,24 @@ CDN から直接読み込む場合は、URL を `https://cdn.jsdelivr.net/npm/@o
|
|
|
391
390
|
<script src="./washi/solvers/glpk.min.js"></script>
|
|
392
391
|
<script>
|
|
393
392
|
const model = new Washi.Model({ solver: "glpk" });
|
|
394
|
-
const x = model.
|
|
393
|
+
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
395
394
|
model.maximize(x);
|
|
396
395
|
model.solve().then(status => console.log("GLPK ステータス:", status));
|
|
397
396
|
</script>
|
|
398
397
|
```
|
|
399
398
|
|
|
400
399
|
> [!NOTE]
|
|
401
|
-
> **ブラウザ環境での Z3
|
|
402
|
-
> Z3 の WebAssembly エンジンはマルチスレッド(`SharedArrayBuffer
|
|
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
|
-
>
|
|
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
|
-
## 💻
|
|
443
|
+
## 💻 サンプルとブラウザデモ
|
|
441
444
|
|
|
442
|
-
|
|
445
|
+
[サンプル一覧](https://github.com/ohtaman/washi/blob/main/examples/README.md)および[オンライン実践レシピ集・API逆引き索引](https://ohtaman.github.io/washi/ja/examples/)では、目的別・WaSHI の API 機能別にサンプルを探せます。
|
|
443
446
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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次元マトリクス変数 (`continuous(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) | 条件付き購入金額閾値 (`indicator`)、併用不可ルール、追加購入提案 | 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) | 数値・論理制約 (`bool`, `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
|
-
|
|
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
|
-
|
|
482
|
+
リポジトリのユニット・統合テストを実行し、配布物をビルドできます:
|
|
467
483
|
|
|
468
484
|
```bash
|
|
469
485
|
# すべてのユニット・統合テストを実行
|