Business Resource Planner Guide

Red Hat JBoss BRMS 6.0

For JBoss Developers and Rules Authors

Doug Hoffman

Red Hat Content Services

Abstract

This guide provides the steps needed for installing Business Resource Planner and instructions for running example projects. Business Resource Planner is provided as a technical preview and is not supported by Red Hat.

Chapter 1. Introduction to Business Resource Planner

1.1. What is Business Resource Planner

Business Resource Planner is a lightweight, embeddable planning engine that optimizes planning problems. It helps normal Java TM programmers solve planning problems efficiently, and it combines optimization heuristics and metaheuristics with very efficient score calculations. Busines Resource Planner is provided as a technical preview and is not supported by Red Hat.
Business Resource Planner helps solve various use cases like the following:
  • Employee/Patient Rosters. It helps create timetables for nurses and keeps track of patient bed management.
  • Educational Timetables. It helps schedule lessons, courses, exams, and conference presentations.
  • Shop Schedules: It tracks car assembly lines, machine queue planning, and workforce task planning.
  • Cutting Stock: It minimizes waste by reducing the consumption of resources such as paper and steel.
Every organization faces planning problems; that is, they provide products and services with a limited set of constrained resources (employees, assets, time, and money).
Use Case Overview

Figure 1.1.  Use Case Overview

Business Resource Planner is open source software under the Apache Software License 2.0. It is 100% pure Java TM and runs on any Java virtual machine.

1.2. Problem Planning

Various use cases, like the ones above, are NP-complete. That is, they are easy to verify a given solution to a problem in a reasonable amount of time.
There are various implications to solving a problem because the two common techniques of problem solving will not suffice:
  • A brute force algorithm (even a smarter variant) will take too long.
  • A quick algorithm, putting in the largest items first (bin packing example), will return a solution that is usually far from optimal.
By using advanced optimization algorithms, Business Resource Planner finds a good solution in a reasonable amount of time for such planning problems.
Usually, a planning problem has at least 2 levels of constraints:
  • A (negative) hard constraint must not be broken. For example: One teacher can not teach two different lessons at the same time.
  • A (negative) soft constraint should not be broken if it can be avoided. For example: Teacher A does not like to teach on Friday afternoon.
Some problems have positive constraints:
  • A positive soft constraint (or reward) should be fulfilled whenever possible. For example: Teacher B likes to teach on Monday morning.
Some toy problems (such as N Queens) only have hard constraints. Some problems have three or more levels of constraints; for example, they have hard, medium and soft constraints.
These constraints define the score calculation (AKA fitness function) of a planning problem. Each solution of a planning problem can be graded with a score. With Business Resource Planner, score constraints are written in an Object Orientated language, such as Java code or Drools rules. Such code is easy, flexible and scalable.
A planning problem has a number of solutions that are placed in several categories:
  • A possible solution is any solution, whether or not it breaks any number of constraints. Planning problems tend to have an incredibly large number of possible solutions. Many of those solutions are worthless.
  • A feasible solution is a solution that does not break any (negative) hard constraints. The number of feasible solutions tends to be relative to the number of possible solutions. Sometimes there are no feasible solutions. Every feasible solution is a possible solution.
  • An optimal solution is a solution with the highest score. Planning problems tend to have 1 or a few optimal solutions. There is always at least 1 optimal solution, even in the case that there are no feasible solutions and the optimal solution isn't feasible.
  • The best solution found is the solution with the highest score found by an implementation in a given amount of time. The best solution found is likely to be feasible and, given enough time, it's an optimal solution.
Business Resource 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 Business Resource Planner, it is easy to switch the optimization algorithm, by changing the solver configuration in a few lines of XML or code.

Chapter 2. Download Business Resource Planner

2.1. Get Business Resource Planner

Business Resource Planner is production ready. The API is 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. This recipe file is included in every release.

Procedure 2.1. Get the release zip and run the examples

  1. Navigate to the Red Hat Customer Portal and log in with your user credentials.
  2. Select DownloadsRed Hat JBoss MiddlewareDownload Software.
  3. From the Products drop-down menu, select BPM Suite or BRMS Platform.
  4. From the Version drop-down menu, select the product version 6.0.
  5. Select the Standalone or Deployable file and then click download.
  6. Unzip the files.
  7. Open the directory examples and run the following script:
    Linux or Mac:
    $ cd examples
    $ ./runExamples.sh
    Windows:
    $ cd examples
    $ runExamples.bat
Download Business Resource Planner

Figure 2.1. Download Business Resource Planner

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

Note

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

2.2. Run the Examples

Procedure 2.2. To run the examples in your favorite IDE:

  1. Configure your IDE:
    • In IntelliJ and NetBeans, just open the file examples/sources/pom.xml as a new project, the Maven integration will take care of the rest.
    • In Eclipse, open a new project for the directory examples/sources.
    • Add all the jars to the classpath from the directory binaries and the directory examples/binaries, except for the file examples/binaries/optaplanner-examples-*.jar.
    • Add the Java source directory src/main/java and the Java resources directory src/main/resources.
  2. Next, create a run configuration:
    • Main class: org.optaplanner.examples.app.OptaPlannerExamplesApp
    • VM parameters (optional): -Xmx512M -server
    • Working directory: examples (this is the directory that contains the directory data)
  3. Run that run configuration.

Procedure 2.3.  Use Business Resource Planner with Maven, Gradle, Ivy, Buildr or ANT:

  1. Get the Business Resource Planner jars at the central maven repository (and also at the JBoss maven repository).
  2. If you use Maven, add a dependency to optaplanner-core in your project's pom.xml:
     
      <dependency>
          <groupId>org.optaplanner</groupId>
          <artifactId>optaplanner-core</artifactId>
          <version>...</version>
      </dependency>
    
    To identify the latest version, check the central maven repository. This is similar for Gradle, Ivy, and Buildr.
  3. 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 optaplanner-core actually uses. It also contains the jars used by other modules, such as optaplanner-benchmark.
Check the Maven repository pom.xml files to determine the minimal dependency set for a specific version of a specific module.

Procedure 2.4.  Build Business Resource Planner from source:

  1. Set up Git and clone optaplanner from GitHub (or alternatively, download the zipball):
    $ git clone git@github.com:droolsjbpm/optaplanner.git optaplanner
    ...
  2. Do a Maven 3 build:
    $ cd optaplanner
    $ mvn -DskipTests clean install
    ...
  3. Run this command to run any example directly from the command line:
    $ cd optaplanner-examples
    $ mvn exec:exec
    ...

Chapter 3. Business Resource Planner Examples

3.1. Introduction to Examples

Business Resource Planner has several examples available. In this guide, the N Queens example will be the primary one used.
The source code of all these examples is available in the distribution zip under examples/sources and also in git under optaplanner/optaplanner-examples.

Table 3.1. Examples overview

Example Domain Entity size Value size Search space Competition?
N queens
  • 1 entity class
  • 1 variable
<= 256 <= 256 <= 10^616 Pointless
Cloud balancing
  • 1 entity class
  • 1 variable
<= 2400 <= 800 <= 10^6967 No
Traveling salesman
  • 1 entity class
  • 1 chained variable
<= 980 <= 980 <= 10^2927 Unrealistic
Manners 2009
  • 1 entity class
  • 1 variable
<= 144 <= 144 <= 10^310 Unrealistic
Course timetabling
  • 1 entity class
  • 2 variables
<= 434 <= 25 and <= 20 <= 10^1171 Realistic
Machine reassignment
  • 1 entity class
  • 1 variable
<= 50000 <= 5000 <= 10^184948 Realistic
Vehicle routing
  • 1 entity class
  • 1 chained variable
<= 134 <= 144 <= 10^285 Unrealistic
Hospital bed planning
  • 1 entity class
  • 1 variable
<= 2750 <= 471 <= 10^6851 Realistic
Exam timetabling
  • 1 entity class
  • 2 variables
<= 1096 <= 80 and <= 49 <= 10^3374 Realistic
Employee rostering
  • 1 entity class
  • 1 variable
<= 752 <= 50 <= 10^1277 Realistic
Sport scheduling
  • 1 entity class
  • 1 variable
<= 1560 <= 78 <= 10^2951 Unrealistic
A realistic competition is an official, independent competition:
  • that clearly defines a real-word use case
  • with real-world constraints
  • with multiple, real-world datasets
  • that expects reproducible results within a specific time limit on specific hardware
  • that has had serious participation from the academic and/or enterprise Operations Research community
These realistic competitions provide an objective comparison of Business Resource Planner with competitive software and academic research.

3.2. Example Problem Size

Place n queens on a n sized chessboard so no 2 queens can attach each other. The most common n queens puzzle is the 8 queens puzzle, with n = 8:
A chessboard depicting the n queens puzzle.

Figure 3.1. N Queens Chart

Constraints:
  • Use a chessboard of n columns and n rows.
  • Place n queens on the chessboard.
  • No 2 queens can attack each other. A queen can attack any other queen on the same horizontal, vertical or diagonal line.
This documentation heavily uses the 4 queens puzzle as the primary example.
A proposed solution could be:
A proposed solution example for the N queens puzzle

Figure 3.2. Proposed solution for the N queens puzzle

The above solution is wrong because queens A1 and B0 can attack each other (so can queens B0 and D0). Removing queen B0 would respect the "no 2 queens can attack each other" constraint, but would break the "place n queens" constraint.
Below is a correct solution:
A solution for the N queens puzzle.

Figure 3.3. Solution for the N queens puzzle

All the constraints have been met, so the solution is correct. Note that most n queens puzzles have multiple correct solutions. We'll focus on finding a single correct solution for a given n, not on finding the number of possible correct solutions for a given n.
Problem size:
4 has 4 queens with a search space of 256.
8 has 8 queens with a search space of 10^7.
16 has 16 queens with a search space of 10^19.
32 has 32 queens with a search space of 10^48.
64 has 64 queens with a search space of 10^115.
256 has 256 queens with a search space of 10^616.
The Business Resource Planner implementation of the N queens example has not been optimized because it functions as a beginner example. Nevertheless, it can easily handle 64 queens. With a few changes it has been shown to easily handle 5000 queens and more.

3.3. Example Domain Model

Use a good domain model: it will be easier to understand and solve your planning problem. 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
}
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.
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 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.

Table 3.2. A solution for 4 queens shown in the domain model

A solution Queen columnIndex rowIndex ascendingDiagonalIndex (columnIndex + rowIndex) descendingDiagonalIndex (columnIndex - rowIndex)
A proposed solution example for the N queens puzzle
A1 0 1 1 (**) -1
B0 1 0 (*) 1 (**) 1
C2 2 2 4 0
D0 3 0 (*) 3 3
When 2 queens share the same column, row or diagonal line, such as (*) and (**), they can attack each other.

Chapter 4. Cloud Balancing Tutorial

4.1. Problem Statement

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:
  • Every computer must be able to handle the minimum hardware requirements of the sum of its processes:
    • The CPU power of a computer must be at least the sum of the CPU power required by the processes assigned to that computer.
    • The RAM memory of a computer must be at least the sum of the RAM memory required by the processes assigned to that computer.
    • The network bandwidth of a computer must be at least the sum of the network bandwidth required by the processes assigned to that computer.
Soft constraints which should be optimized:
  • Each computer that has one or more processes assigned, incurs a maintenance cost (which is fixed per computer).
    • Minimize the total maintenance cost.
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:
An example Cloud Balancing chart that depicts the computer processes and constraints.

Figure 4.1. Cloud Balance Figure

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.
Business Resource 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 Business Resource Planner for this.
Problem size
cb-0002comp-0006proc has 2 computers and 6 processes with a search space of 64.
cb-0003comp-0009proc has 3 computers and 9 processes with a search space of 10^4.
cb-0004comp-0012proc has 4 computers and 12 processes with a search space of 10^7.
cb-0100comp-0300proc has 100 computers and 300 processes with a search space of 10^600.
cb-0200comp-0600proc has 200 computers and 600 processes with a search space of 10^1380.
cb-0400comp-1200proc has 400 computers and 1200 processes with a search space of 10^3122.
cb-0800comp-2400proc has 800 computers and 2400 processes with a search space of 10^6967.

4.2. Domain Model

Let's start by taking a look at the domain model. It's pretty simple:
  • Computer: represents a computer with certain hardware (CPU power, RAM memory, network bandwidth) and maintenance cost.
  • Process: represents a process with a demand. Needs to be assigned to a Computer by Business Resource Planner.
  • CloudBalance: represents a problem. Contains every Computer and Process for a certain data set.
This is a Cloud balance class diagram illustrating Planner concepts.

Figure 4.2. Cloud balance class diagram

In the UML class diagram above, the Business Resource Planner concepts are already annotated:
  • Planning entity: the class (or classes) that changes during planning. In this example that's the class Process.
  • Planning variable: the property (or properties) of a planning entity class that changes during planning. In this examples, that's the property computer on the class Process.
  • Solution: the class that represents a data set and contains all planning entities. In this example that's the class CloudBalance.

4.3. Main Method

Try it yourself. Run org.optaplanner.examples.cloudbalancing.app.CloudBalancingHelloWorld. By default, it is configured to run for 120 seconds. It will execute this code:

Example 4.1. CloudBalancingHelloWorld.java

public class CloudBalancingHelloWorld {

