IBM ILOG Scheduler User's Manual > Integrated Applications > Scheduling with State Resources: the Trolley Problem, with Transition Times and Limited Capacity > Complete Program and Output

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

#include <ilsched/iloscheduler.h>
 
ILOSTLBEGIN
 
const IloInt MACH1 = 1; // Machine 1 area
const IloInt MACH2 = 2; // Machine 2 area
const IloInt MACH3 = 3; // Machine 3 area
const IloInt AREAA = 4; // Arrival area
const IloInt AREAS = 5; // Stock area
 
const IloNum trolleyMaxCapacity = 3;
const IloNum loadDuration     = 20;
const IloInt numberOfJobs     = 6;
const char* JobNames  [] = { "J1",   "J2",   "J3",   "J4",   "J5",   "J6"};
IloInt Machines1      [] = { 1,      2,      2,      1,      3,      2   };
IloNum Durations1     [] = { 80,     120,    80,     160,    180,    140 };
IloInt Machines2      [] = { 2,      3,      1,      3,      2,      3   };
IloNum Durations2     [] = { 60,     80,     60,     100,    80,     60  };
 
 
                              // M1   M2   M3   A     S
IloNum TransitDurations[] = {    0,  50,  60,  50,   90,    // M1
                                50,   0,  60,  90,   50,    // M2
                                60,  60,  0,   80,   80,    // M3
                                50,  90,  80,   0,  120,    // A
                                90,  50,  80, 120,    0   };// S
 
IloNum getMaxTransitDuration(IloInt nbArea, IloNum* transitDurations){
  IloNum max = 0;
  IloInt size = nbArea*nbArea;
  for (IloInt i = 0; i < size; i++)
    if (transitDurations[i] > max) max = transitDurations[i];
  return max;
}
 
///////////////////////////////////////////////////////////////////////////////
//
// DEFINITION OF THE JOB CLASS
//
///////////////////////////////////////////////////////////////////////////////
class Job {
private:
  const char* _name;
  IloActivity _loadA;
  IloActivity _unload1;
  IloActivity _process1;
  IloActivity _load1;
  IloActivity _unload2;
  IloActivity _process2;
  IloActivity _load2;
  IloActivity _unloadS;
  IloInt      _area1;
  IloInt      _area2;
  IloUnaryResource _machine1;
  IloUnaryResource _machine2;
public:
  Job(IloEnv env,
      const char*,
      IloNum loadDuration,
      IloUnaryResource machine1, IloNum duration1, IloInt area1,
      IloUnaryResource machine2, IloNum duration2, IloInt area2);
  void addToModel(IloModel model,
                  IloStateResource trolley,
                  IloNumVar makespan,
                  IloDiscreteResource trolleyCapacity,
                  IloTransitionParam transitionParam);
  ~Job();
  void printSolution(IlcScheduler scheduler, ILOSTD(ostream)& out);
  const char* getName() const { return  _name;}
 
  IloActivity getLoadA() const { return _loadA;}
  IloActivity getUnload1() const { return _unload1;}
  IloActivity getProcess1() const { return _process1;}
  IloActivity getLoad1() const { return _load1;}
  IloActivity getUnload2() const { return _unload2;}
  IloActivity getProcess2() const { return _process2;}
  IloActivity getLoad2() const { return _load2;}
  IloActivity getUnloadS() const { return _unloadS;}
 
};
Job::~Job()
{}
Job::Job(IloEnv env,
   const char* name,
   IloNum loadDuration,
   IloUnaryResource machine1, IloNum duration1, IloInt area1,
   IloUnaryResource machine2, IloNum duration2, IloInt area2)
  : _name(name),
    _loadA(      env,loadDuration),
    _unload1(    env,loadDuration),
    _process1(   env,duration1),
    _load1(      env,loadDuration),
    _unload2(    env,loadDuration),
    _process2(   env,duration2),
    _load2(      env,loadDuration),
    _unloadS(    env,loadDuration),
    _area1(area1),
    _area2(area2),
    _machine1(machine1),
    _machine2(machine2)
{
 char buffer[256];
 
  sprintf(buffer, "arrival_load_%s", name);
  _loadA.setName(buffer);
 
  sprintf(buffer, "area%ld_unload_%s", area1, name);
  _unload1.setName(buffer);
 
  sprintf(buffer, "machine%ld_%s", area1, name);
  _process1.setName(buffer);
 
  sprintf(buffer, "area%ld_load_%s", area1, name);
  _load1.setName(buffer);
 
  sprintf(buffer, "area%ld_unload_%s", area2, name);
  _unload2.setName(buffer);
 
  sprintf(buffer, "machine%ld_%s", area2, name);
  _process2.setName(buffer);
 
  sprintf(buffer, "area%ld_load_%s", area2, name);
  _load2.setName(buffer);
  
  sprintf(buffer, "stock_load_%s", name);
  _unloadS.setName(buffer);
 
  /* ADD TRANSITION TYPE  */
  _loadA.setTransitionType(AREAA - 1);
  _unload1.setTransitionType(area1 - 1);
  _load1.setTransitionType(area1 - 1);
  _unload2.setTransitionType(area2 - 1);
  _load2.setTransitionType(area2 - 1);
  _unloadS.setTransitionType(AREAS - 1);
 
}
 
