IBM ILOG Scheduler User's Manual > Local Search in Scheduler > Tabu Search for the Jobshop Problem with Alternatives > Complete Program

You can see the entire program altls.cpp here or view it online in the standard distribution.

#include <ilsched/iloscheduler.h>
#include <ilsolver/iimls.h>
 
ILOSTLBEGIN
 
IloInt ResourceNumbers06 [] = {2, 0, 1, 3, 5, 4,
                               1, 2, 4, 5, 0, 3,
                               2, 3, 5, 0, 1, 4,
                               1, 0, 2, 3, 4, 5,
                               2, 1, 4, 5, 0, 3,
                               1, 3, 5, 0, 4, 2};
 
IloNum Durations06 [] = { 1,  3,  6,  7,  3,  6,
                          8,  5, 10, 10, 10,  4,
                          5,  4,  8,  9,  1,  7,
                          5,  5,  5,  3,  8,  9,
                          9,  3,  5,  4,  3,  1,
                          3,  3,  9, 10,  4,  1};
 
IloInt ResourceNumbers10 [] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
                               0, 2, 4, 9, 3, 1, 6, 5, 7, 8,
                               1, 0, 3, 2, 8, 5, 7, 6, 9, 4,
                               1, 2, 0, 4, 6, 8, 7, 3, 9, 5,
                               2, 0, 1, 5, 3, 4, 8, 7, 9, 6,
                               2, 1, 5, 3, 8, 9, 0, 6, 4, 7,
                               1, 0, 3, 2, 6, 5, 9, 8, 7, 4,
                               2, 0, 1, 5, 4, 6, 8, 9, 7, 3,
                               0, 1, 3, 5, 2, 9, 6, 7, 4, 8,
                               1, 0, 2, 6, 8, 9, 5, 3, 4, 7};
 
IloNum Durations10 [] = {29, 78,  9, 36, 49, 11, 62, 56, 44, 21,
                         43, 90, 75, 11, 69, 28, 46, 46, 72, 30,
                         91, 85, 39, 74, 90, 10, 12, 89, 45, 33,
                         81, 95, 71, 99,  9, 52, 85, 98, 22, 43,
                         14,  6, 22, 61, 26, 69, 21, 49, 72, 53,
                         84,  2, 52, 95, 48, 72, 47, 65,  6, 25,
                         46, 37, 61, 13, 32, 21, 32, 89, 30, 55,
                         31, 86, 46, 74, 32, 88, 19, 48, 36, 79,
                         76, 69, 76, 51, 85, 11, 40, 89, 26, 74,
                         85, 13, 61,  7, 64, 76, 47, 52, 90, 45};
 
IloInt ResourceNumbers20 [] = {0, 1, 2, 3, 4,
                               0, 1, 3, 2, 4,
                               1, 0, 2, 4, 3,
                               1, 0, 4, 2, 3,
                               2, 1, 0, 3, 4,
                               2, 1, 4, 0, 3,
                               1, 0, 2, 3, 4,
                               2, 1, 0, 3, 4,
                               0, 3, 2, 1, 4,
                               1, 2, 0, 3, 4,
                               1, 3, 0, 4, 2,
                               2, 0, 1, 3, 4,
                               0, 2, 1, 3, 4,
                               2, 0, 1, 3, 4,
                               0, 1, 4, 2, 3,
                               1, 0, 3, 4, 2,
                               0, 2, 1, 3, 4,
                               0, 1, 4, 2, 3,
                               1, 2, 0, 3, 4,
                               0, 1, 2, 3, 4};
 
IloNum Durations20 [] = {29,  9, 49, 62, 44,
                         43, 75, 69, 46, 72,
                         91, 39, 90, 12, 45,
                         81, 71,  9, 85, 22,
                         14, 22, 26, 21, 72,
                         84, 52, 48, 47,  6,
                         46, 61, 32, 32, 30,
                         31, 46, 32, 19, 36,
                         76, 76, 85, 40, 26,
                         85, 61, 64, 47, 90,
                         78, 36, 11, 56, 21,
                         90, 11, 28, 46, 30,
                         85, 74, 10, 89, 33,
                         95, 99, 52, 98, 43,
                          6, 61, 69, 49, 53,
                          2, 95, 72, 65, 25,
                         37, 13, 21, 89, 55,
                         86, 74, 88, 48, 79,
                         69, 51, 11, 89, 74,
                         13,  7, 76, 52, 45};
 
 
 
///////////////////////////////////////////////////////////////////
//
// PRINTING OF SOLUTIONS
//
///////////////////////////////////////////////////////////////////
 
void PrintRange(IloEnv& env, IloNum min, IloNum max) {
  if (min == max)
    env.out() << (IlcInt)min;
  else
    env.out() << (IlcInt)min << ".." << (IlcInt)max;
}
 
void PrintSolution(IloEnv& env, 
                   const IloSchedulerSolution solution,
                   const IloNumVar makespan)
{
  env.out() << "Solution with makespan ["
            << solution.getMin(makespan) << ".." 
            << solution.getMax(makespan) << "]" << endl;
 
  for (IloSchedulerSolution::ResourceConstraintIterator
                                           iter(solution);
       iter.ok();
       ++iter) 
  {
    IloResourceConstraint rc = *iter;
    if (!solution.isResourceSelected(rc))
      IloSchedulerException("No resource assigned!");
 
    IloActivity activity = rc.getActivity();
    env.out() << activity.getName() << "[";
    PrintRange(env, 
               solution.getStartMin(activity),  
               solution.getStartMax(activity));
    env.out() << " -- ";
    PrintRange(env, 
               solution.getDurationMin(activity),
               solution.getDurationMax(activity));
    env.out() << " --> ";
    PrintRange(env, 
               solution.getEndMin(activity),
               solution.getEndMax(activity));
    env.out() << "]: " << solution.getSelected(rc).getName()
              << endl;
  }
}
 
