Every organization faces planning problems: provide products or services with a limited set of
constrained resources (employees, assets, time and money). OptaPlanner optimizes such planning
to do more business with less resources. This is known as Constraint Satisfaction Programming
(which is part of the discipline Operations Research).

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

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

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

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

1.3.2. A planning problem has (hard and soft) constraints

Usually, a planning problem has at least 2 levels of constraints:

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

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

Some problems have positive constraints too:

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

Some basic problems (such as N Queens) only have hard constraints. Some problems have 3 or more levels of
constraints, for example 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 OptaPlanner, score constraints are written in an Object Orientated language, such as
Java code or Drools rules. Such code is easy, flexible and scalable.

1.3.3. A planning problem has a huge search space

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

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.

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

OptaPlanner 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 OptaPlanner, it is easy to switch the
optimization algorithm, by changing the solver configuration in a few lines of XML or code.

1.5.2. Backwards compatibility

OptaPlanner separates its API and implementation:

Public API: All classes in the package namespace org.optaplanner.core.api are 100% backwards compatible in future releases.

Impl classes: All classes in the package namespace org.optaplanner.core.impl are not backwards compatible: they might change in future
releases. The recipe called UpgradeFromPreviousVersionRecipe.txt
describes every such change and on how to quickly deal with it when upgrading to a newer version. That recipe
file is included in every release zip.

XML configuration: The XML solver configuration is backwards compatible
for all elements, except for elements that require the use of non public API classes. The XML solver
configuration is defined by the classes in the package namespace org.optaplanner.core.config.

Note

This documentation covers some impl classes too. Those documented impl classes are reliable and safe to
use (unless explicitly marked as experimental in this documentation), but we're just entirely comfortable yet to
write their signatures in stone.

1.5.3. Community and support

Public questions are welcome on our
community forum. Bugs and feature requests are welcome in our issue tracker. Pull requests are very welcome on
GitHub and get priority treatment! By open sourcing your improvements, you 'll benefit from our peer review and
from our improvements made on top of your improvements.

2.1. Cloud balancing tutorial

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

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.

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

2.1.2. Problem size

2computers-6processes has 2 computers and 6 processes with a search space of 64.
3computers-9processes has 3 computers and 9 processes with a search space of 10^4.
4computers-012processes has 4 computers and 12 processes with a search space of 10^7.
100computers-300processes has 100 computers and 300 processes with a search space of 10^600.
200computers-600processes has 200 computers and 600 processes with a search space of 10^1380.
400computers-1200processes has 400 computers and 1200 processes with a search space of 10^3122.
800computers-2400processes has 800 computers and 2400 processes with a search space of 10^6967.

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.

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.

2.1.6.2. The class Process

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

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

2.1.6.3. The class CloudBalance

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

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

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

2.1.7. Score configuration

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

Of course, Planner needs to be told about these domain-specific score constraints. There are several ways to
implement such a score function:

Easy Java

Incremental Java

Drools

Let's take a look at 2 different implementations:

2.1.7.1. Easy Java score configuration

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

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.

2.1.7.2. Drools score configuration

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

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 OptaPlanner with competitive software and
academic research.

3.2. Basic examples

3.2.1. N queens

3.2.1.1. Problem statement

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:

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:

Figure 3.1. A wrong solution for the 4 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:

Figure 3.2. A correct solution for the 4 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.

3.2.1.2. Problem size

4queens has 4 queens with a search space of 256.
8queens has 8 queens with a search space of 10^7.
16queens has 16 queens with a search space of 10^19.
32queens has 32 queens with a search space of 10^48.
64queens has 64 queens with a search space of 10^115.
256queens has 256 queens with a search space of 10^616.

The 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.2.1.3. 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:

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.

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)

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.

3.2.3.2. Problem size

dj38 has 38 cities with a search space of 10^58.
europe40 has 40 cities with a search space of 10^62.
st70 has 70 cities with a search space of 10^126.
pcb442 has 442 cities with a search space of 10^1166.
lu980 has 980 cities with a search space of 10^2927.

3.2.3.3. Problem difficulty

Despite TSP's simple definition, the problem is surprisingly hard to solve. Because it's an NP-hard
problem (like most planning problems), the optimal solution for a specific problem dataset can change a lot when
that problem dataset is slightly altered:

3.2.4. Dinner party

3.2.4.1. Problem statement

Miss Manners is throwing another dinner party.

This time she invited 144 guests and prepared 12 round tables with 12 seats each.

Every guest should sit next to someone (left and right) of the opposite gender.

And that neighbour should have at least one hobby in common with the guest.

At every table, there should be 2 politicians, 2 doctors, 2 socialites, 2 coaches, 2 teachers and 2
programmers.

And the 2 politicians, 2 doctors, 2 coaches and 2 programmers shouldn't be the same kind at a
table.

Drools Expert also has the normal Miss Manners example (which is much smaller) and employs an exhaustive
heuristic to solve it. OptaPlanner's implementation is far more scalable because it uses heuristics to find the
best solution and Drools Expert to calculate the score of each solution.

3.3.1.3. Domain model

3.3.2. Machine reassignment (Google ROADEF 2012)

3.3.2.1. Problem statement

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

Hard constraints:

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

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

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

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

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

Soft constraints:

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

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

Process move cost: A process has a move cost.

Service move cost: A service has a move cost.

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

3.3.3.2. Problem size

CVRP instances (without time windows):

A-n32-k5 has 1 depots, 5 vehicles and 31 customers with a search space of 10^46.
A-n33-k5 has 1 depots, 5 vehicles and 32 customers with a search space of 10^48.
A-n33-k6 has 1 depots, 6 vehicles and 32 customers with a search space of 10^48.
A-n34-k5 has 1 depots, 5 vehicles and 33 customers with a search space of 10^50.
A-n36-k5 has 1 depots, 5 vehicles and 35 customers with a search space of 10^54.
A-n37-k5 has 1 depots, 5 vehicles and 36 customers with a search space of 10^56.
A-n37-k6 has 1 depots, 6 vehicles and 36 customers with a search space of 10^56.
A-n38-k5 has 1 depots, 5 vehicles and 37 customers with a search space of 10^58.
A-n39-k5 has 1 depots, 5 vehicles and 38 customers with a search space of 10^60.
A-n39-k6 has 1 depots, 6 vehicles and 38 customers with a search space of 10^60.
A-n44-k7 has 1 depots, 7 vehicles and 43 customers with a search space of 10^70.
A-n45-k6 has 1 depots, 6 vehicles and 44 customers with a search space of 10^72.
A-n45-k7 has 1 depots, 7 vehicles and 44 customers with a search space of 10^72.
A-n46-k7 has 1 depots, 7 vehicles and 45 customers with a search space of 10^74.
A-n48-k7 has 1 depots, 7 vehicles and 47 customers with a search space of 10^78.
A-n53-k7 has 1 depots, 7 vehicles and 52 customers with a search space of 10^89.
A-n54-k7 has 1 depots, 7 vehicles and 53 customers with a search space of 10^91.
A-n55-k9 has 1 depots, 9 vehicles and 54 customers with a search space of 10^93.
A-n60-k9 has 1 depots, 9 vehicles and 59 customers with a search space of 10^104.
A-n61-k9 has 1 depots, 9 vehicles and 60 customers with a search space of 10^106.
A-n62-k8 has 1 depots, 8 vehicles and 61 customers with a search space of 10^108.
A-n63-k10 has 1 depots, 10 vehicles and 62 customers with a search space of 10^111.
A-n63-k9 has 1 depots, 9 vehicles and 62 customers with a search space of 10^111.
A-n64-k9 has 1 depots, 9 vehicles and 63 customers with a search space of 10^113.
A-n65-k9 has 1 depots, 9 vehicles and 64 customers with a search space of 10^115.
A-n69-k9 has 1 depots, 9 vehicles and 68 customers with a search space of 10^124.
A-n80-k10 has 1 depots, 10 vehicles and 79 customers with a search space of 10^149.
F-n135-k7 has 1 depots, 7 vehicles and 134 customers with a search space of 10^285.
F-n45-k4 has 1 depots, 4 vehicles and 44 customers with a search space of 10^72.
F-n72-k4 has 1 depots, 4 vehicles and 71 customers with a search space of 10^131.

