(!******************************************************
   Mosel Example Problems
   ======================

   file purchase.mos
   `````````````````
   TYPE:         Purchasing with price breaks
   DIFFICULTY:   3
   FEATURES:     MIP problem, modeling a piecewise linear function with SOS-2,
                 graphical representation of data
   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.     
   FURTHER INFO: `Applications of optimization with Xpress-MP teaching 
                 material', Section 2.4 `SOS-2: Purchasing with price breaks';
                 `Applications of optimization with Xpress-MP', 
                 Section 3.4.5 `Special Ordered Sets of type 2'

   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, Jan. 2001, rev. July 2020
*******************************************************!)

model "Purchase"
 uses "mmxprs", "mmsvg"

 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 'purchase.dat'
  COST B MAXPERC REQ
 end-initializations

! Graphical representation of data
 FACT:=100
 svgsetgraphviewbox(0, 0, max(s in SUPPL) B(s,NB), FACT*max(s in SUPPL,b in BREAK1) COST(s,b)+1)
 svgsetgraphlabels("Units","Cost per unit")
 svgsetgraphstyle(SVG_STROKEWIDTH,5)
 forall(s in SUPPL) do
  svgaddgroup("SG"+s, "Supplier "+s)
  svgaddline(sum(b in BREAK1) [B(s,b-1), FACT*COST(s,b), B(s,b), FACT*COST(s,b)])
 end-do

! 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) Buy(s):= sum(b in BREAK0) B(s,b) * w(s,b) = buy(s)

! The convexity row (w sum to 1)
 forall(s in SUPPL) OneW(s):= sum(b in BREAK0) w(s,b) = 1

! The minimum quantity that must be bought
 Demand:= 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 as we can linearly interpolate between the
! breakpoints. 
 forall(s in SUPPL)  
  makesos2(union(b in BREAK0) {w(s,b)}, sum(b in BREAK0) B(s,b)*w(s,b))

! Alternative formulation:
!   The weight coefficients B are all augmented by EPS 
!   since Mosel does not accept 0-valued weights with `is_sos2'.
! forall(s in SUPPL) sum(b in BREAK0) (B(s,b)+10E-20) * w(s,b) is_sos2

  
 minimize(MinCost)              ! Solve the MIP-problem

 writeln("Minimum cost: ", getobjval)
 forall(s in SUPPL) writeln(" buy(",s,"): ", getsol(buy(s)))

 svgsave("purchase.svg")
 svgrefresh
 svgwaitclose("Close browser window to terminate model execution.", 1)
end-model