///////////////////////////////////////////////////////////////////////////////
//
// MOVES
//
// These are classes that will be associated with a specific
// neighborhood and represent a particular type of local search move
//
///////////////////////////////////////////////////////////////////////////////
 
// Abstract move object
class LSMove {
public:
  LSMove() {}
  virtual ~LSMove() {}
 
  virtual IloBool isReverseMove(IloSolution delta) const = 0;
  virtual IloSolution createDelta(IloEnv, IloSchedulerSolution) = 0;
};
 
class SwapRC : public LSMove {
  // This class represents the data to perform a swap of _rc and
  // _after in the sequence
private:
  IloResourceConstraint _rc;
  IloResourceConstraint _after;
 
public:
  SwapRC() : _rc(0), _after(0) {}
  SwapRC(IloResourceConstraint rc, IloResourceConstraint after) 
    : _rc(rc), _after(after) {}
  virtual ~SwapRC() {}
 
  IloResourceConstraint getRC() const { return _rc; }
  IloResourceConstraint getAfter() const { return _after; }
 
  void setRC(IloResourceConstraint rc) { _rc = rc; }
  void setAfter(IloResourceConstraint after) { _after = after; }
 
  virtual IloBool isReverseMove(IloSolution delta) const {
    IloSchedulerSolution schedDelta(delta);
    return (schedDelta.contains(_rc) &&
            schedDelta.getNextRC(_rc).getImpl() == _after.getImpl());
  }
 
  virtual IloSolution createDelta(IloEnv, IloSchedulerSolution);
 
  static SwapRC* findSwapFromDelta(IloSolver, IloSolution, 
                                   IloSchedulerSolution);
};
 
SwapRC* SwapRC::findSwapFromDelta(IloSolver solver,
                                  IloSolution delta, 
                                  IloSchedulerSolution current) {
  // Try to generate a SwapRC move from the information in delta.  This may
  // fail as delta does not necessarily represent a SwapRC move
  IloSchedulerSolution solution(delta);
 
  for(IloSchedulerSolution::ResourceConstraintIterator preiter(solution);
      preiter.ok(); ++preiter) {
    IloResourceConstraint rc = *preiter;
    if (solution.getSelected(rc).getImpl() != 
        current.getSelected(rc).getImpl())
      return 0;
  }
 
  // find the element that is not next of any element in the delta
  IloResourceConstraint first;
  for(IloSchedulerSolution::ResourceConstraintIterator iter(solution);
      iter.ok() && (0 == first.getImpl()); ++iter) {
    first = *iter;
    for(IloSchedulerSolution::ResourceConstraintIterator iter2(solution);
        iter2.ok() && (0 != first.getImpl()); ++iter2) {
      IloResourceConstraint rc = *iter2;
      if (rc.getImpl() != first.getImpl()) {
        if (solution.hasNextRC(rc) && 
            (solution.getNextRC(rc).getImpl() == first.getImpl()))
          first = 0;
      }
    }
  }
 
  assert(0 != first.getImpl());
 
  IloResourceConstraint second = solution.getNextRC(first);
  IloResourceConstraint third = solution.getNextRC(second);
  if (0 == third.getImpl()) 
    return new (solver.getEnv()) SwapRC(second, first);
  else
    return new (solver.getEnv()) SwapRC(third, second);
}
 
IloSolution SwapRC::createDelta(IloEnv env, IloSchedulerSolution current) {
 
  IloSchedulerSolution swapDelta(env);
 
  // old order: before -> _rc -> _after -> afterAfter
  // new order: before -> _after -> _rc -> afterAfter
 
  swapDelta.add(_rc, current.getRestorable(_rc));
  swapDelta.getSolution().copy(_rc, current);
  swapDelta.unsetNext(_rc, current.getNextRC(_rc));
 
  swapDelta.add(_after, current.getRestorable(_after));
  swapDelta.getSolution().copy(_after, current);
  if (current.hasNextRC(_after))
    swapDelta.unsetNext(_after, current.getNextRC(_after));
 
  // change beforeBefore --> _rc to beforeBefore --> _after
//   IloResourceConstraint before = getBefore();
  IloResourceConstraint before = current.getPrevRC(_rc);
  if (before.getImpl() != 0) {
    swapDelta.add(before, current.getRestorable(before));
    swapDelta.getSolution().copy(before, current);
    swapDelta.unsetNext(before, current.getNextRC(before));
    swapDelta.setNext(before, _after);
  }
 
  // change _after --> afterAfter to _after --> _rc
  swapDelta.setNext(_after, _rc);
  
  // change _rc --> _after to _rc --> afterAfter
  if (current.hasNextRC(_after))
    swapDelta.setNext(_rc, current.getNextRC(_after));
 
  return swapDelta;
}
 
