@ohtaman/washi 0.4.0 → 0.6.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 (130) hide show
  1. package/README.ja.md +149 -121
  2. package/README.md +149 -121
  3. package/README.zh-CN.md +150 -122
  4. package/THIRD_PARTY_LICENSES.md +3 -4
  5. package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
  6. package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
  7. package/dist/browser/highs-worker.js +21 -3
  8. package/dist/browser/highs.wasm +0 -0
  9. package/dist/browser/highs.wasm.LICENSE +1 -2
  10. package/dist/browser/loader-DewlSho8.js +643 -0
  11. package/dist/browser/solvers/glpk.js +433 -286
  12. package/dist/browser/solvers/glpk.min.js +1 -1
  13. package/dist/browser/solvers/z3.js +1008 -981
  14. package/dist/browser/solvers/z3.min.js +2 -2
  15. package/dist/browser/washi.js +2778 -673
  16. package/dist/browser/washi.min.js +3 -4
  17. package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
  18. package/dist/lib/highs-browser-runtime.js +2120 -4124
  19. package/dist/lib/highs-node-runtime.js +24 -1
  20. package/dist/lib/highs-runtime.js +2 -0
  21. package/dist/lib/highs-worker.js +21 -3
  22. package/dist/lib/highs.wasm +0 -0
  23. package/dist/lib/highs.wasm.LICENSE +1 -2
  24. package/dist/lib/loader-BZUQNvh4.js +649 -0
  25. package/dist/lib/registry-C20sxTwm.js +964 -0
  26. package/dist/lib/solvers/glpk.js +201 -90
  27. package/dist/lib/solvers/z3.js +399 -259
  28. package/dist/lib/washi.js +2363 -1056
  29. package/dist/types/decomposition/benders.d.ts +32 -0
  30. package/dist/types/decomposition/benders.d.ts.map +1 -0
  31. package/dist/types/decomposition/column-generation.d.ts +34 -0
  32. package/dist/types/decomposition/column-generation.d.ts.map +1 -0
  33. package/dist/types/decomposition/index.d.ts +4 -0
  34. package/dist/types/decomposition/index.d.ts.map +1 -0
  35. package/dist/types/decomposition/types.d.ts +230 -0
  36. package/dist/types/decomposition/types.d.ts.map +1 -0
  37. package/dist/types/index.d.ts +5 -4
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/modeler/big-m.d.ts +75 -0
  40. package/dist/types/modeler/big-m.d.ts.map +1 -0
  41. package/dist/types/modeler/boolean-expr.d.ts +103 -14
  42. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  43. package/dist/types/modeler/constraint.d.ts +47 -9
  44. package/dist/types/modeler/constraint.d.ts.map +1 -1
  45. package/dist/types/modeler/expression.d.ts +123 -28
  46. package/dist/types/modeler/expression.d.ts.map +1 -1
  47. package/dist/types/modeler/index.d.ts +11 -6
  48. package/dist/types/modeler/index.d.ts.map +1 -1
  49. package/dist/types/modeler/linearizer.d.ts +15 -0
  50. package/dist/types/modeler/linearizer.d.ts.map +1 -0
  51. package/dist/types/modeler/lp-writer.d.ts +30 -3
  52. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  53. package/dist/types/modeler/model.d.ts +623 -131
  54. package/dist/types/modeler/model.d.ts.map +1 -1
  55. package/dist/types/modeler/objective.d.ts +59 -0
  56. package/dist/types/modeler/objective.d.ts.map +1 -0
  57. package/dist/types/modeler/parser.d.ts +34 -10
  58. package/dist/types/modeler/parser.d.ts.map +1 -1
  59. package/dist/types/modeler/quad-expr.d.ts +26 -7
  60. package/dist/types/modeler/quad-expr.d.ts.map +1 -1
  61. package/dist/types/modeler/solution-validator.d.ts +93 -0
  62. package/dist/types/modeler/solution-validator.d.ts.map +1 -0
  63. package/dist/types/modeler/solvers/glpk/solver.d.ts +26 -2
  64. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  65. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
  66. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
  67. package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
  68. package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
  69. package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
  70. package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
  71. package/dist/types/modeler/solvers/highs/loader.d.ts +38 -13
  72. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  73. package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
  74. package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -1
  75. package/dist/types/modeler/solvers/highs/solver.d.ts +12 -5
  76. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  77. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  78. package/dist/types/modeler/solvers/mock.d.ts +14 -5
  79. package/dist/types/modeler/solvers/mock.d.ts.map +1 -1
  80. package/dist/types/modeler/solvers/types.d.ts +73 -11
  81. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  82. package/dist/types/modeler/solvers/z3/loader.d.ts +2 -3
  83. package/dist/types/modeler/solvers/z3/loader.d.ts.map +1 -1
  84. package/dist/types/modeler/solvers/z3/solver.d.ts +15 -5
  85. package/dist/types/modeler/solvers/z3/solver.d.ts.map +1 -1
  86. package/dist/types/modeler/types.d.ts +86 -1
  87. package/dist/types/modeler/types.d.ts.map +1 -1
  88. package/dist/types/modeler/validation.d.ts +56 -0
  89. package/dist/types/modeler/validation.d.ts.map +1 -0
  90. package/dist/types/modeler/variable.d.ts +48 -14
  91. package/dist/types/modeler/variable.d.ts.map +1 -1
  92. package/docs/examples/diet-sensitivity.md +90 -0
  93. package/docs/examples/ecommerce-coupons.md +106 -0
  94. package/docs/examples/index.md +57 -0
  95. package/docs/examples/knapsack.md +60 -0
  96. package/docs/examples/portfolio-qp.md +83 -0
  97. package/docs/examples/production.md +51 -0
  98. package/docs/examples/shift-scheduling.md +175 -0
  99. package/docs/examples/smt-security.md +74 -0
  100. package/docs/examples/transportation.md +90 -0
  101. package/docs/examples/traveling-salesperson.md +173 -0
  102. package/docs/examples/unsat-core.md +64 -0
  103. package/docs/examples/vehicle-routing.md +206 -0
  104. package/docs/getting-started/ai-agents.md +271 -0
  105. package/docs/getting-started/cross-origin.md +87 -0
  106. package/docs/getting-started/index.md +116 -0
  107. package/docs/getting-started/runtimes.md +227 -0
  108. package/docs/guide/constraints.md +177 -0
  109. package/docs/guide/decomposition.md +399 -0
  110. package/docs/guide/expressions.md +204 -0
  111. package/docs/guide/objectives.md +170 -0
  112. package/docs/guide/unsat-core.md +154 -0
  113. package/docs/guide/variables.md +188 -0
  114. package/docs/ja/getting-started/ai-agents.md +270 -0
  115. package/docs/solvers/custom-solver.md +525 -0
  116. package/docs/solvers/glpk.md +66 -0
  117. package/docs/solvers/highs.md +115 -0
  118. package/docs/solvers/index.md +42 -0
  119. package/docs/solvers/z3.md +78 -0
  120. package/docs/zh/getting-started/ai-agents.md +270 -0
  121. package/llms-full.txt +4065 -0
  122. package/llms.txt +201 -0
  123. package/package.json +32 -16
  124. package/skills/washi/SKILL.md +197 -0
  125. package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
  126. package/dist/browser/loader-CVt4CloI.js +0 -58
  127. package/dist/lib/async-mutex-Duo4jwzn.js +0 -195
  128. package/dist/lib/constraint-BRuB3ooC.js +0 -90
  129. package/dist/lib/loader-Dti0v9yC.js +0 -50
  130. package/dist/lib/registry-DksqCSjI.js +0 -32
