@ohtaman/washi 0.3.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.
Files changed (97) hide show
  1. package/README.ja.md +111 -118
  2. package/README.md +109 -116
  3. package/README.zh-CN.md +112 -119
  4. package/dist/browser/highs-worker.js +21 -3
  5. package/dist/browser/loader-CtaDHef_.js +273 -0
  6. package/dist/browser/solvers/glpk.js +433 -286
  7. package/dist/browser/solvers/glpk.min.js +1 -1
  8. package/dist/browser/solvers/z3.js +1002 -981
  9. package/dist/browser/solvers/z3.min.js +2 -2
  10. package/dist/browser/washi.js +2178 -622
  11. package/dist/browser/washi.min.js +4 -4
  12. package/dist/lib/highs-worker.js +21 -3
  13. package/dist/lib/loader-DmNC_UNk.js +279 -0
  14. package/dist/lib/registry-BEfvpZW3.js +951 -0
  15. package/dist/lib/solvers/glpk.js +201 -90
  16. package/dist/lib/solvers/z3.js +399 -259
  17. package/dist/lib/washi.js +1674 -932
  18. package/dist/types/index.d.ts +4 -4
  19. package/dist/types/index.d.ts.map +1 -1
  20. package/dist/types/modeler/big-m.d.ts +75 -0
  21. package/dist/types/modeler/big-m.d.ts.map +1 -0
  22. package/dist/types/modeler/boolean-expr.d.ts +103 -14
  23. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  24. package/dist/types/modeler/constraint.d.ts +36 -9
  25. package/dist/types/modeler/constraint.d.ts.map +1 -1
  26. package/dist/types/modeler/expression.d.ts +117 -27
  27. package/dist/types/modeler/expression.d.ts.map +1 -1
  28. package/dist/types/modeler/index.d.ts +10 -6
  29. package/dist/types/modeler/index.d.ts.map +1 -1
  30. package/dist/types/modeler/lp-writer.d.ts +27 -2
  31. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  32. package/dist/types/modeler/model.d.ts +491 -140
  33. package/dist/types/modeler/model.d.ts.map +1 -1
  34. package/dist/types/modeler/objective.d.ts +59 -0
  35. package/dist/types/modeler/objective.d.ts.map +1 -0
  36. package/dist/types/modeler/parser.d.ts +39 -14
  37. package/dist/types/modeler/parser.d.ts.map +1 -1
  38. package/dist/types/modeler/quad-expr.d.ts +28 -7
  39. package/dist/types/modeler/quad-expr.d.ts.map +1 -1
  40. package/dist/types/modeler/solution-validator.d.ts +93 -0
  41. package/dist/types/modeler/solution-validator.d.ts.map +1 -0
  42. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
  43. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  44. package/dist/types/modeler/solvers/highs/loader.d.ts +36 -11
  45. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  46. package/dist/types/modeler/solvers/highs/solver.d.ts +11 -4
  47. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  48. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  49. package/dist/types/modeler/solvers/mock.d.ts +14 -5
  50. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  51. package/dist/types/modeler/solvers/types.d.ts +71 -11
  52. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  53. package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
  54. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  55. package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
  56. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
  57. package/dist/types/modeler/types.d.ts +60 -2
  58. package/dist/types/modeler/types.d.ts.map +1 -1
  59. package/dist/types/modeler/validation.d.ts +56 -0
  60. package/dist/types/modeler/validation.d.ts.map +1 -0
  61. package/dist/types/modeler/variable.d.ts +53 -13
  62. package/dist/types/modeler/variable.d.ts.map +1 -1
  63. package/docs/examples/diet-sensitivity.md +90 -0
  64. package/docs/examples/ecommerce-coupons.md +106 -0
  65. package/docs/examples/index.md +53 -0
  66. package/docs/examples/knapsack.md +60 -0
  67. package/docs/examples/portfolio-qp.md +83 -0
  68. package/docs/examples/production.md +51 -0
  69. package/docs/examples/shift-scheduling.md +175 -0
  70. package/docs/examples/smt-security.md +74 -0
  71. package/docs/examples/transportation.md +90 -0
  72. package/docs/examples/unsat-core.md +64 -0
  73. package/docs/getting-started/ai-agents.md +267 -0
  74. package/docs/getting-started/cross-origin.md +87 -0
  75. package/docs/getting-started/index.md +116 -0
  76. package/docs/getting-started/runtimes.md +227 -0
  77. package/docs/guide/constraints.md +177 -0
  78. package/docs/guide/expressions.md +204 -0
  79. package/docs/guide/objectives.md +170 -0
  80. package/docs/guide/unsat-core.md +147 -0
  81. package/docs/guide/variables.md +188 -0
  82. package/docs/ja/getting-started/ai-agents.md +266 -0
  83. package/docs/solvers/custom-solver.md +525 -0
  84. package/docs/solvers/glpk.md +66 -0
  85. package/docs/solvers/highs.md +93 -0
  86. package/docs/solvers/index.md +42 -0
  87. package/docs/solvers/z3.md +78 -0
  88. package/docs/zh/getting-started/ai-agents.md +266 -0
  89. package/llms-full.txt +3249 -0
  90. package/llms.txt +197 -0
  91. package/package.json +19 -6
  92. package/skills/washi/SKILL.md +197 -0
  93. package/dist/browser/loader-CVt4CloI.js +0 -58
  94. package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
  95. package/dist/lib/constraint-BRuB3ooC.js +0 -90
  96. package/dist/lib/loader-Dti0v9yC.js +0 -50
  97. package/dist/lib/registry-DksqCSjI.js +0 -32
