OptaPlanner logo
  • Download
  • Learn
    • Documentation
    • Videos

    • Use cases
    • Compatibility
    • Testimonials and case studies
  • Get help
  • Blog
  • Source
  • Team
  • Services
  • Star
  • T
  • L
  • F
  • YT
Fork me on GitHub

Upgrade recipe 6.0

OptaPlanner’s public API classes are backwards compatible (per series), but users often also use impl classes (which are documented in the reference manual too). This upgrade recipe minimizes the pain to upgrade your code and to take advantage of the newest features in OptaPlanner 8.

Legend

Every upgrade note has an indication how likely your code will be affected by that change:

  • Major Likely to affect your code.
  • Minor Unlikely to affect your code (especially if you followed the examples), unless you have hacks.
  • Impl detail Will not affect your code, unless you have very deep hacks.
  • Recommended Not a backward incompatible change, but you probably want to do this.
  • Readme Read this to better understand why the subsequent major changes were made.
  • Automated Can be applied automatically using our migration tooling.

Upgrade from an older version

To upgrade from an older version, first apply the previous upgrade recipes. You will find the order of migration steps bellow:

Note for Red Hat Decision Manager customers

The RHDM version differs from the OptaPlanner version:

RHDM version OptaPlanner version
7.8 7.39
7.9 7.44
7.1 7.48
7.11 8.5 (and 7.52)
7.12 8.11 (and 7.59)
7.13 8.13 (and 7.67)

Automatic upgrade to the latest version

Update your code in seconds, with optaplanner-migration (an OpenRewrite recipe). Try it:

  1. Stash any local changes.
  2. Run this command in your project directory:
    mvn clean org.openrewrite.maven:rewrite-maven-plugin:LATEST:run -Drewrite.recipeArtifactCoordinates=org.optaplanner:optaplanner-migration:8.35.0.Final -Drewrite.activeRecipes=org.optaplanner.migration.ToLatest

    Note: The -Drewrite.recipeArtifactCoordinates might not work, use the more verbose pom.xml approach instead.

  3. Check the local changes and commit them.

It only does upgrade steps with an Automated badge.

From 5.5.0.Final to 6.0.0.Alpha7

Backwards compatible API in sight

Starting from 6.1.0.Final for 6.2, 6.3, …​, planner will have a backwards compatible API. As preparation for that, every part of the API is being reviewed.

Important upgrade advice

Don’t directly upgrade the jars to 6.0.0.Final. In 6.0.0.Beta1, Drools Planner has been renamed to OptaPlanner and the Java package structure has been changed severely. Instead, upgrade the jars to 6.0.0.Alpha9 first. Once that compiles, immediately upgrade the jars to your desired version and continue following the upgrade recipe. The 6.0.0.Alpha9 jars are available in the maven repository and in this zip: https://download.jboss.org/drools/release/6.0.0.Alpha9/drools-planner-distribution-6.0.0.Alpha9.zip

ValueRangeType.FROM_PLANNING_ENTITY_PROPERTY fixed

ValueRangeType.FROM_PLANNING_ENTITY_PROPERTY now works. The regression in 5.5 from 5.4 has been fixed.

Planning variables null when initialized

Planning variables can now have a null value when initialized. If you have any custom hacks for that, take a look at the reference manual how to replace them with a nullable variable.

<assertionScoreDirectorFactory>: config simplified

<assertionScoreDirectorFactory> no longer needs a <scoreDefinition*>: it inherits it from its parent.

Drools and HardAndSoft*Score: usage changed

If you are using Drools and HardAndSoft*Score: ConstraintType no longer has NEGATIVE_HARD, NEGATIVE_SOFT and POSITIVE. It only has HARD and SOFT now. As a result, all negative weights are now a negative number. All positive weights are now a positive number. HardAndSoft*ScoreHolder replaced setHardConstraintsBroken() and setSoftConstraintsBroken() with setHardScore() and setSoftScore().

Before in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.NEGATIVE_HARD,
            $x,
            ...));

After in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.HARD,
            - $x,
            ...));

Before in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.NEGATIVE_SOFT,
            $x,
            ...));

After in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.SOFT,
            - $x,
            ...));

Before in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.POSITIVE,
            - $x,
            ...));

After in *ScoreRules.drl:

    insertLogical(new IntConstraintOccurrence(..., ConstraintType.SOFT,
            $x,
            ...));

Before in *ScoreRules.drl:

rule "hardConstraintsBroken" salience -1
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardConstraintsBroken($hardTotal.intValue());
end
rule "softConstraintsBroken" salience -1
    when
        $softTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_SOFT, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setSoftConstraintsBroken($softTotal.intValue());
end

After in *ScoreRules.drl:

rule "accumulateHardScore" salience -1
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardScore($hardTotal.intValue());
end
rule "accumulateSoftScore" salience -1
    when
        $softTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.SOFT, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setSoftScore($softTotal.intValue());
end

Refactor similarly for LongConstraintOccurrence and longValue().

Solution: new annotation needed

Your Solution implementation class now needs a @PlanningSolution annotation

Before in *.java:

public class ... implements Solution<...Score> {...}

After in *.java:

@PlanningSolution
public class ... implements Solution<...Score> {...}

Solution: cloneSolution(Solution) removed

A Solution no longer need to implement the method cloneSolution(Solution)

Before in *.java:

@PlanningSolution
public class Examination implements Solution<...> {
    ...
    public Examination cloneSolution() {
        Examination clone = new Examination();
        ...
        for (Exam exam : examList) {
            Exam clonedExam = exam.clone();
            ...
        }
        ...
        return clone;
    }
}
public class Exam {
    ...
    public Exam clone() {
        Exam clone = new Exam();
        ...
        return clone;
    }
}

After in *.java, option 1: if you want to use the automatic cloning system:

@PlanningSolution
public class Examination implements Solution<...> {
    ...
}
public class Exam {
    ...
}

After in *.java, option 2: if you want to keep your code:

@PlanningSolution
public class Examination implements Solution<...>, PlanningCloneable<Examination> {
    ...
    public Examination planningClone() {
        Examination clone = new Examination();
        ...
        for (Exam exam : examList) {
            Exam clonedExam = exam.planningClone();
            ...
        }
        ...
        return clone;
    }
}
public class Exam implements PlanningCloneable<Exam> {
    ...
    public Exam planningClone() {
        Exam clone = new Exam();
        ...
        return clone;
    }
}

Custom ScoreDefinition with 3 levels: use HardMediumSoftScoreDefinition

If you’ve defined a custom ScoreDefinition to be able to use 3 score levels of ints, consider using the new HardMediumSoftScoreDefinition instead.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

    <scoreDefinitionClass>...</scoreDefinitionClass>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

    <scoreDefinitionType>HARD_MEDIUM_SOFT</scoreDefinitionType>

Construction Heuristics: behaviour change

The construction heuristics now immediately add all uninitialized entities into the ScoreDirector (so into the WorkingMemory), instead of adding each entity just before initializing it. This change improves consistency and compatibility of construction heuristics with multiple variables, because they can now initialize 1 variable at a time instead of all variables. Also, this change improves support for nullable variables. Unfortunately, this means that in your score rules you might need to add a few or a lot of null checks, which can be annoying if you have a lot of score rules. If you don’t use the constructions heuristics, no changes are required, but they are still highly recommended.

