FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Column generation for a cutting stock problem

Description
  • creating new variables and adding them to constraints and objective
  • basis in- and output
  • sorting and single knapsack algorithms
  • repeat-until statement
  • use of functions
  • hiding / unhiding constraints
  • resetting (deleting) constraints
The first version of this model (cutstk.mos) solves the knapsack subproblem heuristically, the second version (paper.mos) solves it as a second optimization problem, hiding (blending out) the constraints of the master problem. The third version defines the subproblem as a separate problem with the same model, repeatedly creating new problems (papersn.mos) or re-using/redefining the same problem (papers.mos), that is:
  • defining multiple problems within a model
  • switching between problems
A fourth version (master model: paperp[r].mos, submodel: knapsack[r].mos) shows how to implement the algorithm with two separate models, illustrating the following features of Mosel:
  • working with multiple models
  • executing a sequence of models
  • passing data via shared memory


Further explanation of this example: paper.mos, papers.mos, papersn.mos: 'Mosel User Guide', Section 11.2 Column generation, and the Xpress Whitepaper 'Embedding Optimization Algorithms', Section 'Column generation for cutting-stock' (also discusses a generalization to bin-packing problems); for the multiple model versions paperp.mos, paperms.mos see the Xpress Whitepaper 'Multiple models and parallel solving with Mosel', Section 'Column generation: solving different models in sequence'.


Source Files





cutstk.mos