class RelocateRC : public LSMove {
  // This class represents the data to perform a relocate of _and
  // _after in the sequence
private:
  IloResourceConstraint _relocated;
  IloResource _to;
  IloResourceConstraint _beforeInsertion;
 
public:
  RelocateRC() : _relocated(0),_to(0),_beforeInsertion(0) {}
  RelocateRC(IloResourceConstraint rc,
         IloResource to,
         IloResourceConstraint before) 
    : _relocated(rc), _to(to), _beforeInsertion(before) {} 
  virtual ~RelocateRC() {}
 
  IloResourceConstraint getRC() const { return _relocated; }
  IloResource getTo() const { return _to; }
 
  void setRC(IloResourceConstraint rc) { _relocated = rc; }
  void setTo(IloResource res) { _to = res; }
 
  void setBeforeInsertion(IloResourceConstraint before) { 
    _beforeInsertion = before; 
  }
 
  virtual IloBool isReverseMove(IloSolution delta) const {
    IloSchedulerSolution schedDelta(delta);
    return (schedDelta.contains(_relocated) &&
            (schedDelta.getSelected(_relocated).getImpl() != _to.getImpl()));
  }
 
  virtual IloSolution createDelta(IloEnv, IloSchedulerSolution);
  static RelocateRC* findRelocateFromDelta(IloSolver, 
                                           IloSolution, IloSchedulerSolution);
};
 
IloSolution RelocateRC::createDelta(IloEnv env, IloSchedulerSolution current) {
  IloSchedulerSolution relocateDelta(env);
 
  // old state: _relocated is not on resource _to
  // new state: _relocated is on resource _to after _beforeInsertion
 
  // set the selected resource of _relocated
  relocateDelta.add(_relocated, current.getRestorable(_relocated));
  relocateDelta.setSelected(_relocated, _to);
  if (current.hasNextRC(_relocated)) 
    relocateDelta.unsetNext(_relocated, current.getNextRC(_relocated));
 
  // set the next of _relocated
  IloResourceConstraint afterInsertion;
  if (_beforeInsertion.getImpl() == 0)
    // insert _relocated as the first rc on _to
    afterInsertion = current.getSetupRC(_to);
  else
    afterInsertion = current.getNextRC(_beforeInsertion);
 
  if (afterInsertion.getImpl() != 0) 
    relocateDelta.setNext(_relocated, afterInsertion);
 
  // set the next of _beforeInsertion
  if (_beforeInsertion.getImpl() != 0) {
    relocateDelta.add(_beforeInsertion, 
                      current.getRestorable(_beforeInsertion));
		relocateDelta.getSolution().copy(_beforeInsertion, current);
    if (relocateDelta.hasNextRC(_beforeInsertion))
      relocateDelta.unsetNext(_beforeInsertion,
                              current.getNextRC(_beforeInsertion));
    relocateDelta.setNext(_beforeInsertion, _relocated);
  }
 
  // change the rc that was before _relocated in the previous solution
  IloResourceConstraint oldBefore = 
    current.getPrevRC(_relocated);
  if (oldBefore.getImpl() != 0) {
    relocateDelta.add(oldBefore, 
                      current.getRestorable(oldBefore));
		relocateDelta.getSolution().copy(oldBefore, current);
    relocateDelta.unsetNext(oldBefore, _relocated);
    if (current.hasNextRC(_relocated))
      relocateDelta.setNext(oldBefore, current.getNextRC(_relocated));
  }
 
  return relocateDelta;
}
 
RelocateRC* RelocateRC::findRelocateFromDelta(IloSolver solver,
                                  IloSolution delta,
                                  IloSchedulerSolution current) {
  // Try to generate a RelocateRC move from the information in delta.
  // This may fail as delta does not necessarily represent a SwapRC
  // move
  IloSchedulerSolution solution(delta);
  IloResourceConstraint resChange;
  for(IloSchedulerSolution::ResourceConstraintIterator iter2(solution);
      iter2.ok() && (resChange.getImpl() == 0); ++iter2) {
    resChange = *iter2;
    if (solution.getSelected(resChange).getImpl() == 
        current.getSelected(resChange).getImpl())
      resChange = 0;
  }
 
  if (0 == resChange.getImpl())
    return 0;
 
  // find the element that is not next of any element in the delta
  IloResourceConstraint first;
  for(IloSchedulerSolution::ResourceConstraintIterator iter(solution);
      iter.ok() && (0 == first.getImpl()); ++iter) {
    first = *iter;
    for(IloSchedulerSolution::ResourceConstraintIterator iter3(solution);
        iter3.ok() && (0 != first.getImpl()); ++iter3) {
      IloResourceConstraint rc = *iter3;
      if (rc.getImpl() != first.getImpl()) {
        if (solution.hasNextRC(rc) && 
            (solution.getNextRC(rc).getImpl() == first.getImpl()))
          first = 0;
      }
    }
  }
 
  assert(0 != first.getImpl());
 
  if (first.getImpl() == resChange.getImpl())
    return new (solver.getEnv()) RelocateRC(first, 
                                            solution.getSelected(first),
                                            0);
  else
    return new (solver.getEnv()) RelocateRC(resChange, 
                                            solution.getSelected(resChange),
                                            first); 
}
 
 
///////////////////////////////////////////////////////////////////////////////
//
// UTILITIES
//
// We use some utilities classes to help carry out the features common 
// to the neighborhoods we use
//
///////////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////////
//
// Critical Path Object: This object computes and returns a set of
// resource constraints that constitute a randomly selected critical
// path in the current solution
//
///////////////////////////////////////////////////////////////////////////////
 
