IBM ILOG Scheduler User's Manual > Advanced Concepts > Using The Trace Facilities to Relax a Model > Solving the Problem: Analyzing a Fail > Analyzing a Fail Reason: Selecting the Guilty Activity

The fails are analyzed by the class FailAnalysisI that is a subclass of IlcSchedulerTraceI. The fail analysis must be initialized exactly as the printed trace (refer to Part I, Getting Started with Scheduler for additional information about printing the trace).

ILOCPTRACEWRAPPER1(ListSchedulingWithFailAnalysis, solver,
                   FailAnalysisResult&, analysisResult) {
  IlcScheduler sched(solver);
  solver.setTraceMode(IlcTrue);
  IlcSchedulerTrace analysis = FailAnalysis(sched, analysisResult);
  analysis.traceAllFailures();
  analysis.traceAllActivities();
  analysis.traceAllResources();
}

The functions traceAllFailures, traceAllActivities, traceAllResources initialize the search engine so that during the search, it maintains information related to a fail caused by an activity or resource. Each time a fail occurs, the virtual function IlcSchedulerTraceI::failManager is called. We overload this function in FailAnalysisI to implement the code that analyzes the fail.

A fail analysis object must be created when starting a new search and exists only during the search. Therefore, we create a class that stores the information found when analyzing the fail. The following class stores the guilty activity and the partial schedule at the time of failure.

class FailAnalysisResult {
  IloSchedulerSolution _solution;
  Job* _guiltyJob;
public:
  FailAnalysisResult(IloEnv env) : _solution(env), _guiltyJob(0) {}
  void setGuiltyJob(Job* job) { _guiltyJob = job; }
  void reset() { _guiltyJob = 0; }
  IloSchedulerSolution getSolution() const { return _solution; }
  IlcBool hasGuiltyJob() const { return _guiltyJob != 0; }
  Job* getGuiltyJob() const { return _guiltyJob; }
};
 

An instance of this class is given to the constructor of the class FailAnalysisI.

class FailAnalysisI : public IlcSchedulerTraceI {
  IloSolver _solver;
  IlcScheduler _schedule;
  FailAnalysisResult& _analysisResult;
public:
  FailAnalysisI(IlcScheduler sched,
                FailAnalysisResult& analysisResult) 
    : IlcSchedulerTraceI(sched.getImpl()),
    _solver(sched.getSolver()),
    _schedule(sched),
    _analysisResult(analysisResult) {}
  virtual void failManager(IlcInt);
};
 

To find a guilty activity, we first look for an activity, a resource constraint, or an alternative resource constraint that could be related to the fail. If we find such an object, the guilty activity is directly determined. Otherwise, if there is a resource and time interval related to the failure, we look for an activity that can be processed by the resource during the time interval. Heuristically, we choose the guilty activity as the one with minimum slack. If there is no information related to the failure, the instance of FailAnalysisResult is not updated. The guilty job is the job to which the guilty activity belongs.

void FailAnalysisI::failManager(IlcInt) {
  IlcActivity act = getCurrentActivity1();
  if (act.getImpl() == 0) {
    IlcResourceConstraint rct = getCurrentResourceConstraint1();
    if (rct.getImpl()) 
      act = rct.getActivity();
    else {
      IlcAltResConstraint altRct = getCurrentAltResConstraint();
      if (altRct.getImpl()) {
        act = altRct.getActivity();
      }
      else {
        if (getCurrentResource().getImpl() == 0) return;
        IlcInt t1 = getCurrentTimeMin();
        IlcInt t2 = getCurrentTimeMax();
 
	IloTranslator<IlcActivity, IlcResourceConstraint> ac = 
	  IlcActivityResourceConstraintTranslator(_solver);	
	IloPredicate<IlcResourceConstraint> containsTimePeriod = 
          (IlcActivityStartMinEvaluator(_solver) << ac) <= t1 &&
          (IlcActivityEndMaxEvaluator(_solver)  << ac) >= t2;
	IloEvaluator<IlcResourceConstraint> slackEval =
          (IlcActivityEndMaxEvaluator(_solver) - 
	   IlcActivityStartMinEvaluator(_solver) - 
	   IlcActivityDurationMinEvaluator(_solver)) << ac;
	IloBestSelector<IlcResourceConstraint, IlcResource> 
	  sel1(containsTimePeriod, slackEval.makeLessThanComparator());
	
        if (sel1.select(rct, getCurrentResource().getImpl())) {
          act = rct.getActivity();
        } else {
	  IloPredicate<IlcResourceConstraint> intersectsTimePeriod = 
            (IlcActivityEndMaxEvaluator(_solver) << ac) >= t1 ||
	    (IlcActivityStartMinEvaluator(_solver) << ac) <= t2;
	  IloBestSelector<IlcResourceConstraint,IlcResource> 
	    sel2(intersectsTimePeriod, slackEval.makeLessThanComparator());
	  
          if (sel2.select(rct, getCurrentResource().getImpl())) {
            act = rct.getActivity();
          }
        }
      }
    }
  }
  if (act.getImpl())
    _analysisResult.setGuiltyJob(getJob(act));
  _analysisResult.getSolution().store(_schedule);
}
 

Notice that we can generalize this fail analysis when the search goal is not deterministic and may meet several failures. However, in order to have a fast algorithm, a search limit should be used (see IloLimitSearch in the IBM ILOG Solver Reference Manual). All the guilty jobs found within this search limit can be stored in the analysis result object and the actual guilty job is the job that can be identified, for instance, as the one that is guilty the greatest number of times.