void Job::addToModel(IloModel model,
                     IloStateResource trolley,
                     IloNumVar makespan,
                     IloDiscreteResource trolleyCapacity,
                     IloTransitionParam transitionParam)
{
 
  /* ADD MACHINE REQUIREMENT CONSTRAINTS */
  model.add(_process1.requires(_machine1));
  model.add(_process2.requires(_machine2));
 
  /* ADD TEMPORAL CONSTRAINTS BETWEEN ACTIVITIES */
 
  model.add(_unload1.startsAfterEnd(_loadA));
  model.add(_process1.startsAfterEnd(_unload1));
  model.add(_load1.startsAfterEnd(_process1));
  model.add(_unload2.startsAfterEnd(_load1));
  model.add(_process2.startsAfterEnd(_unload2));
  model.add(_load2.startsAfterEnd(_process2));
  model.add(_unloadS.startsAfterEnd(_load2));
 
  
  /* ADD TROLLEY POSITION REQUIREMENTS  */
  model.add(_loadA.requires(trolley,(IloAny)AREAA));
  model.add(_unload1.requires(trolley,(IloAny)_area1));
  model.add(_load1.requires(trolley,(IloAny)_area1));
  model.add(_unload2.requires(trolley,(IloAny)_area2));
  model.add(_load2.requires(trolley,(IloAny)_area2));
  model.add(_unloadS.requires(trolley,(IloAny)AREAS));
 
  /* ADD TROLLEY CAPACITY REQUIREMENTS  */
  IloNum delay = transitionParam.getValue((IloInt)_loadA.getTransitionType(),
                                          (IloInt)_unload1.getTransitionType());
  IloIntVar durationA1(model.getEnv(), 
                       2*(IloInt)loadDuration + (IloInt)delay,
                       (IloInt)makespan.getUB());
  IloActivity onTrolleyA1(model.getEnv(), durationA1);
  onTrolleyA1.shareStartWithStart(_loadA);
  onTrolleyA1.shareEndWithEnd(_unload1);
  model.add(onTrolleyA1.requires(trolleyCapacity));
  delay = transitionParam.getValue((IloInt)_load1.getTransitionType(),
                                   (IloInt)_unload2.getTransitionType());
  IloIntVar duration12(model.getEnv(), 
                       2*(IloInt)loadDuration + (IloInt)delay,
                       (IloInt)makespan.getUB());
  IloActivity onTrolley12(model.getEnv(), duration12);
  onTrolley12.shareStartWithStart(_load1);
  onTrolley12.shareEndWithEnd(_unload2);
  model.add(onTrolley12.requires(trolleyCapacity));
  delay = transitionParam.getValue((IloInt)_load2.getTransitionType(),
                                   (IloInt)_unloadS.getTransitionType());
  IloIntVar duration2S(model.getEnv(), 
                       2*(IloInt)loadDuration + (IloInt)delay,
                       (IloInt)makespan.getUB());
  IloActivity onTrolley2S(model.getEnv(), duration2S);
  onTrolley2S.shareStartWithStart(_load2);
  onTrolley2S.shareEndWithEnd(_unloadS);
  model.add(onTrolley2S.requires(trolleyCapacity));
  
  /* ADD MAKESPAN CONSTRAINT */
  model.add(_unloadS.endsBefore(makespan));
}
 
 
void Job::printSolution(IlcScheduler scheduler, ILOSTD(ostream)&)
{
  /* PRINT JOB */
  scheduler.getSolver().out()
  << "JOB " << _name << endl
  << "\t Load at area A: " 
  << scheduler.getActivity( _loadA ) << endl
  << "\t Unload at area " << (long)_area1 << ": " 
  << scheduler.getActivity( _unload1 )   << endl
  << "\t Process on machine " << (long)_area1 << ": " 
  << scheduler.getActivity( _process1 )  << endl
  << "\t Load at area "   << (long)_area1 << ": " 
  << scheduler.getActivity( _load1 )     << endl
  << "\t Unload at area " << (long)_area2 << ": " 
  << scheduler.getActivity( _unload2 )   << endl
  << "\t Process on machine " << (long)_area2 << ": " 
  << scheduler.getActivity( _process2 )  << endl
  << "\t Load at area "   << (long)_area2 << " : " 
  << scheduler.getActivity( _load2 )     << endl
  << "\t Unload at area S: "
  << scheduler.getActivity( _unloadS )   << endl;
}
 
 
///////////////////////////////////////////////////////////////////////////////
//
// PROBLEM DEFINITION
//
///////////////////////////////////////////////////////////////////////////////
 
