@ohtaman/washi 0.5.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.
- package/README.ja.md +55 -20
- package/README.md +55 -20
- package/README.zh-CN.md +55 -20
- package/THIRD_PARTY_LICENSES.md +3 -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 +1 -1
- 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 +1096 -444
- 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-C20sxTwm.js} +54 -41
- package/dist/lib/solvers/glpk.js +2 -2
- package/dist/lib/solvers/z3.js +1 -1
- package/dist/lib/washi.js +973 -305
- 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 +10 -0
- package/dist/types/modeler/expression.d.ts.map +1 -1
- package/dist/types/modeler/index.d.ts +2 -1
- 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 +147 -2
- package/dist/types/modeler/model.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/types.d.ts +2 -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/index.md +4 -0
- package/docs/examples/traveling-salesperson.md +173 -0
- package/docs/examples/vehicle-routing.md +206 -0
- package/docs/getting-started/ai-agents.md +8 -4
- package/docs/guide/constraints.md +1 -1
- package/docs/guide/decomposition.md +399 -0
- package/docs/guide/unsat-core.md +10 -3
- package/docs/ja/getting-started/ai-agents.md +8 -4
- package/docs/solvers/custom-solver.md +1 -1
- package/docs/solvers/highs.md +25 -3
- package/docs/solvers/index.md +3 -3
- package/docs/zh/getting-started/ai-agents.md +8 -4
- package/llms-full.txt +831 -15
- package/llms.txt +9 -5
- package/package.json +17 -14
- package/skills/washi/SKILL.md +8 -8
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Constraint } from "./constraint.js";
|
|
1
2
|
/**
|
|
2
3
|
* HiGHS & Model Types
|
|
3
4
|
*/
|
|
@@ -55,6 +56,8 @@ export interface SolveOptions {
|
|
|
55
56
|
signal?: AbortSignal;
|
|
56
57
|
/** Callback invoked with real solver progress metrics when available */
|
|
57
58
|
onProgress?: (progress: SolveProgressEvent) => void;
|
|
59
|
+
/** Solve the continuous (LP) relaxation of the model by relaxing integer and binary variables */
|
|
60
|
+
relax?: boolean;
|
|
58
61
|
[key: string]: any;
|
|
59
62
|
}
|
|
60
63
|
export interface SolverSolution {
|
|
@@ -87,4 +90,28 @@ export interface CheckResult {
|
|
|
87
90
|
conflicts?: readonly string[];
|
|
88
91
|
runTime?: number;
|
|
89
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Definition for dynamically adding a column (decision variable + constraint coefficients + objective cost).
|
|
95
|
+
* Core primitive for Column Generation and Dantzig-Wolfe decomposition.
|
|
96
|
+
*/
|
|
97
|
+
export interface ColumnDefinition {
|
|
98
|
+
name?: string;
|
|
99
|
+
type?: VarType;
|
|
100
|
+
lb?: number;
|
|
101
|
+
ub?: number;
|
|
102
|
+
/** Objective cost coefficient */
|
|
103
|
+
cost?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Constraint coefficients for this column.
|
|
106
|
+
* Accepts:
|
|
107
|
+
* - Record<string, number> (keyed by constraint name)
|
|
108
|
+
* - Map<Constraint | string, number>
|
|
109
|
+
* - Array<[Constraint | string, number]>
|
|
110
|
+
* - Array<{ constraint: Constraint | string; coeff: number }>
|
|
111
|
+
*/
|
|
112
|
+
coefficients?: Record<string, number> | Map<Constraint | string, number> | Array<[Constraint | string, number]> | Array<{
|
|
113
|
+
constraint: Constraint | string;
|
|
114
|
+
coeff: number;
|
|
115
|
+
}>;
|
|
116
|
+
}
|
|
90
117
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modeler/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modeler/types.ts"],"names":[],"mappings":"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"}
|
package/docs/examples/index.md
CHANGED
|
@@ -15,6 +15,8 @@ Explore runnable optimization and logic formulations built with WaSHI. Every exa
|
|
|
15
15
|
| **[Portfolio Optimization](./portfolio-qp.md)** | Convex QP | HiGHS | `m.continuous()`, `m.constraint()`, `dot()`, `QuadExpr` | Markowitz mean-variance risk minimization ($w^T \Sigma w$) using `QuadExpr` subject to return targets. | [Playground](https://ohtaman.github.io/washi/demo/?preset=portfolio_qp) |
|
|
16
16
|
| **[SMT Security Policies](./smt-security.md)** | SMT Logic | Z3 | `m.bool()`, `m.constraint()`, `m.check()` | Propositional variables (`BoolVar`) and logical connectives (`implies`, `and`, `not`, `xor`) mixed with integer bounds. | [Playground](https://ohtaman.github.io/washi/demo/?preset=smt_sat_scheduling) |
|
|
17
17
|
| **[Conflict Diagnosis (`unsatCore`)](./unsat-core.md)** | Unsat Core | Z3 | `m.constraint()`, `m.check()`, `m.unsatCore` | Extracting a conflicting constraint subset (`unsatCore`) when staffing and budget rules contradict each other. | [Playground](https://ohtaman.github.io/washi/demo/?preset=conflict_diagnosis_unsat) |
|
|
18
|
+
| **[Vehicle Routing (CVRP)](./vehicle-routing.md)** | Combinatorial OR / Dantzig-Wolfe | HiGHS | `m.solveColumnGeneration()`, `pricing` | Dynamic route column generation from customer dual shadow prices to minimize total fleet vehicles. | [Decomposition Guide](../guide/decomposition.md) |
|
|
19
|
+
| **[Traveling Salesperson (TSP)](./traveling-salesperson.md)** | Combinatorial OR / Benders | HiGHS | `m.solveBenders()`, `subproblem` | Subtour elimination on degree-assignment relaxation using dynamic Benders feasibility cuts. | [Decomposition Guide](../guide/decomposition.md) |
|
|
18
20
|
|
|
19
21
|
## Find by WaSHI API (Reverse Index)
|
|
20
22
|
|
|
@@ -32,6 +34,8 @@ Looking for a specific modeling method or solver capability? Jump straight to th
|
|
|
32
34
|
| `new QuadExpr()` | Convex quadratic objectives ($x^T Q x$) | [Portfolio Optimization](./portfolio-qp.md) | [`03-portfolio-qp.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/03-portfolio-qp.ts) |
|
|
33
35
|
| `m.bool()` & `.implies()` | Propositional logic & SMT rules | [SMT Security Policies](./smt-security.md) | [`04-smt-security-rules.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/04-smt-security-rules.ts) |
|
|
34
36
|
| `m.check()` & `m.unsatCore` | Feasibility check & conflict diagnosis | [Conflict Diagnosis](./unsat-core.md), [Shift Scheduling](./shift-scheduling.md) | [`05-unsat-core-diagnosis.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/05-unsat-core-diagnosis.ts) |
|
|
37
|
+
| `m.solveColumnGeneration()` | Dantzig-Wolfe Column Generation | [Vehicle Routing](./vehicle-routing.md) | [`13-vehicle-routing-column-generation.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/13-vehicle-routing-column-generation.ts) |
|
|
38
|
+
| `m.solveBenders()` | Benders Decomposition & cut separation | [Traveling Salesperson](./traveling-salesperson.md) | [`14-traveling-salesperson-benders.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/14-traveling-salesperson-benders.ts) |
|
|
35
39
|
| `solver: "glpk"` | Solving LP/MIP with GLPK plugin | [GLPK Solver Guide](../solvers/glpk.md) | [`08-glpk-solver-plugin.ts`](https://github.com/ohtaman/washi/blob/main/examples/node/08-glpk-solver-plugin.ts) |
|
|
36
40
|
|
|
37
41
|
## Running Examples Locally in Node.js
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Traveling Salesperson Problem (TSP via Benders Decomposition)
|
|
2
|
+
|
|
3
|
+
| Problem Class | Solver | Key APIs | Links |
|
|
4
|
+
| :--- | :--- | :--- | :--- |
|
|
5
|
+
| Combinatorial OR / Benders | HiGHS (default) | `m.solveBenders()`, `subproblem`, `m.binary()`, `m.constraint()`, `expr` | <ul><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/14-traveling-salesperson-benders.ts">Source Code</a></li><li><a href="../guide/decomposition.md">Decomposition Guide</a></li></ul> |
|
|
6
|
+
|
|
7
|
+
The **Traveling Salesperson Problem (TSP)** seeks the shortest closed tour that visits every city in a given set once and returns to the starting city.
|
|
8
|
+
|
|
9
|
+
In the **Dantzig-Fulkerson-Johnson (DFJ)** formulation, preventing disconnected cycles (**subtours**) requires subtour elimination constraints for every non-empty proper subset of cities $S \subset V$:
|
|
10
|
+
|
|
11
|
+
$$\sum_{i \in S, j \notin S} x_{ij} \ge 1 \quad \forall S \subset V, \; 2 \le |S| \le |V| - 1$$
|
|
12
|
+
|
|
13
|
+
Because there are $2^n - 2$ subsets, enumerating all subtour constraints upfront is impractical as $n$ grows. With **Benders Decomposition (Lazy Constraint Separation)**, constraints are added only when violated:
|
|
14
|
+
1. **Master Problem**: Solves the degree-assignment relaxation (each city has 1 incoming and 1 outgoing edge).
|
|
15
|
+
2. **Separation Subproblem**: Inspects the candidate solution graph. If disconnected cycles exist, it adds a **Benders Feasibility Cut** eliminating the smallest cycle and resolves until a single connected tour is obtained.
|
|
16
|
+
|
|
17
|
+
## Problem Inputs
|
|
18
|
+
|
|
19
|
+
We consider **5 cities** on a 2D Euclidean coordinate grid:
|
|
20
|
+
|
|
21
|
+
| City | Coordinates $(x, y)$ |
|
|
22
|
+
| :--- | :---: |
|
|
23
|
+
| **City A (0)** | $(0, 0)$ |
|
|
24
|
+
| **City B (1)** | $(1, 0)$ |
|
|
25
|
+
| **City C (2)** | $(1, 2)$ |
|
|
26
|
+
| **City D (3)** | $(0, 2)$ |
|
|
27
|
+
| **City E (4)** | $(0.5, 3)$ |
|
|
28
|
+
|
|
29
|
+
## Implementation in WaSHI
|
|
30
|
+
|
|
31
|
+
<CodeRunner>
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Model, expr, minimize, LinExpr } from "@ohtaman/washi";
|
|
35
|
+
|
|
36
|
+
async function runTSP() {
|
|
37
|
+
const cities = [
|
|
38
|
+
{ name: "A", x: 0, y: 0 },
|
|
39
|
+
{ name: "B", x: 1, y: 0 },
|
|
40
|
+
{ name: "C", x: 1, y: 2 },
|
|
41
|
+
{ name: "D", x: 0, y: 2 },
|
|
42
|
+
{ name: "E", x: 0.5, y: 3 },
|
|
43
|
+
];
|
|
44
|
+
const n = cities.length;
|
|
45
|
+
|
|
46
|
+
function dist(i, j) {
|
|
47
|
+
const dx = cities[i].x - cities[j].x;
|
|
48
|
+
const dy = cities[i].y - cities[j].y;
|
|
49
|
+
return Math.hypot(dx, dy);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 1. Master Problem: Degree assignment relaxation
|
|
53
|
+
const master = new Model("TSP_BendersMaster");
|
|
54
|
+
const x = {};
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < n; i++) {
|
|
57
|
+
x[i] = {};
|
|
58
|
+
for (let j = 0; j < n; j++) {
|
|
59
|
+
if (i !== j) {
|
|
60
|
+
x[i][j] = master.binary(`x_${i}_${j}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Objective: Minimize total tour distance
|
|
66
|
+
let totalDistance = new LinExpr();
|
|
67
|
+
for (let i = 0; i < n; i++) {
|
|
68
|
+
for (let j = 0; j < n; j++) {
|
|
69
|
+
if (i !== j) {
|
|
70
|
+
totalDistance = totalDistance.add(x[i][j].mul(dist(i, j)));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
master.objective(minimize(totalDistance));
|
|
75
|
+
|
|
76
|
+
// Each city must have 1 outgoing and 1 incoming edge
|
|
77
|
+
for (let i = 0; i < n; i++) {
|
|
78
|
+
let outExpr = new LinExpr();
|
|
79
|
+
let inExpr = new LinExpr();
|
|
80
|
+
for (let j = 0; j < n; j++) {
|
|
81
|
+
if (i !== j) {
|
|
82
|
+
outExpr = outExpr.add(x[i][j]);
|
|
83
|
+
inExpr = inExpr.add(x[j][i]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
master.constraint(expr`${outExpr} == 1`, `out_${i}`);
|
|
87
|
+
master.constraint(expr`${inExpr} == 1`, `in_${i}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log("Solving TSP with Benders lazy subtour elimination...");
|
|
91
|
+
|
|
92
|
+
// 2. Solve via Benders Decomposition
|
|
93
|
+
let cutCounter = 0;
|
|
94
|
+
const result = await master.solveBenders({
|
|
95
|
+
subproblem: () => {
|
|
96
|
+
// Find active directed edges in the master solution
|
|
97
|
+
const succ = {};
|
|
98
|
+
for (let i = 0; i < n; i++) {
|
|
99
|
+
for (let j = 0; j < n; j++) {
|
|
100
|
+
if (i !== j && Math.round(x[i][j].value ?? 0) === 1) {
|
|
101
|
+
succ[i] = j;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Trace connected cycles
|
|
107
|
+
const visited = new Set();
|
|
108
|
+
const cycles = [];
|
|
109
|
+
for (let i = 0; i < n; i++) {
|
|
110
|
+
if (!visited.has(i)) {
|
|
111
|
+
const cycle = [];
|
|
112
|
+
let curr = i;
|
|
113
|
+
while (!visited.has(curr)) {
|
|
114
|
+
visited.add(curr);
|
|
115
|
+
cycle.push(curr);
|
|
116
|
+
curr = succ[curr];
|
|
117
|
+
}
|
|
118
|
+
cycles.push(cycle);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// If more than 1 cycle, subtours exist: inject Benders feasibility cut
|
|
123
|
+
if (cycles.length > 1) {
|
|
124
|
+
const smallestCycle = cycles.reduce((min, c) => (c.length < min.length ? c : min));
|
|
125
|
+
const S = new Set(smallestCycle);
|
|
126
|
+
cutCounter++;
|
|
127
|
+
console.log(`Subtour detected: [${smallestCycle.map(k => cities[k].name).join(" -> ")}]. Adding Benders cut #${cutCounter}.`);
|
|
128
|
+
|
|
129
|
+
let cutLhs = new LinExpr();
|
|
130
|
+
for (const i of S) {
|
|
131
|
+
for (let j = 0; j < n; j++) {
|
|
132
|
+
if (!S.has(j)) {
|
|
133
|
+
cutLhs = cutLhs.add(x[i][j]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
kind: "feasibility",
|
|
140
|
+
constraint: expr`${cutLhs} >= 1`,
|
|
141
|
+
name: `subtour_cut_${cutCounter}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null; // Single connected tour found
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log(`\nStatus: ${result.status}`);
|
|
150
|
+
console.log(`Optimal Tour Distance: ${result.objectiveValue?.toFixed(3)}`);
|
|
151
|
+
console.log(`Total Benders Cuts Added: ${result.cutsAdded}`);
|
|
152
|
+
console.log(`Iterations: ${result.iterations}`);
|
|
153
|
+
|
|
154
|
+
console.log("\nOptimal tour edges:");
|
|
155
|
+
for (let i = 0; i < n; i++) {
|
|
156
|
+
for (let j = 0; j < n; j++) {
|
|
157
|
+
if (i !== j && (result.values[`x_${i}_${j}`] ?? 0) > 0.5) {
|
|
158
|
+
console.log(` ${cities[i].name} -> ${cities[j].name} (distance: ${dist(i, j).toFixed(3)})`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
runTSP();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
</CodeRunner>
|
|
168
|
+
|
|
169
|
+
## Notes
|
|
170
|
+
|
|
171
|
+
- **Lazy Subtour Elimination**: The master problem starts with only the $2n$ degree constraints, and subtour elimination cuts are added on demand when disconnected cycles appear in the master solution.
|
|
172
|
+
- **Separation Callback**: The `subproblem` callback reads candidate edge values via `x[i][j].value`, traces connected components in $O(n)$, and returns `{ kind: "feasibility", constraint: ... }` when multiple cycles are present.
|
|
173
|
+
- **Termination Condition**: Returning `null` from `subproblem` indicates that no violated subtour constraints remain.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Capacitated Vehicle Routing Problem (CVRP, 50 Customers via Column Generation)
|
|
2
|
+
|
|
3
|
+
| Problem Class | Solver | Key APIs | Links |
|
|
4
|
+
| :--- | :--- | :--- | :--- |
|
|
5
|
+
| Combinatorial OR / Dantzig-Wolfe | HiGHS (default) | `m.solveColumnGeneration()`, `pricing`, `sum()`, `m.binary()`, `m.constraint()`, `expr` | <ul><li><a href="https://github.com/ohtaman/washi/blob/main/examples/node/13-vehicle-routing-column-generation.ts">Source Code</a></li><li><a href="../guide/decomposition.md">Decomposition Guide</a></li></ul> |
|
|
6
|
+
|
|
7
|
+
The **Capacitated Vehicle Routing Problem (CVRP)** determines the minimum number of delivery vehicles needed to visit a set of customers, subject to vehicle cargo capacity, maximum customer stops per shift, and route duration limits.
|
|
8
|
+
|
|
9
|
+
In a monolithic MIP formulation, enumerating all possible customer combinations and visit sequences leads to $O(2^n)$ binary variables. With **Column Generation (Dantzig-Wolfe Decomposition)**, routes are generated on demand:
|
|
10
|
+
1. **Restricted Master Problem (RMP)**: Solves a set partitioning problem that selects a subset of candidate routes so that every customer is visited once.
|
|
11
|
+
2. **Pricing Subproblem**: Uses the dual values (shadow prices) from the master LP relaxation to construct improving routes with negative reduced cost ($\bar{c} = 1 - \sum \pi_i < 0$) via cheapest insertion and 2-opt local search.
|
|
12
|
+
|
|
13
|
+
## Problem Inputs
|
|
14
|
+
|
|
15
|
+
We consider a distribution network with a central depot at $(0, 0)$ and **50 customer locations**:
|
|
16
|
+
|
|
17
|
+
- **Vehicle Cargo Capacity**: At most **10 units** per vehicle.
|
|
18
|
+
- **Maximum Stops**: At most **4 customers** per route.
|
|
19
|
+
- **Maximum Shift Duration**: At most **200 minutes** (transit time + service time) per route.
|
|
20
|
+
- **Transit & Service Times**: Euclidean distances between nodes (1 unit = 1 minute) plus **10 minutes** of unloading service time per customer.
|
|
21
|
+
|
|
22
|
+
## Implementation in WaSHI
|
|
23
|
+
|
|
24
|
+
<CodeRunner>
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { Model, expr, minimize, sum } from "@ohtaman/washi";
|
|
28
|
+
|
|
29
|
+
// --- Extracted Routing Helpers ---
|
|
30
|
+
|
|
31
|
+
// 2-opt local search to eliminate crossing paths in a tour
|
|
32
|
+
function optimize2Opt(tour, dist) {
|
|
33
|
+
let best = [...tour];
|
|
34
|
+
let improved = true;
|
|
35
|
+
|
|
36
|
+
while (improved) {
|
|
37
|
+
improved = false;
|
|
38
|
+
for (let i = 1; i < best.length - 2; i++) {
|
|
39
|
+
for (let j = i + 1; j < best.length - 1; j++) {
|
|
40
|
+
const dCurrent = dist(best[i - 1], best[i]) + dist(best[j], best[j + 1]);
|
|
41
|
+
const dSwap = dist(best[i - 1], best[j]) + dist(best[i], best[j + 1]);
|
|
42
|
+
if (dSwap < dCurrent) {
|
|
43
|
+
best = [...best.slice(0, i), ...best.slice(i, j + 1).reverse(), ...best.slice(j + 1)];
|
|
44
|
+
improved = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Inserts a customer into the tour at the position that minimizes detour distance
|
|
53
|
+
function insertCheapest(tour, id, dist) {
|
|
54
|
+
let bestPos = 1;
|
|
55
|
+
let minCost = Infinity;
|
|
56
|
+
|
|
57
|
+
for (let p = 1; p < tour.length; p++) {
|
|
58
|
+
const cost = dist(tour[p - 1], id) + dist(id, tour[p]) - dist(tour[p - 1], tour[p]);
|
|
59
|
+
if (cost < minCost) {
|
|
60
|
+
minCost = cost;
|
|
61
|
+
bestPos = p;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...tour.slice(0, bestPos), id, ...tour.slice(bestPos)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Calculates transit time and total duration (transit + service) for a tour
|
|
68
|
+
function calcRouteTime(tour, dist, serviceTimes) {
|
|
69
|
+
let travelTime = 0;
|
|
70
|
+
for (let i = 0; i < tour.length - 1; i++) {
|
|
71
|
+
travelTime += dist(tour[i], tour[i + 1]);
|
|
72
|
+
}
|
|
73
|
+
const serviceTime = tour.slice(1, -1).reduce((acc, id) => acc + (serviceTimes[id] ?? 0), 0);
|
|
74
|
+
return { travelTime, totalTime: travelTime + serviceTime };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Pricing subproblem: Finds an improving route with negative reduced cost (1 - dualSum < 0)
|
|
78
|
+
function findImprovingRoute(candidates, config) {
|
|
79
|
+
let bestRoute = null;
|
|
80
|
+
let minReducedCost = -1e-4;
|
|
81
|
+
|
|
82
|
+
for (const seed of candidates.slice(0, 15)) {
|
|
83
|
+
let tour = ["Depot", seed.id, "Depot"];
|
|
84
|
+
let load = seed.demand;
|
|
85
|
+
let dualSum = seed.dual;
|
|
86
|
+
|
|
87
|
+
for (const next of candidates) {
|
|
88
|
+
if (tour.includes(next.id)) continue;
|
|
89
|
+
if (load + next.demand > config.vehicleCapacity) continue;
|
|
90
|
+
if (tour.length - 2 >= config.maxStops) continue;
|
|
91
|
+
|
|
92
|
+
const candidateTour = optimize2Opt(insertCheapest(tour, next.id, config.dist), config.dist);
|
|
93
|
+
const { totalTime } = calcRouteTime(candidateTour, config.dist, config.serviceTimes);
|
|
94
|
+
|
|
95
|
+
if (totalTime <= config.maxRouteTime) {
|
|
96
|
+
tour = candidateTour;
|
|
97
|
+
load += next.demand;
|
|
98
|
+
dualSum += next.dual;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const reducedCost = 1 - dualSum;
|
|
103
|
+
if (reducedCost < minReducedCost) {
|
|
104
|
+
minReducedCost = reducedCost;
|
|
105
|
+
bestRoute = tour;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return bestRoute;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Main CVRP via Column Generation ---
|
|
112
|
+
|
|
113
|
+
async function runCVRP() {
|
|
114
|
+
const vehicleCapacity = 10;
|
|
115
|
+
const maxStops = 4;
|
|
116
|
+
const maxRouteTime = 200; // minutes
|
|
117
|
+
|
|
118
|
+
// Central depot and 50 customer locations
|
|
119
|
+
const depot = { id: "Depot", x: 0, y: 0 };
|
|
120
|
+
const customers = Array.from({ length: 50 }, (_, i) => {
|
|
121
|
+
const angle = i * 2.4;
|
|
122
|
+
const radius = 15 + (i % 5) * 8;
|
|
123
|
+
return {
|
|
124
|
+
id: `C${i + 1}`,
|
|
125
|
+
x: Math.round(Math.cos(angle) * radius),
|
|
126
|
+
y: Math.round(Math.sin(angle) * radius),
|
|
127
|
+
demand: 1 + (i % 4),
|
|
128
|
+
serviceTime: 10,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const nodes = Object.fromEntries([depot, ...customers].map((n) => [n.id, n]));
|
|
133
|
+
const dist = (a, b) =>
|
|
134
|
+
Math.round(Math.hypot(nodes[a].x - nodes[b].x, nodes[a].y - nodes[b].y));
|
|
135
|
+
const serviceTimes = Object.fromEntries(customers.map((c) => [c.id, c.serviceTime]));
|
|
136
|
+
|
|
137
|
+
const routeConfig = { dist, serviceTimes, vehicleCapacity, maxStops, maxRouteTime };
|
|
138
|
+
|
|
139
|
+
// 1. Build Restricted Master Problem (RMP)
|
|
140
|
+
const master = new Model("CVRP_FleetMaster");
|
|
141
|
+
|
|
142
|
+
// Initial naive routes: 1 dedicated vehicle per customer
|
|
143
|
+
const initialRoutes = customers.map((c) => master.binary(`route_init_${c.id}`));
|
|
144
|
+
master.objective(minimize(sum(initialRoutes)));
|
|
145
|
+
|
|
146
|
+
// Each customer must be visited by exactly 1 vehicle
|
|
147
|
+
for (let i = 0; i < customers.length; i++) {
|
|
148
|
+
master.constraint(expr`${initialRoutes[i]} == 1`, `visit_${customers[i].id}`);
|
|
149
|
+
}
|
|
150
|
+
const visitConstrs = master.constraints;
|
|
151
|
+
|
|
152
|
+
console.log(`Initial state: 50 naive single-customer routes (50 vehicles).`);
|
|
153
|
+
console.log("Solving via Column Generation with dual-directed heuristic pricing...");
|
|
154
|
+
|
|
155
|
+
// 2. Solve via Column Generation
|
|
156
|
+
const result = await master.solveColumnGeneration({
|
|
157
|
+
finalizeAsMIP: true,
|
|
158
|
+
pricing: async () => {
|
|
159
|
+
// 1. Sort customers by shadow price (dual profit) descending
|
|
160
|
+
const candidates = customers
|
|
161
|
+
.map((c, i) => ({ id: c.id, demand: c.demand, dual: visitConstrs[i].dual ?? 0 }))
|
|
162
|
+
.filter((c) => c.dual > 0)
|
|
163
|
+
.sort((a, b) => b.dual - a.dual);
|
|
164
|
+
|
|
165
|
+
// 2. Solve pricing subproblem: find route with negative reduced cost (dualSum > 1)
|
|
166
|
+
const bestRoute = findImprovingRoute(candidates, routeConfig);
|
|
167
|
+
if (!bestRoute) return null; // No improving column found
|
|
168
|
+
|
|
169
|
+
// 3. Propose new column to Restricted Master Problem
|
|
170
|
+
const { travelTime, totalTime } = calcRouteTime(bestRoute, dist, serviceTimes);
|
|
171
|
+
const coefficients = {};
|
|
172
|
+
for (const id of bestRoute.slice(1, -1)) {
|
|
173
|
+
coefficients[`visit_${id}`] = 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
cost: 1,
|
|
178
|
+
type: "Binary",
|
|
179
|
+
coefficients,
|
|
180
|
+
meta: { tour: bestRoute, travelTime, totalTime },
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
console.log(`\nStatus: ${result.status}`);
|
|
186
|
+
console.log(`Fleet Size: ${Math.round(result.objectiveValue ?? 0)} vehicles (heuristic column generation)`);
|
|
187
|
+
console.log(`Total Columns Generated: ${result.columnsAdded}`);
|
|
188
|
+
|
|
189
|
+
console.log("\nSample routes selected in fleet:");
|
|
190
|
+
const activeRoutes = result.generatedColumns.filter((c) => (c.variable.value ?? 0) > 0.5);
|
|
191
|
+
for (const r of activeRoutes.slice(0, 5)) {
|
|
192
|
+
const meta = r.proposal.meta;
|
|
193
|
+
console.log(` ${meta?.tour.join(" -> ")} (Transit: ${meta?.travelTime}m, Total: ${meta?.totalTime}m)`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
runCVRP();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
</CodeRunner>
|
|
201
|
+
|
|
202
|
+
## Notes
|
|
203
|
+
|
|
204
|
+
- **Master Problem and Pricing Separation**: The master problem solves the LP relaxation and final set-partitioning MIP, while the `pricing` callback searches for negative-reduced-cost routes using cheapest insertion and 2-opt local search.
|
|
205
|
+
- **Dual Value Access**: Dual values from the master LP relaxation are read from `visitConstrs[i].dual` to prioritize customers with higher shadow prices during route construction.
|
|
206
|
+
- **Route Metadata**: Each proposed column attaches `{ tour, travelTime, totalTime }` under `meta`, allowing the ordered stop sequence to be inspected from `result.generatedColumns` after the solve completes.
|
|
@@ -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
|
> ```
|
|
@@ -132,9 +135,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
|
|
|
132
135
|
- 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
136
|
- Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
|
|
134
137
|
- 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
|
|
138
|
+
- **Satisfiability & Conflict Diagnosis (`await m.check(options?)`)**:
|
|
139
|
+
- 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"`.
|
|
140
|
+
- 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).
|
|
141
|
+
- 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
142
|
- **No concurrent mutation during async solve/check**:
|
|
139
143
|
- 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
144
|
|
|
@@ -154,7 +154,7 @@ m.constraint(and(or(isAdmin, hasMfa), not(xor(isAdmin, hasMfa))), "policy");
|
|
|
154
154
|
| :--- | :--- | :--- |
|
|
155
155
|
| **Trigger variable** | Numeric `Binary` variable (`m.binary()`) | `BoolVar` (`m.bool()`), `BoolOp`, or `Constraint` |
|
|
156
156
|
| **Consequent** | Linear `Constraint` (`<=`, `>=`, `==`) | `BoolVar`, `BoolOp`, or linear `Constraint` |
|
|
157
|
-
| **Target Solvers** | **All MIP/LP solvers** (HiGHS, GLPK, Z3) via Big-M linearization | **
|
|
157
|
+
| **Target Solvers** | **All MIP/LP solvers** (HiGHS, GLPK, Z3) via Big-M linearization | **All solvers** (HiGHS and GLPK via automatic 0-1 MIP linearization, Z3 natively) |
|
|
158
158
|
| **Best For** | Operations research & MIP optimization with an objective (`m.solve()`) | Rule engines, policy verification, and SMT feasibility (`m.check()`) |
|
|
159
159
|
|
|
160
160
|
---
|