Lindo Systems

MODEL:
 ! Illustration of Multi-criteria/Lexico/Pre-emptive goal programming
 ! for a staffing problem with multiple work patterns available;
!   Step 1:
       - Minimize total cost
    Step 2:
       - Given total costs minimized from Step 1
       - Solve to minimize max staff overage
         on any given day, thereby spreading
         excess staff through the week
    Step 3:
       - Fix max overage to it's level from  
         Step 2
       - Solve to minimize the number working
         on a Sunday  ;

! Any Preemptive optimal solution is also Pareto optimal;
! Keywords:  Goal programming, Lexico-goal programming, Multi-criteria,
   Preemptimve criteria, Pareto optimal, Staff scheduling;

 SETS:
   DAY :  NEED,  EXCESS;
   PTRN : DAYSON, HRPDAY, COST, PUB;
   PXD( PTRN, DAY): START;
   LOBJ: OBJ, WGT, SUB; ! The set of objectives;
 ENDSETS

 DATA:
   PTRN = P5X8 P4X10;  ! The names of the work patterns;
   DAYSON =  5     4;  ! Number days on per week for each;
   HRPDAY =  8    10;  ! Hours per day for each pattern type;
   PUB =   999   999;  ! Upper bound on number that can be hired;
   COST =  400   400;  ! Cost per week for each;
 ! Days of the week;
    DAY =   MON,   TUE,    WED,   THU,   FRI,   SAT,  SUN;
 ! Hours needed each day;
    NEED =  1520,  1360,  1200,  1520,  1360,  1120, 1008;
  ! The Lexico objectives;
   LOBJ = Total_Cost Max_Excess Work_Sunday;
 ENDDATA

 SUBMODEL STAFFEM:
! Minimize the appropriate objective, based on WGT();
   MIN = @SUM( LOBJ( p) : WGT( p) * OBJ( p)); 
   ! Compute cost;
   TTL_COST = @SUM( PXD(p,d) : COST(p)*START( p,D) );

  ! The constraints;
  ! We must satisfy the number hours needed each day.
   Hours provided = hours needed + excess;
   @FOR( DAY( D):
     @SUM( PTRN( p):
        @SUM( DAY( COUNT)| COUNT #LE# DAYSON(p):
          HRPDAY( p)*START( p, @WRAP( D - COUNT + 1, @SIZE( DAY)))))  
        = NEED( D) + EXCESS( D) ;
   );

  ! Cannot use more than allowed of each pattern;
   @FOR( PTRN(p):
     @SUM( PXD( p,d): START(p,d)) <= PUB( p);
       );

   ! Computes the maximum staff excess;
   @FOR( DAY( D): MAX_EXCESS >= EXCESS( D)); 

   ! Starts must be integral;
   @FOR( PXD(p,d): @GIN( START(p,d)));

! A redundant but helpful constraint, Total staff >= total need;
   @SUM( DAY( d):
     @SUM( PTRN( p):
        @SUM( DAY( COUNT)| COUNT #LE# DAYSON(p):
          HRPDAY( p)*START( p, @WRAP( D - COUNT + 1, @SIZE( DAY))))))  
        >=  @SUM( DAY( d): NEED( D)) ;

! Objective related stuff;
   ! Total cost;
   ! Compute cost;
   TTL_COST = @SUM( PXD(p,d) : COST(p)*START( p,D) );

   ! Excess maximum staff ;
   @FOR( DAY( D): MAX_EXCESS >= EXCESS( D)); 

! The Lexico stuff. Define the objectives;
   OBJ( 1) = TTL_COST;
   OBJ( 2) = MAX_EXCESS;
   OBJ( 3) = EXCESS( @INDEX( DAY, SUN));
! Their bounds, if any;
   @FOR( LOBJ( p):
     OBJ( p) <= SUB( p);
       ); 
ENDSUBMODEL

PROCEDURE REPORT:
   @WRITE('               ');
   @FOR( DAY( D): @WRITE( DAY( D), '   '));
   @WRITE( 'TOTAL COST', @NEWLINE( 1)); 
   @FOR( PTRN( p):
   @WRITE( ' Start ',@FORMAT( PTRN(p),"5s"),':');
   @FOR( DAY( d): @WRITE( @FORMAT( START( p, d), '6.0f')));
      @WRITE( @FORMAT( @SUM( DAY(d): COST( p)* START( p, d)), '8.0f'));
   @WRITE( @NEWLINE( 1));
    );
   @WRITE( 'Hrs provided:');
   @FOR( DAY: @WRITE( @FORMAT( NEED + EXCESS, '6.0f')));
   @WRITE( @NEWLINE( 1));
   @WRITE( '    Required:');
   @FOR( DAY: @WRITE( @FORMAT( NEED, '6.0f')));
   @WRITE( @NEWLINE( 1));
   @WRITE( '      Excess:');
   @FOR( DAY: @WRITE( @FORMAT( EXCESS, '6.0f')));
   @WRITE( @NEWLINE( 2));
 ENDPROCEDURE

 CALC:
   !Run in quiet mode;
   @SET( 'TERSEO', 2);
  
   @WRITE('Illustrate preemptive objectives in staffing:', @NEWLINE(1),
     ' 1) Minimize cost, 2) Minimize max excess any day, 3) Minimize Sunday staffing.',@NEWLINE(2));

! Initially no weights on any objective;
   @FOR( LOBJ( p): WGT( p) = 0);

! Loop over the objectives;
   @FOR( LOBJ( p):
      @IFC( p #GT# 1: WGT( p-1) = 0);
      WGT( p) = 1;  ! Put all wgt on current obj; 
      @SOLVE( STAFFEM);
      @WRITE( 'Minimize ', LOBJ( p), @NEWLINE( 1));
      REPORT;            ! Display standard report;
      SUB( p) = OBJ( p); !Fix current objective;
       );
 ENDCALC
END