Lindo Systems

! The how many facings problem.                    (NewsVendorFacings.lng)
Given 
  tsw = total shelf width available for facings,
  for each product p:
   c( p) = cost per unit,
   r( p) = revenue or selling price per unit,
   w( p) = width of a facing,
   f( p) = items per facing, e.g., stacked behind each other,
   mu( p) = mean demand per day (assume re-stock each night),
   sd( p) standard deviation in demand per day,

Question:
   How many facings should we allocate to each product?

Essential ideas:
  total facings cannot exceed tsw,
  Want to stock for each product mean demand + safety stock,
  We tend to give more safety stock to product p for which:
   r( p) - c( p) is high,
   f( p) is high,
   w( p) is low,
   sd( p) is high.

This can be done to optimize total expected profit.
! Computed parameters:
    phi( s, p) = Prob{ demand for product p is >= s}
  Variables:
     z( p, s) = 1 if we stock at least s, (thus z(s+1,p) <= z(s,p))
;
! Keywords:  Facings, Inventory, Newsboy problem, Newsvendor, Planogram, Uncertainty; 
sets:
  prod : c, r, w, f, mu, sd, y;
  slvl;
  sxp( slvl, prod): v, z;
endsets
data:
  prod = prod1 prod2;
  slvl = 1..60;
  tsw = 87; ! Total shelf width available;
! c( p) =  cost per unit,;
    c = 40  50;
! r( p) = revenue or selling price per unit;
    r = 95 100;
! w( p) = width of a facing,;
    w =  4   5;
!  f( p) = items per facing, e.g., stacked behind each other;
    f =  3  2;
!  mu( p) = mean demand per day (assume re-stock each night),;
   mu = 20  15;
!  sd( p) standard deviation in demand per day;
   sd =  2   3;
enddata

submodel allocate:! Parameters:
   v( s, p) = expected sales of product p if we stock s units,
;
! Objective: maximize expected profit;
    max = @sum( sxp( s, p): ( r( p) - c( p)) * v( s, p)* z( s, p));
! Variables:
     z( s, p) = 1 if we carry at least s units of product p,
     y( p) = number of facings for product p.

! Can choose 1 stock level for each product;
 @for( prod( p):
   @sum( slvl( s): z( s, p)) <= 1;
     );

! Shelf capacity constraint;
    @sum( prod( p): w( p) * y( p)) <= tsw;

! Must have enough facings to cover the amount stocked;
   @for( prod( p):
    [FACE] @sum( slvl( s): s * z( s, p)) <= f( p) * y( p);
       );

!  Cannot choose fractional stock levels;
    @for( sxp( s, p): @bin( z( s, p)));
!  Number facings must be integer;
    @for( prod( p): @gin( y( p)));
endsubmodel
 
calc:
! Compute v( s, p).
! First compute the probabilities of each level.;
  @for( prod( p):
    cumprev = 0;
    @for( slvl( s) :
! If we stock s, rather than s-1, marginal sales = Prob{D >= s};
      cumnew = cumprev + 1 - @PNORMCDF( mu( p), sd( p), s - 0.5); 
      v( s, p) =  cumnew ;
      cumprev = cumnew;
        );
      );

   @solve( allocate);

! Display a little report;
  @write(' Product   Facings   NumToStock', @newline( 1));
  @for( prod( p):
    stock =  @sum( slvl( s): s * z( s, p)) ;
    @write( @format( prod( p), '9s'), ' ', @format( y( p), '5.0f'), 
      ' ', @format( stock, '9.0f'), @newline( 1)); 
      );
    
endcalc