CVRPTW instances (with time windows):

Solomon_025_C101 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_025_C201 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_025_R101 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_025_R201 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_025_RC101 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_025_RC201 has 1 depots, 25 vehicles and 25 customers with a search space of 10^34.
Solomon_100_C101 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Solomon_100_C201 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Solomon_100_R101 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Solomon_100_R201 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Solomon_100_RC101 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Solomon_100_RC201 has 1 depots, 25 vehicles and 100 customers with a search space of 10^200.
Homberger_0200_C1_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0200_C2_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0200_R1_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0200_R2_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0200_RC1_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0200_RC2_2_1 has 1 depots, 50 vehicles and 200 customers with a search space of 10^460.
Homberger_0400_C1_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0400_C2_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0400_R1_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0400_R2_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0400_RC1_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0400_RC2_4_1 has 1 depots, 100 vehicles and 400 customers with a search space of 10^1040.
Homberger_0600_C1_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0600_C2_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0600_R1_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0600_R2_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0600_RC1_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0600_RC2_6_1 has 1 depots, 150 vehicles and 600 customers with a search space of 10^1666.
Homberger_0800_C1_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_0800_C2_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_0800_R1_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_0800_R2_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_0800_RC1_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_0800_RC2_8_1 has 1 depots, 200 vehicles and 800 customers with a search space of 10^2322.
Homberger_1000_C110_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.
Homberger_1000_C210_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.
Homberger_1000_R110_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.
Homberger_1000_R210_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.
Homberger_1000_RC110_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.
Homberger_1000_RC210_1 has 1 depots, 250 vehicles and 1000 customers with a search space of 10^3000.

3.3.3.3. Domain model

The vehicle routing with timewindows domain model makes heavily use of shadow variables. This allows it to express its constraints more
naturally, because properties such as arrivalTime and departureTime, are
directly available on the domain model.

3.3.3.4. Road distances instead of air distances

In the real world, vehicles can't follow a straight line from location to location: they have to use roads
and highways. From a business point of view, this matters a lot:

For the optimization algorithm, this doesn't matter much, as long as the distance between 2 points can be
looked up (and are preferably precalculated). The road cost doesn't even need to be a distance, it can also be
travel time, fuel cost, or a weighted function of those. There are several Google Maps like technologies
available to precalculate road costs, such as GraphHopper
(embeddable, offline Java engine) and Open
MapQuest (web service). There are also several technologies to render it, such as Leaflet.

Take special care that the road costs between 2 points use the same optimization criteria as the one used
in OptaPlanner. For example, GraphHopper etc will by default return the fastest route, not the shortest route.
Don't use the km (or miles) distances of the fastest GPS routes to optimize the shortest trip in OptaPlanner:
this leads to a suboptimal solution as shown below:

Contrary to popular belief, most users don't want the shortest route: they want the fastest route instead.
They prefer highways over normal roads. They prefer normal roads over dirt roads. In the real world, the fastest
and shortest route are rarely the same.

3.3.4. Project job scheduling

3.3.4.1. Problem statement

Schedule all jobs in time and execution mode to minimize project delays. Each job is part of a project. A
job can be executed in different ways: each way is an execution mode that implies a different duration but also
different resource usages. This is a form of flexible job shop scheduling.

Hard constraints:

Job precedence: a job can only start when all its predecessor jobs are finished.

Resource capacity: do not use more resources then available.

Resources are local (shared between jobs of the same project) or global (shared between all
jobs)

Resource are renewable (capacity available per day) or nonrenewable (capacity available for all
days)

Medium constraints:

Total project delay: minimize the duration (makespan) of each project.

Soft constraints:

Total makespan: minimize the duration of the whole multi-project schedule.

3.3.5. Hospital bed planning (PAS - Patient admission scheduling)

3.3.5.1. Problem statement

Assign each patient (that will come to the hospital) into a bed for each night that the patient will stay
in the hospital. Each bed belongs to a room and each room belongs to a department. The arrival and departure
dates of the patients is fixed: only a bed needs to be assigned for each night.

This problem features overconstrained datasets.

Hard constraints:

2 patients must not be assigned to the same bed in the same night. Weight: -1000hard *
conflictNightCount.

A room can have a gender limitation: only females, only males, the same gender in the same night or no
gender limitation at all. Weight: -50hard * nightCount.

A department can have a minimum or maximum age. Weight: -100hard *
nightCount.

A patient can require a room with specific equipment(s). Weight: -50hard *
nightCount.

Medium constraints:

Assign every patient to a bed, unless the dataset is overconstrained. Weight: -1medium *
nightCount.

Soft constraints:

A patient can prefer a maximum room size, for example if he/she want a single room. Weight:
-8soft * nightCount.

A patient is best assigned to a department that specializes in his/her problem. Weight:
-10soft * nightCount.

A patient is best assigned to a room that specializes in his/her problem. Weight: -20soft *
nightCount.

3.4.1.3. Domain model

Below you can see the main examination domain classes:

Figure 3.3. Examination domain class diagram

Notice that we've split up the exam concept into an Exam class and a
Topic class. The Exam instances change during solving (this is the
planning entity class), when their period or room property changes. The Topic,
Period and Room instances never change during solving (these are problem
facts, just like some other classes).

3.4.3.2. Problem size

1-nl04 has 6 days, 4 teams and 12 matches with a search space of 10^9.
1-nl06 has 10 days, 6 teams and 30 matches with a search space of 10^30.
1-nl08 has 14 days, 8 teams and 56 matches with a search space of 10^64.
1-nl10 has 18 days, 10 teams and 90 matches with a search space of 10^112.
1-nl12 has 22 days, 12 teams and 132 matches with a search space of 10^177.
1-nl14 has 26 days, 14 teams and 182 matches with a search space of 10^257.
1-nl16 has 30 days, 16 teams and 240 matches with a search space of 10^354.
2-bra24 has 46 days, 24 teams and 552 matches with a search space of 10^917.
3-nfl16 has 30 days, 16 teams and 240 matches with a search space of 10^354.
3-nfl18 has 34 days, 18 teams and 306 matches with a search space of 10^468.
3-nfl20 has 38 days, 20 teams and 380 matches with a search space of 10^600.
3-nfl22 has 42 days, 22 teams and 462 matches with a search space of 10^749.
3-nfl24 has 46 days, 24 teams and 552 matches with a search space of 10^917.
3-nfl26 has 50 days, 26 teams and 650 matches with a search space of 10^1104.
3-nfl28 has 54 days, 28 teams and 756 matches with a search space of 10^1309.
3-nfl30 has 58 days, 30 teams and 870 matches with a search space of 10^1534.
3-nfl32 has 62 days, 32 teams and 992 matches with a search space of 10^1778.
4-super04 has 6 days, 4 teams and 12 matches with a search space of 10^9.
4-super06 has 10 days, 6 teams and 30 matches with a search space of 10^30.
4-super08 has 14 days, 8 teams and 56 matches with a search space of 10^64.
4-super10 has 18 days, 10 teams and 90 matches with a search space of 10^112.
4-super12 has 22 days, 12 teams and 132 matches with a search space of 10^177.
4-super14 has 26 days, 14 teams and 182 matches with a search space of 10^257.
5-galaxy04 has 6 days, 4 teams and 12 matches with a search space of 10^9.
5-galaxy06 has 10 days, 6 teams and 30 matches with a search space of 10^30.
5-galaxy08 has 14 days, 8 teams and 56 matches with a search space of 10^64.
5-galaxy10 has 18 days, 10 teams and 90 matches with a search space of 10^112.
5-galaxy12 has 22 days, 12 teams and 132 matches with a search space of 10^177.
5-galaxy14 has 26 days, 14 teams and 182 matches with a search space of 10^257.
5-galaxy16 has 30 days, 16 teams and 240 matches with a search space of 10^354.
5-galaxy18 has 34 days, 18 teams and 306 matches with a search space of 10^468.
5-galaxy20 has 38 days, 20 teams and 380 matches with a search space of 10^600.
5-galaxy22 has 42 days, 22 teams and 462 matches with a search space of 10^749.
5-galaxy24 has 46 days, 24 teams and 552 matches with a search space of 10^917.
5-galaxy26 has 50 days, 26 teams and 650 matches with a search space of 10^1104.
5-galaxy28 has 54 days, 28 teams and 756 matches with a search space of 10^1309.
5-galaxy30 has 58 days, 30 teams and 870 matches with a search space of 10^1534.
5-galaxy32 has 62 days, 32 teams and 992 matches with a search space of 10^1778.
5-galaxy34 has 66 days, 34 teams and 1122 matches with a search space of 10^2041.
5-galaxy36 has 70 days, 36 teams and 1260 matches with a search space of 10^2324.
5-galaxy38 has 74 days, 38 teams and 1406 matches with a search space of 10^2628.
5-galaxy40 has 78 days, 40 teams and 1560 matches with a search space of 10^2951.

