Initializing help system before first use

Nonlinear objective with integer decision variables


Type: discrete NLP
Rating: 2 (easy-medium)
Description: The examples describe problems with a nonlinear objective function and some integer decision variables.
  1. A craftsman wants to optimise its revenue based on the size of the produced wooden boxes (boxes02.mos)
  2. Dealers sell apples at a market at the same price and the game is to find the quantity sold and the associated price (pricechange.mos).
File(s): boxes02.mos, pricechange.mos, pricechange_graph.mos


boxes02.mos
(!*********************************************************************
   Mosel NL examples
   =================
   file boxes02.mos
   ````````````````
   A craftsman makes small wooden boxes for sale. 
   He has four different shapes or styles of box, and can make each of
   them in any size (keeping all the dimensions in proportion). 
   The profit he makes on a box depends on the size.   
   He has only a limited amount of the necessary wood available and a
   limited amount of time in the week to do the work.    
   How many boxes should he make, and what size should they be, 
   in order to maximize his profit?  
 
   (c) 2013 Fair Issac Corporation
       author: S. Heipcke, Nov. 2005, rev. Feb. 2013
*********************************************************************!)

model "Boxes"
 uses "mmxnlp"

 declarations
  BOXES = {"Cube", "Oblong", "Flat", "Economy"}      ! Box types
  make: array(BOXES) of mpvar                        ! Number produced per box type
  size, battens, ply, profit, mtime: array(BOXES) of mpvar  ! Properties per box type
  LENGTH,WIDTH,HEIGHT,PROFIT,DUR: array(BOXES) of real      ! Length, width, height, profit, time
  TotalProfit: nlctr                                 ! Objective: total profit
 end-declarations

 LENGTH :: (["Cube", "Oblong", "Flat", "Economy"])[1,1,4,1]
 WIDTH :: (["Cube", "Oblong", "Flat", "Economy"])[1,2,4,2]
 HEIGHT :: (["Cube", "Oblong", "Flat", "Economy"])[1,1,1,1]
 PROFIT :: (["Cube", "Oblong", "Flat", "Economy"])[20,27.3,90,10] ![20,24,90,10]
 DUR :: (["Cube", "Oblong", "Flat", "Economy"])[1,1,1,0.2]

 forall (b in BOXES) do
  battens(b) = 4*(LENGTH(b)+WIDTH(b)+HEIGHT(b))*size(b)
  ply(b) = 2*(LENGTH(b)*WIDTH(b)+WIDTH(b)*HEIGHT(b)+HEIGHT(b)*LENGTH(b))*size(b)^2
  profit(b) = PROFIT(b)*size(b)^1.5
  mtime(b) = 1 + DUR(b)*1.5^(ply(b)/10)
 end-do

! Bounds on size and number of boxes
 forall(b in BOXES) do
  size(b) <= 2
  make(b) <= 6
  make(b) is_integer
 end-do

! Limits on resource availability
 sum(b in BOXES) make(b)*battens(b) <= 200
 sum(b in BOXES) make(b)*ply(b) <= 210
 sum(b in BOXES) make(b)*mtime(b) <= 35

! Objective function: total profit
 TotalProfit := sum(B in BOXES) make(B)*profit(B)

 setparam("xnlp_verbose", true)

! Solve the problem
 maximize(TotalProfit)

! Solution display
 forall (b in BOXES | make(b).sol>0) writeln(getsol(make(b)), " '", b,
  "' boxes of size ", getsol(size(b)),
  " each using ", getsol(battens(b)),
  " cm of battens, ", getsol(ply(b))," sq cm of plywood, ",
  strfmt(getsol(mtime(b)),0,2), " hours")
  
 writeln("\nTotal profit  ", getobjval)
 writeln("Total battens ",
  strfmt(getsol(sum(b in BOXES) make(b)*battens(b)),0,2)," cm")
 writeln("Total ply     ",
  strfmt(getsol(sum(b in BOXES) make(b)*ply(b)),0,2)," sq cm")
 writeln("Total mtime    ",
  strfmt(getsol(sum(b in BOXES) make(b)*mtime(b)),0,2)," hours")
  
