[[constraintStreams]]
= Constraint streams score calculation
:doctype: book
:imagesdir: ..
:sectnums:
:toc: left
:icons: font
:experimental:


Constraint streams are a Functional Programming form of incremental score calculation in plain Java that is easy to
read, write and debug.
The API should feel familiar if you've worked with Java 8 Streams or SQL.

[WARNING]
====
The ConstraintStreams/ConstraintProvider API is an ongoing project.
It works but it has many API gaps.
Therefore, it is not rich enough yet to handle complex constraints.
<<explainingTheScore,Constraint justifications>> may not function properly.
====

// TODO Remove the previous sentence when PLANNER-1709 is fixed.


[[constraintStreamsIntroduction]]
== Introduction

Using Java 8's Streams API, we could implement an <<easyJavaScoreCalculation,easy score calculator>>
that uses a functional approach:

[source,java,options="nowrap"]
----
    private int doNotAssignAnn() {
        int softScore = 0;
        schedule.getShiftList().stream()
                .filter(Shift::isEmployeeAnn)
                .forEach(shift -> {
                    softScore -= 1;
                });
        return softScore;
    }
----

However, that scales poorly because it doesn't do an <<incrementalScoreCalculation,incremental calculation>>:
When the planning variable of a single `Shift` changes, to recalculate the score,
the normal Streams API has to execute the entire stream from scratch.
The ConstraintStreams API enables you to write similar code in pure Java, while reaping the performance benefits of
incremental score calculation.
This is an example of the same code, using the Constraint Streams API:

// TODO Rewrite the above paragraph PLANNER-1709 is fixed, mentioning constraint justification as another benefit.

[source,java,options="nowrap"]
----
    private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .filter(Shift::isEmployeeAnn)
                .penalize("Don't assign Ann", HardSoftScore.ONE_SOFT);
    }
----

This constraint stream iterates over all instances of class `Shift` in the <<problemFacts,problem facts>> and
<<planningEntity,planning entities>> in the <<planningProblemAndPlanningSolution,planning problem>>.
It finds every `Shift` which is assigned to employee `Ann` and for every such instance (also called a match), it adds a
soft penalty of `1` to the overall <<calculateTheScore,score>>.
The following figure illustrates this process on a problem with 4 different shifts:

image::ConstraintStreams/constraintStreamIntroduction.png[align="center"]

If any of the instances change during solving, the constraint stream automatically detects the change
and only recalculates the minimum necessary portion of the problem that is affected by the change.
The following figure illustrates this <<incrementalScoreCalculation,incremental score calculation>>:

image::ConstraintStreams/constraintStreamIncrementalCalculation.png[align="center"]

////
TODO Include and describe the following image when PLANNER-1709 is fixed.
image::ConstraintStreams/constraintStreamJustification.png[align="center"]
////


[[constraintStreamsConfiguration]]
== Creating a constraint stream

To use the ConstraintStreams API in your project, first write a pure Java `ConstraintProvider` implementation similar
to the following example.

[source,java,options="nowrap"]
----
    public class MyConstraintProvider implements ConstraintProvider {

        @Override
        public Constraint[] defineConstraints(ConstraintFactory factory) {
            return new Constraint[] {
                    penalizeEveryShift(factory)
            };
        }

        private Constraint penalizeEveryShift(ConstraintFactory factory) {
            return factory.from(Shift.class)
                .penalize("Penalize a shift", HardSoftScore.ONE_SOFT);
        }

    }
----

[NOTE]
====
This example contains one constraint, `penalizeEveryShift(...)`.
However, you can include as many as you require.
====

Add the following code to your solver configuration:

[source,xml,options="nowrap"]
----
    <solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
      <scoreDirectorFactory>
        <constraintProviderClass>com.example.MyConstraintProvider</constraintProviderClass>
      </scoreDirectorFactory>
      ...
    </solver>
----


[[constraintStreamsCardinality]]
== Constraint stream cardinality

Constraint stream cardinality is a measure of how many objects a single constraint match consists of.
The simplest constraint stream has a cardinality of 1, meaning each constraint match only consists of 1 object.
Therefore, it is called a `UniConstraintStream`:

[source,java,options="nowrap"]
----
    private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class) // Returns UniStream<Shift>.
                ...
    }
----

