Purchasing with price breaks
There are three suppliers of a good, and they have quoted various prices for various quantities of product. We want to buy at least total cost, yet not buy too much from any one supplier. Each supplier offers decreasing prices for increased lot size, in the form of incremental discounts (the data is given in Table Price profile). We wish to buy 600 items in total.
Supplier | Unit price 1 | Breakpoint 1 | Unit price 2 | Breakpoint 2 | Unit price 3 | Breakpoint 3 |
---|---|---|---|---|---|---|
1 | 9.2 | 100 | 9 | 200 | 7 | 1000 |
2 | 9 | 50 | 8.5 | 250 | 8.3 | 2000 |
3 | 11 | 100 | 8.5 | 300 | 7.5 | 4000 |
For example, if you buy 150 items from supplier 1, you pay 9.2· 100+9· 50. If you buy 225 items from supplier 1, you pay 9.2· 100+9· 100+7· 25.
Model formulation
We write SUPPL for the set of suppliers, BREAK0 for the set of breakpoints including 0, and BREAK1 the set of breakpoints starting with the first point where unit cost changes, that is without 0. For every supplier s, Bsb (b ∈ BREAK1) denotes the breakpoint where the unit cost changes from COSTsb to COSTs,b+1; Bs0 is always 0. Furthermore REQ denotes the total amount we wish to buy, and MAXPERCs the maximum percentage that may be bought from supplier s.

Figure 5.1: Price curve of a supplier
To formulate this problem, we introduce two sets of variables. buys represents the quantity bought from supplier s. These quantities are calculated via weight variables wsb associated with the breakpoints. The weight variables for every supplier s form an SOS-2 (Special Ordered Set of Type 2), that means that at most two neighboring variables wsb in the order induced by the coefficients BRbs may be non-zero. Under the condition that the weight variables sum up to 1 for every supplier we can use these variables to linearly interpolate between the breakpoints to obtain the quantities bought:
∀ s ∈ SUPPL: ∑b ∈ BREAK0 wsb = 1
Furthermore, we need to express the constraints that the total requirement must be met and that no more than a certain given percentage may be bought of each supplier.
∀ s ∈ SUPPL: buy(s) ≤ MAXPERCs/100·REQ
The total cost at the breakpoints, CBsb, is given by the following recursive formula:
∀ b ∈ BREAK1: CBsb = CBs,b-1 + COSTsb· (Bsb-Bs,b-1)
With these total cost values per supplier we can formulate the objective function which is to minimize the total cost of the purchase.
Implementation
The Mosel implementation of the mathematical model looks as follows (file purch.mos).
model "Purchase" uses "mmxprs" declarations NB = 3 BREAK1 = 1..NB ! Price breaks BREAK0 = 0..NB ! Price breaks including 0 SUPPL = 1..3 ! Suppliers COST: array(SUPPL,BREAK1) of real ! Unit cost B: array(SUPPL,BREAK0) of real ! Breakpoints (quantities at which unit ! cost changes) CB: array(SUPPL,BREAK0) of real ! Total cost at break points MAXPERC: array(SUPPL) of real ! Maximum percentages for each supplier REQ: real ! Total quantity required buy: array(SUPPL) of mpvar ! Quantity to purchase from supplier s w: array(SUPPL,BREAK0) of mpvar ! Weights at breakpoint k for supplier s end-declarations initializations from 'purch.dat' COST B MAXPERC REQ end-initializations ! Calculate total cost at breakpoints forall(s in SUPPL) do CB(s,0):= 0 forall(b in BREAK1) CB(s,b):= CB(s,b-1) + COST(s,b)*(B(s,b)-B(s,b-1)) end-do ! Objective: sum of costs*weights MinCost:= sum(s in SUPPL, b in BREAK0) CB(s,b) * w(s,b) ! Define buy and also order the weight variables by breakpoint quantities forall(s in SUPPL) sum(b in BREAK0) B(s,b) * w(s,b) = buy(s) ! The convexity row (w sum to 1) forall(s in SUPPL) sum(b in BREAK0) w(s,b) = 1 ! The minimum quantity that must be bought sum(s in SUPPL) buy(s) >= REQ ! No more than the maximum percentage from each supplier forall(s in SUPPL) buy(s) <= MAXPERC(s)*REQ/100.0 ! Define the w as SOS-2 forall(s in SUPPL) makesos2(union(b in BREAK0) {w(s,b)}, sum(b in BREAK0) B(s,b)*w(s,b)) minimize(MinCost) ! Solve the MIP-problem writeln("Minimum total cost: ", getobjval) forall(s in SUPPL) writeln(" buy(",s,"): ", getsol(buy(s)), " cost: ", getsol(sum(b in BREAK0) CB(s,b) * w(s,b)) ) end-model
In the model above we use the procedure makesos2 for the definition of the SOS-2. Alternatively, if we wish to use is_sos2, then the weight coefficients B(s,b) all need to be augmented by some non-zero constant EPS since Mosel does not accept 0-valued weights with is_sos2.
forall(s in SUPPL) sum(b in BREAK0) (B(s,b)+EPS) * w(s,b) is_sos2
The minimum cost of €5,435 is obtained by buying 240 items from supplier 1, 210 from supplier 2, and 150 items from supplier 3.