end-model

pricechange.mos
(!*********************************************************************
   Mosel NL examples
   =================
   file pricechange.mos
   ````````````````````
   Five dealers in adjacent stalls at a market were selling apples
   of identical quality, so they had to keep their prices equal. At the
   end of the day every dealer had sold a different number of apples, 
   yet all had taken in the same total.
   How much did they charge and how much did they take in, assuming
   that all prices were integer values?  
   Note: The problem is possible only if the price was changed during
   the course of the day. We minimize the number of price changes.

   Based on AMPL model pchange.mod by M.J.Chlond
   Reference: M. Kraitchik, Mathematical Recreations (p. 33-35), Dover

   (c) 2013 Fair Issac Corporation
       author: S. Heipcke, Mar. 2013, rev. Dec. 2017
*********************************************************************!)
  
model "pricechange"
 uses "mmxnlp", "mmsystem"

 parameters
   NP = 4                               ! Maximum number of price changes
   MAXPRICE = 10                        ! Highest permissible price
 end-parameters  

 forward procedure print_sol

 declarations
  DEALERS: set of string                ! Dealers
  PRICES = 1..NP                        ! Prices
  NSOLD: array(DEALERS) of real         ! Total number sold by each dealer
  sell: array(DEALERS,PRICES) of mpvar  ! Number sold by dealer i at price j
  price: array(PRICES) of mpvar	        ! Price j (i.e. first and second prices)
  pchange: array(PRICES) of mpvar       ! 1 iff price j and j+1 are different
  totalrev: mpvar                       ! Total revenu per dealer
 end-declarations

 NSOLD:: (["Mr.Brown","Mrs.White","Mr.Black","Mr.Grey","Ms.Violet"])[10, 25, 30, 15, 35]
   !  More difficult: [10, 25, 30, 12, 35]  
   !  Easy: [10, 25, 30, 15, 35]
 finalize(DEALERS)

 forall(i in DEALERS,p in PRICES) do
  sell(i,p)<=NSOLD(i)
  sell(i,p) is_integer
 end-do

 forall(p in PRICES) do
  price(p)>=1
  price(p)<=MAXPRICE
  price(p) is_integer
  pchange(p) is_binary
 end-do

! Quantities sold 
 forall(i in DEALERS) NSOLD(i) = sum(p in PRICES) sell(i,p)

! All dealers take in the same 
 forall(i in DEALERS)
  sum(p in PRICES) (price(p)*sell(i,p)) = totalrev

! Direction of price changes (symmetry breaking)
 price(2) >= price(1)+1

! Counter the number of price changes 
 forall(p in PRICES | p>1) price(p) >= price(p-1)+pchange(p)
 forall(p in PRICES | p>1) price(p)-price(p-1) <= MAXPRICE*pchange(p)

! Solve the problem
 setparam("xnlp_verbose",true) 

! Obj 1: Minimize total span of prices
 MaxDiff:= price(NP)-price(1)

! Obj2 : Minimize number of price changes 
 TotalChanges:= sum(p in PRICES | p>1) pchange(p)

! Solve the problem for the different objectives
 minimize(MaxDiff)
 print_sol

 minimize(TotalChanges)
 print_sol

! Obj3 + 4 : Minimize + maximize total revenue per dealer
 minimize(totalrev)
 print_sol

 maximize(totalrev)
 print_sol

!**************** Solution printing ****************
 procedure print_sol
  nlstat:=getparam("XNLP_STATUS")
  if nlstat<>XNLP_STATUS_LOCALLY_OPTIMAL and nlstat<>XNLP_STATUS_OPTIMAL then
   writeln("No solution found.")
  else   
   writeln("Number of price changes: ", TotalChanges.sol)
   writeln("Amplitude of prices: ", MaxDiff.sol)
   writeln("Total revenue per dealer: ", totalrev.sol)
   writeln("Quantities sold: ")
   writeln(" "*(21+round(TotalChanges.sol)), "Quantity at prices")
   write("Dealer      TotalQty")
   forall(p in PRICES | p=1 or pchange(p).sol>0) 
     write(strfmt(price(p).sol,5),"/kg")
   writeln("\n", "-"*(22+(round(TotalChanges.sol)+1)*8))
   forall(i in DEALERS) do
    write(strfmt(i,-9), strfmt(NSOLD(i),9), "  ")
    forall(p in PRICES | p=1 or pchange(p).sol>0)
     write(strfmt(sum(q in PRICES | price(q).sol=price(p).sol) 
      round(sell(i,q).sol),8))
    writeln
   end-do   
   writeln("-"*(22+(round(TotalChanges.sol)+1)*8))
  end-if
 end-procedure

