JBoss.orgCommunity Documentation

Drools Planner User Guide


1. Planner introduction
1.1. What is Drools Planner?
1.2. Status of Drools Planner
1.3. Getting Drools Planner and running the examples
1.3.1. Getting the release package and running the examples
1.3.2. Get it with maven
1.3.3. Build it from source
1.4. Questions, issues and blogs
2. Use cases and examples
2.1. Introduction
2.2. The n queens example
2.2.1. Screenshot
2.2.2. Problem statement
2.2.3. Solution(s)
2.2.4. Problem size
2.2.5. Domain class diagram
2.3. The Manners 2009 example
2.3.1. Problem statement
2.4. The Traveling Salesman Problem (TSP) example
2.4.1. Problem statement
2.5. The Traveling Tournament Problem (TTP) example
2.5.1. Screenshot
2.5.2. Problem statement
2.5.3. Simple and smart implementation
2.5.4. Problem size
2.6. Cloud balancing
2.6.1. Problem statement
2.7. The ITC 2007 curriculum course example
2.7.1. Problem statement
2.8. The ITC 2007 examination example
2.8.1. Screenshot
2.8.2. Problem statement
2.8.3. Problem size
2.8.4. Domain class diagram
2.9. The patient admission scheduling (PAS) example
2.10. The INRC 2010 nurse rostering example
2.10.1. Problem statement
3. Planner configuration
3.1. Types of solvers
3.1.1. Brute force
3.1.2. Branch and bound
3.1.3. Simplex
3.1.4. Genetic algorithms
3.1.5. Local search (tabu search, simulated annealing, ...)
3.2. The size of real world problems
3.3. The Solver interface
3.4. Building a Solver
3.4.1. Environment mode
3.5. The Solution interface
3.5.1. The getScore and setScore methods
3.5.2. The getFacts method
3.5.3. The cloneSolution method
3.6. The starting solution
3.6.1. A simple filler algorithm
3.6.2. StartingSolutionInitializer
3.7. Solving a problem
4. Score calculation with a rule engine
4.1. Rule based score calculation
4.2. Defining the score rules source
4.2.1. A scoreDrl resource on the classpath
4.2.2. A RuleBase (possibly defined by Guvnor)
4.3. Implementing a score rule
4.4. Delta based score calculation
4.5. The ScoreDefinition interface
4.6. Tips and tricks
5. Local search solver
5.1. Overview
5.2. A move
5.3. Move generation
5.4. A step
5.5. Getting stuck in local optima
5.6. Deciding the next step
5.6.1. Selector
5.6.2. Acceptor
5.6.3. Forager
5.7. Best solution
5.8. Termination
5.8.1. TimeMillisSpendTermination
5.8.2. StepCountTermination
5.8.3. ScoreAttainedTermination
5.8.4. UnimprovedStepCountTermination
5.8.5. Combining Terminations
5.8.6. Another thread can ask a Solver to terminate early
5.9. Using a custom Selector, Acceptor, Forager or Termination
6. Benchmarking and tweaking
6.1. Finding the best configuration
6.2. Building a Benchmarker
6.3. Best score over time statistic (graph and CSV)
Index

Drools Planner optimizes automated planning by combining a search algorithm with the power of the Drools rule engine. Use cases of planning problems include:

  • Employee shift rostering, such as nurse rostering

  • Vehicle routing, freight routing and people routing, such as The traveling salesman problem and The traveling tournament problem

  • Educational timetabling, such as lesson scheduling, course schedule, exam scheduling and conference scheduling

  • Bin packing, stock sorting and storage organizing

  • Machine queue planning

  • Miss manners too (although the Drools Planner example solves this differently than the pure Drools Expert example)

A planning problem consists out of a number of constraints. Generally, there are 3 types of constraints:

  • A (negative) hard constraint must not be broken. For example: 1 teacher can not teach 2 different lessons at the same time.

  • A (negative) soft constraint should not be broken if it can be avoided. For example: Teacher A does not like to teach on Friday afternoon.

  • A positive constraint (or reward) should be fulfilled if possible. For example: Teacher B likes to teach on Monday morning.