Note: even though your DRL code already needed to be planning variable null-safe (since 5.5), these changes are different and still needed.

No changes needed in *ScoreRules.drl:

when
    $computer : CloudComputer(...) // $computer is never null
    ... : Number(...) from accumulate(
        CloudProcess(computer == $computer, ...), // no null check needed on computer
        sum(...)
    )
then

No changes needed in *ScoreRules.drl:

when
    $room : Room(...) // $room is never null
    $lecture : Lecture(room == $room, ...) // no null check needed on room + period is not (in)directly used
then

Before in *ScoreRules.drl:

when
    ...
    $bedDesignation : BedDesignation(..., $room : room) // room uses bed indirectly: room is null if bed is null
    ...
then

After in *ScoreRules.drl:

when
    ...
    $bedDesignation : BedDesignation(..., $room : room, bed != null)
    ...
then

Before in *ScoreRules.drl:

when
    $leftLecture : Lecture(..., $period : period, $room : room) // null check needed on period and room
    $rightLecture : Lecture(period == $period, room == $room, ...)
then

After in *ScoreRules.drl:

when
    $leftLecture : Lecture(..., period != null, $period : period, room != null, $room : room)
    $rightLecture : Lecture(period == $period, room == $room, ...) // no null check needed on period and room
then

@PlanningVariable: uninitializedEntityFilter renamed

@PlanningVariable’s property uninitializedEntityFilter has been renamed to reinitializeVariableEntityFilter

Before in *.java:

@PlanningVariable(uninitializedEntityFilter = ...)

After in *.java:

@PlanningVariable(reinitializeVariableEntityFilter = ...)

Default*Score: refactored

The Default*Score class have become more encapsulated. In the unlikely case that you’ve used any of them directly, do these changes:

Before in *.java:

... = DefaultHardAndSoftScore.valueOf(-100);

After in *.java:

... = DefaultHardAndSoftScore.valueOf(-100, Integer.MIN_VALUE);

Before in *.java:

... = new DefaultHardAndSoftScore(-100, -20);

After in *.java:

... = DefaultHardAndSoftScore.valueOf(-100, -20);

PlanningEntityDifficultyWeightFactory replaced

The interface PlanningEntityDifficultyWeightFactory has been replaced by SelectionSorterWeightFactory. The method createDifficultyWeight has been replaced by the method createSorterWeight. Sorting direction (difficulty ascending) has not changed.

Before in *.java:

public class QueenDifficultyWeightFactory implements PlanningEntityDifficultyWeightFactory {
    public Comparable createDifficultyWeight(Solution solution, Object planningEntity) {
        NQueens nQueens = (NQueens) solution;
        Queen queen = (Queen) planningEntity;
        ...
        return ...;
    }
    ...
}

After in *.java:

public class QueenDifficultyWeightFactory implements SelectionSorterWeightFactory<NQueens, Queen> {
    public Comparable createSorterWeight(NQueens nQueens, Queen queen) {
        ...
        return ...;
    }
    ...
}

PlanningValueStrengthWeightFactory: replaced

The interface PlanningValueStrengthWeightFactory has been replaced by SelectionSorterWeightFactory. The method createStrengthWeight has been replaced by the method createSorterWeight. Sorting direction (strength ascending) has not changed.

Before in *.java:

public class RowStrengthWeightFactory implements PlanningValueStrengthWeightFactory {
    public Comparable createStrengthWeight(Solution solution, Object planningValue) {
        NQueens nQueens = (NQueens) solution;
        Row row = (Queen) planningValue;
        ...
        return ...;
    }
    ...
}

After in *.java:

public class RowStrengthWeightFactory implements SelectionSorterWeightFactory<NQueens, Row> {
    public Comparable createSorterWeight(NQueens nQueens, Row row) {
        ...
        return ...;
    }
    ...
}

EnvironmentMode: DEBUG and TRACE renamed

The EnvironmentMode DEBUG and TRACE have been renamed to FAST_ASSERT and FULL_ASSERT to avoid confusion with the logging levels debug and trace.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<environmentMode>DEBUG</environmentMode>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<environmentMode>FAST_ASSERT</environmentMode>

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<environmentMode>TRACE</environmentMode>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<environmentMode>FULL_ASSERT</environmentMode>

<entityFilterClass> renamed

Configuration property <entityFilterClass> has been renamed to <filterClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <entityFilterClass>...</entityFilterClass>
</entitySelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <filterClass>...</filterClass>
</entitySelector>

<moveFilterClass> renamed

Configuration property <moveFilterClass> has been renamed to <filterClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<...MoveSelector>
  <moveFilterClass>...</moveFilterClass>
</...MoveSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<...MoveSelector>
  <filterClass>...</filterClass>
</...MoveSelector>

<entityProbabilityWeightFactoryClass> renamed

Configuration property <entityProbabilityWeightFactoryClass> has been renamed to <probabilityWeightFactoryClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <entityProbabilityWeightFactoryClass>...</entityProbabilityWeightFactoryClass>
</entitySelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <probabilityWeightFactoryClass>...</probabilityWeightFactoryClass>
</entitySelector>

<valueProbabilityWeightFactoryClass> renamed

Configuration property <valueProbabilityWeightFactoryClass> has been renamed to <probabilityWeightFactoryClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<valueSelector>
  <valueProbabilityWeightFactoryClass>...</valueProbabilityWeightFactoryClass>
</valueSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<valueSelector>
  <valueProbabilityWeightFactoryClass>...</valueProbabilityWeightFactoryClass>
</valueSelector>

<moveProbabilityWeightFactoryClass> renamed

Configuration property <moveProbabilityWeightFactoryClass> has been renamed to <probabilityWeightFactoryClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<...MoveSelector>
  <moveProbabilityWeightFactoryClass>...</moveProbabilityWeightFactoryClass>
</...MoveSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<...MoveSelector>
  <probabilityWeightFactoryClass>...</probabilityWeightFactoryClass>
</...MoveSelector>

<planningEntityClass> renamed

For <entitySelector>, configuration property <planningEntityClass> has been renamed to <entityClass>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <planningEntityClass>...Lecture</planningEntityClass>
</entitySelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<entitySelector>
  <entityClass>...Lecture</entityClass>
</entitySelector>

<planningVariableName> renamed

Configuration property <planningVariableName> has been renamed to <variableName>.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<valueSelector>
  <planningVariableName>period</planningVariableName>
</valueSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<valueSelector>
  <variableName>period</variableName>
</valueSelector>

HardAndSoftScore renamed

HardAndSoftScore has been renamed to HardSoftScore. Similarly, HardAndSoftLongScore has been renamed to HardSoftLongScore. The package, ScoreDefinitionType, *ScoreDefinition and *ScoreDefinition have been renamed accordingly.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>

Before in *.java:

import org.drools.planner.core.score.buildin.hardandsoft.HardAndSoftScore;
...
public class CloudBalance ... implements Solution<HardAndSoftScore> {
    ...
    private HardAndSoftScore score;
    public HardAndSoftScore getScore() {
        return score;
    }
    public void setScore(HardAndSoftScore score) {
        this.score = score;
    }
}

After in *.java:

import org.drools.planner.core.score.buildin.hardsoft.HardSoftScore;
...
public class CloudBalance ... implements Solution<HardSoftScore> {
    ...
    private HardSoftScore score;
    public HardSoftScore getScore() {
        return score;
    }
    public void setScore(HardSoftScore score) {
        this.score = score;
    }
}

Before in *.drl:

import org.drools.planner.core.score.buildin.hardandsoft.HardAndSoftScoreHolder;
global HardAndSoftScoreHolder scoreHolder;

After in *.drl:

import org.drools.planner.core.score.buildin.hardsoft.HardSoftScoreHolder;
global HardSoftScoreHolder scoreHolder;

Before in *ScoreCalculator.java:

public HardAndSoftScore calculateScore() {
    return DefaultHardAndSoftScore.valueOf(hardScore, softScore);
}

After in *ScoreCalculator.java:

public HardSoftScore calculateScore() {
    return DefaultHardSoftScore.valueOf(hardScore, softScore);
}

Default*Score classes removed

The Default*Score classes have been removed: they have been merged into their *Score interface.

Before in *.java:

... DefaultSimpleScore.valueOf(...)
... DefaultSimpleDoubleScore.valueOf(...)
... DefaultHardSoftScore.valueOf(...)
... DefaultHardSoftLongScore.valueOf(...)
... DefaultHardMediumSoftScore.valueOf(...)

After in *.java:

... SimpleScore.valueOf(...)
... SimpleDoubleScore.valueOf(...)
... HardSoftScore.valueOf(...)
... HardSoftLongScore.valueOf(...)
... HardMediumSoftScore.valueOf(...)

XStream serialization of a Solution

If you store your solutions as XML and reused the example’s XStream serialization technique, you probably want to have a score serialized as such:

<score>0hard/-130870soft</score>

instead of the current way (which writes the full classname of the Score implementation in the XML).

Before in *.java:

public class CloudBalance ... implements Solution<SimpleScore> {
    ...
    private SimpleScore score;
    ...
}

After in *.java:

public class NQueens ... implements Solution<SimpleScore> {
    ...
    @XStreamConverter(value = XStreamScoreConverter.class, types = {SimpleScoreDefinition.class})
    private SimpleScore score;
    ...
}

Before in *.java:

public class CloudBalance ... implements Solution<HardSoftScore> {
    ...
    private HardSoftScore score;
    ...
}

After in *.java:

public class CloudBalance ... implements Solution<HardSoftScore> {
    ...
    @XStreamConverter(value = XStreamScoreConverter.class, types = {HardSoftScoreDefinition.class})
    private HardSoftScore score;
    ...
}

XStream serialization of a Solution: file impact

If you store your solutions as XML and reused the example’s XStream serialization technique, then you’ll also need to change all those xml files which mention the full score class name. Here’s a bash script to automate that change to the new @XStreamConverter way in the recommended change above:

for f in `*.xml`
do
    sed 's/<score class="[^"]*Score"/<score/g' $f > $f.modifiedMigration
    sed ':a;N;$!ba;s/>\n *<hardScore>/>/g' $f.modifiedMigration | sed ':a;N;$!ba;s/<\/hardScore>\n *<softScore>/hard\//g' | sed ':a;N;$!ba;s/<\/softScore>\n *<\/score>/soft<\/score>/g' > $f
    rm -f $f.modifiedMigration
done

Custom ScoreDefinition: formatScore(Score) added

Interface ScoreDefinition has a new method formatScore(Score). It’s implemented by default by AbstractScoreDefinition to use Score.toString().

XStreamProblemIO: moved

XStreamProblemIO has moved package

Before in *.java:

import org.drools.planner.benchmark.core.XStreamProblemIO;

After in *.java:

import org.drools.planner.persistence.xstream.XStreamProblemIO;

ValueRange and ValueRangeType: moved

ValueRange and ValueRangeType have moved package

Before in *.java:

import org.drools.planner.api.domain.variable.ValueRange;
import org.drools.planner.api.domain.variable.ValueRangeType;

After in *.java:

import org.drools.planner.api.domain.value.ValueRange;
import org.drools.planner.api.domain.value.ValueRangeType;

ScoreDefinition: getScoreClass() added

Interface ScoreDefinition has a new method getScoreClass().

After in *ScoreDefinition.java:

public Class<HardSoftScore> getScoreClass() {
    return HardSoftScore.class;
}

From 6.0.0.Alpha7 to 6.0.0.Alpha8

Config properties null by default

If you’re using solver configuration by API (instead of XML): the Config properties are now null by default.

Before in *.java:

    TerminationConfig terminationConfig = solverConfig.getTerminationConfig();

After in *.java:

    TerminationConfig terminationConfig = new TerminationConfig();
    solverConfig.setTerminationConfig(terminationConfig);

Generally this applies to ScoreDirectorFactoryConfig, AcceptorConfig, ForagerConfig, EntitySelectorConfig, ValueSelectorConfig, …​

Before in *.java:

    FooConfig fooConfig = ...Config.getFooConfig();

After in *.java:

    FooConfig fooConfig = new FooConfig();
    ...Config.setFooConfig(fooConfig);

ScoreDirectorFactoryConfig: setScoreDefinition(…​) removed

XML solver configuration: ScoreDirectorFactoryConfig no longer supports setScoreDefinition(…​). Everyone used setScoreDefinitionClass(…​) instead.

Before in *ScoreDefinition.java:

scoreDirectorFactoryConfig.setScoreDefinition(...);

ScoreDirectorFactoryConfig: setSimpleScoreCalculator(…​) removed

XML solver configuration: ScoreDirectorFactoryConfig no longer supports setSimpleScoreCalculator(…​). Everyone used setSimpleScoreCalculatorClass(…​) instead, except maybe for score weighting parametrization, which can be done through the Solution (which enables real-time tweaking), see the InstitutionParametrization in the examples.

Before in *ScoreDefinition.java:

scoreDirectorFactoryConfig.setSimpleScoreCalculator(...);

From 6.0.0.Alpha9 to 6.0.0.Beta1

Drools Planner has been renamed to OptaPlanner

Drools Planner has been renamed to OptaPlanner. See the official announcement here: https://www.optaplanner.org/community/droolsPlannerRenamed.html

Maven dependencies renamed

The maven dependencies their groupId and artifactId’s have been renamed.

Before in pom.xml:

<dependency>
    <groupId>org.drools.planner</groupId>
    <artifactId>drools-planner-core</artifactId>
    ...
</dependency>

After in pom.xml:

<dependency>
    <groupId>org.optaplanner</groupId>
    <artifactId>optaplanner-core</artifactId>
    ...
</dependency>

Before in pom.xml:

<dependency>
    <groupId>org.drools.planner</groupId>
    <artifactId>drools-planner-benchmark</artifactId>
    ...
</dependency>

After in pom.xml:

<dependency>
    <groupId>org.optaplanner</groupId>
    <artifactId>optaplanner-benchmark</artifactId>
    ...
</dependency>

And resync your IDE (IntelliJ, Eclipse, NetBeans) from the pom.xml files. If you’re still using ANT, replace drools-planner-*.jar with optaplanner-*.jar and adjust your ANT script and your IDE’s classpath accordingly.

Note: because of package name changes (see below), you’ll get a lot of compile errors at this point.

