JBoss.orgCommunity Documentation

Chapter 5. Optimization algorithms

5.1. The size of real world problems
5.2. The secret sauce of Drools Planner
5.3. Optimization algorithms overview
5.4. SolverPhase
5.5. Which optimization algorithms should I use?
5.6. Logging level: What is the Solver doing?
5.7. Custom SolverPhase

In number of possible solutions for a planning problem can be mind blowing. For example:

For comparison: the minimal number of atoms in the known universe (10^80). As a planning problem gets bigger, the search space tends to blow up really fast. Adding only 1 extra planning entity or planning value can heavily multiply the running time of some algorithms.

An algorithm that checks every possible solution (even with pruning) can easily run for billions of years on a single real-life planning problem. What we really want is to find the best solution in the limited time at our disposal. Planning competitions (such as the International Timetabling Competition) show that local search variations (tabu search, simulated annealing, ...) usually perform best for real-world problems given real-world time limitations.

Drools Planner is the first framework to combine optimization algorithms (metaheuristics, ...) with score calculation by a rule engine such as Drools Expert. This combination turns out to be a very efficient, because:


If you want to learn more about metaheuristics, read the free book Essentials of Metaheuristics or Clever Algorithms.

A Solver can use multiple optimization algorithms in sequence. Each optimization algorithm is represented by a SolverPhase. There is never more than 1 SolverPhase solving at the same time.

Here's a configuration that runs 3 phases in sequence:

<solver>
  ...
  <customSolverPhase><!-- Phase 1 -->
    ... <!-- custom construction heuristic -->
  </customSolverPhase>
  <localSearch><!-- Phase 2 -->
    ... <!-- simulated annealing -->
  </localSearch>
  <localSearch><!-- Phase 3 -->
    ... <!-- Tabu search -->
  </localSearch>
</solver>

When the first phase terminates, the second phase starts, and so on. When the last phase terminates, the Solver terminates.

Some phases (especially construction heuristics) will terminate automatically. Other phases (especially metaheuristics) will only terminate if the phase is configured to terminate:

<solver>
  ...
  <termination><!-- Solver termination -->
    <maximumSecondsSpend>90</maximumSecondsSpend>
  </termination>
  <localSearch>
    <termination><!-- Phase termination -->
      <maximumSecondsSpend>60</maximumSecondsSpend><!-- Give the next phase a chance to run too, before the solver terminates -->
    </termination>
    ...
  </localSearch>
  <localSearch>
    ...
  </localSearch>
</solver>

If the Solver terminates (before the last phase terminates itself), the current phase is terminated and all subsequent phases won't run.

The best optimization algorithms configuration for your use case depends heavily on your use case. Nevertheless, this vanilla recipe will get you into the game with a pretty good configuration, probably much better than what you're used to.

Start with a quick configuration that involves little or no configuration and optimization code:

Next, implement planning entity difficulty comparison and turn it into:

Next, implement moves and add tabu search behind it:

At this point the free lunch is over. The return on invested time lowers. The result is probably already more than good enough.

But you can do even better, at a lower return on invested time. Use the Benchmarker and try a couple of simulated annealing configurations:

And combine them with tabu search:

If you have time, continue experimenting even further. Blog about your experiments!

The best way to illuminate the black box that is a Solver, is to play with the logging level:

Set the logging level on the category org.drools.planner, for example with log4j:

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <category name="org.drools.planner">
        <priority value="debug" />
    </category>

    ...

</log4j:configuration>

For example, set it to DEBUG logging, to see when the phases end and how fast steps are taken:

INFO  Solver started: time spend (0), score (null), new best score (null), random seed (0).
DEBUG     Step index (0), time spend (1), score (0), initialized planning entity (2 @ 0).
DEBUG     Step index (1), time spend (3), score (0), initialized planning entity (1 @ 2).
DEBUG     Step index (2), time spend (4), score (0), initialized planning entity (3 @ 3).
DEBUG     Step index (3), time spend (5), score (-1), initialized planning entity (0 @ 1).
INFO  Phase construction heuristic finished: step total (4), time spend (6), best score (-1).
DEBUG     Step index (0), time spend (10), score (-1),     best score (-1), accepted move size (12) for picked step (1 @ 2 => 3).
DEBUG     Step index (1), time spend (12), score (0), new best score (0), accepted move size (12) for picked step (3 @ 3 => 2).
INFO  Phase local search finished: step total (2), time spend (13), best score (0).
INFO  Solved: time spend (13), best score (0), average calculate count per second (4846).

All time spends are in milliseconds.

Between phases or before the first phase, you might want to execute a custom action on the Solution to get a better score. Yet you'll still want to reuse the score calculation. For example, to implement a custom construction heuristic without implementing an entire SolverPhase.

Implement the CustomSolverPhaseCommand interface :

public interface CustomSolverPhaseCommand {

    void changeWorkingSolution(SolutionDirector solutionDirector);

}

For example:

public class ExaminationStartingSolutionInitializer implements CustomSolverPhaseCommand {

    public void changeWorkingSolution(SolutionDirector solutionDirector) {
        Examination examination = (Examination) solutionDirector.getWorkingSolution();
        for (Exam exam : examination.getExamList()) {
            Score unscheduledScore = solutionDirector.calculateScoreFromWorkingMemory();
            ...
            for (Period period : examination.getPeriodList()) {
                exam.setPeriod(period)
                workingMemory.update(examHandle, exam);
                Score score = solutionDirector.calculateScoreFromWorkingMemory();
                ...
            }
            ...
        }
    }

}

And configure it like this:

<solver>
  ...
  <customSolverPhase>
    <customSolverPhaseCommandClass>org.drools.planner.examples.examination.solver.solution.initializer.ExaminationStartingSolutionInitializer</customSolverPhaseCommandClass>
  </customSolverPhase>
  ... <!-- Other phases -->
</solver>

It's possible to configure multiple customSolverPhaseCommandClass instances, which will be run in sequence.