Placing stock across a food-distribution network
Conservative inventory keeps shelves full and centres overflowing. The question was whether one model could decide, SKU by SKU, where stock should live, which centre should serve each demand point, and how many pallets to hold — and how much capacity and transport cost that would free.
The cost of playing it safe
A food distributor's inventory decisions ripple into centre capacity, transport cost, stock-outs and overstock all at once. A conservative policy — keep plenty of every SKU close to where it sells — protects service but fills storage, multiplies safety stock, and moves goods between centres that did not need to hold them.
After interviews with the business on ordering logic, inventory policy, capacity and transport conditions, the objective was set as improving capacity utilization and reducing transport cost while keeping supply stable. That made it an optimization problem rather than a forecasting one: given what each centre is likely to sell, where should stock live?
Three decisions, one model
For every SKU the model decides together:
- Storage — which centres hold it, up to a limit per SKU; centres differ by site and storage condition, and some can only receive.
- Sourcing — which storage centre serves each demand point, one source per point, avoiding lanes that cannot carry the item.
- Size — how many whole pallets each storage centre keeps: lead-time demand plus a safety buffer of three standard deviations, never below the supplier's minimum order, and within the centre's capacity.
The objective is transport cost over four weeks plus a weighted holding cost per pallet. The interesting part is the safety buffer. When a hub serves several demand points, their uncertainties pool: the buffer grows with the square root of the summed variances, not with their sum. Consolidating a SKU into fewer hubs therefore saves pallets even when it adds transport — and the model has to weigh exactly that trade, SKU by SKU, under shared capacity.
| Ingredient | How it enters |
|---|---|
| Demand | Per SKU and centre, a weighted mean and spread from the last 90 days — 80 / 15 / 5 % by month — measured from the first day the item sold there. |
| Safety stock | lead time × mean + 3 × √(lead time × Σ variance) over the demand points a hub serves. |
| Pallets | Rounded up to whole pallets, at least the minimum order quantity, zero if the centre does not hold the item. |
| Feasibility | Checked per SKU before the model: an item whose demand cannot be reached from any allowed centre is set aside instead of making the whole model infeasible. |
Live model, solved on a Python server for a generated network of eight centres and 24 SKUs. Left: every SKU stocked where it sells. Right: the optimized storage, sourcing and pallet counts. Change the holding-cost weight to watch the model consolidate harder as storage gets more expensive; "New network" solves a fresh one. One SKU is planted to be unreachable and is caught by the pre-check. Open the live model on its own page ↗
Architecture, stack and core formulation
A notebook-level feasibility study: recency-weighted demand statistics, a per-SKU feasibility check and one mixed-integer model with a quadratic safety-stock constraint, solved with SCIP.
Recency-weighted statistics
Mean and standard deviation of daily demand per item and location, with recent months weighted more.
Can it be served at all?
For each SKU, check that one to three centres can cover its demand points within capacity — a single unreachable SKU would make the whole model infeasible.
One MIQCP
Storage, single sourcing, pooled safety stock and whole pallets in one nonconvex mixed-integer quadratic model.
Tables for the business
Placement, sourcing, pallets and capacity use per centre, with cost and pallet totals.
| Layer | Technology | What it does here |
|---|---|---|
| Data | Python, pandas, NumPy | Daily demand per item × location, lanes and costs, lead times, MOQ and case packs |
| Statistics | Recency-weighted mean and variance | Lead-time demand and safety-stock inputs |
| Feasibility | Combinatorial pre-check | Filters SKUs that no centre combination can serve |
| Solver | SCIP through PySCIPOpt | Nonconvex MIQCP with a time limit and an optimality-gap target |
| Form | Jupyter notebook | Proof of concept; no service layer |
y_icj ∈ {0,1} centre c supplies demand point j with item i (single source)
z_ic ∈ {0,1} item i stocked at centre c p_ic ∈ ℤ₊ pallets
min Σ cost_cj · w_i · μ_ij · H · y_icj + w_s · Σ p_ic
s.t. Σ_c y_icj = 1, y_icj ≤ z_ic, Σ_c z_ic ≤ K
S_ic² = LT_i · Σ_j σ_ij² · y_icj pooled safety stock
R_ic ≥ LT_i · Σ_j μ_ij · y_icj + 3·S_ic, R_ic ≥ MOQ_i · b_i · z_ic
b_i·p_ic ≥ R_ic, b_i·p_ic ≤ R_ic + b_i − 1 p = ⌈R / b⌉
Σ_i p_ic ≤ cap_c- Why it is nonconvex. The equality S² = LT·Σσ²y is quadratic; the second-order-cone form S ≥ ‖√LT·σ∘y‖₂ would make the model convex — noted as the next step.
- Pooling drives the trade. Safety stock grows with the square root of pooled variance, so fewer hubs mean fewer pallets but longer lanes.
- Feasibility first. Filtering unreachable SKUs before the solve keeps a full-scale model from being infeasible.
| Component | In production | In the live model above |
|---|---|---|
| Solver | One MIQCP in SCIP over thousands of SKUs × tens of centres | Per-SKU plan enumeration + an integer program (SciPy / HiGHS) choosing one plan per SKU |
| Safety stock | Quadratic constraint inside the model | Computed exactly for each enumerated plan |
| Scale | Thousands of SKUs × tens of centres | 24 SKUs × 8 centres generated from a seed |
Inside the model
A square root inside an integer program
Written directly, the pooled buffer is a quadratic equality — the squared buffer equals the lead-time-weighted sum of variances of the points assigned to the hub — which makes the model a non-convex mixed-integer quadratically constrained program. It was solved with SCIP, which branches on the non-convexity; the same constraint can also be written as a second-order cone, which keeps it convex and is the direction for scaling up. Linking constraints tie assignment to storage, force the rounded-up pallet count, and enforce capacity and self-supply.
Methods compared
The study did not assume the MIP was the answer. Mathematical programming, search-based optimization and reinforcement learning were reviewed against each other, and a simulation structure was built to evaluate how a change in policy moves capacity, cost and service level — so the business could test alternatives rather than accept a single number.
How the demo solves it
The live model keeps the same ingredients but solves a small network a different way: for each SKU it enumerates every feasible set of storage centres, routes each demand point to its cheapest open hub, and sizes the pooled pallets; a small integer program then picks one plan per SKU subject to centre capacity. For a network this size that gives the same optimum in milliseconds, and it makes the trade-off visible.
What it showed
As a feasibility study, the result was a case rather than a deployment: the optimization confirmed room to secure centre capacity and cut transport cost, and it proposed moving from conservative stocking to demand-based inventory operation. Just as important, it left a decision environment — data marts joining inventory, demand and capacity, plus the model and simulation — in which inventory policy alternatives can be tested before they are tried.
Limitations
- This was a feasibility study; the policy was proposed, not rolled out, and no operational savings are claimed here.
- Single sourcing per demand point and a fixed service factor (three standard deviations) are modelling choices, not business constraints.
- The demand statistics are recency-weighted history; there is no forecast model for promotions or new items.
- The live model uses plan enumeration and a linear integer program instead of the original non-convex solver; it is exact only because the network is small.
About the demo and confidentiality
The centres, SKUs, demand histories, lanes and costs in the embedded model are generated from a seed. No network layout, item master, cost table or result from the real study appears here.