Toggle navigation OptaPlanner logo
  • Home
  • Download
  • Learn
    • Documentation
    • Videos
    • Slides
    • Training
    • Use cases
    • Compatibility
    • Testimonials and case studies
  • Get help
  • Source
  • Team
  • Services
  • Star
  • @OptaPlanner
  • Fb
Fork me on GitHub

Upgrade recipe 6.3

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

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.

Upgrade from an older version

To upgrade from an older version, first apply the previous upgrade recipes.

Note for Red Hat Decision Manager customers

The RHDM version differs from the OptaPlanner version:

RHDM version OptaPlanner version
7.7 7.33
7.8 7.39
7.9 7.44

From 6.2.0.Final to 6.3.0.Beta1

@InverseRelationShadowVariable for non-chained planning variables

@InverseRelationShadowVariable is now also supported for non-chained planning variables, in which case it the inverse property must be Collection (Set or List). So it’s no longer needed to use a CustomShadowVariable to implement the bi-directional relationship behaviour.

Before in *.java:

public class CloudComputer {
    ...
    @CustomShadowVariable(variableListenerClass = MyCustomInverseVariableListener.class,
            sources = {@CustomShadowVariable.Source(entityClass = CloudProcess.class, variableName = "computer")})
    public List<CloudProcess> getProcessList() {
        return processList;
    }
}

After in *.java:

@PlanningEntity // Shadow variable only
public class CloudComputer {
    ...
    @InverseRelationShadowVariable(sourceVariableName = "computer")
    public List<CloudProcess> getProcessList() {
        return processList;
    }
}

Move.toString() changed

To adhere to Java best practices, which state that an Object.toString() should identify an instance and be short (instead of trying to verbalize its entire state), the Move.toString() methods have been modified to mention the old value too (as well as the entity and the new value). Their notations have also been made consistent:

  • ChangeMove: a {v1 → v2}

  • SwapMove: a {v1} <→ b {v2}

  • PillarChangeMove: [a, b] {v1 → v2}

  • PillarSwapMove: [a, b] {v1} <→ [c, d, e] {v2}

  • TailChainSwapMove: a3 {a2} ←tailChainSwap→ b1 {b0}

  • SubChainChangeMove: [a2..a5] {a1 → b0}

    • Reversing: [a2..a5] {a1 -reversing→ b0}

  • SubChainSwapMove: [a2..a5] {a1} <→ [b1..b3] {b0}

    • Reversing: [a2..a5] {a1} ←reversing→ [b1..b3] {b0}

This mainly affects the logging output. In the examples, the toString() method of planning entities and planning values has been modified accordingly to avoid mentioning the old value twice:

Before in *.java:

public class CloudProcess {
    ...

    public String `toString()` {
        return processName + "@" + computer.getName();
    }
}

After in *.java:

public class CloudProcess {
    ...

    public String toString() {
        return processName;
    }
}

Empty <constructionHeuristic>: default algorithm changed

Default parameter tweaked: an empty <constructionHeuristic> changes its default algorithm from FIRST_FIT to ALLOCATE_ENTITY_FROM_QUEUE. If no entity difficulty comparison and no planning value strength comparison is defined, the behavior is exactly the same. Otherwise, the behaviour is FIRST_FIT_DECREASING or WEAKEST_FIT(_DECREASING).

<solverBenchmarkBluePrintType>: ALL_CONSTRUCTION_HEURISTIC_TYPES renamed

The <solverBenchmarkBluePrintType> ALL_CONSTRUCTION_HEURISTIC_TYPES has been renamed to EVERY_CONSTRUCTION_HEURISTIC_TYPE. The old name ALL_CONSTRUCTION_HEURISTIC_TYPES is deprecated and will be removed in 7.0.

Before in *BenchmarkConfig.xml:

<solverBenchmarkBluePrintType>ALL_CONSTRUCTION_HEURISTIC_TYPES</solverBenchmarkBluePrintType>

After in *BenchmarkConfig.xml:

<solverBenchmarkBluePrintType>EVERY_CONSTRUCTION_HEURISTIC_TYPE</solverBenchmarkBluePrintType>

Real-time planning: addProblemFactChange() resets time spent terminations

In real-time planning, calling addProblemFactChange() now resets the time spent terminations too. It did already reset all other terminations (despite that the docs claimed otherwise). The docs have also been fixed to reflect reality, which is also the desired behaviour by users.

Real-time planning: addProblemFactChange() keeps the ScoreDirector