Some constraint stream <<constraintStreamsBuildingBlocks, building blocks>> can increase stream cardinality, such as
<<constraintStreamsJoin,join>> or <<constraintStreamsGroupingAndCollectors,groupBy>>:

[source,java,options="nowrap"]
----
    private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class) // Returns Uni<Shift>.
                .join(Employee.class)    // Returns Bi<Shift, Employee>.
                .join(DayOff.class)      // Returns Tri<Shift, Employee, DayOff>.
                .join(Country.class)     // Returns Quad<Shift, Employee, DayOff, Country>.
                ...
    }
----

The latter can also decrease stream cardinality:

[source,java,options="nowrap"]
----
    private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class) // Returns UniStream<Shift>.
                .join(Employee.class) // Returns BiStream<Shift, Employee>.
                .groupBy((shift, employee) -> employee) // Returns UniStream<Employee>.
                ...
    }
----

The following constraint stream cardinalities are currently supported:

|===
|Cardinality|Prefix|Defining interface
|1          |   Uni|`UniConstraintStream<A>`
|2          |    Bi|`BiConstraintStream<A, B>`
|3          |   Tri|`TriConstraintStream<A, B, C>`
|4          |  Quad|`QuadConstraintStream<A, B, C, D>`
|===

[[constraintStreamsHigherCardinalities]]
=== Achieving higher cardinalities

OptaPlanner currently does not support constraint stream cardinalities higher than 4.
However, with <<constraintStreamsMappingTuples,tuple mapping>> effectively infinite cardinality is possible:

[source,java,options="nowrap"]
----
    private Constraint pentaStreamExample(ConstraintFactory factory) {
        return factory.from(Shift.class) // UniConstraintStream<Shift>
                .join(Shift.class)       // BiConstraintStream<Shift, Shift>
                .join(Shift.class)       // TriConstraintStream<Shift, Shift, Shift>
                .join(Shift.class)       // QuadConstraintStream<Shift, Shift, Shift, Shift>
                .map(Tuple::of)          // UniConstraintStream<Tuple<Shift, Shift, Shift, Shift>>
                .join(Shift.class)       // BiConstraintStream<Tuple<Shift, Shift, Shift, Shift>, Shift>
                ...                      // This BiConstraintStream carries 5 Shift elements.
    }
----

[NOTE]
====
OptaPlanner does not provide any `Tuple` implementations out of the box.
It's recommended to use one of the myriad freely available 3rd party implementations.
Should a custom implementation be necessary, see <<constraintStreamsDesigningMappingFunction,guidelines for mapping functions>>.
====

[[constraintStreamsBuildingBlocks]]
== Building blocks

Constraint streams are chains of different operations, called building blocks.
Each constraint stream starts with a `from(...)` building block and is terminated by either a penalty or a reward.
The following example shows the simplest possible constraint stream:

[source,java,options="nowrap"]
----
    private Constraint penalizeInitializedShifts(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .penalize("Initialized shift", HardSoftScore.ONE_SOFT);
    }
----

This constraint stream penalizes each known and initialized instance of `Shift`.

[[constraintStreamsFrom]]
=== From

The `.from(T)` building block selects every `T` instance that
is in a <<problemFacts, problem fact collection>>
or a <<planningEntitiesOfASolution, planning entity collection>>
and has no `null` planning variables.

To include instances with a `null` planning variable,
especially if you use <<nullablePlanningVariable, nullable>> variables,
replace the `from()` building block by `fromUnfiltered()`:

[source,java,options="nowrap"]
----
    private Constraint penalizeAllShifts(ConstraintFactory factory) {
        return factory.fromUnfiltered(Shift.class)
                .penalize("A shift", HardSoftScore.ONE_SOFT);
    }
----


[[constraintStreamsPenaltiesRewards]]
=== Penalties and rewards

The purpose of constraint streams is to build up a <<whatIsAScore,score>> for a <<planningProblemAndPlanningSolution,solution>>.
To do this, every constraint stream must be terminated by a call to either a `penalize()` or a `reward()`
building block.
The `penalize()` building block makes the score worse and the `reward()` building block improves the score.
Penalties and rewards have several components:

- Constraint package is the Java package that contains the constraint.
The default value is the package that contains the `ConstraintProvider` implementation or the value from
<<constraintConfiguration,constraint configuration>>, if implemented.
- Constraint name is the human readable descriptive name for the constraint, which
(together with the constraint package) must be unique within the entire `ConstraintProvider` implementation.
- Constraint weight is a constant score value indicating how much every breach of the constraint affects the score.
Valid examples include `SimpleScore.ONE`, `HardSoftScore.ONE_HARD` and `HardMediumSoftScore.of(1, 2, 3)`.
- Constraint match weigher is an optional function indicating how many times the constraint weight should be applied in
the score.
The penalty or reward score impact is the constraint weight multiplied by the match weight.
The default value is `1`.

The ConstraintStreams API supports many different types of penalties.
Browse the API in your IDE for the full list of method overloads.
Here are some examples:

- Simple penalty (`penalize("Constraint name", SimpleScore.ONE)`) makes the score worse by `1` per every match in the
constraint stream.
The score type must be the same type as used on the `@PlanningScore` annotated member on the planning solution.
- Dynamic penalty (`penalize("Constraint name", SimpleScore.ONE, Shift::getHours)`) makes the score worse by the number
of hours in every matching `Shift` in the constraint stream.
This is an example of using a constraint match weigher.
- Configurable penalty (`penalizeConfigurable("Constraint name")`) makes the score worse using constraint weights
defined in <<constraintConfiguration,constraint configuration>>.
- Configurable dynamic penalty(`penalizeConfigurable("Constraint name", Shift::getHours)`) makes the score worse using
constraint weights defined in <<constraintConfiguration,constraint configuration>>, multiplied by the number of hours in
every matching `Shift` in the constraint stream.

By replacing the keyword `penalize` by `reward` in the name of these building blocks, you will get operations that
affect score in the opposite direction.


[[constraintStreamsFilter]]
=== Filtering

Filtering enables you to reduce the number of constraint matches in your stream.
It first enumerates all constraint matches and then applies a predicate to filter some matches out.
The predicate is a function that only returns `true` if the match is to continue in the stream.
The following constraint stream removes all of Beth's shifts from all `Shift` matches:

[source,java,options="nowrap"]
----
    private Constraint penalizeAnnShifts(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .filter(shift -> shift.getEmployeeName().equals("Ann"))
                .penalize("Ann's shift", SimpleScore.ONE);
    }
----

The following example retrieves a list of shifts where an employee has asked for a day off from a bi-constraint match
of `Shift` and `DayOff`:

[source,java,options="nowrap"]
----
    private Constraint penalizeShiftsOnOffDays(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .join(DayOff.class)
                .filter((shift, dayOff) -> shift.date == dayOff.date && shift.employee == dayOff.employee)
                .penalize("Shift on an off-day", SimpleScore.ONE);
    }
----

The following figure illustrates both these examples:

image::ConstraintStreams/constraintStreamFilter.png[align="center"]

[NOTE]
====
For performance reasons, using the <<constraintStreamsJoin,join>> building block with the appropriate `Joiner` is
preferrable when possible.
Using a `Joiner` creates only the constraint matches that are necessary, while filtered join creates all possible
constraint matches and only then filters some of them out.
====

The following functions are required for filtering constraint streams of different cardinality:

|===
|Cardinality|Filtering Predicate
|1          |`java.util.function.Predicate<A>`
|2          |`java.util.function.BiPredicate<A, B>`
|3          |`org.optaplanner.core.api.function.TriPredicate<A, B, C>`
|4          |`org.optaplanner.core.api.function.QuadPredicate<A, B, C, D>`
|===


[[constraintStreamsJoin]]
=== Joining

Joining is a way to increase <<constraintStreamsCardinality,stream cardinality>> and it is similar to the inner join
operation in SQL. As the following figure illustrates, a join creates a cartesian product of the streams being joined:

image::ConstraintStreams/constraintStreamJoinWithoutJoiners.png[align="center"]

Doing this is inefficient because the resulting stream might contain constraint matches that are of no interest to your
constraint.
Use `Joiner` to restrict your joins only to the matches you are actually interested in, as shown in this example:

[source,java,options="nowrap"]
----
    import static org.optaplanner.core.api.score.stream.Joiners.*;

    ...

    private Constraint shiftOnDayOff(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Shift.class)
                .join(DayOff.class,
                    equal(Shift::getDate, DayOff::getDate),
                    equal(Shift::getEmployee, DayOff::getEmployee))
                .penalize("Shift on an off-day",
                        HardSoftScore.ONE_HARD);
    }
