JBoss.orgCommunity Documentation

Drools Planner User Guide

Version 5.5.0.Beta1


1. Planner introduction
1.1. What is Drools Planner?
1.2. What is a planning problem?
1.2.1. A planning problem is NP-complete
1.2.2. A planning problem has (hard and soft) constraints
1.2.3. A planning problem has a huge search space
1.3. Status of Drools Planner
1.4. Download and run the examples
1.4.1. Get the release zip and run the examples
1.4.2. Run the examples in an IDE (IntelliJ, Eclipse, NetBeans)
1.4.3. Use Drools Planner with maven, gradle, ivy, buildr or ANT
1.4.4. Build Drools Planner from source
1.5. Questions, issues and blog
2. Quick start
2.1. Cloud balancing tutorial
2.1.1. Problem statement
2.1.2. Domain model diagram
2.1.3. Main method
2.1.4. Solver configuration
2.1.5. Domain model implementation
2.1.6. Score configuration
2.1.7. Beyond this tutorial
3. Use cases and examples
3.1. Introduction
3.2. Toy examples
3.2.1. N queens
3.2.2. Cloud balancing
3.2.3. Traveling salesman (TSP - Traveling salesman problem)
3.2.4. Manners 2009
3.3. Real examples
3.3.1. Course timetabling (ITC 2007 track 3 - Curriculum course scheduling)
3.3.2. Machine reassignment (Google ROADEF 2012)
3.3.3. Vehicle routing
3.3.4. Hospital bed planning (PAS - Patient admission scheduling)
3.4. Difficult examples
3.4.1. Exam timetabling (ITC 2007 track 1 - Examination)
3.4.2. Employee rostering (INRC 2010 - Nurse rostering)
3.4.3. Sport scheduling (TTP - Traveling tournament problem)
4. Planner configuration
4.1. Overview
4.2. Solver configuration
4.2.1. Solver configuration by XML file
4.2.2. Solver configuration by Java API
4.3. Model your planning problem
4.3.1. Is this class a problem fact or planning entity?
4.3.2. Problem fact
4.3.3. Planning entity and planning variables
4.3.4. Planning value and planning value ranges
4.3.5. Planning problem and planning solution
4.4. Use the Solver
4.4.1. The Solver interface
4.4.2. Solving a problem
4.4.3. Environment mode: Are there bugs in my code?
4.4.4. Logging level: What is the Solver doing?
5. Score calculation
5.1. Score terminology
5.1.1. What is a score?
5.1.2. Positive and negative constraints
5.1.3. Score constraint weighting
5.1.4. Score level
5.1.5. Pareto scoring (AKA multi-objective optimization scoring)
5.1.6. The Score interface
5.2. Choose a Score definition
5.2.1. SimpleScore
5.2.2. HardAndSoftScore (recommended)
5.2.3. Implementing a custom Score
5.3. Calculate the Score
5.3.1. Score calculation types
5.3.2. Simple Java score calculation
5.3.3. Incremental Java score calculation
5.3.4. Drools score calculation
5.3.5. Detecting invalid scores
5.4. Score calculation performance tricks
5.4.1. Overview
5.4.2. Average calculation count per second
5.4.3. Incremental score calculation (with delta's)
5.4.4. Caching
5.4.5. Unused constraint
5.4.6. Build-in hard constraint
5.4.7. Other performance tricks
5.4.8. Score trap
5.4.9. stepLimit benchmark
5.5. Reusing the score calculation outside the Solver
6. Optimization algorithms
6.1. The size of real world problems
6.2. The secret sauce of Drools Planner
6.3. Optimization algorithms overview
6.4. Which optimization algorithms should I use?
6.5. SolverPhase
6.6. Termination
6.6.1. TimeMillisSpendTermination
6.6.2. ScoreAttainedTermination
6.6.3. StepCountTermination
6.6.4. UnimprovedStepCountTermination
6.6.5. Combining multiple Terminations
6.6.6. Asynchronous termination from another thread
6.7. SolverEventListener
6.8. Custom SolverPhase
7. Move and neighborhood selection
7.1. Move and neighborhood introduction
7.1.1. What is a Move?
7.1.2. What is a MoveSelector?
7.1.3. Subselecting of entities, values and other moves
7.2. General Selector features
7.2.1. CacheType: Create moves in advance or Just In Time
7.2.2. SelectionOrder: original, random or shuffled selection
7.2.3. Recommended combinations of CacheType and SelectionOrder
7.2.4. Filtered selection
7.2.5. Sorted selection
7.2.6. Probability selection
7.3. Generic MoveSelectors
7.3.1. changeMoveSelector
7.3.2. swapMoveSelector
7.3.3. pillarSwapMoveSelector
7.3.4. subChainChangeMoveSelector
7.3.5. subChainSwapMoveSelector
7.4. Combining multiple MoveSelectors
7.4.1. unionMoveSelector
7.4.2. cartesianProductMoveSelector
7.5. EntitySelector
7.5.1. TODO
7.6. ValueSelector
7.6.1. TODO
7.7. Custom moves
7.7.1. Custom moves introduction
7.7.2. The interface Move
7.7.3. MoveListFactory: the easy way to generate custom moves
7.7.4. MoveIteratorFactory: generate custom moves just in time
7.7.5. Move generation through DRL
8. Construction heuristics
8.1. Overview
8.2. First Fit
8.2.1. Algorithm description
8.2.2. Configuration
8.3. First Fit Decreasing
8.3.1. Algorithm description
8.3.2. Configuration
8.4. Best Fit
8.4.1. Algorithm description
8.4.2. Configuration
8.5. Best Fit Decreasing
8.5.1. Algorithm description
8.5.2. Configuration
8.6. Cheapest insertion
8.6.1. Algorithm description
8.6.2. Configuration
9. Local search
9.1. Overview
9.2. Hill climbing (simple local search)
9.2.1. Algorithm description
9.3. Tabu search
9.3.1. Algorithm description
9.4. Simulated annealing
9.4.1. Algorithm description
9.5. Late acceptance
9.5.1. Algorithm description
9.6. About neighborhoods, moves and steps
9.6.1. Move generation tips
9.6.2. A step
9.6.3. Getting stuck in local optima
9.7. Deciding the next step
9.7.1. Acceptor
9.7.2. Forager
9.8. Using a custom Termination, MoveSelector, EntitySelector, ValueSelector or Acceptor
10. Evolutionary algorithms
10.1. Overview
10.2. Evolutionary Strategies
10.3. Genetic algorithms
11. Exact methods
11.1. Overview
11.2. Brute Force
11.2.1. Algorithm description
11.2.2. Configuration
11.3. Depth-first search
11.3.1. Algorithm description
11.3.2. Configuration
12. Benchmarking and tweaking
12.1. Finding the best Solver configuration
12.2. Doing a benchmark
12.2.1. Adding the extra dependency
12.2.2. Building and running a PlannerBenchmark
12.2.3. ProblemIO: input and output of Solution files
12.2.4. Warming up the HotSpot compiler
12.3. Benchmark report
12.3.1. HTML report
12.3.2. Summary statistics
12.3.3. Statistic per data set (graph and CSV)
12.3.4. Ranking the Solvers
12.4. Advanced benchmarking
12.4.1. Benchmarking performance tricks
12.4.2. Template based benchmarking and matrix benchmarking
13. Repeated planning
13.1. Introduction to repeated planning
13.2. Backup planning
13.3. Continuous planning (windowed planning)
13.3.1. Immovable planning entities
13.4. Real-time planning (event based planning)

Drools Planner is a lightweight, embeddable planning engine that optimizes planning problems. It solves use cases, such as:

  • Employee shift rostering: timetabling nurses, repairmen, ...

  • Agenda scheduling: scheduling meetings, appointments, maintenance jobs, advertisements, ...

  • Educational timetabling: scheduling lessons, courses, exams, conference presentations, ...

  • Vehicle routing: planning vehicles (trucks, trains, boats, airplanes, ...) with freight and/or people

  • Bin packing: filling containers, trucks, ships and storage warehouses, but also cloud computers nodes, ...

  • Job shop scheduling: planning car assembly lines, machine queue planning, workforce task planning, ...

  • Cutting stock: minimizing waste while cutting paper, steel, carpet, ...

  • Sport scheduling: planning football leagues, baseball leagues, ...

  • Financial optimization: investment portfolio optimization, risk spreading, ...

Every organization faces planning problems: provide products and services with a limited set of constrained resources (employees, assets, time and money).

Drools Planner helps normal JavaTM programmers solve planning problems efficiently. Under the hood, it combines optimization heuristics and metaheuristics with very efficient score calculation.

Drools Planner, like the rest of Drools, is business-friendly open source software under the Apache Software License 2.0 (layman's explanation). It is 100% pure JavaTM and runs on any JVM.

All the use cases above are probably NP-complete. In layman's terms, this means:

  • It's easy to verify a given solution to a problem in reasonable time.

  • There is no silver bullet to find the optimal solution of a problem in reasonable time (*).

Note

(*) At least, none of the smartest computer scientists in the world have found such a silver bullet yet. But if they find one for 1 NP-complete problem, it will work for every NP-complete problem.

In fact, there's a $ 1,000,000 reward for anyone that proves if such a silver bullet actually exists or not.

The implication of this is pretty dire: solving your problem is probably harder than you anticipated, because the 2 common techniques won't suffice:

  • A brute force algorithm (even a smarter variant) will take too long.

  • A quick algorithm, for example in bin packing, putting in the largest items first, will return a solution that is usually far from optimal.

By using advanced optimization algorithms, Planner does find a good solution in reasonable time for such planning problems.

A planning problem has a number of solutions. There are several categories of solutions:

Counterintuitively, the number of possible solutions is huge (if calculated correctly), even with a small dataset. As you can see in the examples, most instances have a lot more possible solutions than the minimal number of atoms in the known universe (10^80). Because there is no silver bullet to find the optimal solution, any implementation is forced to evaluate at least a subset of all those possible solutions.

Drools Planner supports several optimization algorithms to efficiently wade through that incredibly large number of possible solutions. Depending on the use case, some optimization algorithms perform better than others, but it's impossible to tell in advance. With Planner, it is easy to switch the optimization algorithm, by changing the solver configuration in a few lines of XML or code.

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 to a newer version and quickly deal with any backwards incompatible changes. That recipe file is included in every release.

To try it now:

The Examples GUI application will open. Just pick an example:

Note

Planner itself has no GUI dependencies. It runs just as well on a server or a mobile JVM as it does on the desktop.

The Drools Planner jars are available in the central maven repository (and also in the JBoss maven repository).

If you use maven, 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>...</version>
    </dependency>

This is similar for gradle, ivy and buildr. To identify the latest version, check the central maven repository.

If you're still using ant (without ivy), copy all the jars from the download zip's binaries directory and manually verify that your classpath doesn't contain duplicate jars.

Note

The download zip's binaries directory contains far more jars then drools-planner-core actually uses. It also contains the jars used by other modules, such as drools-planner-benchmark.

Check the maven repository pom.xml files to determine the minimal dependency set for a specific version of a specific module.

You can also easily build Drools Planner from source yourself.

Set up Git and clone drools-planner from GitHub (or alternatively, download the zipball):

$ git clone git@github.com:droolsjbpm/drools-planner.git drools-planner
...

Then do a Maven 3 build:

$ cd drools-planner
$ mvn -DskipTests clean install
...

After that, you can run any example directly from the command line, just run this command and pick an example:

$ cd drools-planner-examples
$ mvn exec:exec
...

Your questions and comments 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! Include the pull request link 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 and from our improvements made upon your improvements.

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

Suppose your company owns a number of cloud computers and needs to run a number of processes on those computers. Assign each process to a computer under the following 4 constraints.

Hard constraints which must be fulfilled:

Soft constraints which should be optimized:

How would you do that? This problem is a form of bin packing. Here's a simplified example where we assign 4 processes to 2 computers with 2 constraints (CPU and RAM) with a simple algorithm:

The simple algorithm used here is the First Fit Decreasing algorithm, which assigns the bigger processes first and assigns the smaller processes to the remaining space. As you can see, it's not optimal, because it does not leave enough room to assign the yellow process D.

Drools Planner does find the more optimal solution fast, by using additional, smarter algorithms. And it scales too: both in data (more processes, more computers) and constraints (more hardware requirements, other constraints). So let's take a look how we can use Planner for this.

Try it yourself. Download and configure the examples in your favorite IDE. Run org.drools.planner.examples.cloudbalancing.app.CloudBalancingHelloWorld. By default, it is configured to run for 120 seconds. It will execute this code:


The code above does this:

  • Build the Solver based on a solver configuration (in this case an XML file).

            SolverFactory solverFactory = new XmlSolverFactory(
    
                    "/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
            Solver solver = solverFactory.buildSolver();
  • Load the problem. CloudBalancingGenerator generates a random problem: you'll replace this with a class that loads a real problem, for example from a database.

            CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(400, 1200);
  • Solve the problem.

            solver.setPlanningProblem(unsolvedCloudBalance);
    
            solver.solve();
            CloudBalance solvedCloudBalance = (CloudBalance) solver.getBestSolution();
  • Display the result.

            System.out.println("\nSolved cloudBalance with 400 computers and 1200 processes:\n"
    
                    + toDisplayString(solvedCloudBalance));

The only non-obvious part is building the Solver. Let's examine that.

Take a look at the solver configuration:


This solver configuration consists out of 3 parts:

  • Domain model configuration: What can Planner change? We need to make Planner aware of our domain classes:

    
      <solutionClass>org.drools.planner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
      <planningEntityClass>org.drools.planner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>
  • Score configuration: How should Planner optimize the planning variables? Since we have hard and soft constraints, we use a HardAndSoftScore. But we also need to tell Planner how to calculate such the score, depending on our business requirements. Further down, we 'll look into 2 alternatives to calculate the score: using a simple Java implementation or using Drools DRL.

    
      <scoreDirectorFactory>
        <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
        <simpleScoreCalculatorClass>org.drools.planner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
        <!--<scoreDrl>/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
      </scoreDirectorFactory>
  • Optimization algorithms configuration: How should Planner optimize it? Don't worry about this for now: this is a good default configuration that works on most planning problems. It will already surpass human planners and most in-house implementations. Using the Planner benchmark toolkit, you can tweak it to get even better results.

    
      <termination>
        <maximumSecondsSpend>120</maximumSecondsSpend>
      </termination>
      <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType>
      </constructionHeuristic>
      <localSearch>
        <acceptor>
          <planningEntityTabuSize>7</planningEntityTabuSize>
        </acceptor>
        <forager>
          <minimalAcceptedSelection>1000</minimalAcceptedSelection>
        </forager>
      </localSearch>

Let's examine the domain model classes and the score configuration.

The class Process is a little bit special. We need to tell Planner that it can change the field computer, so we annotate the class with @PlanningEntity and the getter getComputer with @PlanningVariable:


The values that Planner can choose from for the field computer, are retrieved from a method on the Solution implementation: CloudBalance.getComputerList() which returns a list of all computers in the current data set. We tell Planner about this by using the annotation @ValueRange.

The method clone() is used by the class CloudBalance.

The class CloudBalance implements the Solution interface. It holds a list of all computers and processes. We need to tell Planner how to retrieve the collection of process which it can change, so we need to annotate the getter getProcessList with @PlanningEntityCollectionProperty.

The CloudBalance class also has a property score which is the Score of that Solution instance in it's current state:

Example 2.5. CloudBalance.java

public class CloudBalance ... implements Solution<HardAndSoftScore> {


    private List<CloudComputer> computerList;
    private List<CloudProcess> processList;
    private HardAndSoftScore score;
    public List<CloudComputer> getComputerList() {
        return computerList;
    }
    @PlanningEntityCollectionProperty
    public List<CloudProcess> getProcessList() {
        return processList;
    }
    ...
    public HardAndSoftScore getScore() {
        return score;
    }
    public void setScore(HardAndSoftScore score) {
        this.score = score;
    }
    // ************************************************************************
    // Complex methods
    // ************************************************************************
    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(computerList);
        // Do not add the planning entity's (processList) because that will be done automatically
        return facts;
    }
    /**
     * Clone will only deep copy the {@link #processList}.
     */
    public CloudBalance cloneSolution() {
        CloudBalance clone = new CloudBalance();
        clone.id = id;
        clone.computerList = computerList;
        List<CloudProcess> clonedProcessList = new ArrayList<CloudProcess>(
                processList.size());
        for (CloudProcess process : processList) {
            CloudProcess clonedProcess = process.clone();
            clonedProcessList.add(clonedProcess);
        }
        clone.processList = clonedProcessList;
        clone.score = score;
        return clone;
    }
    ...
}

The method getProblemFacts() is only needed for score calculation with Drools. It's not needed for the other score calculation types.

The method clone() is required. Planner uses it to make a clone of the best Solution in encounters during search.

Planner will search for the Solution with the highest Score. We're using a HardAndSoftScore, which means Planner will look for the solution with no hard constraints broken (fulfill hardware requirements) and as little as possible soft constraints broken (minimize maintenance cost).

There are several ways to implement the score function:

Let's take a look look at 2 different implementations:

One way to define a score function is to implement the interface SimpleScoreCalculator in plain Java.


  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.drools.planner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
  </scoreDirectorFactory>

Just implement the method calculateScore(Solution) to return a DefaultHardAndSoftScore instance.

Example 2.6. CloudBalancingSimpleScoreCalculator.java

public class CloudBalancingSimpleScoreCalculator implements SimpleScoreCalculator<CloudBalance> {


    /**
     * A very simple implementation. The double loop can easily be removed by using Maps as shown in
     * {@link CloudBalancingMapBasedSimpleScoreCalculator#calculateScore(CloudBalance)}.
     */
    public HardAndSoftScore calculateScore(CloudBalance cloudBalance) {
        int hardScore = 0;
        int softScore = 0;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            int cpuPowerUsage = 0;
            int memoryUsage = 0;
            int networkBandwidthUsage = 0;
            boolean used = false;
            // Calculate usage
            for (CloudProcess process : cloudBalance.getProcessList()) {
                if (computer.equals(process.getComputer())) {
                    cpuPowerUsage += process.getRequiredCpuPower();
                    memoryUsage += process.getRequiredMemory();
                    networkBandwidthUsage += process.getRequiredNetworkBandwidth();
                    used = true;
                }
            }
            
            // Hard constraints
            int cpuPowerAvailable = computer.getCpuPower() - cpuPowerUsage;
            if (cpuPowerAvailable < 0) {
                hardScore += cpuPowerAvailable;
            }
            int memoryAvailable = computer.getMemory() - memoryUsage;
            if (memoryAvailable < 0) {
                hardScore += memoryAvailable;
            }
            int networkBandwidthAvailable = computer.getNetworkBandwidth() - networkBandwidthUsage;
            if (networkBandwidthAvailable < 0) {
                hardScore += networkBandwidthAvailable;
            }
            
            // Soft constraints
            if (used) {
                softScore -= computer.getCost();
            }
        }
        return DefaultHardAndSoftScore.valueOf(hardScore, softScore);
    }
}

Even if we optimize the code above to use Maps to iterate through the processList only once, it is still slow because it doesn't do incremental score calculation. To fix that, either use an incremental Java score function or a Drools score function. Let's take a look at the latter.

To use the Drools rule engine as a score function, simply add a scoreDrl resource in the classpath:


  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
    <scoreDrl>/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

First, we want to make sure that all computers have enough CPU, RAM and network bandwidth to support all their processes, so we make these hard constraints:

Example 2.7. cloudBalancingScoreRules.drl - hard constraints

...

import org.drools.planner.examples.cloudbalancing.domain.CloudBalance;
import org.drools.planner.examples.cloudbalancing.domain.CloudComputer;
import org.drools.planner.examples.cloudbalancing.domain.CloudProcess;

global HardAndSoftScoreHolder scoreHolder;

// ############################################################################
// Hard constraints
// ############################################################################

rule "requiredCpuPowerTotal"
    when
        $computer : CloudComputer($cpuPower : cpuPower)
        $requiredCpuPowerTotal : Number(intValue > $cpuPower) from accumulate(
            CloudProcess(
                computer == $computer,
                $requiredCpuPower : requiredCpuPower),
            sum($requiredCpuPower)
        )
    then
        insertLogical(new IntConstraintOccurrence("requiredCpuPowerTotal", ConstraintType.NEGATIVE_HARD,
                $requiredCpuPowerTotal.intValue() - $cpuPower,
                $computer));
end

rule "requiredMemoryTotal"
    ...
end

rule "requiredNetworkBandwidthTotal"
    ...
end

// ############################################################################
// Calculate hard score
// ############################################################################

// Accumulate hard constraints
rule "hardConstraintsBroken"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardConstraintsBroken($hardTotal.intValue());
end

Next, if those constraints are met, we want to minimize the maintenance cost, so we add that as a soft constraint:


If you use the Drools rule engine for score calculation, you can integrate with other Drools technologies, such as decision tables (XSL or web based), the Guvnor rule repository, ...

Use a good domain model: it will be easier to understand and solve your planning problem with Drools Planner. This is the domain model for the n queens example:

public class Column {

    
    private int index;
    // ... getters and setters
}
public class Row {

    
    private int index;
    // ... getters and setters
}
public class Queen {

    
    private Column column;
    private Row row;
    public int getAscendingDiagonalIndex() {...}
    public int getDescendingDiagonalIndex() {...}
    // ... getters and setters
}
public class NQueens implements Solution<SimpleScore> {

    
    private int n;
    private List<Column> columnList;
    private List<Row> rowList;
    private List<Queen> queenList;
    private SimpleScore score;
    // ... getters and setters
}

A Queen instance has a Column (for example: 0 is column A, 1 is column B, ...) and a Row (its row, for example: 0 is row 0, 1 is row 1, ...). Based on the column and the row, the ascending diagonal line as well as the descending diagonal line can be calculated. The column and row indexes start from the upper left corner of the chessboard.


When 2 queens share the same column, row or diagonal line, such as (*) and (**), they can attack each other.

A single NQueens instance contains a list of all Queen instances. It is the Solution implementation which will be supplied to, solved by and retrieved from the Solver. Notice that in the 4 queens example, NQueens's getN() method will always return 4.

Assign each process to a machine. All processes already have an original (unoptimized) assignment. Each process requires an amount of each resource (such as CPU, RAM, ...). This is more complex version of the Cloud balancing example.

The problem is defined by the Google ROADEF/EURO Challenge 2012.

Hard constraints:

  • Maximum capacity: The maximum capacity for each resource for each machine must not be exceeded.

  • Conflict: Processes of the same service must run on distinct machines.

  • Spread: Processes of the same service must be spread across locations.

  • Dependency: The processes of a service depending on another service must run in the neighborhood of a process of the other service.

  • Transient usage: Some resources are transient and count towards the maximum capacity of both the original machine as the newly assigned machine.

Soft constraints:

  • Load: The safety capacity for each resource for each machine should not be exceeded.

  • Balance: Leave room for future assignments by balancing the available resources on each machine.

  • Process move cost: A process has a move cost.

  • Service move cost: A service has a move cost.

  • Machine move cost: Moving a process from machine A to machine B has another A-B specific move cost.

model_a1_1: 2 resources, 1 neighborhoods, 4 locations, 4 machines, 79 services, 100 processes and 1 balancePenalties with flooredPossibleSolutionSize (10^60).
model_a1_2: 4 resources, 2 neighborhoods, 4 locations, 100 machines, 980 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^2000).
model_a1_3: 3 resources, 5 neighborhoods, 25 locations, 100 machines, 216 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^2000).
model_a1_4: 3 resources, 50 neighborhoods, 50 locations, 50 machines, 142 services, 1000 processes and 1 balancePenalties with flooredPossibleSolutionSize (10^1698).
model_a1_5: 4 resources, 2 neighborhoods, 4 locations, 12 machines, 981 services, 1000 processes and 1 balancePenalties with flooredPossibleSolutionSize (10^1079).
model_a2_1: 3 resources, 1 neighborhoods, 1 locations, 100 machines, 1000 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^2000).
model_a2_2: 12 resources, 5 neighborhoods, 25 locations, 100 machines, 170 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^2000).
model_a2_3: 12 resources, 5 neighborhoods, 25 locations, 100 machines, 129 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^2000).
model_a2_4: 12 resources, 5 neighborhoods, 25 locations, 50 machines, 180 services, 1000 processes and 1 balancePenalties with flooredPossibleSolutionSize (10^1698).
model_a2_5: 12 resources, 5 neighborhoods, 25 locations, 50 machines, 153 services, 1000 processes and 0 balancePenalties with flooredPossibleSolutionSize (10^1698).

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

        XmlSolverFactory solverFactory = new XmlSolverFactory();

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

