Initializing help system before first use

Purchase: A model for optimal purchasing with price-breaks


Type: Purchasing with price breaks
Rating: 3 (intermediate)
Description: 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. We wish to buy 600 items in total.
  • complex MIP model
  • data input from file, including parameters for dimensioning
  • splitting the declaration section to read first the parameters that are used to declare the data arrays
  • using SOS-2
File(s): purch.mos
Data file(s): purch.dat


purch.mos
(!*******************************************************
  * Mosel Example Problems                              *
  * ======================                              *
  *                                                     *
  * file purch.mos                                      *
  * ``````````````                                      *
  * Example for the use of the Mosel language           *
  * (Purchasing problem. Using SOS-2.)                  *
  *                                                     *
  * (c) 2008 Fair Isaac Corporation                     *
  *     author: S. Heipcke, 2001, rev. Feb. 2010        *
  *******************************************************!)
(! The formulation of this model is tricky because the discounts are 
   all-quantity, so the graph of cost against quantity purchased is
   discontinuous.
   To maintain sanity in the special ordered set formulation, we must
   coarsen the discontinuity by stopping purchases just at the break 
   point.
!)

model Purchase                  ! Start a new model

uses "mmxprs"                   ! Load the optimizer library

declarations
 NBS,NBR: integer               ! Problem parameters
end-declarations

initializations from 'Data/purch.dat'
 NBS as "Nb suppliers"
 NBR as "Nb price breaks"
end-initializations

declarations
 RS=1..NBS                ! Range of suppliers
 RB=1..NBR                ! Range of price breaks
 NB2=2*NBR
 RB2=1..NB2
 delta = 0.10                   ! Base coarsening factor
 
 UC: array(RS,RB) of real       ! Unit cost
 BR: array(RS,RB) of real       ! Breakpoints (quantities at which unit cost changes)
 X: array(RS,RB2) of real       ! Coarsened break points
 C: array(RS,RB2) of real       ! Total cost at break points
 DELTA: array(RB2) of real      ! Coarsening factors
 MAXPERC: array(RS) of real     ! Maximum percentages for each supplier
 REQ: real                      ! Total quantity required

 x: array(RS) of mpvar          ! Quantity to purchase from supplier s
 lam: array(RS,RB2) of mpvar    ! Weights at breakpoint k for supplier s
end-declarations

                                ! Read data from file
initializations from 'Data/purch.dat'
 UC BR MAXPERC REQ
end-initializations

                                ! Recursively define coarsening factors
 DELTA(1):= 0
 DELTA(2):= -delta
 forall(b in 3..NB2) DELTA(b):= -DELTA(b-1)

! Pick out the data from the tables into which they have just been read.
 forall(s in RS) do
  C(s,1):= 0                    ! First total cost and (new) breakpoint
  X(s,1):= 0                    ! are (0,0). Rest calculated:
 end-do
 forall(s in RS, b in 2..NB2) do
  C(s,b):= UC(s,(b+1) div 2) * BR(s,b div 2) ! Unit price*quantity
  X(s,b):= BR(s,b div 2) + DELTA(b)          ! Coarsened grids
 end-do

                                ! Objective: sum of costs*weights
 MinCost:= sum(s in RS,b in RB2) C(s,b) * lam(s,b)

                                ! Define x and also order the lam variables 
                                ! by breakpoint quantities
 forall(s in RS) DefX(s):= sum(b in RB2) X(s,b) * lam(s,b) = x(s)

                                ! The convexity row (lam sum to 1)
 forall(s in RS) Conv(s):= sum(b in RB2) lam(s,b) = 1.0

                                ! The minimum quantity that must be bought
 MustBuy:= sum(s in RS) x(s) >= REQ

                                ! No more than the maximum percentage 
                                ! from each supplier
 forall(s in RS) x(s) <= MAXPERC(s)*REQ/100.0

! Define the lam as SOS2 as we can linearly interpolate between the
! breakpoints. 
 forall(s in RS)  
  makesos2(union(b in RB2) {lam(s,b)}, sum(b in RB2) X(s,b)*lam(s,b))

! Alternative formulation:
!   The weight coefficients X (as in the DefX constraints), are all 
!   augmented by delta since Mosel does not accept 0-valued weights
!   with `is_sos2'.
! forall(s in RS) SetLam(s):= sum(b in RB2) (X(s,b)+delta) * lam(s,b) is_sos2
  
 minimize(MinCost)              ! Solve the MIP-problem

                                ! Print out the solution
 writeln("Solution:\n Objective: ", getobjval)
 forall(s in RS) do
  write(" x(",s,"):", x(s).sol, "  ( = ")
  forall(b in RB2 | lam(s,b).sol>0)
   write(X(s,b)," * lam(",s,",",b,"):", lam(s,b).sol, "; ")   
  writeln(")")
 end-do
 
end-model

Background:

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, and these are all-item prices.

For example :

Supplier Max %age   Price 0-100       Price 101-200   Price 200+
Bob            40     9.2    100        9    200        7   1000
                       ^      ^
                    price    breakpoint

i.e. if you buy 150 items from Bob, the total cost is 9*150.