These constraints define the score function of a planning problem. This is where the drools rule engine comes into play: adding constraints with score rules is easy and scalable.

A planning problem has a number of solutions. Each solution has a score. There are 3 categories of solutions:

  • A possible solution is a solution that does or does not break any number of constraints. Planning problems tend to have a incredibly large number of possible solutions. Most of those solutions are worthless.

  • A feasible solution is a solution that does not break any (negative) hard constraints. The number of feasible solutions tends to be relative to the number of possible solutions. Sometimes there are no feasible solutions. Every feasible solution is a possible solution.

  • An optimal solution is a solution with the highest score. Planning problems tend to have 1 or a few optimal solutions. There is always at least 1 optimal solution, even in the remote case that it's not a feasible solution because there are no feasible solutions.

Drools Planner supports several search algorithms to efficiently wade through the incredibly large number of possible solutions. It makes it easy to switch the search algorithm, by simply changing the solver configuration.

Drools Planner is production ready. The API is almost stable but backward incompatible changes can occur. With the recipe called UpgradeFromPreviousVersionRecipe.txt you can easily upgrade and deal with any backwards incompatible changes between versions. This recipe is included in every release.

Drools Planner, like Drools, is open source software under the Apache Software License.

You can download a release of Drools Planner from the drools download site. To run an example, just open the directory examples and run the script (runExamples.sh on linux or runExamples.bat on windows) and pick an example:

$ cd examples
$ ./runExamples.sh
$ cd examples
$ runExamples.bat

The Drools Planner jars are available on the jboss maven repository. If you use maven 2 or 3, just add a dependency to drools-planner-core in your project's pom.xml:


    <dependency>
        <groupId>org.drools.planner</groupId>
        <artifactId>drools-planner-core</artifactId>
        <version>5.x</version>
    </dependency>

You might also need to add the jboss repository in your repository manager (nexus, archiva, ...) or your ~/settings.xml:


    <repositories>
        <repository>
            <id>jboss-public-repository-group</id>
            <name>JBoss Public Maven Repository Group</name>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        ...
    </repositories>

Your questions and remarks are welcome on the user mailing list. Start the subject of your mail with [planner]. You can read/write to the user mailing list without littering your mailbox through this web forum or this newsgroup.

Feel free to report an issue (such as a bug, improvement or a new feature request) for the Drools Planner code or for this manual to the drools issue tracker. Select the component drools-planner.

Pull requests (and patches) are very welcome and get priority treatment! Attach them to a JIRA issue and optionally send a mail to the dev mailing list to get the issue fixed fast. By open sourcing your improvements, you 'll benefit from our peer review, improvements made upon your improvements and maybe even a thank you on our blog.

Check our blog and twitter (Geoffrey De Smet) for news. If Drools Planner helps you, don't forget to blog or to twitter about it!

Different solvers solve problems in different ways. Each type has advantages and disadvantages. We 'll roughly discuss a few of the solver types here. You can safely skip this section.

You can build a Solver instance with the XmlSolverConfigurer. Configure it with a solver configuration XML file:

    XmlSolverConfigurer configurer = new XmlSolverConfigurer();

    configurer.configure("/org/drools/planner/examples/nqueens/solver/nqueensSolverConfig.xml");
    Solver solver = configurer.buildSolver();

A basic solver configuration file looks something like this:


<?xml version="1.0" encoding="UTF-8"?>
<localSearchSolver>
    <scoreDrl>/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
    <scoreDefinition>
        <scoreDefinitionType>SIMPLE</scoreDefinitionType>
    </scoreDefinition>
    <termination>
        <scoreAttained>0</scoreAttained>
    </termination>
    <selector>
        <moveFactoryClass>org.drools.planner.examples.nqueens.solver.NQueensMoveFactory</moveFactoryClass>
    </selector>
    <acceptor>
        <completeSolutionTabuSize>1000</completeSolutionTabuSize>
    </acceptor>
    <forager>
        <pickEarlyType>NEVER</pickEarlyType>
    </forager>
</localSearchSolver>

This is a tabu search configuration for n queens. We 'll explain the various parts of a configuration later in this manual.