A solver configuration file looks something like this:


<?xml version="1.0" encoding="UTF-8"?>
<solver>
  <!-- Define the model -->
  <solutionClass>org.drools.planner.examples.nqueens.domain.NQueens</solutionClass>
  <planningEntityClass>org.drools.planner.examples.nqueens.domain.Queen</planningEntityClass>

  <!-- Define the score function -->
  <scoreDirectorFactory>
    <scoreDefinitionType>SIMPLE</scoreDefinitionType>
    <scoreDrl>/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

  <!-- Configure the optimization algorithm(s) -->
  <termination>
    ...
  </termination>
  <constructionHeuristic>
    ...
  </constructionHeuristic>
  <localSearch>
    ...
  </localSearch>
</solver>

Notice the 3 parts in it:

We'll explain these various parts of a configuration later in this manual.

Drools Planner makes it relatively easy to switch optimization algorithm(s) 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.

As an alternative to the XML file, a solver configuration can also be configured with the SolverConfig API:

        SolverConfig solverConfig = new SolverConfig();


        solverConfig.setSolutionClass(NQueens.class);
        Set<Class<?>> planningEntityClassSet = new HashSet<Class<?>>();
        planningEntityClassSet.add(Queen.class);
        solverConfig.setPlanningEntityClassSet(planningEntityClassSet);
        ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig();
        scoreDirectorFactoryConfig.setScoreDefinitionType(ScoreDirectorFactoryConfig.ScoreDefinitionType.SIMPLE);
        scoreDirectorFactoryConfig.setScoreDrlList(
                Arrays.asList("/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl"));
        TerminationConfig terminationConfig = solverConfig.getTerminationConfig();
        // ...
        List<SolverPhaseConfig> solverPhaseConfigList = new ArrayList<SolverPhaseConfig>();
        ConstructionHeuristicSolverPhaseConfig constructionHeuristicSolverPhaseConfig
                = new ConstructionHeuristicSolverPhaseConfig();
        // ...
        solverPhaseConfigList.add(constructionHeuristicSolverPhaseConfig);
        LocalSearchSolverPhaseConfig localSearchSolverPhaseConfig = new LocalSearchSolverPhaseConfig();
        // ...
        solverPhaseConfigList.add(localSearchSolverPhaseConfig);
        solverConfig.setSolverPhaseConfigList(solverPhaseConfigList);
        Solver solver = solverConfig.buildSolver();

