Lindo Systems

! Using Lagrangean relaxation to try to get
better bounds, and perhaps solve a 
Task to Machine assignment problem;
!Reference:
   Geoffrion, A. (1974),"Lagrangean Relaxation for Integer Programming",
Math. Programming Study 2, pp.82-114;
! Keywords: Lagrangean relaxation, Task Assignment;
SETS:
 TASK: LAMBDA;
 MACH: TIMEA;
 TXM( TASK, MACH): VAL, USAGE, Y, YCUR;
ENDSETS
DATA:
! A set of tasks...;
 TASK = T01  T02  T03  T04  T05  T06  T07;
! and machines to which tasks can be assigned;
 MACH =   M01  M02  M03;
! Time capacity available on each machine;
 TIMEA =  40  40  60;
! Value of assigning task i to machine j;
 VAL =    24  43  41
          17  52  28
          81  73  74
          39  48  47
          55  62  58
          73  71  68
          29  38  36;
! Usage by task i of machine j capacity;
 USAGE =  24  28  19
          32  31  29
          21  18  15
          17  14  21
          11  13  19
          17  12  17
          15  15  17;
ENDDATA

SUBMODEL REAL_PROB:
 ! The original, full model is...;
  MAX = OBJ;
    OBJ = @SUM( TASK(i): 
          @SUM( MACH(j): VAL(i,j)*Y(i,j)));

! Cannot assign more work to machine j than capacity available;
   @FOR( MACH(j):
   [CAP]  @SUM( TASK(i): USAGE(i, j)*Y(i,j)) <= TIMEA(j)
       );

! Can assign task i to at most one machine.
  These constraints will be relaxed vial Lagrange multipliers;
   @FOR( TASK(i):
    [ATMOST1] @SUM( MACH(j): Y(i,j)) <= 1; ! Apply muliplier LAMBD(i);
       );

!  The Y(i,j) must be 0 or 1. Can turn this off by
  setting NORELAX = 0;
   @FOR( TXM( i,j) | NORELAX : @BIN( Y(i,j)));
! End of original full model;
ENDSUBMODEL

SUBMODEL SOLVE_MACH:
! Relaxed problem for machine jnow if the ATMOST1 constraints
 are relaxed.  The problem decomposes into a separate problem
 for each machine;
 ! Inputs:
    jnow = machine for which we are solving knapsack/loading problem;
 ! Maximize value minus lagrange multiplier charge of tasks assigned to machine jnow;
  MAX = OBJ;
   OBJ = @SUM( TASK(i): VAL(i,jnow)*Y(i,jnow)) 
       - @SUM( TASK(i): LAMBDA(i)*Y(i,jnow));
 ! Cannot assign more work to machine jnow than time available;
  [CAPJ] @SUM( TASK(i): USAGE(i, jnow)*Y(i,jnow)) <= TIMEA(jnow);
 ! The Y(i,j) must be 0 or 1, i.e., binary;
  @FOR( TASK(i): @BIN( Y(i,jnow)));
ENDSUBMODEL

CALC:
  @SET('TERSEO',2); ! Output level (0:verb, 1:terse, 2:errors, 3:none);
  @SET('OROUTE',1); ! Route every line immediately to the window;

! If we want to cheat and solve the orginal problem, turn this on;
!  NORELAX = 0; ! Set to 0 to get LP relaxation;
!  @SOLVE( REAL_PROB);
!  @WRITE(' True optimal value= ', OBJ, @NEWLINE(1));

 ! Guess initial multipliers;
  @FOR( TASK(i):
    LAMBDA(i) = 0;
     );
 ! Initial multiplier adjustment factor;
  DELTA = 10;
 ! Max iteration limit;
  ITERMX = 100;
 ! Current interation number;
  iter = 0;

  @WRITE('  Iteration    Upper bound   Lower bound   Step size', @NEWLINE(1));

 ! Main loop, adjusting Lagrange multipliers each pass.
    One may wish to add additional termination conditions
    besides a max iterations limit;
  @WHILE( iter #LT# ITERMX:
   iter = iter + 1;

 ! For given set of Lagrange Multipliers, solve current relaxed problem.
   The relaxation separates into independent problems for each machine;
  UBOUND = @SUM( TASK(i): LAMBDA(i)*1);
  @FOR( MACH( j):
    jnow = j;
    @SOLVE( SOLVE_MACH);
    UBOUND = UBOUND + OBJ;
! Save the current Y values;
    @FOR( TASK(i):
      YCUR(i,jnow) = Y(i,jnow);
       );
     );

! Begin code to adjust the multipliers;
 @FOR( TASK(i):
! Compute the LHS of the relaxed constraint;
   LHS = @SUM( MACH(j) | YCUR(i,j) #GT# 0.5: 1);
 ! Begin code to adjust multiplier based on the amount of constraint violation.
   Increase LAMBDA(i) if violated, decrease if there is slack.
    There may be better ways to do this;
   LAMBDA(i) = @SMAX(0, LAMBDA(i) + DELTA*(LHS - 1));
!   @WRITE(i,' LHS=', LHS,' Adjust multiplier to ', LAMBDA(i),@NEWLINE(1));  
      );
    
 ! Multiplier adjustment step size adjustment.
     The stepsize must decrease towards 0 to get convergence.
     There may be better ways to this;
  DELTA = DELTA*(iter+1)/(iter+2);
 ! End code to adjust multipliers and stepsize;

 ! Begin code to try to find a feasible solution from
    the relaxed solution.
    There may be better ways of guessing a feasible solution;
 @FOR( TASK(i):
! Compute the LHS of the relaxed constraint;
   LHS = @SUM( MACH(j) | YCUR(i,j) #GT# 0.5: 1);
 ! If LHS > 1, then decrease LHS by setting some of the YCUR(i,j)
   = 0 until constraint is feasible;
     @FOR( MACH(j) | YCUR(i,j) #GE# 0.5 #AND# LHS #GT# 1:
         YCUR(i,j) = 0;
         LHS = LHS - 1;
       );
     );

 !  Compute the objective value of this feasible solution;
   LBOUND = @SUM( TXM(i,j): VAL(i,j)*YCUR(i,j));
 ! End code to try to find a feasible solution;

 ! If the original problem is nonconvex, e.g., has integer variables,
  then it is generally impossible to reduce the gap between
  UBOUND and LBOUND to 0;
 @WRITE(@FORMAT(iter,'5.0f'),'      ', @FORMAT( UBOUND,'12.2f'),
        '    ', @FORMAT(LBOUND,'12.2f'),'  ',@FORMAT(DELTA,'10.3f'),@NEWLINE(1));

   );  ! @WHILE( iter...;
    
ENDCALC