Drools Planner makes it relatively easy to switch a solver type just by changing the configuration. There's even a benchmark utility which allows you to play out different configurations against each other and report the most appropriate configuration for your problem. You could for example play out tabu search versus simulated annealing, on 4 queens and 64 queens.

A solver has a single Random instance. Some solver configurations use the Random instance a lot more than others. For example simulated annealing depends highly on random numbers, while tabu search only depends on it to deal with score ties. The environment mode influences the seed of that Random instance.

The environment mode also allows you to detect common bugs in your implementation.

You can set the environment mode in the solver configuration XML file:


<localSearchSolver>
    <environmentMode>DEBUG</environmentMode>
    ...
</localSearchSolver>

There are 3 environment modes:

A Solver can only solve 1 problem instance at a time.

You need to present the problem as a starting Solution instance to the solver.

You need to implement the Solution interface:

public interface Solution<extends Score> {


    S getScore();
    void setScore(S score);
    Collection<? extends Object> getFacts();
    Solution<S> cloneSolution();
}

For example, an NQueens instance just holds a list of all its queens:

public class NQueens implements Solution<SimpleScore> {


    private List<Queen> queenList;
    // ...
}

Most solvers use the cloneSolution() method to clone the solution each time they encounter a new best solution. The NQueens implementation just clones all Queen instances:

    public NQueens cloneSolution() {

        NQueens clone = new NQueens();
        List<Queen> clonedQueenList = new ArrayList<Queen>(queenList.size());
        for (Queen queen : queenList) {
            clonedQueenList.add(queen.clone());
        }
        clone.queenList = clonedQueenList;
        clone.score = score;
        return clone;
    }

The cloneSolution() method should clone no more and no less than the parts of the Solution that can change during planning. For example, in the curriculum course schedule example the lectures are cloned, but teachers, courses, timeslots, periods, rooms, ... are not cloned because only a lecture's appointed period or room changes during solving:

    /**

     * Clone will only deep copy the {@link #lectureList}.
     */
    public CurriculumCourseSchedule cloneSolution() {
        CurriculumCourseSchedule clone = new CurriculumCourseSchedule();
        ...
        clone.teacherList = teacherList;
        clone.curriculumList = curriculumList;
        clone.courseList = courseList;
        clone.dayList = dayList;
        clone.timeslotList = timeslotList;
        clone.periodList = periodList;
        clone.roomList = roomList;
        clone.unavailablePeriodConstraintList = unavailablePeriodConstraintList;
        List<Lecture> clonedLectureList = new ArrayList<Lecture>(lectureList.size());
        for (Lecture lecture : lectureList) {
            Lecture clonedLecture = lecture.clone();
            clonedLectureList.add(clonedLecture);
        }
        clone.lectureList = clonedLectureList;
        clone.score = score;
        return clone;
    }

First, you will need to make a starting solution and set that on the solver:

solver.setStartingSolution(startingSolution);

For large problems, a simple filler algorithm like createNQueens(int) doesn't suffice. A (local search) solver starting from a bad starting solution wastes a lot of time to reach a solution which an initializer algorithm can generate in a fraction of that time.

An initializer algorithm usually works something like this:

Such an algorithm is very deterministic: it's really fast, but you can't give it more time to generate an even better solution. In some cases the solution it generates will be feasible, but in most cases it won't. You 'll need a real solver to get to a feasible or more optimal solution. Nevertheless you 'll want to such an initializer to give the real solver a serious head start. You can do this by implementing the StartingSolutionInitializer interface:

public interface StartingSolutionInitializer extends SolverAware {


    boolean isSolutionInitialized(Solution solution);
    void initializeSolution(Solution solution);
}

You'll need to set a (uninitialized) solution on the solver. Once the solver starts, it will first call the StartingSolutionInitializer to initialize the solution. If the StartingSolutionInitializer adds, edits or removes facts it needs to notify the workingMemory about this. It can use score calculation during its initialization process.

Here's an example on how you add the StartingSolutionInitializer to the configuration:


<localSearchSolver>
    ...
    <startingSolutionInitializerClass>org.drools.planner.examples.examination.solver.solution.initializer.ExaminationStartingSolutionInitializer</startingSolutionInitializerClass>
    ...