----

The following figure illustrates the behavior:

image::ConstraintStreams/constraintStreamJoinWithJoiners.png[align="center"]

The following `Joiner` types are supported:

- `equal` for joining constraint matches where they `equals()` one another.
- `greaterThan`, `greaterThanOrEqual`, `lessThan` and `lessThanOrEqual` for joining `Comparable` constraint matches per
the prescribed ordering.

For a full list of all supported `Joiner` implementations and their various overloads, refer to the
`org.optaplanner.core.api.score.stream.Joiners` class.

[NOTE]
====
If the other stream might match multiple times, but it must only impact the score once (for each element of the original
stream), use <<constraintStreamsConditionalPropagation,ifExists>> instead.
It does not create cartesian products and therefore generally performs better.
====


[[constraintStreamsGroupingAndCollectors]]
=== Grouping and collectors

Grouping collects items in a stream according to user-provider criteria (also called "group key"), similar to what a
`GROUP BY` SQL clause does. Additionally, some grouping operations also accept one or more `Collector` instances, which
provide various aggregation functions. The following figure illustrates a simple `groupBy()` operation:

image::ConstraintStreams/constraintStreamGroupBy.png[align="center"]

For example, the following code snippet first groups all processes by the computer they run on, sums up all the power
required by the processes on that computer using the `ConstraintCollectors.sum(...)` collector, and finally penalizes
every computer whose processes consume more power than is available.

[source,java,options="nowrap"]
----
    import static org.optaplanner.core.api.score.stream.ConstraintCollectors.*;

    ...

    private Constraint requiredCpuPowerTotal(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_HARD,
                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower());
    }
----

[NOTE]
====
Information might be lost during grouping.
In the previous example, `filter()` and all subsequent operations no longer have direct access to the original
`CloudProcess` instance.
====

There are several collectors available out of the box. You can also provide your own collectors by implementing the
`org.optaplanner.core.api.score.stream.uni.UniConstraintCollector` interface, or its `Bi...`, `Tri...` counterparts.

[collectorsOutOfTheBox]
==== Out-of-the-box collectors

The following section focuses on the collector implementations provided out of the box. This section only describes the
`int`-based variants of the collectors in detail. Many of the collectors also provide variants for other applicable
result data types, such as `long`, `BigDecimal` or `Duration`. You can find a complete list by exploring the
`org.optaplanner.core.api.score.stream.ConstraintCollectors` class.

[collectorsCount]
===== Collecting `count()`

The `ConstraintCollectors.count(...)` counts all elements in a group. For example, the following use of the collector
gives a number of items for two separate groups - one where the talks have unavailable speakers, and one where they
don't.

[source,java,options="nowrap"]
----
    private Constraint speakerAvailability(ConstraintFactory factory) {
        return factory.from(Talk.class)
                .groupBy(Talk::hasAnyUnavailableSpeaker, count())
                .penalize("speakerAvailability",
                        HardSoftScore.ONE_HARD,
                        (hasUnavailableSpeaker, count) -> ...);
    }
----

The return value for this collector is a 32-bit signed integer (`int`). There is also a 64-bit variant, `countLong()`.

[collectorsCountDistinct]
===== Collecting `countDistinct()`

The `ConstraintCollectors.countDistinct(...)` counts any element in a group once, regardless of how many times it
occurs. For example, the following use of the collector gives a number of talks in each unique room.

[source,java,options="nowrap"]
----
    private Constraint roomCount(ConstraintFactory factory) {
        return factory.from(Talk.class)
                .groupBy(Talk::getRoom, countDistinct())
                .penalize("roomCount",
                        HardSoftScore.ONE_SOFT,
                        (room, count) -> ...);
    }
----

The return value for this collector is a 32-bit signed integer (`int`). There is also a 64-bit variant, `countLong()`.

[collectorsSum]
===== Collecting `sum()`

To sum the values of a particular property of all elements in the group, use the `ConstraintCollectors.sum(...)`
collector. The following code snippet first groups all processes by the computer they run on and sums up all the power
required by the processes on that computer using the `ConstraintCollectors.sum(...)` collector.

[source,java,options="nowrap"]
----
    private Constraint requiredCpuPowerTotal(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_SOFT,
                        (computer, requiredCpuPower) -> requiredCpuPower);
    }
----

