@ohtaman/washi 0.5.0 → 0.7.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 +103 -386
- package/README.md +104 -387
- package/README.zh-CN.md +103 -386
- package/THIRD_PARTY_LICENSES.md +5 -4
- package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
- package/dist/browser/highs-worker.js +1 -1
- package/dist/browser/highs.wasm +0 -0
- package/dist/browser/highs.wasm.LICENSE +1 -2
- package/dist/browser/loader-DewlSho8.js +643 -0
- package/dist/browser/solvers/glpk.js +3 -3
- package/dist/browser/solvers/glpk.min.js +1 -1
- package/dist/browser/solvers/z3.js +7 -1
- package/dist/browser/solvers/z3.min.js +1 -1
- package/dist/browser/washi.js +1708 -540
- package/dist/browser/washi.min.js +3 -4
- package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
- package/dist/lib/highs-browser-runtime.js +2120 -4124
- package/dist/lib/highs-node-runtime.js +24 -1
- package/dist/lib/highs-runtime.js +2 -0
- package/dist/lib/highs-worker.js +1 -1
- package/dist/lib/highs.wasm +0 -0
- package/dist/lib/highs.wasm.LICENSE +1 -2
- package/dist/lib/loader-BZUQNvh4.js +649 -0
- package/dist/lib/{registry-BEfvpZW3.js → registry-D7TZ3Wh_.js} +142 -59
- package/dist/lib/solvers/glpk.js +4 -4
- package/dist/lib/solvers/z3.js +1 -1
- package/dist/lib/washi.js +1576 -465
- package/dist/types/decomposition/benders.d.ts +32 -0
- package/dist/types/decomposition/benders.d.ts.map +1 -0
- package/dist/types/decomposition/column-generation.d.ts +34 -0
- package/dist/types/decomposition/column-generation.d.ts.map +1 -0
- package/dist/types/decomposition/index.d.ts +4 -0
- package/dist/types/decomposition/index.d.ts.map +1 -0
- package/dist/types/decomposition/types.d.ts +230 -0
- package/dist/types/decomposition/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
- package/dist/types/modeler/constraint.d.ts +11 -0
- package/dist/types/modeler/constraint.d.ts.map +1 -1
- package/dist/types/modeler/expression.d.ts +57 -26
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +4 -3
- package/dist/types/modeler/index.d.ts.map +1 -1
- package/dist/types/modeler/linearizer.d.ts +15 -0
- package/dist/types/modeler/linearizer.d.ts.map +1 -0
- package/dist/types/modeler/lp-writer.d.ts +3 -1
- package/dist/types/modeler/lp-writer.d.ts.map +1 -1
- package/dist/types/modeler/model.d.ts +159 -8
- package/dist/types/modeler/model.d.ts.map +1 -1
- package/dist/types/modeler/parser.d.ts +25 -5
- package/dist/types/modeler/parser.d.ts.map +1 -1
- package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
- package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
- package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/loader.d.ts +4 -4
- package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
- package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
- package/dist/types/modeler/solvers/highs/node-runtime.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/highs/worker-client.d.ts.map +1 -1
- package/dist/types/modeler/solvers/types.d.ts +4 -0
- package/dist/types/modeler/solvers/types.d.ts.map +1 -1
- package/dist/types/modeler/types.d.ts +27 -0
- package/dist/types/modeler/types.d.ts.map +1 -1
- package/docs/examples/diet-sensitivity.md +15 -14
- package/docs/examples/ecommerce-coupons.md +15 -22
- package/docs/examples/index.md +18 -12
- package/docs/examples/knapsack.md +8 -7
- package/docs/examples/portfolio-qp.md +11 -4
- package/docs/examples/production.md +4 -4
- package/docs/examples/shift-scheduling.md +20 -7
- package/docs/examples/smt-security.md +16 -4
- package/docs/examples/transportation.md +46 -19
- package/docs/examples/traveling-salesperson.md +157 -0
- package/docs/examples/unsat-core.md +15 -5
- package/docs/examples/vehicle-routing.md +208 -0
- package/docs/getting-started/ai-agents.md +33 -18
- package/docs/getting-started/cross-origin.md +56 -28
- package/docs/getting-started/index.md +40 -69
- package/docs/getting-started/runtimes.md +66 -61
- package/docs/guide/constraints.md +47 -41
- package/docs/guide/decomposition.md +267 -0
- package/docs/guide/expressions.md +90 -39
- package/docs/guide/formulation-tips.md +212 -0
- package/docs/guide/objectives.md +7 -4
- package/docs/guide/unsat-core.md +28 -9
- package/docs/guide/variables.md +33 -38
- package/docs/ja/getting-started/ai-agents.md +32 -18
- package/docs/solvers/custom-solver.md +1 -1
- package/docs/solvers/highs.md +48 -5
- package/docs/solvers/index.md +4 -4
- package/docs/solvers/z3.md +3 -0
- package/docs/zh/getting-started/ai-agents.md +32 -18
- package/llms-full.txt +1536 -478
- package/llms.txt +28 -22
- package/package.json +19 -16
- package/skills/washi/SKILL.md +21 -17
- package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
- package/dist/browser/loader-CtaDHef_.js +0 -273
- package/dist/lib/loader-DmNC_UNk.js +0 -279
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
> **Scope Note**: This document is the **consumer modeling quick-reference** for AI coding agents and developers building optimization or satisfiability models with `@ohtaman/washi`.
|
|
4
4
|
> - **Portable Agent Skill (`SKILL.md`)**: For an actionable end-to-end modeling, `toLpString()` inspection, `validateSolution()` verification, and Z3 `unsatCore` conflict-diagnosis workflow skill (compatible with Claude Code, Antigravity, Cursor, Codex, and other `SKILL.md`-aware agents), use [`skills/washi/SKILL.md`](https://github.com/ohtaman/washi/blob/main/skills/washi/SKILL.md) (also shipped in the npm package at `node_modules/@ohtaman/washi/skills/washi/SKILL.md`):
|
|
5
5
|
> ```bash
|
|
6
|
-
> #
|
|
6
|
+
> # Install directly via the skills CLI (skills.sh / vercel-labs/skills)
|
|
7
|
+
> npx skills add ohtaman/washi
|
|
8
|
+
>
|
|
9
|
+
> # Or link/copy the packaged skill into your project's agent skills directory
|
|
7
10
|
> mkdir -p .agents/skills/washi
|
|
8
11
|
> cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
|
|
9
12
|
> ```
|
|
@@ -54,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
54
57
|
1. **Bundled WASM by default (no implicit CDN fallback)**:
|
|
55
58
|
- In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
|
|
56
59
|
- In browser deployments, deploy the full `dist/browser/` directory (JS, Worker, and `.wasm` files) together. If a local WASM URL is missing or returns an HTML SPA fallback, initialization fails immediately and reports the attempted URL—it **never** silently switches to a public CDN.
|
|
57
|
-
- In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads
|
|
60
|
+
- In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads WaSHI's bundled local `highs.wasm` runtime).
|
|
58
61
|
2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
|
|
59
62
|
- Z3 WebAssembly uses `SharedArrayBuffer` multithreading. Browser pages running Z3 must run in a secure context (`window.isSecureContext === true`, i.e., HTTPS or `localhost`) and be cross-origin isolated (`window.crossOriginIsolated === true`) by serving:
|
|
60
63
|
- `Cross-Origin-Opener-Policy: same-origin` (on the document)
|
|
@@ -63,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
|
|
|
63
66
|
- Importing `@ohtaman/washi` never auto-injects a Service Worker. When a secure context or cross-origin isolation is absent in a browser, Z3 rejects immediately with a descriptive error.
|
|
64
67
|
3. **Web Workers & Cancellation (`AbortSignal`)**:
|
|
65
68
|
- Pre-solve cancellation (`signal.aborted` before execution starts) is supported across all solvers.
|
|
66
|
-
- In browsers where `Worker` is available, HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
|
|
69
|
+
- In browsers where `Worker` is available on a non-opaque effective origin (`window.origin !== "null"`), HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). On opaque effective origins (`window.origin === "null"`, such as `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), HiGHS defaults to main-thread execution (`useWorker: false`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
|
|
67
70
|
- On main-thread HiGHS (`useWorker: false` or in Node.js), cooperative mid-solve cancellation is supported when `abort()` is triggered synchronously inside an `onProgress` callback at a solver interrupt checkpoint; arbitrary asynchronous aborts from outside cannot preempt a blocked synchronous WASM thread until a callback checkpoint or completion. Single-threaded WASM solvers without interrupt hooks (GLPK, Z3) check `signal` before execution starts.
|
|
68
71
|
|
|
69
72
|
---
|
|
@@ -79,6 +82,7 @@ const m = new Model("ExampleModel");
|
|
|
79
82
|
|
|
80
83
|
// Scalar variables
|
|
81
84
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
85
|
+
const y = m.continuous("y", { lb: 0, ub: 100 });
|
|
82
86
|
const n = m.integer("n", { lb: 0, ub: 10 });
|
|
83
87
|
const b = m.binary("b");
|
|
84
88
|
const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
|
|
@@ -100,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
|
|
|
100
104
|
### Step 2: Build Expressions (`expr`, `sum`, `dot`)
|
|
101
105
|
|
|
102
106
|
- **Unified `expr` template tag**:
|
|
103
|
-
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
|
|
104
|
-
- Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${
|
|
105
|
-
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
|
|
106
|
-
- **Aggregations**:
|
|
107
|
-
- `sum(
|
|
108
|
-
- `dot(a, b)` computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]
|
|
107
|
+
- Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``, `` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``, `` expr`sum((${price} - ${cost}) * ${byItem})` ``
|
|
108
|
+
- Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``, `` expr`sum((${vec} - ${target})^2)` `` (supported for convex QP objectives on purely continuous models using `m.continuous`; built-in HiGHS does not support MIQP models containing integer, binary, or boolean variables).
|
|
109
|
+
- Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``, `` expr`sum(${byItem}) <= 100` `` (supports `<=`, `>=`, `==`, and `=`).
|
|
110
|
+
- **Aggregations & Element-Wise Container Arithmetic**:
|
|
111
|
+
- `sum(containerOrItems, mapper?)` (usable inside `` expr`sum(...)` `` or standalone `sum(...)`) sums a 1D/2D `Array`, `Iterable`, or plain `Record` of `Var` / `LinExpr` / `number` (without needing `Object.values(record)`), or maps over items/counts via `sum(items, (item, i) => ...)`. Inside `` expr`sum(...)` ``, element-wise container arithmetic (`+`, `-`, `*`, `/`, `^2`) is also supported.
|
|
112
|
+
- `dot(a, b)` (usable inside `` expr`dot(${a}, ${b})` `` or standalone `dot(a, b)`) computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`, computing the Frobenius inner product $\sum_{i,j} A_{i,j} B_{i,j}$), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
|
|
109
113
|
- **Fluent methods**:
|
|
110
114
|
- Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
|
|
111
115
|
- Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
|
|
@@ -119,8 +123,8 @@ Constructing an `expr` or `.le()` object does **not** attach it to the model unt
|
|
|
119
123
|
// Set objective using maximize() or minimize()
|
|
120
124
|
m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
|
|
121
125
|
|
|
122
|
-
// Register constraints on the model
|
|
123
|
-
m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
126
|
+
// Register constraints on the model (returns the registered Constraint / BoolExpr or array)
|
|
127
|
+
const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
|
|
124
128
|
|
|
125
129
|
// Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
|
|
126
130
|
m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
@@ -132,9 +136,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
132
136
|
- Returns `SolverStatus` (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`, `"Sat"`, or `"Unsat"`). Built-in optimization solvers return the optimization statuses (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`), where a time-, iteration-, or solution-limited run (or an interrupted solve) that found a valid primal incumbent returns `"Feasible"`; `"Sat"` and `"Unsat"` are included in `SolverStatus` for satisfiability/custom solver adapters (such as `MockSolver`).
|
|
133
137
|
- Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
|
|
134
138
|
- Call `m.validateSolution()` after solving to independently verify variable finiteness, variable bounds (`lb`/`ub`), integer/binary/boolean domains, linear constraint residuals, and logical constraints (`SolutionValidationReport`: `{ valid, complete, maxViolation, violations, ... }`). Note that `validateSolution()` checks primal feasibility of variables and constraints; it does not recompute or compare `m.objectiveValue`.
|
|
135
|
-
- **Satisfiability &
|
|
136
|
-
- Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts`
|
|
137
|
-
- For models containing
|
|
139
|
+
- **Satisfiability & Conflict Diagnosis (`await m.check(options?)`)**:
|
|
140
|
+
- Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` is populated when the model is proven `"Unsat"`.
|
|
141
|
+
- For models containing linear arithmetic constraints or logical constraints (`BoolVar`, `and`, `or`, `implies`, `xor`), `m.check()` works with any solver (including the default HiGHS and GLPK via single-solve elastic slack relaxation, automatically linearizing logical constraints into 0-1 MIP formulation) and diagnoses conflicting constraints as `conflicts` (leaving `unsatCore: undefined` for semantic rigor).
|
|
142
|
+
- Exact SMT unsatisfiable-core extraction (`result.unsatCore` and `result.conflicts`) is provided when using an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `unsatCore` reports a subset of registered constraint names that conflicts *together with the model's fixed variable bounds/domains* (and is not guaranteed minimal).
|
|
138
143
|
- **No concurrent mutation during async solve/check**:
|
|
139
144
|
- Mutating variables, constraints, or the objective while `await m.solve()` or `await m.check()` is pending invalidates the in-flight execution and rejects the stale promise.
|
|
140
145
|
|
|
@@ -157,10 +162,13 @@ export async function runMipExample() {
|
|
|
157
162
|
const qty = m.integer(products, { lb: 0, ub: 100 });
|
|
158
163
|
const setup = m.binary("setup");
|
|
159
164
|
|
|
160
|
-
m.objective(maximize(expr
|
|
161
|
-
m.constraint(expr
|
|
162
|
-
m.constraint(expr
|
|
163
|
-
m.
|
|
165
|
+
m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${setup}`));
|
|
166
|
+
m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_cap");
|
|
167
|
+
m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_cap");
|
|
168
|
+
m.indicator(setup, expr`sum(${qty}) <= 0`, {
|
|
169
|
+
activeValue: 0,
|
|
170
|
+
name: "require_setup",
|
|
171
|
+
});
|
|
164
172
|
|
|
165
173
|
const status = await m.solve();
|
|
166
174
|
const report = m.validateSolution();
|
|
@@ -265,3 +273,10 @@ export function verifyNegativePatterns() {
|
|
|
265
273
|
}
|
|
266
274
|
```
|
|
267
275
|
|
|
276
|
+
### Practical OR Modeling Guardrails
|
|
277
|
+
|
|
278
|
+
- **Prefer inequalities (`>=` / `<=`) over exact equalities (`==`)** for demand coverage and resource limits when objective costs are positive; reserve `==` for physical conservation laws (e.g., flow balance, portfolio weights summing to `1`).
|
|
279
|
+
- **Always provide finite `lb` and `ub` bounds** on variables referenced inside `m.indicator(...)` and omit `M` so WaSHI automatically derives the tightest valid Big-M instead of falling back to `1e6`.
|
|
280
|
+
- **Use soft constraints (elastic slack variables)** for non-rigid business targets (e.g., preferred staffing, safety stock, day-off requests) by adding a non-negative slack variable (`lb: 0`) with a tiered objective penalty, reserving hard constraints for physical or legal invariants.
|
|
281
|
+
|
|
282
|
+
|
|
@@ -1,47 +1,70 @@
|
|
|
1
1
|
# Cross-Origin Isolation (Z3 & Browsers)
|
|
2
2
|
|
|
3
|
-
When running the **Z3** solver
|
|
3
|
+
When running the **Z3** solver inside a web browser, Cross-Origin Isolation HTTP headers are required so the browser enables `SharedArrayBuffer`.
|
|
4
|
+
|
|
5
|
+
> [!TIP]
|
|
6
|
+
> **Not needed for HiGHS, GLPK, or Node.js**: The built-in **HiGHS** solver and **GLPK** plugin run in standard browser contexts without Cross-Origin Isolation or `SharedArrayBuffer`. Running Z3 in **Node.js** also requires no HTTP headers. You only need this page when using `@ohtaman/washi/solvers/z3` in the browser.
|
|
4
7
|
|
|
5
8
|
---
|
|
6
9
|
|
|
7
10
|
## Why is Cross-Origin Isolation Required?
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
In modern browsers (Chrome, Firefox, Safari, Edge), `SharedArrayBuffer` is disabled by default to mitigate Spectre-style timing side-channel attacks. To re-enable it, the hosting page must be **Cross-Origin Isolated**.
|
|
12
|
+
Z3's WebAssembly build uses multithreading (pthreads) backed by `SharedArrayBuffer` and Web Workers. Modern browsers (Chrome, Firefox, Safari, Edge) disable `SharedArrayBuffer` by default to mitigate Spectre-style side-channel attacks unless the top-level document opts into **Cross-Origin Isolation** (`window.crossOriginIsolated === true`).
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
## Required HTTP Response Headers
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Serve the top-level HTML document with:
|
|
18
19
|
|
|
19
20
|
```http
|
|
20
21
|
Cross-Origin-Opener-Policy: same-origin
|
|
21
22
|
Cross-Origin-Embedder-Policy: require-corp
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
If your page loads cross-origin subresources (such as scripts, images, or WASM files from another domain), those responses must also include `Cross-Origin-Resource-Policy: cross-origin` (or CORS headers).
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
Cross-Origin-
|
|
28
|
-
```
|
|
27
|
+
> [!NOTE]
|
|
28
|
+
> Chromium and Firefox also support `Cross-Origin-Embedder-Policy: credentialless`, which relaxes subresource CORP requirements, **but Safari (WebKit) does not support `credentialless`**. Use `require-corp` if you support Safari / iOS.
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Setup by Environment
|
|
33
33
|
|
|
34
|
-
### 1.
|
|
34
|
+
### 1. Vite (`vite.config.ts`)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
Configure both the local dev server (`vite`) and production preview server (`vite preview`):
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
server: {
|
|
43
|
+
headers: {
|
|
44
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
45
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
preview: {
|
|
49
|
+
headers: {
|
|
50
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
51
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
41
54
|
});
|
|
42
55
|
```
|
|
43
56
|
|
|
44
|
-
### 2.
|
|
57
|
+
### 2. Cloudflare Pages / Netlify (`public/_headers`)
|
|
58
|
+
|
|
59
|
+
Place a `_headers` file in `public/` so it is copied to the root of your build output:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
/*
|
|
63
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
64
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Next.js (`next.config.js`)
|
|
45
68
|
|
|
46
69
|
```javascript
|
|
47
70
|
/** @type {import('next').NextConfig} */
|
|
@@ -62,26 +85,31 @@ const nextConfig = {
|
|
|
62
85
|
module.exports = nextConfig;
|
|
63
86
|
```
|
|
64
87
|
|
|
65
|
-
###
|
|
88
|
+
### 4. Custom Node.js / Express Server
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
app.use((req, res, next) => {
|
|
92
|
+
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
93
|
+
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
94
|
+
next();
|
|
95
|
+
});
|
|
96
|
+
```
|
|
66
97
|
|
|
67
|
-
|
|
98
|
+
### 5. Static Hosting without Header Control (e.g., GitHub Pages)
|
|
68
99
|
|
|
69
|
-
|
|
100
|
+
Platforms like GitHub Pages do not allow customizing HTTP response headers directly. You can use **`coi-serviceworker`** to inject the isolation headers via a client-side Service Worker:
|
|
70
101
|
|
|
71
|
-
1. Place `coi-serviceworker.js`
|
|
72
|
-
2.
|
|
102
|
+
1. Place `coi-serviceworker.js` at your site root.
|
|
103
|
+
2. Load it in your HTML `<head>`:
|
|
73
104
|
|
|
74
105
|
```html
|
|
75
106
|
<script src="/coi-serviceworker.js"></script>
|
|
76
107
|
```
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
On the first visit, the Service Worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
|
|
79
110
|
|
|
80
111
|
---
|
|
81
112
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
WaSHI's Z3 solver adapter automatically inspects the environment at runtime. If Z3 is invoked in a browser lacking cross-origin isolation, it immediately rejects with a clear, informative error message explaining that COOP/COEP headers or a service worker are required, rather than hanging silently.
|
|
113
|
+
## Runtime Check in WaSHI
|
|
85
114
|
|
|
86
|
-
|
|
87
|
-
> The default **HiGHS** solver engine does not require cross-origin isolation or `SharedArrayBuffer`. It works out of the box in standard browser contexts.
|
|
115
|
+
WaSHI's Z3 adapter checks `crossOriginIsolated` before initializing Z3 in the browser. If Cross-Origin Isolation is missing, `solve()` / `check()` immediately rejects with an actionable error explaining that COOP/COEP headers or a Service Worker are required, rather than hanging silently.
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
# Introduction & Installation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) brings the algebraic modeling style of Python's **PuLP** to the TypeScript and JavaScript ecosystem. Write expressions naturally with tagged templates (`expr`) and solve mathematical optimization problems directly in **Node.js and the browser** using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```typescript
|
|
6
|
+
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
7
|
+
|
|
8
|
+
const m = new Model("production");
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
// 1. Define decision variables with realistic finite bounds (lb / ub)
|
|
11
|
+
const desks = m.integer("desks", { lb: 0, ub: 100 });
|
|
12
|
+
const chairs = m.integer("chairs", { lb: 0, ub: 100 });
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
// 2. Set the objective function and constraints
|
|
15
|
+
m.objective(maximize(expr`30 * ${desks} + 50 * ${chairs}`));
|
|
16
|
+
m.constraint(expr`${desks} + 2 * ${chairs} <= 40`, "wood_capacity");
|
|
17
|
+
m.constraint(expr`2 * ${desks} + ${chairs} <= 50`, "labor_capacity");
|
|
18
|
+
|
|
19
|
+
// 3. Solve (built-in HiGHS WebAssembly) and validate the solution
|
|
20
|
+
const status = await m.solve();
|
|
21
|
+
if (m.hasPrimalSolution) {
|
|
22
|
+
const report = m.validateSolution();
|
|
23
|
+
console.log(status, report.valid); // "Optimal", true
|
|
24
|
+
console.log(`Profit: ${m.objectiveValue} (desks=${desks.value}, chairs=${chairs.value})`); // 1100 (desks=20, chairs=10)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
11
27
|
|
|
12
28
|
---
|
|
13
29
|
|
|
14
30
|
## 📦 Installation
|
|
15
31
|
|
|
16
|
-
|
|
32
|
+
The default **HiGHS** solver (LP / MIP / Convex QP) is bundled and works out of the box:
|
|
17
33
|
|
|
18
34
|
::: code-group
|
|
19
35
|
|
|
@@ -35,82 +51,37 @@ bun add @ohtaman/washi
|
|
|
35
51
|
|
|
36
52
|
:::
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
WaSHI ships with **HiGHS** built-in with zero configuration. If you need SMT logic / Unsat Core diagnosis via **Z3**, or compatibility with **GLPK**, install their respective peer dependencies:
|
|
54
|
+
Install optional peer dependencies only if you need **Z3** (SMT logic & Unsat Core diagnosis) or **GLPK**:
|
|
41
55
|
|
|
42
56
|
```bash
|
|
43
|
-
# For SMT logic and Unsat Core conflict diagnosis
|
|
44
|
-
npm install
|
|
45
|
-
|
|
46
|
-
# For GLPK solver (GPL-3.0)
|
|
47
|
-
npm install glpk.js
|
|
57
|
+
npm install z3-solver # For SMT logic and Unsat Core conflict diagnosis
|
|
58
|
+
npm install glpk.js # For GLPK solver (GPL-3.0)
|
|
48
59
|
```
|
|
49
60
|
|
|
50
61
|
---
|
|
51
62
|
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
Here is a step-by-step example of solving a production allocation problem.
|
|
55
|
-
|
|
56
|
-
### 1. Import the Core Classes
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### 2. Instantiate a Model
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
const model = new Model("WoodworkingProfit");
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### 3. Add Decision Variables
|
|
63
|
+
## 💡 Why TypeScript & WebAssembly?
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
- **Zero native setup across Node.js and the browser**: No platform-specific C/C++ builds or separate Python runtimes to manage. A single `npm install` runs the same modeling code across **Node.js APIs, CLIs, and scripts** as well as in the **browser**.
|
|
66
|
+
- **Lightweight embedding with standard Web APIs & UIs**: Go beyond local analysis scripts by shipping **interactive Web tools, mini-games, or Chrome extensions** that anyone can open via a URL with zero backend hosting cost. You can wire mathematical optimization directly into drag-and-drop UIs or **standard browser capabilities**—such as screen/camera capture and speech recognition (Web Speech API)—with no network round-trips or external data transfer.
|
|
67
|
+
- **End-to-end type safety from app data to model**: Prevent common indexing and dictionary-key mistakes with TypeScript's type system. Typed variable dictionaries (`Record<K, Var>`) and variable-reference interpolation (`` expr`30 * ${qty.desk} <= 40` ``) connect directly to your application schemas, preserving compile-time variable/key checks, IDE autocompletion, and symbol renaming.
|
|
68
|
+
- **Strong synergy with AI development & LLM workflows**: Keeping your application and optimization model in a **single TypeScript codebase** lets you validate everything in one test runner—without crossing a Python service boundary—and deploy easily as static assets or a single service. That tight test-and-deploy loop fits AI-assisted coding naturally, while at runtime you can pair flexible LLM extraction with exact solver feasibility and conflict diagnosis (`unsatCore`) in the same runtime.
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
const x = model.integer("x", { lb: 0 });
|
|
74
|
-
const y = model.integer("y", { lb: 0 });
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 4. Set the Objective Function
|
|
78
|
-
|
|
79
|
-
Use the `expr` tagged template literal along with `maximize(...)` or `minimize(...)` to define the goal:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// Maximize 30*x + 50*y
|
|
83
|
-
model.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 5. Add Constraints
|
|
87
|
-
|
|
88
|
-
Use `model.constraint()` with the `expr` tagged template to formulate equality and inequality constraints:
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
model.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_capacity");
|
|
92
|
-
model.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_capacity");
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### 6. Solve and Extract Results
|
|
70
|
+
---
|
|
96
71
|
|
|
97
|
-
|
|
72
|
+
## 🔍 Core Workflow Essentials
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
const status = await model.solve();
|
|
74
|
+
Three practical habits cover the essentials shown in the snippet above:
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```
|
|
76
|
+
1. **Set realistic finite bounds (`lb`, `ub`)**: When defining variables with `m.continuous`, `m.integer`, or `m.binary`, finite bounds strengthen MIP presolve and enable automatic tight Big-M derivation for logical constraints.
|
|
77
|
+
2. **Formulate objectives and constraints with `expr`**: Use `` m.objective(maximize(expr`...`)) `` and `` m.constraint(expr`... <= ...`, "name") ``. Naming constraints lets you pinpoint conflicting rules immediately if a model becomes infeasible.
|
|
78
|
+
3. **Check `hasPrimalSolution` and call `validateSolution()`**: After `await m.solve()`, check `m.hasPrimalSolution` before reading `.value` on variables, and call `m.validateSolution()` to independently verify that bounds, integrality, and constraints hold within tolerance.
|
|
106
79
|
|
|
107
80
|
---
|
|
108
81
|
|
|
109
82
|
## 🧭 Next Steps
|
|
110
83
|
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
- Browse 9 runnable optimization and SMT recipes in [Examples & API Finder](../examples/).
|
|
116
|
-
|
|
84
|
+
- **[Modeling Guide](../guide/variables.md)**: Array/dictionary variables, aggregations (`sum`, `dot`), logical constraints, and Unsat Core diagnosis
|
|
85
|
+
- **[Examples & API Finder](../examples/)**: 11 runnable optimization and SMT recipes—from shift scheduling and portfolio QP to vehicle routing
|
|
86
|
+
- **[Solvers Overview](../solvers/)**: Compare built-in HiGHS with optional Z3 and GLPK adapters
|
|
87
|
+
- **[Runtime Environments](./runtimes.md)**: Setup details for Node.js, Vite / Next.js bundlers, CDNs, and static hosting
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Runtime Environments & Deployment
|
|
2
2
|
|
|
3
|
-
WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, vanilla browser `<script>` tags
|
|
3
|
+
WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, static hosting, and vanilla browser `<script>` tags—without native C/C++ compilation.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 🟢 Node.js
|
|
7
|
+
## 🟢 Node.js, Bun & Deno
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Node.js
|
|
10
|
+
|
|
11
|
+
WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package:
|
|
10
12
|
|
|
11
13
|
```typescript
|
|
12
14
|
import { Model, expr, maximize } from "@ohtaman/washi";
|
|
@@ -19,8 +21,10 @@ m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
|
|
|
19
21
|
m.constraint(expr`${x} + ${y} <= 15`, "capacity");
|
|
20
22
|
|
|
21
23
|
await m.solve();
|
|
22
|
-
|
|
23
|
-
console.log(`
|
|
24
|
+
if (m.hasPrimalSolution) {
|
|
25
|
+
console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
|
|
26
|
+
console.log(`x = ${x.value}, y = ${y.value}`);
|
|
27
|
+
}
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
Run directly with `tsx` or `node`:
|
|
@@ -29,11 +33,33 @@ Run directly with `tsx` or `node`:
|
|
|
29
33
|
npx tsx app.ts
|
|
30
34
|
```
|
|
31
35
|
|
|
36
|
+
### Bun & Deno
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Bun
|
|
40
|
+
bun add @ohtaman/washi
|
|
41
|
+
bun run app.ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
In Deno (v1.30+ with `npm:` specifiers):
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { Model, maximize } from "npm:@ohtaman/washi";
|
|
48
|
+
|
|
49
|
+
const m = new Model();
|
|
50
|
+
const x = m.continuous("x", { lb: 0, ub: 10 });
|
|
51
|
+
m.objective(maximize(x));
|
|
52
|
+
await m.solve();
|
|
53
|
+
if (m.hasPrimalSolution) {
|
|
54
|
+
console.log("x =", x.value);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
32
58
|
---
|
|
33
59
|
|
|
34
|
-
## 🌐 Bundlers (Vite, Webpack, Rollup
|
|
60
|
+
## 🌐 Bundlers & Static Hosting (Vite, Next.js, Webpack, Rollup)
|
|
35
61
|
|
|
36
|
-
### Modern Bundlers
|
|
62
|
+
### Modern Bundlers & Static Deployment
|
|
37
63
|
|
|
38
64
|
When importing `@ohtaman/washi` in a browser bundler, the `browser` export condition resolves automatically:
|
|
39
65
|
|
|
@@ -45,14 +71,16 @@ export async function runOptimization() {
|
|
|
45
71
|
const x = m.continuous("x", { lb: 0, ub: 100 });
|
|
46
72
|
m.objective(maximize(x));
|
|
47
73
|
await m.solve();
|
|
48
|
-
return x.value;
|
|
74
|
+
return m.hasPrimalSolution ? x.value : null;
|
|
49
75
|
}
|
|
50
76
|
```
|
|
51
77
|
|
|
78
|
+
Because both the modeler and solver run on WebAssembly, the static output of `vite build` (or your bundler's production build) can be deployed directly to **GitHub Pages, Cloudflare Pages, Vercel, or Netlify** with no backend server required. (The default HiGHS and GLPK solvers require no special HTTP headers; if you use Z3 in the browser, see [Cross-Origin Isolation](./cross-origin.md).)
|
|
79
|
+
|
|
52
80
|
#### Background Web Worker Execution (Default)
|
|
53
|
-
In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so
|
|
81
|
+
In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (`onProgress`) and cancellation (`AbortSignal`).
|
|
54
82
|
|
|
55
|
-
If your environment prohibits Web Workers or you explicitly want
|
|
83
|
+
If your environment prohibits Web Workers or you explicitly want main-thread execution, pass `useWorker: false`:
|
|
56
84
|
|
|
57
85
|
```typescript
|
|
58
86
|
await m.solve({ useWorker: false });
|
|
@@ -91,7 +119,9 @@ export default function OptimizerComponent() {
|
|
|
91
119
|
const x = m.continuous("x", { lb: 0, ub: 50 });
|
|
92
120
|
m.objective(maximize(x));
|
|
93
121
|
await m.solve();
|
|
94
|
-
|
|
122
|
+
if (m.hasPrimalSolution) {
|
|
123
|
+
setVal(x.value);
|
|
124
|
+
}
|
|
95
125
|
};
|
|
96
126
|
|
|
97
127
|
return (
|
|
@@ -107,36 +137,36 @@ export default function OptimizerComponent() {
|
|
|
107
137
|
|
|
108
138
|
## 📦 Browser ESM (`<script type="module">`)
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
Without a bundler, you can import the pre-built browser ESM module directly from your own static server or an npm CDN:
|
|
111
141
|
|
|
112
142
|
```html
|
|
113
143
|
<script type="module">
|
|
114
|
-
//
|
|
115
|
-
import { Model, expr, maximize } from "
|
|
116
|
-
// Or from an npm CDN once a compatible release is published:
|
|
117
|
-
// import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.js";
|
|
144
|
+
// From an npm CDN (or "./dist/browser/washi.js" when self-hosted):
|
|
145
|
+
import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.js";
|
|
118
146
|
// Optional solver plugins:
|
|
119
|
-
// import "
|
|
120
|
-
// import "
|
|
147
|
+
// import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.js";
|
|
148
|
+
// import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.js";
|
|
121
149
|
|
|
122
150
|
const m = new Model("BrowserESM");
|
|
123
|
-
const x = m.integer("x", { lb: 0 });
|
|
124
|
-
const y = m.integer("y", { lb: 0 });
|
|
151
|
+
const x = m.integer("x", { lb: 0, ub: 100 });
|
|
152
|
+
const y = m.integer("y", { lb: 0, ub: 100 });
|
|
125
153
|
|
|
126
154
|
m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
|
|
127
155
|
m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
|
|
128
156
|
m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor");
|
|
129
157
|
|
|
130
158
|
await m.solve();
|
|
131
|
-
|
|
159
|
+
if (m.hasPrimalSolution) {
|
|
160
|
+
console.log("Optimal Objective:", m.objectiveValue);
|
|
161
|
+
}
|
|
132
162
|
</script>
|
|
133
163
|
```
|
|
134
164
|
|
|
135
165
|
---
|
|
136
166
|
|
|
137
|
-
## 🟨 Classic `<script>` Tags (IIFE / Standalone
|
|
167
|
+
## 🟨 Classic `<script>` Tags (IIFE / Standalone HTML / CDN)
|
|
138
168
|
|
|
139
|
-
For
|
|
169
|
+
For single-file HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard `<script>` tag. All exports are attached to the global `window.Washi` object:
|
|
140
170
|
|
|
141
171
|
```html
|
|
142
172
|
<!DOCTYPE html>
|
|
@@ -149,13 +179,11 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
|
|
|
149
179
|
<h1>WaSHI Browser Optimization</h1>
|
|
150
180
|
<pre id="output">Solving...</pre>
|
|
151
181
|
|
|
152
|
-
<!-- Core WaSHI + HiGHS (MIT)
|
|
153
|
-
<script src="
|
|
154
|
-
<!-- Or via CDN once a compatible release is published: -->
|
|
155
|
-
<!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js"></script> -->
|
|
182
|
+
<!-- Core WaSHI + HiGHS (MIT) via CDN (or "./dist/browser/washi.min.js" when self-hosted) -->
|
|
183
|
+
<script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.min.js"></script>
|
|
156
184
|
<!-- Optional plugins (auto-register on window.Washi): -->
|
|
157
|
-
<!-- <script src="
|
|
158
|
-
<!-- <script src="
|
|
185
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.min.js"></script> -->
|
|
186
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.min.js"></script> -->
|
|
159
187
|
|
|
160
188
|
<script>
|
|
161
189
|
(async () => {
|
|
@@ -170,10 +198,12 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
|
|
|
170
198
|
|
|
171
199
|
await m.solve();
|
|
172
200
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
if (m.hasPrimalSolution) {
|
|
202
|
+
document.getElementById("output").textContent =
|
|
203
|
+
`Status: ${m.status}\n` +
|
|
204
|
+
`Objective: ${m.objectiveValue}\n` +
|
|
205
|
+
`x = ${x.value}, y = ${y.value}`;
|
|
206
|
+
}
|
|
177
207
|
})();
|
|
178
208
|
</script>
|
|
179
209
|
</body>
|
|
@@ -181,7 +211,7 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
|
|
|
181
211
|
```
|
|
182
212
|
|
|
183
213
|
> [!NOTE]
|
|
184
|
-
> When loading
|
|
214
|
+
> When loading from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct `new Worker("https://cdn...")` construction under the same-origin policy. On standard web origins (`https://` / `http://`), WaSHI automatically bridges cross-origin workers via a lightweight ES module `Blob` bootstrap (`import "https://..."`). On opaque effective origins (`window.origin === "null"`, such as local `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), WaSHI automatically runs HiGHS on the main thread when `useWorker` is omitted.
|
|
185
215
|
|
|
186
216
|
---
|
|
187
217
|
|
|
@@ -195,33 +225,8 @@ WaSHI never phones home or falls back to public CDNs. To self-host in an air-gap
|
|
|
195
225
|
- Optional Z3: `solvers/z3.js` / `solvers/z3.min.js`, `solvers/z3-built.js`, `solvers/z3-built.wasm`
|
|
196
226
|
2. **Content Security Policy (CSP) Headers**:
|
|
197
227
|
- Allow WebAssembly compilation (`'wasm-unsafe-eval'` in `script-src`).
|
|
198
|
-
- If using the default Web Worker or cross-origin Blob worker
|
|
228
|
+
- If using the default Web Worker or cross-origin Blob worker bootstrap, allow `worker-src 'self' blob:;`.
|
|
199
229
|
- If `blob:` workers are prohibited by policy, either serve `highs-worker.js` from `'self'` or run on the main thread with `await m.solve({ useWorker: false })`.
|
|
200
230
|
3. **Cross-Origin Isolation (Z3 Only)**:
|
|
201
231
|
- HiGHS and GLPK run in standard browser contexts without special headers.
|
|
202
|
-
- Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## ⚡ Bun & Deno
|
|
207
|
-
|
|
208
|
-
### Bun
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
bun add @ohtaman/washi
|
|
212
|
-
bun run app.ts
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Deno
|
|
216
|
-
|
|
217
|
-
In Deno (v1.30+ with `npm:` specifiers):
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
import { Model, maximize } from "npm:@ohtaman/washi";
|
|
221
|
-
|
|
222
|
-
const m = new Model();
|
|
223
|
-
const x = m.continuous("x", { lb: 0, ub: 10 });
|
|
224
|
-
m.objective(maximize(x));
|
|
225
|
-
await m.solve();
|
|
226
|
-
console.log("x =", x.value);
|
|
227
|
-
```
|
|
232
|
+
- Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (`credentialless` works in Chromium/Firefox, whereas Safari requires `require-corp`). See the [Cross-Origin Isolation Guide](./cross-origin.md) for setup details.
|