    public static void main(String[] args) {
        // Build the Solver
        SolverFactory solverFactory = new XmlSolverFactory(
                "/org/optaplanner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
        Solver solver = solverFactory.buildSolver();

        // Load a problem with 400 computers and 1200 processes
        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 code above does this:
  • Build the Solver based on a solver configuration (in this case an XML file).
     
      SolverFactory solverFactory = new XmlSolverFactory(
                    "/org/optaplanner/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.

Chapter 5. Business Resource Planner Configuration

5.1. 5 Steps of Solving a Planning Problem

Solving a planning problem with Business Resource Planner consists out of 5 steps:
  1. Model your planning problem as a class that implements the interface Solution, for example the class NQueens.
  2. Configure a Solver, for example a first fit and tabu search solver for any NQueens instance.
  3. Load a problem data set from your data layer, for example a 4 Queens instance. Set it as the planning problem on the Solver with Solver.setPlanningProblem(...).
  4. Solve it with Solver.solve().
  5. Get the best solution found by the Solver with Solver.getBestSolution().
Input/Output overview for Solver configuration.

Figure 5.1. Input/Output overview

5.2. Model your Planning Problem

Is this class a problem fact or planning entity?
Look at a dataset of your planning problem. You 'll recognize domain classes in there, each of which is one of these:
  • A unrelated class: not used by any of the score constraints. From a planning standpoint, this data is obsolete.
  • A problem fact class: used by the score constraints, but does NOT change during planning (as long as the problem stays the same). For example: Bed, Room, Shift, Employee, Topic, Period, ...
  • A planning entity class: used by the score constraints and changes during planning. For example: BedDesignation, ShiftAssignment, Exam, ...
Ask yourself: What class changes during planning? Which class has variables that I want the Solver to change for me? That class is a planning entity. Most use cases have only 1 planning entity class.

Note

In , problem facts can change during planning, because the problem itself changes. However, that doesn't make them planning entities.
A good model can greatly improve the success of your planning implementation. For inspiration, take a look at how the examples modeled their domain:
An example chart demonstrating entities, variables and values for use cases.

Figure 5.2. Entity, variable and value

When in doubt, it's usually the many side of a many to one relationship that is the planning entity. For example in employee rostering, the planning entity class is ShiftAssignment, not Employee. Vehicle routing is special, because it uses a .
In Business Resource Planner all problems facts and planning entities are plain old JavaBeans (POJO's). You can load them from a database (JDBC/JPA/JDO), an XML file, a data repository, a noSQL cloud, ...

5.3. Problem Fact

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.

Note

Generally, better designed domain classes lead to simpler and more efficient score constraints. Therefore, when dealing with a messy legacy system, it can sometimes be worth it to convert the messy domain set into a planner specific POJO set first. For example: if your domain model has 2 Teacher instances for the same teacher that teaches at 2 different departments, it's hard to write a correct score constraint that constrains a teacher's spare time.
Alternatively, you can sometimes also introduce to enrich the domain model for planning only.

5.4. Planning Entities and Variables

5.4.1. Planning Entity

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.optaplanner.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.

Note

Do not create unnecessary planning entity classes. This leads to difficult Move implementations and slower score calculation.
For example, do not create a planning entity class to hold the total free time of a teacher, which needs to be kept up to date as the Lecture planning entities change. Instead, calculate the free time in the score constraints and put the result per teacher into a logically inserted score object.
If historic data needs to be considered too, then create problem fact to hold the historic data up to, but not including, the planning window (so it doesn't change when a planning entity changes) and let the score constraints take it into account.

5.4.2. Entity Difficulty

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 {
    // ...
}
See the Sorted Selection topic for more information.

Important

Difficulty should be implemented ascending: easy entities are lower, difficult entities are higher. For example in bin packing: small item < medium item < big item.
Even though some algorithms start with the more difficult entities first, they just reverse the ordering.
None of the current planning variable state should be used to compare planning entity difficult. During construction heuristics, those variables are likely to be null anyway. For example, a Queen's row variable should not be used.

5.4.3. Planning Variable

A planning variable is a property (including getter and setter) on a planning entity. It points to a planning value, which changes during planning. For example, a Queen's row property is a planning variable. Note that even though a Queen's row property changes to another Row during planning, no Row instance itself is changed.
A planning variable getter needs annotation with the @PlanningVariable. Accordingly, it needs a non-empty valueRangeProviderRefs property.
@PlanningEntity
public class Queen {

    private Row row;

    // ...

    @PlanningVariable(valueRangeProviderRefs = {"rowRange"})
    public Row getRow() {
        return row;
    }

    public void setRow(Row row) {
        this.row = row;
    }

}

5.4.4. Nullable Variable

By default, an initialized planning variable cannot be null, so an initialized solution will never use null for any of its planning variables. In over-constrained use case, this can be contra productive. For example: in task assignment with too many tasks for the workforce, we would rather leave low priority tasks unassigned instead of assigning them to an overloaded worker.
To allow an initialized planning variable to be null, set nullable to true:
 @PlanningVariable(nullable = true)
    @ValueRange(...)
    public Worker getWorker() {
        return worker;
    }

Important

Planner will automatically add the value null to the value range. There is no need to add null in a collection used by a ValueRange.

Note

Using a nullable planning variable implies that your score calculation is responsible for punishing (or even rewarding) variables with a null value.
does not mix well with a nullable planning variable: every time the Solver starts or a problem fact change is made, the construction heuristics will try to initialize all the null variables again, which can be a huge waste of time. One way to deal with this, is to change when a planning entity should be reinitialized with an reinitializeVariableEntityFilter:
 @PlanningVariable(nullable = true, reinitializeVariableEntityFilter = ReinitializeTaskFilter.class)
    @ValueRange(...)
    public Worker getWorker() {
        return worker;
    }

5.4.5. Initialized Entity

A planning variable is considered initialized if its value is not null or if the variable is nullable. So a nullable variable is always considered initialized, even when a custom reinitializeVariableEntityFilter triggers a reinitialization.
A planning entity is initialized if all of its planning variables are initialized.
A Solution is initialized if all of its planning entities are initialized.

5.5. Planning Value and Value Ranges

5.5.1. Planning Value and Value Ranges

A planning value is a possible value for a planning variable. Usually, a planning value is a problem fact, but it can also be any object, for example a double. It can even be another planning entity or even a interface implemented by a planning entity and a problem fact.
A planning value range is the set of possible planning values for a planning variable. This set can be a discrete (for example row 1, 2, 3 or 4) or continuous (for example any double between 0.0 and 1.0). Continuous planning variables are currently undersupported and require the use of custom moves.
There are several ways to define the value range of a planning variable with the @ValueRange annotation.
ValueRange from Solution property
All instances of the same planning entity class share the same set of possible planning values for that planning variable. This is the most common way to configure a value range.
The Solution implementation has property which returns a Collection. Any value from that Collection is a possible planning value for this planning variable.
 @PlanningVariable
    @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "rowList")
    public Row getRow() {
        return row;
    }
public class NQueens implements Solution<SimpleScore> {

    // ...

    public List<Row> getRowList() {
        return rowList;
    }

}
That Collection must not contain the value null, even for a .
ValueRange from planning entity
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).

Note

By limiting the value range specifically of 1 planning entity, you are effectively making a build-in hard constraint. This can be a very good thing, as the number of possible solutions is severely lowered. But this can also be a bad thing because it takes away the freedom of the optimization algorithms to temporarily break that constraint in order to escape a local optima.
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.
This value range is not compatible with a variable.
ValueRange undefined
Leaves the value range undefined. Most optimization algorithms do not support this value range.
 @PlanningVariable
    @ValueRange(type = ValueRangeType.UNDEFINED)
    public Row getRow() {
        return row;
    }
Combining ValueRanges
Value ranges can be combined, for example:
 @PlanningVariable(...)
    @ValueRanges({
            @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "companyCarList"),
            @ValueRange(type = ValueRangeType.FROM_PLANNING_ENTITY_PROPERTY, planningEntityProperty = "personalCarList"})
    public Car getCar() {
        return car;
    }
A ValueRange which includes other planning entities
In some cases (such as in chaining), the planning value itself is sometimes another planning entity. In such cases, it's often required that a planning entity is only eligible as a planning value if it's initialized:
 @PlanningVariable
    @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "copList", excludeUninitializedPlanningEntity = true)
    public Cop getPartner() {
        return partner;
    }
TODO: this is likely to change in the future , as it should support specific planning variable initialization too.

5.5.2. Chained Planning Variables

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:
  • Directly points to a planning fact, which is called an anchor.
  • Points to another planning entity with the same planning variable, which recursively points to an anchor.
Here are some example of valid and invalid chains:
Planner Chain principles with valid and invalid chains.

Figure 5.3. Chain Principles Chart

Every initialized planning entity is part of an open-ended chain that begins from an anchor. A valid model means that:
  • A chain is never a loop. The tail is always open.
  • Every chain always has exactly 1 anchor. The anchor is a problem fact, never a planning entity.
  • A chain is never a tree, it is always a line. Every anchor or planning entity has at most 1 trailing planning entity.
  • Every initialized planning entity is part of a chain.
  • An anchor with no planning entities pointing to it, is also considered a chain.

Warning

A planning problem instance given to the Solver must be valid.

Note

If your constraints dictate a closed chain, model it as an open-ended chain (which is easier to persist in a database) and implement a score constraint for the last entity back to the anchor.
The optimization algorithms and build-in MoveFactory's do chain correction to guarantee that the model stays valid:
Planner Chain corrections chart depicting planning variables.

Figure 5.4. Chain corrections figure for changing planning variables.

Warning

A custom Move implementation must leave the model in a valid state.
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:
  • The value range which holds the anchors, for example domicileList.
  • The value range which holds the initialized planning entities, for example visitList. This always requires an enabled excludeUninitializedPlanningEntity, because an initialized entity should never point to an uninitialized entity: that would break the principle that every chain must have an anchor.

5.5.3. Planning Value Strength

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

}

Note

If you have multiple planning value classes in the same value range, the strengthComparatorClass needs to implement a Comparator of a common superclass (for example Comparator<Object>) and be able to handle comparing instances of those different classes.
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() {
        // ...
    }
See for more information.

Important

Strength should be implemented ascending: weaker values are lower, stronger values are higher. For example in bin packing: small container < medium container < big container.
None of the current planning variable state in any of the planning entities should be used to compare planning values. During construction heuristics, those variables 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.

5.6. Planning Problem and Planning Solution

5.6.1. Planning Problem Instance

A dataset for a planning problem needs to be wrapped in a class for the Solver to solve. You must implement this class. For example in n queens, this in the NQueens class which contains a Column list, a Row list and a Queen list.
A planning problem is actually a unsolved planning solution or - stated differently - an uninitialized Solution. Therefor, that wrapping class must implement the Solution interface. For example in n queens, that NQueens class implements Solution, yet every Queen in a fresh NQueens class is not yet assigned to a Row (their row property is null). So it's not a feasible solution. It's not even a possible solution. It's an uninitialized solution.

5.6.2. Solution Interface

You need to present the problem as a Solution instance to the Solver. So you need to have a class that implements the Solution interface:
public interface Solution<S extends Score> {

    S getScore();
    void setScore(S score);

    Collection<? extends Object> getProblemFacts();

}
For example, an NQueens instance holds a list of all columns, all rows and all Queen instances:
public class NQueens implements Solution<SimpleScore> {

    private int n;

    // Problem facts
    private List<Column> columnList;
    private List<Row> rowList;

    // Planning entities
    private List<Queen> queenList;

    // ...
}

5.6.3. getScore and setScore

A Solution requires a score property. The score property is null if the Solution is uninitialized or if the score has not yet been (re)calculated. The score property is usually typed to the specific Score implementation you use. For example, NQueens uses a SimpleScore:
public class NQueens implements Solution<SimpleScore> {

    private SimpleScore score;

    public SimpleScore getScore() {
        return score;
    }

    public void setScore(SimpleScore score) {
        this.score = score;
    }

    // ...
}
Most use cases use a HardSoftScore instead:
public class CourseSchedule implements Solution<HardSoftScore> {

    private HardSoftScore score;

    public HardSoftScore getScore() {
        return score;
    }

    public void setScore(HardSoftScore score) {
        this.score = score;
    }

    // ...
}
See the Score calculation section for more information on the Score implementations.

5.6.4. getProblemFacts

The method is only used if Drools is used for score calculation. Other score directors do not use it.
All objects returned by the getProblemFacts() method will be asserted into the Drools working memory, so the score rules can access them. For example, NQueens just returns all Column and Row instances.
 public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(columnList);
        facts.addAll(rowList);
        // Do not add the planning entity's (queenList) because that will be done automatically
        return facts;
    }
All planning entities are automatically inserted into the Drools working memory. Do not add them in the method getProblemFacts().
The method getProblemFacts() is not called much: at most only once per solver phase per solver thread.

5.6.5. Cached Problem Facts

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.

5.6.6. Cloning a Solution

Most (if not all) optimization algorithms 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.

Note

There are many ways to clone, such as a shallow clone, deep clone, ... This context focuses on a planning clone.
A planning clone of a Solution must fulfill these requirements:
  • The clone must represent the same planning problem. Usually it reuses the same instances of the problem facts and problem fact collections as the original.
  • The clone must use different, cloned instances of the entities and entity collections. Changes to an original Solution's entity's variables must not effect its clone.
Planner Solution Cloning Chart depicting CloudBalancing processes.

Figure 5.5. Solution Cloning

Implementing a planning clone method is hard, therefore you do not need to implement it.
FieldAccessingSolutionCloner
This SolutionCloner is used by default. It works for the majority of use cases.

Warning

When the FieldAccessingSolutionCloner clones your entity collection, it might not recognize the implementation and replace it with ArrayList, LinkedHashSet or TreeSet (whichever is more applicable). It recognizes most of the common JDK Collection implementations.
Custom cloning: Make Solution implement PlanningCloneable
If your Solution implements PlanningCloneable, Business Resource Planner will automatically choose to clone it by calling the method planningClone().
public interface PlanningCloneable<T> {

    T planningClone();

}
For example: If NQueens implements PlanningCloneable, it would only deep clone all Queen instances. When the original solution is changed during planning, by changing a Queen, the clone stays the same.
public class NQueens implements Solution<...>, PlanningCloneable<NQueens> {
    ...

    /**
     * Clone will only deep copy the {@link #queenList}.
     */
    public NQueens planningClone() {
        NQueens clone = new NQueens();
        clone.id = id;
        clone.n = n;
        clone.columnList = columnList;
        clone.rowList = rowList;
        List<Queen> clonedQueenList = new ArrayList<Queen>(queenList.size());
        for (Queen queen : queenList) {
            clonedQueenList.add(queen.planningClone());
        }
        clone.queenList = clonedQueenList;
        clone.score = score;
        return clone;
    }
}
The planningClone() 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. If you were to clone the problem facts too, then you'd have to make sure that the new planning entity clones also refer to the new problem facts clones used by the solution. For example, if you would clone all Row instances, then each Queen clone and the NQueens clone itself should refer to those new Row clones.

Warning

Cloning an entity with a variable is devious: a variable of an entity A might point to another entity B. If A is cloned, then it's variable must point to the clone of B, not the original B.

5.6.7. Building an Uninitialized Solution

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);
        nQueens.setColumnList(createColumnList(nQueens));
        nQueens.setRowList(createRowList(nQueens));
        nQueens.setQueenList(createQueenList(nQueens));
        return nQueens;
    }

    private List<Queen> createQueenList(NQueens nQueens) {
        int n = nQueens.getN();
        List<Queen> queenList = new ArrayList<Queen>(n);
        long id = 0;
        for (Column column : nQueens.getColumnList()) {
            Queen queen = new Queen();
            queen.setId(id);
            id++;
            queen.setColumn(column);
            // Notice that we leave the PlanningVariable properties on null
            queenList.add(queen);
        }
        return queenList;
    }