@@ -0,0 +1,60 @@
1
+ # 0-1 Knapsack Problem (Binary MIP)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | 0-1 Binary MIP | HiGHS (default) | `m.binary()`, `m.constraint()`, `expr`, `maximize`, `dot` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=knapsack">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/02-knapsack-problem.ts">Source Code</a></li></ul> |
6
+
7
+ The 0-1 Knapsack problem is an essential combinatorial optimization problem: given a set of items, each with a weight and a value, determine which items to include in a collection so that the total weight does not exceed a given limit and the total value is maximized.
8
+
9
+ ## Mathematical Formulation
10
+
11
+ Let $x_i \in \{0, 1\}$ be binary decision variables indicating whether item $i$ is selected ($x_i = 1$) or not ($x_i = 0$):
12
+
13
+ $$\begin{aligned}
14
+ \text{Maximize} \quad & \sum_{i} v_i x_i & (\text{Total Value}) \\
15
+ \text{Subject to} \quad & \sum_{i} w_i x_i \le W & (\text{Weight Capacity}) \\
16
+ & x_i \in \{0, 1\} & (\text{Binary Decisions})
17
+ \end{aligned}$$
18
+
19
+ ## Implementation in WaSHI
20
+
21
+ <CodeRunner preset="knapsack">
22
+
23
+ ```typescript
24
+ import { Model, maximize, dot, expr } from "@ohtaman/washi";
25
+
26
+ async function solveKnapsack() {
27
+ const m = new Model("Knapsack");
28
+
29
+ const items = [
30
+ { name: "Laptop", weight: 3, value: 50 },
31
+ { name: "Camera", weight: 2, value: 40 },
32
+ { name: "Drone", weight: 4, value: 65 },
33
+ { name: "FirstAid", weight: 1, value: 15 },
34
+ { name: "Tent", weight: 5, value: 70 },
35
+ ];
36
+ const maxCapacity = 7; // kg
37
+
38
+ // Create binary variables: x[name] (0 or 1)
39
+ const x = m.binary(items.map((it) => it.name));
40
+
41
+ const values = Object.fromEntries(items.map((it) => [it.name, it.value]));
42
+ const weights = Object.fromEntries(items.map((it) => [it.name, it.weight]));
43
+
44
+ // Maximize total value via dot product
45
+ m.objective(maximize(dot(values, x)));
46
+
47
+ // Weight capacity constraint using natural expr and constraint() syntax
48
+ m.constraint(expr`${dot(weights, x)} <= ${maxCapacity}`, "capacity_limit");
49
+
50
+ const status = await m.solve();
51
+
52
+ console.log(`Status: ${status}`);
53
+ console.log(`Max Total Value: $${m.objectiveValue}`);
54
+ console.log("Selected Items:", m.getValues(x));
55
+ }
56
+
57
+ solveKnapsack();
58
+ ```
59
+
60
+ </CodeRunner>
@@ -0,0 +1,83 @@
1
+ # Portfolio Optimization (QP)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Convex Quadratic Programming (QP) | HiGHS (default) | `m.continuous()`, `m.constraint()`, `expr`, `minimize`, `dot`, `QuadExpr` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=portfolio_qp">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts">Source Code</a></li></ul> |
6
+
7
+ The Markowitz mean-variance portfolio optimization problem is the foundation of modern quantitative finance: allocate capital across risky assets to minimize overall portfolio variance (risk) subject to achieving a minimum required expected return.
8
+
9
+ <PortfolioQpDemo />
10
+
11
+ ## Mathematical Formulation
12
+
13
+ Let $w_i \ge 0$ denote the allocation weight for asset $i$:
14
+
15
+ $$\begin{aligned}
16
+ \text{Minimize} \quad & \mathbf{w}^T \boldsymbol{\Sigma} \mathbf{w} & (\text{Portfolio Variance}) \\
17
+ \text{Subject to} \quad & \sum_{i=1}^n w_i = 1 & (\text{Full Capital Budget}) \\
18
+ & \sum_{i=1}^n r_i w_i \ge R_{\text{target}} & (\text{Minimum Target Return}) \\
19
+ & 0 \le w_i \le 1 & (\text{Long-Only Weights})
20
+ \end{aligned}$$
21
+
22
+ where $\boldsymbol{\Sigma}$ is the positive semi-definite asset return covariance matrix.
23
+
24
+ ## Implementation in WaSHI
25
+
26
+ <CodeRunner preset="portfolio_qp">
27
+
28
+ ```typescript
29
+ import { Model, QuadExpr, sum, dot, minimize, expr } from "@ohtaman/washi";
30
+
31
+ async function solvePortfolio() {
32
+ const m = new Model("PortfolioQP");
33
+
34
+ const assets = ["Tech", "Bonds", "GlobalEq", "RealEstate", "Gold"];
35
+ const expectedReturns = [0.12, 0.04, 0.09, 0.07, 0.05];
36
+ const targetReturn = 0.085; // 8.5% minimum expected return
37
+
38
+ // 5x5 Covariance Matrix
39
+ const cov = [
40
+ [0.040, 0.002, 0.025, 0.015, 0.005],
41
+ [0.002, 0.005, 0.003, 0.002, 0.001],
42
+ [0.025, 0.003, 0.030, 0.018, 0.008],
43
+ [0.015, 0.002, 0.018, 0.025, 0.004],
44
+ [0.005, 0.001, 0.008, 0.004, 0.020],
45
+ ];
46
+
47
+ // Allocation weights w_i in [0, 1]
48
+ const w = m.continuous(assets, { lb: 0, ub: 1 });
49
+
50
+ // 1. Budget constraint: sum(w_i) == 1.0 (100%)
51
+ m.constraint(expr`${sum(assets, (a) => w[a])} == 1.0`, "budget_limit");
52
+
53
+ // 2. Expected return constraint: sum(r_i * w_i) >= 8.5%
54
+ const retMap = Object.fromEntries(assets.map((a, i) => [a, expectedReturns[i]]));
55
+ m.constraint(expr`${dot(retMap, w)} >= ${targetReturn}`, "target_return");
56
+
57
+ // 3. Quadratic Objective: minimize risk w^T * Sigma * w
58
+ let risk = new QuadExpr();
59
+ for (let i = 0; i < assets.length; i++) {
60
+ for (let j = 0; j < assets.length; j++) {
61
+ if (cov[i][j] !== 0) {
62
+ risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
63
+ }
64
+ }
65
+ }
66
+ m.objective(minimize(risk));
67
+
68
+ // Solve natively with HiGHS Convex QP engine
69
+ const status = await m.solve();
70
+
71
+ console.log(`Status: ${status}`);
72
+ console.log(`Variance: ${m.objectiveValue?.toFixed(6)}`);
73
+ console.log(`Volatility (Std Dev): ${((Math.sqrt(m.objectiveValue || 0)) * 100).toFixed(2)}%`);
74
+
75
+ assets.forEach((name) => {
76
+ console.log(` ${name.padEnd(10)}: ${((w[name].value ?? 0) * 100).toFixed(2)}%`);
77
+ });
78
+ }
79
+
80
+ solvePortfolio();
81
+ ```
82
+
83
+ </CodeRunner>
@@ -0,0 +1,51 @@
1
+ # Production Planning (LP / MIP)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Linear / Integer Programming (LP / MIP) | HiGHS (default) | `m.integer()`, `m.constraint()`, `expr` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=production">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/01-production-planning.ts">Source Code</a></li></ul> |
6
+
7
+ Production planning is a classic Linear Programming problem: determine the optimal quantity of each product to manufacture in order to maximize total profit while respecting resource constraints (raw materials, machine time, labor hours).
8
+
9
+ ## Mathematical Formulation
10
+
11
+ Let $x$ be the number of chairs and $y$ be the number of tables.
12
+
13
+ $$\begin{aligned}
14
+ \text{Maximize} \quad & 45x + 80y & (\text{Profit}) \\
15
+ \text{Subject to} \quad & 5x + 20y \le 400 & (\text{Mahogany Wood limit}) \\
16
+ & 10x + 15y \le 450 & (\text{Carpentry Labor limit}) \\
17
+ & x, y \ge 0, \quad x, y \in \mathbb{Z} & (\text{Integer Quantities})
18
+ \end{aligned}$$
19
+
20
+ ## Implementation in WaSHI
21
+
22
+ <CodeRunner preset="production">
23
+
24
+ ```typescript
25
+ import { Model, expr, maximize } from "@ohtaman/washi";
26
+
27
+ async function runProductionOptimization() {
28
+ const m = new Model("FactoryProduction");
29
+
30
+ // Decision variables: non-negative integer quantities
31
+ const chairs = m.integer("chairs", { lb: 0 });
32
+ const tables = m.integer("tables", { lb: 0 });
33
+
34
+ // Objective: Maximize total profit ($45 per chair, $80 per table)
35
+ m.objective(maximize(expr`45 * ${chairs} + 80 * ${tables}`));
36
+
37
+ // Resource constraints: declarative formulas via expr and fluent constraint()
38
+ m.constraint(expr`5 * ${chairs} + 20 * ${tables} <= 400`, "mahogany_limit")
39
+ .constraint(expr`10 * ${chairs} + 15 * ${tables} <= 450`, "labor_limit");
40
+
41
+ const status = await m.solve();
42
+
43
+ console.log(`Status: ${status}`);
44
+ console.log(`Optimal Profit: $${m.objectiveValue}`);
45
+ console.log(`Chairs: ${chairs.value}, Tables: ${tables.value}`);
46
+ }
47
+
48
+ runProductionOptimization();
49
+ ```
50
+
51
+ </CodeRunner>
@@ -0,0 +1,175 @@
1
+ # Staff Shift Scheduling & Conflict Diagnosis (MIP + Z3)
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Binary MIP & SMT Unsat Core | HiGHS + Z3 | `m.binary(staff, days)`, `m.constraint()`, `expr`, `minimize`, `dot`, `m.check()` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=shift_scheduling">Playground (Roster)</a></li><li><a href="https://ohtaman.github.io/washi/demo/?preset=shift_conflict_unsat">Playground (Conflict)</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/11-shift-scheduling.ts">Source Code</a></li></ul> |
6
+
7
+ Staff shift scheduling is a classic real-world application of Mixed-Integer Programming in SaaS and workforce management tools. However, in real applications, **staff vacation requests frequently collide with minimum coverage or leadership rules**, causing the solver to return `Infeasible`.
8
+
9
+ This example demonstrates a practical production workflow:
10
+ 1. **Try HiGHS MIP Optimization First:** Solve the shift model under the given vacation requests and operational constraints.
11
+ 2. **Accept Solution if Optimal:** If a feasible roster is found, display the schedule and minimized payroll cost without unnecessary diagnostic overhead.
12
+ 3. **Diagnose with Z3 `unsatCore` Only When Infeasible:** If HiGHS reports `Infeasible`, diagnose the **exact same model and constraints** with Z3 to isolate the conflicting constraint subset (`unsatCore`), pinpointing which day's coverage rules and which staff requests collided.
13
+
14
+ > [!IMPORTANT]
15
+ > **Vacation Requests are Hard Constraints:**
16
+ > In this model, vacation requests are enforced as strict hard constraints (`shift[staff][day] == 0`), not soft constraints with penalty relaxation. When requests collide with mandatory staffing requirements, the model is strictly infeasible.
17
+
18
+ <ShiftSchedulingDemo />
19
+
20
+ ## Scenario & Rules
21
+
22
+ We schedule **5 staff members** (`Alice`, `Bob`, `Charlie`, `Diana`, `Evan`) across **7 days** (`Mon`–`Sun`):
23
+
24
+ - **Daily Headcount:** `Mon–Thu`: 3 staff, `Fri`: 4 staff, `Sat`: 3 staff, `Sun`: 2 staff.
25
+ - **Senior Duty:** At least 1 senior staff member (`Alice` or `Bob`) must be on duty every day.
26
+ - **Work Limit:** Each staff member works at most **5 days** per week.
27
+ - **Weekend Premium:** Weekend shifts (`Sat`, `Sun`) incur an extra `+$30` shift premium on top of base daily wage.
28
+
29
+ We evaluate two distinct, named scenarios with different vacation inputs to illustrate the full workflow:
30
+ - **Scenario A (Balanced Requests — Feasible):** Alice (Sun), Bob (Mon), Charlie (Sat), Diana (Wed). All constraints can be satisfied simultaneously. HiGHS outputs an optimal roster.
31
+ - **Scenario B (Conflicting Requests — Infeasible & Diagnosis):** Alice (Sat), Bob (Sat), Charlie (Sat), Diana (Wed). Because both senior leaders (Alice and Bob) request Saturday off simultaneously, the rule requiring at least 1 senior on duty every day cannot be satisfied. HiGHS reports `Infeasible`, triggering Z3 conflict diagnosis.
32
+
33
+ ## Implementation in WaSHI
34
+
35
+ <CodeRunner preset="shift_scheduling">
36
+
37
+ ```typescript
38
+ import { Model, sum, dot, minimize, expr } from "@ohtaman/washi";
39
+ import "@ohtaman/washi/solvers/z3";
40
+
41
+ const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] as const;
42
+ type Day = (typeof days)[number];
43
+
44
+ const staffList = [
45
+ { name: "Alice", isSenior: true, dailyWage: 220, maxShifts: 5 },
46
+ { name: "Bob", isSenior: true, dailyWage: 210, maxShifts: 5 },
47
+ { name: "Charlie", isSenior: false, dailyWage: 160, maxShifts: 5 },
48
+ { name: "Diana", isSenior: false, dailyWage: 160, maxShifts: 5 },
49
+ { name: "Evan", isSenior: false, dailyWage: 150, maxShifts: 5 },
50
+ ];
51
+
52
+ const requiredCoverage: Record<Day, number> = {
53
+ Mon: 3, Tue: 3, Wed: 3, Thu: 3, Fri: 4, Sat: 3, Sun: 2,
54
+ };
55
+
56
+ function buildShiftModel(name: string, dayOffRequests: Array<{ staff: string; day: Day }>) {
57
+ const m = new Model(name);
58
+
59
+ // 2D binary matrix: shift[staff][day] ∈ {0, 1}
60
+ const shift = m.binary(staffList.map((s) => s.name), [...days]);
61
+
62
+ // Objective: Minimize weekly payroll cost via 2D dot product
63
+ const costMatrix = Object.fromEntries(
64
+ staffList.map((s) => [
65
+ s.name,
66
+ Object.fromEntries(
67
+ days.map((d) => [d, s.dailyWage + (d === "Sat" || d === "Sun" ? 30 : 0)])
68
+ ),
69
+ ])
70
+ );
71
+ m.objective(minimize(dot(costMatrix, shift)));
72
+
73
+ // Rule 1: Daily headcount coverage
74
+ for (const d of days) {
75
+ m.constraint(
76
+ expr`${sum(staffList, (s) => shift[s.name][d])} >= ${requiredCoverage[d]}`,
77
+ `coverage_${d}`
78
+ );
79
+ }
80
+
81
+ // Rule 2: At least 1 senior staff member per day
82
+ const seniors = staffList.filter((s) => s.isSenior);
83
+ for (const d of days) {
84
+ m.constraint(
85
+ expr`${sum(seniors, (s) => shift[s.name][d])} >= 1`,
86
+ `senior_duty_${d}`
87
+ );
88
+ }
89
+
90
+ // Rule 3: Max working days per staff member
91
+ for (const s of staffList) {
92
+ m.constraint(
93
+ expr`${sum(days, (d) => shift[s.name][d])} <= ${s.maxShifts}`,
94
+ `max_shifts_${s.name}`
95
+ );
96
+ }
97
+
98
+ // Rule 4: Named day-off requests as HARD constraints (shift[staff][day] == 0)
99
+ for (const req of dayOffRequests) {
100
+ m.constraint(
101
+ expr`${shift[req.staff][req.day]} == 0`,
102
+ `day_off_${req.staff}_${req.day}`
103
+ );
104
+ }
105
+
106
+ return { model: m, shift };
107
+ }
108
+
109
+ async function runScenario(
110
+ title: string,
111
+ modelName: string,
112
+ dayOffRequests: Array<{ staff: string; day: Day }>
113
+ ) {
114
+ console.log(`=== ${title} ===`);
115
+ console.log(
116
+ "Hard Vacation Requests: " +
117
+ dayOffRequests.map((r) => `${r.staff} (${r.day})`).join(", ")
118
+ );
119
+
120
+ const { model, shift } = buildShiftModel(modelName, dayOffRequests);
121
+
122
+ // Step 1: Optimize with HiGHS
123
+ const status = await model.solve();
124
+ console.log(`HiGHS Optimize Status: ${status}`);
125
+
126
+ if (status === "Optimal") {
127
+ console.log(`Optimal Weekly Payroll: $${model.objectiveValue}`);
128
+ for (const s of staffList) {
129
+ const schedule = days.map((d) => (Math.round(shift[s.name][d].value ?? 0) === 1 ? "ON" : " ·"));
130
+ console.log(` ${s.name.padEnd(8)}: ${schedule.join(" ")}`);
131
+ }
132
+ console.log("Schedule resolved successfully (no UNSAT diagnosis needed).\n");
133
+ } else if (status === "Infeasible") {
134
+ // Step 2: Diagnose the exact same model and constraints with Z3
135
+ console.log("HiGHS returned Infeasible! Diagnosing conflict with Z3 unsatCore...");
136
+ const checkRes = await model.check({ solver: "z3" });
137
+ console.log(`Z3 Check Status: ${checkRes.status}`);
138
+ console.log("Conflicting Constraint Subset (unsatCore):", model.unsatCore);
139
+ console.log(
140
+ "Note: unsatCore returns a conflicting subset (minimality/order not guaranteed). " +
141
+ "Independent requests like Diana's Wednesday off are excluded.\n"
142
+ );
143
+ }
144
+ }
145
+
146
+ async function main() {
147
+ // Scenario A: Balanced requests (Solvable)
148
+ await runScenario(
149
+ "Scenario A: Balanced Vacation Requests",
150
+ "FeasibleRoster",
151
+ [
152
+ { staff: "Alice", day: "Sun" },
153
+ { staff: "Bob", day: "Mon" },
154
+ { staff: "Charlie", day: "Sat" },
155
+ { staff: "Diana", day: "Wed" },
156
+ ]
157
+ );
158
+
159
+ // Scenario B: Conflicting Saturday requests (Infeasible)
160
+ await runScenario(
161
+ "Scenario B: Conflicting Saturday Requests",
162
+ "ConflictRoster",
163
+ [
164
+ { staff: "Alice", day: "Sat" }, // Senior
165
+ { staff: "Bob", day: "Sat" }, // Senior
166
+ { staff: "Charlie", day: "Sat" },
167
+ { staff: "Diana", day: "Wed" },
168
+ ]
169
+ );
170
+ }
171
+
172
+ main();
173
+ ```
174
+
175
+ </CodeRunner>
@@ -0,0 +1,74 @@
1
+ # SMT Security Policies
2
+
3
+ | Problem Class | Solver | Key APIs | Links |
4
+ | :--- | :--- | :--- | :--- |
5
+ | Propositional Logic & SMT | Z3 (`@ohtaman/washi/solvers/z3`) | `m.bool()`, `m.integer()`, `m.constraint()`, `expr`, `m.check()` | <ul><li><a href="https://ohtaman.github.io/washi/demo/?preset=smt_sat_scheduling">Open in Playground</a></li><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts">Source Code</a></li></ul> |
6
+
7
+ Satisfiability Modulo Theories (SMT) combines propositional logic (Boolean SAT) with first-order theories (linear integer arithmetic, real inequalities).
8
+
9
+ This example demonstrates how to formally verify and deduce system states in an IoT home security system using WaSHI and the Z3 solver engine.
10
+
11
+ ## The Scenario
12
+
13
+ We model a security rule system with sensor readings and logic rules:
14
+ 1. When ambient light $\le 50$, it implies night time (`isNight`).
15
+ 2. When motion is detected at night and the alarm is armed, the siren must sound.
16
+ 3. If the alarm is armed, the front door must never be unlocked.
17
+ 4. When motion is detected at night, comfort lights should turn on.
18
+
19
+ ## Implementation in WaSHI
20
+
21
+ <CodeRunner preset="smt_sat_scheduling">
22
+
23
+ ```typescript
24
+ import { Model, not, expr } from "@ohtaman/washi";
25
+ import "@ohtaman/washi/solvers/z3";
26
+
27
+ async function verifySecurityRules() {
28
+ const m = new Model({ name: "SmartHomeSecurity", solver: "z3" });
29
+
30
+ // 1. Boolean Propositional Variables
31
+ const isNight = m.bool("isNight");
32
+ const motionDetected = m.bool("motionDetected");
33
+ const alarmArmed = m.bool("alarmArmed");
34
+ const frontDoorUnlocked = m.bool("frontDoorUnlocked");
35
+ const sirenActive = m.bool("sirenActive");
36
+ const lightsOn = m.bool("lightsOn");
37
+
38
+ // 2. Arithmetic metric (lux sensor)
39
+ const luxLevel = m.integer("luxLevel", { lb: 0, ub: 1000 });
40
+
41
+ // Rule 1: luxLevel <= 50 implies isNight
42
+ m.constraint(luxLevel.le(50).implies(isNight), "dark_implies_night");
43
+
44
+ // Rule 2: (motionDetected AND isNight AND alarmArmed) => sirenActive
45
+ m.constraint(
46
+ motionDetected.and(isNight).and(alarmArmed).implies(sirenActive),
47
+ "intrusion_rule"
48
+ );
49
+
50
+ // Rule 3: alarmArmed => NOT(frontDoorUnlocked)
51
+ m.constraint(alarmArmed.implies(not(frontDoorUnlocked)), "lock_safety_rule");
52
+
53
+ // Rule 4: (motionDetected AND isNight) => lightsOn
54
+ m.constraint(motionDetected.and(isNight).implies(lightsOn), "comfort_lights");
55
+
56
+ // 3. Observed Context
57
+ m.constraint(expr`${luxLevel} <= 20`, "sensor_reading_dark");
58
+ m.constraint(alarmArmed, "user_armed_alarm");
59
+ m.constraint(motionDetected, "sensor_motion_triggered");
60
+
61
+ // Check satisfiability and deduce state
62
+ const res = await m.check({ solver: "z3" });
63
+
64
+ console.log(`Status: ${res.status}`);
65
+ console.log(`Siren Active : ${sirenActive.value}`);
66
+ console.log(`Lights On : ${lightsOn.value}`);
67
+ console.log(`Door Locked : ${frontDoorUnlocked.value ? "No" : "Yes"}`);
68
+ console.log(`Is Night : ${isNight.value}`);
69
+ }
70
+
71
+ verifySecurityRules();
72
+ ```
73
+
74
+ </CodeRunner>
@@ -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>