The return value for this collector is a 32-bit signed integer (`int`). There are also the following variants:

* 64-bit variant `sumLong()`
* `java.math.BigDecimal`-based variant `sumBigDecimal()`
* `java.math.BigInteger`-based variant `sumBigInteger()`
* `java.time.Duration`-based variant `sumDuration()`
* `java.time.Period`-based variant `sumPeriod()`
* generic `sum()` variant for summing up custom types.


[collectorsMinMax]
===== Minimums and maximums

To extract the minimum or maximum of a group, use the `ConstraintCollectors.min(...)` and
`ConstraintCollectors.max(...)` collectors respectively.

These collectors operate on values of properties which are `Comparable` (such as `Integer`, `String` or `Duration`),
although there are also variants of these collectors which allow you to provide your own `Comparator`.

The following example finds a computer which runs the most power-demanding process:

[source,java,options="nowrap"]
----
    private Constraint computerWithBiggestProcess(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, max(CloudProcess::getRequiredCpuPower))
                .penalize("computerWithBiggestProcess",
                        HardSoftScore.ONE_HARD,
                        (computer, biggestProcess) -> ...);
    }
----

[NOTE]
====
`Comparator` and `Comparable` implementations used with `min(...)` and `max(...)` constraint collectors are expected to
be consistent with `equals(...)`.
See https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html[Javadoc for `Comparable`] to learn more.
====


[collectorsCollection]
===== Collection collectors

To extract all elements in the group into a collection, use the `ConstraintCollectors.toList(...)` and
`ConstraintCollectors.toSet(...)` collectors respectively.
`ConstraintCollectors.toCollection(...)` enables you to use a custom `Collection` implementation.

The following example retrieves all processes running on a computer in a `List`:

[source,java,options="nowrap"]
----
    private Constraint computerWithBiggestProcess(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, toList())
                .penalize("computerAndItsProcesses",
                        HardSoftScore.ONE_HARD,
                        (computer, processList) -> ...);
    }
----

[NOTE]
====
The iteration order of elements in the resulting collection is not guaranteed to be stable.
To achieve stable iteration order, use `ConstraintCollectors.toCollection()` together with a sorted collection, such as `TreeSet`.
====


[[constraintStreamsConditionalPropagation]]
=== Conditional propagation

Conditional propagation enables you to exclude constraint matches from the constraint stream based on the presence or
absence of some other object.

image::ConstraintStreams/constraintStreamIfExists.png[align="center"]

The following example penalizes computers which have at least one process running:

[source,java,options="nowrap"]
----
    private Constraint runningComputer(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudComputer.class)
                .ifExists(CloudProcess.class, Joiners.equal(Function.identity(), CloudProcess::getComputer))
                .penalize("runningComputer",
                        HardSoftScore.ONE_SOFT,
                        computer -> ...);
    }
----

Note the use of the `ifExists()` building block.
On `UniConstraintStream`, the `ifExistsOther()` building block is also available which is useful in situations where the
`from()` constraint match type is the same as the `ifExists()` type.

Conversely, if the `ifNotExists()` building block is used (as well as the `ifNotExistsOther()` building block on
`UniConstraintStream`) you can achieve the opposite affect:

[source,java,options="nowrap"]
----
    private Constraint unusedComputer(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudComputer.class)
                .ifNotExists(CloudProcess.class, Joiners.equal(Function.identity(), CloudProcess::getComputer))
                .penalize("unusedComputer",
                        HardSoftScore.ONE_HARD,
                        computer -> ...);
    }
----

Here, only the computers without processes running are penalized.

Also note the use of the `Joiner` class to limit the constraint matches.
For a description of available joiners, see <<constraintStreamsJoin,joining>>.
Conditional propagation operates much like joining, with the exception of not increasing the
<<constraintStreamsCardinality, stream cardinality>>.
Matches from these building blocks are not available further down the stream.

[NOTE]
====
For performance reasons, using conditional propagation with the appropriate `Joiner` instance is preferable to joining.
While using `join()` creates a cartesian product of the facts being joined, with conditional propagation, the resulting
stream only has at most the original number of constraint matches in it.
Joining should only be used in cases where the other fact is actually required for another operation further down
the stream.
====

[[constraintStreamsMappingTuples]]
=== Mapping tuples

Mapping enables you to transform each tuple in a constraint stream by applying a mapping function to it.
The result of such mapping is `UniConstraintStream` of the mapped tuples.