Uninitialized solution for the 4 queens puzzle

Figure 5.6. Uninitialized solution for the 4 queens puzzle

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

Chapter 6. Solver Configuration

6.1. Three Parts of Solver

Take a look at the solver configuration:

Example 6.1. cloudBalancingSolverConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<solver>
  <!--<environmentMode>FAST_ASSERT</environmentMode>-->

  <!-- Domain model configuration -->
  <solutionClass>org.optaplanner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
  <planningEntityClass>org.optaplanner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>

  <!-- Score configuration -->
  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
    <!--<scoreDrl>/org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
  </scoreDirectorFactory>
  
  <!-- Optimization algorithms configuration -->
  <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>
</solver>
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.optaplanner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
    <planningEntityClass>org.optaplanner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>
  • Score configuration: How should Planner optimize the planning variables? Since we have hard and soft constraints, we use a HardSoftScore. 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_SOFT</scoreDefinitionType>
       <simpleScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
      <!--<scoreDrl>/org/optaplanner/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.

6.2. Solver Configuration by XML File

You can build a Solver instance with the XmlSolverFactory. Configure it with a solver configuration XML file:
XmlSolverFactory solverFactory = new XmlSolverFactory(
	"/org/optaplanner/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.optaplanner.examples.nqueens.domain.NQueens;/solutionClass>
  <planningEntityClass>org.optaplanner.examples.nqueens.domain.Queen</planningEntityClass>

  <!-- Define the score function -->
  <scoreDirectorFactory>
    <scoreDefinitionType>SIMPLE</scoreDefinitionType>
    <scoreDrl>/org/optaplanner/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:
  • Define the model
  • Define the score function
  • Configure the optimization algorithm(s)
We'll explain these various parts of a configuration later in this manual.
Business Resource 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.

6.3. Solver Configuration by Java API

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);
        solverConfig.setPlanningEntityClassSet(Collections.<Class<?>>singleton(Queen.class));

        ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig();
        scoreDirectorFactoryConfig.setScoreDefinitionType(ScoreDirectorFactoryConfig.ScoreDefinitionType.SIMPLE);
        scoreDirectorFactoryConfig.setScoreDrlList(
                Arrays.asList("/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl"));
        solverConfig.setScoreDirectorFactoryConfig(scoreDirectorFactoryConfig);

        TerminationConfig terminationConfig = new TerminationConfig();
        // ...
        solverConfig.setTerminationConfig(terminationConfig);
        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/optaplanner/examples/nqueens/solver/nqueensSolverConfig.xml");

        SolverConfig solverConfig = solverFactory.getSolverConfig();
        solverConfig.getTerminationConfig().setMaximumMinutesSpend(userInput);
        Solver solver = solverConfig.buildSolver();

6.4. The Solver

6.4.1. The Solver Interface

A Solver implementation will solve your planning problem.
public interface Solver {

    void setPlanningProblem(Solution planningProblem);
    
    void solve();

    Solution getBestSolution();

    // ...

}
A Solver can only solve 1 planning problem instance at a time. A Solver should only be accessed from a single thread, except for the methods that are specifically javadocced as being thread-safe. It's build with a SolverFactory, do not implement or build it yourself.

6.4.2. Solving a Problem

Solving a problem is quite easy once you have:
  • A Solver build from a solver configuration
  • A Solution that represents the planning problem instance
Just set the planning problem, solve it and extract the best solution:
 solver.setPlanningProblem(planningProblem);
    solver.solve();
    Solution bestSolution = solver.getBestSolution();
For example in n queens, the method getBestSolution() will return an NQueens instance with every Queen assigned to a Row.
Best solution for the 4 queens puzzle in 8 ms (also an optimal solution)

Figure 6.1. Best solution for the 4 queens puzzle in 8 ms (also an optimal solution)

The solve() method can take a long time (depending on the problem size and the solver configuration). The Solver will remember (actually clone) the best solution it encounters during its solving. Depending on a number factors (including problem size, how much time the Solver has, the solver configuration, ...), that best solution will be a feasible or even an optimal solution.

Note

The Solution instance given to the method setPlanningProblem() will be changed by the Solver, but it do not mistake it for the best solution.
The Solution instance returned by the method getBestSolution() will most likely be a clone of the instance given to the method setPlanningProblem(), which means it's a different instance.

Note

The Solution instance given to the method setPlanningProblem() does not need to be uninitialized. It can be partially or fully initialized, which is likely to be the case in .

6.4.3. Environment Mode

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>FAST_ASSERT</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:
FULL_ASSERT
The FULL_ASSERT mode is reproducible (see the reproducible mode) and also turns on all assertions (such as assert that the incremental score calculation is uncorrupted) to fail-fast on rule engine bugs.
The FULL_ASSERT mode is very slow (because it doesn't rely on delta based score calculation).
FAST_ASSERT
The FAST_ASSERT mode is reproducible (see the reproducible mode) and also turns on most assertions (such as assert that an undo Move's score is the same as before the Move) to fail-fast on a bug in your Move implementation, your score rule, ...
The FAST_ASSERT mode is slow.
It's recommended to write a test case which does a short run of your planning problem with the FAST_ASSERT mode on.
REPRODUCIBLE (default)
The reproducible mode is the default mode because it is recommended during development. In this mode, 2 runs in the same Planner version will execute the same code in the same order. Those 2 runs will have the same result, except if the note below applies . This allows you to consistently reproduce bugs. It also allows you to benchmark certain refactorings (such as a score constraint optimization) fairly across runs.

Note

Despite the reproducible mode, your application might still not be fully reproducible because of:
  • Use of HashSet (or another Collection which has an inconsistent order between JVM runs) for collections of planning entities or planning values (but not normal problem facts), especially in the Solution implementation. Replace it with LinkedHashSet.
  • Combining a time gradient dependent algorithms (most notably simulated annealing) together with time spend termination. A sufficiently large difference in allocated CPU time will influence the time gradient values. Replace simulated annealing with late acceptance. Or instead, replace time spend termination with step count termination.
The reproducible mode is not much slower than the production mode. If your production environment requires reproducibility, use it in production too.
In practice, this mode uses the default random seed, and it also disables certain concurrency optimizations (such as work stealing).
PRODUCTION
The production mode is the fastest and the most robust, but not reproducible. It is recommended for a production environment.
The random seed is different on every run, which makes it more robust against an unlucky random seed. An unlucky random seed gives a bad result on a certain data set with a certain solver configuration. Note that in most use cases the impact of the random seed is relatively low on the result (even with simulated annealing). An occasional bad result is far more likely to be caused by another issue (such as a ).

6.4.4. Logging Level

The best way to illuminate the black box that is a Solver, is to play with the logging level:
  • error: Log errors, except those that are thrown to the calling code as a RuntimeException.

    Note

    If an error happens, Business Resource Planner normally fails fast: it throws a subclass of RuntimeException with a detailed message to the calling code. It does not log it as an error itself to avoid duplicate log messages. Except if the calling code explicitly catches and eats that RuntimeException, a Thread's default ExceptionHandler will log it as an error anyway. Meanwhile, the code is disrupted from doing further harm or obfuscating the error.
  • warn: Log suspicious circumstances.
  • info: Log every phase and the solver itself. See .
  • debug: Log every step of every phase. See .
  • trace: Log every move of every step of every phase. See .

    Note

    Turning on trace logging, will slow down performance considerably: it's often 4 times slower. However, it's invaluable during development to discover a bottleneck.
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 (0) constructionHeuristic ended: 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 (1) 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 , which is a simple logging facade which delegates every log message 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, use Logback by adding this Maven dependency (there is no need to add an extra bridge dependency):
 <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.x</version>
    </dependency>
Configure the logging level on the package org.optaplanner in your logback.xml file:
<configuration>

  <logger name="org.optaplanner" level="debug"/>

  ...

<configuration>
If instead, you're still using Log4J (and you don't want to switch to its faster successor, Logback), add the bridge dependency:
 <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.x</version>
    </dependency>
And configure the logging level on the package org.optaplanner in your log4j.xml file:
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

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

  ...

</log4j:configuration>

Chapter 7. Score Configuration

7.1. Score Configuration

Business Resource Planner will search for the Solution with the highest Score. We're using a HardSoftScore, 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).
A Cloud Balance Score calculation chart displaying Processes for CPUs.

Figure 7.1. Cloud process calculation graph.

Of course, Planner needs to be told about these domain-specific score constraints. There are several ways to implement such a score function:
  • Simple Java
  • Incremental Java
  • Drools
Let's take a look look at 2 different implementations:

7.2. Java Configuration

One way to define a score function is to implement the interface SimpleScoreCalculator in plain Java.
 <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
  </scoreDirectorFactory>
Just implement the method calculateScore(Solution) to return a HardSoftScore instance.

Example 7.1. 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 HardSoftScore 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 HardSoftScore.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.

7.3. Drools Configuration

To use the Drools rule engine as a score function, simply add a scoreDrl resource in the classpath:
 <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>/org/optaplanner/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 7.2. cloudBalancingScoreRules.drl - hard constraints

...

import org.optaplanner.examples.cloudbalancing.domain.CloudBalance;
import org.optaplanner.examples.cloudbalancing.domain.CloudComputer;
import org.optaplanner.examples.cloudbalancing.domain.CloudProcess;

global HardSoftScoreHolder 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.HARD,
                $cpuPower - $requiredCpuPowerTotal.intValue(),
                $computer));
end

rule "requiredMemoryTotal"
    ...
end

rule "requiredNetworkBandwidthTotal"
    ...
end

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

// Accumulate hard constraints
rule "accumulateHardScore"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardScore($hardTotal.intValue());
end
Next, if those constraints are met, we want to minimize the maintenance cost, so we add that as a soft constraint:

Example 7.3. cloudBalancingScoreRules.drl - soft constraints

// ############################################################################
// Soft constraints
// ############################################################################

rule "computerCost"
    when
        $computer : CloudComputer($cost : cost)
        exists CloudProcess(computer == $computer)
    then
        insertLogical(new IntConstraintOccurrence("computerCost", ConstraintType.SOFT,
                - $cost,
                $computer));
end

// ############################################################################
// Calculate soft score
// ############################################################################

// Accumulate soft constraints
rule "accumulateSoftScore"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $softTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.SOFT, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setSoftScore($softTotal.intValue());
end
If you use the Drools rule engine for score calculation, you can integrate with other Drools technologies, such as decision tables (XLS or web based), the Guvnor rule repository, ...

7.4. Score Calculation

7.4.1. Score Terminology

Every initialized Solution has a score. That score is an objective way to compare 2 solutions: the solution with the higher score is better. The Solver aims to find the Solution with the highest Score of all possible solutions. The best solution is the Solution with the highest Score that Solver has encountered during solving, which might be the optimal solution.
Business Resource Planner cannot automatically know which Solution is best for your business, so you need to tell it how to calculate the score of a given Solution according to your business needs. There are multiple score techniques that you can use and combine:
  • Maximize or minimize a constraint: score constraint signum (positive or negative)
  • Put a cost/profit on constraints: score constraint weight
  • Prioritize constraints: score level
  • Pareto scoring

7.4.2. Positive and Negative Constraints

All score techniques are based on constraints. Such a constraint can be a simple pattern (such as Maximize the apple harvest in the solution) or a more complex pattern. A positive constraint is a constraint you're trying to maximize. A negative constraint is a constraint you're trying to minimize.
Negative and Positive Score constraint for optimal solutions.

Figure 7.2. Score Constraints

Notice in the image above, that the optimal solution always has the highest score, regardless if the constraints are positive or negative.
Most planning problems have only negative constraints and therefore have a negative score. In that case, the score is usually the sum of the weight of the negative constraints being broken, with a perfect score of 0. This explains why the score of a solution of 4 queens is the negative (and not the positive!) of the number of queen couples which can attack each other.
Negative and positive constraints can be combined, even in the same score level.

Note

Don't presume your business knows all its score constraints in advance. Expect score constraints to be added or changed after the first releases.
When a constraint activates (because the negative constraint is broken or the positive constraint is fulfilled) on a certain planning entity set, it is called a constraint occurrence.

7.4.3. Constraint Weighting

Not all score constraints are equally important. If breaking one constraint is equally bad as breaking another constraint x times, then those 2 constraints have a different weight (but they are in the same score level). For example in vehicle routing, you can make 2 "unhappy driver" constraint occurrences count as much as 1 "fuel tank usage" constraint occurrence:
Score weighting image depicting driver unhappiness and fuel usage.

Figure 7.3. Fuel Usage Score Weighting Image.

Score weighting is often used in use cases where you can put a price tag on everything. In that case, the positive constraints maximize revenue and the negative constraints minimize expenses: together they maximize profit. Alternatively, score weighting is also often used to create social fairness. For example: nurses that request a free day on New Year's eve pay a higher weight than on a normal day.
The weight of a constraint occurrence can be dynamically based on the planning entities involved. For example in cloud balance: the weight of the soft constraint occurrence for an active Computer is the cost of that Computer.

7.4.4. Score Level

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 all 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.
Planner fuel usage image demonstrating score levels

Figure 7.4. Fuel Usage Score Level Image

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.
Don't use a big constraint weight when your business actually wants different score levels. That hack, known as score folding, is broken:
Planner image illustrating broken score folding.

Figure 7.5. Broken Score Folding

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 . 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.

7.4.5. Multi-Objective Optimization Scoring

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.
Planner Pareto optimization scoring for maximum harvest stats.