3.4.4. Cheap time scheduling

3.4.4.1. Problem statement

Schedule all tasks in time and on a machine to minimize power cost. Power prices differs in time. This is
a form of job shop scheduling.

Hard constraints:

Start time limits: each task must start between its earliest start and latest start limit.

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

Startup and shutdown: each machine must be active in the periods during which it has assigned tasks.
Between tasks it is allowed to be idle to avoid startup and shutdown costs.

Medium constraints:

Power cost: minimize the total power cost of the whole schedule.

Machine power cost: Each active or idle machine consumes power, which infers a power cost
(depending on the power price during that time).

Task power cost: Each task consumes power too, which infers a power cost (depending on the power
price during its time).

Machine startup and shutdown cost: Every time a machine starts up or shuts down, an extra cost is
inflicted.

In a typical project (following the Maven directory structure), that solverConfig XML file would be located
at
$PROJECT_DIR/src/main/resources/org/optaplanner/examples/nqueens/solver/nqueensSolverConfig.xml.
On some environments (OSGi, JBoss modules, ...), classpath resources in your jars might not be
available in by default to the classes in optaplanner's jars.

Alternatively, a SolverFactory can be created from a File, an
InputStream or a Reader with methods such as
SolverFactory.createFromXmlFile(). However, for portability reasons, a classpath resource is
recommended.

These various parts of a configuration are explained further in this manual.

OptaPlanner makes it relatively easy to switch optimization algorithm(s) just by
changing the configuration. There's even a Benchmarker utility which allows you to
play out different configurations against each other and report the most appropriate configuration for your use
case.

4.2.2. Solver configuration by Java API

A solver configuration can also be configured with the SolverConfig API. This especially
useful to change some values dynamically at runtime, for example to change the running time based on user input,
before building the Solver:

Every element in the solver configuration XML is available as a *Config class or a
property on a *Config class in the package namespace
org.optaplanner.core.config. These *Config classes are the Java
representation of the XML format and they also provide the user-friendly way to assemble the runtime components
(of the package namespace org.optaplanner.core.impl) into an efficient
Solver.

4.3. Model a planning problem

4.3.1. 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 can be
categorized as 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 real-time planning, 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:

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 chained planning variable.

In OptaPlanner 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, ...: OptaPlanner doesn't care.

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

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 a cached
problem fact to enrich the domain model for planning only.

4.3.3. Planning entity

4.3.3.1. Planning entity annotation

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.

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.

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

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 total of the historic
assignments 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.

4.3.3.2. Planning 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:

Important

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.

4.3.4. Planning variable

4.3.4.1. Planning variable annotation

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 to be annotated with the @PlanningVariable annotation,
which needs a non-empty valueRangeProviderRefs property.

The valueRangeProviderRefs property defines what are the possible planning values for
this planning variable. It references 1 or more @ValueRangeProviderid's.

4.3.4.2. Nullable planning 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 an 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:

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

Note

Using a nullable planning variable implies that your score calculation is responsible for punishing (or
even rewarding) variables with a null value.

Repeated planning (especially real-time planning) 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:

4.3.4.3. When is a planning variable considered initialized?

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 during construction
heuristics.

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.

4.3.5. Planning value and planning value range

4.3.5.1. Planning value

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 both 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
countable (for example row 1, 2, 3 or
4) or uncountable (for example any double between 0.0
and 1.0).

4.3.5.2. Planning value range provider

4.3.5.2.1. Introduction

The value range of a planning variable is defined with the @ValueRangeProvider
annotation. A @ValueRangeProvider annotation always has a property id,
which is referenced by the @PlanningVariable's property
valueRangeProviderRefs.

This annotation can be located on 2 types of methods:

On the Solution: All planning entities share the same value range.

On the planning entity: The value range differs per planning entity. This is less common.

The return type of that method can be 2 types:

Collection: The value range is defined by a Collection
(usually a List) of it's possible values.

ValueRange: The value range is defined by its bounds. This is less common.

4.3.5.2.2. ValueRangeProvider on the Solution

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 method which returns a Collection
(or a ValueRange). Any value from that Collection is a possible planning
value for this planning variable.

Important

4.3.5.2.3. ValueRangeProvider on the 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.

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

Warning

A value range on planning entity is not (yet) compatible with a chained variable, nor with generic swap moves.

4.3.5.2.4. ValueRangeFactory

Instead of a Collection, you can also return a ValueRange or
CountableValueRange, build by the ValueRangeFactory:

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

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:

Important

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.

4.3.5.4. Chained planning variable (TSP, VRP, ...)

Some use cases, such as TSP and Vehicle Routing, require chaining. This means the
planning entities point to each other and form a chain. By modeling the problem as a set of chains (instead of a
set of trees/loops), the search space is heavily reduced.

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:

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 Move's do chain correction to guarantee that
the model stays valid:

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
Vehicle):

The value range provider which holds the anchors, for example domicileList.

The value range provider which holds the initialized planning entities, for example
visitList.

4.3.6. Shadow variable

4.3.6.1. Introduction

A shadow variable is a variables who's correct value can be deduced from the state of the genuine planning
variables. Even though such a variable violates the principle of normalization by definition, in some use cases
it can be very practical to use a shadow variable, especially to express the constraints more naturally. For
example in vehicle routing with time windows: the arrival time at a customer for a vehicle can be calculated
based on the previously visited customers of that vehicle (and the known travel times between 2
locations).

When the customers for a vehicle change, the arrival time for each customer is automatically adjusted. For
more information, see the vehicle routing domain model.

From a score calculation perspective, a shadow variable is like any other planning variable. From an
optimization perspective, Planner effectively only optimizes the genuine variables (and mostly ignores the
shadow variables): it just assures that when a genuine variable changes, any dependent shadow variables are
changed accordingly.

There are several build-in shadow variables:

4.3.6.2. Bi-directional variable (inverse relation shadow variable)

2 variables are bi-directional if their instances always point to each other (unless they point to null).
So if A references B, then B references A.