(!*******************************************************
  * Mosel Example Problems                              *
  * ======================                              *
  *                                                     *
  * file cutstk.mos                                     *
  * ```````````````                                     *
  * Example for the use of the Mosel language           *
  * (Cutting stock problem, solved by column (= cutting *
  *  pattern) generation heuristic looping over the     *
  *  root node)                                         *
  *                                                     *
  * (c) 2008 Fair Isaac Corporation                     *
  *     author: S. Heipcke, 2001                        *
  *******************************************************!)

model CutStock                         ! Start a new model

uses "mmxprs","mmsystem"               ! Load the optimizer library

                                       ! Declare functions and procedures
                                       ! that are defined later
forward function colgen:real
forward function knapsack(c:array(range) of real, a:array(range) of real, 
                  b:real, N:integer, xbest:array(range) of integer):real

declarations
 NWIDTHS = 4                           ! Number of different widths
 RW = 1..NWIDTHS                       ! Range of widths
 MAXWIDTH = 91                         ! Max. roll width
 EPS = 1e-6                            ! Zero tolerance

 WIDTH: array(RW) of real              ! Possible widths
 DEMAND: array(RW) of integer          ! Demand per width
 PATTERNS: array(RW, RW) of integer    ! (Basic) cutting patterns

 pat: array(range) of mpvar            ! Rolls per pattern
 dem: array(RW) of linctr              ! Demand constraints
 minRolls: linctr                      ! Objective function
 solpat: array(RP:range) of real       ! Solution values for variables pat
end-declarations

 WIDTH:: [25.5, 22.5, 20.0, 15.0]
 DEMAND:: [78, 40, 30, 30]
 PATTERNS:: [3,0,0,0,
             0,4,0,0,
             0,0,4,0,
             0,0,0,6]

 forall(j in RW) create(pat(j))        ! Create NWIDTHS variables pat

 minRolls:= sum(j in RW) pat(j)        ! Objective: minimize no. of rolls

                                       ! Satisfy all demands
 forall(i in RW) dem(i):= sum(j in RW) PATTERNS(i,j) * pat(j) >= DEMAND(i)  

 forall(j in RW) do
  pat(j) is_integer                    ! Variables are integer and bounded
  pat(j) <= integer(ceil(DEMAND(j)/PATTERNS(j,j)))
 end-do 

 setparam("zerotol", EPS)              ! Set Mosel comparison tolerance

 starttime:= gettime
 objval:=colgen                        ! Solve the problem by using
                                       ! column generation

                                       ! Solution printing
 writeln("(", gettime-starttime, " sec) Optimal solution: ", 
       objval, " rolls, ", RP.size, " patterns")
 write("   Rolls per pattern: ")
 forall(i in RP) write(solpat(i),", ")
 writeln   

!************************************************************************
!  Column generation loop at the top node:
!    solve the LP and save the basis
!    get the solution values
!    generate new column(s) (=cutting pattern)
!    load the modified problem and load the saved basis
!************************************************************************
function colgen:real

 declarations
  MAXCOL = 10                          ! Max. no. of patterns to be generated

  dualdem: array(RW) of real           ! Dual values of demand constraints
  c,a: array(RW) of real
  indx,xbest: array(RW) of integer
  dw,zbest: real
  bas: basis
 end-declarations

  setparam("XPRS_CUTSTRATEGY", 0)      ! Disable automatic cuts
  setparam("XPRS_PRESOLVE", 0)         ! Switch presolve off
  npatt:=NWIDTHS
  npass:=1

  while(npass<=MAXCOL) do
    minimize(XPRS_LIN, minRolls)       ! Solve the LP
    savebasis(bas)                     ! Save the current basis
    returned:= getobjval               ! Get the objective value

                                       ! Get the solution values
    forall(j in 1..npatt)  solpat(j):=pat(j).sol
    forall(i in RW)  dualdem(i):=dem(i).dual
  
      ! Sort (basic) patterns into descending order shadow_price/width:
    forall(j in RW) do
      dw:=0
      forall(i in RW) 
        if (dualdem(i)/WIDTH(i) > dw) then
         dw:= dualdem(i)/WIDTH(i)
         k:= i
        end-if
      c(j):= dualdem(k)
      a(j):= WIDTH(k)
      indx(j):= k
      dualdem(k):= 0
    end-do

    zbest:=knapsack(c,a,MAXWIDTH,NWIDTHS,xbest)

    write("(", gettime-starttime, " sec) Pass ", npass, ": ")   
    if zbest < 1 then
      writeln("no profitable column found.\n")
      break
    else                                ! Print the new pattern
      writeln("new pattern found with marginal cost ", zbest-1)
      write("   Widths distribution: ")
      dw:=0
      forall(i in 1..NWIDTHS) do 
        write(WIDTH(indx(i)), ":", xbest(i), "  ")
        dw += WIDTH(indx(i))*xbest(i)
      end-do 
      writeln("Total width: ", dw)
 
      npatt+=1
      create(pat(npatt))                ! Create a new var. for this pattern
      pat(npatt) is_integer

      minRolls+= pat(npatt)             ! Add new var. to the objective
      dw:=0
      forall(i in RW)                 
        if xbest(i) > 0 then
         dem(indx(i))+= xbest(i)*pat(npatt)  ! Add new var. to demand constr.s
         if (ceil(DEMAND(indx(i))/xbest(i)) > dw) then
         dw:= integer(ceil(DEMAND(indx(i))/xbest(i)))
         end-if 
        end-if
      pat(npatt) <= dw                  ! Set upper bound on the new var.

      loadprob(minRolls)                ! Reload the problem
      loadbasis(bas)                    ! Load the saved basis
    end-if
    npass+=1
  end-do

end-function

!************************************************************************
!  Solve (heuristically) the integer knapsack problem
!    z = max{cx : ax<=b, x in Z^N}
!  with b=MAXWIDTH, N=NWIDTHS
!************************************************************************

function knapsack(c:array(range) of real, a:array(range) of real, 
                  b:real, N:integer, xbest:array(range) of integer):real
 declarations
  x: array(1..N) of integer
  z,ax,zbest: real
 end-declarations

(!
  write ("Solving z = max{cx : ax <= b; x in Z^n}\n   c   = ")
  forall(j in 1..N) write (c(j),", ")
  write("\n   a   = ")
  forall(j in 1..N) write(a(j),", ")
  write("\n   c/a = ")
  forall(j in 1..N) write(c(j)/a(j),", ")
  writeln("\n   b   = ", b)
!)

  z:=0                                  ! Value of working solution
  ax:=0                                 ! Resource use of current solution
  zbest:=0                              ! Value of best solution so far
  k:=0
  repeat 
      ! Construct a greedy solution using remaining items (k+1..N):
    forall(j in k+1..N) do
      x(j):= integer(floor((b-ax)/a(j)))
      z  += c(j)*x(j)
      ax += a(j)*x(j)
    end-do

      ! Compare new solution to best so far and update best if necessary:
    if z > zbest then
      zbest:= z
      forall(j in 1..N)  xbest(j):= x(j)
    end-if

      ! Backtrack:
      ! For each item type k used in solution, starting from least profitable,
      ! try deleting one item and replacing it by items of type k+1.
      ! If this gives a better linear solution, construct new integer solution.
      ! Otherwise, remove all items k and consider next item up (k--).
    k:=N-1
    while(k>0)
      if (x(k) <> 0) then
        z  -= c(k)
        ax -= a(k)
        x(k)-=1
        if (z + c(k+1) * (b-ax)/a(k+1) >= zbest + EPS) then
          break
        else
          z  -= x(k) * c(k)
          ax -= x(k) * a(k) 
          x(k):= 0
          k -=1
        end-if
      else    
        k -=1  
      end-if  
  until (k<1)

(! 
  write("   z = ", zbest, "\n   x = ")
  forall(j in 1..N) write(xbest(j), ", ")
  writeln
!)
  returned:=zbest
end-function

end-model


Back to examples browserPrevious exampleNext example