Figure 7.6. Pareto Optimization Scoring

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, . 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.
All the score techniques mentioned above can be combined seamlessly:
Planner petrol score composition chart.

Figure 7.7. Score Composition Chart.

7.4.6. Score Interface

A score is represented by the Score interface, which naturally extends Comparable:
public interface Score<...> extends Comparable<...> {
    ...
}
The Score implementation to use depends on your use case. Your score might not efficiently fit in a single double value. Planner has several build-in Score implementations, but you can implement a custom Score too. Most use cases will just use the build-in HardSoftScore.
Planner diagram for built-in score implementations.

Figure 7.8. Score class diagram

The Score implementation (for example HardSoftScore) must be the same throughout a Solver runtime. The Score implementation is configured in the solver configuration as a ScoreDefinition:
 <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    ...
  </scoreDirectorFactory>
As for all calculations with a computer, choose the correct type. Don't use double for financial data.
Planner chart for precision of Score weight types.

Figure 7.9. Score weight type for precision.

Based on your score constraints and score level requirements, you'll choose a certain ScoreDefinition:

7.5. Score Definition

7.5.1. SimpleScore

A SimpleScore has a single int value, for example -123. It has a single score level.
 <scoreDirectorFactory>
    <scoreDefinitionType>SIMPLE</scoreDefinitionType>
    ...
  </scoreDirectorFactory>
Variants of this scoreDefinitionType:
  • SIMPLE_LONG: Uses SimpleLongScore which has a long value instead of an int value.
  • SIMPLE_DOUBLE: Uses SimpleDoubleScore which has a double value instead of an int value.
  • SIMPLE_BIG_DECIMAL: Uses SimpleBigDecimalScore which has a BigDecimal value instead of an int value.

7.5.2. HardAndSoftScore

A HardSoftScore has a hard int value and a soft int value, for example -123hard/-456soft. It has 2 score levels (hard and soft).
 <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    ...
  </scoreDirectorFactory>
Variants of this scoreDefinitionType:
  • HARD_SOFT_LONG: Uses HardSoftLongScore which has long values instead of int values.
  • HARD_SOFT_DOUBLE: Uses HardSoftDoubleScore which has double values instead of int values.
  • HARD_SOFT_BIG_DECIMAL: Uses HardSoftBigDecimalScore which has BigDecimal values instead of int values..
HardMediumSoftScore
A HardMediumSoftScore which has a hard int value, a medium int value and a soft int value, for example -123hard/-456medium/-789soft. It has 3 score levels (hard, medium and soft).
 <scoreDirectorFactory>
    <scoreDefinitionType>HARD_MEDIUM_SOFT</scoreDefinitionType>
    ...
  </scoreDirectorFactory>
BendableScore
A BendableScore has a configurable number of score levels. It has an array of hard int values and an array of soft int value, for example 2 hard levels and 3 soft levels for a score -123/-456/-789/-012/-345. The number of hard and soft score levels needs to be set at configuration time, it's not flexible to change during solving.
 <scoreDirectorFactory>
    <scoreDefinitionType>BENDABLE</scoreDefinitionType>
    <bendableHardLevelCount>2</bendableHardLevelCount>
    <bendableSoftLevelCount>3</bendableSoftLevelCount>
    ...
  </scoreDirectorFactory>

7.5.3. Custom Score

The ScoreDefinition interface defines the score representation.
To implement a custom Score, you'll also need to implement a custom ScoreDefinition. Extend AbstractScoreDefinition (preferable by copy pasting HardSoftScoreDefinition or SimpleScoreDefinition) and start from there.
Then hook your custom ScoreDefinition in your SolverConfig.xml:
 <scoreDirectorFactory>
    <scoreDefinitionClass>...MyScoreDefinition</scoreDefinitionClass>
    ...
  </scoreDirectorFactory>

7.6. Calculate the Score

7.6.1. Score Calculation Types

There are several ways to calculate the Score of a Solution:
  • Simple Java score calculation: implement a single Java method
  • Incremental Java score calculation: implement multiple Java methods
  • Drools score calculation: implement score rules
Every score calculation type can use any Score definition. For example, simple Java score calculation can output a HardSoftScore.
All score calculation types are Object Orientated and can reuse existing Java code.

7.6.2. Simple Java Calculation

A simple way to implement your score calculation in Java.
  • Advantages:
    • Plain old Java: no learning curve
    • Opportunity to delegate score calculation to an existing code base or legacy system
  • Disadvantages:
    • Slower and less scalable
      • Because there is no
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 SimpleScore.valueOf(score);
    }

}
Configure it in your solver configuration:
 <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.optaplanner.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);

7.6.3. Incremental Java Calculation

A way to implement your score calculation incrementally in Java.
  • Advantages:
    • Very fast and scalable
      • Currently the fastest if implemented correctly
  • Disadvantages:
    • Hard to write
      • A scalable implementation heavily uses maps, indexes, ... (things the Drools rule engine can do for you)
      • You have to learn, design, write and improve all these performance optimizations yourself
    • Hard to read
      • Regular score constraint changes can lead to a high maintenance cost
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>>(n * 2);
        descendingDiagonalIndexMap = new HashMap<Integer, List<Queen>>(n * 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 (i != 0) {
                ascendingDiagonalIndexMap.put(n - 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 SimpleScore.valueOf(score);
    }

}
Configure it in your solver configuration:
 <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <incrementalScoreCalculatorClass>org.optaplanner.examples.nqueens.solver.score.NQueensAdvancedIncrementalScoreCalculator</incrementalScoreCalculatorClass>
  </scoreDirectorFactory>
Optionally, to get better output when the IncrementalScoreCalculator is corrupted in environmentMode FAST_ASSERT or FULL_ASSERT, you can overwrite the method buildScoreCorruptionAnalysis from AbstractIncrementalScoreCalculator.

7.6.4. Drools Calculation

7.6.4.1. Drools Calculation

Implement your score calculation using the Drools rule engine. Every score constraint is written as one or more score rules.
  • Advantages:
    • Incremental score calculation for free
      • Because most DRL syntax uses forward chaining, it does incremental calculation without any extra code
    • Score constraints are isolated as separate rules
      • Easy to add or edit existing score rules
    • Flexibility to augment your score constraints by
      • Defining them in decision tables
        • Excel (XLS) spreadsheet
        • Guvnor WebUI
      • Translate them into natural language with DSL
      • Store and release in the Guvnor repository
    • Performance optimizations in future versions for free
      • In every release, the Drools rule engine tends to become faster.
  • Disadvantages:
    • DRL learning curve
    • Usage of DRL
      • Polyglot fear can prohibit the use of a new language such as DRL in some organizations

7.6.4.2. Drools Score Rules Configuration

There are several ways to define where your score rules live.
A scoreDrl resource on the classpath
This is the easy way: the score rule live in a DRL file which is a resource on the classpath. Just add your score rules *.drl file in the solver configuration:
 <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <scoreDrl>/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
You can add multiple <scoreDrl> entries if needed, but normally you'll define all your score rules in 1 file.
A RuleBase (possibly defined by Guvnor)
If you prefer to build the RuleBase yourself or if you're combining Planner with Guvnor, you can set the RuleBase on the SolverFactory before building the Solver:
 solverFactory.getSolverConfig().getScoreDirectorFactoryConfig.setRuleBase(ruleBase);
Implementing a score rule
Here's an example of a score constraint implemented as a score rule in a DRL file:
rule "multipleQueensHorizontal"
    when
        $q1 : Queen($id : id, $y : y);
        $q2 : Queen(id > $id, y == $y);
    then
        insertLogical(new UnweightedConstraintOccurrence("multipleQueensHorizontal", $q1, $q2));
end
This score rule will fire once for every 2 queens with the same y. The (id > $id) condition is needed to assure that for 2 queens A and B, it can only fire for (A, B) and not for (B, A), (A, A) or (B, B). Let's take a closer look at this score rule on this solution of 4 queens:
Planner 6.0 unsolved N queens example.

Figure 7.10. Unsolved N Queens

In this solution the multipleQueensHorizontal score rule will fire for 6 queen couples: (A, B), (A, C), (A, D), (B, C), (B, D) and (C, D). Because none of the queens are on the same vertical or diagonal line, this solution will have a score of -6. An optimal solution of 4 queens has a score of 0.

Note

Notice that every score rule will relate to at least 1 planning entity class (directly or indirectly though a logically inserted fact).
This is normal: it would be a waste of time to write a score rule that only relates to problem facts, as the consequence will never change during planning, no matter what the possible solution.
Aggregating the score rules into the Score
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 "accumulateScore"
    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.SOFT,
                ($capacity - $studentSize),
                $room, $lecture));
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.SOFT,
                -2,
                $lecture, $curriculum));
end


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

7.6.5. Detecting Invalid Scores

Put the environmentMode in FULL_ASSERT (or FAST_ASSERT) to detect corruption in the . For the difference between both modes, . 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 triggered by the environmentMode. Just configure it as assertionScoreDirectorFactory:
 <environmentMode>FAST_ASSERT</environmentMode>
  ...
  <scoreDirectorFactory>
    <scoreDefinitionType>...</scoreDefinitionType>
    <scoreDrl>/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
    <assertionScoreDirectorFactory>
      <simpleScoreCalculatorClass>org.optaplanner.examples.nqueens.solver.score.NQueensSimpleScoreCalculator</simpleScoreCalculatorClass>
    </assertionScoreDirectorFactory>
  </scoreDirectorFactory>

7.7. Score Calculation Performance Tricks

7.7.1. Score Calculation Performance Tricks

The Solver will normally spend most of its execution time running the score calculation (which is called in its deepest loops). Faster score calculation will return the same solution in less time with the same algorithm, which normally means a better solution in equal time.

7.7.2. Average Calculation Count

After solving a problem, the Solver will log the average calculation count per second. This is a good measurement of Score calculation performance, despite that it is affected by non score calculation execution time. It depends on the problem scale of the problem dataset. Normally, even for high scale problems, it is higher than 1000, except when you're using SimpleScoreCalculator.

Important

When improving your score calculation, focus on maximizing the average calculation count per second, instead of maximizing the best score. A big improvement in score calculation can sometimes yield little or no best score improvement, for example when the algorithm is stuck in a local or global optima. If you're watching the calculation count instead, score calculation improvements are far more visible.
Furthermore, watching the calculation count, allows you to remove or add score constraints, and still compare it with the original calculation count. Comparing the best score with the original would be wrong, because it's comparing apples and oranges.

7.7.3. Incremental Score Calculation

When a Solution changes, incremental score calculation (AKA delta based score calculation), will calculate the delta with the previous state to find the new Score, instead of recalculating the entire score on every solution evaluation.
For example, if a single queen A moves from row 1 to 2, it won't bother to check if queen B and C can attack each other, since neither of them changed.
Planner 6.0 Delta based score calculation for N queens example.

Figure 7.11. Incremental score calculation for the 4 queens puzzle

This is a huge performance and scalability gain. Drools score calculation gives you this huge scalability gain without forcing you to write a complicated incremental score calculation algorithm. Just let the Drools rule engine do the hard work.
Notice that the speedup is relative to the size of your planning problem (your n), making incremental score calculation far more scalable.

7.7.4. Caching

Do not call remote services in your score calculation (except if you're bridging SimpleScoreCalculator to a legacy system). The network latency will kill your score calculation performance. Cache the results of those remote services if possible.
If some parts of a constraint can be calculated once, when the Solver starts, and never change during solving, then turn them into .

7.7.5. Unused Constraint

If you know a certain constraint can never be broken, don't bother writing a score constraint for it. For example in n queens, the score calculation doesn't check if multiple queens occupy the same column, because a Queen's column never changes and every Solution starts with each Queen on a different column.

Note

Don't go overboard with this. If some datasets don't use a specific constraint but others do, just return out of the constraint as soon as you can. There is no need to dynamically change your score calculation based on the dataset.

7.7.6. Build-in Hard Constraint

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 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.

7.7.7. Other Performance Tricks

  • Verify that your score calculation happens in the correct Number type. If you're making the sum of int values, don't let Drools sum it in a double which takes longer.
  • For optimal performance, always use server mode (java -server). We have seen performance increases of 50% by turning on server mode.
  • For optimal performance, use at least java 1.6. We have seen performance increases of 30% by switching from java 1.5 to 1.6.
  • Always remember that premature optimization is the root of all evil. Make sure your design is flexible enough to allow configuration based tweaking.

7.7.8. Score Trap

Be watchful for score traps. A score trap is a state in which several moves need to be done to resolve or lower the weight of a single constraint occurrence. Some examples of score traps:
  • If you need 2 doctors at each table, but you're only moving 1 doctor at a time, then the solver has no incentive to move a doctor to a table with no doctors. Punish a table with no doctors more then a table with only 1 doctor in your score function.
  • If you only add the table as a cause of the ConstraintOccurrence and forget the jobType (which is doctor or politician), then the solver has no incentive to move a doctor to table which is short of a doctor and a politician.
For example, consider this score trap. If the blue item moves from an overloaded computer to an empty computer, the hard score should improve. The trapped score implementation fails to do that:
Planner 6.0 trapped score implementation example.

Figure 7.12. Score Trap

The Solver should eventually get out of this trap, but it will take a lot of effort (especially if there are even more processes on the overloaded computer). Before they do that, they might actually start moving more processes into that overloaded computer, as there is no penalty for doing so.

Important

Always specify the degree of infeasibility. The business will often say: "if the solution is infeasible, it doesn't matter how infeasible it." While that's true for the business, it's not true for score calculation: it benefits from knowing how infeasible it is. In practice, soft constraints usually do this naturally and it's just a matter of doing it for the hard constraints too.

Note

Mixing course-grained and fine-grained moves reduces the chance of hitting a score trap.

7.7.9. stepLimit Benchmark

Not all score constraints have the same performance cost. Sometimes 1 score constraint can kill the score calculation performance outright. Use the benchmarker to do a 1 minute run and check what happens to the average calculation count per second if you comment out all but 1 of the score constraints.

7.8. Reusing the Score Calculation

Other parts of your application, for example your webUI, might need to calculate the score too. Do that by reusing the ScoreDirectorFactory of the Solver to build a separate ScoreDirector for that webUI:
ScoreDirectorFactory scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
Then use it when you need to calculate the Score of a Solution:
guiScoreDirector.setWorkingSolution(solution);
Score score = guiScoreDirector.calculateScore();
To explain in the GUI what entities are causing which part of the Score, get the ConstraintMatch objects from the ScoreDirector (after calling calculateScore()):
for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
    String constraintName = constraintMatchTotal.getConstraintName();
    Number weightTotal = constraintMatchTotal.getWeightTotalAsNumber();
    for (ConstraintMatch constraintMatch : constraintMatchTotal.getConstraintMatchSet()) {
        List<Object> justificationList = constraintMatch.getJustificationList();
        Number weight = constraintMatch.getWeightAsNumber();
        ...
    }
}