end-model

pricechange_graph.mos
(!*********************************************************************
   Mosel NL examples
   =================
   file pricechange_graph.mos
   ``````````````````````````
   Five dealers in adjacent stalls at a market were selling apples
   of identical quality, so they had to keep their prices equal. At the
   end of the day every dealer had sold a different number of apples, 
   yet all had taken in the same total.
   How much did they charge and how much did they take in, assuming
   that all prices were integer values?  
   Note: The problem is possible only if the price was changed during
   the course of the day. We minimize the number of price changes.

   Based on AMPL model pchange.mod by M.J.Chlond
   Reference: M. Kraitchik, Mathematical Recreations (p. 33-35), Dover

   - Graphical representation of results -   

   (c) 2013 Fair Issac Corporation
       author: S. Heipcke, Mar. 2013, rev. Dec. 2017
*********************************************************************!)
  
model "pricechange"
 uses "mmxnlp", "mmsystem", "mmsvg"

 parameters
   NP = 4                               ! Maximum number of price changes
   MAXPRICE = 10                        ! Highest permissible price
 end-parameters  

 forward procedure print_sol
 forward procedure draw_sol(title: text, offset: real, iter: integer)

 declarations
  DEALERS: set of string                ! Dealers
  PRICES = 1..NP                        ! Prices
  NSOLD: array(DEALERS) of real         ! Total number sold by each dealer
  sell: array(DEALERS,PRICES) of mpvar  ! Number sold by dealer i at price j
  price: array(PRICES) of mpvar	        ! Price j (i.e. first and second prices)
  pchange: array(PRICES) of mpvar       ! 1 iff price j and j+1 are different
  totalrev: mpvar                       ! Total revenu per dealer
 end-declarations

 NSOLD:: (["Mr.Brown","Mrs.White","Mr.Black","Mr.Grey","Ms.Violet"])[10, 25, 30, 15, 35]
   !  More difficult: [10, 25, 30, 12, 35]  
   !  Easy: [10, 25, 30, 15, 35]
 finalize(DEALERS)

 forall(i in DEALERS,p in PRICES) do
  sell(i,p)<=NSOLD(i)
  sell(i,p) is_integer
 end-do

 forall(p in PRICES) do
  price(p)>=1
  price(p)<=MAXPRICE
  price(p) is_integer
  pchange(p) is_binary
 end-do

! Quantities sold 
 forall(i in DEALERS) NSOLD(i) = sum(p in PRICES) sell(i,p)

! All dealers take in the same 
 forall(i in DEALERS)
  sum(p in PRICES) (price(p)*sell(i,p)) = totalrev

! Direction of price changes (symmetry breaking)
 price(2) >= price(1)+1

! Counter the number of price changes 
 forall(p in PRICES | p>1) price(p) >= price(p-1)+pchange(p)
 forall(p in PRICES | p>1) price(p)-price(p-1) <= MAXPRICE*pchange(p)

! Solve the problem
 setparam("xnlp_verbose",true) 

! Obj 1: Minimize total span of prices
 MaxDiff:= price(NP)-price(1)

! Obj2 : Minimize number of price changes 
 TotalChanges:= sum(p in PRICES | p>1) pchange(p)

! Scale the size of the displayed graph
 svgsetgraphscale(5)

! Solve the problem for the different objectives
 minimize(MaxDiff)
 print_sol
 draw_sol("Obj 1 (rev="+totalrev.sol+")", 0, 1)

 minimize(TotalChanges)
 print_sol
 draw_sol("Obj 2 (rev="+totalrev.sol+")", 0.2, 2)