In real-time planning, addProblemFactChange() no longer causes the ScoreDirector to be replaced (so it no longer creates a new KieSession) upon solver restart. This might expose a hidden bug in your ProblemFactChange implementation. Enable environmentMode FULL_ASSERT and do a few addProblemFactChange() calls to validate that there are no such bugs.

From 6.3.0.Beta1 to 6.3.0.Beta2

Custom ValueRange: new method isEmpty()

If you implemented a custom ValueRange, also implement the method isEmpty(). Normally, you should not have any need for a custom ValueRange, because ValueRangeFactory supports all sensible ranges.

Before in *.java:

public class MyDoubleValueRange extends AbstractUncountableValueRange<Double> {
    ...
}

After in *.java:

public class MyDoubleValueRange extends AbstractUncountableValueRange<Double> {
    ...
    @Override
    public boolean isEmpty() {
        return from == to;
    }
}

Multiple planning variables: use new folded configuration

If you use multiple planning variables, consider switching to the folded configuration.

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

  <changeMoveSelector>
    <valueSelector>
      <variableName>period</variableName>
    </valueSelector>
  </changeMoveSelector>
  <changeMoveSelector>
    <valueSelector>
      <variableName>room</variableName>
    </valueSelector>
  </changeMoveSelector>

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

  <changeMoveSelector/>

Multiple entity classes: use new folded configuration

If you use multiple entity classes, consider switching to the folded configuration.

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

  <changeMoveSelector>
    <entitySelector>
      <entityClass>...CoachEntity</entityClass>
    </entitySelector>
  </changeMoveSelector>
  <changeMoveSelector>
    <entitySelector>
      <entityClass>...ShuttleEntity</entityClass>
    </entitySelector>
  </changeMoveSelector>
  <swapMoveSelector>
    <entitySelector>
      <entityClass>...CoachEntity</entityClass>
    </entitySelector>
  </swapMoveSelector>
  <swapMoveSelector>
    <entitySelector>
      <entityClass>...ShuttleEntity</entityClass>
    </entitySelector>
  </swapMoveSelector>

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

  <changeMoveSelector/>
  <swapMoveSelector/>

Planning solution with a superclass with planner annotations: behaviour change

If your planning solution has a superclass with planner annotations, those will now be ignored (just like solution subclass annotations are ignored and just like entity superclass or subclass annotations are ignored unless they are a declare planning entity class too). Declare the @PlanningSolution on the superclass instead, the solver will handle subclass instances gracefully (presuming there are no planner annotations in the subclass).

Before in *.java:

public abstract class ParentSolution {
    @ValueRangeProvider(...)
    public List<Computer> getComputers() {...}
}
@PlanningSolution
public class ChildSolution extends ParentSolution {...}

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

<solutionClass>...ChildSolution</solutionClass>

After in *.java:

@PlanningSolution
public abstract class ParentSolution {
    @ValueRangeProvider(...)
    public List<Computer> getComputers() {...}
}
public class ChildSolution extends ParentSolution {...}

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

<solutionClass>...ParentSolution</solutionClass>

From 6.3.0.Beta2 to 6.3.0.CR1

Custom VariableListener that changes 2 shadow variables: use variableListenerRef

If a custom VariableListener changes 2 shadow variables, use the new variableListenerRef property accordingly to indicate that the VariableListener class of another shadow variable also updates this shadow variable:

Before in *.java:

@PlanningVariable(...)
public Standstill getPreviousStandstill() {
    return previousStandstill;
}
@CustomShadowVariable(variableListenerClass = TransportTimeAndCapacityUpdatingVariableListener.class,
        sources = {@CustomShadowVariable.Source(variableName = "previousStandstill")})
public Integer getTransportTime() {
    return transportTime;
}
@CustomShadowVariable(variableListenerClass = DummyListener.class, sources = ...)
public Integer getCapacity() {
    return capacity;
}

After in *.java:

@PlanningVariable(...)
public Standstill getPreviousStandstill() {
    return previousStandstill;
}
@CustomShadowVariable(variableListenerClass = TransportTimeAndCapacityUpdatingVariableListener.class,
        sources = {@CustomShadowVariable.Source(variableName = "previousStandstill")})
public Integer getTransportTime() {
    return transportTime;
}
@CustomShadowVariable(variableListenerRef = @PlanningVariableReference(variableName = "transportTime"))
public Integer getCapacity() {
    return capacity;
}

From 6.3.0.CR1 to 6.3.0.CR2

VariableListeners no longer trigger chaotically

VariableListeners no longer trigger chaotically. This applies to both out of the box shadow variables and custom shadow variables. Planner now guarantees that the first VariableListener 's after*() method triggers AFTER the last genuine variable has been modified. This means a VariableListener is no longer exposed to intermediate state. The before*() methods still trigger immediately (otherwise they would not be able to capture the source variable’s orginal state). Furthermore, Planner guarantees this triggering in stages also for VariableListener for a shadow variable that depend on earlier shadow variables.

Custom Move: doMove() must call triggerVariableListeners()

If you have a custom Move, its doMove() method must now call scoreDirector.triggerVariableListeners() at the end. In practice, you should have extended AbstractMove - which does the triggerVariableListeners() call for you - but you’ll need to rename your doMove() method to doMoveOnGenuineVariables().

Before in *.java:

public class CloudComputerChangeMove extends AbstractMove {
    ...
    public void doMove(ScoreDirector scoreDirector) {
        CloudBalancingMoveHelper.moveCloudComputer(scoreDirector, cloudProcess, toCloudComputer);
    }
}

After in *.java:

public class CloudComputerChangeMove extends AbstractMove {
    ...
    protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) {
        CloudBalancingMoveHelper.moveCloudComputer(scoreDirector, cloudProcess, toCloudComputer);
    }
}

Real-time planning ProblemFactChange: doChange() must call triggerVariableListeners()

If you have a ProblemFactChange, its doChange() method must now call scoreDirector.triggerVariableListeners() after every set of changes (before calling calculateScore() or relying on shadow variables).

Before in *.java:

public void deleteComputer(final CloudComputer computer) {
    doProblemFactChange(new ProblemFactChange() {
        public void doChange(ScoreDirector scoreDirector) {
            CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
            // First remove the problem fact from all planning entities that use it
            for (CloudProcess process : cloudBalance.getProcessList()) {
                if (Objects.equals(process.getComputer(), computer)) {
                    scoreDirector.beforeVariableChanged(process, "computer");
                    process.setComputer(null);
                    scoreDirector.afterVariableChanged(process, "computer");
                }
            }
            ...
        }
    });
}

After in *.java:

public void deleteComputer(final CloudComputer computer) {
    doProblemFactChange(new ProblemFactChange() {
        public void doChange(ScoreDirector scoreDirector) {
            CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
            // First remove the problem fact from all planning entities that use it
            for (CloudProcess process : cloudBalance.getProcessList()) {
                if (Objects.equals(process.getComputer(), computer)) {
                    scoreDirector.beforeVariableChanged(process, "computer");
                    process.setComputer(null);
                    scoreDirector.afterVariableChanged(process, "computer");
                }
            }
            scoreDirector.triggerVariableListeners();
            ...
        }
    });
}

Custom phase CustomPhaseCommand: changeWorkingSolution() must call triggerVariableListeners()

If you have a CustomPhaseCommand, its changeWorkingSolution() method must now call scoreDirector.triggerVariableListeners() after every set of changes (before calling calculateScore() or relying on shadow variables).

Before in *.java:

public class MyCustomPhase implements CustomPhaseCommand {
    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        scoreDirector.beforeVariableChanged(processAssignment, "machine");
        processAssignment.setMachine(machine);
        scoreDirector.afterVariableChanged(processAssignment, "machine");
        Score score = scoreDirector.calculateScore();
    }
}

After in *.java:

public class MyCustomPhase implements CustomPhaseCommand {
    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        scoreDirector.beforeVariableChanged(processAssignment, "machine");
        processAssignment.setMachine(machine);
        scoreDirector.afterVariableChanged(processAssignment, "machine");
        scoreDirector.triggerVariableListeners();
        Score score = scoreDirector.calculateScore();
    }
}

Custom Move: read shadow variables first

A custom Move must now read any shadow variables it needs before its first beforeVariableChanged() call. It no longer needs to assign genuine variables to intermediate values to avoid errors in the VariableListeners that update shadow variables.

All built-in chained moves simplified

All built-in moves that affect chained variables have been greatly simplified due to the new VariableListener guarantee.

Chained moves: constructors changed

The constructor of ChainedChangeMove, ChainedSwapMove, SubChainChangeMove and SubChainSwapMove now require the SingletonInverseVariableSupply parameter.

Before in *.java:

return new ChainedChangeMove(entity, variableDescriptor, toValue);

After in *.java:

SingletonInverseVariableSupply inverseVariableSupply = ((InnerScoreDirector) scoreDirector).getSupplyManager()
        .demand(new SingletonInverseVariableDemand(variableDescriptor));
return new ChainedChangeMove(entity, variableDescriptor, inverseVariableSupply, toValue);

Furthermore, a ChainedSwapMove 's constructor now requires a List instead of Collection of VariableDescriptor s.

InnerScoreDirector: getTrailingEntity() removed

The method InnerScoreDirector.getTrailingEntity() has been removed. Use SingletonInverseVariableSupply instead.

One score rule that affects 2 score levels

One score rule can now change 2 score levels in its RHS.

Before in *.drl:

rule "Costly and unfair: part 1"
when
    // Complex pattern
then
    scoreHolder.addMediumConstraintMatch(kcontext, -1); // Financial cost
end
rule "Costly and unfair: part 2"
when
    // Complex pattern (duplication)
then
    scoreHolder.addSoftConstraintMatch(kcontext, -1); // Employee happiness cost
end

After in *.drl:

rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMediumConstraintMatch(kcontext, -1); // Financial cost
    scoreHolder.addSoftConstraintMatch(kcontext, -1); // Employee happiness cost
end

From 6.3.0.CR2 to 6.3.0.CR3

Build a Solver from API: use SolverFactory.createEmpty()

If you build a Solver entirely from API (not recommended - it’s better to load it partially from XML), use SolverFactory.createEmpty() and solverFactory.getSolverConfig() accordingly.

Before in *.java:

    SolverConfig solverConfig = new SolverConfig();
    ...
    return solverConfig.buildSolver();

After in *.java:

    SolverFactory solverFactory = SolverFactory.createEmpty();
    SolverConfig solverConfig = solverFactory.getSolverConfig();
    ...
    return solverFactory.buildSolver();

Comments

Visit our forum to comment
Latest release
  • 8.1.0.Final released
    Fri 15 January 2021
Paid support and consulting

Want to talk to the experts? Red Hat offers certified binaries with enterprise consulting. Contact optaplanner-info for more information.

Upcoming events
  • KIE Live
    Worldwide - Tue 19 January 2021
    • OptaPlanner Shadow Variables for the Vehicle Routing Problem and Task Assignment by Geoffrey De Smet, Karina Varela, Alex Porcelli
  • Javaland
    Worldwide - Tue 16 March 2021
    • AI on Quarkus: I love it when an OptaPlan comes together by Geoffrey De Smet
Add event / Archive
Latest blog posts
  • Solve the facility location problem
    Fri 9 October 2020
     Jiří Locker
  • OptaPlanner Week 2020 recordings
    Mon 7 September 2020
     Geoffrey De Smet
  • Let’s OptaPlan your jBPM tasks (part 1) - Integrating the two worlds
    Fri 3 July 2020
     Walter Medvedeo
  • AI versus Covid-19: How Java helps nurses and doctors in this fight
    Fri 8 May 2020
     Christopher Chianelli
  • Workflow processes with AI scheduling
    Tue 5 May 2020
     Christopher Chianelli
  • Constraint Streams - Modern Java constraints without the Drools Rule Language
    Tue 7 April 2020
     Geoffrey De Smet
  • How to plan (and optimize) a Secret Santa
    Wed 18 December 2019
     Christopher Chianelli
Blog archive
Latest videos
  • YT Shadow variables
    Tue 19 January 2021
     Geoffrey De Smet
  • YT Domain modeling and design patterns
    Tue 17 November 2020
     Geoffrey De Smet
  • YT Quarkus insights: AI constraint solving
    Tue 20 October 2020
     Geoffrey De Smet
  • YT AI in kotlin
    Wed 23 September 2020
     Geoffrey De Smet
  • YT Planning agility: continuous planning, real-time planning and more
    Thu 3 September 2020
     Geoffrey De Smet
  • YT Quarkus and OptaPlanner: create a school timetable application
    Thu 3 September 2020
     Radovan Synek
  • YT Business use cases and the impact of OptaPlanner
    Thu 3 September 2020
     Satish Kale
Video archive

KIE projects

  • Drools rule engine
  • OptaPlanner constraint solver
  • jBPM workflow engine

Community

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

Code

  • Build from source
  • Submit a bug
  • License (Apache-2.0)
  • Release notes
  • Upgrade recipes
Sponsored by
Red Hat
More coder content at
Red Hat Developers
© Copyright 2006-2021, Red Hat, Inc. or third-party contributors - Privacy statement - Terms of use - Website info