</localSearchSolver>

The ScoreDefinition interface defines the score representation. The score must a Score instance and the instance type (for example DefaultHardAndSoftScore) must be stable throughout the solver runtime.

The solver aims to find the solution with the highest score. The best solution is the solution with the highest score that it has encountered during its solving.

Most planning problems tend to use negative scores (the amount of negative constraints being broken) with an impossible perfect score of 0. This explains why the score of a solution of 4 queens is the negative of the number of queen couples which can attack each other.

A ScoreDefinition instance is configured in the solver configuration:


    <scoreDefinition>
        <scoreDefinitionType>SIMPLE</scoreDefinitionType>
    </scoreDefinition>

There are a couple of build-in ScoreDefinition implementations:

You can implement your own ScoreDefinition, although the build-in score definitions should suffice for most needs.

A ScoreCalculator instance is asserted into the working memory as a global called scoreCalculator. Your score rules need to (indirectly) update that instance. Usually you 'll make a single rule as an aggregation of the other rules to update the score:

global SimpleScoreCalculator scoreCalculator;

rule "multipleQueensHorizontal"
    when
        $q1 : Queen($id : id, $y : y);
        $q2 : Queen(id > $id, y == $y);
    then
        insertLogical(new UnweightedConstraintOccurrence("multipleQueensHorizontal", $q1, $q2));
end

// multipleQueensVertical is obsolete because it is always 0

rule "multipleQueensAscendingDiagonal"
    when
        $q1 : Queen($id : id, $ascendingD : ascendingD);
        $q2 : Queen(id > $id, ascendingD == $ascendingD);
    then
        insertLogical(new UnweightedConstraintOccurrence("multipleQueensAscendingDiagonal", $q1, $q2));
end

rule "multipleQueensDescendingDiagonal"
    when
        $q1 : Queen($id : id, $descendingD : descendingD);
        $q2 : Queen(id > $id, descendingD == $descendingD);
    then
        insertLogical(new UnweightedConstraintOccurrence("multipleQueensDescendingDiagonal", $q1, $q2));
end

rule "hardConstraintsBroken"
    when
        $occurrenceCount : Number() from accumulate(
            $unweightedConstraintOccurrence : UnweightedConstraintOccurrence(),
            count($unweightedConstraintOccurrence)
        );
    then
        scoreCalculator.setScore(- $occurrenceCount.intValue());
end

Optionally, you can also weigh your constraints differently, by multiplying the count of each score rule with its weight. For example in freight routing, you can make 5 broken "avoid crossroads" soft constraints count as much as 1 broken "avoid highways at rush hour" soft constraint. This allows your business analysts to easily tweak the score function as they see fit.

Here's an example of all the NQueens constraints written as a single rule, using multi pattern accumulates and making multipleQueensHorizontal constraint outweigh the other constraints 5 times:

// Warning: This currently triggers backwards chaining instead of forward chaining and seriously hurts performance and scalability.
rule "constraintsBroken"
    when
        $multipleQueensHorizontal : Long()
        from accumulate(
            $q1 : Queen($id : id, $y : y)
            and Queen(id > $id, y == $y),
           count($q1)
        );
        $multipleQueensAscendingDiagonal : Long()
        from accumulate(
            $q2 : Queen($id : id, $ascendingD : ascendingD)
            and Queen(id > $id, ascendingD == $ascendingD),
           count($q2)
        );
        $multipleQueensDescendingDiagonal : Long()
        from accumulate(
            $q3 : Queen($id : id, $descendingD : descendingD)
            and Queen(id > $id, descendingD == $descendingD),
           count($q3)
        );
    then
        scoreCalculator.setScore(- (5 * $multipleQueensHorizontal) - $multipleQueensAscendingDiagonal - $multipleQueensDescendingDiagonal);
end

In case you haven't figured it out yet: performance (and scalability) is very important for solving planning problems. What good is a real-time freight routing solver that takes a day to find a feasible solution? Even small and innocent looking problems can hide an enormous problem size. For example, they probably still don't know the optimal solution of the traveling tournament problem for as little as 10 traveling teams.

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