It is highly recommended to configure by XML file instead of this API. To dynamically configure a value at runtime, use the XML file as a template and extract the SolverConfig class with getSolverConfig() to configure the dynamic value at runtime:

        XmlSolverFactory solverFactory = new XmlSolverFactory();

        solverFactory.configure("/org/drools/planner/examples/nqueens/solver/nqueensSolverConfig.xml");
        SolverConfig solverConfig = solverFactory.getSolverConfig();
        solverConfig.getTerminationConfig().setMaximumMinutesSpend(userInput);
        Solver solver = solverConfig.buildSolver();

A problem fact is any JavaBean (POJO) with getters that does not change during planning. Implementing the interface Serializable is recommended (but not required). For example in n queens, the columns and rows are problem facts:

public class Column implements Serializable {


    private int index;
    // ... getters
}
public class Row implements Serializable {


    private int index;
    // ... getters
}

A problem fact can reference other problem facts of course:

public class Course implements Serializable {


    private String code;
    private Teacher teacher; // Other problem fact
    private int lectureSize;
    private int minWorkingDaySize;
    private List<Curriculum> curriculumList; // Other problem facts
    private int studentSize;
    // ... getters
}

A problem fact class does not require any Planner specific code. For example, you can reuse your domain classes, which might have JPA annotations.

A planning entity is a JavaBean (POJO) that changes during solving, for example a Queen that changes to another row. A planning problem has multiple planning entities, for example for a single n queens problem, each Queen is a planning entity. But there's usually only 1 planning entity class, for example the Queen class.

A planning entity class needs to be annotated with the @PlanningEntity annotation.

Each planning entity class has 1 or more planning variables. It usually also has 1 or more defining properties. For example in n queens, a Queen is defined by its Column and has a planning variable Row. This means that a Queen's column never changes during solving, while its row does change.