class RCSortElement {
private:
  IloResourceConstraint _rc;
  IloInt _startValue;
  IloInt _tieBreaker;
  
public:
  RCSortElement() : _rc(0), _startValue(0), _tieBreaker(0) {}
  RCSortElement(IloResourceConstraint rc, IloInt startValue, IloInt tieBreaker)
    : _rc(rc), _startValue(startValue), _tieBreaker(tieBreaker) {}
 
  IloResourceConstraint getRC() { return _rc; }
  IloInt getStartValue() { return _startValue; }
  IloInt getTieBreaker() { return _tieBreaker; }
 
  void setRC(IloResourceConstraint rc) { _rc = rc; }
  void setStartValue(IloInt st) { _startValue = st; }
  void setTieBreaker(IloInt tie) { _tieBreaker = tie; }
};
 
static
int RCSortElementAscendingSTCompare(const void* first,
				    const void* second)
{
  RCSortElement *a = (RCSortElement *) first;
  RCSortElement *b = (RCSortElement *) second;
  
  if (a->getStartValue() > b->getStartValue())
    return 1;
  else if (a->getStartValue() < b->getStartValue())
    return -1;
  else if (a->getTieBreaker() > b->getTieBreaker())
    return 1;
  else if (a->getTieBreaker() < b->getTieBreaker())
    return -1;
 
  return 0;
}
 
class CriticalPath {
private:
  IloBool _needsRecomputing;
 
  IloInt _cpSize;
  IloArray<IloResourceConstraint> _cpArray;
 
  IloRandom _randGen;
 
  RCSortElement *getCriticalRCs(IloSchedulerSolution, IloInt&);
  void pickRandomCP(RCSortElement *, IloSchedulerSolution,IloInt);
  void addCPElement(IloResourceConstraint);
 
public:
  CriticalPath(IloEnv env) :
    _needsRecomputing(IloTrue), _cpSize(0), 
    _cpArray(env), _randGen(env, 98998) {}
  ~CriticalPath() {}
 
  void needsRecomputing() { _needsRecomputing = IloTrue; }
 
  IloArray<IloResourceConstraint>& computeCP(IloSchedulerSolution, IloInt&);
};
 
RCSortElement *CriticalPath::getCriticalRCs(IloSchedulerSolution solution,
                                            IloInt& nbCriticalRCs) {
  // count the number of RCs with assigned start times
  for(IloSchedulerSolution::ResourceConstraintIterator iter(solution);
      iter.ok(); ++iter) {
    IloResourceConstraint rc = *iter;
    IloActivity act = rc.getActivity();
    if (solution.getStartMin(act) == solution.getStartMax(act)) 
      ++nbCriticalRCs;
  }
 
  // populate array
  RCSortElement *sortArray = new RCSortElement[nbCriticalRCs];
  IloInt index = 0;
  for(IloSchedulerSolution::ResourceConstraintIterator iter2(solution);
      iter2.ok(); ++iter2) {
 
    IloResourceConstraint rc = *iter2;
    IloActivity act = rc.getActivity();
 
    if (solution.getStartMin(act) == solution.getStartMax(act)) {
      sortArray[index].setRC(rc);
      sortArray[index].setStartValue( (IloInt)solution.getStartMin(act) );
      sortArray[index].setTieBreaker(index);
      ++index;
    }
  }
 
  // order in ascending start time
  qsort(sortArray, nbCriticalRCs, 
        sizeof(RCSortElement), RCSortElementAscendingSTCompare);
 
  return sortArray;
}
 
void CriticalPath::addCPElement(IloResourceConstraint rc) {
  if (_cpSize < _cpArray.getSize())
    // reuse
    _cpArray[_cpSize] = rc;
  else 
    // add
    _cpArray.add(rc);
 
  _cpSize++;
}
 
void CriticalPath::pickRandomCP(RCSortElement *sortArray,
                                IloSchedulerSolution solution,
                                IloInt nbCriticalRCs) {
  _cpSize = 0;
  IloInt endRC = 0;
  IloInt i = -1;
  while(i < nbCriticalRCs - 1) {
    // skip elements not on the same critical path as rc
    IloInt nextIndexStart = i + 1;
    while((nextIndexStart < nbCriticalRCs) &&
          (sortArray[nextIndexStart].getStartValue() < endRC))
      nextIndexStart++;
    
    // gather elements that are successors of rc on the critical
    // path. There may be more than one
    IloInt nextIndexEnd = nextIndexStart + 1;
    while((nextIndexEnd < nbCriticalRCs) &&
          (sortArray[nextIndexEnd].getStartValue() == endRC))
      nextIndexEnd++;
      
    if (nextIndexStart < nbCriticalRCs) {
 
      // At this point the elements between sortArray[nextIndexStart]
      // and sortArray[nextIndexEnd-1] inclusive are all successors to
      // rc on some critical path. 
      // We randomly pick one of them.
      IloInt randIndex = nextIndexStart;
      IloInt indexDiff = nextIndexEnd - nextIndexStart;
      if (indexDiff > 1)
        randIndex += _randGen.getInt(indexDiff);
            
      IloResourceConstraint next = sortArray[randIndex].getRC();
      addCPElement(next);
 
      i = randIndex;
      endRC = (IloInt)solution.getEndMin(next.getActivity());
    }
    else
      i = nbCriticalRCs;
  }
 
}
 