To map a bi-directional relationship between 2 planning variables, annotate the master side as a normal (=
genuine) planning variable:

The sourceVariableName property is the name of the chained variable on the same entity
class.

4.3.6.4. Custom VariableListener

To update a shadow variable, Planner uses a VariableListener. To define a custom shadow
variable, write a custom VariableListener: implement the interface and annotate it on the
shadow variable that needs to change.

The variableName is the variable that triggers changes in the shadow
variable(s).

Note

If the class of the trigger variable is different than the shadow variable, also specify the
entityClass on @CustomShadowVariable.Source. In that case, make sure
that that entityClass is also properly configured as a planning entity class in the solver
config, or the VariableListener will simply never trigger.

Any class that has at least 1 shadow variable, is a planning entity class, even it has no genuine
planning variables.

For example, the VehicleUpdatingVariableListener assures that every Customer in a chain
has the same Vehicle, namely the chain's anchor.

Warning

A VariableListener can only change shadow variables. It must never change a genuine
planning variable or a problem fact.

Warning

Any change of a shadow variable must be told to the ScoreDirector.

4.3.7. Planning problem and planning solution

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

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

4.3.7.3. The getScore() and setScore() methods

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:

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

4.3.7.4. The getProblemFacts() method

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.

publicCollection<?extendsObject> getProblemFacts(){List<Object> facts =newArrayList<Object>(); facts.addAll(columnList); facts.addAll(rowList);//Do not add the planning entity's (queenList) because that will be done automaticallyreturn facts;}

All planning entities are automatically inserted into the Drools working memory. Do
not add them in the method getProblemFacts().

Note

A common mistake is to use facts.add(...) instead of
fact.addAll(...) for a Collection, which leads to score rules failing to
match because the elements of that Collection aren't in the Drools working memory.

The method getProblemFacts() is not called much: at most only once per solver phase per
solver thread.

4.3.7.5. Cached problem fact

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.

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.

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

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

The FieldAccessingSolutionCloner does not clone problem facts by default. If any of
your problem facts needs to be deep cloned for a planning clone, for example if the problem fact references a
planning entity or the planning solution, mark it with a @DeepPlanningClone
annotation:

In the example above, because SeatDesignation is a planning entity (which is deep
planning cloned automatically), SeatDesignationDependency must be deep planning cloned
too.

Alternatively, the @DeepPlanningClone annotation can also be used on a getter
method.

4.3.7.6.2. Custom cloning: Make Solution implement PlanningCloneable

If your Solution implements PlanningCloneable, Planner will automatically choose to clone it by calling
the method planningClone().

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

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

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

4.4. Use the Solver

4.4.1. The Solver interface

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.

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

For example in n queens, the method getBestSolution() will return an
NQueens instance with every Queen assigned to a
Row.

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

The solve(Solution) 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 solve(Solution) will be
changed by the Solver, but 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 solve(Solution), which means it's
a different instance.

Note

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

4.4.3. Environment mode: Are there bugs in my code?

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:

4.4.3.1. FULL_ASSERT

The FULL_ASSERT mode turns on all assertions (such as assert that the incremental score calculation is
uncorrupted for each move) to fail-fast on a bug in a Move implementation, a score rule, the rule engine itself,
...

This mode is reproducible (see the reproducible mode). It is also intrusive because it calls the method
calculateScore() more frequently than a non assert mode.

The FULL_ASSERT mode is horribly slow (because it doesn't rely on delta based score calculation).

4.4.3.2. NON_INTRUSIVE_FULL_ASSERT

The NON_INTRUSIVE_FULL_ASSERT turns on several assertions to fail-fast on a bug in a Move implementation,
a score rule, the rule engine itself, ...

This mode is reproducible (see the reproducible mode). It is non-intrusive because it does not call the
method calculateScore() more frequently than a non assert mode.

The NON_INTRUSIVE_FULL_ASSERT mode is horribly slow (because it doesn't rely on delta based score
calculation).

4.4.3.3. FAST_ASSERT

The FAST_ASSERT mode 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 a Move implementation, a score rule, the rule engine itself,
...

This mode is reproducible (see the reproducible mode). It is also intrusive because it calls the method
calculateScore() more frequently than a non assert mode.

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.

4.4.3.4. REPRODUCIBLE (default)

The reproducible mode is the default mode because it is recommended during development. In this mode, 2
runs in the same OptaPlanner version will execute the same code in the same order. Those 2
runs will have the same result at every step, except if the note below applies. This enables you to
reproduce bugs consistently. It also allows you to benchmark certain refactorings (such as a score constraint
performance 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
spent 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 spent termination with
step count termination.

The reproducible mode is slightly slower than the production mode. If your production environment requires
reproducibility, use this mode in production too.

In practice, this mode uses the default, fixed random seed if
no seed is specified, and it also disables certain concurrency optimizations (such as work stealing).

4.4.3.5. PRODUCTION

The production mode is the fastest, but it is not reproducible. It is recommended for a production
environment, unless reproducibility is required.

In pratice, this mode uses no fixed random seed if no seed is
specified.

4.4.4. Logging level: What is the Solver doing?

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

Everything is logged to SLF4J, 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):

4.4.5. Random number generator

Many heuristics and metaheuristics depend on a pseudorandom number generator for move selection, to resolve
score ties, probability based move acceptance, ... During solving, the same Random instance is
reused to improve reproducibility, performance and uniform distribution of random values.

To change the random seed of that Random instance, specify a
randomSeed:

<solver><randomSeed>0</randomSeed> ...</solver>

To change the pseudorandom number generator implementation, specify a randomType:

5.1. Score terminology

5.1.1. What is a score?

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.

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:

5.1.2. Score constraint signum (positive or negative)

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.

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

5.1.3. Score constraint weight

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 make1 "unhappy driver" constraint match count as much as 2 "fuel
tank usage" constraint matches:

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: a nurse that
requests a free day pays a higher weight on New Year's eve than on a normal day.

Put a good weight on a constraint can be a difficult analytical decision, because it's about making choices
and tradeoffs with other constraints. However, a non-accurate weight is less damaging than not good
algorithms:

Furthermore, it is often useful to allow the planning end-user to recalibrate penalty weights in his/her
user interface, as demonstrated in the exam timetabling example.

The weight of a constraint match can be dynamically based on the planning entities involved. For example in
cloud balance: the weight of the soft constraint match for an active Computer is the
cost of that Computer.

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

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:

Note

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

Three or more score levels are also 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.

5.1.5. Pareto scoring (AKA 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.

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

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.

5.1.6. Combining score techniques

All the score techniques mentioned above, can be combined seamlessly:

5.1.7. Score interface

A score is represented by the Score interface, which naturally extends
Comparable:

publicinterfaceScore<...>extendsComparable<...>{...}

The Score implementation to use depends on your use case. Your score might not
efficiently fit in a single long value. Planner has several build-in Score
implementations, but you can implement a custom Score too. Most use cases tend to use the
build-in HardSoftScore.

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:

HARD_MEDIUM_SOFT_LONG: Uses HardMediumSoftLongScore which has
long values instead of int values.

5.2.4. 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 with 2 hard levels
and 3 soft levels, the score can be -123/-456/-789/-012/-345.

Every score calculation type can use any Score definition. For example, easy Java score calculation can
output a HardSoftScore.

All score calculation types are Object Orientated and can reuse existing Java code.

Important

The score calculation should be read-only: it should not change the planning entities or the problem facts
in any way. For example, it must not call a setter method on a planning entity in a Drools score rule's RHS.
This does not apply to logically inserted objects, which can be changed by the score rules
who logically inserted them in the first place.

OptaPlanner will not recalculate the score of a Solution if it can predict it (unless
an environmentMode assertion is enabled). For example, after a winning
step is done, there is no need to calculate the score because that move was done and undone earlier. As a
result, there's no guarantee that such changes applied during score calculation are actually done.

5.3.2. Easy Java score calculation

An easy 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

Optionally, to explain a score with ScoreDirector.getConstraintMatchTotals() or to get
better output when the IncrementalScoreCalculator is corrupted in
environmentModeFAST_ASSERT or FULL_ASSERT, implement the
interface ConstraintMatchAwareIncrementalScoreCalculator too:

In a typical project (following the Maven directory structure), that DRL file would be located at
$PROJECT_DIR/src/main/resources/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl
(even for a war project).

Note

The <scoreDrl> element expects a classpath resource, as defined by
ClassLoader.getResource(String), it does not accept a File, nor an
URL, nor a webapp resource. See below to use a File instead.

Warning

For portability reasons, a classpath resource is recommended over a File. An application build on one
computer, but used on another computer, might not find the file on the same location. Worse, if they use a
different Operating System, it's hard to choose a portable file path.

This score rule will fire once for every 2 queens with the same rowIndex. 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:

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.

Note

The variable kcontext is a magic variable in Drools Expert. The scoreHolder's method
uses it to do incremental score calculation correctly and to create a ConstraintMatch
instance.

5.3.4.4. Weighing score rules

A ScoreHolder instance is asserted into the KieSession as a global
called scoreHolder. Your score rules need to (directly or indirectly) update that
instance.

Most use cases will also weigh their constraint types or even their matches differently, by using a
specific weight for each constraint match.

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:

global HardSoftScoreHolder scoreHolder;
// 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
$room : Room($capacity : capacity)
$lecture : Lecture(room == $room, studentSize > $capacity, $studentSize : studentSize)
then
scoreHolder.addSoftConstraintMatch(kcontext, ($capacity - $studentSize));
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
scoreHolder.addSoftConstraintMatch(kcontext, -2);
end

5.3.5. InitializingScoreTrend

The InitializingScoreTrend specifies how the Score will change as more and more variables
are initialized (while the already initialized variables don't change). Some optimization algorithms (such
Construction Heuristics and Exhaustive Search) run faster if they have such information.

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

5.4. Score calculation performance tricks

5.4.1. Overview

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.

5.4.2. Average calculation count per second

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

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.

5.4.3. Incremental score calculation (with delta's)

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.

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

5.4.4. Avoid calling remote services during score calculation

Do not call remote services in your score calculation (except if you're bridging
EasyScoreCalculator 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 cached problem facts.

5.4.5. Pointless constraints

If you know a certain constraint can never be broken (or it is always 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.

5.4.6. Build-in hard constraint

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

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

Note

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

5.4.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 the latest Java version. For example, in the past 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.

5.4.8. Score trap

Make sure that none of your score constraints cause a score trap. A trapped score constraint uses the same
weight for different constraint matches, when it could just as easily use a different weight. It effectively lumps
its constraint matches together, which creates a flatlined score function for that constraint. This can cause a
solution state in which several moves need to be done to resolve or lower the weight of that single constraint.
Some examples of score traps:

If you need 2 doctors at each table, but you're only moving 1 doctor at a time. So 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 that score constraint in the score function.

2 exams needs to be conducted at the same time, but you're only move 1 exam at a time. So the solver has
a disincentive move one of those exams to another timeslot without moving the other in the same move. Add a
course-grained move that moves both exams at the same time.

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:

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.

Note

Avoiding score traps does not mean that your score function should be smart enough to avoid local optima.
Leave it to the optimization algorithms to deal with the local optima.

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.

There are several ways to deal with a score trap:

Improve the score constraint to make a distinction in the score weight. For example: penalize
-1hard for every missing CPU, instead of just -1hard if any CPU is
missing.

If changing the score constraint is not allowed from the business perspective, add a lower score level
with a score constraint that makes such a distinction. For example: penalize -1subsoft for
every missing CPU, on top of -1hard if any CPU is missing. The business ignores the subsoft
score level.

Add course-grained moves and union select them with the existing fine-grained moves. A course-grained
move effectively does multiple moves to directly get out of a score trap with a single move. For example: move
multiple items from the same container to another container.

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

5.4.10. Fairness score constraints

Some use cases have a business requirement to provide a fair schedule (usually as a soft score constraint),
for example:

Implementing such a constraint can seem difficult (especially because there are different ways to formalize
fairness), but usually the squared workload implementation behaves most desirable: for each
employee/asset, count the workload w and subtract the square w² from the
score.

As shown above, the squared workload implementation guarantees that if you select 2
employees from a given solution and make their distribution between those 2 employees more fair that the resulting
new solution will have a better overall score.

5.5. Explaining the score: using score calculation outside the Solver

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:

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.

Calculating the number of possible solutions depends on the design of the domain model:

Note

This search space size calculation includes infeasible solutions (if they can be represented by the model),
because:

The optimal solution might be infeasible.

There are many types of hard constraints which cannot be incorporated in the formula practically. For
example in Cloud Balancing, try incorporating the CPU capacity constraint in the formula.

Even in cases were adding some of the hard constraints in the formula is practical, for example Course
Scheduling, the resulting search space is still huge.

An algorithm that checks every possible solution (even with pruning such as in Branch And Bound) 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, Late Acceptance, ...) usually perform best for real-world
problems given real-world time limitations.

6.2. Does 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, OptaPlanner focuses on finding the best solution in available
time. In realistic, independent competitions, OptaPlanner 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 issues cannot be
mitigated by hardware purchases later on. Start testing with a production sized dataset as soon as possible. Don't
asses quality on small datasets (unless production encounters only such datasets). Instead, solve a production sized
dataset and compare the results of longer executions, different algorithms and - if available - the human
planner.

6.3. Architecture overview

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

A rule engine such as Drools Expert 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.

6.5. Which optimization algorithms should I use?

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:

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:

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

6.8. 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
Phase 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 spent solving and the estimated entire solving time of the Solver or
Phase.

This termination should not be applied to Construction Heuristics, because they only update the best
solution at the end. Therefore it might be better to configure it on a specific Phase (such as
<localSearch>), instead of on the Solver itself.

Note

This Termination will most likely sacrifice perfect reproducibility (even with
environmentModeREPRODUCIBLE) because the available CPU time differs
frequently between runs:

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.

6.8.3. BestScoreTermination

Terminates when a certain score has been reached. You can use this Termination if you
know the perfect score, for example for 4 queens (which uses a SimpleScore):

6.8.6. UnimprovedStepCountTermination

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 Phase (such as
<localSearch>), not for the Solver itself.

6.8.7. Combining multiple Terminations

Terminations can be combined, for example: terminate after 100 steps or if a score of
0 has been reached:

This example ensures it doesn't just terminate after finding a feasible solution, but also completes any
obvious improvements on that solution before terminating.

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

Warning

The bestSolutionChanged() method is called in the solver's thread, as part of
Solver.solve(). So it should return quickly to avoid slowing down the solving.

6.10. Custom solver phase

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

Note

Most of the time, a custom construction heuristic is not worth the hassle. The supported constructions
heuristics are configurable (use the Benchmarker to tweak them),
Termination aware and support partially initialized solutions too.

Warning

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

Warning

Do not change any of the planning facts in a CustomPhaseCommand. That will corrupt the
Solver because any previous score or solution was for a different problem. To do that, read
about repeated planning and do it with a ProblemFactChange instead.

Configure multiple customPhaseCommandClass instances to run them in sequence.

Important

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

7.1. Move and neighborhood introduction

7.1.1. What is 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:

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

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

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

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.

7.1.2. What is a 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 might want to configure a filter to discard pointless
moves.

7.1.3. Subselecting of entities, values and other moves

To create a Move, a MoveSelector needs 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). Therefore, they all 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:

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 score traps into account). Of course, normally it is unioned with other, more course
grained move selectors.

7.2.2. swapMoveSelector

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

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.

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.

7.2.3. pillarChangeMoveSelector

A pillar is a set of planning entities which have the same planning value(s) for their
planning variable(s). The PillarChangeMove selects 1 entity pillar (or subset of those) and
changes the value of 1 variable (which is the same for all entities) to another value.

In the example above, queen A and C have the same value (row 0) and are moved to row 2. Also the yellow and
blue process have the same value (computer Y) and are moved to computer X.

A sub pillar is a subset of entities that share the same value(s) for their variable(s). For example if
queen A, B, C and D are all located on row 0, they are a pillar and [A, D] is one of the many
sub pillars. If subPillarEnabled (defaults to true) is false, no sub pillars
are selected. If sub pillars are enabled, the pillar itself is also included and the properties
minimumSubPillarSize (defaults to 1) and
maximumSubPillarSize (defaults to infinity) limit the size of the selected
(sub) pillar.

Note

The number of sub pillars of a pillar is exponential to the size of the pillar. For example a pillar of
size 32 has (2^32 - 1) subpillars. Therefore a pillarSelector only
supports JIT random selection (which is the default).

7.2.4. pillarSwapMoveSelector

A pillar is a set of planning entities which have the same planning value(s) for their
planning variable(s). The PillarSwapMove selects 2 different entity pillars and swaps the
values of all their variables for all their entities.

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

The subChainSelector selects a number of entities, no less than
minimumSubChainSize (defaults to 1) and no more than
maximumSubChainSize (defaults to infinity).

Note

If minimumSubChainSize is 1 (which is the default), this selector
might select the same move as a ChangeMoveSelector, at a far lower selection probability
(because each move type has the same selection chance by default (not every move instance)
and there are far more SubChainChangeMove instances than ChangeMove
instances). However, don't just remove the ChangeMoveSelector, because experiments show that
it's good to focus on ChangeMoves.

Furthermore, in a SubChainSwapMoveSelector, setting
minimumSubChainSize prevents swapping a subchain of size 1 with a subchain
of at least size 2.

The property selectReversingMoveToo (defaults to true) enabled selecting the reverse of
every subchain too.

7.2.6. subChainSwapMoveSelector

The subChainSwapMove selects 2 different subChains and moves it to another place in a
different or the same anchor chain.

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:

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:

The propery ignoreEmptyChildIterators (true by default) will ignore every empty
childMoveSelector to avoid returning no moves. For example: a cartesian product of
changeMoveSelector A and B, for which B is empty (because all it's entities are immovable)
returns no moves if ignoreEmptyChildIterators is false and the moves of A if
ignoreEmptyChildIterators is true.

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 solver phase and cache them in a list for the remainder of the phase. 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.

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

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

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

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

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 selectionOrderORIGINAL. Avoid overwriting the
selectionOrder of those nested Selectors.

7.6.3. Recommended combinations of CacheType and SelectionOrder

7.6.3.1. Just in time random selection (default)

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:

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):

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>

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

7.6.4. Filtered selection

There can be 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 a build-in hard constraint, 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,
also 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 cacheType and selectionOrder.

7.6.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 cacheTypeJUST_IN_TIME and it only works with
selectionOrderSORTED.

It's mostly used in construction heuristics.

Note

If the chosen construction heuristic implies sorting, for example FIRST_FIT_DECREASING
implies that the EntitySelector is sorted, there is no need to explicitly configure a
Selector with sorting. If you do explicitely configure the Selector, it
overwrites the default settings of that construction heuristic.

7.6.5.1. Sorted selection by SorterManner

Some Selector types implement a SorterManner out of the box:

EntitySelector supports:

DECREASING_DIFFICULTY: Sorts the planning entities according to decreasing
planning entity difficulty. Requires that planning
entity difficulty is annotated on the domain model.

7.6.6. Probabilistic selection

Probabilistic selection can happen on any Selector in the selector tree, including any
MoveSelector, EntitySelector or ValueSelector. It does
not work with cacheTypeJUST_IN_TIME and it only works with
selectionOrderPROBABILISTIC.

Each selection has a probabilityWeight, which determines the chance that selection
will be selected:

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.

7.6.7. Limited selection

Selecting all possible moves sometimes does not scale well enough, especially for construction heuristics
(which don't support acceptedCountLimit).

To limit the number of selected selection per step, apply a selectedCountLimit on the
selector:

Note

To scale Local Search, setting acceptedCountLimit is usually
better than using selectedCountLimit.

7.6.8. Mimic selection (record/replay)

During mimic selection, 1 normal selector records its selection and 1 or multiple other special selectors
replay that selection. The recording selector acts as a normal selector and supports all other configuration
properties. A replaying selector mimics the recording selection and support no other configuration
properties.

The recording selector needs an id. A replaying selector must reference a recorder's id
with a mimicSelectorRef:

Mimic selection is useful to create a composite move from 2 moves that affect the same entity.

7.6.9. Nearby selection

With nearby selection, it is possible to increase the probability of selecting an entity or value which is
nearby to the first entity being moved in that move.

7.7. Custom moves

7.7.1. Which move types might be missing in my implementation?

To determine which move types might be missing in your implementation, run a Benchmarkerfor a short amount of time and configure it to write the best solutions to disk. 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.

7.7.2. Custom moves introduction

Instead of reusing the generic Moves (such as ChangeMove) you can also
implement your own Moves. Generic and custom MoveSelectors can be combined
as desired.

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 the 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 environmentModeFULL_ASSERT to
check for score corruptions.

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:

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

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.

Planner automatically filters out non doable moves by calling the
isMoveDoable(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:

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.

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

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:

publicString toString(){return queen +" => "+ toRow;}

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

7.7.4. MoveListFactory: the easy way to generate custom moves

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

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

7.7.5. MoveIteratorFactory: generate custom moves just in time

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

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 selectionOrderRANDOM 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):

8.2.2. Configuration

8.3. Branch And Bound

8.3.1. Algorithm description

Branch And Bound also explores nodes in an exponential search tree, but it investigates more promising nodes
first and prunes away worthless nodes.

For each node, Branch And Bound calculates the optimistic bound: the best possible score to which that node
can lead to. If the optimistic bound of a node is lower or equal to the global pessimistic bound, then it prunes
away that node (including the entire branch of all its subnodes).

Note

Academic papers use the term lower bound instead of optimistic bound (and the term upper bound instead of
pessimistic bound), because they minimize the score.

OptaPlanner maximizes the score (because it supports combining negative and positive constraints).
Therefore, for clarity, OptaPlanner uses different terms, as it would be confusing to use the term lower bound
for a bound which is always higher.

For example: at index 15, it can prune away all unvisited solutions with queen A on row 0, because none will
be better than the solution of index 14 with a score of -1.

Notice that Branch And Bound (much like Brute Force) creates a search tree
that explodes exponentially as the problem size increases. So it hits the same scalability wall, only a little bit
later.

8.4. Scalability of Exhaustive Search

As shown in these time spent graphs from the Benchmarker, Brute
Force and Branch And Bound both hit a performance scalability wall. For example, on N queens it hits wall at a few
dozen queens:

In most use cases, such as Cloud Balancing, the wall appears out of thin air:

Exhaustive Search hits this wall on small datasets already, so in production these
optimizations algorithms are mostly useless. Use Construction Heuristics with Local Search instead: those
can handle thousands of queens/computers easily.

Note

Throwing hardware at these scalability issues has no noticeable impact. Newer and more hardware are just a
drop in the ocean. Moore's law cannot win against the onslaught of a few more planning entities in the
dataset.

9.1. Overview

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

9.2. First Fit

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

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 metaheuristics can
remedy that.

9.2.2. Configuration

Note

If the InitializingScoreTrend is
ONLY_DOWN, this algorithm is faster: for an entity, it picks the first move for which the
score does not deteriorate the last step score, ignoring all subsequent moves.

9.3.2. Configuration

Note

If the InitializingScoreTrend is
ONLY_DOWN, this algorithm is faster: for an entity, it picks the first move for which the
score does not deteriorate the last step score, ignoring all subsequent moves.

9.4. Weakest Fit

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

9.4.2. Configuration

Note

If the InitializingScoreTrend is
ONLY_DOWN, this algorithm is faster: for an entity, it picks the first move for which the
score does not deteriorate the last step score, ignoring all subsequent moves.

9.5.2. Configuration

Note

If the InitializingScoreTrend is
ONLY_DOWN, this algorithm is faster: for an entity, it picks the first move for which the
score does not deteriorate the last step score, ignoring all subsequent moves.

Per step, the QueuedEntityPlacer selects 1 uninitialized entity from the
EntitySelector and applies the winning Move (out of all the moves for that
entity generated by the MoveSelector). The mimic
selection ensures that the winning Move changes (only) the selected entity.

Important

Especially for sequential ChangeMoves, the order of the variables is important. In the
example above, it's better to select the period first (instead of the other way around), because there are more
hard constraints that do not involve the room (for example: no teacher should teach 2 lectures at the same
time). Let the Benchmarker guide you.

With 3 or more variables, it's possible to combine the cartesian product and sequential techniques:

FIRST_NON_DETERIORATING_SCORE: Initialize the variable(s) with the first move that
doesn't deteriorate the score, ignore the remaining selected moves. This is the default if the InitializingScoreTrend is ONLY_DOWN.

Note

If there are only negative constraints, but the InitializingScoreTrend is strictly not ONLY_DOWN,
it can sometimes make sense to apply FIRST_NON_DETERIORATING_SCORE. Use the Benchmarker to decide if the score quality loss is worth the time
gain.

FIRST_FEASIBLE_SCORE: Initialize the variable(s) with the first move that has a
feasible score.

9.7. Allocate To Value From Queue

9.7.1. Algorithm description

Allocate To Value From Queue is a versatile, generic form of Nearest Neighbour. It works like this:

Put all values in a round-robin queue.

Assign the best entity to the first value (from that queue).

Repeat until all entities are assigned.

9.7.2. Configuration

Not yet implemented.

9.8. Cheapest Insertion

9.8.1. Algorithm description

The Cheapest Insertion algorithm cycles through all the planning values for all the planning entities,
initializing 1 planning entity at a time. It assigns a planning entity to the best available planning value (out
of all the planning entities and values), 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.

Note

Cheapest Insertion scales considerably worse than First Fit, etc.

9.8.2. Configuration

Note

If the InitializingScoreTrend is
ONLY_DOWN, this algorithm is faster: for an entity, it picks the first move for which the
score does not deteriorate the last step score, ignoring all subsequent moves.

10.1. Overview

Local Search starts from an initial solution and evolves that single solution into a mostly better and better
solution. It uses a single search path of solutions, not a search tree. At each solution in this path it evaluates a
number of moves on the solution and applies the most suitable move to take the step to the next solution. It does
that for a high number of iterations until it's terminated (usually because its time has run out).

Local Search acts a lot like a human planner: it uses a single search path and moves facts around to find a
good feasible solution. Therefore it's pretty natural to implement.

Local Search usually needs to start from an initialized solution, therefore
it's usually required to configure a construction heuristic solver phase before it.

10.2. Local Search concepts

10.2.1. Taking steps

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

Figure 10.1. Decide the next step at step 0 (4 queens example)

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

The step is applied on the solution. From that new solution, the local search solver tries every move again,
to decide the next step after that. It continually does this in a loop, and we get something like this:

Figure 10.2. All steps (4 queens example)

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

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

B0 to B3

D0 to B2

A0 to B1

If we turn on debug logging for the category org.optaplanner, then
those steps are shown into the log:

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

A naive Local Search configuration solves the 4 queens problem in 3 steps, by evaluating only 37 possible
solutions (3 steps with 12 moves each + 1 starting solution), which is only fraction of all 256 possible
solutions. It solves 16 queens in 31 steps, by evaluating only 7441 out of 18446744073709551616 possible
solutions. Note: with construction heuristics it's even a lot more efficient.

10.2.2. Deciding the next step

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

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

10.2.3. Acceptor

An Acceptor is used (together with a Forager) to active Tabu Search,
Simulated Annealing, Late Acceptance, ... For each move it checks whether it is accepted or not.

By changing a few lines of configuration, you can easily switch from Tabu Search to Simulated Annealing or
Late Acceptance and back.

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

10.2.4. Forager

A Forager gathers all accepted moves and picks the move which is the next step. Normally
it picks the accepted move with the highest score. If several accepted moves have the highest score, one is picked
randomly.

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

10.2.4.1. Accepted count limit

When there are many possible moves, it becomes inefficient to evaluate all of them at every step. To
evaluate only a random subset of all the moves, use:

An acceptedCountLimit integer, which specifies how many accepted moves should be
evaluated during each step. By default, all accepted moves are evaluated at every step.

<forager><acceptedCountLimit>1000</acceptedCountLimit></forager>

Unlike the n queens problem, real world problems require the use of acceptedCountLimit.
Start from an acceptedCountLimit that takes a step in less then 2 seconds. Turn on INFO logging to see the step times. Use the Benchmarker to tweak the value.

Important

With a low acceptedCountLimit it is recommended to avoid using
selectionOrder SHUFFLED because the shuffling generates a random number for every element
in the selector, taking up a lot of time, but only a few elements are actually selected.

10.2.4.2. Pick early type

A forager can pick a move early during a step, ignoring subsequent selected moves. There are 3 pick early
types for Local Search:

NEVER: A move is never picked early: all accepted moves are evaluated that the
selection allows. This is the default.

<forager><pickEarlyType>NEVER</pickEarlyType></forager>

FIRST_BEST_SCORE_IMPROVING: Pick the first accepted move that improves the best
score. If none improve the best score, it behaves exactly like the pickEarlyType NEVER.

10.3. Hill Climbing (Simple Local Search)

10.3.1. Algorithm description

Hill Climbing tries all selected moves and then takes the best move, which is the move which leads to the
solution with the highest score. That best move is called the step move. From that new solution, it again tries
all selected moves and takes the best move and continues like that iteratively. If multiple selected moves tie for
the best move, one of them is randomly chosen as the best move.

Notice that once a queen has moved, it can be moved again later. This is a good thing, because in an
NP-complete problem it's impossible to predict what will be the optimal final value for a planning
variable.

10.3.2. Getting stuck in local optima

Hill Climbing always takes improving moves. This may seem like a good thing, but it's not: Hill Climbing can easily get stuck in a local optimum. This happens when it reaches a
solution for which all the moves deteriorate the score. Even if it picks one of those moves, the next step might
go back to the original solution and which case chasing it's own tail:

Improvements upon Hill Climbing (such as Tabu Search, Simulated Annealing and Late Acceptance) address the
problem of being stuck in local optima. Therefore, it's recommend to never use Hill Climbing, unless you're
absolutely sure there are no local optima in your planning problem.

10.3.3. Configuration

10.4. Tabu Search

10.4.1. Algorithm description

Tabu Search works like Hill Climbing, but it maintains a tabu list to avoid getting stuck in local optima.
The tabu list holds recently used objects that are taboo to use for now. Moves that involve
an object in the tabu list, are not accepted. The tabu list objects can be anything related to the move, such as
the planning entity, planning value, move, solution, ... Here's an example with entity tabu for 4 queens, so the
queens are put in the tabu list:

Important

A Tabu Search acceptor should be combined with a high acceptedCountLimit, such as
1000.

OptaPlanner implements several tabu types:

Planning entity tabu makes the planning entities of recent steps tabu. For example,
for N queens it makes the recently moved queens tabu. It's recommended to start with this tabu type.

<acceptor><entityTabuSize>7</entityTabuSize></acceptor>

To avoid hard coding the tabu size, configure a tabu ratio, relative to the number of entities, for
example 2%:

<acceptor><entityTabuRatio>0.02</entityTabuRatio></acceptor>

Planning value tabu makes the planning values of recent steps tabu. For example,
for N queens it makes the recently moved to rows tabu.

<acceptor><valueTabuSize>7</valueTabuSize></acceptor>

To avoid hard coding the tabu size, configure a tabu ratio, relative to the number of values, for
example 2%:

<acceptor><valueTabuRatio>0.02</valueTabuRatio></acceptor>

Move tabu makes recent steps tabu. It does not accept a move equal to one of those
steps.

<acceptor><moveTabuSize>7</moveTabuSize></acceptor>

Undo move tabu makes the undo move of recent steps tabu.

<acceptor><undoMoveTabuSize>7</undoMoveTabuSize></acceptor>

Solution tabu makes recently visited solutions tabu. It does not accept a move that
leads to one of those solutions. It requires that the Solution implements
equals() and hashCode() properly. If you can spare the memory, don't be
cheap on the tabu size.

<acceptor><solutionTabuSize>1000</solutionTabuSize></acceptor>

For non-trivial cases, it's usually useless because the search space
size makes it statistically almost impossible to reach the same solution twice.

If you pick a too small tabu size, your solver can still get stuck in a local optimum. On the other hand,
with the exception of solution tabu, if you pick a too large tabu size, your solver can get stuck by bouncing of
the walls. Use the Benchmarker to fine tweak your
configuration.

10.5. Simulated Annealing

10.5.1. Algorithm description

Simulated Annealing evaluates only a few moves per step, so it steps quickly. In the classic implementation,
the first accepted move is the winning step. A move is accepted if it doesn't decrease the score or - in case it
does decrease the score - if passes a random check. The chance that a decreasing move passes the random check
decreases relative to the size of the score decrement and the time the phase has been running (which is
represented as the temperature).

10.5.2. Configuration

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

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

10.6. Late Acceptance

10.6.1. Algorithm description

Late Acceptance (also known as Late Acceptance Hill Climbing) also evaluates only a few moves per step. A
move is accepted if does not decrease the score, or if it leads to a score that is at least the late score (which
is the winning score of a fixed number of steps ago).

10.7. Step Counting Hill Climbing

10.7.1. Algorithm description

Step Counting Hill Climbing also evaluates only a few moves per step. For a number of steps, it keeps the
step score as a threshold. A move is accepted if does not decrease the score, or if it leads to a score that is at
least the threshold score.

10.7.2. Configuration

Step Counting Hill Climbing accepts any move that has a score which is higher than a threshold score. Every
number of steps (specified by stepCountingHillClimbingSize), the threshold score is set to the
step score.

10.8. Strategic Oscillation

10.8.1. Algorithm description

Strategic Oscillation is an add-on, which works especially well with Tabu
Search. Instead of picking the accepted move with the highest score, it employs a different mechanism: If
there's an improving move, it picks it. If there's no improving move however, it prefers moves which improve a
softer score level, over moves which break a harder score level less.

10.8.2. Configuration

Configure a finalistPodiumType, for example in a Tabu Search configuration:

STRATEGIC_OSCILLATION_BY_LEVEL: If there is an accepted improving move, pick it. If
no such move exists, prefer an accepted move which improves a softer score level over one that doesn't (even
if it has a better harder score level). A move is improving if it's better than the last completed step
score.

STRATEGIC_OSCILLATION_BY_LEVEL_ON_BEST_SCORE: Like
STRATEGIC_OSCILLATION_BY_LEVEL, but define improving as better than the bet score (instead
of the last completed step score).

10.9. Using a custom Termination, MoveSelector, EntitySelector, ValueSelector or Acceptor

You can plug in a custom Termination, MoveSelector,
EntitySelector, ValueSelector or Acceptor by extending the
abstract class and also the related *Config class.

For example, to use a custom MoveSelector, extend the
AbstractMoveSelector class, extend the MoveSelectorConfig class and configure
it in the solver configuration.

Chapter 11. Evolutionary algorithms

11.1. Overview

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

11.2. Evolutionary Strategies

This algorithm has not been implemented yet.

11.3. Genetic Algorithms

This algorithm has not been implemented yet.

Note

A good Genetic Algorithms prototype in OptaPlanner has been written, but it wasn't practical to merge and
support at the time. The results of Genetic Algorithms were consistently and seriously inferior to all the Local Search variants (except Hill Climbing) on all use cases tried. Nevertheless, a
future version of OptaPlanner will add support for Genetic Algorithms, so you can easily benchmark genetic
algorithms on your use case too.

Chapter 12. Hyperheuristics

12.1. Overview

A hyperheuristic automates the decision which heuristic(s) to use on a specific data set.

A future version of OptaPlanner will have native support for hyperheuristics. Meanwhile, it's pretty easy to
implement it yourself: Based on the size or difficulty of a data set (which is a criterion), use a different Solver
configuration (or adjust the default configuration using the Solver configuration API). The Benchmarker can help to identify such criteria.

Chapter 13. Partitioned search

13.1. Overview

For very big datasets, it is sometimes worthwhile to partition the datasets into smaller pieces.

However, partitioning leads to suboptimal results, even if the pieces are
solved optimally:

A future version of OptaPlanner will have native support for several forms of partitioning. Meanwhile, you can
implement it yourself as shown in the image above. Use an OptaPlanner Solver to solve each
piece.

Note

Not all use cases can be partitioned. It only works on use cases for which the planning entities and value
ranges can be divided into n pieces, such that none of the constraints crosses piece boundaries.

14.1. Finding the best Solver configuration

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

14.2. Doing a benchmark

14.2.1. Adding a dependency on optaplanner-benchmark

The benchmarker is in a separate artifact called optaplanner-benchmark.

This PlannerBenchmark will try 3 configurations (Tabu Search, Simulated Annealing and
Late Acceptance) on 2 data sets (32queens and 64queens), so it will run 6 solvers.

Every <solverBenchmark> element contains a solver configuration 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 SolutionFileIO (relative
to the working directory).

Note

Use a forward slash (/) as the file separator (for example in the element
<inputSolutionFile>). That will work on any platform (including Windows).

Do not use backslash (\) as the file separator: that breaks portability because it does
not work on Linux and Mac.

To lower verbosity, the common parts of multiple <solverBenchmark> elements are
extracted to the <inheritedSolverBenchmark> element. Every property 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.

The benchmark report will be written in the directory specified the
<benchmarkDirectory> element (relative to the working 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.

If an Exception or Error occurs in a single benchmark, the entire
Benchmarker will not fail-fast (unlike everything else in OptaPlanner). Instead, the Benchmarker will continue to
run all other benchmarks, write the benchmark report and then fail (if there is at least 1 failing single
benchmark). The failing benchmarks will be clearly marked as such in the benchmark report.

14.2.3. SolutionFileIO: input and output of Solution files

14.2.3.1. SolutionFileIO interface

The benchmarker needs to be able to read the input files to load a Solution. Also, it
might need to write the best Solution of each benchmark to an output file. For that it uses a
class that implements the SolutionFileIO interface: