## Chapter 5. Score calculation

5.1. Score terminology
5.1.1. What is a score?
5.1.2. Score constraint signum (positive or negative)
5.1.3. Score constraint weight
5.1.4. Score level
5.1.5. Pareto scoring (AKA multi-objective optimization scoring)
5.1.6. Combining score techniques
5.1.7. The Score interface
5.1.8. Avoid floating point numbers in score calculation
5.2. Choose a Score definition
5.2.1. SimpleScore
5.2.2. HardSoftScore (recommended)
5.2.3. HardMediumSoftScore
5.2.4. BendableScore
5.2.5. Implementing a custom Score
5.3. Calculate the Score
5.3.1. Score calculation types
5.3.2. Simple Java score calculation
5.3.3. Incremental Java score calculation
5.3.4. Drools score calculation
5.3.5. Invalid score detection
5.4. Score calculation performance tricks
5.4.1. Overview
5.4.2. Average calculation count per second
5.4.3. Incremental score calculation (with delta's)
5.4.4. Avoid calling remote services during score calculation
5.4.5. Pointless constraints
5.4.6. Build-in hard constraint
5.4.7. Other performance tricks
5.4.8. Score trap
5.4.9. stepLimit benchmark
5.4.10. Fairness score constraints
5.5. Reusing the score calculation outside the Solver

## 5.1. Score terminology

### 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 `Process`es, then it must be weighted 7 times as much as if it had only 1 CPU too little. This way, there is an incentive to move a `Process` with 6 CPU or less away from that Computer.

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

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

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

## Note

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

### 5.1.6. Combining score techniques

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

## 5.2. Choose a Score definition

Each `Score` implementation also has a `ScoreDefinition` implementation. For example: `SimpleScore` is definied by `SimpleScoreDefinition`.

### 5.2.1. SimpleScore

A `SimpleScore` has a single `int` value, for example `-123`. It has a single score level.

```
<scoreDirectorFactory>
<scoreDefinitionType>SIMPLE</scoreDefinitionType>
...
</scoreDirectorFactory>
```

Variants of this `scoreDefinitionType`:

### 5.2.2. HardSoftScore (recommended)

A `HardSoftScore` has a hard `int` value and a soft `int` value, for example `-123hard/-456soft`. It has 2 score levels (hard and soft).

```
<scoreDirectorFactory>
<scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
...
</scoreDirectorFactory>
```

Variants of this `scoreDefinitionType`:

### 5.2.3. HardMediumSoftScore

A `HardMediumSoftScore` which has a hard `int` value, a medium `int` value and a soft `int` value, for example `-123hard/-456medium/-789soft`. It has 3 score levels (hard, medium and soft).

```
<scoreDirectorFactory>
<scoreDefinitionType>HARD_MEDIUM_SOFT</scoreDefinitionType>
...
</scoreDirectorFactory>
```

### 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 2 hard levels and 3 soft levels for a score `-123/-456/-789/-012/-345`. The number of hard and soft score levels needs to be set at configuration time, it's not flexible to change during solving.

```
<scoreDirectorFactory>
<scoreDefinitionType>BENDABLE</scoreDefinitionType>
<bendableHardLevelCount>2</bendableHardLevelCount>
<bendableSoftLevelCount>3</bendableSoftLevelCount>
...
</scoreDirectorFactory>
```

## 5.3. Calculate the `Score`

### 5.3.2. Simple Java score calculation

A simple way to implement your score calculation in Java.

Just implement one method of the interface `SimpleScoreCalculator`:

```public interface SimpleScoreCalculator<Sol extends Solution> {

Score calculateScore(Sol solution);

}```

For example in n queens:

```public class NQueensSimpleScoreCalculator implements SimpleScoreCalculator<NQueens> {

public SimpleScore calculateScore(NQueens nQueens) {
int n = nQueens.getN();
List<Queen> queenList = nQueens.getQueenList();

int score = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Queen leftQueen = queenList.get(i);
Queen rightQueen = queenList.get(j);
if (leftQueen.getRow() != null && rightQueen.getRow() != null) {
if (leftQueen.getRowIndex() == rightQueen.getRowIndex()) {
score--;
}
if (leftQueen.getAscendingDiagonalIndex() == rightQueen.getAscendingDiagonalIndex()) {
score--;
}
if (leftQueen.getDescendingDiagonalIndex() == rightQueen.getDescendingDiagonalIndex()) {
score--;
}
}
}
}
return SimpleScore.valueOf(score);
}

}```

Configure it in your solver configuration:

```
<scoreDirectorFactory>
<scoreDefinitionType>...</scoreDefinitionType>
<simpleScoreCalculatorClass>org.optaplanner.examples.nqueens.solver.score.NQueensSimpleScoreCalculator</simpleScoreCalculatorClass>
</scoreDirectorFactory>
```

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

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

### 5.3.3. Incremental Java score calculation

A way to implement your score calculation incrementally in Java.

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

```public interface IncrementalScoreCalculator<Sol extends Solution> {

void resetWorkingSolution(Sol workingSolution);

void beforeVariableChanged(Object entity, String variableName);

void afterVariableChanged(Object entity, String variableName);

void beforeEntityRemoved(Object entity);

void afterEntityRemoved(Object entity);

Score calculateScore();

}```

For example in n queens:

```public class NQueensAdvancedIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<NQueens> {

private Map<Integer, List<Queen>> rowIndexMap;
private Map<Integer, List<Queen>> ascendingDiagonalIndexMap;
private Map<Integer, List<Queen>> descendingDiagonalIndexMap;

private int score;

public void resetWorkingSolution(NQueens nQueens) {
int n = nQueens.getN();
rowIndexMap = new HashMap<Integer, List<Queen>>(n);
ascendingDiagonalIndexMap = new HashMap<Integer, List<Queen>>(n * 2);
descendingDiagonalIndexMap = new HashMap<Integer, List<Queen>>(n * 2);
for (int i = 0; i < n; i++) {
rowIndexMap.put(i, new ArrayList<Queen>(n));
ascendingDiagonalIndexMap.put(i, new ArrayList<Queen>(n));
descendingDiagonalIndexMap.put(i, new ArrayList<Queen>(n));
if (i != 0) {
ascendingDiagonalIndexMap.put(n - 1 + i, new ArrayList<Queen>(n));
descendingDiagonalIndexMap.put((-i), new ArrayList<Queen>(n));
}
}
score = 0;
for (Queen queen : nQueens.getQueenList()) {
insert(queen);
}
}

// Do nothing
}

insert((Queen) entity);
}

public void beforeVariableChanged(Object entity, String variableName) {
retract((Queen) entity);
}

public void afterVariableChanged(Object entity, String variableName) {
insert((Queen) entity);
}

public void beforeEntityRemoved(Object entity) {
retract((Queen) entity);
}

public void afterEntityRemoved(Object entity) {
// Do nothing
}

private void insert(Queen queen) {
Row row = queen.getRow();
if (row != null) {
int rowIndex = queen.getRowIndex();
List<Queen> rowIndexList = rowIndexMap.get(rowIndex);
score -= rowIndexList.size();
List<Queen> ascendingDiagonalIndexList = ascendingDiagonalIndexMap.get(queen.getAscendingDiagonalIndex());
score -= ascendingDiagonalIndexList.size();
List<Queen> descendingDiagonalIndexList = descendingDiagonalIndexMap.get(queen.getDescendingDiagonalIndex());
score -= descendingDiagonalIndexList.size();
}
}

private void retract(Queen queen) {
Row row = queen.getRow();
if (row != null) {
List<Queen> rowIndexList = rowIndexMap.get(queen.getRowIndex());
rowIndexList.remove(queen);
score += rowIndexList.size();
List<Queen> ascendingDiagonalIndexList = ascendingDiagonalIndexMap.get(queen.getAscendingDiagonalIndex());
ascendingDiagonalIndexList.remove(queen);
score += ascendingDiagonalIndexList.size();
List<Queen> descendingDiagonalIndexList = descendingDiagonalIndexMap.get(queen.getDescendingDiagonalIndex());
descendingDiagonalIndexList.remove(queen);
score += descendingDiagonalIndexList.size();
}
}

public SimpleScore calculateScore() {
return SimpleScore.valueOf(score);
}

}```

Configure it in your solver configuration:

```
<scoreDirectorFactory>
<scoreDefinitionType>...</scoreDefinitionType>
</scoreDirectorFactory>
```

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

### 5.3.4. Drools score calculation

#### 5.3.4.2. Drools score rules configuration

There are several ways to define where your score rules live.

##### 5.3.4.2.1. A scoreDrl resource on the classpath

This is the easy way: the score rule live in a DRL file which is a resource on the classpath. Just add your score rules `*.drl` file in the solver configuration:

```
<scoreDirectorFactory>
<scoreDefinitionType>...</scoreDefinitionType>
<scoreDrl>/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
</scoreDirectorFactory>
```

You can add multiple `<scoreDrl>` entries if needed, but normally you'll define all your score rules in 1 file.

Optionally, you can also set drools configuration properties but beware of backwards compatibility issues:

```
<scoreDirectorFactory>
...
<scoreDrl>/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
<kieBaseConfigurationProperties>
<drools.equalityBehavior>...</drools.equalityBehavior>
</kieBaseConfigurationProperties>
</scoreDirectorFactory>
```
##### 5.3.4.2.2. A KieBase (possibly defined by Drools Workbench)

If you prefer to build the `KieBase` yourself or if you're combining Planner with KIE Workbench (formerly known as Guvnor), you can set the `KieBase` on the `SolverFactory` before building the `Solver`:

`    solverFactory.getSolverConfig().getScoreDirectorFactoryConfig.setKieBase(kieBase);`

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

```global SimpleScoreHolder scoreHolder;

rule "multipleQueensHorizontal"
when
Queen(\$id : id, row != null, \$i : rowIndex)
Queen(id > \$id, rowIndex == \$i)
then
end

// multipleQueensVertical is obsolete because it is always 0

rule "multipleQueensAscendingDiagonal"
when
Queen(\$id : id, row != null, \$i : ascendingDiagonalIndex)
Queen(id > \$id, ascendingDiagonalIndex == \$i)
then
end

rule "multipleQueensDescendingDiagonal"
when
Queen(\$id : id, row != null, \$i : descendingDiagonalIndex)
Queen(id > \$id, descendingDiagonalIndex == \$i)
then
end```

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
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
end```

### 5.3.5. Invalid score detection

Put the `environmentMode` in `FULL_ASSERT` (or `FAST_ASSERT`) to detect corruption in the incremental score calculation. For more information, see the section about `environmentMode`. However, that will not verify that your score calculator implements your score constraints as your business actually desires.

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

```
<environmentMode>FAST_ASSERT</environmentMode>
...
<scoreDirectorFactory>
<scoreDefinitionType>...</scoreDefinitionType>
<scoreDrl>/org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl</scoreDrl>
<assertionScoreDirectorFactory>
<simpleScoreCalculatorClass>org.optaplanner.examples.nqueens.solver.score.NQueensSimpleScoreCalculator</simpleScoreCalculatorClass>
</assertionScoreDirectorFactory>
</scoreDirectorFactory>
```

This way, the `scoreDrl` will be validated by the `SimpleScoreCalculator`.

## 5.5. Reusing the 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:

```ScoreDirectorFactory scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector guiScoreDirector = scoreDirectorFactory.buildScoreDirector();```

Then use it when you need to calculate the `Score` of a `Solution`:

```guiScoreDirector.setWorkingSolution(solution);
Score score = guiScoreDirector.calculateScore();```

To explain in the GUI what entities are causing which part of the `Score`, get the `ConstraintMatch` objects from the `ScoreDirector` (after calling `calculateScore()`):

```for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
String constraintName = constraintMatchTotal.getConstraintName();
Number weightTotal = constraintMatchTotal.getWeightTotalAsNumber();
for (ConstraintMatch constraintMatch : constraintMatchTotal.getConstraintMatchSet()) {
List<Object> justificationList = constraintMatch.getJustificationList();
Number weight = constraintMatch.getWeightAsNumber();
...
}
}```