@@ -0,0 +1,90 @@
1
+ # Transportation & Allocation Network (LP)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Linear Programming (LP) | HiGHS (default) | `m.continuous()`, `m.constraint()`, `expr`, `minimize`, `dot` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=transportation">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/09-transportation-problem.ts">Source Code</a></li></ul> |
6
+
7
+ The transportation problem is one of the most fundamental network optimization models: determine how many units to ship from each supply origin (factory) to each destination (store) to minimize total shipping cost while respecting factory supply limits and meeting store demands.
8
+
9
+ This example demonstrates how to construct a **2D matrix of decision variables** using `m.continuous(factories, stores)` and write aggregated constraints declaratively via `dot()` and `m.constraint(expr`...`)`.
10
+
11
+ ## Problem Inputs
12
+
13
+ We have **3 factories** (`Tokyo`, `Nagoya`, `Osaka`) shipping to **4 regional stores** (`North`, `East`, `West`, `South`):
14
+
15
+ | Factory \ Store | North ($/unit) | East ($/unit) | West ($/unit) | South ($/unit) | Supply Capacity |
16
+ | :--- | :---: | :---: | :---: | :---: | :---: |
17
+ | **Tokyo** | $4 | $2 | $7 | $8 | **120** units |
18
+ | **Nagoya** | $6 | $4 | $3 | $5 | **100** units |
19
+ | **Osaka** | $9 | $7 | $2 | $3 | **80** units |
20
+ | **Store Demand** | **70** units | **90** units | **60** units | **60** units | Total: 280 / 300 |
21
+
22
+ ## Mathematical Formulation
23
+
24
+ Let $x_{f, s} \ge 0$ be the quantity shipped from factory $f \in F$ to store $s \in S$, and $c_{f, s}$ be the unit shipping cost.
25
+
26
+ $$\begin{aligned}
27
+ \text{Minimize} \quad & \sum_{f \in F} \sum_{s \in S} c_{f,s} \, x_{f,s} \\
28
+ \text{Subject to} \quad & \sum_{s \in S} x_{f,s} \le \text{Supply}_f \quad \forall f \in F \quad (\text{Factory capacity}) \\
29
+ & \sum_{f \in F} x_{f,s} \ge \text{Demand}_s \quad \forall s \in S \quad (\text{Store demand}) \\
30
+ & x_{f,s} \ge 0 \quad \forall f \in F, s \in S
31
+ \end{aligned}$$
32
+
33
+ ## Implementation in WaSHI
34
+
35
+ <CodeRunner preset="transportation">
36
+
37
+ ```typescript
38
+ import { Model, sum, dot, minimize, expr } from "@ohtaman/washi";
39
+
40
+ const factories = ["Tokyo", "Nagoya", "Osaka"] as const;
41
+ const stores = ["North", "East", "West", "South"] as const;
42
+
43
+ const shippingCost = {
44
+ Tokyo: { North: 4, East: 2, West: 7, South: 8 },
45
+ Nagoya: { North: 6, East: 4, West: 3, South: 5 },
46
+ Osaka: { North: 9, East: 7, West: 2, South: 3 },
47
+ };
48
+
49
+ const supplies = { Tokyo: 120, Nagoya: 100, Osaka: 80 };
50
+ const demands = { North: 70, East: 90, West: 60, South: 60 };
51
+
52
+ async function runTransportation() {
53
+ const m = new Model("TransportationNetwork");
54
+
55
+ // 1. Create 2D continuous variables: ship[factory][store] >= 0
56
+ const ship = m.continuous([...factories], [...stores], { lb: 0 });
57
+
58
+ // 2. Objective: Minimize total shipping cost across all routes via 2D dot product
59
+ m.objective(minimize(dot(shippingCost, ship)));
60
+
61
+ // 3. Supply constraints (row sums): outgoing from f <= supplies[f]
62
+ for (const f of factories) {
63
+ m.constraint(
64
+ expr`${sum(stores, (s) => ship[f][s])} <= ${supplies[f]}`,
65
+ `supply_${f}`
66
+ );
67
+ }
68
+
69
+ // 4. Demand constraints (column sums): incoming to s >= demands[s]
70
+ for (const s of stores) {
71
+ m.constraint(
72
+ expr`${sum(factories, (f) => ship[f][s])} >= ${demands[s]}`,
73
+ `demand_${s}`
74
+ );
75
+ }
76
+
77
+ const status = await m.solve();
78
+ console.log(`Status: ${status}, Total Shipping Cost: $${m.objectiveValue}`);
79
+ for (const f of factories) {
80
+ const routes = stores
81
+ .map((s) => `${s}:${Math.round(ship[f][s].value ?? 0)}`)
82
+ .join(", ");
83
+ console.log(` ${f.padEnd(7)} -> ${routes}`);
84
+ }
85
+ }
86
+
87
+ runTransportation();
88
+ ```
89
+
90
+ </CodeRunner>
@@ -0,0 +1,64 @@
1
+ # Conflict Diagnosis (Unsat Core)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Infeasibility Diagnosis / Unsat Core | Z3 (`@ohtaman/washi/solvers/z3`) | `m.integer()`, `m.continuous()`, `m.constraint()`, `expr`, `m.check()`, `m.conflicts` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=conflict_diagnosis_unsat">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts">Source Code</a></li></ul> |
6
+
7
+ When business rules, budgets, and operational policies grow complex, constraints inevitably conflict. Traditional solvers simply report "Infeasible", leaving engineers to guess which of hundreds of rules caused the contradiction.
8
+
9
+ This example shows how to isolate the conflicting constraint subset using Z3 Unsat Core extraction.
10
+
11
+ ## The Conflicting Scenario
12
+
13
+ A tech team is staffing a customer project:
14
+ 1. Each engineer costs $8,000 against the project budget (`budget_limit`).
15
+ 2. The customer requires rush delivery (`rush_required`).
16
+ 3. Rush delivery mandates at least 8 engineers (`rush_staffing_rule`).
17
+ 4. The board has imposed a strict budget cap of $50,000 (`strict_board_budget_cap`).
18
+ 5. Labor law requires at least 1 employee on staff (`minimum_one_employee`).
19
+
20
+ Notice that:
21
+
22
+ $$8 \text{ engineers} \times \$8,000 = \$64,000 > \$50,000 \text{ budget!}$$
23
+
24
+ ## Implementation in WaSHI
25
+
26
+ <CodeRunner preset="conflict_diagnosis_unsat">
27
+
28
+ ```typescript
29
+ import { Model, expr } from "@ohtaman/washi";
30
+ import "@ohtaman/washi/solvers/z3";
31
+
32
+ async function diagnoseConflicts() {
33
+ const m = new Model({ name: "ConflictDiagnosis", solver: "z3" });
34
+
35
+ const engineers = m.integer("engineers", { lb: 0, ub: 20 });
36
+ const budget = m.continuous("budget", { lb: 0, ub: 100000 });
37
+ const rushDelivery = m.bool("rushDelivery");
38
+
39
+ // Rule 1: Each engineer costs $8,000
40
+ m.constraint(expr`8000 * ${engineers} <= ${budget}`, "budget_limit");
41
+
42
+ // Rule 2: Customer requires rush delivery
43
+ m.constraint(rushDelivery, "rush_required");
44
+
45
+ // Rule 3: Rush delivery requires at least 8 engineers
46
+ m.constraint(rushDelivery.implies(engineers.ge(8)), "rush_staffing_rule");
47
+
48
+ // Rule 4: Board budget cap of $50,000 (Conflict!)
49
+ m.constraint(expr`${budget} <= 50000`, "strict_board_budget_cap");
50
+
51
+ // Rule 5: Innocent bystander rule
52
+ m.constraint(expr`${engineers} >= 1`, "minimum_one_employee");
53
+
54
+ // Run diagnosis
55
+ const res = await m.check({ solver: "z3" });
56
+
57
+ console.log(`Status: ${res.status}`);
58
+ console.log("Conflicting Constraints (conflicts):", m.conflicts);
59
+ }
60
+
61
+ diagnoseConflicts();
62
+ ```
63
+
64
+ </CodeRunner>
@@ -0,0 +1,267 @@
1
+ # AI Coding Agent Quick-Reference Guide
2
+
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
+ > - **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
+ > ```bash
6
+ > # Link or copy the packaged skill into your project's agent skills directory
7
+ > mkdir -p .agents/skills/washi
8
+ > cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
9
+ > ```
10
+ > - **Repository Review Guidelines**: For repository contribution and pull-request review rules, see [`AGENTS.md`](https://github.com/ohtaman/washi/blob/main/AGENTS.md) in the repository root.
11
+
12
+ ---
13
+
14
+ ## 1. Package Imports & Deployment
15
+
16
+ ### Node.js / Bundlers (Vite, Webpack, Next.js, Bun)
17
+
18
+ ```ts
19
+ import {
20
+ Model,
21
+ expr,
22
+ sum,
23
+ dot,
24
+ maximize,
25
+ minimize,
26
+ and,
27
+ or,
28
+ not,
29
+ implies,
30
+ xor,
31
+ setHighsWasmUrl,
32
+ } from "@ohtaman/washi";
33
+
34
+ // Optional solver plugins (require installing peer dependencies first):
35
+ // npm install z3-solver
36
+ import "@ohtaman/washi/solvers/z3";
37
+ // npm install glpk.js
38
+ import "@ohtaman/washi/solvers/glpk";
39
+ ```
40
+
41
+ ### Browser ESM & Classic Script Tags
42
+
43
+ - **Browser ESM**:
44
+ - Core (HiGHS built-in): `import { Model, expr, maximize } from "./washi/washi.js";`
45
+ - GLPK plugin: `import "./washi/solvers/glpk.js";`
46
+ - Z3 plugin: `import "./washi/solvers/z3.js";`
47
+ - **Classic `<script>` / CodePen (IIFE)**:
48
+ - Core: `<script src="./washi/washi.min.js"></script>` (exposes global `Washi`)
49
+ - GLPK plugin: `<script src="./washi/solvers/glpk.min.js"></script>` (registers `"glpk"`, exposes `WashiGLPK`)
50
+ - Z3 plugin: `<script src="./washi/solvers/z3.min.js"></script>` (registers `"z3"`, exposes `WashiZ3`)
51
+
52
+ ### WebAssembly Asset & Runtime Rules
53
+
54
+ 1. **Bundled WASM by default (no implicit CDN fallback)**:
55
+ - In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
56
+ - 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 the local Node `highs` package runtime).
58
+ 2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
59
+ - 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
+ - `Cross-Origin-Opener-Policy: same-origin` (on the document)
61
+ - `Cross-Origin-Embedder-Policy: require-corp` (on the document)
62
+ - `Cross-Origin-Resource-Policy: cross-origin` and `Access-Control-Allow-Origin` (on cross-origin script/WASM asset responses)
63
+ - 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
+ 3. **Web Workers & Cancellation (`AbortSignal`)**:
65
+ - 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).
67
+ - 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
+
69
+ ---
70
+
71
+ ## 2. Canonical Modeling Workflow
72
+
73
+ Formulating and solving a model consists of four distinct steps:
74
+
75
+ ### Step 1: Create Model & Decision Variables
76
+
77
+ ```ts
78
+ const m = new Model("ExampleModel");
79
+
80
+ // Scalar variables
81
+ const x = m.continuous("x", { lb: 0, ub: 100 });
82
+ const n = m.integer("n", { lb: 0, ub: 10 });
83
+ const b = m.binary("b");
84
+ const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
85
+
86
+ // Indexed variable containers:
87
+ // - Numeric dimension -> Array
88
+ // - Key-array dimension -> Record
89
+ const vec = m.continuous(4, { lb: 0, prefix: "v" }); // Var[] (length 4)
90
+ const grid = m.binary(2, 3, { prefix: "g" }); // Var[][] (2x3)
91
+ const byItem = m.integer(["desk", "chair"] as const, { lb: 0 }); // Record<"desk" | "chair", Var>
92
+ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
93
+ ```
94
+
95
+ - **Optional Primal Initial Values (`initialValue?: number`)**:
96
+ - Numeric variable constructors (`m.continuous`, `m.integer`, `m.binary`, and `new Var`) and the mutable `v.initialValue` property accept an optional finite primal hint (validated with tolerance `1e-6` against bounds `[lb, ub]` and integer/binary domains), kept distinct from the solved `v.value`.
97
+ - A start may be complete or partial (e.g., transferred by stable variable names when rebuilding a MIP model). Supplying `initialValue` is a hint only—it does **not** fix the variable, add a hard constraint, or guarantee solver acceptance or a runtime speedup.
98
+ - Supported by **HiGHS** (`supportsWarmStart: true`); solvers without warm-start support (**GLPK**, **Z3**) throw an error if any variable has `initialValue !== undefined`.
99
+
100
+ ### Step 2: Build Expressions (`expr`, `sum`, `dot`)
101
+
102
+ - **Unified `expr` template tag**:
103
+ - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
104
+ - Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${n} + ${n}^2` `` (variable-by-variable products and squares are supported for convex QP objectives).
105
+ - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
106
+ - **Aggregations**:
107
+ - `sum(iterable, mapper?)` sums an `Iterable` of `Var` / `LinExpr` / `number` (e.g., `Var[]`, `grid.flat()`, or `Object.values(record)`).
108
+ - `dot(a, b)` computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
109
+ - **Fluent methods**:
110
+ - Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
111
+ - Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
112
+ - Boolean logic (on `BoolVar`, `BoolExpr`, and `Constraint` for Z3): standalone `and(...)`, `or(...)`, `not(...)`, `implies(a, b)`, `xor(a, b)` and fluent `.and(...)`, `.or(...)`, `.not()`, `.implies(...)`, `.xor(...)`
113
+
114
+ ### Step 3: Register Objective & Constraints on the Model
115
+
116
+ Constructing an `expr` or `.le()` object does **not** attach it to the model until you pass it to `m.constraint(...)` or `m.objective(...)`:
117
+
118
+ ```ts
119
+ // Set objective using maximize() or minimize()
120
+ m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
121
+
122
+ // Register constraints on the model
123
+ m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
124
+
125
+ // Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
126
+ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
127
+ ```
128
+
129
+ ### Step 4: Execute (`solve` or `check`) & Verify (`validateSolution`)
130
+
131
+ - **Optimization (`await m.solve(options?)`)**:
132
+ - 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
+ - Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
134
+ - 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 & Unsat Core (`await m.check(options?)`)**:
136
+ - Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` and `unsatCore` are optional and populated only when an unsat-core-capable solver (such as Z3) returns `"Unsat"`.
137
+ - For models containing only linear arithmetic constraints, `m.check()` works with any solver (including the default HiGHS and GLPK) by falling back to a feasibility `solve()`. Logical constraints (`BoolVar`, `and`, `or`, `implies`, etc.) and unsatisfiable-core extraction (`result.conflicts` / `result.unsatCore`) require an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `conflicts` / `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
+ - **No concurrent mutation during async solve/check**:
139
+ - 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
+
141
+ ---
142
+
143
+ ## 3. Executable Examples
144
+
145
+ ### Example A: Mixed-Integer Linear Programming (MIP) with Indexed Containers & Solution Validation
146
+
147
+ ```ts
148
+ import { Model, expr, dot, sum, maximize } from "@ohtaman/washi";
149
+
150
+ export async function runMipExample() {
151
+ const m = new Model("ProductionMIP");
152
+ const products = ["desk", "chair"] as const;
153
+ const profit = { desk: 30, chair: 50 };
154
+ const woodPerUnit = { desk: 1, chair: 2 };
155
+ const laborPerUnit = { desk: 2, chair: 1 };
156
+
157
+ const qty = m.integer(products, { lb: 0, ub: 100 });
158
+ const setup = m.binary("setup");
159
+
160
+ m.objective(maximize(expr`${dot(profit, qty)} - 20 * ${setup}`));
161
+ m.constraint(expr`${dot(woodPerUnit, qty)} <= 40`, "wood_cap");
162
+ m.constraint(expr`${dot(laborPerUnit, qty)} <= 50`, "labor_cap");
163
+ m.constraint(expr`${sum(Object.values(qty))} <= 30 * ${setup}`, "require_setup");
164
+
165
+ const status = await m.solve();
166
+ const report = m.validateSolution();
167
+ const values = m.getValues(qty);
168
+
169
+ return {
170
+ status,
171
+ objectiveValue: m.objectiveValue,
172
+ values,
173
+ setup: setup.value,
174
+ valid: report.valid,
175
+ maxViolation: report.maxViolation,
176
+ };
177
+ }
178
+ ```
179
+
180
+ ### Example B: Convex Quadratic Programming (QP)
181
+
182
+ Variable-by-variable products (`${x} * ${y}`) and squares (`${x}^2`) are supported in `expr` for quadratic objectives:
183
+
184
+ ```ts
185
+ import { Model, expr, minimize } from "@ohtaman/washi";
186
+
187
+ export async function runQpExample() {
188
+ const m = new Model("PortfolioQP");
189
+ const x = m.continuous("x", { lb: 0 });
190
+ const y = m.continuous("y", { lb: 0 });
191
+
192
+ // Minimize x^2 + y^2 subject to x + y >= 4
193
+ m.objective(minimize(expr`${x}^2 + ${y}^2`));
194
+ m.constraint(expr`${x} + ${y} >= 4`, "budget");
195
+
196
+ const status = await m.solve();
197
+ const report = m.validateSolution();
198
+
199
+ return {
200
+ status,
201
+ objectiveValue: m.objectiveValue,
202
+ x: x.value,
203
+ y: y.value,
204
+ valid: report.valid,
205
+ };
206
+ }
207
+ ```
208
+
209
+ ### Example C: Z3 Feasibility Check & Unsat Core Diagnosis
210
+
211
+ ```ts
212
+ import { Model, expr, implies } from "@ohtaman/washi";
213
+ import "@ohtaman/washi/solvers/z3";
214
+
215
+ export async function runZ3Example() {
216
+ const m = new Model("PolicyCheck", { solver: "z3" });
217
+ const vip = m.bool("vip");
218
+ const discount = m.integer("discount", { lb: 0, ub: 50 });
219
+
220
+ m.constraint(vip, "customer_is_vip");
221
+ m.constraint(implies(vip, expr`${discount} >= 20`), "vip_min_discount");
222
+ m.constraint(expr`${discount} <= 10`, "campaign_cap");
223
+
224
+ const unsatResult = await m.check();
225
+ return {
226
+ status: unsatResult.status,
227
+ conflicts: unsatResult.conflicts,
228
+ };
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 4. Negative Examples & Common Pitfalls
235
+
236
+ Avoid these invalid patterns and retired aliases:
237
+
238
+ ```ts
239
+ import { Model, expr, maximize } from "@ohtaman/washi";
240
+
241
+ export function verifyNegativePatterns() {
242
+ const m = new Model("NegativeExamples");
243
+ const x = m.continuous("x", { lb: 0 });
244
+ const y = m.continuous("y", { lb: 0 });
245
+
246
+ // 1. DO NOT pass a Constraint to m.objective() or arithmetic methods:
247
+ // WRONG: m.objective(maximize(expr`${x} <= 10` as any)); -> throws Error
248
+ // WRONG: expr`${x} + 1`.add(expr`${y} <= 5` as any); -> throws Error
249
+
250
+ // 2. DO NOT pass an arithmetic expression without (<=, >=, ==) to m.constraint():
251
+ // WRONG: m.constraint(expr`${x} + ${y}` as any); -> throws Error
252
+
253
+ // 3. DO NOT use quadratic terms inside linear constraints:
254
+ // WRONG: expr`${x}^2 <= 10`; -> throws Error ("Quadratic constraints are not supported")
255
+
256
+ // 4. DO NOT use retired aliases (removed in #59 / #81):
257
+ // - Use `expr` instead of retired `c` template tag
258
+ // - Use `m.constraint(...)` instead of `m.constrain(...)` or `m.add(...)`
259
+ // - Use `m.objective(maximize(...))` / `m.objective(minimize(...))` instead of `m.maximize(...)` / `m.minimize(...)` / `m.setObjective(...)`
260
+ // - Use `await m.solve()` instead of `await m.optimize()`
261
+ // - Use `.le()`, `.ge()`, `.eq()` instead of `.lessThanOrEqual()`, `.greaterThanOrEqual()`, `.equalTo()`
262
+ // - Use `.value` or `m.getValues(...)` instead of `expr.getValue()`
263
+ // - Use `m.variables` / `m.constraints` instead of `m.vars` / `m.constrs`
264
+ return { m, x, y, maximize };
265
+ }
266
+ ```
267
+
@@ -0,0 +1,87 @@
1
+ # Cross-Origin Isolation (Z3 & Browsers)
2
+
3
+ When running the **Z3** solver engine inside a web browser, special HTTP headers are required due to browser security models.
4
+
5
+ ---
6
+
7
+ ## Why is Cross-Origin Isolation Required?
8
+
9
+ The Z3 WebAssembly engine utilizes multithreading backed by `SharedArrayBuffer` and Web Workers.
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
+
13
+ ---
14
+
15
+ ## Required HTTP Response Headers
16
+
17
+ To establish cross-origin isolation, your web server must serve the top-level HTML document with the following HTTP headers:
18
+
19
+ ```http
20
+ Cross-Origin-Opener-Policy: same-origin
21
+ Cross-Origin-Embedder-Policy: require-corp
22
+ ```
23
+
24
+ In addition, any cross-origin assets (e.g. scripts or WASM files loaded from external domains) must send:
25
+
26
+ ```http
27
+ Cross-Origin-Resource-Policy: cross-origin
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Solutions by Hosting Environment
33
+
34
+ ### 1. Custom Node.js / Express Server
35
+
36
+ ```javascript
37
+ app.use((req, res, next) => {
38
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
39
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
40
+ next();
41
+ });
42
+ ```
43
+
44
+ ### 2. Next.js (`next.config.js`)
45
+
46
+ ```javascript
47
+ /** @type {import('next').NextConfig} */
48
+ const nextConfig = {
49
+ async headers() {
50
+ return [
51
+ {
52
+ source: "/(.*)",
53
+ headers: [
54
+ { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
55
+ { key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
56
+ ],
57
+ },
58
+ ];
59
+ },
60
+ };
61
+
62
+ module.exports = nextConfig;
63
+ ```
64
+
65
+ ### 3. Static Hosting without Header Control (e.g., GitHub Pages)
66
+
67
+ Platforms like GitHub Pages or preview environments do not allow customizing response headers directly.
68
+
69
+ For these environments, you can use **`coi-serviceworker`** (a lightweight Service Worker that injects isolation headers on the client side):
70
+
71
+ 1. Place `coi-serviceworker.js` in your website root.
72
+ 2. In your HTML `<head>`, include:
73
+
74
+ ```html
75
+ <script src="/coi-serviceworker.js"></script>
76
+ ```
77
+
78
+ When a user visits for the first time, the service worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
79
+
80
+ ---
81
+
82
+ ## Graceful Degradation in WaSHI
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.
85
+
86
+ > [!TIP]
87
+ > The default **HiGHS** solver engine does not require cross-origin isolation or `SharedArrayBuffer`. It works out of the box in standard browser contexts.
@@ -0,0 +1,116 @@
1
+ # Introduction & Installation
2
+
3
+ Welcome to **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface).
4
+
5
+ WaSHI brings the algebraic modeling style of Python's **PuLP** and Julia's **JuMP** to the TypeScript and JavaScript ecosystem. You can formulate mathematical optimization problems using algebraic expressions and solve them locally using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**)—without a separate Python service or native C/C++ build steps.
6
+
7
+ ### Why TypeScript & WebAssembly?
8
+
9
+ - **Removing the deployment bottleneck**: In traditional optimization workflows (e.g., Python with PuLP or Pyomo), turning a model into an interactive application requires provisioning a backend service and managing native C/C++ solver binaries. As AI-assisted development accelerates UI and application prototyping, backend infrastructure often becomes the primary bottleneck to shipping. Running both the modeler and WebAssembly solvers on standard Web technologies allows an optimization tool to be deployed as static assets (e.g., on GitHub Pages or Cloudflare Pages, serving the generated JS, Worker, and WASM files; with COOP/COEP headers when using Z3)—and makes it straightforward to pair mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) entirely on the client.
10
+ - **Algebraic syntax without operator overloading (`expr`)**: Because JavaScript lacks operator overloading, libraries usually trade off between verbose method chaining (`x.mul(30).add(y.mul(50))`) and untyped strings (`"30 * x + 50 * y"`). WaSHI uses tagged template literals (`` expr`30 * ${x} + 50 * ${y} <= 40` ``) similar to parameterized SQL queries—parsing infix operators while binding `${x}` directly as typed TypeScript object references for static type checking, IDE symbol renaming, and multi-dimensional indexing (`${flow[i][j]}`).
11
+
12
+ ---
13
+
14
+ ## 📦 Installation
15
+
16
+ Install WaSHI using your preferred package manager:
17
+
18
+ ::: code-group
19
+
20
+ ```bash [npm]
21
+ npm install @ohtaman/washi
22
+ ```
23
+
24
+ ```bash [pnpm]
25
+ pnpm add @ohtaman/washi
26
+ ```
27
+
28
+ ```bash [yarn]
29
+ yarn add @ohtaman/washi
30
+ ```
31
+
32
+ ```bash [bun]
33
+ bun add @ohtaman/washi
34
+ ```
35
+
36
+ :::
37
+
38
+ ### Optional Solver Dependencies
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:
41
+
42
+ ```bash
43
+ # For SMT logic and Unsat Core conflict diagnosis
44
+ npm install z3-solver
45
+
46
+ # For GLPK solver (GPL-3.0)
47
+ npm install glpk.js
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 🚀 Quickstart Walkthrough
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
69
+
70
+ Variables represent the unknowns you want to determine. Factory methods (`m.continuous`, `m.integer`, `m.binary`) clearly define their domain:
71
+
72
+ ```typescript
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
96
+
97
+ Solve the model asynchronously via WebAssembly (HiGHS by default):
98
+
99
+ ```typescript
100
+ const status = await model.solve();
101
+
102
+ console.log("Status:", status); // "Optimal"
103
+ console.log("Optimal Objective Value:", model.objectiveValue); // 1100
104
+ console.log(`Solution: x = ${x.value}, y = ${y.value}`); // x = 20, y = 10
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 🧭 Next Steps
110
+
111
+ - Give AI coding agents a concise API & deployment reference with the [AI Coding Agent Quick-Reference Guide](./ai-agents.md).
112
+ - Learn how to run WaSHI across different environments in [Runtime Environments](./runtimes.md).
113
+ - Understand how to formulate complex expressions in the [Modeling Guide](../guide/expressions.md).
114
+ - Explore built-in and pluggable solvers in the [Solvers Matrix](../solvers/).
115
+ - Browse 9 runnable optimization and SMT recipes in [Examples & API Finder](../examples/).
116
+