An algorithm that checks every possible solution (even with pruning) can easily run for billions of years on a single real-life planning problem. Most of the time, we are happy with a feasible solution found in a limited amount of time. Local search tends to find a feasible solution relatively fast. Because it acts very much like a human, it is also pretty natural to program.

Local search solves a problem by making a move on the current solution which changes it into a better solution. It does that high number of iterations untill its time runs out and it is satisfied with the solution. It starts with the starting solution.

A local search algorithm and the drools rule engine turn out to be a really nice combination, because:

Drools Planner's local search implementation combines both. On top of that, it also offers additional support for benchmarking, etc.

A move is the change from a solution A to a solution B. For example, below you can see a single move on the starting solution of 4 queens that moves a single queen to another row:


A move can have a small or large impact. In the above example, the move of queen C0 to C2 is a small move. Some moves are the same move type. These are some possibilities for move types in n queens:

  • Move a single queen to another row. This is a small move. For example, move queen C0 to C2.

  • Move all queens a number of rows down or up. This a big move.

  • Move a single queen to another column. This is a small move. For example, move queen C2 to A0 (placing it on top of queen A0).

  • Add a queen to the board at a certain row and column.

  • Remove a queen from the board.

Because we have decided that all queens will be on the board at all times and each queen has an appointed column (for performance reasons), only the first 2 move types are usable in our example. Furthermore, we 're only using the first move type in the example because we think it gives the best performance, but you are welcome to prove us wrong.

Each of your move types will be an implementation of the Move interface:

public interface Move {


    boolean isMoveDoable(EvaluationHandler evaluationHandler);
    Move createUndoMove(EvaluationHandler evaluationHandler);
    void doMove(EvaluationHandler evaluationHandler);
}

Let's take a look at the Move implementation for 4 queens which moves a queen to a different row:

public class YChangeMove implements Move {


    private Queen queen;
    private int toY;
    public YChangeMove(Queen queen, int toY) {
        this.queen = queen;
        this.toY = toY;
    }
    // ... see below
}

An instance of YChangeMove moves a queen from its current y to a different y.

Drools Planner calls the doMove(WorkingMemory) method to do a move. The Move implementation must notify the working memory of any changes it does on the solution facts:

    public void doMove(WorkingMemory workingMemory) {

        FactHandle queenHandle = workingMemory.getFactHandle(queen);
        queen.setY(toY);
        workingMemory.update(queenHandle, queen); // after changes are made
    }

You need to call the workingMemory.update(FactHandle, Object) method after modifying the fact. Note that you can alter multiple facts in a single move and effectively create a big move (also known as a coarse-grained move).

Drools Planner automatically filters out non doable moves by calling the isDoable(WorkingMemory) method on a move. A non doable move is:

  • A move that changes nothing on the current solution. For example, moving queen B0 to row 0 is not doable.

  • A move that is impossible to do on the current solution. For example, moving queen B0 to row 10 is not doable because it would move it outside the board limits.

In the n queens example, a move which moves the queen from its current row to the same row isn't doable:

    public boolean isMoveDoable(WorkingMemory workingMemory) {

        int fromY = queen.getY();
        return fromY != toY;
    }

Because we won't generate a move which can move a queen outside the board limits, we don't need to check it. A move that is currently not doable can become doable on a later solution.

Each move has an undo move: a move (usually of the same type) which does the exact opposite. In the above example the undo move of C0 to C2 would be the move C2 to C0. An undo move can be created from a move, but only before the move has been done on the current solution.

    public Move createUndoMove(WorkingMemory workingMemory) {

        return new YChangeMove(queen, queen.getY());
    }

Notice that if C0 would have already been moved to C2, the undo move would create the move C2 to C2, instead of the move C2 to C0.

The local search solver can do and undo a move more than once, even on different (successive) solutions.

A move must implement the equals() and hashcode() methods. 2 moves which make the same change on a solution, must be equal.

    public boolean equals(Object o) {

        if (this == o) {
            return true;
        } else if (instanceof YChangeMove) {
            YChangeMove other = (YChangeMove) o;
            return new EqualsBuilder()
                    .append(queen, other.queen)
                    .append(toY, other.toY)
                    .isEquals();
        } else {
            return false;
        }
    }
    public int hashCode() {
        return new HashCodeBuilder()
                .append(queen)
                .append(toY)
                .toHashCode();
    }