@PlanningEntity

public class Queen {
    private Column column;
    // Planning variables: changes during planning, between score calculations.
    private Row row;
    // ... getters and setters
}

A planning entity class can have multiple planning variables. For example, a Lecture is defined by its Course and its index in that course (because 1 course has multiple lectures). Each Lecture needs to be scheduled into a Period and a Room so it has 2 planning variables (period and room). For example: the course Mathematics has 8 lectures per week, of which the first lecture is Monday morning at 08:00 in room 212.

@PlanningEntity

public class Lecture {
    private Course course;
    private int lectureIndexInCourse;
    // Planning variables: changes during planning, between score calculations.
    private Period period;
    private Room room;
    // ...
}

The solver configuration also needs to be made aware of each planning entity class:

<solver>

  ...
  <planningEntityClass>org.drools.planner.examples.nqueens.domain.Queen</planningEntityClass>
  ...
</solver>

Some uses cases have multiple planning entity classes. For example: route freight and trains into railway network arcs, where each freight can use multiple trains over its journey and each train can carry multiple freights per arc. Having multiple planning entity classes directly raises the implementation complexity of your use case.

Some optimization algorithms work more efficiently if they have an estimation of which planning entities are more difficult to plan. For example: in bin packing bigger items are harder to fit, in course scheduling lectures with more students are more difficult to schedule and in n queens the middle queens are more difficult to fit on the board.

Therefore, you can set a difficultyComparatorClass to the @PlanningEntity annotation:

@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class)

public class CloudProcess {
    // ...
}
public class CloudProcessDifficultyComparator implements Comparator<CloudProcess> {


    public int compare(CloudProcess a, CloudProcess b) {
        return new CompareToBuilder()
                .append(a.getRequiredMultiplicand(), b.getRequiredMultiplicand())
                .append(a.getId(), b.getId())
                .toComparison();
    }
}

Alternatively, you can also set a difficultyWeightFactoryClass to the @PlanningEntity annotation, so you have access to the rest of the problem facts from the solution too:

@PlanningEntity(difficultyWeightFactoryClass = QueenDifficultyWeightFactory.class)

public class Queen {
    // ...
}
public interface PlanningEntityDifficultyWeightFactory {


    Comparable createDifficultyWeight(Solution solution, Object planningEntity);
}
public class QueenDifficultyWeightFactory implements PlanningEntityDifficultyWeightFactory {


    public Comparable createDifficultyWeight(Solution solution, Object planningEntity) {
        NQueens nQueens = (NQueens) solution;
        Queen queen = (Queen) planningEntity;
        int distanceFromMiddle = calculateDistanceFromMiddle(nQueens.getN(), queen.getColumnIndex());
        return new QueenDifficultyWeight(queen, distanceFromMiddle);
    }
    // ...
    public static class QueenDifficultyWeight implements Comparable<QueenDifficultyWeight> {
        private final Queen queen;
        private final int distanceFromMiddle;
        public QueenDifficultyWeight(Queen queen, int distanceFromMiddle) {
            this.queen = queen;
            this.distanceFromMiddle = distanceFromMiddle;
        }
        public int compareTo(QueenDifficultyWeight other) {
            return new CompareToBuilder()
                    // The more difficult queens have a lower distance to the middle
                    .append(other.distanceFromMiddle, distanceFromMiddle) // Decreasing
                    .append(queen.getColumnIndex(), other.queen.getColumnIndex())
                    .toComparison();
        }
    }
}

None of the current planning variable state may be used to compare planning entities. They are likely to be null anyway. For example, a Queen's row variable may not be used.

Each planning entity has its own set of possible planning values for a planning variable. For example, if a teacher can never teach in a room that does not belong to his department, lectures of that teacher can limit their room value range to the rooms of his department.

    @PlanningVariable

    @ValueRange(type = ValueRangeType.FROM_PLANNING_ENTITY_PROPERTY, planningEntityProperty = "possibleRoomList")
    public Room getRoom() {
        return room;
    }
    public List<Room> getPossibleRoomList() {
        return getCourse().getTeacher().getPossibleRoomList();
    }

Never use this to enforce a soft constraint (or even a hard constraint when the problem might not have a feasible solution). For example: Unless there is no other way, a teacher can not teach in a room that does not belong to his department. In this case, the teacher should not be limited in his room value range (because sometimes there is no other way).

A planning entity should not use other planning entities to determinate its value range. That would only try to make it solve the planning problem itself and interfere with the optimization algorithms.

Some use cases, such as TSP and Vehicle Routing, require chaining. This means the planning entities point to each other and form a chain.

A planning variable that is chained either:

Here are some example of valid and invalid chains:

Every initialized planning entity is part of an open-ended chain that begins from an anchor. A valid model means that:

The optimization algorithms and build-in MoveFactory's do chain correction to guarantee that the model stays valid:

For example, in TSP the anchor is a Domicile (in vehicle routing it is the vehicle):

public class Domicile ... implements Appearance {


    ...
    public City getCity() {...}
}

The anchor (which is a problem fact) and the planning entity implement a common interface, for example TSP's Appearance:

public interface Appearance {


    City getCity();
}

That interface is the return type of the planning variable. Furthermore, the planning variable is chained. For example TSP's Visit (in vehicle routing it is the customer):

@PlanningEntity

public class Visit ... implements Appearance {
    ...
    public City getCity() {...}
    @PlanningVariable(chained = true)
    @ValueRanges({
            @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "domicileList"),
            @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "visitList",
                    excludeUninitializedPlanningEntity = true)})
    public Appearance getPreviousAppearance() {
        return previousAppearance;
    }
    public void setPreviousAppearance(Appearance previousAppearance) {
        this.previousAppearance = previousAppearance;
    }
}

Notice how 2 value ranges need to be combined:

Some optimization algorithms work more efficiently if they have an estimation of which planning values are stronger, which means they are more likely to satisfy a planning entity. For example: in bin packing bigger containers are more likely to fit an item and in course scheduling bigger rooms are less likely to break the student capacity constraint.

Therefore, you can set a strengthComparatorClass to the @PlanningVariable annotation:

    @PlanningVariable(strengthComparatorClass = CloudComputerStrengthComparator.class)

    // ...
    public CloudComputer getComputer() {
        // ...
    }
public class CloudComputerStrengthComparator implements Comparator<CloudComputer> {


    public int compare(CloudComputer a, CloudComputer b) {
        return new CompareToBuilder()
                .append(a.getMultiplicand(), b.getMultiplicand())
                .append(b.getCost(), a.getCost()) // Descending (but this is debatable)
                .append(a.getId(), b.getId())
                .toComparison();
    }
}

Alternatively, you can also set a strengthWeightFactoryClass to the @PlanningVariable annotation, so you have access to the rest of the problem facts from the solution too:

    @PlanningVariable(strengthWeightFactoryClass = RowStrengthWeightFactory.class)

    // ...
    public Row getRow() {
        // ...
    }
public interface PlanningValueStrengthWeightFactory {


    Comparable createStrengthWeight(Solution solution, Object planningValue);
}
public class RowStrengthWeightFactory implements PlanningValueStrengthWeightFactory {


    public Comparable createStrengthWeight(Solution solution, Object planningValue) {
        NQueens nQueens = (NQueens) solution;
        Row row = (Row) planningValue;
        int distanceFromMiddle = calculateDistanceFromMiddle(nQueens.getN(), row.getIndex());
        return new RowStrengthWeight(row, distanceFromMiddle);
    }
    // ...
    public static class RowStrengthWeight implements Comparable<RowStrengthWeight> {
        private final Row row;
        private final int distanceFromMiddle;
        public RowStrengthWeight(Row row, int distanceFromMiddle) {
            this.row = row;
            this.distanceFromMiddle = distanceFromMiddle;
        }
        public int compareTo(RowStrengthWeight other) {
            return new CompareToBuilder()
                    // The stronger rows have a lower distance to the middle
                    .append(other.distanceFromMiddle, distanceFromMiddle) // Decreasing (but this is debatable)
                    .append(row.getIndex(), other.row.getIndex())
                    .toComparison();
        }
    }
}

None of the current planning variable state in any of the planning entities may be used to compare planning values. They are likely to be null anyway. For example, None of the row variables of any Queen may be used to determine the strength of a Row.

A cached problem fact is a problem fact that doesn't exist in the real domain model, but is calculated before the Solver really starts solving. The method getProblemFacts() has the chance to enrich the domain model with such cached problem facts, which can lead to simpler and faster score constraints.

For example in examination, a cached problem fact TopicConflict is created for every 2 Topic's which share at least 1 Student.

    public Collection<? extends Object> getProblemFacts() {

        List<Object> facts = new ArrayList<Object>();
        // ...
        facts.addAll(calculateTopicConflictList());
        // ...
        return facts;
    }
    private List<TopicConflict> calculateTopicConflictList() {
        List<TopicConflict> topicConflictList = new ArrayList<TopicConflict>();
        for (Topic leftTopic : topicList) {
            for (Topic rightTopic : topicList) {
                if (leftTopic.getId() < rightTopic.getId()) {
                    int studentSize = 0;
                    for (Student student : leftTopic.getStudentList()) {
                        if (rightTopic.getStudentList().contains(student)) {
                            studentSize++;
                        }
                    }
                    if (studentSize > 0) {
                        topicConflictList.add(new TopicConflict(leftTopic, rightTopic, studentSize));
                    }
                }
            }
        }
        return topicConflictList;
    }

Any score constraint that needs to check if no 2 exams have a topic which share a student are being scheduled close together (depending on the constraint: at the same time, in a row or in the same day), can simply use the TopicConflict instance as a problem fact, instead of having to combine every 2 Student instances.

