@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.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**
|
|
27
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) 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
|
|
51
|
+
|
|
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.
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
2. 📦 **Frictionless Zero-Dependency Distribution**: No Python runtimes, no C/C++ compilation, and no OS-dependent native solver binaries to bundle. Just `npm install @ohtaman/washi`—everything (including WebAssembly solvers) works instantly out of the box.
|
|
52
|
-
3. ✍️ **Familiar Algebraic Modeling (PuLP & python-mip DX)**: Never struggle with low-level matrix indices. Formulate models naturally using tagged template literals (`c`${x} + 2 * ${y} <= 40``) or drop-in PuLP/python-mip syntax (`LpProblem`, `lpSum`).
|
|
53
|
-
4. 🔄 **Pluggable Solvers & Unified API**: Formulate your mathematical model once; solve with **HiGHS** (industrial MIP/QP), **Z3** (SMT logic & Unsat Core diagnosis), or **GLPK** with a single parameter.
|
|
54
|
-
5. 🔍 **Automatic Conflict Diagnosis (`unsatCore`)**: When constraints are contradictory (Infeasible), diagnose root causes by extracting a conflicting constraint subset (unsatisfiable core) via the Z3 engine.
|
|
55
|
-
6. ⚡ **Industrial Scale Performance in WebAssembly**: Built with an $O(N)$ linear-time expression builder. Formulate 20,000+ variable MIPs in ~100ms and solve them in ~15 seconds right inside WebAssembly via HiGHS.
|
|
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,25 +202,25 @@ 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";
|
|
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
|
// Logical rules
|
|
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
|
// Current sensor observations
|
|
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
|
// Check satisfiability and deduce state without an objective function
|
|
223
226
|
const res = await m.check({ solver: "z3" });
|
|
@@ -233,28 +236,28 @@ console.log(`Siren active? ${m.values.get("siren")}`); // true
|
|
|
233
236
|
When business rules or constraints contradict each other, extract a **conflicting constraint subset** (unsatisfiable core; minimality is not guaranteed) for troubleshooting (requires 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
|
// Rule 1: Each engineer costs $8,000
|
|
245
|
-
m.
|
|
248
|
+
m.constrain(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
246
249
|
|
|
247
250
|
// Rule 2: Customer requires rush delivery
|
|
248
|
-
m.
|
|
251
|
+
m.constrain(rushDelivery, "rush_required");
|
|
249
252
|
|
|
250
253
|
// Rule 3: Rush delivery requires at least 8 engineers
|
|
251
|
-
m.
|
|
254
|
+
m.constrain(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
|
|
252
255
|
|
|
253
256
|
// Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
|
|
254
|
-
m.
|
|
257
|
+
m.constrain(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
255
258
|
|
|
256
259
|
// Rule 5: Innocent bystander rule
|
|
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("Conflicting constraints:", res.unsatCore);
|
|
|
271
274
|
To use the GNU Linear Programming Kit (GLPK) solver plugin:
|
|
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: ${status}`); // "Optimal"
|
|
287
290
|
console.log(`Objective: ${m.objectiveValue}`); // 1100
|
|
288
291
|
```
|
|
289
292
|
|
|
290
293
|
---
|
|
291
294
|
|
|
292
|
-
### 6.
|
|
295
|
+
### 6. Vector Operations & Fast Dot Product
|
|
293
296
|
|
|
294
|
-
|
|
297
|
+
Cleanly compute 1D/2D linear combinations without nested loops:
|
|
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(`Status: ${prob.status}`);
|
|
319
|
-
console.log(`Objective: ${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
|
+
// 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);
|
|
321
320
|
```
|
|
322
321
|
|
|
323
322
|
---
|
|
@@ -378,7 +377,7 @@ The browser distribution includes pre-bundled, standalone solver plugins under `
|
|
|
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 status:", model.status);
|
|
@@ -391,20 +390,24 @@ The browser distribution includes pre-bundled, standalone solver plugins under `
|
|
|
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:", status));
|
|
397
396
|
</script>
|
|
398
397
|
```
|
|
399
398
|
|
|
400
399
|
> [!NOTE]
|
|
401
|
-
> **Z3 in Browser Environments**:
|
|
402
|
-
> Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`),
|
|
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:
|
|
403
402
|
> ```http
|
|
404
403
|
> Cross-Origin-Opener-Policy: same-origin
|
|
405
404
|
> Cross-Origin-Embedder-Policy: require-corp
|
|
406
405
|
> ```
|
|
407
|
-
> And any cross-origin assets
|
|
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.
|
|
408
411
|
|
|
409
412
|
Complete standalone examples for ESM, script tags, and CodePen are available in `examples/distribution/`.
|
|
410
413
|
|
|
@@ -437,33 +440,46 @@ WaSHI features a decoupled, pluggable solver architecture. You can easily build
|
|
|
437
440
|
|
|
438
441
|
---
|
|
439
442
|
|
|
440
|
-
## 💻
|
|
443
|
+
## 💻 Examples and browser demo
|
|
441
444
|
|
|
442
|
-
|
|
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.
|
|
443
446
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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 (`continuous(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 (`indicator`), 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 (`bool`, `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 |
|
|
453
457
|
|
|
454
|
-
|
|
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:
|
|
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
|
+
# Local browser demo: http://localhost:5173/demo/
|
|
457
473
|
npm run dev
|
|
458
|
-
# Documentation: http://localhost:5173/
|
|
459
|
-
# Interactive Demo: http://localhost:5173/demo/
|
|
460
474
|
```
|
|
461
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
|
+
|
|
462
478
|
---
|
|
463
479
|
|
|
464
480
|
## 🧪 Testing
|
|
465
481
|
|
|
466
|
-
|
|
482
|
+
Run the repository's unit and integration tests, and build the distributions:
|
|
467
483
|
|
|
468
484
|
```bash
|
|
469
485
|
# Run all unit and integration tests
|