Chapter 8. Optimization Algorithms

8.1. Algorithm Checking

The number of possible solutions for a planning problem can be mind blowing. For example:
  • 4 queens has 256 possible solutions (4 ^ 4) and 2 optimal solutions.
  • 5 queens has 3125 possible solutions (5 ^ 5) and 1 optimal solution.
  • 8 queens has 16777216 possible solutions (8 ^ 8) and 92 optimal solutions.
  • 64 queens has more than 10^115 possible solutions (64 ^ 64).
  • Most real-life planning problems have an incredible number of possible solutions and only 1 or a few optimal solutions.
For comparison: the minimal number of atoms in the known universe (10^80). As a planning problem gets bigger, the search space tends to blow up really fast. Adding only 1 extra planning entity or planning value can heavily multiply the running time of some algorithms.
An algorithm that checks every possible solution (even with pruning) can easily run for billions of years on a single real-life planning problem. What we really want is to find the best solution in the limited time at our disposal. Planning competitions (such as the International Timetabling Competition) show that local search variations (tabu search, simulated annealing, ...) usually perform best for real-world problems given real-world time limitations.
Does Business Resource Planner find the optimal solution?
The business wants the optimal solution, but they also have other requirements:
  • Scale out: Large production datasets must not crash and have good results too.
  • Optimize the right problem: The constraints must match the actual business needs.
  • Available time: The solution must be found in time, before it becomes useless to execute.
  • Reliability: Every dataset must have at least a decent result (better than a human planner).
Given these requirements, and despite the promises of some salesmen, it's usually impossible for anyone or anything to find the optimal solution. Therefore, Business Resource Planner focuses on finding the best solution in available time, and often comes out as the best reusable software.
The nature of NP-complete problems make scaling a prime concern. The result quality of a small dataset guarantees nothing about the result quality of a large dataset. Scaling problems cannot be mitigated by hardware purchases. Start testing with a production sized dataset as soon as possible. Don't asses quality on small datasets (unless production encounters such datasets). Instead, solve a production sized dataset and compare with the results of longer execution, different algorithms and - if available - the human planner.

8.2. Framework of Planner

Business Resource Planner is the first framework to combine optimization algorithms (metaheuristics, ...) with score calculation by a rule engine such as BRMS. This combination turns out to be a very efficient because of the following:
  • A rule engine such as BRMS is great for calculating the score of a solution of a planning problem. It makes it easy and scalable to add additional soft or hard constraints such as "a teacher shouldn't teach more then 7 hours a day". It does delta based score calculation without any extra code. However it tends to be not suitable to actually find new solutions.
  • An optimization algorithm is great at finding new improving solutions for a planning problem, without necessarily brute-forcing every possibility. However it needs to know the score of a solution and offers no support in calculating that score efficiently.
Planner 6.0 Solver Architecture overview chart.

Figure 8.1. Solver Architecture

8.3. Optimization Algorithm Overview

Table 8.1. Optimization algorithms overview

Algorithm Scalable? Optimal? Easy to use? Tweakable? Requires CH?
Exact algorithms
Brute force 0/5 5/5 5/5 0/5 No
Branch and bound 0/5 5/5 4/5 1/5 No
Construction heuristics (CH)
First Fit 5/5 1/5 5/5 1/5 No
First Fit Decreasing 5/5 2/5 4/5 2/5 No
Best Fit 5/5 2/5 4/5 2/5 No
Best Fit Decreasing 5/5 2/5 4/5 2/5 No
Cheapest Insertion 3/5 2/5 5/5 2/5 No
Metaheuristics (MH)
Local search
Hill-climbing 5/5 2/5 4/5 3/5 Yes
Tabu search 5/5 4/5 3/5 5/5 Yes
Simulated annealing 5/5 4/5 2/5 5/5 Yes
Late acceptance 5/5 4/5 3/5 5/5 Yes
Evolutionary algorithms
Evolutionary strategies 4/5 3/5 ?/5 ?/5 Yes
Genetic algorithms 4/5 3/5 ?/5 ?/5 Yes
If you want to learn more about metaheuristics, read the free book .

8.4. Best Choice Optimization Algorithm

The best optimization algorithms configuration for your use case depends heavily on your use case. Nevertheless, this vanilla recipe will get you into the game with a pretty good configuration, probably much better than what you're used to.
Start with a quick configuration that involves little or no configuration and optimization code:
  1. First Fit
Next, implement planning entity difficulty comparison and turn it into:
  1. First Fit Decreasing
Next, implement moves and add tabu search behind it:
  1. First Fit Decreasing
  2. Tabu search (use planning entity tabu)
At this point the free lunch is over. The return on invested time lowers. The result is probably already more than good enough.
But you can do even better, at a lower return on invested time. Use the Benchmarker and try a couple of simulated annealing configurations:
  1. First Fit Decreasing
  2. Simulated annealing (try several starting temperatures)
And combine them with tabu search:
  1. First Fit Decreasing
  2. Simulated annealing (relatively long time)
  3. Tabu search (relatively short time)
If you have time, continue experimenting even further. Blog about your experiments!

8.5. SolverPhase

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.

Note

Some SolverPhase implementations can combine techniques from multiple optimization algorithms, but they are still just 1 SolverPhase. For example: a local search SolverPhase can do simulated annealing with planning entity tabu.
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. Usually, a solver will first run a construction heuristic and then run 1 or multiple metaheuristics:
Planner 6.0 general solver phase sequence.

Figure 8.2. General phase sequence

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.

8.6. Scope Overview

A solver will iteratively run phases. Each phase will usually iteratively run steps. Each step, in turn, usually iteratively runs moves. These form 4 nested scopes: solver, phase, step and move.
BRMS Business Resource Planner 6.0 overview of scopes running phases.

Figure 8.3. Scope Phase Overview

Configure to display the log messages of each scope.

8.7. Termination

8.7.1. Termination

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 metaheuristic 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 metaheuristic 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.

8.7.2. Different Types of Termination

TimeMillisSpendTermination
Terminates when an amount of time has been reached:
 <termination>
        <maximumTimeMillisSpend>500</maximumTimeMillisSpend>
    </termination>
 <termination>
        <maximumSecondsSpend>10</maximumSecondsSpend>
    </termination>
 <termination>
        <maximumMinutesSpend>5</maximumMinutesSpend>
    </termination>
 <termination>
        <maximumHoursSpend>1</maximumHoursSpend>
    </termination>

Note

If you use this Termination, you will most likely sacrifice perfect reproducibility (even with environmentMode REPRODUCIBLE) because of an available CPU time difference:
  • The available CPU time influences the number of steps that can be taken, which might be a few more or less.
  • The Termination might produce slightly different time gradient values, which will send time gradient based algorithms (such as simulated annealing) on a radically different path.
ScoreAttainedTermination
Terminates when a certain score has been reached. You can use this Termination if you know the perfect score, for example for 4 queens:
 <termination>
        <scoreAttained>0</scoreAttained>
    </termination>
For a planning problem with hard and soft constraints, it could look like this:
 <termination>
        <scoreAttained>0hard/-5000soft</scoreAttained>
    </termination>
You can use this Termination to terminate once it reaches a feasible solution.
StepCountTermination
Terminates when an amount of steps has been reached:
 <termination>
        <maximumStepCount>100</maximumStepCount>
    </termination>
This Termination can only be used for a SolverPhase, not for the Solver itself.
UnimprovedStepCountTermination
Terminates when the best score hasn't improved in a number of steps:
 <termination>
        <maximumUnimprovedStepCount>100</maximumUnimprovedStepCount>
    </termination>
If the score hasn't improved recently, it's probably not going to improve soon anyway and it's not worth the effort to continue. We have observed that once a new best solution is found (even after a long time of no improvement on the best solution), the next few steps tend to improve the best solution too.
This Termination can only be used for a SolverPhase, not for the Solver itself.
Combining multiple Terminations
Terminations can be combined, for example: terminate after 100 steps or if a score of 0 has been reached:
 <termination>
        <terminationCompositionStyle>OR</terminationCompositionStyle>
        <maximumStepCount>100</maximumStepCount>
        <scoreAttained>0</scoreAttained>
    </termination>
Alternatively you can use AND, for example: terminate after reaching a feasible score of at least -100 and no improvements in 5 steps:
 <termination>
        <terminationCompositionStyle>AND</terminationCompositionStyle>
        <maximumUnimprovedStepCount>5</maximumUnimprovedStepCount>
        <scoreAttained>-100</scoreAttained>
    </termination>
This example ensures it doesn't just terminate after finding a feasible solution, but also completes any obvious improvements on that solution before terminating.
Asynchronous termination from another thread
Sometimes you'll want to terminate a Solver early from another thread, for example because a user action or a server restart. This cannot be configured by a Termination as it's impossible to predict when and if it will occur. Therefore the Solver interface has these 2 thread-safe methods:
public interface Solver {

    // ...

    boolean terminateEarly();
    boolean isTerminateEarly();

}
If you call the terminateEarly() method from another thread, the Solver will terminate at its earliest convenience and the solve() method will return in the original Solver thread.

8.8. SolverEventListener

Each time a new best solution is found, the Solver fires a BestSolutionChangedEvent.
To listen to such events, add a SolverEventListener to the Solver:
public interface Solver {

    // ...

    void addEventListener(SolverEventListener eventListener);
    void removeEventListener(SolverEventListener eventListener);

}

8.9. Custom 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.

Note

Most of the time, a custom construction heuristic is not worth the hassle. The supported constructions heuristics are configurable (so you can tweak them with the Benchmarker), Termination aware and support partially initialized solutions too.
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();
                ...
            }
            ...
        }
    }

}

Warning

Any change on the planning entities in a CustomSolverPhaseCommand must be notified to the ScoreDirector.

Warning

Do not change any of the planning facts in a CustomSolverPhaseCommand. That will corrupt the Solver because any previous score or solution was for a different problem. If you want to do that, see and real-time planning instead.
And configure it like this:
<solver>
  ...
  <customSolverPhase>
    <customSolverPhaseCommandClass>org.optaplanner.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>...MyUninitializer</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.

Chapter 9. Move and Neighborhood Selection

9.1. Definition of a Move

9.1.1. Definition of a Move

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:
Planner 6.0 N Queens single move image.

Figure 9.1. Single move solution

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:
Planner 6.0 N Queens Example with changemoves.

Figure 9.2. Possible changeMoves

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:
Planner 6.0 four changeMove solutions.

Figure 9.3. changeMove's solution

Note

There are many other types of moves besides changeMove's. Many move types are included out-of-the-box, but you can also implement custom moves.
A Move can affect multiple entities or even create/delete entities. But it must not change the problem facts.
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.

9.1.2. MoveSelector

A MoveSelector's main function is to create Iterator<Move> when needed. An optimization algorithm will iterate through a subset of those moves.
Here's an example how to configure a changeMoveSelector for the optimization algorithm Local Search:
 <localSearch>
    <changeMoveSelector/>
    ...
  </localSearch>
Out of the box, this works and all properties of the changeMoveSelector are defaulted sensibly (unless that fails fast due to ambiguity). On the other hand, the configuration can be customized significantly for specific use cases. For example: you want want to configure a filter to discard pointless moves.

9.1.3. Subselecting of Entities, Values, and other Moves

To create a Move, we need to select 1 or more planning entities and/or planning values to move. Just like MoveSelectors, EntitySelectors and ValueSelectors need to support a similar feature set (such as scalable just-in-time selection). Therefor, they implement a common interface Selector and they are configured similarly.
A MoveSelector is often composed out of EntitySelectors, ValueSelectors or even other MoveSelectors, which can be configured individually if desired:
 <unionMoveSelector>
      <changeMoveSelector>
        <entitySelector>
          ...
        </entitySelector>
        <valueSelector>
          ...
        </valueSelector>
        ...
      </changeMoveSelector>
      ...
    </unionMoveSelector>
Together, this structure forms a Selector tree:
Planner 6.0 Selector Tree demonstrating Value, Entity, and Move Selectors.

Figure 9.4. Selector Tree

The root of this tree is a MoveSelector which is injected into the optimization algorithm implementation to be (partially) iterated in every step.

9.2. Selector Features

9.2.1. CacheType

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:
  • JUST_IN_TIME (default): Not cached. Construct each selection (Move, ...) just before it's used. This scales up well in memory footprint.
  • STEP: Cached. Create each selection (Move, ...) at the beginning of a step and cache them in a list for the remainder of the step. This scales up badly in memory footprint.
  • PHASE: Cached. Create each selection (Move, ...) at the beginning of a SolverPhase and cache them in a list for the remainder of the SolverPhase. Some selections cannot be phase cached because the list changes every step. This scales up badly in memory footprint, but has a slight performance gain.
  • SOLVER: Cached. Create each selection (Move, ...) at the beginning of a Solver and cache them in a list for the remainder of the Solver. Some selections cannot be solver cached because the list changes every step. This scales up badly in memory footprint, but has a slight performance gain.
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.

9.2.2. SelectionOrder

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 actually evaluated.
Almost every Selector supports setting a selectionOrder:
 <changeMoveSelector>
      ...
      <selectionOrder>RANDOM</selectionOrder>
      ...
    </changeMoveSelector>
The following selectionOrders are supported:
  • ORIGINAL: Select the selections (Moves, entities, values, ...) in default order. Each selection will be selected only once.
    • For example: A0, A1, A2, A3, ..., B0, B1, B2, B3, ..., C0, C1, C2, C3, ...
  • SORTED: Select the selections (Moves, entities, values, ...) in sorted order. Each selection will be selected only once. Requires cacheType >= STEP. Mostly used on an entitySelector or valueSelector for construction heuristics. See .
    • For example: A0, B0, C0, ..., A2, B2, C2, ..., A1, B1, C1, ...
  • RANDOM (default): Select the selections (Moves, entities, values, ...) in non-shuffled random order. A selection might be selected multiple times. This scales up well in performance because it does not require caching.
    • For example: C2, A3, B1, C2, A0, C0, ...
  • SHUFFLED: Select the selections (Moves, entities, values, ...) in shuffled random order. Each selection will be selected only once. Requires cacheType >= STEP. This scales up badly in performance, not just because it requires caching, but also because a random number is generated for each element, even if it's not selected (which is the grand majority when scaling up).
    • For example: C2, A3, B1, A0, C0, ...
  • PROBABILISTIC: Select the selections (Moves, entities, values, ...) in random order, based on the selection probability of each element. A selection with a higher probability has a higher chance to be selected than elements with a lower probability. A selection might be selected multiple times. Requires cacheType >= STEP. Mostly used on an entitySelector or valueSelector. See .
    • For example: B1, B1, A1, B2, B1, C2, B1, B1, ...
A selectionOrder can be set on composite selectors too.

Note

When a Selector is cached, all of its nested Selectors will naturally default to selectionOrder ORIGINAL. Avoid overwriting the selectionOrder of those nested Selectors.

9.2.3. Selector Combinations

9.2.3.1. Just in Time Random Selection

This combination is great for big use cases (10 000 entities or more), as it scales up well in memory footprint and performance. Other combinations are often not even viable on such sizes. It works for smaller use cases too, so it's a good way to start out. It's the default, so this explicit configuration of cacheType and selectionOrder is actually obsolete:
 <unionMoveSelector>
      <cacheType>JUST_IN_TIME</cacheType>
      <selectionOrder>RANDOM</selectionOrder>

      <changeMoveSelector/>
      <swapMoveSelector/>
    </unionMoveSelector>
Here's how it works. When Iterator<Move>.next() is called, a child MoveSelector is randomly selected (1), which creates a random Move is created (2, 3, 4) and is then returned (5):
Planner 6.0 random move selection graph

Figure 9.5. Just-in-time Random Selector

Notice that it never creates a list of Moves and it generates random numbers only for Moves that are actually selected.

9.2.3.2. Cached Shuffled Selection

This combination often wins for small and medium use cases (5000 entities or less). Beyond that size, it scales up badly in memory footprint and performance.
 <unionMoveSelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>SHUFFLED</selectionOrder>

      <changeMoveSelector/>
      <swapMoveSelector/>
    </unionMoveSelector>
Here's how it works: At the start of the phase (or step depending on the cacheType), all moves are created (1) and cached (2). When MoveSelector.iterator() is called, the moves are shuffled (3). When Iterator<Move>.next() is called, the next element in the shuffled list is returned (4):
Planner 6.0 Chached Shuffle selection for all possible moves.

Figure 9.6. Cached shuffled selection

Notice that each Move will only be selected once, even though they are selected in random order.
Use cacheType PHASE if none of the (possibly nested) Selectors require STEP. Otherwise, do something like this:
 <unionMoveSelector>
      <cacheType>STEP</cacheType>
      <selectionOrder>SHUFFLED</selectionOrder>

      <changeMoveSelector>
        <cacheType>PHASE</cacheType>
      </changeMoveSelector>
      <swapMoveSelector/>
        <cacheType>PHASE</cacheType>
      </swapMoveSelector>
      <pillarSwapMoveSelector/><!-- Does not support cacheType PHASE -->
    </unionMoveSelector>

9.2.3.3. Cached Random Selection

This combination is often a worthy competitor for medium use cases, especially with fast stepping optimization algorithms (such as simulated annealing). Unlike cached shuffled selection, it doesn't waste time shuffling the move list at the beginning of every step.
 <unionMoveSelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>RANDOM</selectionOrder>

      <changeMoveSelector/>
      <swapMoveSelector/>
    </unionMoveSelector>

9.2.4. Filtered Selection

There are certain moves that you don't want to select, because:
  • The move is pointless and would only waste CPU time. For example, swapping 2 lectures of the same course will result in the same score and the same schedule because all lectures of 1 course are interchangeable (same teacher, same students, same topic).
  • Doing the move would break , so the solution would be infeasible but the score function doesn't check build-in hard constraints (for performance gain). For example, don't change a gym lecture to a room which is not a gym room.
    • Note that any build-in hard constraint must usually be filtered on every move type. For example, don't swap the room of a gym lecture with another lecture if the other lecture's original room isn't a gym room.
Filtered selection can happen on any Selector in the selector tree, including any MoveSelector, EntitySelector or ValueSelector. It works with any kind of cacheType and selectionOrder.
Planner 6.0 selection filter graph.

Figure 9.7. SelectionFilters

Filtering uses 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 filterClass on the moveSelector:
 <swapMoveSelector>
      <filterClass>org.optaplanner.examples.curriculumcourse.solver.move.DifferentCourseSwapMoveFilter</filterClass>
    </swapMoveSelector>
But if possible, apply it on a lower levels, such as a filterClass on the entitySelector or valueSelector:
 <changeMoveSelector>
      <entitySelector>
        <filterClass>...EntityFilter</filterClass>
      </entitySelector>
    </changeMoveSelector>
You can configure multiple filterClass elements on a single selector.

9.2.5. Sorted Selection

Sorted selection can happen on any Selector in the selector tree, including any MoveSelector, EntitySelector or ValueSelector. It does not work with cacheType JUST_IN_TIME and it only works with selectionOrder SORTED.
It's mostly used in construction heuristics.
Sorted selection by Comparator
The easiest way to sort a Selector is with a plain old Comparator:
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();
    }

}
You 'll also need to configure it (unless it's annotated applied for this optimization algorithm):
 <entitySelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>SORTED</selectionOrder>
      <sorterComparatorClass>...CloudProcessDifficultyComparator</sorterComparatorClass>
      <sorterOrder>DESCENDING</sorterOrder>
    </entitySelector>
Sorted selection by SelectionSorterWeightFactory
If you need the entire Solution to sort a Selector, use a SelectionSorterWeightFactory instead:
public interface SelectionSorterWeightFactory<Sol extends Solution, T> {

    Comparable createSorterWeight(Sol solution, T selection);

}
public class QueenDifficultyWeightFactory implements SelectionSorterWeightFactory<NQueens, Queen> {

    public Comparable createSorterWeight(NQueens nQueens, Queen queen) {
        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
                    // Tie breaker
                    .append(queen.getColumnIndex(), other.queen.getColumnIndex())
                    .toComparison();
        }

    }

}
You 'll also need to configure it (unless it's annotated and automatically applied for this optimization algorithm):
 <entitySelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>SORTED</selectionOrder>
      <sorterWeightFactoryClass>...QueenDifficultyWeightFactory</sorterWeightFactoryClass>
      <sorterOrder>DESCENDING</sorterOrder>
    </entitySelector>
Sorted selection by SelectionSorter
Alternatively, you can also use the interface SelectionSorter directly:
public interface SelectionSorter<T> {

    void sort(ScoreDirector scoreDirector, List<T> selectionList);

}
 <entitySelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>SORTED</selectionOrder>
      <sorterClass>...MyEntitySorter</sorterClass>
    </entitySelector>

9.2.6. Probability Selection

Probabilistic selection can happen on any Selector in the selector tree, including any MoveSelector, EntitySelector or ValueSelector. It does not work with cacheType JUST_IN_TIME and it only works with selectionOrder PROBABILISTIC.
Each selection has a probabilityWeight, which determines the chance that's that selection will be selected:
public interface SelectionProbabilityWeightFactory<T> {

    double createProbabilityWeight(ScoreDirector scoreDirector, T selection);

}
 <entitySelector>
      <cacheType>PHASE</cacheType>
      <selectionOrder>PROBABILISTIC</selectionOrder>
      <probabilityWeightFactoryClass>...MyEntityProbabilityWeightFactoryClass</probabilityWeightFactoryClass>
    </entitySelector>
For example, if there are 3 entities: process A (probabilityWeight 2.0), process B (probabilityWeight 0.5) and process C (probabilityWeight 0.5), then process A will be selected 4 times more than B and C.

9.3. Move Selectors

9.3.1. changeMoveSelector

For 1 planning variable, the ChangeMove selects 1 planning entity and 1 planning value and assigns the entity's variable to that value.
Planner 6.0 Change Move for 1 variable and 1 entity.

Figure 9.8. changeMoveSelector

Simplest configuration:
 <changeMoveSelector/>
Advanced configuration:
 <changeMoveSelector>
      ... <!-- Normal selector properties -->
      <entitySelector>
        <entityClass>...Lecture</entityClass>
        ...
      </entitySelector>
      <valueSelector>
        <variableName>room</variableName>
        ...
      </valueSelector>
    </changeMoveSelector>
A ChangeMove is the finest grained move.

Important

Almost every moveSelector configuration injected into a metaheuristic algorithm should include a changeMoveSelector or a custom implementation. This guarantees that every possible Solution can be reached through applying a number of moves in sequence (not taking into account). Of course, normally it is unioned with other, more course grained moves.

9.3.2. swapMoveSelector

The SwapMove selects 2 different planning entities and swaps the planning values of all their planning variables.
SwapMove variable entities

Figure 9.9. SwapMove

Although a SwapMove on a single variable is essentially just 2 ChangeMoves, it's often the winning step where the first of the 2 ChangeMoves would not be the winning step because it leave the solution in a state with broken hard constraints. For example: swapping the room of 2 lectures doesn't bring the solution in a intermediate state where both lectures are in the same room which breaks a hard constraint.
Simplest configuration:
 <swapMoveSelector/>
Advanced configuration:
 <swapMoveSelector>
      ... <!-- Normal selector properties -->
      <entitySelector>
        <entityClass>...Lecture</entityClass>
        ...
      </entitySelector>
      <secondaryEntitySelector>
        ...
      </secondaryEntitySelector>
      <variableNameInclude>room</variableNameInclude>
      <variableNameInclude>...</variableNameInclude>
    </swapMoveSelector>
The secondaryEntitySelector is rarely needed: if it is not specified, entities from the same entitySelector are swapped.
If one or more variableNameInclude properties are specified, not all planning variables will be swapped, but only those specified. For example for course scheduling, specifying only variableNameInclude room will make it only swap room, not period.

9.3.3. pillarSwapMoveSelector

A pillar is a set of planning entities which have the same planning value(s) for each of their planning variables. The pillarSwapMove selects 2 different entity pillars and swaps the values of all their variables for all their entities.
Business Resource Planner 6.0 variable swap chart demonstrating a PillarSwapMove.

Figure 9.10. PillarSwapMoveSelector

Simplest configuration:
 <pillarSwapMoveSelector/>
Advanced configuration:
 <pillarSwapMoveSelector>
      ... <!-- Normal selector properties -->
      <pillarSelector>
        <entitySelector>
        <entityClass>...Lecture</entityClass>
          ...
        </entitySelector>
      </pillarSelector>
      <secondaryPillarSelector>
        <entitySelector>
          ...
        </entitySelector>
      </secondaryPillarSelector>
      <variableNameInclude>room</variableNameInclude>
      <variableNameInclude>...</variableNameInclude>
    </pillarSwapMoveSelector>
The secondaryPillarSelector is rarely needed: if it is not specified, entities from the same pillarSelector are swapped.
The other properties are explained in .

9.3.4. subChainChangeMoveSelector

A subChain is a set of planning entities with a chained planning variable which form part of a chain. The subChainChangeMove selects a subChain and moves it to another place in a different or the same anchor chain.
Simplest configuration:
 <subChainChangeMoveSelector/>
Advanced configuration:
 
      <subChainChangeMoveSelector>
      ... <!-- Normal selector properties -->
      <subChainSelector>
        <entitySelector>
          <entityClass>...Customer</entityClass>
          ...
        </entitySelector>
        <minimumSubChainSize>2</minimumSubChainSize>
        <maximumSubChainSize>40</maximumSubChainSize>
      </subChainSelector>
      <valueSelector>
        <variableName>previousStandstill</variableName>
        ...
      </valueSelector>
      <selectReversingMoveToo>true</selectReversingMoveToo>
    </subChainChangeMoveSelector>
The subChainSelector selects a number of entities, no less than minimumSubChainSize (defaults to 2) and no more than maximumSubChainSize (defaults to infinity).
The property selectReversingMoveToo (defaults to true) enabled selecting the reverse of every subchain too.

9.3.5. subChainSwapMoveSelector

The subChainSwapMove selects 2 different subChains and moves it to another place in a different or the same anchor chain.
Simplest configuration:
 <subChainSwapMoveSelector/>
Advanced configuration:
 
      <subChainSwapMoveSelector>
      ... <!-- Normal selector properties -->
      <subChainSelector>
        <entitySelector>
          <entityClass>...Customer</entityClass>
          ...
        </entitySelector>
        <minimumSubChainSize>2</minimumSubChainSize>
        <maximumSubChainSize>40</maximumSubChainSize>
      </subChainSelector>
      <secondarySubChainSelector>
        <entitySelector>
          <entityClass>...Customer</entityClass>
          ...
        </entitySelector>
        <minimumSubChainSize>2</minimumSubChainSize>
        <maximumSubChainSize>40</maximumSubChainSize>
      </secondarySubChainSelector>
      <selectReversingMoveToo>true</selectReversingMoveToo>
    </subChainSwapMoveSelector>
The secondarySubChainSelector is rarely needed: if it is not specified, entities from the same subChainSelector are swapped.

9.4. Combining Move Selectors

9.4.1. unionMoveSelector

A unionMoveSelector selects a Move by selecting 1 of its child MoveSelectors to supply the next Move.
Simplest configuration:
 <unionMoveSelector>
      <...MoveSelector/>
      <...MoveSelector/>
      <...MoveSelector/>
      ...
    </unionMoveSelector>