[source,java,options="nowrap"]
----
    private Constraint computerWithBiggestProcess(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class) // UniConstraintStream<CloudProcess>
                .map(CloudProcess::getComputer)           // UniConstraintStream<CloudComputer>
                ...
    }
----

[NOTE]
====
In the example above, the mapping function produces duplicate tuples if two different `CloudProcess`es share a single `CloudComputer`.
That is, such `CloudComputer` will appear in the resulting constraint stream twice.
See <<constraintStreamsDealingWithDuplicateTuplesUsingDistinct,`distinct()`>> for how to deal with duplicate tuples.
====

[[constraintStreamsDesigningMappingFunction]]
==== Designing the mapping function

When designing the mapping function, follow these guidelines for optimal performance:

* Keep the function pure.
  The mapping function should only depend on its input.
  That is, given the same input, it always returns the same output.
* Keep the function bijective.
  No two input tuples should map to the same output tuple, or to tuples that are equal.
  Not following this recommendation creates a constraint stream with duplicate tuples,
  and may force you to use <<constraintStreamsDealingWithDuplicateTuplesUsingDistinct,`distinct()`>> later.
* Use immutable data carriers.
  The tuples returned by the mapping function should be immutable and identified by their contents and nothing else.
  If two tuples carry objects which equal one another,
  those two tuples should likewise equal and preferably be the same instance.

[[constraintStreamsDealingWithDuplicateTuplesUsingDistinct]]
==== Dealing with duplicate tuples using `distinct()`

As a general rule, tuples in constraint streams will be distinct.
That is, no two tuples that equal one another.
However, certain operations such as <<constraintStreamsMappingTuples,tuple mapping>> may produce constraint streams
where that is not true.

If a constraint stream produces duplicate tuples, you can use the `distinct()` building block
to have the duplicate copies eliminated.

[source,java,options="nowrap"]
----
    private Constraint computerWithBiggestProcess(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudProcess.class) // UniConstraintStream<CloudProcess>
                .map(CloudProcess::getComputer)           // UniConstraintStream<CloudComputer>
                .distinct()                               // The same, each CloudComputer just once.
                ...
    }
----

[NOTE]
====
There is a performance cost to `distinct()`.
For optimal performance, don't use constraint stream operations that produce duplicate tuples, to avoid the need to call `distinct()`.
====

[[constraintStreamsTesting]]
== Testing a constraint stream

Constraint streams include the Constraint Verifier unit testing harness.
To use it, first add a test scoped dependency to the `optaplanner-test` JAR.


[[constraintStreamsTestingIsolatedConstraints]]
=== Testing constraints in isolation

Consider the following constraint stream:

[source,java,options="nowrap"]
----
    protected Constraint horizontalConflict(ConstraintFactory factory) {
        return factory
                .fromUniquePair(Queen.class, equal(Queen::getRowIndex))
                .penalize("Horizontal conflict", SimpleScore.ONE);
    }
----

The following example uses the Constraint Verifier API to create a simple unit test for the preceding constraint stream:

[source,java,options="nowrap"]
----
    private ConstraintVerifier<NQueensConstraintProvider, NQueens> constraintVerifier
            = ConstraintVerifier.build(new NQueensConstraintProvider(), NQueens.class, Queen.class);

    @Test
    public void horizontalConflictWithTwoQueens() {
        Row row1 = new Row(0);
        Column column1 = new Column(0);
        Column column2 = new Column(1);
        Queen queen1 = new Queen(0, row1, column1);
        Queen queen2 = new Queen(1, row1, column2);
        constraintVerifier.verifyThat(NQueensConstraintProvider::horizontalConflict)
                .given(queen1, queen2)
                .penalizesBy(1);
    }
----

This test ensures that the horizontal conflict constraint assigns a penalty of `1` when there are two queens on the same
row.
The following line creates a shared `ConstraintVerifier` instance and initializes the instance with the
`NQueensConstraintProvider`:


[source,java,options="nowrap"]
----
    private ConstraintVerifier<NQueensConstraintProvider, NQueens> constraintVerifier
            = ConstraintVerifier.build(new NQueensConstraintProvider(), NQueens.class, Queen.class);
----

The `@Test` annotation indicates that the method is a unit test in a testing framework of your choice.
Constraint Verifier works with many testing frameworks including JUnit and AssertJ.

The first part of the test prepares the test data.
In this case, the test data includes two instances of the `Queen` planning entity and their dependencies
(`Row`, `Column`):

[source,java,options="nowrap"]
----
        Row row1 = new Row(0);
        Column column1 = new Column(0);
        Column column2 = new Column(1);
        Queen queen1 = new Queen(0, row1, column1);
        Queen queen2 = new Queen(1, row1, column2);
----

Further down, the following code test the constraint:

[source,java,options="nowrap"]
----
    constraintVerifier.verifyThat(NQueensConstraintProvider::horizontalConflict)
            .given(queen1, queen2)
            .penalizesBy(1);
----

The `verifyThat(...)` call is used to specify a method on the `NQueensConstraintProvider` class which is under test.
This method must be visible to the test class, which the Java compiler will enforce.

The `given(...)` call is used to enumerate all the facts that the constraint stream will operate on.
In this case, the `given(...)` call takes the `queen1` and `queen2` instances previously created.
Alternatively, you can use a `givenSolution(...)` method here and provide a planning solution instead.

Finally, the `penalizesBy(...)` call completes the test, making sure that the horizontal conflict constraint, given
one `Queen`, results in a penalty of `1`.
This number is a product of multiplying the match weight, as defined in the constraint stream, by the number of matches.

Alternatively, you can use a `rewardsWith(...)` call to check for rewards instead of penalties.
The method to use here depends on whether the constraint stream in question is terminated with a `penalize` or a
`reward` building block.


[[constraintStreamsTestingAllConstraints]]
=== Testing all constraints together

In addition to testing individual constraints, you can test the entire `ConstraintProvider` instance.
Consider the following test:

[source,java,options="nowrap"]
----
    @Test
    public void givenFactsMultipleConstraints() {
        Queen queen1 = new Queen(0, row1, column1);
        Queen queen2 = new Queen(1, row2, column2);
        Queen queen3 = new Queen(2, row3, column3);
        constraintVerifier.verifyThat()
                .given(queen1, queen2, queen3)
                .scores(SimpleScore.of(-3));
    }
----

There are only two notable differences to the previous example.
First, the `verifyThat()` call takes no argument here, signifying that the entire `ConstraintProvider` instance is
being tested.
Second, instead of either a `penalizesBy()` or `rewardsWith()` call, the `scores(...)` method is used.
This runs the `ConstraintProvider` on the given facts and returns a sum of `Score`s of all constraint matches resulting
from the given facts.

Using this method, you ensure that the constraint provider does not miss any constraints and that the scoring function
remains consistent as your code base evolves.

[[constraintStreamsTestingQuarkus]]
=== Testing in Quarkus

If you are using the `optaplanner-quarkus` extension, you can inject the `ConstraintVerifier` in your tests.

[source,java,options="nowrap"]
----
@QuarkusTest
public class MyConstraintProviderTest {
    @Inject
    ConstraintVerifier<MyConstraintProvider, MyPlanningSolution> constraintProvider;
}
----

[[constraintStreamsTestingSpringBoot]]
=== Testing in Spring Boot

If you are using the `optaplanner-spring-boot-starter` module, you can autowire the `ConstraintVerifier` in your tests.

[source,java,options="nowrap"]
----
@SpringBootTest
public class MyConstraintProviderTest {
    @Autowired
    ConstraintVerifier<MyConstraintProvider, MyPlanningSolution> constraintProvider;
}
----

[[constraintStreamsImplementations]]
== Variant implementation types

Constraint streams come in two flavors, a default implementation using Drools under the hood and a pure Java-based
implementation called _Bavet_.
The Drools-based implementation is more feature-complete.
Both of these variants implement the same `ConstraintProvider` API.
No Java code changes are necessary to switch between the two.

Bavet is an experimental implementation that focuses on raw speed and provides superior performance.
However, it lacks features and therefore many of the <<examplesOverview,examples>> are not supported.
To try it out, implement the `ConstraintProvider` interface and use the following in your solver config:

[source,xml,options="nowrap"]
----
    <solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
      <scoreDirectorFactory>
        <constraintStreamImplType>BAVET</constraintStreamImplType>
        <constraintProviderClass>com.example.MyConstraintProvider</constraintProviderClass>
      </scoreDirectorFactory>
      ...
    </solver>
----