#if defined(ILO_SDXLOUTPUT)
#include "sdxltrolley.h"
#endif
 
IloModel DefineModel(IloEnv& env,
                     const char** jobNames,
                     IloNum*      transitDurations,
                     IloInt*      machines1,
                     IloNum*      durations1,
                     IloInt*      machines2,
                     IloNum*      durations2,
                     IloAnyArray&  jobs,
                     IloStateResource& trolley,
                     IloArray<IloUnaryResource>& machines,
                     IloNumVar&   makespan)
{
  IloInt i, j;
  IloModel model(env);
  
  /* CREATE THE MAKESPAN VARIABLE. */
  IloNum horizon = 
    numberOfJobs * ((6 * loadDuration) +
                    (4 * getMaxTransitDuration(5, transitDurations)));
  for (i = 0; i < numberOfJobs; i++)
    horizon += durations1[i] + durations2[i];   
  makespan = IloNumVar(env,0,horizon,ILOINT);   
 
  /* CREATE THE TROLLEY POSITION RESOURCE */
  IloAnyArray arrayOfPossibleAreas(env,
                                   5,
                                   (IloAny)AREAA,
                                   (IloAny)MACH1,
                                   (IloAny)MACH2,
                                   (IloAny)MACH3,
                                   (IloAny)AREAS);
  IloAnySet   setOfPossibleAreas(env,arrayOfPossibleAreas);
  trolley = IloStateResource(env,setOfPossibleAreas, "trolley");
 
  /* CREATE THE (SYMMETRIC) TRANSITION TIME TABLE */
  IloTransitionParam transitionParam(env, 5, IloTrue);
  for (i = 0; i < 5; i++)
    for (j = i; j < 5; j++)
      transitionParam.setValue(i,j,transitDurations[j + (5 * i)]);
 
  /* ASSOCIATE A TRANSITION TIME TO THE RESOURCE */
  IloTransitionTime transitionTime(trolley,transitionParam);
 
  /* SELECT APPROPRIATE ENFORCEMENT LEVEL */
  trolley.setTransitionTimeEnforcement(IloBasic);
  trolley.setCapacityEnforcement(IloMediumHigh);
 
  /* CREATE THE TROLLEY CAPACITY RESOURCE */
  IloDiscreteResource trolleyCapacity(env,trolleyMaxCapacity);
 
  /* SELECT APPROPRIATE ENFORCEMENT LEVEL */
  trolleyCapacity.setCapacityEnforcement(IloMediumHigh);
 
  /* CREATE THE MACHINES RESOURCES */
  char buffer[256];
  machines = IloArray<IloUnaryResource>(env, 3);
  for (i = 0; i < 3; i++) {
    sprintf(buffer, "machine%ld", i + 1);
    machines[i] = IloUnaryResource(env, buffer);
  }
 
 
  /* CREATION OF JOB INSTANCES */
  jobs = IloAnyArray(env, numberOfJobs);
  for (i = 0; i < numberOfJobs; i++) {
    Job* job = new (env) Job(env,
                             jobNames[i],
                             loadDuration,
                             machines[machines1[i] - 1], 
                             durations1[i], machines1[i], 
                             machines[machines2[i] - 1], 
                             durations2[i], machines2[i]);
    jobs[i] = job;
    job->addToModel(model, 
                    trolley, makespan,trolleyCapacity, transitionParam);
  }
 /* WE LOOK FOR AN OPTIMAL SOLUTION */
  model.add(IloMinimize(env, makespan));
 
  /* RETURN THE CREATED MODEL */
  return model;
}                
 
