IBM ILOG Scheduler User's Manual > Advanced Concepts > Scheduling with Durable Resources > Solving The Problem > Using Durable Resources

In Scheduler there is another way of handling this kind of problem, thus avoiding the major drawback of accumulated activities and constraints. It is based on the observation that as we cannot backtrack over an already solved request, the only useful information needed from one iteration to another is the resource availability for each worker. This information is kept by the timetable associated with each resource.

Durable resources provide you with a natural way to keep timetable information from one iteration to another while deleting the activities and constraints which were used to compute the information.

In this approach, we consider the workers as durable unary resources and identify each customer request as a separate scheduling problem involving those durable resources.

Let us consider that the group of eight workers is represented by an array of IloUnaryResource, where the first four positions represent the first team and the last four positions represent the second team of workers.

  const IloInt nbOfWorkers = 8;
  IloUnaryResource workers[nbOfWorkers];
  IloUnaryResource* firstTeam = workers;
  IloUnaryResource* secondTeam = workers + 4;

Next the durable model and resources are created with following function.

IlcScheduler CreateDurableResources(IloSolver solver0,
                                    IloInt nbOfWorkers,
                                    IloUnaryResource* workers) {
  /* CREATE A DURABLE MODEL. */
  IloEnv env0 = solver0.getEnv();
  IloModel model0(env0);
  IloSchedulerEnv schedEnv0(env0);
 
  IloIntervalList blist(env0, 0, 100);
  blist.addPeriodicInterval(5, 2, 7, 100);
  schedEnv0.setBreakListParam(blist);
 
  IlcInt i=0;
  for (i = 0; i < nbOfWorkers; i++){
    workers[i] = IloUnaryResource(env0);
    model0.add(workers[i]);
  }
 
  IlcScheduler durSched(solver0);
  durSched.setDurable();
  solver0.extract(model0);
 
  /* CLOSE THE DURABLE SCHEDULER. */
  durSched.close(); 
 
  return durSched;
}
 

The following goal defines the scheduling problem associated with a given request and solves it. If the computed solution is not accepted, then the whole problem is backtracked, putting the system in the state it was before treating the request.

The schedule defined within this goal is associated with the given request. The durable resources representing the members of the team must be locked before using them, as shown in the following code.

ILCGOAL3(TreatRequestIlc,
         IlcInt, reqIndex,
         IlcUnaryResource*, team,
         IlcInt, releaseDate){

  /* CREATING A SCHEDULE. */
  IloSolver solver = getSolver();
  IlcSchedule schedule(solver, 0, Horizon);
  /* LOCKING THE NEEDED RESOURCES. */
  schedule.lock(4, team);


  /* ... */
  return 0;
}

Then, in the same goal, the scheduling problem is defined.

  /* DEFINING THE PROBLEM. */
  IlcActivity* act = new (solver.getHeap()) IlcActivity[4];
  for (IlcInt i = 0; i < 4; i++){
    act[i] = IlcActivity(schedule, 1);
    solver.add(act[i].requires(team[i]));
  }
  solver.add(act[0].startsAfter(releaseDate));
  solver.add(act[1].startsAfterEnd(act[0]));
  solver.add(act[2].startsAfterEnd(act[0]));
  solver.add(act[3].startsAfterEnd(act[1]));
  solver.add(act[3].startsAfterEnd(act[2]));

The goal returned by IlcSetTimes is used to solve the problem.

  /* SOLVING THE PROBLEM. */
  solver.startNewSearch(IlcSetTimes(schedule));
  IloBool solved = solver.next();

As the durable resources are no longer needed, they can now be unlocked, thus allowing other requests to lock them.

  /* UNLOCKING THE USED COMPUTATIONAL RESOURCES. */
  schedule.unlock(4, team);

The computed solution communicated to the customer is represented by the end time of the activity act[3], which is the last in the manufacturing process. We call this date the due date of the request. For the sake of the example, the fact that the due date may not be accepted by the customer is simulated by comparing it with an acceptance date. This acceptance date is defined as being five days later than the release date. If the due date is greater than the acceptance date, then the proposed solution is refused and the whole problem associated to the request is backtracked by calling solver.restartSearch().

  if (solved) {
  /* ACCEPTING OR NOT. */
    IlcInt acceptDate = releaseDate + 5;
    IlcInt dueDate = act[3].getEndMin();
    if (dueDate > acceptDate){
      // Not accepting :
      solver.out() << "request " << reqIndex << ": refused " << endl;
      solver.restartSearch();
    }
    else {
      // Accepting :
      solver.out() << "request " << reqIndex
              << ": promised at " << act[3].getEndMin()
              << endl;
    }
  }
  else
    solver.out() << "request " << reqIndex
            << ": not solved " << endl;
  solver.endSearch();
  return 0;
}
 

The main loop of the application reads the requests one by one from some input stream of requests that we here simulate by an array of pairs (type of product, release date), each pair defining a request.

const IloInt HowManyReqs = 20;
 
IloInt StreamOfReqs [20] [2] = { /* pairs type releaseDate */
  {1, 5},    // request 0
  {2, 15},   // request 1
  {2, 7},    // request 2
  {1, 24},   // request 3
  {2, 2},    // request 4
  {1, 10},   // request 5
  {1, 25},   // request 6
  {1, 17},   // request 7
  {2, 8},    // request 8
  {1, 15},   // request 9
  {2, 21},   // request 10
  {2, 5},    // request 11
  {1, 15},   // request 12
  {2, 17},   // request 13
  {2, 24},   // request 14
  {2, 2},    // request 15
  {1, 10},   // request 16
  {2, 28},   // request 17
  {1, 17},   // request 18
  {2, 10}    // request 19
};
 

At each iteration a new environment is created and used to treat the current request. The environment is deleted after the request treatment, as the scheduling problem it contains is no longer of interest.

  /* TREATING THE REQUESTS. */
  for (IloInt j = 0; j < HowManyReqs; j++){
    // Reading a request :
    IloInt requestType = StreamOfReqs[j][0];
    IloInt releaseDate = StreamOfReqs[j][1];
 
    IloEnv env;
    IloSolver solver(env);
 
    // Solving the specific problem associated with the request:
    IloUnaryResource* team = (requestType == 1 ?
                              firstTeam :
                              secondTeam);
    solver.solve(TreatRequest(env, durableSched, j, team, releaseDate));         
    env.end();
  }
 

After all customer requests are processed, the timetable of the associated durable resource will contain a day by day timetable for the coming month for each worker. Timetables can be inspected using cursors.

  /* PRINTING THE SCHEDULE OF WORKERS. */
  IlcScheduler scheduler0(solver0);
  IloSchedulerSolution solution(env0);
 
  for (IloInt i = 0; i < nbOfWorkers; i++){
    solver0.out() << endl << "worker" << i << ": " << endl;
    solution.store(workers[i], scheduler0);
    for (IloNumToNumStepFunctionCursor curs(solution.getLevelMin(workers[i]));
         curs.ok();
         ++curs)
      solver0.out() << curs.getSegmentMin() << " .. "
                    << IloMin(Horizon, (curs.getSegmentMax() - 1)) << " : "
                    << (curs.getValue() == 1 ? "On" : "Off")
                    << endl;
  }