Logging category renamed

The logging category org.drools.planner has been renamed to org.optaplanner

Before in logback*.xml:

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

After in logback*.xml:

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

Similar for log4j files.

GitHub repository renamed

The GitHub repository has been renamed. Before: https://github.com/kiegroup/drools-planner

After:
  https://github.com/kiegroup/optaplanner

Package org.drools.planner renamed

The package org.drools.planner has been renamed to org.optaplanner

Before in *.java, *.drl:

import org.drools.planner...

After in *.java, *.drl:

import org.optaplanner...

Before in *.java:

"/org/drools/planner/..."

After in *.java:

"/org/optaplanner/..."

Before in *.xml:

<...>org.drools.planner...</...>

After in *.xml:

<...>org.optaplanner...</...>

Note: because of other package name changes (see below), you’ll get a lot of compile errors after these changes.

Packages split up into api, config and implementation classes

The packages now make a clear distinction between api, config and implementation classes. Starting from 6.1 for future versions (6.2, 6.3, …​):

  • The api namespaced classes will be backwards compatible.

  • The config namespaced classes will be backwards compatible on an XML level only.

  • The impl namespaced classes will NOT be backwards compatible.

Also, each artifact now has a unique package namespace. For example:

  • optaplanner-core*.jar:

    • org.optaplanner.core: this package contains all classes from this jar

      • .api

      • .config

      • .impl

  • optaplanner-benchmark*.jar:

    • org.optaplanner.benchmark: this package contains all classes from this jar

      • .api

      • .config

      • .impl

Package org.optaplanner.core renamed

The package org.optaplanner.core has been renamed to org.optaplanner.core.impl

Before in .java, .drl:

import org.optaplanner.core...

After in .java, .drl:

import org.optaplanner.core.impl...

Before in *.java:

"/org/optaplanner/core/..."

After in *.java:

"/org/optaplanner/core/impl/..."

Before in *.xml:

<...>org.optaplanner.core...</...>

After in *.xml:

<...>org.optaplanner.core.impl...</...>

Package org.optaplanner.api renamed

The package org.optaplanner.api has been renamed to org.optaplanner.core.api

Before in *.java, *.drl:

import org.optaplanner.api...

After in *.java, *.drl:

import org.optaplanner.core.api...

Before in *.java:

"/org/optaplanner/api/..."

After in *.java:

"/org/optaplanner/core/api/..."

Before in *.xml:

<...>org.optaplanner.api...</...>

After in *.xml:

<...>org.optaplanner.core.api...</...>

Package org.optaplanner.config renamed

The package org.optaplanner.config has been renamed to org.optaplanner.core.config

Before in *.java, *.drl:

import org.optaplanner.config...

After in \*.java, *.drl:

import org.optaplanner.core.config...

Before in *.java:

"/org/optaplanner/config/..."

After in *.java:

"/org/optaplanner/core/config/..."

Before in *.xml:

<...>org.optaplanner.config...</...>

After in *.xml:

<...>org.optaplanner.core.config...</...>

Package org.optaplanner.benchmark.core renamed

The package org.optaplanner.benchmark.core has been renamed to org.optaplanner.benchmark.impl

Before in *.java, *.drl:

import org.optaplanner.benchmark.core...

After in *.java, *.drl:

import org.optaplanner.benchmark.impl...

Solver moved

The interface Solver has been moved from the package …​core.impl to …​core.api.solver

Before in *.java:

import org.optaplanner.core.impl.Solver;

After in *.java:

import org.optaplanner.core.api.solver.Solver;

SolverFactory moved

The interface SolverFactory has been moved from the package …​core.config to …​core.api.solver

Before in *.java:

import org.optaplanner.core.config.SolverFactory;

After in *.java:

import org.optaplanner.core.api.solver.SolverFactory;

EnvironmentMode and XmlSolverFactory moved

The classes EnvironmentMode and XmlSolverFactory has been moved from the package …​core.config to …​core.config.solver

Before in *.java:

import org.optaplanner.core.config.EnvironmentMode;
import org.optaplanner.core.config.XmlSolverFactory;

After in *.java:

import org.optaplanner.core.config.solver.EnvironmentMode;
import org.optaplanner.core.config.solver.XmlSolverFactory;

XmlPlannerBenchmarkFactory replaced

Use the interface PlannerBenchmarkFactory in favor of XmlPlannerBenchmarkFactory

Before in *.java:

XmlPlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory("...BenchmarkConfig.xml");

After in *.java:

PlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory("...BenchmarkConfig.xml");

Score and ScoreHolder moved

The interfaces Score and ScoreHolder and their subclasses have been promoted from the impl to the api package.

Before in *.java and *.drl:

import org.optaplanner.core.impl.score...Score;
import org.optaplanner.core.impl.score...SimpleScore;
import org.optaplanner.core.impl.score...HardAndSoftScore;
...

After in *.java and *.drl:

import org.optaplanner.core.api.score...Score;
import org.optaplanner.core.api.score...SimpleScore;
import org.optaplanner.core.api.score...HardAndSoftScore;
...

Before in *.java and *.drl:

import org.optaplanner.core.impl.score...ScoreHolder;
import org.optaplanner.core.impl.score...SimpleScoreHolder;
import org.optaplanner.core.impl.score...HardAndSoftScoreHolder;
...

After in *.java and *.drl:

import org.optaplanner.core.api.score...ScoreHolder;
import org.optaplanner.core.api.score...SimpleScoreHolder;
import org.optaplanner.core.api.score...HardAndSoftScoreHolder;
...

Note: ScoreDefinition has not been promoted (yet), even though you might use that in @XStreamConverter.

Note: ConstraintOccurrence hasn’t been promoted yet, even though you use it in the drl files.

Drools 4 RuleBase API replaced

Planner has upgraded from the Drools 4 RuleBase API to the Drools 6 KieBase API. It has skipped the Drools 5 KnowledgeBase API.

ScoreDirectorFactoryConfig: setRuleBase() replaced

ScoreDirectorFactoryConfig’s method setRuleBase() has been replaced by setKieBase()

Before in *.java:

RuleBase ruleBase = ...;
solverFactory.getSolverConfig().getScoreDirectorFactoryConfig.setRuleBase(ruleBase);

After in *.java:

KieBase kieBase = ...;
solverFactory.getSolverConfig().getScoreDirectorFactoryConfig.setKieBase(kieBase);

Better way to extract the ConstraintOccurrences

If you used the hack from the examples to extract the ConstraintOccurrences from the guiScoreDirectory.

Before in *.java:

import org.drools.core.ClassObjectFilter;
import org.drools.core.WorkingMemory;
...
        WorkingMemory workingMemory = ((DroolsScoreDirector) guiScoreDirector).getWorkingMemory();
        if (workingMemory == null) {
            return Collections.emptyList();
        }
        Iterator<ConstraintOccurrence> it = (Iterator<ConstraintOccurrence>) workingMemory.iterateObjects(
                new ClassObjectFilter(ConstraintOccurrence.class));
        while (it.hasNext()) {
            ConstraintOccurrence constraintOccurrence = it.next();
            ...
        }

After in *.java:

import org.kie.api.runtime.ClassObjectFilter;
import org.kie.api.runtime.KieSession;
...
        KieSession kieSession = ((DroolsScoreDirector) guiScoreDirector).getKieSession();
        if (kieSession == null) {
            return Collections.emptyList();
        }
        Collection<ConstraintOccurrence> constraintOccurrences = (Collection<ConstraintOccurrence>)
                kieSession.getObjects(new ClassObjectFilter(ConstraintOccurrence.class));
        for (ConstraintOccurrence constraintOccurrence : constraintOccurrences) {
            ...
        }

ConstraintOccurrence replaced

In score DRLs, the insertLogical ConstraintOccurrence technique has been replaced with the ConstraintMatch technique. That new technique is much faster (see blog for details), easier to use and far less error-prone. Unlike ConstraintOccurrence, ConstraintMatch doesn’t care about the equals/hashcode implementations of your classes. Also, the ConstraintMatch and ConstraintMatchTotal instances are designed to be reused outside Planner.

scoreDrls: use ConstraintMatch technique

Change your scoreDrls to use the ConstraintMatch technique. Instead of doing an insertLogical of a ConstraintOccurrence, they now call scoreHolder.add*ConstraintMatch() and no longer need to supply the infamous causes parameter. In the DRL, the LHS (= when parts) remain unchanged: only the RHS (= then parts) change and the accumulate*Score rules are removed. First, backup the old DRL, so you can easily verify that the new DRL works exactly the same as the old one:

cp cloudBalancingScoreRules.drl cloudBalancingScoreRulesOld.drl

Before in *ScoreRules.drl, to delete:

import org.optaplanner.core.impl.score.constraint.IntConstraintOccurrence;
import org.optaplanner.core.impl.score.constraint.ConstraintType;
...
rule "accumulateHardScore"
        salience -1
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardScore($hardTotal.intValue());
end
rule "accumulateSoftScore"
    ...
end

Before in *ScoreRules.drl (hard constraints):

rule "conflictingLecturesSameCourseInSamePeriod"
    when
        ...
    then
        insertLogical(new IntConstraintOccurrence("conflictingLecturesSameCourseInSamePeriod", ConstraintType.HARD,
                -1,
                $leftLecture, $rightLecture));
end

After in *ScoreRules.drl:

rule "conflictingLecturesSameCourseInSamePeriod"
    when
        ...
    then
        scoreHolder.addHardConstraintMatch(kcontext, -1);
end

Before in *ScoreRules.drl (soft constraints):

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

After in *ScoreRules.drl:

rule "computerCost"
    when
        ...
    then
        scoreHolder.addSoftConstraintMatch(kcontext, - $cost);
end

Before in *ScoreRules.drl (SimpleScore):

rule "multipleQueensHorizontal"
    when
        ...
    then
        insertLogical(new UnweightedConstraintOccurrence("multipleQueensHorizontal", $q1, $q2));
end

After in *ScoreRules.drl:

rule "multipleQueensHorizontal"
    when
        ...
    then
        scoreHolder.addConstraintMatch(kcontext, -1);
end

Note: kcontext is a magic variable name automatically made available by Drools Expert in the RHS (= then part).

Note: The causes array parameter (for example $leftLecture, $rightLecture, $computer) is gone, because it is automatically deduced from kcontext.

Warning: Because ConstraintMatch doesn’t do an insertLogical, nor does it depend on the equals/hashcode of your objects, there is a small chance that the ConstraintOccurrence counted a lower score (often unintentional by the author).

To detect this uncomment this code in *SolverConfig.xml to verify that the new DRL works exactly the same as the old one:

<!--<environmentMode>FULL_ASSERT</environmentMode>-->
...
<scoreDirectorFactory>
  ...
  <scoreDrl>...ScoreRules.drl</scoreDrl>
  <!--<assertionScoreDirectorFactory>-->
    <!--<scoreDrl>...ScoreRulesOld.drl</scoreDrl>-->
  <!--</assertionScoreDirectorFactory>-->
</scoreDirectorFactory>

In 6.0 (not in 6.1), the score corruption analysis helps to identify the rule which behaves differently. If it fails, it is because in the old way, 2 or more different fire events of a score rule inserted equal ConstraintOccurrences. In the new way, every fire event of a score rule adds a unique ConstraintMatch (there’s a 1 to 1 relationship - which is expected by most users anyway).

Important: The class ConstraintOccurrence will be removed in 6.1.0.Final, so switch to ConstraintMatch now. The only reason why ConstraintOccurrence has not been removed already, is to make the migration easier: so you can easily verify that after migration to ConstraintMatch, it gives the exact same scores, but faster.

Note: The examples still include their old drl variant too until 6.0.0.Beta4, if you want to use it for comparison.

ConstraintOccurrence outside usage: use ConstraintMatch

If you use ConstraintOccurrence outside of Planner itself, in the gui or your middleware, for example, to show the user the constraint matches, switch to using ConstraintMatch and ConstraintMatchTotal instead.

Before in *.java:

public List<ScoreDetail> getScoreDetailList() {
    if (!(guiScoreDirector instanceof DroolsScoreDirector)) {
        return null;
    }
    Map<String, ScoreDetail> scoreDetailMap = new HashMap<String, ScoreDetail>();
    KieSession kieSession = ((DroolsScoreDirector) guiScoreDirector).getKieSession();
    if (kieSession == null) {
        return Collections.emptyList();
    }
    Collection<ConstraintOccurrence> constraintOccurrences = (Collection<ConstraintOccurrence>)
            kieSession.getObjects(new ClassObjectFilter(ConstraintOccurrence.class));
    for (ConstraintOccurrence constraintOccurrence : constraintOccurrences) {
        ScoreDetail scoreDetail = scoreDetailMap.get(constraintOccurrence.getRuleId());
        if (scoreDetail == null) {
            scoreDetail = new ScoreDetail(constraintOccurrence.getRuleId(), constraintOccurrence.getConstraintType());
            scoreDetailMap.put(constraintOccurrence.getRuleId(), scoreDetail);
        }
        scoreDetail.addConstraintOccurrence(constraintOccurrence);
    }
    List<ScoreDetail> scoreDetailList = new ArrayList<ScoreDetail>(scoreDetailMap.values());
    Collections.sort(scoreDetailList);
    return scoreDetailList;

After in *.java:

public List<ConstraintMatchTotal> getConstraintMatchTotalList() {
    List<ConstraintMatchTotal> constraintMatchTotalList = new ArrayList<ConstraintMatchTotal>(
            guiScoreDirector.getConstraintMatchTotals());
    Collections.sort(constraintMatchTotalList);
    return constraintMatchTotalList;
}

Before in *.java:

... constraintOccurrence.getCauses()

After in *.java:

... constraintMatch.getJustificationList()

Note: the justificationList might have more or different elements than the causes, but it should be possible to extract the same information.

DRL query to extract ConstraintOccurrence: use ConstraintMatch

If you use a DRL query to extract the ConstraintOccurrence, use ConstraintMatch and ConstraintMatchTotal instead.

Before in *.drl:

query "selectAllBrokenRules"
   $brokenRule : IntConstraintOccurrence(constraintType == ConstraintType.HARD)
end

After in *.java:

guiScoreDirector.getConstraintMatchTotals()

Custom ScoreDefinition: buildScoreHolder() changed

If you have a custom ScoreDefinition implementation: the method buildScoreHolder() has changed signature.

Before in *ScoreDefinition.java:

public ScoreHolder buildScoreHolder() {
    return new HardSoftScoreHolder();
}

After in *ScoreDefinition.java:

public ScoreHolder buildScoreHolder(boolean constraintMatchEnabled) {
    return new HardSoftScoreHolder(constraintMatchEnabled);
}

6.0 supports a bunch more score types: it’s easier (as well as recommended) to switch to a built-in one if you can.

From 6.0.0.Beta1 to 6.0.0.Beta2

XStreamSolutionDaoImpl renamed

optaplanner-examples: The class XStreamSolutionDaoImpl has been renamed to XStreamSolutionDao. This should not affect you because you should not be depending on optaplanner-examples.

Before in *.java:

public class ...DaoImpl extends XStreamSolutionDaoImpl {...}

After in *.java:

public class ...Dao extends XStreamSolutionDao {...}

Benchmarker from a FreeMarker Template: usage changed

The API to configure a Benchmarker from a FreeMarker Template has moved to a separate calls and the methods have been renamed.

Before in *.java:

PlannerBenchmarkFactory plannerBenchmarkFactory = new XmlPlannerBenchmarkFactory()
        .configureFromTemplate(benchmarkConfigTemplate);

After in *.java:

PlannerBenchmarkFactory plannerBenchmarkFactory = new FreemarkerXmlPlannerBenchmarkFactory(
        benchmarkConfigTemplate);

<minimalAcceptedSelection> renamed

The <forager> property <minimalAcceptedSelection> has been renamed to <acceptedCountLimit>.

Before in *Config.xml:

<forager>
  <minimalAcceptedSelection>1000</minimalAcceptedSelection>
</forager>

After in *Config.xml:

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

From 6.0.0.Beta2 to 6.0.0.Beta3

Custom ScoreDefinition: Score.power(double) added

If you have a custom ScoreDefinition implementation: the interface Score has a new method power(double).

After in *Score.java:

public HardSoftScore power(double exponent) {
    return new HardSoftScore((int) Math.floor(Math.pow(hardScore, exponent)),
            (int) Math.floor(Math.pow(softScore, exponent)));
}

Custom ScoreDefinition: toDoubleLevels() renamed

If you have a custom Score implementation: Score’s method toDoubleLevels() has been renamed to toLevelNumbers(). Now it returns an array of Number instead of an array of doubles.

Before in *Score.java:

public double[] toDoubleLevels() {
    return new double[]{hardScore, softScore};
}

After in *Score.java:

public Number[] toLevelNumbers() {
    return new Number[]{hardScore, softScore};
}

Late Acceptance: behaviour changed

The LateAcceptanceAcceptor now also accepts any move that improves the current solution. If you use <lateAcceptanceSize> in your config, this will impact your results (normally in a good way).

<planningEntityTabuSize> and <planningValueTabuSize> renamed

The tabu search acceptor properties <planningEntityTabuSize> and <planningValueTabuSize> have been renamed to entityTabuSize and valueTabuSize.

Before in *Config.xml:

<acceptor>
  <planningEntityTabuSize>...</planningEntityTabuSize>
  <fadingPlanningEntityTabuSize>...</fadingPlanningEntityTabuSize>
  <planningValueTabuSize>...</planningValueTabuSize>
  <fadingPlanningValueTabuSize>...</fadingPlanningValueTabuSize>
</acceptor>

After in *Config.xml:

<acceptor>
  <entityTabuSize>...</entityTabuSize>
  <fadingEntityTabuSize>...</fadingEntityTabuSize>
  <valueTabuSize>...</valueTabuSize>
  <fadingValueTabuSize>...</fadingValueTabuSize>
</acceptor>

PlanningEntityTabuAcceptor and PlanningValueTabuAcceptor renamed

The implementation classes PlanningEntityTabuAcceptor and PlanningValueTabuAcceptor have been renamed to EntityTabuAcceptor and ValueTabuAcceptor

<subChainChangeMoveSelector> and <subChainSwapMoveSelector>: <maximumSubChainSize> behaviour changed

<subChainChangeMoveSelector> and <subChainSwapMoveSelector> combined with a <maximumSubChainSize> value did not select all possible moves previously. If you use this, you might want to rerun benchmarks.

From 6.0.0.Beta3 to 6.0.0.Beta4

Descriptor classes: method renamed

*Descriptor classes’ methods have dropped the Planning prefix verbosity: Methods like getPlanningEntityDescriptor() have been renamed to getEntityDescriptor() Methods like getPlanningVariableDescriptor() have been renamed to getVariableDescriptor() Normally, your code should not be using those classes/methods.

<problemStatisticType> BEST_SOLUTION_CHANGED renamed

Benchmarker: the <problemStatisticType> BEST_SOLUTION_CHANGED has been renamed to BEST_SCORE

Before in *BenchmarkConfig.xml:

<problemBenchmarks>
   ...
  <problemStatisticType>BEST_SOLUTION_CHANGED</problemStatisticType>
</problemBenchmarks>

After in *BenchmarkConfig.xml:

<problemBenchmarks>
   ...
  <problemStatisticType>BEST_SCORE</problemStatisticType>
</problemBenchmarks>

ScoreDirector: beforeAllVariablesChanged() and afterAllVariablesChanged() removed

The methods beforeAllVariablesChanged() and afterAllVariablesChanged() have been removed from IncrementalScoreCalculator and ScoreDirector. This was needed to make planning variable listeners work efficiently.

Before in *IncrementalScoreCalculator.java:

public void beforeAllVariablesChanged(Object entity) {
    ...
}
public void afterAllVariablesChanged(Object entity) {
    ...
}

Before in *Move.java:

public void doMove(ScoreDirector scoreDirector) {
    scoreDirector.beforeAllVariablesChanged(lecture);
    lecture.setPeriod(period);
    lecture.setRoom(room);
    scoreDirector.afterAllVariablesChanged(lecture);
}

After in *Move.java:

public void doMove(ScoreDirector scoreDirector) {
    scoreDirector.beforeVariableChanged(lecture, "period"); // because setPeriod() will be called
    scoreDirector.beforeVariableChanged(lecture, "room"); // because setRoom() will be called
    lecture.setPeriod(period);
    lecture.setRoom(room);
    scoreDirector.afterVariableChanged(lecture, "period");
    scoreDirector.afterVariableChanged(lecture, "room");
}

VehicleRouting example rewritten

The VehicleRouting example has been rewritten to take advantage of Variable Listeners. This makes it easier to implement time windowed vehicle routing. A variable listener is triggered when the variable (previousStandstill) changes and updates a shadow variable (vehicle, arrivalTime) accordingly.

<subChainChangeMoveSelector> and <subChainSwapMoveSelector>: behaviour changed

The generic moves <subChainChangeMoveSelector> and <subChainSwapMoveSelector> no longer signal Drools/IncrementalScoreCalculator that an entity has changed when the anchor of an entity changes if none of the actual planning variables of the entity changed. So if you have shadow variable representing the anchor (for example the vehicle in VRP) this might cause score corruption. Instead, add a variable listener to update the shadow variable and signal the ScoreDirector accordingly.

Before in *.java:

public class VrpCustomer ... {
    // Planning variables: changes during planning, between score calculations.
    protected VrpStandstill previousStandstill;
    @PlanningVariable(chained = true) ...
    public VrpStandstill getPreviousStandstill() {
        return previousStandstill;
    }
    public VrpVehicle getVehicle() {
        // HACK
        VrpStandstill firstStandstill = getPreviousStandstill();
        while (firstStandstill instanceof VrpCustomer) {
            if (firstStandstill == this) {
                throw new IllegalStateException("Impossible state"); // fail fast during infinite loop
            }
            firstStandstill = ((VrpCustomer) firstStandstill).getPreviousStandstill();
        }
        return (VrpVehicle) firstStandstill;
    }
    ...
}

After in *.java:

public class VrpCustomer ... {
    // Planning variables: changes during planning, between score calculations.
    protected VrpStandstill previousStandstill;
    // Shadow variable
    protected VrpVehicle vehicle;
    @PlanningVariable(chained = true, variableListenerClasses = {VehicleUpdatingVariableListener.class}) ...
    public VrpStandstill getPreviousStandstill() {
        return previousStandstill;
    }
    public VrpVehicle getVehicle() {
        return vehicle;
    }
    ...
}

To make it easier to implement that listener, a bi-directional relationship was introduced on VrpStandstill, which made VrpStandstill a @PlanningEntity (which effectively makes VrpVehicle a planning entity too) and Solution.getVehicleList()’s method annotated with @PlanningEntityCollectionProperty:

Before in VrpStandstill.java:

public interface VrpStandstill {
    ...
}

After in VrpStandstill.java:

@PlanningEntity
public interface VrpStandstill {
    ...
    @PlanningVariable(mappedBy = "previousStandstill") // Bi-directional relationship. This is the shadow side
    VrpCustomer getNextCustomer();
    void setNextCustomer(VrpCustomer nextCustomer);
}

Before in VrpSchedule.java:

public List<VrpVehicle> getVehicleList() {
    return vehicleList;
}
public Collection<? extends Object> getProblemFacts() {
    ...
    facts.addAll(vehicleList);
    return facts;
}

After in VrpSchedule.java:

@PlanningEntityCollectionProperty
public List<VrpVehicle> getVehicleList() {
    return vehicleList;
}
public Collection<? extends Object> getProblemFacts() {
    ...
    return facts;
}

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

<solver>
  ...
  <planningEntityClass>org.optaplanner.examples.vehiclerouting.domain.VrpCustomer</planningEntityClass>
  ...
</solver>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

<solver>
  ...
  <planningEntityClass>org.optaplanner.examples.vehiclerouting.domain.VrpCustomer</planningEntityClass>
  <planningEntityClass>org.optaplanner.examples.vehiclerouting.domain.VrpStandstill</planningEntityClass>
  ...
</solver>

SolverConfig: setPlanningEntityClassSet() changed

SolverConfig’s method setPlanningEntityClassSet() has changed into setPlanningEntityClassList() because the order is important.

Before in *.java:

... = solverConfig.getPlanningEntityClassSet()
solverConfig.setPlanningEntityClassSet(...)

After in *.java:

... = solverConfig.getPlanningEntityClassList()
solverConfig.setPlanningEntityClassList(...)

From 6.0.0.Beta4 to 6.0.0.Beta5

Extending a Config class: build*() signature changed

If you extended a Config class: the build*() method’s parameters have been wrapped into a HeuristicConfigPolicy instance.

Before in *Config.java:

public ... build...(EnvironmentMode environmentMode, ScoreDefinition scoreDefinition) {

After in *Config.java:

public ... build...(HeuristicConfigPolicy configPolicy) {

From 6.0.0.Beta5 to 6.0.0.CR1

XStreamProblemIO uses a vanilla XStream

XStreamProblemIO has been modified to use a vanilla XStream instance (but still with ID_REFERENCES) instead of a complex construction to avoid an issue that has been fixed meanwhile. Before it did:

xStream = new XStream(new PureJavaReflectionProvider(new FieldDictionary(new NativeFieldKeySorter())));
xStream.setMode(XStream.ID_REFERENCES);

Now it simply does:

xStream = new XStream();
xStream.setMode(XStream.ID_REFERENCES);

Normally this should have no relevant impact on your XML dataset files or your code.

Construction Heuristics rewritten

The construction heuristics have been rewritten from scratch to take advantage of the selector architecture. The basic configuration hasn’t changed much, but power users can now optionally do advanced configuration too. These advanced options allow you to use construction heuristics with multiple entity classes, a higher number of variables, nullable variables, …​

<constructionHeuristicPickEarlyType> renamed

The construction heuristic property <constructionHeuristicPickEarlyType> has been renamed to <pickEarlyType>. The value FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING has been renamed to FIRST_NON_DETERIORATING_SCORE. The <pickEarlyType> has been nested into a <forager> element (similar like for Local Search).

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

  <constructionHeuristic>
    ...
    <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType>
  </constructionHeuristic>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

  <constructionHeuristic>
    ...
    <forager>
      <pickEarlyType>FIRST_NON_DETERIORATING_SCORE</pickEarlyType>
    </forager>
  </constructionHeuristic>

ConstructionHeuristicPickEarlyType moved

The class ConstructionHeuristicPickEarlyType has moved to another package.

Multiple planning variables: impact by Construction Heuristics refactor

If you have multiple variables, the result of the Construction Heuristics is likely to differ. Even though the default still does a cartesian production over the variables for the FIT algorithm you choose, it will order the combinations in the original order (instead of reverse order as the old implementation did by accident), which might cause it to take a totally different path.

Note: Through advanced configuration it’s possible to make the new implementation behave exactly the same as the old, but this is NOT recommended:

  <constructionHeuristic>
     ...
     <queuedEntityPlacer>
       <entitySelector id="placerEntitySelector">
         <cacheType>PHASE</cacheType>
         ...
       </entitySelector>
       <cartesianProductMoveSelector>
         <changeMoveSelector>
           <entitySelector mimicSelectorRef="placerEntitySelector"/>
           <valueSelector>
             <variableName>secondVariable</variableName>
             ...
           </valueSelector>
         </changeMoveSelector>
         <changeMoveSelector>
           <entitySelector mimicSelectorRef="placerEntitySelector"/>
           <valueSelector>
             <variableName>firstVariable</variableName>
             ...
           </valueSelector>
         </changeMoveSelector>
       </cartesianProductMoveSelector>
     </queuedEntityPlacer>
   </constructionHeuristic>

@ValueRange: excludeUninitializedPlanningEntity removed

The @ValueRange property excludeUninitializedPlanningEntity has been removed. Planner now does the right thing automatically for a chained variable.

Before in *.java:

@PlanningVariable(chained = true, ...)
@ValueRanges({...,
        @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "customerList",
                excludeUninitializedPlanningEntity = true)})
public VrpStandstill getPreviousStandstill() {...}

After in *.java:

@PlanningVariable(chained = true, ...)
@ValueRanges({...,
        @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "customerList")})
public VrpStandstill getPreviousStandstill() {...}

If you used it on a non-chained variable (which is highly unlikely), look into ValueSelector filtering and let us know that such use cases exist (by creating a jira in our issue tracker).

ValueRangeType.FROM_SOLUTION_PROPERTY and ValueRangeType.FROM_ENTITY_PROPERTY removed

The annotation @ValueRange on a planning variable has been replaced by @ValueRangeProvider on the providing method itself and @PlanningVariable(valueRangeProviderRefs) on planning variable. ValueRangeType.FROM_SOLUTION_PROPERTY has been removed:

Before in *.java:

@PlanningEntity
public class CloudProcess ... {
    ...
    @PlanningVariable()
    @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "computerList")
    public CloudComputer getComputer() {
        return computer;
    }
}
@PlanningSolution
public class CloudBalance ... {
    ...
    public List<CloudComputer> getComputerList() {
        return computerList;
    }
}