Most optimization algorithms use the cloneSolution() method to clone the solution each time they encounter a new best solution (so they can recall it later) or to work with multiple solutions in parallel.

The NQueens implementation only deep clones all Queen instances. When the original solution is changed during planning, by changing a Queen, the clone stays the same.

    /**

     * Clone will only deep copy the {@link #queenList}.
     */
    public NQueens cloneSolution() {
        NQueens clone = new NQueens();
        clone.id = id;
        clone.= n;
        clone.columnList = columnList;
        clone.rowList = rowList;
        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 only deep clone the planning entities. Notice that the problem facts, such as Column and Row are normally not cloned: even their List instances are not cloned.

Build a Solution instance to represent your planning problem, so you can set it on the Solver as the planning problem to solve. For example in n queens, an NQueens instance is created with the required Column and Row instances and every Queen set to a different column and every row set to null.

    private NQueens createNQueens(int n) {

        NQueens nQueens = new NQueens();
        nQueens.setId(0L);
        nQueens.setN(n);
        List<Column> columnList = new ArrayList<Column>(n);
        for (int i = 0; i < n; i++) {
            Column column = new Column();
            column.setId((long) i);
            column.setIndex(i);
            columnList.add(column);
        }
        nQueens.setColumnList(columnList);
        List<Row> rowList = new ArrayList<Row>(n);
        for (int i = 0; i < n; i++) {
            Row row = new Row();
            row.setId((long) i);
            row.setIndex(i);
            rowList.add(row);
        }
        nQueens.setRowList(rowList);
        List<Queen> queenList = new ArrayList<Queen>(n);
        long id = 0;
        for (Column column : columnList) {
            Queen queen = new Queen();
            queen.setId(id);
            id++;
            queen.setColumn(column);
            // Notice that we leave the PlanningVariable properties (row) on null
            queenList.add(queen);
        }
        nQueens.setQueenList(queenList);
        return nQueens;
    }

Usually, most of this data comes from your data layer, and your Solution implementation just aggregates that data and creates the uninitialized planning entity instances to plan:

        private void createLectureList(CurriculumCourseSchedule schedule) {

            List<Course> courseList = schedule.getCourseList();
            List<Lecture> lectureList = new ArrayList<Lecture>(courseList.size());
            for (Course course : courseList) {
                for (int i = 0; i < course.getLectureSize(); i++) {
                    Lecture lecture = new Lecture();
                    lecture.setCourse(course);
                    lecture.setLectureIndexInCourse(i);
                    // Notice that we leave the PlanningVariable properties (period and room) on null
                    lectureList.add(lecture);
                }
            }
            schedule.setLectureList(lectureList);
        }

The environment mode allows you to detect common bugs in your implementation. It does not affect the logging level.

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


<solver>
  <environmentMode>DEBUG</environmentMode>
  ...
</solver>

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.

There are 4 environment modes:

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

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

INFO  Solving 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 (col2@row0).
DEBUG     Step index (1), time spend (3), score (0), initialized planning entity (col1@row2).
DEBUG     Step index (2), time spend (4), score (0), initialized planning entity (col3@row3).
DEBUG     Step index (3), time spend (5), score (-1), initialized planning entity (col0@row1).
INFO  Phase constructionHeuristic finished: step total (4), time spend (6), best score (-1).
DEBUG     Step index (0), time spend (10), score (-1),     best score (-1), accepted/selected move count (12/12) for picked step (col1@row2 => row3).
DEBUG     Step index (1), time spend (12), score (0), new best score (0), accepted/selected move count (12/12) for picked step (col3@row3 => row2).
INFO  Phase localSearch ended: step total (2), time spend (13), best score (0).
INFO  Solving ended: time spend (13), best score (0), average calculate count per second (4846).

All time spends are in milliseconds.

Everything is logged to SLF4J, which is a simple logging facade that can delegate any log to Logback, Apache Commons Logging, Log4j or java.util.logging. Add a dependency to the logging adaptor for your logging framework of choice. If you're not using any logging framework yet, you can use Logback by adding this Maven dependency:


    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.x</version>
    </dependency>

Configure the logging level on the package org.drools.planner. For example:

In Logback, configure it in your logback.xml file:


<configuration>

  <logger name="org.drools.planner" level="debug"/>

  ...

<configuration>

In Log4J, configure it in your log4j.xml file:


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

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

  ...

</log4j:configuration>

Sometimes a score constraint outranks another score constraint, no matter how many times the other is broken. In that case, those score constraints are in different levels. For example: a nurse cannot do 2 shifts at the same time (due to the constraints of physical reality), this outranks nurse happiness constraints.

Most use cases have only 2 score levels: hard and soft. When comparing 2 scores, they are compared lexicographically: the first score level gets compared first. If those differ, the others score levels are ignored. For example: a score that breaks 0 hard constraints and 1000000 soft constraints is better than a score that breaks 1 hard constraint and 0 soft constraints.

Score levels often employ score weighting per level. In such case, the hard constraint level usually makes the solution feasible and the soft constraint level maximizes profit by weighting the constraints on price.

Note

Your business will probably tell you that your hard constraints all have the same weight, because they cannot be broken (so their weight does not matter). This is not true and it could create a score trap. For example in cloud balance: if a Computer has 7 CPU too little for its Processes, then it must be weighted 7 times as much as if it had only 1 CPU too little. This way, there is an incentive to move a Process with 6 CPU or less away from that Computer.

3 or more score levels is supported. For example: a company might decide that profit outranks employee satisfaction (or visa versa), while both are outranked by the constraints of physical reality.

Far less common is the use case of pareto optimization, which is also known under the more confusing term multi-objective optimization. In pareto scoring, score constraints are in the same score level, yet they are not weighted against each other. When 2 scores are compared, each of the score constraints are compared individually and the score with the most dominating score constraints wins. Pareto scoring can even be combined with score levels and score constraint weighting.

Consider this example with positive constraints, where we want to get the most apples and oranges. Since it's impossible to compare apples and oranges, we can't weight them against each other. Yet, despite that we can't compare them, we can state that 2 apples are better then 1 apple. Similarly, we can state that 2 apples and 1 orange are better than just 1 orange. So despite our inability to compare some Scores conclusively (at which point we declare them equal), we can find a set of optimal scores. Those are called pareto optimal.

Scores are considered equal far more often. It's left up to a human to choose the better out of a set of best solutions (with equal scores) found by Planner. In the example above, the user must choose between solution A (3 apples and 1 orange) and solution B (1 apples and 6 oranges). It's guaranteed that Planner has not found another solution which has more apples or more oranges or even a better combination of both (such as 2 apples and 3 oranges).

To implement pareto scoring in Planner, implement a custom ScoreDefinition and Score. Future versions will provide out-of-the-box support.

Note

A pareto Score's method compareTo is not transitive because it does a pareto comparison. For example: 2 apples is greater than 1 apple. 1 apples is equal to 1 orange. Yet, 2 apples are not greater than 1 orange (but actually equal). Pareto comparison violates the contract of the interface java.lang.Comparable's method compareTo, but Planner's systems are pareto comparison safe, unless explicitly stated otherwise in this documentation.

A simple way to implement your score calculation in Java.

Just implement one method of the interface SimpleScoreCalculator:

public interface SimpleScoreCalculator<Sol extends Solution> {


    Score calculateScore(Sol solution);
   
}

For example in n queens:

public class NQueensSimpleScoreCalculator implements SimpleScoreCalculator<NQueens> {


    public SimpleScore calculateScore(NQueens nQueens) {
        int n = nQueens.getN();
        List<Queen> queenList = nQueens.getQueenList();
        
        int score = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                Queen leftQueen = queenList.get(i);
                Queen rightQueen = queenList.get(j);
                if (leftQueen.getRow() != null && rightQueen.getRow() != null) {
                    if (leftQueen.getRowIndex() == rightQueen.getRowIndex()) {
                        score--;
                    }
                    if (leftQueen.getAscendingDiagonalIndex() == rightQueen.getAscendingDiagonalIndex()) {
                        score--;
                    }
                    if (leftQueen.getDescendingDiagonalIndex() == rightQueen.getDescendingDiagonalIndex()) {
                        score--;
                    }
                }
            }
        }
        return DefaultSimpleScore.valueOf(score);
    }
}

Configure it in your solver configuration:


  <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.drools.planner.examples.nqueens.solver.score.NQueensSimpleScoreCalculator</simpleScoreCalculatorClass>
  </scoreDirectorFactory>

Alternatively, build a SimpleScoreCalculator instance at runtime and set it with the programmatic API:

    solverFactory.getSolverConfig().getScoreDirectorFactoryConfig.setSimpleScoreCalculator(simpleScoreCalculator);

A way to implement your score calculation incrementally in Java.

Implement all the methods of the interface IncrementalScoreCalculator and extend the class AbstractIncrementalScoreCalculator:

public interface IncrementalScoreCalculator<Sol extends Solution> {


    void resetWorkingSolution(Sol workingSolution);
    void beforeEntityAdded(Object entity);
    void afterEntityAdded(Object entity);
    void beforeAllVariablesChanged(Object entity);
    void afterAllVariablesChanged(Object entity);
    void beforeVariableChanged(Object entity, String variableName);
    void afterVariableChanged(Object entity, String variableName);
    void beforeEntityRemoved(Object entity);
    void afterEntityRemoved(Object entity);
    Score calculateScore();
    
}

For example in n queens:

public class NQueensAdvancedIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<NQueens> {


