@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.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 - Algebraic Modeler for TypeScript & JavaScript" src="./assets/washi-header-light.png" width="420" />
|
|
6
6
|
</picture>
|
|
7
|
-
<p><strong>
|
|
7
|
+
<p><strong>Algebraic Modeling for TypeScript & JavaScript</strong></p>
|
|
8
8
|
<p>
|
|
9
9
|
<em>Linear Programming (LP) • Mixed-Integer Programming (MIP) • Quadratic Programming (QP) • SMT & Conflict Diagnosis</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** is
|
|
27
|
+
**WaSHI** is an algebraic modeler for TypeScript and JavaScript, inspired by Python's **PuLP** and Julia's **JuMP**. Define variables, objectives, and arithmetic or logical constraints in application code. WaSHI constructs the model, translates it for a solver adapter, and maps results back to your variables. **HiGHS**, **Z3**, and **GLPK** perform the optimization or satisfiability checks.
|
|
28
|
+
|
|
29
|
+
HiGHS is the default solver; Z3 and GLPK are optional plugins. The package provides Node.js and browser distributions, including WebAssembly assets. See the [browser integration guide](#-browser--web-worker-usage) for deployment requirements.
|
|
30
|
+
|
|
31
|
+
[Try the browser demo](https://ohtaman.github.io/washi/demo/) · [Browse example source code](https://github.com/ohtaman/washi/blob/main/examples/README.md) · [ESM / script / CodePen examples](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("WoodworkingProfit");
|
|
37
|
+
const x = m.integer("desks", { lb: 0 });
|
|
38
|
+
const y = m.integer("chairs", { 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`, "wood_limit");
|
|
42
|
+
m.constrain(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
|
|
41
43
|
|
|
42
|
-
await m.
|
|
44
|
+
await m.optimize();
|
|
43
45
|
console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
---
|
|
47
49
|
|
|
48
|
-
## 🎯
|
|
50
|
+
## 🎯 What WaSHI provides
|
|
49
51
|
|
|
50
|
-
1.
|
|
51
|
-
2.
|
|
52
|
-
3.
|
|
53
|
-
4.
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
1. **Algebraic model construction**: Formulate optimization models using domain-specific variable constructors (`m.binary`, `m.integer`, `m.continuous`), tagged template literals (`expr`), vector aggregations (`dot`, `sum`), and first-class constraints (`m.constrain`).
|
|
53
|
+
2. **Solver adapters**: Use a common modeling API with HiGHS, Z3, or GLPK, or [register your own adapter](https://github.com/ohtaman/washi/blob/main/docs/custom-solver-guide.md). Choose an engine that supports the model's problem class; the adapters do not make every solver support every feature.
|
|
54
|
+
3. **Local execution and WebAssembly distribution**: With the local solver adapters, solving runs in the application without a separate Python service or native C/C++ build. Browser deployments must include the generated JS, Worker, and WASM assets. Offline use also requires those resources to be available locally or cached. Z3 has additional browser isolation requirements.
|
|
55
|
+
4. **Named constraints and SMT results**: With Z3, explicitly check satisfiability and inspect an unsatisfiable core to investigate conflicting constraints. A returned core is a conflicting subset, not necessarily the smallest one.
|
|
56
|
+
|
|
57
|
+
Model construction and translation are WaSHI's responsibility. Supported problem classes, numerical behavior, and solve time depend on the selected solver, its settings, and the problem. Modeling overhead and solver execution time should be measured separately; a result for one model is not a general performance guarantee.
|
|
56
58
|
|
|
57
59
|
---
|
|
58
60
|
|
|
59
|
-
##
|
|
61
|
+
## ⚙️ Solver adapters and capabilities
|
|
60
62
|
|
|
61
|
-
|
|
|
63
|
+
| Adapter | Engine license | Supported problem classes | Engine role | Integration |
|
|
62
64
|
| :--- | :--- | :--- | :--- | :--- |
|
|
63
|
-
| **HiGHS** | **MIT** | **LP, MIP,
|
|
64
|
-
| **Z3** | **MIT** | **SMT, SAT,
|
|
65
|
-
| **GLPK** | **GPL-3.0** | **LP, MIP** |
|
|
66
|
-
| **Mock** | **MIT** |
|
|
65
|
+
| **HiGHS** | **MIT** | **LP, MIP, convex QP** | Numerical optimization | Built-in default |
|
|
66
|
+
| **Z3** | **MIT** | **SMT, SAT, logic, MIP** | Satisfiability checks, unsatisfiable cores, optimization | Optional plugin (`@ohtaman/washi/solvers/z3`) |
|
|
67
|
+
| **GLPK** | **GPL-3.0** | **LP, MIP** | Linear and mixed-integer optimization | Optional plugin (`@ohtaman/washi/solvers/glpk`) |
|
|
68
|
+
| **Mock** | **MIT** | — | Simulated solver responses for integration tests | Built-in; does not solve the model |
|
|
67
69
|
|
|
68
70
|
---
|
|
69
71
|
|
|
@@ -99,49 +101,49 @@ npm install glpk.js # For GLPK solver (GPL-3.0)
|
|
|
99
101
|
Formulate production scheduling, resource allocation, and facility location problems:
|
|
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
|
// Decision variables (Integer, non-negative)
|
|
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
|
// Natural formula objective & constraints via Tagged Template Literals
|
|
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
|
// Solve using default HiGHS WASM engine
|
|
116
|
-
const status = await m.
|
|
118
|
+
const status = await m.optimize();
|
|
117
119
|
|
|
118
120
|
console.log(`Status: ${status}`); // "Optimal"
|
|
119
121
|
console.log(`Objective: $${m.objectiveValue}`); // 1100
|
|
120
122
|
console.log(`Chairs: ${chairs.value}, Tables: ${tables.value}`); // 20 chairs, 10 tables
|
|
121
123
|
```
|
|
122
124
|
|
|
123
|
-
#### 🗂 Multidimensional Variables Grid
|
|
125
|
+
#### 🗂 Multidimensional Variables Grid
|
|
124
126
|
|
|
125
|
-
Construct variable arrays or multi-index matrices in one
|
|
127
|
+
Construct variable arrays or multi-index matrices in one call:
|
|
126
128
|
|
|
127
129
|
```ts
|
|
128
130
|
// 1D dictionary of binary variables
|
|
129
|
-
const openCity = m.
|
|
131
|
+
const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
|
|
130
132
|
|
|
131
133
|
// 2D transport flow matrix: [Factory] x [Store]
|
|
132
|
-
const flow = m.
|
|
134
|
+
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
|
|
133
135
|
|
|
134
136
|
// 2D coordinate grid (e.g. 8x8 chessboard)
|
|
135
|
-
const board = m.
|
|
137
|
+
const board = m.binary(8, 8, { prefix: "queen" });
|
|
136
138
|
```
|
|
137
139
|
|
|
138
|
-
#### 🔀 Big-M Indicator Constraints (`
|
|
140
|
+
#### 🔀 Big-M Indicator Constraints (`m.indicator`)
|
|
139
141
|
|
|
140
142
|
Model conditional activation rules and fixed-charge economics without manual Big-M algebra:
|
|
141
143
|
|
|
142
144
|
```ts
|
|
143
145
|
// If factory is closed (openCity['Tokyo'] == 0), its production capacity must be 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
|
Minimize investment risk based on a covariance matrix subject to return and budget constraints:
|
|
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
|
// Decision variables: portfolio allocation weights (0 to 1)
|
|
164
|
-
const w =
|
|
166
|
+
const w = m.continuous(assets, { lb: 0, ub: 1 });
|
|
165
167
|
|
|
166
168
|
// 1. Fully invested budget: sum(w) == 1.0
|
|
167
|
-
m.
|
|
169
|
+
m.constrain(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
|
|
168
170
|
|
|
169
171
|
// 2. Minimum target return: portfolio return >= 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. Covariance risk matrix
|
|
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(`Minimal Variance: ${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
|
Express boolean variables, propositional connectives (`and`, `or`, `implies`, `not`, `xor`), and mixed arithmetic rules:
|
|
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
|
// Logical rules
|
|
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
|
// Current sensor observations
|
|
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
|
// Check satisfiability and deduce state without an objective function
|
|
222
226
|
const res = await m.check({ solver: "z3" });
|
|
@@ -232,28 +236,28 @@ console.log(`Siren active? ${m.values.get("siren")}`); // true
|
|
|
232
236
|
When business rules or constraints contradict each other, extract a **conflicting constraint subset** (unsatisfiable core; minimality is not guaranteed) for troubleshooting (requires 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
|
// Rule 1: Each engineer costs $8,000
|
|
244
|
-
m.
|
|
248
|
+
m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
245
249
|
|
|
246
250
|
// Rule 2: Customer requires rush delivery
|
|
247
|
-
m.
|
|
251
|
+
m.constrain(rushDelivery, "rush_required");
|
|
248
252
|
|
|
249
253
|
// Rule 3: Rush delivery requires at least 8 engineers
|
|
250
|
-
m.
|
|
254
|
+
m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
|
|
251
255
|
|
|
252
256
|
// Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
|
|
253
|
-
m.
|
|
257
|
+
m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
254
258
|
|
|
255
259
|
// Rule 5: Innocent bystander rule
|
|
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("Conflicting constraints:", res.unsatCore);
|
|
|
265
269
|
|
|
266
270
|
---
|
|
267
271
|
|
|
268
|
-
### 5.
|
|
272
|
+
### 5. Solving with GLPK (GPL-3.0)
|
|
273
|
+
|
|
274
|
+
To use the GNU Linear Programming Kit (GLPK) solver plugin:
|
|
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: ${status}`); // "Optimal"
|
|
290
|
+
console.log(`Objective: ${m.objectiveValue}`); // 1100
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### 6. Vector Operations & Fast Dot Product
|
|
269
296
|
|
|
270
|
-
|
|
297
|
+
Cleanly compute 1D/2D linear combinations without nested loops:
|
|
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(`Status: ${prob.status}`);
|
|
295
|
-
console.log(`Objective: ${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
|
+
// Total transportation cost in one line:
|
|
314
|
+
m.minimize(dot(costs, ship));
|
|
315
|
+
await m.optimize();
|
|
316
|
+
|
|
317
|
+
// Extract all optimal variable values as a structured object
|
|
318
|
+
const solution = m.getValues(ship);
|
|
319
|
+
console.log(solution);
|
|
297
320
|
```
|
|
298
321
|
|
|
299
322
|
---
|
|
@@ -341,7 +364,52 @@ The package also includes `dist/browser/washi.min.js`, which exports the `Washi`
|
|
|
341
364
|
|
|
342
365
|
For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.1.2/dist/browser/washi.min.js` (or pin your desired release version `>= 0.1.2`). For ESM use the corresponding `dist/browser/washi.js` URL. Selecting the CDN URL is an explicit choice; local copies never switch to a CDN.
|
|
343
366
|
|
|
344
|
-
|
|
367
|
+
### Solver Plugins in the Browser (GLPK & Z3)
|
|
368
|
+
|
|
369
|
+
The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/` (available from `>= 0.2.0` or `@latest` on CDN):
|
|
370
|
+
- **GLPK**: `dist/browser/solvers/glpk.js` (ESM) and `dist/browser/solvers/glpk.min.js` (IIFE, registers global `WashiGLPK`)
|
|
371
|
+
- **Z3**: `dist/browser/solvers/z3.js` (ESM) and `dist/browser/solvers/z3.min.js` (IIFE, registers global `WashiZ3`), accompanied by `z3-built.js` and `z3-built.wasm`
|
|
372
|
+
|
|
373
|
+
#### Browser ESM with 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 status:", model.status);
|
|
384
|
+
</script>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Plain Script with 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:", status));
|
|
396
|
+
</script>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
> [!NOTE]
|
|
400
|
+
> **Z3 in Browser Environments & Cross-Origin Isolation**:
|
|
401
|
+
> Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation. The HTML document response must send:
|
|
402
|
+
> ```http
|
|
403
|
+
> Cross-Origin-Opener-Policy: same-origin
|
|
404
|
+
> Cross-Origin-Embedder-Policy: require-corp
|
|
405
|
+
> ```
|
|
406
|
+
> And any cross-origin assets must serve `Cross-Origin-Resource-Policy: cross-origin`.
|
|
407
|
+
>
|
|
408
|
+
> General static sites without custom header control (e.g. standard GitHub Pages or preview platforms) and third-party iframe embeds (e.g. CodePen / JSFiddle) do not satisfy these requirements without extra setup. Importing `@ohtaman/washi` never automatically injects or registers a Service Worker into consumer applications.
|
|
409
|
+
>
|
|
410
|
+
> To enable Z3 on GitHub Pages, the official WaSHI Web Demo bundles a dedicated `coi-serviceworker.js` strictly scoped to `/washi/`. On fresh browser sessions, it registers the worker and reloads once to establish `window.crossOriginIsolated === true` before running Z3. Environments lacking Service Workers or failing isolation abort Z3 execution with a descriptive explanation rather than hanging.
|
|
411
|
+
|
|
412
|
+
Complete standalone examples for ESM, script tags, and CodePen are available in `examples/distribution/`.
|
|
345
413
|
|
|
346
414
|
### Offline distribution, Workers and custom hosting
|
|
347
415
|
|
|
@@ -364,32 +432,54 @@ Failed initialization reports the attempted URL and does not try a different hos
|
|
|
364
432
|
|
|
365
433
|
---
|
|
366
434
|
|
|
367
|
-
##
|
|
435
|
+
## 🛠️ Custom Solvers & Extensibility
|
|
368
436
|
|
|
369
|
-
|
|
437
|
+
WaSHI features a decoupled, pluggable solver architecture. You can easily build and register your own custom solver adapter (e.g., for cloud REST optimizers, WebAssembly engines, or proprietary solvers):
|
|
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
|
+
- See the complete [Custom Solver Developer Guide](docs/custom-solver-guide.md) for step-by-step tutorials, complete API contracts (`Solver`, `LpModelData`, `SolverResult`), and testing best practices.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 💻 Examples and browser demo
|
|
379
444
|
|
|
380
|
-
|
|
445
|
+
Start with the [example gallery](https://github.com/ohtaman/washi/blob/main/examples/README.md) or the [online Example & Feature Finder](https://ohtaman.github.io/washi/examples/), which organize examples by problem category and by WaSHI API feature.
|
|
446
|
+
|
|
447
|
+
| Category | Example | What it demonstrates | Solver |
|
|
448
|
+
| :--- | :--- | :--- | :--- |
|
|
449
|
+
| **Fundamentals** | [Production planning](https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts) | Integer variables, a profit objective, and resource constraints (`expr`, `c`) | HiGHS |
|
|
450
|
+
| **Fundamentals** | [Transportation network](https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts) | 2D origin-destination variable matrix (`addVars(rows, cols)`) and flow balance | HiGHS |
|
|
451
|
+
| **Fundamentals** | [Diet & sensitivity analysis](https://github.com/ohtaman/washi/blob/main/examples/node/10-diet-sensitivity.ts) | Shadow prices (`constr.dual`), surpluses (`constr.slack`), and reduced costs (`var.reducedCost`) | HiGHS |
|
|
452
|
+
| **Practical** | [Staff shift scheduling](https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts) | Weekly roster optimization + diagnosing conflicting Saturday vacation requests | HiGHS + Z3 |
|
|
453
|
+
| **Practical** | [E-commerce coupon optimizer](https://github.com/ohtaman/washi/blob/main/examples/node/12-ecommerce-coupon-optimizer.ts) | Conditional spend thresholds (`addIndicator`), coupon exclusivity, and add-on recommendations | HiGHS |
|
|
454
|
+
| **Practical** | [Portfolio allocation](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) | A convex quadratic objective (`QuadExpr`) with allocation constraints | HiGHS |
|
|
455
|
+
| **Logic / SMT** | [Business rule checks](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) | Arithmetic and Boolean constraints (`addBoolVar`, `implies`) with satisfiability checking | Z3 |
|
|
456
|
+
| **Logic / SMT** | [Conflict diagnosis](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) | Named constraints and an unsatisfiable core (`unsatCore`) | Z3 |
|
|
457
|
+
|
|
458
|
+
[Open the hosted demo](https://ohtaman.github.io/washi/demo/) to edit and run models in your browser. For browser ESM, classic script tags, and CodePen, see the [distribution examples](https://github.com/ohtaman/washi/blob/main/examples/distribution/README.md). Z3 requires cross-origin isolation; see the browser requirements above.
|
|
459
|
+
|
|
460
|
+
To run examples locally, clone the repository and install its development dependencies first:
|
|
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
|
+
# Local browser demo: http://localhost:5173/demo/
|
|
383
473
|
npm run dev
|
|
384
|
-
# Documentation: http://localhost:5173/
|
|
385
|
-
# Interactive Demo: http://localhost:5173/demo/
|
|
386
474
|
```
|
|
387
475
|
|
|
476
|
+
The example sources import the repository's `src/` modules. In an application using the published package, import `@ohtaman/washi` and the required solver plugin instead.
|
|
477
|
+
|
|
388
478
|
---
|
|
389
479
|
|
|
390
480
|
## 🧪 Testing
|
|
391
481
|
|
|
392
|
-
|
|
482
|
+
Run the repository's unit and integration tests, and build the distributions:
|
|
393
483
|
|
|
394
484
|
```bash
|
|
395
485
|
# Run all unit and integration tests
|