In the above example, the Queen class uses the default Object equal() and hashcode() implementations. Notice that it checks if the other move is an instance of the same move type. This is important because a move will be compared to a move with another move type if you're using more then 1 move type.

It's also recommended to implement the toString() method as it allows you to read Drools Planner's logging more easily:

    public String toString() {

        return queen + " => " + toY;
    }

Now that we can make a single move, let's take a look at generating moves.

At each solution, local search will try all possible moves and pick the best move to change to the next solution. It's up to you to generate those moves. Let's take a look at all the possible moves on the starting solution of 4 queens:


As you can see, not all the moves are doable. At the starting solution we have 12 doable moves (n * (n - 1)), one of which will be move which changes the starting solution into the next solution. Notice that the number of possible solutions is 256 (n ^ n), much more that the amount of doable moves. Don't create a move to every possible solution. Instead use moves which can be sequentially combined to reach every possible solution.

It's highly recommended that you verify all solutions are connected by your move set. This means that by combining a finite number of moves you can reach any solution from any solution. Otherwise you're already excluding solutions at the start. Especially if you're using only big moves, you should check it. Just because big moves outperform small moves in a short test run, it doesn't mean that they will outperform them in a long test run.

You can mix different move types. Usually you're better off preferring small (fine-grained) moves over big (course-grained) moves because the score delta calculation will pay off more. However, as the traveling tournament example proves, if you can remove a hard constraint by using a certain set of big moves, you can win performance and scalability. Try it yourself: run both the simple (small moves) and the smart (big moves) version of the traveling tournament example. The smart version evaluates a lot less unfeasible solutions, which enables it to outperform and outscale the simple version.

Move generation currently happens with a MoveFactory:

public class NQueensMoveFactory extends CachedMoveListMoveFactory {


    public List<Move> createMoveList(Solution solution) {
        NQueens nQueens = (NQueens) solution;
        List<Move> moveList = new ArrayList<Move>();
        for (Queen queen : nQueens.getQueenList()) {
            for (int n : nQueens.createNList()) {
                moveList.add(new YChangeMove(queen, n));
            }
        }
        return moveList;
    }
}

But we might be making move generation part of the DRL's in the future.

A step is the winning move. The local search solver tries every move on the current solution and picks the best accepted move as the step:


Because the move B0 to B3 has the highest score (-3), it is picked as the next step. Notice that C0 to C3 (not shown) could also have been picked because it also has the score -3. If multiple moves have the same highest score, one is picked randomly, in this case B0 to B3.

The step is made and from that new solution, the local search solver tries all the possible moves again, to decide the next step after that. It continually does this in a loop, and we get something like this:


Notice that the local search solver doesn't use a search tree, but a search path. The search path is highlighted by the green arrows. At each step it tries all possible moves, but unless it's the step, it doesn't investigate that solution further. This is one of the reasons why local search is very scalable.

As you can see, the local search solver solves the 4 queens problem by starting with the starting solution and make the following steps sequentially:

  1. B0 to B3

  2. D0 to B2

  3. A0 to B1

If we turn on INFO logging, this is reflected into the logging:

INFO  Solving with random seed (0).
INFO  Starting with time spend (0), score (-6), new best score (-6).
INFO  Step index (0), time spend (4), score (-3), new best score (-3), accepted move size (12) for picked step ([Queen-1] 1 @ 0 => 3).
INFO  Step index (1), time spend (7), score (-1), new best score (-1), accepted move size (12) for picked step ([Queen-0] 0 @ 0 => 1).
INFO  Step index (2), time spend (10), score (0), new best score (0), accepted move size (12) for picked step ([Queen-3] 3 @ 0 => 2).
INFO  Solved at step index (2) with time spend (10) for best score (0) with average calculate count per second (7300).

Notice that the logging uses the toString() method of our Move implementation: [Queen-1] 1 @ 0 => 3.