///////////////////////////////////////////////////////////////////////////////
//
// PRINTING OF SOLUTIONS
//
///////////////////////////////////////////////////////////////////////////////
 
void PrintSolution(IloSolver solver,
                   IloAnyArray jobs,
                   IloStateResource trolley,
                   IloArray<IloUnaryResource> machines,
                   IloNumVar makespan)
{
  IlcScheduler scheduler(solver);
 
  solver.out() << "makespan = " << solver.getMin(makespan) << endl;
  for (IloInt i = 0; i < numberOfJobs; i++)
    ((Job*) jobs[i])->printSolution(scheduler, solver.out());
 
  solver.out() << "TROLLEY POSITIONS: " << endl;
 
  IlcStateResourceIterator ite(scheduler);
  IlcAnyTimetable tt = scheduler.getStateResource(trolley).getTimetable();
  for (IlcAnyTimetableCursor cr(tt,0); cr.ok(); ++cr)
    if (cr.isBound())
      solver.out() << "\t [" << cr.getTimeMin()
                   << "," << cr.getTimeMax() 
                   << "): Position " << (long)cr.getValue()
                   << endl;
 
  solver.printInformation();
 
#if defined(ILO_SDXLOUTPUT)
  IloSDXLTrolleyOutput output(solver.getEnv());
  ofstream outFile("trolley3.xml");
  output.writeTrolley(scheduler, 
                      outFile, 
                      jobs, 
                      trolley, 
                      trolleyMaxCapacity,
                      machines, 
                      makespan);
  outFile.close();
#endif 
 
}
 
///////////////////////////////////////////////////////////////////////////////
//
// MAIN FUNCTION
//
///////////////////////////////////////////////////////////////////////////////
 
int main(int argc, char** argv) {
 
  IloInt failLimit          = 1000;
  if (argc > 1)
    failLimit = atol(argv[1]);
 
  try {
    IloEnv env;
    IloAnyArray jobs;
    IloStateResource trolley;
    IloArray<IloUnaryResource> machines;
    IloNumVar makespan;
    IloModel model = DefineModel(env,
        JobNames,
        TransitDurations,
        Machines1, Durations1,
        Machines2, Durations2,
        jobs,
        trolley,
        machines,
        makespan);
 
    IloBool solved = IloFalse;
    IloNum  minMakespan = 0.;
    IloSolver solver(model);
 
    // create limited search goal
    IloGoal unlimitedGoal = IloSetTimesForward(env, makespan);
    IloGoal limitedGoal = IloLimitSearch(env, unlimitedGoal, IloFailLimit(env,failLimit) );
    solver.startNewSearch(limitedGoal);
 
    while (solver.next()==IlcTrue) {
             solved = IloTrue;
      minMakespan = solver.getMin(makespan);
      solver.out() << "SOLUTION WITH MAKESPAN " 
                          << minMakespan << endl;
    }
    solver.endSearch();
 
    if (solved){      
      makespan.setBounds(minMakespan,minMakespan);
      solver.solve(unlimitedGoal);
      PrintSolution(solver,jobs,trolley,machines,makespan);
    } else {
      solver.out() << "NO SOLUTION FOUND" << endl;
    }
 
    env.end();
 
  } catch (IloException& exc) {
    cout << exc << endl;
  }
 
  return 0;
}
 
