|   | |||||||||||
| 
 | |||||||||||
| 
 Cut generation for an economic lot-sizing (ELS) problem Description This model implements various forms of cut-and-branch and branch-and-cut algorithms. In its simplest form (looping over LP solving) it illustrates the following
		    features:
                     
 
 Another implementation (main model: runels.mos, submodel: elsp.mos) parallelizes the execution of several model instances, showing the following features: 
 Further explanation of this example: elscb.mos, elsglobal.mos, runels.mos: Xpress Whitepaper 'Multiple models and parallel solving with Mosel', Section 'Solving several model instances in parallel'. 
 Source Files By clicking on a file name, a preview is opened at the bottom of this page. Data Files elscb_graph.mos 
(!*******************************************************
   Mosel Example Problems
   ======================
   file elscb_graph.mos
   ````````````````````
   Economic lot sizing, ELS, problem
   Basic version with large data set and logging callbacks.
   Economic lot sizing (ELS) considers production planning 
   over a given planning horizon. In every period, there is 
   a given demand for every product that must be satisfied by 
   the production in this period and by inventory carried over 
   from previous periods.
   A set-up cost is associated with production in a period, 
   and the total production capacity per period is limited. 
   The unit production cost per product and time period is 
   given. There is no inventory or stock-holding cost.
       
   - Using the GAPNOTIFY and CHECKTIME callbacks -  
   - Drawing progress graph of MIP gap -
       
   (c) 2012 Fair Isaac Corporation
       author: S. Heipcke, Sep. 2012, rev. Sep. 2022
  *******************************************************!)
model "ELS with logging callbacks"
 uses "mmxprs","mmsystem","mmsvg"
 parameters
  DATAFILE = "els4.dat"
  T = 60
  P = 4
 end-parameters 
 forward function cb_node: boolean 
 forward procedure cb_intsol
 forward procedure cb_gapnotify(rt,at,aot,abt:real)
 forward function cb_checktime: boolean 
 declarations
  TIMES = 1..T                               ! Range of time
  PRODUCTS = 1..P                            ! Set of products
  DEMAND: array(PRODUCTS,TIMES) of integer   ! Demand per period
  SETUPCOST: array(TIMES) of integer         ! Setup cost per period
  PRODCOST: array(PRODUCTS,TIMES) of integer ! Production cost per period
  CAP: array(TIMES) of integer               ! Production capacity per period
  D: array(PRODUCTS,TIMES,TIMES) of integer  ! Total demand in periods t1 - t2
  produce: array(PRODUCTS,TIMES) of mpvar    ! Production in period t
  setup: array(PRODUCTS,TIMES) of mpvar      ! Setup in period t
  starttime, logtime, objval, mipgap: real   ! Scalars used for logging info
  ctchecks, ctnode: integer                  ! Counters used in callbacks
 end-declarations
 initializations from DATAFILE
  DEMAND SETUPCOST PRODCOST CAP
 end-initializations
 forall(p in PRODUCTS,s,t in TIMES) D(p,s,t):= sum(k in s..t) DEMAND(p,k)
! Objective: minimize total cost
 MinCost:= sum(t in TIMES) (SETUPCOST(t) * sum(p in PRODUCTS) setup(p,t) + 
                            sum(p in PRODUCTS) PRODCOST(p,t) * produce(p,t) )
! Satisfy the total demand
 forall(p in PRODUCTS,t in TIMES) 
   Dem(p,t):= sum(s in 1..t) produce(p,s) >= sum (s in 1..t) DEMAND(p,s)
! If there is production during t then there is a setup in t
 forall(p in PRODUCTS, t in TIMES) 
  ProdSetup(p,t):= produce(p,t) <= D(p,t,getlast(TIMES)) * setup(p,t)
! Capacity limits
 forall(t in TIMES) Capacity(t):= sum(p in PRODUCTS) produce(p,t) <= CAP(t)
! Variables setup are 0/1
 forall(p in PRODUCTS, t in TIMES) setup(p,t) is_binary 