After in *.java:

@PlanningEntity
public class CloudProcess ... {
    ...
    @PlanningVariable(valueRangeProviderRefs = {"computerRange"})
    public CloudComputer getComputer() {
        return computer;
    }
}
@PlanningSolution
public class CloudBalance ... {
    ...
    @ValueRangeProvider(id = "computerRange")
    public List<CloudComputer> getComputerList() {
        return computerList;
    }
}

Consequently, the annotation @ValueRanges has been removed.

Before in *.java:

@PlanningEntity
public class Visit ... {
    ...
    @PlanningVariable(chained = true)
    @ValueRanges({
            @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "domicileList"),
            @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "visitList")})
    public Standstill getPreviousStandstill() {...}
}
@PlanningSolution
public class TravelingSalesmanTour ... {
    ...
    public List<Domicile> getDomicileList() {...}
    @PlanningEntityCollectionProperty
    public List<Visit> getVisitList() {...}
}

After in *.java:

@PlanningEntity
public class Visit ... {
    ...
    @PlanningVariable(chained = true, valueRangeProviderRefs = {"domicileRange", "visitRange"})
    public Standstill getPreviousStandstill() {...}
}
@PlanningSolution
public class TravelingSalesmanTour ... {
    ...
    @ValueRangeProvider(id = "domicileRange")
    public List<Domicile> getDomicileList() {...}
    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "visitRange")
    public List<Visit> getVisitList() {...}
}