The local search solver solves the 4 queens problem in 3 steps, by evaluating only 37 possible solutions (3 steps with 12 moves each + 1 starting solution), which is only fraction of all 256 possible solutions. It solves 16 queens in 31 steps, by evaluating only 7441 out of 18446744073709551616 possible solutions.

The local search solver decides the next step with the aid of 3 configurable components:


In the above example the selector generated the moves shown with the blue lines, the acceptor accepted all of them and the forager picked the move B0 to B3.

If we turn on DEBUG logging, we can see the decision making in the log:

INFO  Solving with random seed (0).
INFO  Starting with time spend (0), score (-6), new best score (-6).
DEBUG     Ignoring not doable move ([Queen-0] 0 @ 0 => 0).
DEBUG     Move score (-4), accept chance (1.0) for move ([Queen-0] 0 @ 0 => 1).
DEBUG     Move score (-4), accept chance (1.0) for move ([Queen-0] 0 @ 0 => 2).
DEBUG     Move score (-4), accept chance (1.0) for move ([Queen-0] 0 @ 0 => 3).
...
DEBUG     Move score (-3), accept chance (1.0) for move ([Queen-1] 1 @ 0 => 3).
...
DEBUG     Move score (-3), accept chance (1.0) for move ([Queen-2] 2 @ 0 => 3).
...
DEBUG     Move score (-4), accept chance (1.0) for move ([Queen-3] 3 @ 0 => 3).
INFO  Step index (0), time spend (6), score (-3), new best score (-3), accepted move size (12) for picked step ([Queen-1] 1 @ 0 => 3).
...

An acceptor is used (together with a forager) to active tabu search, simulated annealing, great deluge, ... For each move it generates an accept chance. If a move is rejected it is given an accept chance of 0.0.

You can implement your own Acceptor, although the build-in acceptors should suffice for most needs. You can also combine multiple acceptors.

When tabu search takes steps it creates tabu's. It does not accept a move as the next step if that move breaks tabu. Drools Planner implements several tabu types:

You can even combine tabu types:


    <acceptor>
        <completeSolutionTabuSize>1000</completeSolutionTabuSize>
        <completeMoveTabuSize>7</completeMoveTabuSize>
    </acceptor>

If you pick a too small tabu size, your solver can still get stuck in a local optimum. On the other hand, with the exception of solution tabu, if you pick a too large tabu size, your solver can get stuck by bouncing of the walls. Use the benchmarker to fine tweak your configuration. Experiments teach us that it is generally best to use a prime number for the move tabu, undo move tabu or property tabu size.

A tabu search acceptor should be combined with a high or no subset selection.

Simulated annealing does not always pick the move with the highest score, neither does it evaluate many moves per step. At least at first. Instead, it gives unimproving moves also a chance to be picked, depending on its score and the time gradient of the Termination. In the end, it gradually turns into a simple local search, only accepting improving moves.

In many use cases, simulated annealing surpasses tabu search. By changing a few lines of configuration, you can easily switch from tabu search to simulated annealing and back.

Start with a simulatedAnnealingStartingTemperature set to the maximum score delta a single move can cause. Use the Benchmarker to tweak the value.


    <acceptor>
      <simulatedAnnealingStartingTemperature>2hard/100soft</simulatedAnnealingStartingTemperature>
    </acceptor>
    <forager>
        <minimalAcceptedSelection>4</minimalAcceptedSelection>
    </forager>

A simulated annealing acceptor should be combined with a low subset selection. The classic algorithm uses a minimalAcceptedSelection of 1, but usually 4 performs better.

You can even combine it with a tabu acceptor at the same time. Use a lower tabu size than in a pure tabu search configuration.


    <acceptor>
      <simulatedAnnealingStartingTemperature>10.0</simulatedAnnealingStartingTemperature>
      <completePropertyTabuSize>5</completePropertyTabuSize>
    </acceptor>
    <forager>
        <minimalAcceptedSelection>4</minimalAcceptedSelection>
    </forager>

This differs from phasing, another powerful technique, where first simulated annealing is used, followed by tabu search.

A forager gathers all accepted moves and picks the move which is the next step. Normally it picks the accepted move with the highest score. If several accepted moves have the highest score, one is picked randomly, weighted on their accept chance.