! Uncomment to get detailed MIP output
 setparam("XPRS_VERBOSE", true)
 
! All cost data are integer, we therefore only need to search for integer
! solutions
 setparam("XPRS_MIPADDCUTOFF", -0.999)
!**** Setting callbacks for logging
 setcallback(XPRS_CB_INTSOL, ->cb_intsol)
(! The INTSOL callback implements the stopping criterion
   'Stop if model runs for more than 60sec and new solution is just slightly 
   better'. 
   A simpler version, namely 'Stop MIP search after n seconds without a new 
   incumbent' can be obtained by setting the control MAXSTALLTIME:
 setparam("XPRS_MAXSTALLTIME", 60)
!)
 setcallback(XPRS_CB_PRENODE, ->cb_node) 
 mipgap:= 0.10
 setparam("XPRS_MIPRELGAPNOTIFY", mipgap)
 setcallback(XPRS_CB_GAPNOTIFY, ->cb_gapnotify) 
! Invoke the CHECKTIME callback once every 4 seconds only:
 setparam("XPRS_CALLBACKCHECKTIMEDELAY", 4000)
 setcallback(XPRS_CB_CHECKTIME, ->cb_checktime)
 ctchecks:=0
  
!**** Configuration of graphical output
 svgaddgroup("msg", "Model output")
 svgsetstyle(SVG_FONTSIZE, "6pt")
 svgaddgroup("bbound", "Best bound")
 svgaddgroup("sol", "Solutions")
 xoffset:=-150.0
 lastxb:=0.0; lastxs:=0.0; 
 svgsetgraphlabels("Time (in sec)", "MIP gap")
 svgsetreffreq(5)          ! High update frequency
 svgrefresh
 starttime:=gettime
 logtime:=starttime
!**** Solve the problem
! Stop after root LP to initialize graph setup info
 minimize(XPRS_LPSTOP,MinCost)
 initval:=getobjval
 indl:=initval*1.01; lastys:=initval; lastyb:=initval;
 svgsetgraphviewbox(xoffset-10,initval,300,400)
! Continue solving
 minimize(XPRS_CONT,MinCost)
 case getparam("XPRS_MIPSTATUS") of
   XPRS_MIP_SOLUTION:
     svgaddtext("msg", xoffset, indl, "Search incomplete")
   XPRS_MIP_OPTIMAL:
     svgaddtext("msg", xoffset, indl, "Optimality proven")
 end-case 
 bbound:=getparam("XPRS_BESTBOUND")
 if bbound-lastyb>0 then
   grtime:=2*gettime
   svgaddline("sol", lastxs, lastys, grtime, objval)
   svgaddline("bbound", lastxb, lastyb, grtime, bbound)
 end-if
 svgrefresh
 writeln("Time: ", gettime-starttime, "sec,  Nodes: ", getparam("XPRS_NODES"),
         ",  Solution: ", getobjval) 
 write("Period  setup    ")
 forall(p in PRODUCTS) write(strfmt(p,-7))
 forall(t in TIMES) do
  write("\n ", strfmt(t,2), strfmt(getsol(sum(p in PRODUCTS) setup(p,t)),8), "     ")
  forall(p in PRODUCTS) write(getsol(produce(p,t)), " (",DEMAND(p,t),")  ")
 end-do
 writeln
 svgwaitclose("Close browser window to terminate model execution.", 1)
!*****************************************************************
!@doc.descr Function called at every B&B node
!@doc.info Return value 'true' marks node as infeasible
 function cb_node: boolean 
   if svgclosing then 
     writeln("Stopped by closing display window")
     stopoptimize(XPRS_STOP_USER) 
   end-if
   timeNow:=gettime
   if timeNow-logtime>=5 then
     bbound:= getparam("XPRS_BESTBOUND")
     grtime:=2*gettime
     if lastxs<>0.0 then
       svgaddline("sol", lastxs, lastys, grtime, objval)
     end-if
     lastxs:=grtime; lastys:=objval
     svgaddline("bbound", lastxb, lastyb, grtime, bbound)
     lastxb:=grtime; lastyb:=bbound
     svgrefresh
     actnodes:= getparam("XPRS_ACTIVENODES")
     writeln(timeNow-starttime, "sec. Best bound:", bbound, "  best sol.:",
             if(getparam("XPRS_MIPSTATUS")=XPRS_MIP_SOLUTION, 
                text(objval), text(" - ")), 
             "  active nodes: ", actnodes)
     logtime:=timeNow
   end-if 
   returned:= false 
 end-function