///////////////////////////////////////////////////////////////////////////////
//
// RESULTS
//
///////////////////////////////////////////////////////////////////////////////
 
/*
  SOLUTION WITH MAKESPAN 1540
  SOLUTION WITH MAKESPAN 1450
  SOLUTION WITH MAKESPAN 1440
  SOLUTION WITH MAKESPAN 1370
  makespan = 1370
  JOB J1
         Load at area A: [460 -- 20 --> 480]
         Unload at area 1: [530 -- 20 --> 550]
         Process on machine 1: [550 -- 80 --> 630]
         Load at area 1: [870 -- 20 --> 890]
         Unload at area 2: [940 -- 20 --> 960]
         Process on machine 2: [960 -- 60 --> 1020]
         Load at area 2 : [1080 -- 20 --> 1100]
         Unload at area S: [1150 -- 20 --> 1170]
  JOB J2
         Load at area A: [140 -- 20 --> 160]
         Unload at area 2: [320 -- 20 --> 340]
         Process on machine 2: [340 -- 120 --> 460]
         Load at area 2: [600 -- 20 --> 620]
         Unload at area 3: [700 -- 20 --> 720]
         Process on machine 3: [880 -- 80 --> 960]
         Load at area 3 : [1250 -- 20 --> 1270]
         Unload at area S: [1350 -- 20 --> 1370]
  JOB J3
         Load at area A: [460 -- 20 --> 480]
         Unload at area 2: [600 -- 20 --> 620]
         Process on machine 2: [620 -- 80 --> 700]
         Load at area 2: [800 -- 20 --> 820]
         Unload at area 1: [870 -- 20 --> 890]
         Process on machine 1: [890 -- 60 --> 950]
         Load at area 1 : [1010 -- 20 --> 1030]
         Unload at area S: [1150 -- 20 --> 1170]
  JOB J4
         Load at area A: [0 -- 20 --> 20]
         Unload at area 1: [70 -- 20 --> 90]
         Process on machine 1: [90 -- 160 --> 250]
         Load at area 1: [390 -- 20 --> 410]
         Unload at area 3: [700 -- 20 --> 720]
         Process on machine 3: [720 -- 100 --> 820]
         Load at area 3 : [1250 -- 20 --> 1270]
         Unload at area S: [1350 -- 20 --> 1370]
  JOB J5
         Load at area A: [0 -- 20 --> 20]
         Unload at area 3: [240 -- 20 --> 260]
         Process on machine 3: [260 -- 180 --> 440]
         Load at area 3: [720 -- 20 --> 740]
         Unload at area 2: [800 -- 20 --> 820]
         Process on machine 2: [820 -- 80 --> 900]
         Load at area 2 : [940 -- 20 --> 960]
         Unload at area S: [1150 -- 20 --> 1170]
  JOB J6
         Load at area A: [0 -- 20 --> 20]
         Unload at area 2: [320 -- 20 --> 340]
         Process on machine 2: [460 -- 140 --> 600]
         Load at area 2: [620 -- 20 --> 640]
         Unload at area 3: [700 -- 20 --> 720]
         Process on machine 3: [820 -- 60 --> 880]
         Load at area 3 : [1250 -- 20 --> 1270]
         Unload at area S: [1350 -- 20 --> 1370]
  TROLLEY POSITIONS: 
         [0,20): Position 4
         [70,90): Position 1
         [140,160): Position 4
         [240,260): Position 3
         [320,340): Position 2
         [390,410): Position 1
         [460,480): Position 4
         [530,550): Position 1
         [600,640): Position 2
         [700,740): Position 3
         [800,820): Position 2
         [870,890): Position 1
         [940,960): Position 2
         [1010,1030): Position 1
         [1080,1100): Position 2
         [1150,1170): Position 5
         [1250,1270): Position 3
         [1350,1370): Position 5
*/
    

This solution corresponds to a maximal number of fails per optimization step of 1000. It is represented in Figure 34.1. Because the trolley capacity is now limited, 17 moves of the trolley are necessary to achieve the schedule (compared to 11 moves for a trolley without capacity limits).

schedulingStateRes3ci.gif

Figure 34.1 A Solution for the Trolley Problem with Transition Times and Limited Capacity