Advanced configuration:
 <unionMoveSelector>
      ... <!-- Normal selector properties -->
      <selectorProbabilityWeightFactoryClass>...ProbabilityWeightFactory</selectorProbabilityWeightFactoryClass>
      <changeMoveSelector>
        <fixedProbabilityWeight>...</fixedProbabilityWeight>
        ...
      </changeMoveSelector>
      <swapMoveSelector>
        <fixedProbabilityWeight>...</fixedProbabilityWeight>
        ...
      </swapMoveSelector>
      <...MoveSelector>
        <fixedProbabilityWeight>...</fixedProbabilityWeight>
        ...
      </...MoveSelector>
      ...
    </unionMoveSelector>
The selectorProbabilityWeightFactory determines in selectionOrderRANDOM how often a child MoveSelector is selected to supply the next Move. By default, each child MoveSelector has the same chance of being selected. Change the fixedProbabilityWeight of such a child to select it more often. For example, the unionMoveSelector can return a SwapMove twice as often as a ChangeMove:
 <unionMoveSelector>
      <changeMoveSelector>
        <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
        ...
      </changeMoveSelector>
      <swapMoveSelector>
        <fixedProbabilityWeight>2.0</fixedProbabilityWeight>
        ...
      </swapMoveSelector>
    </unionMoveSelector>
The number of possible ChangeMoves is very different from the number of possible SwapMoves and furthermore it's problem dependent. To give each individual Move the same selection chance (as opposed to each MoveSelector), use the FairSelectorProbabilityWeightFactory:
 <unionMoveSelector>
      <selectorProbabilityWeightFactoryClass>org.optaplanner.core.impl.heuristic.selector.common.decorator.FairSelectorProbabilityWeightFactory</selectorProbabilityWeightFactoryClass>
      <changeMoveSelector/>
      <swapMoveSelector/>
    </unionMoveSelector>

9.4.2. cartesianProductMoveSelector

A cartesianProductMoveSelector selects a new CompositeMove. It builds that CompositeMove by selecting 1 Move per child MoveSelector and adding it to the CompositiveMove.
Simplest configuration:
 <cartesianProductMoveSelector>
      <...MoveSelector/>
      <...MoveSelector/>
      <...MoveSelector/>
      ...
    </cartesianProductMoveSelector>
Advanced configuration:
 <cartesianProductMoveSelector>
      ... <!-- Normal selector properties -->
      <changeMoveSelector>
        ...
      </changeMoveSelector>
      <swapMoveSelector>
        ...
      </swapMoveSelector>
      <...MoveSelector>
        ...
      </...MoveSelector>
      ...
    </cartesianProductMoveSelector>

9.5. EntitySelector

Simplest configuration:
 <entitySelector/>
Advanced configuration:
 <entitySelector>
        ... <!-- Normal selector properties -->
        <entityClass>org.optaplanner.examples.curriculumcourse.domain.Lecture</entityClass>
      </entitySelector>
The entityClass property is only required if it cannot be deduced automatically because there are multiple entity classes.

9.6. ValueSelector

Simplest configuration:
 <valueSelector/>
Advanced configuration:
 <valueSelector>
        ... <!-- Normal selector properties -->
        <variableName>room</variableName>
      </valueSelector>
The variableName property is only required if it cannot be deduced automatically because there are multiple variables (for the related entity class).

9.7. Custom Moves

9.7.1. Introduction to Custom Moves

Which move types might be missing in my implementation?
To determine which move types might be missing in your implementation, run a benchmarker for a short amount of time and . Take a look at such a best solution: it will likely be a local optima. Try to figure out if there's a move that could get out of that local optima faster.
If you find one, implement that course-grained move, mix it with the existing moves and benchmark it against the previous configurations to see if you want to keep it.
Instead of reusing the generic Moves (such as ChangeMove) you can also implement your own Moves. Generic and custom MoveSelectors can be combined as wanted.
A custom Move can be tailored to work to the advantage of your constraints. For example, in examination scheduling, changing the period of an exam A also changes te period of all the exams that need to coincide with exam A.
A custom Move is also slightly faster than a generic Move. However, it's far more work to implement and much harder to avoid bugs. After implementing a custom Move, make sure to turn on environmentMode FULL_ASSERT to check for score corruptions.

9.7.2. The Move Interface

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 planning entity'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).

Note

You can alter multiple entities in a single move and effectively create a big move (also known as a coarse-grained move).

Warning

A Move can only change/add/remove planning entities, it must not change any of the problem facts.
Business Resource Planner automatically filters out non doable moves by calling the isDoable(ScoreDirector) method on a move. A non doable move is:
  • A move that changes nothing on the current solution. For example, moving queen B0 to row 0 is not doable, because it is already there.
  • A move that is impossible to do on the current solution. For example, moving queen B0 to row 10 is not doable because it would move it outside the board limits.