ValueRangeType.FROM_ENTITY_PROPERTY has been removed too:

Before in *.java:

@PlanningEntity
public class Allocation ... {
    ...
    @PlanningVariable()
    @ValueRange(type = ValueRangeType.FROM_PLANNING_ENTITY_PROPERTY, planningEntityProperty = "executionModeRange")
    public ExecutionMode getExecutionMode() {
        return executionMode;
    }
    public List<ExecutionMode> getExecutionModeRange() {
        return job.getExecutionModeList();
    }
}

After in *.java:

@PlanningEntity
public class Allocation ... {
    ...
    @PlanningVariable(valueRangeProviderRefs = {"executionModeRange"})
    public ExecutionMode getExecutionMode() {
        return executionMode;
    }
    @ValueRangeProvider(id = "executionModeRange")
    public List<ExecutionMode> getExecutionModeRange() {
        return job.getExecutionModeList();
    }
}

ValueRangeType.UNDEFINED removed

The ValueRangeType.UNDEFINED has been removed: it is no longer supported.

DefaultDecider renamed

DefaultDecider has been renamed to LocalSearchDecider.

From 6.0.0.CR1 to 6.0.0.CR2

From 6.0.0.CR3 to 6.0.0.CR4

Examples data dirs renamed

The examples data dir input has been renamed to import and output has been renamed to export.

VRP example calculations improved

The VRP example’s now calculates the distance and time more accurately. To avoid floating-point arithmetic with HardSoftDoubleScore (which causes rounding errors and score corruption) and to avoid a performance loss with HardSoftBigDecimalScore, all distances and times have been multiplied by 1000 so it can continue to use HardSoftScore (which uses ints).

From 6.0.0.CR4 to 6.0.0.CR5

<acceptor> requires configuration

If the <acceptor> element has no configuration, it now fail-fast. It used to default to entityTabuSize 7. To continue that behaviour, configure it explicitly. For good advice on what to configure, see the docs section "Which optimization algorithms should I use?".

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

  <localSearch>
<acceptor/>
...
  </localSearch>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

  <localSearch>
<acceptor>
  <entityTabuSize>7</entityTabuSize>
</acceptor>
...
  </localSearch>

From 6.0.0.CR5 to 6.0.0.Final

Hospital bed planning (PAS) example does overconstrained planning

The hospital bed planning (PAS) example’s score function has severally changed. It now has more hard constraints and demonstrates overconstrained planning (with a nullable variable).

MoveListFactory generified

The interface MoveListFactory now has a generic type (which extends Solution) that is used as the parameter in the createMoveList() method.

Before in *.java:

public class RowChangeMoveFactory implements MoveListFactory {
    public List<Move> createMoveList(Solution solution) {
        NQueens nQueens = (NQueens) solution;
        ...
    }
}

After in *.java:

public class RowChangeMoveFactory implements MoveListFactory<NQueens> {
    public List<Move> createMoveList(NQueens nQueens) {
        ...
    }
}

ConstraintMatch: getConstraintMatchTotal() removed

ConstraintMatch no longer has a getConstraintMatchTotal() method because a ConstraintMatch can survive many Local Search steps, but its Total (which holds a Set) should not survive. Instead it has getConstraintPackage(), getConstraintName() and getScoreLevel().

Latest release
  • 8.35.0.Final released
    Fri 3 March 2023
Upcoming events
    Add event / Archive
Latest blog posts
  • OptaPlanner 9 is coming
    Tue 21 February 2023
    Lukáš Petrovický
  • Farewell - a new lead
    Tue 15 November 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part II
    Wed 9 November 2022
    Radovan Synek
  • Bavet - A faster score engine for OptaPlanner
    Tue 6 September 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part I.
    Thu 9 June 2022
    Radovan Synek
  • OptaPlanner deprecates score DRL
    Thu 26 May 2022
    Lukáš Petrovický
  • Real-time planning meets SolverManager
    Mon 7 March 2022
    Radovan Synek
  • Blog archive
Latest videos
  • The Vehicle Routing Problem
    Fri 23 September 2022
    Geoffrey De Smet
  • Introduction to OptaPlanner AI constraint solver
    Thu 25 August 2022
    Anna Dupliak
  • On schedule: Artificial Intelligence plans that meet expectations
    Sat 23 July 2022
    Geoffrey De Smet
  • Host your OptaPlanner app on OpenShift (Kubernetes)
    Mon 7 February 2022
    Geoffrey De Smet
  • OptaPlanner - A fast, easy-to-use, open source AI constraint solver for software developers
    Mon 31 January 2022
  • Order picking planning with OptaPlanner
    Fri 31 December 2021
    Anna Dupliak
  • AI lesson scheduling on Quarkus with OptaPlanner
    Thu 18 November 2021
    Geoffrey De Smet
  • Video archive

OptaPlanner is open. All dependencies of this project are available under the Apache Software License 2.0 or a compatible license. OptaPlanner is trademarked.

This website was built with JBake and is open source.

Community

  • Blog
  • Get Help
  • Team
  • Governance
  • Academic research

Code

  • Build from source
  • Issue tracker
  • Release notes
  • Upgrade recipes
  • Logo and branding
CC by 3.0 | Privacy Policy
Sponsored by Red Hat