IloArray<IloResourceConstraint>& CriticalPath::computeCP(
                                       IloSchedulerSolution solution,
                                       IloInt& cpSize) {
  if (_needsRecomputing) {
    IloInt nbCriticalRCs = 0;
    RCSortElement *sortArray = getCriticalRCs(solution, nbCriticalRCs);
    
    // pick one (randomly selected) critical path
    pickRandomCP(sortArray, solution, nbCriticalRCs);
    delete [] sortArray;
    _needsRecomputing = IloFalse;
  }
 
  cpSize = _cpSize;
  return _cpArray;
}
 
 
///////////////////////////////////////////////////////////////////////////////
//
// NEIGHBORHOODS
//
///////////////////////////////////////////////////////////////////////////////
 
// Abstract Neighborhood
class MyNHoodI : public IloNHoodI {
private:
  void resetCache()  { _cp->needsRecomputing(); }
 
protected:
  IloSchedulerSolution _solution;
 
  CriticalPath *_cp;
 
  IloArray<LSMove*> _moves;
  IloInt _nbMoves;
 
  void realAddMove(LSMove *m) {
    if (_nbMoves < _moves.getSize()) _moves[_nbMoves] = m;
    else _moves.add(m);
    _nbMoves++;
  }
 
public:
  MyNHoodI(IloEnv env, CriticalPath *cp,
           const char *name = 0 )
    : IloNHoodI(env, name), 
      _solution(0), _cp(cp),
      _moves(env), _nbMoves(0) {}
  ~MyNHoodI() {}
 
  void start(IloSolver, IloSolution soln) {
    _solution = IloSchedulerSolution(soln);
    _nbMoves = 0;
  }
 
  IloSolution define(IloSolver solver, IloInt i) {
    return (_moves[i])->createDelta(solver.getEnv(), _solution);
  }
 
  IloInt getSize(IloSolver) const { return _nbMoves; }
 
 
  void notify(IloSolver, IloInt) { resetCache(); }
  void notifyOther (IloSolver, IloSolution) { resetCache(); }
  void reset() { _solution = 0; _nbMoves = 0; resetCache(); }
};
 
///////////////////////////////////////////////////////////////////////////////
//
// NEIGHBORHOOD #1:  The N1 neighborhood found by swapping activities
//                      on the critical path
//
///////////////////////////////////////////////////////////////////////////////
 
class N1NHoodI : public MyNHoodI {
private:
  void addMove(IloSolver solver, 
               IloResourceConstraint rc, IloResourceConstraint after) {
    SwapRC *move = new (solver.getEnv()) SwapRC(rc, after);
    MyNHoodI::realAddMove(move);
  }
 
public:
  N1NHoodI(IloEnv env, CriticalPath *cp, 
           const char *name = 0 ) :
    MyNHoodI (env, cp, name) {}
  ~N1NHoodI() {}
 
  void start(IloSolver solver, IloSolution soln);
};
 
void N1NHoodI::start(IloSolver solver, IloSolution soln) {
  MyNHoodI::start(solver, soln);
  
  IloInt cpSize = 0;
  IloArray<IloResourceConstraint> cpArray = _cp->computeCP(_solution, cpSize);
 
  // create the moves
  for(IloInt i = 0; i < cpSize - 1; ++i) {
    IloResourceConstraint rc = cpArray[i];
    IloResourceConstraint after = cpArray[i + 1];
 
    if ((_solution.getNextRC(rc).getImpl() != 0) &&
        (after.getImpl() == _solution.getNextRC(rc).getImpl())) {
        
      addMove(solver, rc, after);
    }
  }
}
 
 
IloNHood N1NHood(IloEnv env, CriticalPath *cp, 
		 const char *name = 0) {
  return new (env) N1NHoodI(env, cp, name);
}
 
///////////////////////////////////////////////////////////////////////////////
//
// NEIGHBORHOOD #2:  The neighborhood found by relocating activities
//                      on the critical path to another possible resource
//                     alternative
//
///////////////////////////////////////////////////////////////////////////////
 
 
class RelocateNHoodI : public MyNHoodI {
private:
  void addMove(IloSolver solver, 
               IloResourceConstraint relocate, IloResource to,
               IloResourceConstraint beforeInsertion) {
    RelocateRC *move = new (solver.getEnv()) 
      RelocateRC(relocate, to, beforeInsertion);
    MyNHoodI::realAddMove(move);
  }
 
public:
  RelocateNHoodI(IloEnv env, CriticalPath *cp,
                 const char *name = 0 ) :
    MyNHoodI(env, cp, name) {}
  ~RelocateNHoodI() {}
 
  void  start(IloSolver solver, IloSolution soln);
};
 