    private Map<Integer, List<Queen>> rowIndexMap;
    private Map<Integer, List<Queen>> ascendingDiagonalIndexMap;
    private Map<Integer, List<Queen>> descendingDiagonalIndexMap;
    private int score;
    public void resetWorkingSolution(NQueens nQueens) {
        int n = nQueens.getN();
        rowIndexMap = new HashMap<Integer, List<Queen>>(n);
        ascendingDiagonalIndexMap = new HashMap<Integer, List<Queen>>(* 2);
        descendingDiagonalIndexMap = new HashMap<Integer, List<Queen>>(* 2);
        for (int i = 0; i < n; i++) {
            rowIndexMap.put(i, new ArrayList<Queen>(n));
            ascendingDiagonalIndexMap.put(i, new ArrayList<Queen>(n));
            descendingDiagonalIndexMap.put(i, new ArrayList<Queen>(n));
            if (!= 0) {
                ascendingDiagonalIndexMap.put(- 1 + i, new ArrayList<Queen>(n));
                descendingDiagonalIndexMap.put((-i), new ArrayList<Queen>(n));
            }
        }
        score = 0;
        for (Queen queen : nQueens.getQueenList()) {
            insert(queen);
        }
    }
    public void beforeEntityAdded(Object entity) {
        // Do nothing
    }
    public void afterEntityAdded(Object entity) {
        insert((Queen) entity);
    }
    public void beforeAllVariablesChanged(Object entity) {
        retract((Queen) entity);
    }
    public void afterAllVariablesChanged(Object entity) {
        insert((Queen) entity);
    }
    public void beforeVariableChanged(Object entity, String variableName) {
        retract((Queen) entity);
    }
    public void afterVariableChanged(Object entity, String variableName) {
        insert((Queen) entity);
    }
    public void beforeEntityRemoved(Object entity) {
        retract((Queen) entity);
    }
    public void afterEntityRemoved(Object entity) {
        // Do nothing
    }
    private void insert(Queen queen) {
        Row row = queen.getRow();
        if (row != null) {
            int rowIndex = queen.getRowIndex();
            List<Queen> rowIndexList = rowIndexMap.get(rowIndex);
            score -= rowIndexList.size();
            rowIndexList.add(queen);
            List<Queen> ascendingDiagonalIndexList = ascendingDiagonalIndexMap.get(queen.getAscendingDiagonalIndex());
            score -= ascendingDiagonalIndexList.size();
            ascendingDiagonalIndexList.add(queen);
            List<Queen> descendingDiagonalIndexList = descendingDiagonalIndexMap.get(queen.getDescendingDiagonalIndex());
            score -= descendingDiagonalIndexList.size();
            descendingDiagonalIndexList.add(queen);
        }
    }
    private void retract(Queen queen) {
        Row row = queen.getRow();
        if (row != null) {
            List<Queen> rowIndexList = rowIndexMap.get(queen.getRowIndex());
            rowIndexList.remove(queen);
            score += rowIndexList.size();
            List<Queen> ascendingDiagonalIndexList = ascendingDiagonalIndexMap.get(queen.getAscendingDiagonalIndex());
            ascendingDiagonalIndexList.remove(queen);
            score += ascendingDiagonalIndexList.size();
            List<Queen> descendingDiagonalIndexList = descendingDiagonalIndexMap.get(queen.getDescendingDiagonalIndex());
            descendingDiagonalIndexList.remove(queen);
            score += descendingDiagonalIndexList.size();
        }
    }
    public SimpleScore calculateScore() {
        return DefaultSimpleScore.valueOf(score);
    }
}

Configure it in your solver configuration:


  <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <incrementalScoreCalculatorClass>org.drools.planner.examples.nqueens.solver.score.NQueensAdvancedIncrementalScoreCalculator</incrementalScoreCalculatorClass>
  </scoreDirectorFactory>

Optionally, to get better output when the IncrementalScoreCalculator is corrupted in environmentMode DEBUG or TRACE, you can overwrite the method buildScoreCorruptionAnalysis from AbstractIncrementalScoreCalculator.

A ScoreHolder instance is asserted into the WorkingMemory as a global called scoreHolder. Your score rules need to (directly or indirectly) update that instance. Usually you'll make a single rule as an aggregation of the other rules to update the score:

global SimpleScoreHolder scoreHolder;

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
        scoreHolder.setScore(- $occurrenceCount.intValue());
end

Most use cases will also weigh their constraints differently, by multiplying the count of each score rule with its weight.

Here's an example from CurriculumCourse, where assigning a Lecture to a Room which is missing 2 seats is weighted equally bad as having 1 isolated Lecture in a Curriculum:

// RoomCapacity: For each lecture, the number of students that attend the course must be less or equal
// than the number of seats of all the rooms that host its lectures.
// Each student above the capacity counts as 1 point of penalty.
rule "roomCapacity"
    when
        ...
    then
        insertLogical(new IntConstraintOccurrence("roomCapacity", ConstraintType.NEGATIVE_SOFT,
                ($studentSize - $capacity),
                ...));
end

// CurriculumCompactness: Lectures belonging to a curriculum should be adjacent
// to each other (i.e., in consecutive periods).
// For a given curriculum we account for a violation every time there is one lecture not adjacent
// to any other lecture within the same day.
// Each isolated lecture in a curriculum counts as 2 points of penalty.
rule "curriculumCompactness"
    when
        ...
    then
        insertLogical(new IntConstraintOccurrence("curriculumCompactness", ConstraintType.NEGATIVE_SOFT,
                2,
                ...));
end


// Accumulate soft constraints
rule "softConstraintsBroken"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $softTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_SOFT, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setSoftConstraintsBroken($softTotal.intValue());
end

Put the environmentMode in TRACE (or DEBUG) to detect corruption in the incremental score calculation. On the difference between TRACE and DEBUG, see the section about environmentMode. However, that will not detect if your score calculator implements your score constraints as your business actually desires.

A piece of incremental score calculator code can be difficult to write and to review. You can assert its correctness by using a different implementation (for example a SimpleScoreCalculator) to do the assertions trigged by the environmentMode. Just configure it as assertionScoreDirectorFactory:


  <environmentMode>DEBUG</environmentMode>
  ...
  <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <scoreDrl>/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
    <assertionScoreDirectorFactory>
      <scoreDefinitionType>...</scoreDefinitionType>
      <simpleScoreCalculatorClass>org.drools.planner.examples.nqueens.solver.score.NQueensSimpleScoreCalculator</simpleScoreCalculatorClass>
    </assertionScoreDirectorFactory>
  </scoreDirectorFactory>

Instead of implementing a hard constraint, you can sometimes make it build-in too. For example: If Course A should never be assigned to Room X, but it uses ValueRange from Solution, the Solver will often try to assign it to Room X too (only to find out that it breaks a hard constraint). Use filtered selection to define that Course A should only be assigned a Room other than X.

This tends to give a good performance gain, not just because the score calculation is faster, but mainly because most optimization algorithms will spend less time evaluating unfeasible solutions.

Note

Don't go overboard with this. Many optimization algorithms rely on the freedom to break hard constraints when changing planning entities, to get out of local optima. There is a real risk of trading short term benefits for long term harm.

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>
  ...
  <constructionHeuristic>
    ... <!-- First phase: First Fit decreasing -->
  </constructionHeuristic>
  <localSearch>
    ... <!-- Second phase: Simulated annealing -->
  </localSearch>
  <localSearch>
    ... <!-- Third phase: Tabu search -->
  </localSearch>
</solver>

The solver phases are run in the order defined by solver configuration. 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.

Not all phases terminate automatically and sometimes you don't want to wait that long anyway. A Solver can be terminated synchronously by up-front configuration or asynchronously from another thread.

Especially metaheuristics phases will need to be told when 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 metaheuristics algorithm generally 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 could take billions of years, so you 'll want to terminate sooner anyway. The only thing that matters is finding the best solution in the available time.

For synchronous termination, configure a Termination on a Solver or a SolverPhase when it needs to stop. You can implement your own Termination, but the build-in implementations should suffice for most needs. Every Termination can calculate a time gradient (needed for some optimization algorithms), which is a ratio between the time already spend solving and the estimated entire solving time of the Solver or SolverPhase.

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(ScoreDirector scoreDirector);
}

For example:

public class ExaminationSolutionInitializer implements CustomSolverPhaseCommand {


    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        Examination examination = (Examination) scoreDirector.getWorkingSolution();
        for (Exam exam : examination.getExamList()) {
            Score unscheduledScore = scoreDirector.calculateScore();
            ...
            for (Period period : examination.getPeriodList()) {
                scoreDirector.beforeVariableChanged(exam, "period");
                exam.setPeriod(period)
                scoreDirector.afterVariableChanged(exam, "period");
                Score score = scoreDirector.calculateScore();
                ...
            }
            ...
        }
    }
}

And configure it like this:


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

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

Important

If the changes of a CustomSolverPhaseCommand don't result in a better score, the best solution won't be changed (so effectively nothing will have changed for the next SolverPhase or CustomSolverPhaseCommand). To force such changes anyway, use forceUpdateBestSolution:


  <customSolverPhase>
    <customSolverPhaseCommandClass>...MyUnitializer</customSolverPhaseCommandClass>
    <forceUpdateBestSolution>true</forceUpdateBestSolution>
  </customSolverPhase>

Note

If the Solver or SolverPhase wants to terminate while a CustomSolverPhaseCommand is still running, it will wait to terminate until the CustomSolverPhaseCommand is done, however long that takes.

A Move is a change (or set of changes) from a solution A to a solution B. For example, the move below changes queen C from row 0 to row 2:

The new solution is called a neighbor of the original solution, because it can be reached in a single Move. Although a single move can change multiple queens, the neighbors of a solution should always be a very small subset of all possible solutions. For example, on that original solution, these are all possible changeMove's:

If we ignore the 4 changeMove's that have not impact and are therefore not doable, we can see that number of moves is n * (n - 1) = 12. This is far less than the number of possible solutions, which is n ^ n = 256. As the problem scales out, the number of possible moves increases far less than the number of possible solutions.

Yet, in 4 changeMove's or less we can reach any solution. For example we can reach a very different solution in 3 changeMove's:

All optimization algorithms use Move's to transition from one solution to a neighbor solution. Therefor, all the optimization algorithms are confronted with Move selection: the craft of creating and iterating moves efficiently and the art of finding the most promising subset of random moves to evaluate first.

A Selector's cacheType determines when a selection (such as a Move, an entity, a value, ...) is created and how long it lives.

Almost every Selector supports setting a cacheType:


    <changeMoveSelector>
      <cacheType>PHASE</cacheType>
      ...
    </changeMoveSelector>

The following cacheTypes are supported:

A cacheType can be set on composite selectors too:


    <unionMoveSelector>
      <cacheType>PHASE</cacheType>
      <changeMoveSelector/>
      <swapMoveSelector/>
      ...
    </unionMoveSelector>

Nested selectors of a cached selector cannot be configured to be cached themselves, unless it's a higher cacheType. For example: a STEP cached unionMoveSelector can hold a PHASE cached changeMoveSelector, but not a STEP cached changeMoveSelector.

A Selector's selectionOrder determines the order in which the selections (such as Moves, entities, values, ...) are iterated. An optimization algorithm will usually only iterate through a subset of its MoveSelector's selections, starting from the start, so the selectionOrder is critical to decide which Moves are evaluated.

Almost every Selector supports setting a selectionOrder:


    <changeMoveSelector>
      ...
      <selectionOrder>ORIGINAL</selectionOrder>
      ...
    </changeMoveSelector>

The following selectionOrders are supported:

A selectionOrder can be set on composite selectors too.

There are certain moves that you don't want to select, because:

Filtered selection can happen on any Selector in the selector tree, including any MoveSelector, EntitySelector or ValueSelector:

Filtering us the interface SelectionFilter:

public interface SelectionFilter<T> {


    boolean accept(ScoreDirector scoreDirector, T selection);
}

Implement the method accept to return false on a discarded selection. Unaccepted moves will not be selected and will therefore never have their method doMove called.

public class DifferentCourseSwapMoveFilter implements SelectionFilter<SwapMove> {


    public boolean accept(ScoreDirector scoreDirector, SwapMove move) {
        Lecture leftLecture = (Lecture) move.getLeftEntity();
        Lecture rightLecture = (Lecture) move.getRightEntity();
        return !leftLecture.getCourse().equals(rightLecture.getCourse());
    }
}

Apply the filter on the lowest level possible. In most cases, you 'll need to know both the entity and the value involved and you'll have to apply a moveFilterClass on the moveSelector:


    <swapMoveSelector>
      <moveFilterClass>org.drools.planner.examples.curriculumcourse.solver.move.DifferentCourseSwapMoveFilter</moveFilterClass>
    </swapMoveSelector>

But if possible apply it on a lower levels, such as an entityFilterClass on the entitySelector or a valueFilterClass on the valueSelector:


    <changeMoveSelector>
      <entitySelector>
        <entityFilterClass>...EntityFilter</entityFilterClass>
      </entitySelector>
    </changeMoveSelector>

Filtered selection works with any kind of cacheType and selectionOrder. You can configure multiple *FilterClass elements on a single selector.

Your custom moves must implement the Move interface:

public interface Move {


    boolean isMoveDoable(ScoreDirector scoreDirector);
    Move createUndoMove(ScoreDirector scoreDirector);
    void doMove(ScoreDirector scoreDirector);
    Collection<? extends Object> getPlanningEntities();
    Collection<? extends Object> getPlanningValues();
}

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

public class RowChangeMove implements Move {


    private Queen queen;
    private Row toRow;
    public RowChangeMove(Queen queen, Row toRow) {
        this.queen = queen;
        this.toRow = toRow;
    }
    // ... see below
}

An instance of RowChangeMove moves a queen from its current row to a different row.

Planner calls the doMove(ScoreDirector) method to do a move. The Move implementation must notify the ScoreDirector of any changes it make to the planning entities's variables:

    public void doMove(ScoreDirector scoreDirector) {

        scoreDirector.beforeVariableChanged(queen, "row"); // before changes are made to the queen.row
        queen.setRow(toRow);
        scoreDirector.afterVariableChanged(queen, "row"); // after changes are made to the queen.row
    }

You need to call the methods scoreDirector.beforeVariableChanged(Object, String) and scoreDirector.afterVariableChanged(Object, String) directly before and after modifying the entity. Alternatively, you can also call the methods scoreDirector.beforeAllVariablesChanged(Object) and scoreDirector.afterAllVariablesChanged(Object).

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

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(ScoreDirector scoreDirector) {

        return !ObjectUtils.equals(queen.getRow(), toRow);
    }

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 could become doable on the working Solution of a later step.

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

    public Move createUndoMove(ScoreDirector scoreDirector) {

        return new RowChangeMove(queen, queen.getRow());
    }

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.

A solver phase might do and undo the same Move more than once. In fact, many solver phases will iteratively do an undo a number of moves to evaluate them, before selecting one of those and doing that move again (without undoing it this time).

A Move must implement the getPlanningEntities() and getPlanningValues() methods. They are used by entity tabu and value tabu respectively. When they are called, the Move has already been done.

    public List<? extends Object> getPlanningEntities() {

        return Collections.singletonList(queen);
    }
    public Collection<? extends Object> getPlanningValues() {
        return Collections.singletonList(toRow);
    }