In the n queens example, a move which moves the queen from its current row to the same row isn't doable:
 public boolean isMoveDoable(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 (o 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.

9.7.3. MoveListFactory

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.optaplanner.examples.nqueens.solver.move.factory.RowChangeMoveFactory</moveListFactoryClass>
    </moveListFactory>
Advanced configuration:
 <moveListFactory>
      ... <!-- Normal moveSelector properties -->
      <moveListFactoryClass>org.optaplanner.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.

9.7.4. MoveIteratorFactory

Use this advanced form to generate custom moves by implementing the interface MoveIteratorFactory:
public interface MoveIteratorFactory {

    long getSize(ScoreDirector scoreDirector);

    Iterator<Move> createOriginalMoveIterator(ScoreDirector scoreDirector);

    Iterator<Move> createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom);

}
The method getSize() must give an estimation of the size. It doesn't need to be correct. The method createOriginalMoveIterator is called if the selectionOrder isORIGINAL or if it is cached. The method createRandomMoveIterator is called for selectionOrder RANDOM combined with cacheType JUST_IN_TIME.

Important

Don't create a collection (list, array, map, set) of Moves when creating the Iterator<Move>: the whole purpose of MoveIteratorFactory over MoveListFactory is giving you the ability to create a Move just in time in the Iterator's method next().
Simple configuration (which can be nested in a unionMoveSelector just like any other MoveSelector):
 <moveIteratorFactory>
      <moveIteratorFactoryClass>...</moveIteratorFactoryClass>
    </moveIteratorFactory>
Advanced configuration:
 <moveIteratorFactory>
      ... <!-- Normal moveSelector properties -->
      <moveIteratorFactoryClass>...</moveIteratorFactoryClass>
    </moveIteratorFactory>

Chapter 10. Construction Heuristics

10.1. Overview of Construction Heuristics

A construction heuristic builds a pretty good initial solution in a finite length of time. Its solution isn't always feasible, but it finds it fast and metaheuristics can finish the job.
Construction heuristics terminate automatically, so there's usually no need to configure a Termination on the construction heuristic phase specifically.

10.2. First Fit

10.2.1. Algorithm Description

The First Fit algorithm cycles through all the planning entities (in default order), initializing 1 planning entity at a time. It assigns the planning entity to the best available planning value, taking the already initialized planning entities into account. It terminates when all planning entities have been initialized. It never changes a planning entity after it has been assigned.
Business Resource Planner 6.0 construction heuristic chart demonstrating assigned entities.

Figure 10.1. N Queens First Fit Example.

Notice that it starts with putting Queen A into row 0 (and never moving it later), which makes it impossible to reach the optimal solution. Suffixing this construction heuristic with metaheurstics can remedy that.

10.2.2. Configuration

Configure this SolverPhase:
 <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
    <!-- Speedup that can be applied to most, but not all use cases: -->
    <!-- <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType> -->
  </constructionHeuristic>

Note

The constructionHeuristicPickEarlyType of FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING is a big speedup, which should be applied when initializing a planning entity which can only make the score lower or equal. So if:
  • There are no positive constraints.
  • There is no negative constraint that can stop being broken by adding a planning entity (except if another negative constraint gets broken which outweighs the first negative constraint).
If that is not the case, then it can still be good to apply it in some cases, but not in most cases. Use the Benchmarker to decide.

10.3. First Fit Decreasing

10.3.1. Algorithm Description

Like First Fit, but assigns the more difficult planning entities first, because they are less likely to fit in the leftovers. So it sorts the planning entities on decreasing difficulty.
Requires the model to support .
Business Resource Planner 6.0 construction heuristic chart demonstrating assigned entities decreasing.

Figure 10.2. Decreasing Difficulty Planning Entity Example

Note

One would expect that this algorithm always performs better than First Fit. That's not always the case, but usually is.

10.3.2. Configuration

Configure this SolverPhase:
 <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
    <!-- Speedup that can be applied to most, but not all use cases: -->
    <!-- <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType> -->
  </constructionHeuristic>

10.4. Best Fit

10.4.1. Algorithm Description

Like First Fit, but uses the weaker planning values first, because the strong planning values are more likely to be able to accommodate later planning entities. So it sorts the planning values on increasing strength.
Requires the model to support .

Note

One would expect that this algorithm always performs better than First Fit. That's not always the case.

10.4.2. Configuration

Configure this SolverPhase:
 <constructionHeuristic>
    <constructionHeuristicType>BEST_FIT</constructionHeuristicType>
    <!-- Speedup that can be applied to most, but not all use cases: -->
    <!-- <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType> -->
  </constructionHeuristic>

10.5. Best Fit Decreasing

10.5.1. Algorithm Description

Combines First Fit Decreasing and Best Fit. So it sorts the planning entities on decreasing difficulty and the planning values on increasing strength.
Requires the model to support .

Note

One would expect that this algorithm always performs better than First Fit, First Fit Decreasing and Best Fit. That's not always the case.

10.5.2. Configuration

Configure this SolverPhase:
 <constructionHeuristic>
    <constructionHeuristicType>BEST_FIT_DECREASING</constructionHeuristicType>
    <!-- Speedup that can be applied to most, but not all use cases: -->
    <!-- <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType> -->
  </constructionHeuristic>

10.6. Cheapest Insertion

10.6.1. Algorithm Description

Not implemented yet

10.6.2. Configuration

Not implemented yet

Chapter 12. Evolutionary Algorithms

12.1. Overview of Evolutionary Algorithms

Evolutionary algorithms work on a population of solutions and evolve that population.

12.2. Evolutionary Strategies

This algorithm has not been implemented yet.

12.3. Genetic Algorithms

This algorithm has not been implemented yet.

Chapter 13. Exact Methods

13.1. Overview of Exact Methods

Exact methods will always find the global optimum and recognize it too. That being said, they don't scale (not even beyond toy problems) and are therefore mostly useless.

13.2. Brute Force

13.2.1. Algorithm Description

The Brute Force algorithm creates and evaluates every possible solution.
Business Resource Planner 6.0 N Queens Brute Force example evaluating every solution.

Figure 13.1. N Queens Brute Force Example

Notice that it creates a search tree that explodes as the problem size increases. Brute Force is mostly unusable for a real-world problem due to time limitations, as proven by this scalability graph from the benchmarker:
Business Resource Planner 6.0 Brute Force Scalability Summary.

Figure 13.2. Scalability Summary

Business Resource Planner 6.0 Brute Force Scalability Summary for Cloud Balancing.

Figure 13.3. Scalability Summary

13.2.2. Configuration

Using the brute force algorithm is easy:
<solver>
  ...
  <bruteForce>
  </bruteForce>
</solver>

Chapter 14. Benchmarking and Tweaking

14.1. Finding the Best Solver Configuration

Business Resource Planner supports several optimization algorithms, but you're probably wondering which is the best one? Although some optimization algorithms generally perform better than others, it really depends on your problem domain. Most solver phases have parameters which can be tweaked. Those parameters can influence the results a lot, even though most solver phases work pretty well out-of-the-box.
Luckily, Business Resource Planner includes a benchmarker, which allows you to play out different solver phases with different settings against each other, so you can pick the best configuration for your planning problem.
Business Resource Planner 6.0 Benchmark Overview for Problem Datasets.

Figure 14.1. Benchmark Overview Chart

14.2. The Benchmarker

14.2.1. Adding the Dependency

The benchmarker is in a separate artifact called optaplanner-benchmark.
If you use maven, add a dependency in your pom.xml file:
 <dependency>
      <groupId>org.optaplanner</groupId>
      <artifactId>optaplanner-benchmark</artifactId>
      <version>...</version>
    </dependency>
This is similar for gradle, ivy and buildr. The version must be exactly the same as the optaplanner-core version used.
If you use ANT, you've probably already copied the required jars from the download zip's binaries directory.

14.2.2. Building and Running a PlannerBenchmark

You can build a PlannerBenchmark instance with the XmlPlannerBenchmarkFactory. Configure it with a benchmark configuration xml file:
 XmlPlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory();
    plannerBenchmarkFactory.configure("/org/optaplanner/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.optaplanner.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.optaplanner.examples.nqueens.domain.NQueens</solutionClass>
      <planningEntityClass>org.drools.planner.examples.nqueens.domain.Queen</planningEntityClass>
      <scoreDirectorFactory>
        <scoreDefinitionType>SIMPLE</scoreDefinitionType>
        <scoreDrl>/org/optaplanner/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 .
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). A benchmark report will be written in that directory.

Note

It's recommended that the benchmarkDirectory is a directory ignored for 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.

14.2.3. ProblemIO

14.2.3.1. ProblemIO Interface

The benchmarker needs to be able to read the input files to contain a Solution write the best Solution of each benchmark to an output file. For that it uses a class that implements the ProblemIO interface:
public interface ProblemIO {

    String getFileExtension();

    Solution read(File inputSolutionFile);

    void write(Solution solution, File outputSolutionFile);

}

Warning

Your input files need to have been written with the same ProblemIO class as they are being read by the benchmarker.

14.2.3.2. The Default ProblemIO

By default, a benchmarker uses a XStreamProblemIO instance to read and write solutions.
You need to tell the benchmarker about your Solution class which is annotated with XStream annotations:
 <problemBenchmarks>
      <xstreamAnnotatedClass>org.optaplanner.examples.nqueens.domain.NQueens</xstreamAnnotatedClass>
      <inputSolutionFile>data/nqueens/unsolved/unsolvedNQueens32.xml</inputSolutionFile>
      ...
    </problemBenchmarks>
Your input files need to have been written with a XStreamProblemIO instance, not just any XStream instance, because the XStreamProblemIO uses a customized XStream instance.

Warning

XStream and XML in general is a very verbose format. Reading or writing large datasets in this format can cause an OutOfMemoryError and performance degradation.

14.2.3.3. Custom ProblemIO

Alternatively, you can implement your own ProblemIO implementation and configure it with the problemIOClass element:
 <problemBenchmarks>
      <problemIOClass>org.optaplanner.examples.machinereassignment.persistence.MachineReassignmentProblemIO</problemIOClass>
      <inputSolutionFile>data/machinereassignment/input/model_a1_1.txt</inputSolutionFile>
      ...
    </problemBenchmarks>

Warning

A ProblemIO implementation must be thread-safe.

14.2.4. Writing the Output Solution

The best solution of each benchmark run can be written to the in the benchmarkDirectory. By default, this is disabled, because the files are rarely used and considered bloat. Also, on large datasets, writing the best solution of each single benchmark can take quite some time and memory (causing an OutOfMemoryError), especially in a verbose format like XStream.
You can enable to write the output solution in the benchmarkDirectory with writeOutputSolutionEnabled:
 <problemBenchmarks>
      ...
      <writeOutputSolutionEnabled>true</writeOutputSolutionEnabled>
      ...
    </problemBenchmarks>

14.2.5. Warm Up

Without a warm up, the results of the first (or first few) benchmarks are not reliable, because they will have lost CPU time on HotSpot JIT compilation (and possibly DRL compilation too).
The avoid that distortion, the benchmarker can run some of the benchmarks for a specified amount of time, before running the real benchmarks. Generally, a warm up of 30 seconds suffices:
<plannerBenchmark>
  ...
  <warmUpSecondsSpend>30</warmUpSecondsSpend>
  ...
</plannerBenchmark>

14.3. Benchmark Report

14.3.1. HTML Report

After the running a benchmark, a HTML report will be written in the benchmarkDirectory with the filename index.html. Open it in your browser. It has a nice overview of your benchmark including:
  • Summary statistics: graphs and tables
  • Problem statistics per inputSolutionFile
  • Each solver configuration (ranked): easy to copy and paste.
  • Benchmark information
The HTML report will use your default locale to format numbers. If you need to share the benchmark report with people from another country, you might want to overwrite the benchmarkReportLocale:
<plannerBenchmark>
  ...
  <benchmarkReportLocale>en_US</benchmarkReportLocale>
  ...
</plannerBenchmark>

14.3.2. Summary Statistics

Best score summary useful for visualizing the best solver configuration.
Shows the best score per inputSolutionFile for the various solver configurations.
Business Resource Planner 6.0 best score chart for inputSolutionFile.

Figure 14.2. Best Score Summary Statistic

  • Best Score Scalability: Shows the best problem scale score for each solver configuration
  • Winning Score Difference: Shows the winning score difference score per inputSolutionFile for each solver configuration. The winning score difference is the score difference with the score of the winning solver configuration for that particular inputSolutionFile. It is useful for zooming in on the results of the best score summary.
  • Worst Score Difference Percentage: Shows the return on investment (ROI) per inputSolutionFile for each solver configuration if you'd upgrade from the worst solver configuration for that particular inputSolutionFile. It is useful for visualizing the return on investment (ROI) to decision makers.
  • Average Calculation Count: Shows the score calculation speed: the average calculation count per second per problem scale for each solver configuration. It is useful for comparing different score calculators and/or score rule implementations (presuming that the solver configurations do not differ otherwise). Also useful to measure the scalability cost of an extra constraint.
  • Time Spend: Shows the time spend per inputSolutionFile for each solver configuration. This is pointless if it is benchmarking against a fixed time limit. It is useful for visualizing the performance of construction heuristics (presuming that no other solver phases are configured).
  • Time Spend Scalability:Shows the time spend per problem scale for each solver configuration. This is pointless if it is benchmarking against a fixed time limit. It is useful for extrapolating the scalability of construction heuristics (presuming that no other solver phases are configured).
  • Best Score Per Time Spend: Shows the best score per time spend for each solver configuration. This is pointless if it is benchmarking against a fixed time limit. It is useful for visualizing trade-off between the best score versus the time spend for construction heuristics (presuming that no other solver phases are configured).

14.3.3. Statistics per Data Set

During the enabling of a problem statistic, the benchmarker supports outputting problem statistics as graphs and CSV (comma separated values) files to the benchmarkDirectory.
To configure graph and CSV output of a statistic, just add a problemStatisticType line:
  <plannerBenchmark>
  <benchmarkDirectory>local/data/nqueens/solved</benchmarkDirectory>
  <inheritedSolverBenchmark>
    <problemBenchmarks>
      ...
      <problemStatisticType>BEST_SOLUTION_CHANGED</problemStatisticType>
      <problemStatisticType>CALCULATE_COUNT_PER_SECOND</problemStatisticType>
    </problemBenchmarks>
    ...
  </inheritedSolverBenchmark>
  ...
  </plannerBenchmark>
Multiple problemStatisticType elements are allowed. Some statistic types might influence performance and benchmark results noticeably. The following types are supported:
  • Best Score Over Time Statistic: To see how the best score evolves over time, add BEST_SOLUTION_CHANGED as a problemStatisticType.
     
         <problemBenchmarks>
          ...
          <problemStatisticType>BEST_SCORE</problemStatisticType>
         </problemBenchmarks>
    
  • Step Score Over Time Statistic: Compares the step score statistics with the best score statistics. If the score hits local optima, the solver should take deteriorating steps to escape it. But it should not deteriorate too much either.
     
        <problemBenchmarks>
          ...
          <problemStatisticType>STEP_SCORE</problemStatisticType>
        </problemBenchmarks>
    
  • Calculate Count Per Second Statistic: To see how fast the scores are calculated.
     
        <problemBenchmarks>
          ...
          <problemStatisticType>CALCULATE_COUNT_PER_SECOND</problemStatisticType>
        </problemBenchmarks>
    
  • Best Solution Mutation Over Time Statistic: To see how much each new best solution differs from the previous best solution by counting the number of planning variables which have a different value.
        <problemBenchmarks>
          ...
          <problemStatisticType>BEST_SOLUTION_MUTATION</problemStatisticType>
        </problemBenchmarks>
    
  • Move Count Per Step Statistic: To see how the selected and accepted move count per step evolves over time.
     
        <problemBenchmarks>
          ...
          <problemStatisticType>MOVE_COUNT_PER_STEP</problemStatisticType>
        </problemBenchmarks>
    

14.3.4. Ranking the Solvers

The benchmark report automatically ranks the solvers. The Solver with rank 0 is called the favorite Solver: it performs best overall, but it might not be the best on every problem. It's recommended to use that favorite Solver in production.
However, there are different ways of ranking the solvers. You can configure how:
<plannerBenchmark>
  ...
  <solverBenchmarkRankingType>TOTAL_SCORE</solverBenchmarkRankingType>
  ...
</plannerBenchmark>
The following solverBenchmarkRankingTypes are supported:
  • TOTAL_SCORE (default): Maximize the overall score, so minimize the overall cost if all solutions would be executed.
  • WORST_SCORE: Minimize the worst case scenario.
  • TOTAL_RANKING: Maximize the overall ranking. Use this if your datasets differ greatly in size or difficulty, producing a difference in Score magnitude.
You can also use a custom ranking, by implementing a Comparator:
 <solverBenchmarkRankingComparatorClass>...TotalScoreSolverBenchmarkRankingComparator</solverBenchmarkRankingComparatorClass>
Or a weight factory:
 <solverBenchmarkRankingWeightFactoryClass>...TotalRankSolverBenchmarkRankingWeightFactory</solverBenchmarkRankingWeightFactoryClass>

14.4. Advanced Benchmarking

14.4.1. Benchmarking Performance Tricks

Parallel benchmarking on multiple threads
If you have multiple processors available on your computer, you can run multiple benchmarks in parallel on multiple threads to get your benchmarks results faster:
<plannerBenchmark>
  ...
  <parallelBenchmarkCount>AUTO</parallelBenchmarkCount>
  ...
</plannerBenchmark>

Warning

Running too many benchmarks in parallel will affect the results of benchmarks negatively. Leave some processors unused for garbage collection and other processes.
We tweak parallelBenchmarkCount AUTO to maximize the reliability and efficiency of the benchmark results.
The following parallelBenchmarkCounts are supported:
  • 1 (default): Run all benchmarks sequentially.
  • AUTO: Let Planner decide how many benchmarks to run in parallel. This formula is based on experience. It's recommended to prefer this over the other parallel enabling options.
  • Static number: The number of benchmarks to run in parallel.
    <parallelBenchmarkCount>2</parallelBenchmarkCount>
  • JavaScript formula: Formula for the number of benchmarks to run in parallel. It can use the variable availableProcessorCount. For example:
    <parallelBenchmarkCount>(availableProcessorCount / 2) + 1</parallelBenchmarkCount>

Note

The parallelBenchmarkCount is always limited to the number of available processors. If it's higher, it will be automatically decreased.

Note

In the future, we will also support multi-JVM benchmarking. This feature is independent of or multi-JVM solving.

14.4.2. Template Based and Matrix Benchmarking

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 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/optaplanner/examples/cloudbalancing/benchmark/cloudBalancingBenchmarkConfigTemplate.xml.ftl");
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();

Chapter 15. Repeated Planning

15.1. Introduction to Repeated Planning

There are three types of situations that might change during the execution of a solution. They are depicted below:
  • Unforeseen fact changes. Situations like injuries, technical delays, and sickness may alter the facts of a solution. For instances like this, use backup planning.
  • Unknown long term future facts. Situations where long term scheduling is not reliable (like distant appointments) should use continuous planning.
  • Constantly changing planning facts. Situations where plans change continuously (like walk-in appointments) should use real-time planning.
The Business Resource Planner algorithm supports planning a solution that is even partially planned, known as repeated planning. This algorithm supports the changing of facts and lowers the risks of poor planning.

15.2. Backup Planning

Backup planning is the technique of adding extra score constraints to create space in the planning for when things go wrong. This creates a backup for the plan.
For example, a hospital employee assigned as a spare worker once for every ten shifts should have one hospital bed available in the working department. This allows the employee to take a patient during their spare shift time slot. If things go wrong, such as an employee calling in sick, then the planning facts of the original solution will change. That is, it will reassign the sick employee's patients and restart the planning. The solution will be restarted with a different score. Construction heuristics will fill in the newly created gaps with the spare employees data and metaheuristics will improve the match.

15.3. Continuous Planning

Continuous planning is the technique of planning one or more upcoming planning windows at the same time and repeating the process monthly, weekly, daily or hourly. It is ideal to plan only a fixed number of upcoming planning windows for easier control.
Past planning windows are unlikely to change; accordingly, the first upcoming planning window is considered stable and unchanging. However, the later planning windows are likely to change during the next planning efforts. Distant future planning windows are not planned at all.
Past planning windows have only immovable planning entities; that is, the planning entities can no longer be changed , but some of them are still needed in the score calculation. These entities might affect some of the score constraints that apply on the upcoming planning entities. This is useful for constraints used to stop employees from being over rostered at work if they have worked too many consecutive days in a row.
Planning entities can be semi-immovable; that is, they can be changed but occur a certain score penalty if they differ from their original place. This is useful to reserve data slots for scheduled events unless emergency issues arise that call for change.
Business Resource Planner 6.0 Continuous Planning Chart for Patient Admission Schedule.

Figure 15.1. Continuous Planning Diagram

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 make some planning entities immovable, simply add an entity SelectionFilter that returns true if an entity is movable and false if it is immovable.
public class MovableShiftAssignmentSelectionFilter implements SelectionFilter<ShiftAssignment> {

    public boolean accept(ScoreDirector scoreDirector, ShiftAssignment shiftAssignment) {
        ShiftDate shiftDate = shiftAssignment.getShift().getShiftDate();
        NurseRoster nurseRoster = (NurseRoster) scoreDirector.getWorkingSolution();
        return nurseRoster.getNurseRosterInfo().isInPlanningWindow(shiftDate);
    }

}
And configure it like this:
@PlanningEntity(movableEntitySelectionFilter = MovableShiftAssignmentSelectionFilter.class)
public class ShiftAssignment {
    ...
}

Warning

Custom MoveListFactory and MoveIteratorFactory implementations must make sure that they don't move immovable entities.

15.4. Real-time Planning

To do real-time planning, first combine backup planning and continuous planning with short planning windows to lower the burden of real-time planning.
Business Resource Planner Real-Time Vehicle Routing Planning Chart.

Figure 15.2. 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;
                    }
                }
            }
        });
    }

Warning

Any change on the problem facts or planning entities in a ProblemFactChange must be done on the instances of the Solution of scoreDirector.getWorkingSolution(). Note that these are not the same entity instances as in the bestSolution (or therefore your user interface): they are clones.

Warning

Any change on the problem facts or planning entities in a ProblemFactChange must be told to the ScoreDirector.

Note

Many types of changes can leave a planning entity uninitialized, resulting in a partially initialized solution. That's fine, as long as the first solver phase can handle it. All construction heuristics solver phases can handle that, so it's recommended to configure such a SolverPhase as the first phase.
In essence, the Solver will stop, run the ProblemFactChange and restart. Each SolverPhase will run again. Each configured Termination (except terminateEarly) will reset. This means the construction heuristic will run again, but because little or no planning variables will be uninitialized (unless you have a nullable planning variable, this won't take long.
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.

Chapter 16. Technology Preview

16.1. Technical Preview

Warning

Business Resource Planner is included with this release as a technical preview. Technical previews are included as a preview of the software only. They are not supported in production by Red Hat, may not be functionally complete, and are not intended to be deployed in a production environment.

Note

These features are included to provide customers with early access to upcoming product innovations, enabling them to test functionality and provide feedback during the development process.

Appendix A. Revision History

Revision History
Revision 1.0.0-7Mon Sep 22 2014Vikram Goyal
Built from Content Specification: 22699, Revision: 684314 by vigoyal

Legal Notice

Copyright © 2014 Red Hat, Inc.
This document is licensed by Red Hat under the Creative Commons Attribution-ShareAlike 3.0 Unported License. If you distribute this document, or a modified version of it, you must provide attribution to Red Hat, Inc. and provide a link to the original. If the document is modified, all Red Hat trademarks must be removed.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat Software Collections is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.