void RelocateNHoodI::start(IloSolver solver, IloSolution soln) {
  MyNHoodI::start(solver, soln);
  
  IloInt cpSize = 0;
  IloArray<IloResourceConstraint> cpArray = _cp->computeCP(_solution, cpSize);
  
  // create the moves
  for(IloInt i = 0; i < cpSize; ++i) {
    IloResourceConstraint rc = cpArray[i];
    IloResource selected = _solution.getSelected(rc);
    if (rc.hasAltResSet()) {
      for(IloAltResSet::Iterator iter(rc.getAltResSet()); 
          iter.ok(); ++iter) {
        IloResource res = *iter;
        if (selected.getImpl() != res.getImpl()) {
 
          // add move to insert rc first on res
          addMove(solver, rc, res, 0);
          IloResourceConstraint prev = _solution.getSetupRC(res);
          while(prev.getImpl() != 0) {
            addMove(solver, rc, res, prev);
            prev = _solution.getNextRC(prev);
          }
        }
      }
    }
  }
}
 
 
IloNHood RelocateNHood(IloEnv env, CriticalPath *cp,
                       const char *name = 0) {
  return new (env) RelocateNHoodI(env, cp, name);
}
 
 
///////////////////////////////////////////////////////////////////////////////
//
// TABU LIST
//
///////////////////////////////////////////////////////////////////////////////
 
class MyTabuI : public IloMetaHeuristicI {
private:
  IloSchedulerSolution _currentSolution;
  IloInt _maxTabuSize;
  IloArray<LSMove*> _tabuList;
 
  IloInt _nbTabuEles;
  IloInt _nextTabuEle;
 
  IloNumVar _costVar;
  IloNum _bestCost;
 
public:
  MyTabuI(IloEnv env, IloNumVar costVar, const char *name = 0) 
    : IloMetaHeuristicI(env, name)
    , _currentSolution(0)
    , _maxTabuSize(10)
    , _tabuList(env, _maxTabuSize)
    , _nbTabuEles(0)
    , _nextTabuEle(0)
    , _costVar(costVar)
    , _bestCost(IloInfinity)
  { }
  
  virtual IloBool start(IloSolver, IloSolution);
  virtual IloBool test(IloSolver, IloSolution);
  virtual void notify(IloSolver, IloSolution);
  virtual IloBool complete();
 
  virtual void reset() {
    _bestCost = IloInfinity; _nbTabuEles = 0; _nextTabuEle = 0;
  }
 
};
 
IloBool MyTabuI::start(IloSolver, IloSolution solution) {
  _currentSolution = IloSchedulerSolution(solution);
 
  if (solution.isObjectiveSet()) {
    IloNumVar obj = solution.getObjectiveVar();
    if (obj.getImpl() && obj.getImpl() == _costVar.getImpl()) {
      if (solution.getObjectiveValue() < _bestCost) 
        _bestCost = solution.getObjectiveValue();
    }
    else
      throw "MyTabuI::start - Solution objective must be a simple variable.";
  }
  else
    throw "MyTabuI::start - Solution has no objective.";
 
  return IloTrue;
}
 
 
IloBool MyTabuI::test(IloSolver solver, IloSolution delta) {
  if (solver.getMin(_costVar) < _bestCost)
    return IloTrue;
 
  for(IloInt i = 0; i < _nbTabuEles; ++i) {
    if ((_tabuList[i] != 0) && _tabuList[i]->isReverseMove(delta))
      return IloFalse;
  }
 
  return IloTrue;
}
 
void MyTabuI::notify(IloSolver solver, IloSolution delta) {
  LSMove *move = 0;
  RelocateRC *relocateMove = 
    RelocateRC::findRelocateFromDelta(solver, delta, _currentSolution);
  if (0 != relocateMove) 
    move = relocateMove;
  else {
    SwapRC *swapMove = SwapRC::findSwapFromDelta(solver, 
                                                 delta, _currentSolution);
    if (0 != swapMove)
      move = swapMove;
  }
 
  assert(move != 0);
 
  _tabuList[_nextTabuEle] = move;
  
  _nextTabuEle++;
  _nextTabuEle %= _maxTabuSize;
 
  if (_nbTabuEles < _maxTabuSize)
    _nbTabuEles++;
}
 
IloBool MyTabuI::complete() {
  // iterate through the tabu list (from oldest to youngest) removing
  // elements up to and including the oldest element that is part of
  // the neighborbood. Insert the youngest element in place of all
  // elements that are removed.
 
  IloInt youngest = _nextTabuEle - 1;
  if (youngest < 0)
    youngest = _nbTabuEles - 1;
 
  if ((youngest < 0) || 
      (_tabuList[youngest] == _tabuList[_nextTabuEle]))
    return IloTrue;
 
  _tabuList[_nextTabuEle] = _tabuList[youngest];
 
  _nextTabuEle++;
  _nextTabuEle %= _maxTabuSize;
 
  return IloFalse;
}
 
IloMetaHeuristic MyTabu(IloEnv env, IloNumVar costVar, 
                          const char *name = 0)
{
  return (IloMetaHeuristicI *) new (env) MyTabuI(env, costVar, name);
} 
 
////////////////////////////////////////////////////////////////////
//
// FIND A FIRST SOLUTION
//
////////////////////////////////////////////////////////////////////
void FindInitialSolution(IloModel model, 
                         IloSchedulerSolution globalSolution,
                         IloSchedulerSolution lsSolution,
                         IloNumVar costVar) {
  IloEnv env = model.getEnv();
  IloSolver solver(model);
  IlcScheduler scheduler(solver);
 
  IloGoal g = IloAssignAlternative(env) && 
    IloRankForward(env) && 
    IloInstantiate(env, costVar);
  solver.startNewSearch(g);
 
  if(solver.next()) {
    IloNum best = solver.getValue(costVar);
    solver.out() << "Solution for Cost " <<  best << endl;
    solver.printInformation();
    globalSolution.store(scheduler);
    lsSolution.store(scheduler);
  }
 
  solver.endSearch();
  solver.end();
}
 
