Inventory optimization, to the point
It’s the classic optimization problem: the order has to go out today, and the sales figures of the last few years give a clear signal on average. But real demand is not an average — it fluctuates from day to day and therefore follows a distribution.
Let’s say a unit in stock costs you €1 per day. Use the slider to set what a missing unit costs you by comparison.
You describe your situation
How much does a missing unit cost you?
That’s all you need to provide. Quicopt finds the optimal order quantity.
demand coveredshortfall
✗ planned with the average✓ Quicopt’s optimum
That works out to a 80% service level. Covering the rest completely would cost more in stock than it saves in shortages.
Which order quantity really pays off across all possible scenarios?
If you plan with the average
Optimal order quantity Q* = 243
You could simply take average demand and plan with it. But that misleads you, because the costs don’t rise evenly: a shortfall weighs heavier than surplus stock. Anyone working only with the average overlooks these expensive outliers. Quicopt does the opposite: it evaluates every possible day individually with your actual costs, and averages only afterwards. That way the bad days are counted correctly, and you get the order quantity that is cheapest across all scenarios. In short: don’t average the demand — choose the order quantity whose expected cost across all possible days is lowest.
This demo deliberately shows the simplest example, with a single product, so the principle is immediately clear. In operation, Quicopt optimizes hundreds of items at once, with shared budget, limited warehouse space and supply constraints.
One step further
For a single product there is still a formula for the best order quantity. The moment several products share the same space, that ends: what product A gets, product B goes without. Here there are eight products, each with its own demand and its own shortage cost, sharing 1020 units of shelf space — less than their average demand adds up to.
| Product | Avg demand | Shortage costs | by average | optimized |
|---|---|---|---|---|
| A | 200 | €4 | 170 | 194 |
| B | 150 | €4 | 128 | 131 |
| C | 90 | €12 | 76 | 112 |
| D | 300 | €2 | 255 | 80 |
| E | 120 | €9 | 102 | 142 |
| F | 60 | €15 | 51 | 83 |
| G | 180 | €3 | 153 | 168 |
| H | 100 | €7 | 85 | 110 |
Product D is the striking one: it has by far the highest average demand and still gets the least — because running out of it only costs €2. Products C and F are stocked the other way, above their own average. No rule built on averages arrives at this split.
That is all of it. The code runs against Quicopt’s API endpoint — no solver runs on your machine.
using QuicoptClient, JuMP
products = ["A", "B", "C", "D", "E", "F", "G", "H"]
mu = [200.0, 150.0, 90.0, 300.0, 120.0, 60.0, 180.0, 100.0] # average daily demand
sigma = [ 20.0, 55.0, 30.0, 30.0, 45.0, 25.0, 18.0, 38.0] # how much it varies
short = [ 4.0, 4.0, 12.0, 2.0, 9.0, 15.0, 3.0, 7.0] # cost of one missing unit
hold = 1.0 # cost of one unit in stock
capacity = 1020.0 # shared shelf space
m = Model()
set_scenarios(m, 500; seed = 7) # 500 possible days, drawn once
@variable(m, Q[1:8] >= 0, Int) # what we order, in whole units
@variable(m, d[1:8]) # demand: one random variable per product
for i in 1:8
set_distribution(m, d[i], :normal, mu[i], sigma[i]; name = Symbol("demand_", products[i]))
end
@constraint(m, sum(Q) <= capacity) # everything has to fit on the shelf
cost = sum(short[i] * max(d[i] - Q[i], 0.0) + hold * max(Q[i] - d[i], 0.0) for i in 1:8)
@objective(m, Min, expectation(cost)) # expected cost over all 500 days
result = solve(m)
▄▀▀▀▄ ▄ █ █ █ █ █ ▄▄ ▄▀▀▀▄ ▄▀▀▀▄ █▀▀▀▄ ▀█▀▀ █ ▀▄▀ █ █ █ █ ▄ █ █ █▄▄▄▀ █ ▄ ▀▀ ▀ ▀▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀ █ ▀▀ ├── status: heuristic ├── feasible: true ├── objective: 1041.8957479341643 ├── x: x1=194, x2=131, x3=112, x4=80, x5=142, x6=83, … (8 variables) └── solve_time: 1.0182 s
planned with the average : 1381.6 EUR/day optimized : 1041.9 EUR/day product avg demand by average optimized A 200 170 194 B 150 128 131 C 90 76 112 D 300 255 80 E 120 102 142 F 60 51 83 G 180 153 168 H 100 85 110
Quicopt works purely primally: it evaluates your cost function scenario by scenario without presupposing any structure. So you can formulate costs and conditions that have no “smooth” mathematical formulation. The two examples below each add exactly one line to the model above, and nothing else changes:
A flat cost falls due the moment a product cannot be served
@objective(m, Min, expectation(cost) + 50.0 * sum(prob(d[i] - Q[i], ≥, 0.0) for i in 1:8))
A step function jumps at the first missing unit and then stays flat. Precisely the kind of term that defeats methods relying on derivatives. Against the simpler model above, this comes out more expensive: → €1,215.7 per day
A delivery guarantee as a hard requirement
@constraint(m, prob(sum(max(d[i] - Q[i], 0.0) for i in 1:8), ≤, 350.0) >= 0.90)
This constraint reads as “on 90% of days, total shortfall stays under 350 units.” So you see straight away what such a guarantee would cost you — about 5% against the roughly €1042 per day above: → €1,095.7 per day
For context: the solver status reads “heuristic”: for this class of objective there is no proof of optimality, and the solver returns the best solution found. Several allocations come within less than a per mille of each other, so a later version of the solver may return a slightly different one.