How it works
Each day, vinpack runs a three-layer pipeline and every layer is a knapsack-family problem:
| Layer | Planning stage | Problem | Exact method | Fast method |
|---|---|---|---|---|
| L1 Allocation | Build plan / firming: which orders get built this cycle? | Multidimensional knapsack: orders compete for 10 scarce supply pools | MIP (HiGHS) | Dual-ratio greedy |
| L2 Matching | Vehicle plan: which delivery center fulfils each order? | Generalized assignment: every filled order goes to one DC, and DCs have handling capacity | MIP | Regret greedy + local search |
| L3 Loading | Routing: how many carriers leave each DC? | Bin packing: order load units onto carriers of capacity 150 | Column generation, then an arc-flow MIP if needed | FFD / BFD |
| L0 Kernel | — | 0-1 knapsack | Numba DP (float profits) | Branch and bound |
The L0 kernel is the engine inside L3: every column-generation step asks it "what is the most valuable carrier load at today's prices?"
Re-solving every day without thrashing the plan
The plan is re-solved every day as orders arrive, customers cancel, supply drifts and a DC loses capacity. A plan that is re-optimised from scratch each day moves customers around constantly. So each daily solve penalises changes to yesterday's decisions:
Because \(x\) is binary, \(|x_j - \bar x_j|\) is linear: it equals \(x_j\) where \(\bar x_j = 0\) and \(1 - x_j\) where \(\bar x_j = 1\). No extra variables or constraints are needed. Orders filled for three consecutive days count as promised and carry a large soft lock. It is soft so that a supply shock can still break a promise instead of making the model infeasible, and every broken promise is counted. L2 uses the same idea for DC reassignments. Sweeping λ traces the value-vs-stability frontier.
Explaining the plan
The explainer can only cite facts computed by the solver:
- LP shadow prices of each supply pool: what one more unit is worth.
- Reduced cost of each order: its value minus the shadow cost of the supply it would use. A negative reduced cost means the order is priced out.
- What-if re-solves of the exact same day: force an order in, or add supply. The day is rebuilt deterministically from the seed and yesterday's stored plan. Each what-if reports the value change, the churn change and the churn-adjusted objective change, so a what-if that buys value by reshuffling yesterday's plan is not mistaken for a free win.
The optional Claude agent (claude-haiku-4-5 by default, set by VINPACK_MODEL; only used if an API key is set) calls these as tools. After it
answers, every number in the answer is checked against the tool outputs, and numbers without a
source are flagged. What-ifs are proposals: nothing changes until a planner clicks Approve.
Approving replays the run from that day into a new run id, so the original run is never
overwritten.
Where the data comes from
Every number comes from published academic benchmark files, fetched and checked against a SHA-256 manifest:
- L1: OR-Library
mknapcb5problem 10.250-20 (Chu & Beasley 1998): 250 orders, 10 pools, tightness 0.75. - L2: OR-Library
gapc10x200-4 plus the first 50 jobs ofgapc10x100-3. Type-C capacity is defined as \(b_i = 0.8 \sum_j r_{ij} / m\). That formula is additive over jobs, so it is recomputed exactly on the concatenated columns and then scaled by the expected fill rate. - L3: Falkenauer
u250_00item sizes (OR-Librarybinpack2), carrier capacity 150.
The composed scenario is not a published benchmark. Each layer is also benchmarked on its own against published optima on the unmodified instances. See Results.