////////////////////////////////////////////////////////////////////
//
// SOLVE THE MODEL USING LOCAL SEARCH
//
////////////////////////////////////////////////////////////////////
IloSchedulerSolution CreateLSSolution(IloEnv env, 
                                      IloSchedulerSolution globalSolution) {
 
 
  /* CREATE LOCAL SEARCH SOLUTION */
  IloSchedulerSolution lsSolution = globalSolution.makeClone(env);
 
  for (IloIterator <IloResourceConstraint> iter(env); iter.ok(); ++iter)
    lsSolution.setRestorable(*iter, 
                             IloRestoreRCNext | IloRestoreRCSelected);
 
  for (IloIterator <IloResource> resIter(env); resIter.ok(); ++resIter)
    lsSolution.add(*resIter);
 
  return lsSolution;
}
 
void SolveModel(IloModel model, 
                IloNumVar makespan,
                IloSchedulerSolution& globalSolution) {
 
  IloEnv env = model.getEnv();
 
  /* CREATE LOCAL SEARCH SOLUTION */
  IloSchedulerSolution lsSolution = CreateLSSolution(env, globalSolution);
  IloObjective obj = IloMinimize(env, makespan);
  lsSolution.getSolution().add(obj);
 
  globalSolution.getSolution().add(makespan);
 
  /* GENERATE AN INITIAL SOLUTION. */
  FindInitialSolution(model, globalSolution, lsSolution, makespan);
  IloNum best = globalSolution.getMax(makespan);
  env.out() << "Initial solution:" << endl;
  PrintSolution(env, globalSolution, makespan);
 
  /* SET PARAMETERS FOR LOCAL SEARCH */
  IloSchedulerEnv schedEnv(env);
  schedEnv.setPrecedenceEnforcement(IloHigh);
  schedEnv.getResourceParam().ignoreCapacityConstraints();
 
  IloSolver lsSolver(model);
  IlcScheduler lsScheduler(lsSolver);
 
  /* HILL CLIMB */
  CriticalPath cp(env);
  IloNHood nhood = N1NHood(env, &cp) + RelocateNHood(env, &cp);
  IloGoal greedyMove = IloSingleMove(env, 
                                     lsSolution, 
                                     nhood,
                                     IloImprove(env), 
                                     IloFirstSolution(env),
                                     IloInstantiate(env, makespan));
 
  IloInt maxIter = 100;
  IloInt movesDone = 0;
  while((movesDone < maxIter) && lsSolver.solve(greedyMove)) {
    IloNum cost = lsSolution.getSolution().getObjectiveValue();
    lsSolver.out() << "Move: " << movesDone << ":\t";
    ++movesDone;
    lsSolver.out() << "solution at cost: " << cost << " ** HC" << endl;
    best = cost;
    globalSolution.store(lsScheduler);
  }
 
  /* TABU */
  IloMetaHeuristic mh = MyTabu(env, makespan);
  IloGoal move = IloSingleMove(env, 
                               lsSolution,
                               nhood, 
                               mh, 
                               IloMinimizeVar(env, makespan),
                               IloInstantiate(env, makespan));
 
  for(IloInt i = movesDone; i < maxIter; ++i) {
    lsSolver.out() << "Move: " << i << ":\t";
    if (!lsSolver.solve(move)) {
      lsSolver.out() << "no solution" << endl;   
      if ((nhood.getSize(lsSolver) == 0) || mh.complete()) 
        break;
    }
    else {
      IloNum cost = lsSolution.getSolution().getObjectiveValue();
      lsSolver.out() << "solution at cost: " << cost;
      if (cost < best) {
        globalSolution.store(lsScheduler);
        best = cost;
        lsSolver.out() << " **";
      }
      lsSolver.out() << endl;
    }
  }
 
  env.out() << "Final solution Cost: " << best << endl;
 
  PrintSolution(env, globalSolution, makespan);
  lsSolver.end();
}
 
