@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.
- package/README.ja.md +195 -105
- package/README.md +195 -105
- package/README.zh-CN.md +197 -107
- package/dist/browser/highs-worker.js +2 -2
- package/dist/browser/{highs-loader-T47YOyos.js → loader-CVt4CloI.js} +2 -2
- package/dist/browser/solvers/glpk.js +1065 -0
- package/dist/browser/solvers/glpk.min.js +1 -0
- package/dist/browser/solvers/z3-built.js +8245 -0
- package/dist/browser/solvers/z3-built.wasm +4 -0
- package/dist/browser/solvers/z3.js +7550 -0
- package/dist/browser/solvers/z3.min.js +5 -0
- package/dist/browser/washi.js +370 -642
- package/dist/browser/washi.min.js +4 -4
- package/dist/lib/async-mutex-Duo4jwzn.js +195 -0
- package/dist/lib/constraint-BRuB3ooC.js +90 -0
- package/dist/lib/highs-worker.js +2 -2
- package/dist/lib/{highs-loader-Blb99785.js → loader-Dti0v9yC.js} +1 -1
- package/dist/lib/registry-DksqCSjI.js +32 -0
- package/dist/lib/solvers/glpk.js +161 -2
- package/dist/lib/solvers/z3-assets.js +1 -0
- package/dist/lib/solvers/z3-built.js +8245 -0
- package/dist/lib/solvers/z3-built.wasm +4 -0
- package/dist/lib/solvers/z3.js +476 -2
- package/dist/lib/washi.js +604 -248
- package/dist/types/index.d.ts +7 -10
- 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 +16 -10
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +3 -5
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +91 -12
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/solvers/glpk/index.d.ts +3 -0
- package/dist/types/modeler/solvers/glpk/index.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk/loader.d.ts +16 -0
- package/dist/types/modeler/solvers/glpk/loader.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -0
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk.d.ts +1 -21
- package/dist/types/modeler/solvers/glpk.d.ts.map +1 -1
- package/dist/types/{highs-assets.d.ts → modeler/solvers/highs/assets.d.ts} +1 -1
- package/dist/types/modeler/solvers/highs/assets.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -0
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts +6 -0
- package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -0
- package/dist/types/{highs-loader.d.ts → modeler/solvers/highs/loader.d.ts} +2 -2
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -0
- package/dist/types/{highs-node-runtime.d.ts → modeler/solvers/highs/node-runtime.d.ts} +1 -1
- package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/solver.d.ts +33 -0
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/worker-client.d.ts +13 -0
- package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -0
- package/dist/types/{highs-worker-factory.d.ts → modeler/solvers/highs/worker-factory.d.ts} +1 -1
- package/dist/types/modeler/solvers/highs/worker-factory.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/worker.d.ts +2 -0
- package/dist/types/modeler/solvers/highs/worker.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs.d.ts +1 -31
- package/dist/types/modeler/solvers/highs.d.ts.map +1 -1
- package/dist/types/modeler/solvers/index.d.ts +0 -2
- package/dist/types/modeler/solvers/index.d.ts.map +1 -1
- package/dist/types/modeler/solvers/mock.d.ts +1 -0
- package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
- package/dist/types/modeler/solvers/registry.d.ts +1 -2
- package/dist/types/modeler/solvers/registry.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +1 -1
- package/dist/types/modeler/solvers/z3/assets.d.ts +11 -0
- package/dist/types/modeler/solvers/z3/assets.d.ts.map +1 -0
- package/dist/types/modeler/solvers/z3/index.d.ts +3 -0
- package/dist/types/modeler/solvers/z3/index.d.ts.map +1 -0
- package/dist/types/modeler/solvers/z3/loader.d.ts +56 -0
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -0
- package/dist/types/modeler/solvers/z3/solver.d.ts +34 -0
- package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/z3.d.ts +1 -35
- package/dist/types/modeler/solvers/z3.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 +1 -6
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/package.json +28 -6
- package/dist/browser/_washi-optional-engine-282Ug7Gw.js +0 -7
- package/dist/lib/glpk-5cUpjyb9.js +0 -135
- package/dist/lib/z3-C6GeIicJ.js +0 -509
- package/dist/types/glpk-loader.d.ts +0 -8
- package/dist/types/glpk-loader.d.ts.map +0 -1
- package/dist/types/highs-assets.d.ts.map +0 -1
- package/dist/types/highs-browser-runtime.d.ts +0 -2
- package/dist/types/highs-browser-runtime.d.ts.map +0 -1
- package/dist/types/highs-loader.d.ts.map +0 -1
- package/dist/types/highs-node-runtime.d.ts.map +0 -1
- package/dist/types/highs-worker-client.d.ts +0 -12
- package/dist/types/highs-worker-client.d.ts.map +0 -1
- package/dist/types/highs-worker-factory.d.ts.map +0 -1
- package/dist/types/highs-worker.d.ts +0 -2
- package/dist/types/highs-worker.d.ts.map +0 -1
- 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/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
|
|
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** は、Python の **PuLP** や Julia の **JuMP**
|
|
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
|
|
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 が提供するもの
|
|
49
51
|
|
|
50
|
-
1.
|
|
51
|
-
2.
|
|
52
|
-
3.
|
|
53
|
-
4.
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
|
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,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.
|
|
214
|
+
const lux = m.integer("lux", { lb: 0 });
|
|
211
215
|
|
|
212
216
|
// 論理ルール
|
|
213
|
-
m.
|
|
214
|
-
m.
|
|
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.
|
|
218
|
-
m.
|
|
219
|
-
m.
|
|
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.
|
|
240
|
-
const budget = m.
|
|
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.
|
|
248
|
+
m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
245
249
|
|
|
246
250
|
// ルール 2: 特急納品が必須
|
|
247
|
-
m.
|
|
251
|
+
m.constrain(rushDelivery, "rush_required");
|
|
248
252
|
|
|
249
253
|
// ルール 3: 特急納品には最低 8 人のエンジニアが必要
|
|
250
|
-
m.
|
|
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.
|
|
257
|
+
m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
254
258
|
|
|
255
259
|
// ルール 5: 最低 1 人雇用の労働基準(無関係なルール)
|
|
256
|
-
m.
|
|
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.
|
|
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
|
-
|
|
297
|
+
ネストしたループを書くことなく、1次元・2次元の線形結合を1行で記述:
|
|
271
298
|
|
|
272
299
|
```ts
|
|
273
|
-
import {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
435
|
+
## 🛠️ カスタムソルバー開発と拡張性
|
|
368
436
|
|
|
369
|
-
|
|
437
|
+
WaSHI は疎結合なプラグイン方式のソルバーアーキテクチャを採用しています。独自の最適化エンジン(クラウド REST ソルバー、WebAssembly エンジン、商用ソルバーアダプターなど)を容易に構築・登録できます:
|
|
370
438
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
482
|
+
リポジトリのユニット・統合テストを実行し、配布物をビルドできます:
|
|
393
483
|
|
|
394
484
|
```bash
|
|
395
485
|
# すべてのユニット・統合テストを実行
|