If your Move changes multiple planning entities, return all of them in getPlanningEntities() and return all their values (to which they are changing) in getPlanningValues().

    public Collection<? extends Object> getPlanningEntities() {

        return Arrays.asList(leftCloudProcess, rightCloudProcess);
    }
    public Collection<? extends Object> getPlanningValues() {
        return Arrays.asList(leftCloudProcess.getComputer(), rightCloudProcess.getComputer());
    }

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

    public boolean equals(Object o) {

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

Notice that it checks if the other move is an instance of the same move type. This instanceof check 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 Planner's logging more easily:

    public String toString() {

        return queen + " => " + toRow;
    }

Now that we can implement a single custom Move, let's take a look at generating such custom moves.

The easiest way to generate custom moves is by implementing the interface MoveListFactory:

public interface MoveListFactory {


    List<Move> createMoveList(Solution solution);
}

For example:

public class RowChangeMoveFactory implements MoveListFactory {


    public List<Move> createMoveList(Solution solution) {
        NQueens nQueens = (NQueens) solution;
        List<Move> moveList = new ArrayList<Move>();
        for (Queen queen : nQueens.getQueenList()) {
            for (Row toRow : nQueens.getRowList()) {
                moveList.add(new RowChangeMove(queen, toRow));
            }
        }
        return moveList;
    }
}

Simple configuration (which can be nested in a unionMoveSelector just like any other MoveSelector):


    <moveListFactory>
      <moveListFactoryClass>org.drools.planner.examples.nqueens.solver.move.factory.RowChangeMoveFactory</moveListFactoryClass>
    </moveListFactory>

Advanced configuration:


    <moveListFactory>
      ... <!-- Normal moveSelector properties -->
      <moveListFactoryClass>org.drools.planner.examples.nqueens.solver.move.factory.RowChangeMoveFactory</moveListFactoryClass>
    </moveListFactory>

Because the MoveListFactory generates all moves at once in a List<Move>, it does not support cacheType JUST_IN_TIME. Therefore, moveListFactory uses cacheType STEP by default and it scales badly in memory footprint.

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 DEBUG logging for the category org.drools.planner, then those steps are shown into the log:

INFO  Solving started: time spend (0), score (-6), new best score (-6), random seed (0).
DEBUG     Step index (0), time spend (20), score (-3), new best score (-3), accepted/selected move count (12/12) for picked step (col1@row0 => row3).
DEBUG     Step index (1), time spend (31), score (-1), new best score (-1), accepted/selected move count (12/12) for picked step (col0@row0 => row1).
DEBUG     Step index (2), time spend (40), score (0), new best score (0), accepted/selected move count (12/12) for picked step (col3@row0 => row2).
INFO  Phase localSearch ended: step total (3), time spend (41), best score (0).
INFO  Solving ended: time spend (41), best score (0), average calculate count per second (1780).

Notice that the logging uses the toString() method of our Move implementation: col1@row0 => row3.

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. Note: with construction heuristics it's even a lot more efficient.

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 TRACE logging for the category org.drools.planner, then the decision making is shown in the log:

INFO  Solver started: time spend (0), score (-6), new best score (-6), random seed (0).
TRACE         Ignoring not doable move (col0@row0 => row0).
TRACE         Move index (1), score (-4), accepted (true) for move (col0@row0 => row1).
TRACE         Move index (2), score (-4), accepted (true) for move (col0@row0 => row2).
TRACE         Move index (3), score (-4), accepted (true) for move (col0@row0 => row3).
...
TRACE         Move index (6), score (-3), accepted (true) for move (col1@row0 => row3).
...
TRACE         Move index (9), score (-3), accepted (true) for move (col2@row0 => row3).
...
TRACE         Move index (12), score (-4), accepted (true) for move (col3@row0 => row3).
DEBUG     Step index (0), time spend (6), score (-3), new best score (-3), accepted/selected move count (12/12) for picked step (col1@row0 => row3).
...

Because the last solution can degrade (especially in tabu search and simulated annealing), the Solver remembers the best solution it has encountered through the entire search path. Each time the current solution is better than the last best solution, the current solution is cloned and referenced as the new best solution.

An acceptor is used (together with a forager) to active tabu search, simulated annealing, great deluge, ... For each move it checks whether it is accepted or not.

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>
        <solutionTabuSize>1000</solutionTabuSize>
        <moveTabuSize>7</moveTabuSize>
    </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, entity tabu or value tabu size.

A tabu search acceptor should be combined with a high subset selection, such as 1000.

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 non improving 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 hill climber, 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>
      <planningEntityTabuSize>5</planningEntityTabuSize>
    </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.

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

You can build a PlannerBenchmark instance with the XmlPlannerBenchmarkFactory. Configure it with a benchmark configuration xml file:

    XmlPlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory();

    plannerBenchmarkFactory.configure("/org/drools/planner/examples/nqueens/benchmark/nqueensBenchmarkConfig.xml");
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();
    plannerBenchmark.benchmark();

A basic benchmark configuration file looks something like this:


<?xml version="1.0" encoding="UTF-8"?>
<plannerBenchmark>
  <benchmarkDirectory>local/data/nqueens</benchmarkDirectory>
  <!--<parallelBenchmarkCount>AUTO</parallelBenchmarkCount>-->
  <warmUpSecondsSpend>30</warmUpSecondsSpend>

  <inheritedSolverBenchmark>
    <problemBenchmarks>
      <xstreamAnnotatedClass>org.drools.planner.examples.nqueens.domain.NQueens</xstreamAnnotatedClass>
      <inputSolutionFile>data/nqueens/unsolved/unsolvedNQueens32.xml</inputSolutionFile>
      <inputSolutionFile>data/nqueens/unsolved/unsolvedNQueens64.xml</inputSolutionFile>
      <problemStatisticType>BEST_SOLUTION_CHANGED</problemStatisticType>
    </problemBenchmarks>
    <solver>
      <solutionClass>org.drools.planner.examples.nqueens.domain.NQueens</solutionClass>
      <planningEntityClass>org.drools.planner.examples.nqueens.domain.Queen</planningEntityClass>
      <scoreDirectorFactory>
        <scoreDefinitionType>SIMPLE</scoreDefinitionType>
        <scoreDrl>/org/drools/planner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
      </scoreDirectorFactory>
      <termination>
        <maximumSecondsSpend>20</maximumSecondsSpend>
      </termination>
      <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType>
      </constructionHeuristic>
    </solver>
  </inheritedSolverBenchmark>

  <solverBenchmark>
    <name>Entity tabu</name>
    <solver>
      <localSearch>
        <changeMoveSelector>
          <selectionOrder>ORIGINAL</selectionOrder>
        </changeMoveSelector>
        <acceptor>
          <planningEntityTabuSize>5</planningEntityTabuSize>
        </acceptor>
        <forager>
          <pickEarlyType>NEVER</pickEarlyType>
        </forager>
      </localSearch>
    </solver>
  </solverBenchmark>
  <solverBenchmark>
    <name>Value tabu</name>
    <solver>
      <localSearch>
        <changeMoveSelector>
          <selectionOrder>ORIGINAL</selectionOrder>
        </changeMoveSelector>
        <acceptor>
          <planningValueTabuSize>5</planningValueTabuSize>
        </acceptor>
        <forager>
          <pickEarlyType>NEVER</pickEarlyType>
        </forager>
      </localSearch>
    </solver>
  </solverBenchmark>
  <solverBenchmark>
    <name>Move tabu</name>
    <solver>
      <localSearch>
        <changeMoveSelector>
          <selectionOrder>ORIGINAL</selectionOrder>
        </changeMoveSelector>
        <acceptor>
          <moveTabuSize>5</moveTabuSize>
        </acceptor>
        <forager>
          <pickEarlyType>NEVER</pickEarlyType>
        </forager>
      </localSearch>
    </solver>
  </solverBenchmark>
</plannerBenchmark>

This PlannerBenchmark will try 3 configurations (1 move tabu, 1 entity tabu and 1 value tabu) on 2 data sets (32 and 64 queens), so it will run 6 solvers.

Every solverBenchmark element contains a solver configuration (for example with a local search solver phase) and one or more inputSolutionFile elements. It will run the solver configuration on each of those unsolved solution files. The element name is optional, because it is generated if absent. The inputSolutionFile is read by a ProblemIO.

To lower verbosity, the common part of multiple solverBenchmark entities can be extracted to the inheritedSolverBenchmark element. Yet, every element can still be overwritten per solverBenchmark element. Note that inherited solver phases such as <constructionHeuristic> or <localSearch> are not overwritten but instead are added to the tail of the solver phases list.

You need to specify a benchmarkDirectory (relative to the working directory). The best solution of each Solver run and a handy overview report will be written in that directory.

Note

It's recommended that the benchmarkDirectory is a directory not committed to source control and not cleaned by your build system. This way the generated files are not bloating your source control and they aren't lost when doing a build. Usually that directory is called local.

Matrix benchmarking is benchmarking a combination of value sets. For example: benchmark 4 planningEntityTabuSize values (5, 7, 11 and 13) combined with 3 minimalAcceptedSelection values (500, 1000 and 2000), resulting in 12 solver configurations.

To reduce the verbosity of such a benchmark configuration, you can use a Freemarker template for the benchmark configuration instead:


<plannerBenchmark>
  ...

  <inheritedSolverBenchmark>
    ...
  </inheritedSolverBenchmark>

<#list [5, 7, 11, 13] as planningEntityTabuSize>
<#list [500, 1000, 2000] as minimalAcceptedSelection>
  <solverBenchmark>
    <name>entityTabu ${planningEntityTabuSize} acceptedSelection ${minimalAcceptedSelection}</name>
    <solver>
      <localSearch>
        <unionMoveSelector>
          <changeMoveSelector/>
          <swapMoveSelector/>
        </unionMoveSelector>
        <acceptor>
          <planningEntityTabuSize>${planningEntityTabuSize}</planningEntityTabuSize>
        </acceptor>
        <forager>
          <minimalAcceptedSelection>${minimalAcceptedSelection}</minimalAcceptedSelection>
        </forager>
      </localSearch>
    </solver>
  </solverBenchmark>
</#list>
</#list>
</plannerBenchmark>

And configure it with the method configureFromTemplate:

    XmlPlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory();

    plannerBenchmarkFactory.configureFromTemplate("/org/drools/planner/examples/cloudbalancing/benchmark/cloudBalancingBenchmarkConfigTemplate.xml.ftl");
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();

Continuous planning is the technique of planning one or more upcoming planning windows at the same time and repeating that process monthly, weekly, daily or hourly. Because time is infinite, there are infinite future windows, so planning all future windows is impossible. Instead, plan only a fixed number of upcoming planning windows.

Past planning windows are immutable. The first upcoming planning window is considered stable (unlikely to change), while later upcoming planning windows are considered draft (likely to change during the next planning effort). Distant future planning windows are not planned at all.

Past planning windows have only immovable planning entities: the planning entities can no longer be changed (they are unable to move), but some of them are still needed in the score calculation, as they might affect some of the score constraints that apply on the upcoming planning entities. For example: when an employee should not work more than 5 days in a row, he shouldn't work today and tomorrow if he worked the past 4 days already.

Sometimes some planning entities are semi-immovable: they can be changed, but occur a certain score penalty if they differ from their original place. For example: avoid rescheduling hospital beds less than 2 days before the patient arrives (unless it's really worth it), avoid changing the airplane gate during the 2 hours before boarding (unless there is no alternative), ...


Notice the difference between the original planning of November 1th and the new planning of November 5th: some planning facts (F, H, I, J, K) changed, which results in unrelated planning entities (G) changing too.

To do real-time planning, first combine backup planning and continuous planning with short planning windows to lower the burden of real-time planning.

While the Solver is solving, an outside event might want to change one of the problem facts, for example an airplane is delayed and needs the runway at a later time. Do not change the problem fact instances used by the Solver while it is solving, as that will corrupt it. Instead, add a ProblemFactChange to the Solver which it will execute in the solver thread as soon as possible.

public interface Solver {


    ...
    boolean addProblemFactChange(ProblemFactChange problemFactChange);
    boolean isEveryProblemFactChangeProcessed();
    ...
}
public interface ProblemFactChange {


    void doChange(ScoreDirector scoreDirector);
}

Here's an example:

    public void deleteComputer(final CloudComputer computer) {

        solver.addProblemFactChange(new ProblemFactChange() {
            public void doChange(ScoreDirector scoreDirector) {
                CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
                // First remove the planning fact from all planning entities that use it
                for (CloudProcess process : cloudBalance.getProcessList()) {
                    if (ObjectUtils.equals(process.getComputer(), computer)) {
                        scoreDirector.beforeVariableChanged(process, "computer");
                        process.setComputer(null);
                        scoreDirector.afterVariableChanged(process, "computer");
                    }
                }
                // Next remove it the planning fact itself
                for (Iterator<CloudComputer> it = cloudBalance.getComputerList().iterator(); it.hasNext(); ) {
                    CloudComputer workingComputer = it.next();
                    if (ObjectUtils.equals(workingComputer, computer)) {
                        scoreDirector.beforeProblemFactRemoved(workingComputer);
                        it.remove(); // remove from list
                        scoreDirector.beforeProblemFactRemoved(workingComputer);
                        break;
                    }
                }
            }
        });
    }

In essence, the Solver will stop, run the ProblemFactChange and restart. Each SolverPhase will run again. Each configured Termination (except terminateEarly) will reset.

Normally, you won't configure any Termination, just call Solver.terminateEarly() when the results are needed. Alternatively, you can subscribe to the BestSolutionChangedEvent. A BestSolutionChangedEvent doesn't guarantee that every ProblemFactChange has been processed already, so check Solver.isEveryProblemFactChangeProcessed() and ignore any BestSolutionChangedEvent fired while that method returns false.