You can implement your own Forager, although the build-in forager should suffice for most needs.

Sooner or later the local search solver will have to stop solving. This can be because of a number of reasons: the time is up, the perfect score has been reached, ... The only thing you can't depend on is on finding the optimal solution (unless you know the optimal score), because a local search algorithm doesn't know it when it finds the optimal solution. For real-life problems this doesn't turn out to be much of a problem, because finding the optimal solution would take billions of years, so you 'll want to terminate sooner anyway.

You can configure when a local search solver needs to stop by configuring a Termination. A Termination can calculate a time gradient, which is a ratio between the time already spend solving and the expected entire solving time.

You can implement your own Termination, although the build-in Terminations should suffice for most needs.

You can build a Benchmarker instance with theXmlSolverBenchmarker. Configure it with a benchmarker configuration xml file:

    XmlSolverBenchmarker benchmarker = new XmlSolverBenchmarker();

    benchmarker.configure("/org/drools/planner/examples/nqueens/benchmark/nqueensSolverBenchmarkConfig.xml");
    benchmarker.benchmark();
    benchmarker.writeResults(resultFile);

A basic benchmarker configuration file looks something like this:


<?xml version="1.0" encoding="UTF-8"?>
<solverBenchmarkSuite>
    <benchmarkDirectory>local/data/nqueens</benchmarkDirectory>

    <inheritedSolverBenchmark>
        <unsolvedSolutionFile>data/nqueens/unsolved/unsolvedNQueens32.xml</unsolvedSolutionFile>
        <unsolvedSolutionFile>data/nqueens/unsolved/unsolvedNQueens64.xml</unsolvedSolutionFile>
        <localSearchSolver>
            <scoreDrl>/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
            <scoreDefinition>
                <scoreDefinitionType>SIMPLE</scoreDefinitionType>
            </scoreDefinition>
            <termination>
                <maximumSecondsSpend>20</maximumSecondsSpend>
            </termination>
            <selector>
                <moveFactoryClass>org.drools.planner.examples.nqueens.solver.move.factory.NQueensMoveFactory</moveFactoryClass>
            </selector>
            <forager>
                <pickEarlyType>NEVER</pickEarlyType>
            </forager>
        </localSearchSolver>
    </inheritedSolverBenchmark>

    <solverBenchmark>
        <name>Solution tabu</name>
        <localSearchSolver>
            <acceptor>
            <completeSolutionTabuSize>1000</completeSolutionTabuSize>
            </acceptor>
        </localSearchSolver>
    </solverBenchmark>
    <solverBenchmark>
        <name>Move tabu 5</name>
        <localSearchSolver>
            <acceptor>
            <completeMoveTabuSize>5</completeMoveTabuSize>
            </acceptor>
        </localSearchSolver>
    </solverBenchmark>
    <solverBenchmark>
        <name>Move tabu 7</name>
        <localSearchSolver>
            <acceptor>
            <completeMoveTabuSize>7</completeMoveTabuSize>
            </acceptor>
        </localSearchSolver>
    </solverBenchmark>
    <solverBenchmark>
        <name>Solution tabu and move tabu 7</name>
        <localSearchSolver>
            <acceptor>
            <completeSolutionTabuSize>1000</completeSolutionTabuSize>
            <completeMoveTabuSize>7</completeMoveTabuSize>
            </acceptor>
        </localSearchSolver>
    </solverBenchmark>
</solverBenchmarkSuite>

This benchmarker will try 4 configurations (1 solution tabu, 2 move tabu's and 1 solution-move tabu) on 2 data sets (32 and 64 queens), so it will run 8 solvers.

Every solverBenchmark entity contains a solver configuration (for example a local search solver) and one or more unsolvedSolutionFile entities. It will run the solver configuration on each of those unsolved solution files. A name is optional and generated if absent. The common part of multiple solverBenchmark entities can be extracted to the inheritedSolverBenchmark entity, but they can still be overwritten per solverBenchmark entity.

You need to specify a benchmarkDirectory (relative to the working directory). A summary statistic and the best solution of each solver run will be written in that directory.