!@doc.descr Store and display new solution
 procedure cb_intsol
   lastobjval:=objval
   objval:= getparam("XPRS_LPOBJVAL")   ! Retrieve current objective value
   writeln(gettime-starttime, "sec. New solution: ", objval)
   
   bbound:= getparam("XPRS_BESTBOUND")
   svgaddtext("msg", xoffset, indl, "New solution "+ objval)
   indl+=10   
   grtime:=2*gettime
   svgaddpoint("sol", grtime, objval)
   svgsetstyle(svggetlastobj, SVG_STROKE, SVG_GREEN)
   svgsetstyle(svggetlastobj, SVG_FILL, SVG_GREEN)
   if lastxs<>0.0 then
     svgaddline("sol", [lastxs, lastys, grtime, lastys, grtime, objval])
   end-if
   lastxs:=grtime; lastys:=objval
   svgaddline("bbound", lastxb, lastyb, grtime, bbound)
   lastxb:=grtime; lastyb:=bbound
   svgrefresh
   ! If model runs for more than 50sec and new solution is just slightly
   ! better, then interrupt search
   if gettime-starttime>50 and abs(lastobjval-objval)<=5 then 
     writeln("Stopping search (insufficient improvement after time limit)")
     svgaddtext("msg", xoffset, indl, "Stopping search at time limit")
     indl+=10
     svgrefresh
     stopoptimize(XPRS_STOP_USER)
   end-if
 end-procedure
!@doc.descr Notify about gap changes
(!@doc.info With the setting XPRS_MIPRELGAPNOTIFY=0.10 this routine will be 
  called first when gap reaches 10%. We then reset the target, so that it 
  gets called once more at a 2% smaller gap
!)
 procedure cb_gapnotify(rt,at,aot,abt:real)
   writeln(gettime-starttime, "sec. Reached ", 100*mipgap, "% gap")
   svgaddtext("msg", xoffset, indl, "Reached "+100*mipgap+ "% gap")
   svgrefresh
   indl+=10
   mipobj:= getparam("XPRS_MIPOBJVAL")
   bbound:= getparam("XPRS_BESTBOUND")
   relgap:= abs( (mipobj-bbound)/mipobj )
   if relgap<=0.1 then
     ! Call "setgndata" to return new target value to the Optimizer
     mipgap-=0.02
     setgndata(XPRS_GN_RELTARGET, mipgap)
   end-if
   if relgap<=0.02 then
     setgndata(XPRS_GN_RELTARGET, -1)  ! Don't call gapnotify callback any more
   end-if
 end-procedure
!@doc.descr Notify about time limit checks within the solver
(!@doc.info Return value 'true' will stop the solver.
!)
 function cb_checktime: boolean
   returned:=false
   ctchecks+=1
   tcompl:= getparam("XPRS_TREECOMPLETION")*100	   
   cnode:=getparam("XPRS_NODES")
   localsetparam("realfmt", "%.4g")
   writeln(gettime-starttime, "sec. Checking time limit (", ctchecks, 
     " total checks), nodes: ", cnode, ". Est. tree completion ", tcompl, "%")
   svgaddtext("msg", xoffset, indl, text(ctchecks) + 
     " time checks, tree completion "+tcompl+"%")
   svgrefresh
   indl+=10
   ctnode:=cnode
(!   if gettime-starttime > 5 then
      writeln("**** Stopping search from checktime")
     returned:=true
   end-if !)
 end-function  
end-model
 | |||||||||||
| © Copyright 2025 Fair Isaac Corporation. |