////////////////////////////////////////////////////////////////////
//
// DEFINE THE MODEL WITH ALTERNATIVE RESOURCES
//
////////////////////////////////////////////////////////////////////
IloModel
DefineModel(IloEnv& env,
            IloInt numberOfJobs,
            IloInt numberOfResources,        
            IloInt* resourceNumbers,
            IloNum* durations,
            IloRandom randomGenerator,
            IloSchedulerSolution solution,
            IloNumVar& makespan)
{
  IloModel model(env);
 
  /* CREATE THE MAKESPAN VARIABLE. */
  IloInt numberOfActivities = numberOfJobs * numberOfResources;
  IloNum horizon = 0;
  IloInt k;
 
  for (k = 0; k < numberOfActivities; k++)
    horizon += durations[k];
 
  makespan = IloNumVar(env, 0, IloIntMax/2, ILOINT);
 
  /* CREATE THE RESOURCES. */
  IloSchedulerEnv schedEnv(env);
  IloResourceParam resParam = schedEnv.getResourceParam();
  resParam.setPrecedenceEnforcement(IloMediumHigh);
 
  char buffer[128];
  IloInt j;
  IloUnaryResource *resources = 
    new (env) IloUnaryResource[numberOfResources];
  for (j = 0; j < numberOfResources; j++) {
    sprintf(buffer, "R%ld", j);
    resources[j] = IloUnaryResource(env, buffer);
  }
 
  /* CREATE THE ALTERNATIVE RESOURCE SETS */
  env.out() << "Creating resource sets" << endl;
  IloInt *altResourceNumbers = new (env) IloInt[numberOfResources];
  IloAltResSet *altResSets = 
    new (env) IloAltResSet[numberOfResources];
  for (j = 0; j < numberOfResources; j++) {
    altResSets[j] = IloAltResSet(env);
    altResSets[j].add(resources[j]);
 
    // RANDOMLY PICK ANOTHER RESOURCE TO BE IN THE SET
    assert(numberOfResources > 1);
    IloInt index = randomGenerator.getInt(numberOfResources);
    while(index == j)
      index = randomGenerator.getInt(numberOfResources);
 
    altResSets[j].add(resources[index]);
    altResourceNumbers[j] = index;
    env.out() << "Set #" << j << ":\t" << resources[j].getName()
              << " " << resources[index].getName() << endl;
  }
 
  /* CREATE THE ALTERNATIVE DURATIONS */
  IloNum *altDurations = new (env) IloNum[numberOfActivities];
  for(k = 0; k < numberOfActivities; k++) {
    IloNum multiplier = 1.0 + (randomGenerator.getFloat() / 2.0);
    altDurations[k] = IloCeil(multiplier * durations[k]);
  }
 
  /* CREATE THE ACTIVITIES. */
  env.out() << "Setting alternative processing times" << endl;
  k = 0;
  IloInt i;
  for (i = 0; i < numberOfJobs; i++) {
    IloActivity previousActivity;
    for (j = 0; j < numberOfResources; j++) {
 
      IloNum ptMin = IloMin(durations[k], altDurations[k]);
      IloNum ptMax = IloMax(durations[k], altDurations[k]);
      IloNumVar ptVar(env, ptMin, ptMax, ILOINT);
 
      IloActivity activity(env, ptVar);
 
      sprintf(buffer, "J%ldS%ldR%ld", i, j, resourceNumbers[k]);
      activity.setName(buffer);
      solution.add(activity, IloRestoreNothing);
 
      IloResourceConstraint rc =
        activity.requires(altResSets[resourceNumbers[k]]);
      
      // SET THE DIFFERENT DURATIONS DEPENDING ON 
      // RESOURCE SELECTION 
      rc.setProcessingTimeMax(resources[resourceNumbers[k]],
                              durations[k]);
      rc.setProcessingTimeMin(resources[resourceNumbers[k]],
                              durations[k]);
 
      rc.setProcessingTimeMax(
             resources[altResourceNumbers[resourceNumbers[k]]],
             altDurations[k]);
      rc.setProcessingTimeMin(
             resources[altResourceNumbers[resourceNumbers[k]]],
             altDurations[k]);
 
      model.add(rc);
 
      solution.add(rc, IloRestoreNothing);
      env.out() << activity.getName() 
                << ":\tProcessing Time(" 
                << resources[resourceNumbers[k]].getName()
                << "): " << durations[k]
                << "\n\tProcessing Time(" 
                << resources[altResourceNumbers[
                                    resourceNumbers[k]]].getName()
                << "): " << altDurations[k]
                << endl;
 
 
      if (j != 0)
        model.add(activity.startsAfterEnd(previousActivity));
      previousActivity = activity;
      k++;
    }
    model.add(previousActivity.endsBefore(makespan));
  }
 
  /* RETURN THE MODEL. */
  return model;
}
 
 
/////////////////////////////////////////////////////////////////
//
// INITIALIZE THE PROGRAM ARGUMENTS
//
/////////////////////////////////////////////////////////////////
void
InitParameters(int argc,
               char** argv,
               IloInt& numberOfJobs,
               IloInt& numberOfResources,
               IloInt*& resourceNumbers,
               IloNum*& durations)
{
  numberOfJobs = 6;
  numberOfResources = 6;
  resourceNumbers = ResourceNumbers06;
  durations = Durations06;
 
  if (argc > 1) {
    IloInt number = atol(argv[1]);
    if (number == 10) {
      numberOfJobs = 10;
      numberOfResources = 10;
      resourceNumbers = ResourceNumbers10;
      durations = Durations10;
    }
    else if (number == 20) {
      numberOfJobs = 20;
      numberOfResources = 5;
      resourceNumbers = ResourceNumbers20;
      durations = Durations20;
    }
  }
}
 
/////////////////////////////////////////////////////////////////
//
// MAIN FUNCTION
//
/////////////////////////////////////////////////////////////////
 
int main(int argc, char** argv)
{
  IloInt numberOfJobs;
  IloInt numberOfResources;
  IloInt* resourceNumbers;
  IloNum* durations;
 
  InitParameters(argc,
                 argv,
                 numberOfJobs,
                 numberOfResources,
                 resourceNumbers,
                 durations);
 
  try {
 
    IloEnv env;
    IloNumVar makespan;
    IloRandom randGen(env, 8975324);
    IloSchedulerSolution solution(env);
    IloModel model = DefineModel(env,
                                 numberOfJobs,
                                 numberOfResources,
                                 resourceNumbers,
                                 durations,
                                 randGen,
                                 solution,
                                 makespan);
 
    SolveModel(model, makespan, solution);
    env.end();
  }
  catch (IloSchedulerException& exc) {
    cout << exc << endl;
  }
  catch (IloException& exc) {
    cout << exc << endl;
  }
 
  return 0;
}