! Obj3 + 4 : Minimize + maximize total revenue per dealer
 minimize(totalrev)
 print_sol
 draw_sol("Obj 3 (rev="+totalrev.sol+")", 0.4, 3)

 maximize(totalrev)
 print_sol
 draw_sol("Obj 4 (rev="+totalrev.sol+")", 0.6, 4)

 svgsave("pricechange.svg")
 svgwaitclose("Close browser window to terminate model execution.", 1)

!**************** Solution printing ****************
 procedure print_sol
  nlstat:=getparam("XNLP_STATUS")
  if nlstat<>XNLP_STATUS_LOCALLY_OPTIMAL and nlstat<>XNLP_STATUS_OPTIMAL then
   writeln("No solution found.")
  else   
   writeln("Number of price changes: ", TotalChanges.sol)
   writeln("Amplitude of prices: ", MaxDiff.sol)
   writeln("Total revenue per dealer: ", totalrev.sol)
   writeln("Quantities sold: ")
   writeln(" "*(21+round(TotalChanges.sol)), "Quantity at prices")
   write("Dealer      TotalQty")
   forall(p in PRICES | p=1 or pchange(p).sol>0) 
     write(strfmt(price(p).sol,5),"/kg")
   writeln("\n", "-"*(22+(round(TotalChanges.sol)+1)*8))
   forall(i in DEALERS) do
    write(strfmt(i,-9), strfmt(NSOLD(i),9), "  ")
    forall(p in PRICES | p=1 or pchange(p).sol>0)
     write(strfmt(sum(q in PRICES | price(q).sol=price(p).sol) 
      round(sell(i,q).sol),8))
    writeln
   end-do   
   writeln("-"*(22+(round(TotalChanges.sol)+1)*8))
  end-if
 end-procedure

!**************** Graphical representation of results ****************

 procedure draw_sol(title: text, offset: real, iter: integer)
  nlstat:=getparam("XNLP_STATUS")
  if nlstat=XNLP_STATUS_LOCALLY_OPTIMAL or nlstat=XNLP_STATUS_OPTIMAL then
   SCALE:=10
   svgsetgraphviewbox(0,-4,SCALE*(DEALERS.size+2),MAXPRICE*min(i in DEALERS) NSOLD(i)+10)
   svgsetgraphlabels("","Total sales prices per dealer")

 ! Draw the total revenue
   svgaddgroup("plotr"+iter, title, svgcolor(200,25,25))
   svgaddline(0, totalrev.sol, SCALE*(DEALERS.size+1), totalrev.sol)
   svgaddtext(SCALE*(DEALERS.size+1)+1, totalrev.sol-1, "Iteration "+iter)
 
 ! Draw a bar chart with the quantity sold per price for every dealer 
   if iter=1 then
     forall(p in 1..MAXPRICE) do  
       svgaddgroup("P"+p, string(p)+"$/kg", svgcolor(25*p,100-5*p,250-25*p))
       writeln( svgcolor(25*p,100-5*p,250-25*p))
       svgsetstyle(SVG_FILL,SVG_CURRENT)
     end-do
     svgaddgroup("D", "Dealers", SVG_BLACK)
     svgsetstyle(SVG_FONTSIZE,"xx-small")
     forall(i in DEALERS, ct as counter)
       svgaddtext(SCALE*(ct-0.4+offset), -2, i)
     ct:=0
   end-if

   forall(i in DEALERS, ct as counter) do
     totd:=0
     forall(p in PRICES | p=1 or pchange(p).sol>0) do
       totq:=sum(q in PRICES | price(q).sol=price(p).sol) round(sell(i,q).sol*price(p).sol)
       if totq>0 then
         pv:=round(price(p).sol)
      	 svgaddrectangle("P"+pv, SCALE*(ct-0.4+offset), totd, SCALE*(0.15), totq)
      	 totd+=totq
       end-if	
     end-do
   end-do

   svgrefresh
  end-if

 end-procedure

end-model

© 2001-2019 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.