@ohtaman/washi 0.4.0 → 0.5.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 +100 -107
- package/README.md +98 -105
- package/README.zh-CN.md +101 -108
- package/dist/browser/highs-worker.js +21 -3
- package/dist/browser/loader-CtaDHef_.js +273 -0
- package/dist/browser/solvers/glpk.js +433 -286
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +1002 -981
- package/dist/browser/solvers/z3.min.js +2 -2
- package/dist/browser/washi.js +2129 -676
- package/dist/browser/washi.min.js +4 -4
- package/dist/lib/highs-worker.js +21 -3
- package/dist/lib/loader-DmNC_UNk.js +279 -0
- package/dist/lib/registry-BEfvpZW3.js +951 -0
- package/dist/lib/solvers/glpk.js +201 -90
- package/dist/lib/solvers/z3.js +399 -259
- package/dist/lib/washi.js +1627 -988
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/big-m.d.ts +75 -0
- package/dist/types/modeler/big-m.d.ts.map +1 -0
- package/dist/types/modeler/boolean-expr.d.ts +103 -14
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +36 -9
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +113 -28
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +10 -6
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/lp-writer.d.ts +27 -2
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +478 -131
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/objective.d.ts +59 -0
- package/dist/types/modeler/objective.d.ts.map +1 -0
- package/dist/types/modeler/parser.d.ts +34 -10
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/quad-expr.d.ts +26 -7
- package/dist/types/modeler/quad-expr.d.ts.map +1 -1
- package/dist/types/modeler/solution-validator.d.ts +93 -0
- package/dist/types/modeler/solution-validator.d.ts.map +1 -0
- package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/loader.d.ts +36 -11
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/solver.d.ts +11 -4
- package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
- package/dist/types/modeler/solvers/mock.d.ts +14 -5
- package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +71 -11
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
- package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
- package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +59 -1
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/dist/types/modeler/validation.d.ts +56 -0
- package/dist/types/modeler/validation.d.ts.map +1 -0
- package/dist/types/modeler/variable.d.ts +48 -14
- package/dist/types/modeler/variable.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +90 -0
- package/docs/examples/ecommerce-coupons.md +106 -0
- package/docs/examples/index.md +53 -0
- package/docs/examples/knapsack.md +60 -0
- package/docs/examples/portfolio-qp.md +83 -0
- package/docs/examples/production.md +51 -0
- package/docs/examples/shift-scheduling.md +175 -0
- package/docs/examples/smt-security.md +74 -0
- package/docs/examples/transportation.md +90 -0
- package/docs/examples/unsat-core.md +64 -0
- package/docs/getting-started/ai-agents.md +267 -0
- package/docs/getting-started/cross-origin.md +87 -0
- package/docs/getting-started/index.md +116 -0
- package/docs/getting-started/runtimes.md +227 -0
- package/docs/guide/constraints.md +177 -0
- package/docs/guide/expressions.md +204 -0
- package/docs/guide/objectives.md +170 -0
- package/docs/guide/unsat-core.md +147 -0
- package/docs/guide/variables.md +188 -0
- package/docs/ja/getting-started/ai-agents.md +266 -0
- package/docs/solvers/custom-solver.md +525 -0
- package/docs/solvers/glpk.md +66 -0
- package/docs/solvers/highs.md +93 -0
- package/docs/solvers/index.md +42 -0
- package/docs/solvers/z3.md +78 -0
- package/docs/zh/getting-started/ai-agents.md +266 -0
- package/llms-full.txt +3249 -0
- package/llms.txt +197 -0
- package/package.json +18 -5
- package/skills/washi/SKILL.md +197 -0
- package/dist/browser/loader-CVt4CloI.js +0 -58
- package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
- package/dist/lib/constraint-BRuB3ooC.js +0 -90
- package/dist/lib/loader-Dti0v9yC.js +0 -50
- package/dist/lib/registry-DksqCSjI.js +0 -32
package/README.md
CHANGED
|
@@ -28,20 +28,20 @@
|
|
|
28
28
|
|
|
29
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
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)
|
|
31
|
+
[Documentation](https://ohtaman.github.io/washi/) · [Try the browser demo](https://ohtaman.github.io/washi/demo/) · [AI Agent Quick-Reference](docs/getting-started/ai-agents.md) · [Agent Skill (`SKILL.md`)](skills/washi/SKILL.md) · [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)
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
import { Model, expr } from "@ohtaman/washi";
|
|
34
|
+
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
35
35
|
|
|
36
36
|
const m = new Model("WoodworkingProfit");
|
|
37
37
|
const x = m.integer("desks", { lb: 0 });
|
|
38
38
|
const y = m.integer("chairs", { lb: 0 });
|
|
39
39
|
|
|
40
|
-
m.maximize(expr`30 * ${x} + 50 * ${y}`);
|
|
41
|
-
m.
|
|
42
|
-
m.
|
|
40
|
+
m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
|
|
41
|
+
m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
|
|
42
|
+
m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
|
|
43
43
|
|
|
44
|
-
await m.
|
|
44
|
+
await m.solve();
|
|
45
45
|
console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`);
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -49,10 +49,10 @@ console.log(`Produce: x=${x.value}, y=${y.value} (Profit: $${m.objectiveValue})`
|
|
|
49
49
|
|
|
50
50
|
## 🎯 What WaSHI provides
|
|
51
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.
|
|
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
|
|
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
|
|
52
|
+
1. **Algebraic model construction**: Formulate optimization models using domain-specific variable constructors (`m.binary`, `m.integer`, `m.continuous`), unified tagged template literals (`expr`), vector aggregations (`dot`, `sum`), and first-class constraints (`m.constraint`). Because JavaScript lacks operator overloading, `` expr`...` `` parses infix math (`+`, `-`, `*`, `^2`, `<=`, `>=`, `==`) while binding interpolated `${...}` values directly as typed `Var` or expression references—similar to parameterized SQL templates—preserving static type checking, IDE refactoring, and indexed variable lookups (`${flow[i][j]}`).
|
|
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/solvers/custom-solver.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—removing backend provisioning as a bottleneck when shipping interactive tools or combining mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) on the client. 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 conflicting constraints (`conflicts` / `unsatCore`) to investigate infeasibilities. A returned core is a conflicting subset, not necessarily the smallest one.
|
|
56
56
|
|
|
57
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.
|
|
58
58
|
|
|
@@ -92,61 +92,54 @@ npm install z3-solver # For SMT and unsat core diagnosis
|
|
|
92
92
|
npm install glpk.js # For GLPK solver (GPL-3.0)
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
*(Optional)* For AI coding agents (Claude Code, Antigravity, Cursor, Codex, etc.), `@ohtaman/washi` ships a portable [`skills/washi/SKILL.md`](skills/washi/SKILL.md) workflow alongside the [AI Agent Quick-Reference](docs/getting-started/ai-agents.md):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
mkdir -p .agents/skills/washi
|
|
99
|
+
cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
|
|
100
|
+
```
|
|
101
|
+
|
|
95
102
|
---
|
|
96
103
|
|
|
97
104
|
## 🚀 Usage Guide
|
|
98
105
|
|
|
99
|
-
### 1.
|
|
106
|
+
### 1. Multidimensional Variables, Indicator Constraints & Solution Validation
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
Construct 1D dictionaries or 2D matrices of variables in a single call, express conditional Big-M logic with `m.indicator()`, monitor live progress (`onProgress`), and independently verify solutions with `m.validateSolution()`:
|
|
102
109
|
|
|
103
110
|
```ts
|
|
104
111
|
import { Model, expr } from "@ohtaman/washi";
|
|
105
112
|
|
|
106
|
-
const m = new Model("
|
|
107
|
-
|
|
108
|
-
// Decision variables (Integer, non-negative)
|
|
109
|
-
const chairs = m.integer("chairs", { lb: 0 });
|
|
110
|
-
const tables = m.integer("tables", { lb: 0 });
|
|
111
|
-
|
|
112
|
-
// Natural formula objective & constraints via Tagged Template Literals
|
|
113
|
-
m.maximize(expr`30 * ${chairs} + 50 * ${tables}`);
|
|
114
|
-
m.constrain(expr`${chairs} + 2 * ${tables} <= 40`, "wood_limit");
|
|
115
|
-
m.constrain(expr`2 * ${chairs} + ${tables} <= 50`, "labor_limit");
|
|
116
|
-
|
|
117
|
-
// Solve using default HiGHS WASM engine
|
|
118
|
-
const status = await m.optimize();
|
|
119
|
-
|
|
120
|
-
console.log(`Status: ${status}`); // "Optimal"
|
|
121
|
-
console.log(`Objective: $${m.objectiveValue}`); // 1100
|
|
122
|
-
console.log(`Chairs: ${chairs.value}, Tables: ${tables.value}`); // 20 chairs, 10 tables
|
|
123
|
-
```
|
|
113
|
+
const m = new Model("FacilityPlanning");
|
|
124
114
|
|
|
125
|
-
#### 🗂 Multidimensional Variables Grid
|
|
126
|
-
|
|
127
|
-
Construct variable arrays or multi-index matrices in one call:
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
115
|
// 1D dictionary of binary variables
|
|
131
116
|
const openCity = m.binary(["Tokyo", "Osaka", "Nagoya"]);
|
|
132
117
|
|
|
133
118
|
// 2D transport flow matrix: [Factory] x [Store]
|
|
134
|
-
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0 });
|
|
119
|
+
const flow = m.continuous(["F1", "F2"], ["StoreA", "StoreB"], { lb: 0, ub: 1000 });
|
|
135
120
|
|
|
136
121
|
// 2D coordinate grid (e.g. 8x8 chessboard)
|
|
137
122
|
const board = m.binary(8, 8, { prefix: "queen" });
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
#### 🔀 Big-M Indicator Constraints (`m.indicator`)
|
|
141
123
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
// If factory is closed (openCity['Tokyo'] == 0), its production capacity must be 0
|
|
124
|
+
// Conditional Big-M constraint: if openCity["Tokyo"] == 0, flow["F1"]["StoreA"] <= 0
|
|
125
|
+
// (Omit M to let WaSHI automatically derive a tight Big-M from finite variable bounds)
|
|
146
126
|
m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
|
|
147
127
|
activeValue: 0,
|
|
148
|
-
|
|
128
|
+
name: "tokyo_closed_no_flow",
|
|
149
129
|
});
|
|
130
|
+
|
|
131
|
+
// Solve with optional progress callback & AbortSignal cancellation
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
await m.solve({
|
|
134
|
+
signal: controller.signal,
|
|
135
|
+
onProgress: (ev) => {
|
|
136
|
+
// Live branch-and-bound metrics: ev.elapsedTime, ev.objectiveValue, ev.objectiveBound, ev.mipGap
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Independently verify variable bounds, integrality, and linear/logical constraints
|
|
141
|
+
const report = m.validateSolution();
|
|
142
|
+
console.log("Solution valid?", report.valid);
|
|
150
143
|
```
|
|
151
144
|
|
|
152
145
|
---
|
|
@@ -156,7 +149,7 @@ m.indicator(openCity["Tokyo"], expr`${flow["F1"]["StoreA"]} <= 0`, {
|
|
|
156
149
|
Minimize investment risk based on a covariance matrix subject to return and budget constraints:
|
|
157
150
|
|
|
158
151
|
```ts
|
|
159
|
-
import { Model, QuadExpr, sum, dot, expr } from "@ohtaman/washi";
|
|
152
|
+
import { Model, QuadExpr, sum, dot, minimize, expr } from "@ohtaman/washi";
|
|
160
153
|
|
|
161
154
|
const m = new Model("MarkowitzPortfolio");
|
|
162
155
|
const assets = ["Tech", "Bonds", "RealEstate"];
|
|
@@ -166,11 +159,11 @@ const returns = [0.14, 0.04, 0.08];
|
|
|
166
159
|
const w = m.continuous(assets, { lb: 0, ub: 1 });
|
|
167
160
|
|
|
168
161
|
// 1. Fully invested budget: sum(w) == 1.0
|
|
169
|
-
m.
|
|
162
|
+
m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget");
|
|
170
163
|
|
|
171
164
|
// 2. Minimum target return: portfolio return >= 8.5%
|
|
172
165
|
const retMap = Object.fromEntries(assets.map((a, i) => [a, returns[i]]));
|
|
173
|
-
m.
|
|
166
|
+
m.constraint(expr`${dot(retMap, w)} >= 0.085`, "min_return");
|
|
174
167
|
|
|
175
168
|
// 3. Covariance risk matrix
|
|
176
169
|
const cov = [
|
|
@@ -186,9 +179,9 @@ for (let i = 0; i < assets.length; i++) {
|
|
|
186
179
|
risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
|
|
187
180
|
}
|
|
188
181
|
}
|
|
189
|
-
m.minimize(risk);
|
|
182
|
+
m.objective(minimize(risk));
|
|
190
183
|
|
|
191
|
-
await m.
|
|
184
|
+
await m.solve();
|
|
192
185
|
console.log(`Minimal Variance: ${m.objectiveValue?.toFixed(6)}`);
|
|
193
186
|
assets.forEach((name) => {
|
|
194
187
|
console.log(` ${name}: ${((w[name].value || 0) * 100).toFixed(2)}%`);
|
|
@@ -214,13 +207,13 @@ const siren = m.bool("siren");
|
|
|
214
207
|
const lux = m.integer("lux", { lb: 0 });
|
|
215
208
|
|
|
216
209
|
// Logical rules
|
|
217
|
-
m.
|
|
218
|
-
m.
|
|
210
|
+
m.constraint(lux.le(50).implies(isNight), "dark_implies_night");
|
|
211
|
+
m.constraint(motion.and(isNight).and(alarm).implies(siren), "alarm_rule");
|
|
219
212
|
|
|
220
213
|
// Current sensor observations
|
|
221
|
-
m.
|
|
222
|
-
m.
|
|
223
|
-
m.
|
|
214
|
+
m.constraint(expr`${lux} <= 10`, "sensor_dark");
|
|
215
|
+
m.constraint(motion, "sensor_motion");
|
|
216
|
+
m.constraint(alarm, "alarm_on");
|
|
224
217
|
|
|
225
218
|
// Check satisfiability and deduce state without an objective function
|
|
226
219
|
const res = await m.check({ solver: "z3" });
|
|
@@ -231,7 +224,7 @@ console.log(`Siren active? ${m.values.get("siren")}`); // true
|
|
|
231
224
|
|
|
232
225
|
---
|
|
233
226
|
|
|
234
|
-
### 4. Conflict Diagnosis (`unsatCore`)
|
|
227
|
+
### 4. Conflict Diagnosis (`conflicts` / `unsatCore`)
|
|
235
228
|
|
|
236
229
|
When business rules or constraints contradict each other, extract a **conflicting constraint subset** (unsatisfiable core; minimality is not guaranteed) for troubleshooting (requires Z3):
|
|
237
230
|
|
|
@@ -245,24 +238,24 @@ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
|
|
|
245
238
|
const rushDelivery = m.bool("rushDelivery");
|
|
246
239
|
|
|
247
240
|
// Rule 1: Each engineer costs $8,000
|
|
248
|
-
m.
|
|
241
|
+
m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
|
|
249
242
|
|
|
250
243
|
// Rule 2: Customer requires rush delivery
|
|
251
|
-
m.
|
|
244
|
+
m.constraint(rushDelivery, "rush_required");
|
|
252
245
|
|
|
253
246
|
// Rule 3: Rush delivery requires at least 8 engineers
|
|
254
|
-
m.
|
|
247
|
+
m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
|
|
255
248
|
|
|
256
249
|
// Rule 4: Hard board budget cap of $50,000 (Conflict: 8 * $8000 = $64,000 > $50,000!)
|
|
257
|
-
m.
|
|
250
|
+
m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
|
|
258
251
|
|
|
259
252
|
// Rule 5: Innocent bystander rule
|
|
260
|
-
m.
|
|
253
|
+
m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
|
|
261
254
|
|
|
262
255
|
const res = await m.check({ solver: "z3" });
|
|
263
256
|
|
|
264
257
|
console.log(`Status: ${res.status}`); // "Unsat"
|
|
265
|
-
console.log("Conflicting constraints:", res.
|
|
258
|
+
console.log("Conflicting constraints:", res.conflicts);
|
|
266
259
|
// Output: ["budget_limit", "rush_required", "rush_staffing_rule", "strict_board_budget_cap"]
|
|
267
260
|
// Note: "minimum_one_employee" is correctly excluded!
|
|
268
261
|
```
|
|
@@ -274,18 +267,18 @@ console.log("Conflicting constraints:", res.unsatCore);
|
|
|
274
267
|
To use the GNU Linear Programming Kit (GLPK) solver plugin:
|
|
275
268
|
|
|
276
269
|
```ts
|
|
277
|
-
import { Model, expr } from "@ohtaman/washi";
|
|
270
|
+
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
278
271
|
import "@ohtaman/washi/solvers/glpk";
|
|
279
272
|
|
|
280
273
|
const m = new Model({ solver: "glpk" });
|
|
281
274
|
const x = m.integer("x", { lb: 0 });
|
|
282
275
|
const y = m.integer("y", { lb: 0 });
|
|
283
276
|
|
|
284
|
-
m.maximize(expr`30 * ${x} + 50 * ${y}`);
|
|
285
|
-
m.
|
|
286
|
-
m.
|
|
277
|
+
m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
|
|
278
|
+
m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_limit");
|
|
279
|
+
m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_limit");
|
|
287
280
|
|
|
288
|
-
const status = await m.
|
|
281
|
+
const status = await m.solve();
|
|
289
282
|
console.log(`Status: ${status}`); // "Optimal"
|
|
290
283
|
console.log(`Objective: ${m.objectiveValue}`); // 1100
|
|
291
284
|
```
|
|
@@ -297,7 +290,7 @@ console.log(`Objective: ${m.objectiveValue}`); // 1100
|
|
|
297
290
|
Cleanly compute 1D/2D linear combinations without nested loops:
|
|
298
291
|
|
|
299
292
|
```ts
|
|
300
|
-
import { Model, dot } from "@ohtaman/washi";
|
|
293
|
+
import { Model, dot, minimize } from "@ohtaman/washi";
|
|
301
294
|
|
|
302
295
|
const m = new Model();
|
|
303
296
|
const factories = ["Tokyo", "Osaka"];
|
|
@@ -311,8 +304,8 @@ const costs = {
|
|
|
311
304
|
const ship = m.continuous(factories, stores);
|
|
312
305
|
|
|
313
306
|
// Total transportation cost in one line:
|
|
314
|
-
m.minimize(dot(costs, ship));
|
|
315
|
-
await m.
|
|
307
|
+
m.objective(minimize(dot(costs, ship)));
|
|
308
|
+
await m.solve();
|
|
316
309
|
|
|
317
310
|
// Extract all optimal variable values as a structured object
|
|
318
311
|
const solution = m.getValues(ship);
|
|
@@ -362,23 +355,23 @@ The package also includes `dist/browser/washi.min.js`, which exports the `Washi`
|
|
|
362
355
|
</script>
|
|
363
356
|
```
|
|
364
357
|
|
|
365
|
-
For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi
|
|
358
|
+
For CDN usage, replace the local URL with `https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js` (specifying a compatible release version once published). 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.
|
|
366
359
|
|
|
367
360
|
### Solver Plugins in the Browser (GLPK & Z3)
|
|
368
361
|
|
|
369
|
-
The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers
|
|
362
|
+
The browser distribution includes pre-bundled, standalone solver plugins under `dist/browser/solvers/`:
|
|
370
363
|
- **GLPK**: `dist/browser/solvers/glpk.js` (ESM) and `dist/browser/solvers/glpk.min.js` (IIFE, registers global `WashiGLPK`)
|
|
371
364
|
- **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
365
|
|
|
373
366
|
#### Browser ESM with GLPK
|
|
374
367
|
```html
|
|
375
368
|
<script type="module">
|
|
376
|
-
import { Model } from "./washi/washi.js";
|
|
369
|
+
import { Model, maximize } from "./washi/washi.js";
|
|
377
370
|
import "./washi/solvers/glpk.js";
|
|
378
371
|
|
|
379
372
|
const model = new Model({ solver: "glpk" });
|
|
380
373
|
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
381
|
-
model.maximize(x);
|
|
374
|
+
model.objective(maximize(x));
|
|
382
375
|
await model.solve();
|
|
383
376
|
console.log("GLPK status:", model.status);
|
|
384
377
|
</script>
|
|
@@ -391,52 +384,34 @@ The browser distribution includes pre-bundled, standalone solver plugins under `
|
|
|
391
384
|
<script>
|
|
392
385
|
const model = new Washi.Model({ solver: "glpk" });
|
|
393
386
|
const x = model.continuous("x", { lb: 0, ub: 10 });
|
|
394
|
-
model.maximize(x);
|
|
387
|
+
model.objective(Washi.maximize(x));
|
|
395
388
|
model.solve().then(status => console.log("GLPK status:", status));
|
|
396
389
|
</script>
|
|
397
390
|
```
|
|
398
391
|
|
|
399
392
|
> [!NOTE]
|
|
400
393
|
> **Z3 in Browser Environments & Cross-Origin Isolation**:
|
|
401
|
-
> Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation.
|
|
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/`.
|
|
413
|
-
|
|
414
|
-
### Offline distribution, Workers and custom hosting
|
|
415
|
-
|
|
416
|
-
Deploy the full application build or the entire browser distribution to a local HTTP / intranet server. With all assets locally available, an empty browser cache does not require access to the internet. Saving only the HTML or the first JS file is insufficient. `file://` execution and automatic PWA caching are not supported by this distribution.
|
|
417
|
-
|
|
418
|
-
ESM assets resolve relative to their module URL; the script build captures its own script URL at load time. CDN Workers use a Blob module bootstrap. The server must permit cross-origin JS / WASM loading; CSP must permit the selected script / connection origins and `worker-src blob:` for CDN Workers. If that is unavailable, use `useWorker: false`. Worker load / message failures reject pending solves rather than leaving them unresolved.
|
|
394
|
+
> Because the Z3 WebAssembly engine uses multithreading (`SharedArrayBuffer`), modern browsers require cross-origin isolation (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`). See the [Cross-Origin Isolation Guide](docs/getting-started/cross-origin.md) and [Runtime Environments Guide](docs/getting-started/runtimes.md) for full details on static hosting, `coi-serviceworker.js`, offline/intranet deployment, CSP (`worker-src blob:`), and custom WASM URLs (`setHighsWasmUrl`).
|
|
419
395
|
|
|
420
|
-
|
|
396
|
+
Complete standalone examples for ESM, script tags, and CodePen are available in [`examples/distribution/`](examples/distribution/README.md).
|
|
421
397
|
|
|
422
|
-
|
|
423
|
-
import { setHighsWasmUrl, OFFICIAL_HIGHS_CDN_WASM_URL } from "@ohtaman/washi";
|
|
424
|
-
setHighsWasmUrl(OFFICIAL_HIGHS_CDN_WASM_URL);
|
|
425
|
-
```
|
|
398
|
+
---
|
|
426
399
|
|
|
427
|
-
|
|
400
|
+
## 🛠️ Custom Solvers & Extensibility
|
|
428
401
|
|
|
429
|
-
|
|
402
|
+
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):
|
|
430
403
|
|
|
431
|
-
|
|
404
|
+
- See the complete [Custom Solver Developer Guide](docs/solvers/custom-solver.md) for step-by-step tutorials, complete API contracts (`Solver`, `SolveModelData`, `CheckModelData`, `SolverResult`), and testing best practices.
|
|
432
405
|
|
|
433
406
|
---
|
|
434
407
|
|
|
435
|
-
##
|
|
408
|
+
## 📚 Documentation
|
|
436
409
|
|
|
437
|
-
|
|
410
|
+
Full documentation is available at **[ohtaman.github.io/washi](https://ohtaman.github.io/washi/)** and in the repository's [`docs/`](docs/) directory:
|
|
438
411
|
|
|
439
|
-
-
|
|
412
|
+
- **Getting Started**: [Quick Start](docs/getting-started/index.md) · [AI Agent Quick-Reference](docs/getting-started/ai-agents.md) · [Runtime Environments](docs/getting-started/runtimes.md) · [Cross-Origin Isolation (Z3)](docs/getting-started/cross-origin.md)
|
|
413
|
+
- **Modeling Guide**: [Variables (`Var`, `BoolVar`)](docs/guide/variables.md) · [Expressions (`expr`, `sum`, `dot`)](docs/guide/expressions.md) · [Constraints & Logic (`indicator`, SMT)](docs/guide/constraints.md) · [Objectives & Solving (`solve`, `validateSolution`)](docs/guide/objectives.md) · [Feasibility & Unsat Core (`check`)](docs/guide/unsat-core.md)
|
|
414
|
+
- **Solvers**: [Solver Overview](docs/solvers/index.md) · [HiGHS](docs/solvers/highs.md) · [Z3](docs/solvers/z3.md) · [GLPK](docs/solvers/glpk.md) · [Custom Solvers](docs/solvers/custom-solver.md)
|
|
440
415
|
|
|
441
416
|
---
|
|
442
417
|
|
|
@@ -446,7 +421,7 @@ Start with the [example gallery](https://github.com/ohtaman/washi/blob/main/exam
|
|
|
446
421
|
|
|
447
422
|
| Category | Example | What it demonstrates | Solver |
|
|
448
423
|
| :--- | :--- | :--- | :--- |
|
|
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
|
|
424
|
+
| **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`) | HiGHS |
|
|
450
425
|
| **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
426
|
| **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
427
|
| **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 |
|
|
@@ -477,7 +452,7 @@ The example sources import the repository's `src/` modules. In an application us
|
|
|
477
452
|
|
|
478
453
|
---
|
|
479
454
|
|
|
480
|
-
## 🧪 Testing
|
|
455
|
+
## 🧪 Testing & Reproducible Benchmarks
|
|
481
456
|
|
|
482
457
|
Run the repository's unit and integration tests, and build the distributions:
|
|
483
458
|
|
|
@@ -489,6 +464,24 @@ npm test
|
|
|
489
464
|
npm run build
|
|
490
465
|
```
|
|
491
466
|
|
|
467
|
+
### Optional Reproducible Benchmarks
|
|
468
|
+
|
|
469
|
+
To measure modeling and solver phases on deterministic `small-lp` (24 variables, 18 constraints) and `scheduling-mip` (315 variables, 231 constraints, including rebuilt `initialValue` warm starts) scenarios without mixing performance timings into correctness test thresholds:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Run Node.js in-process WASM benchmarks (HiGHS and GLPK, 5 warm iterations by default)
|
|
473
|
+
npm run bench
|
|
474
|
+
|
|
475
|
+
# Customize iteration count or emit structured JSON
|
|
476
|
+
node scripts/run-benchmarks.mjs --iterations=10 --json
|
|
477
|
+
|
|
478
|
+
# Also measure headless Chromium Web Worker execution (requires Playwright Chromium)
|
|
479
|
+
node scripts/run-benchmarks.mjs --browser
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
- **Explicit phase boundaries**: Reports cold runtime initialization (`coldRuntimeInitMs`, `coldFirstSolveMs`) separately from repeated warm-runtime measurements (`modelBuildMs`, `validationMs`, `lpSerializationMs` for LP-string adapters or `symbolMapMs` for structured adapters, `resultMappingMs` via `MockSolver`, `adapterSolveMs`, and `endToEndSolveMs`). Timings are measured directly per phase and are never subtracted to estimate "native solver time".
|
|
483
|
+
- **Distinct execution environments**: Node.js in-process WASM (`usesBrowserWorker: false`) and Browser Web Worker execution (`usesBrowserWorker: true`, which includes Worker lifecycle and `postMessage` structured-clone transfer) are reported as separate environments, not interchangeable baselines.
|
|
484
|
+
|
|
492
485
|
---
|
|
493
486
|
|
|
494
487
|
## 📄 License
|