No known workingObject when trying to enable multi-threading in Optaplanner

Viewed 129

I'm trying to enable multi-threading for my Optaplanner setup. The documentation states that, in doing so, one should annotate each planning entity and planning value class with @PlanningId on a field or getter serving as a unique ID.

Below, as a planning value class.

public class TimeSlot {

@PlanningId
private String id; //Unique for each instance, and initialized in constructor

...
}

And, multi-threading is enabled in a properties file.

optaplanner.solver.move-thread-count=AUTO

When I run my code, I get the following exception.

java.lang.IllegalStateException: The move thread with moveThreadIndex (0) has thrown an exception. Relayed here in the parent thread.
at org.optaplanner.core.impl.heuristic.thread.OrderByMoveIndexBlockingQueue.take(OrderByMoveIndexBlockingQueue.java:147)
at org.optaplanner.core.impl.constructionheuristic.decider.MultiThreadedConstructionHeuristicDecider.forageResult(MultiThreadedConstructionHeuristicDecider.java:186)
at org.optaplanner.core.impl.constructionheuristic.decider.MultiThreadedConstructionHeuristicDecider.decideNextStep(MultiThreadedConstructionHeuristicDecider.java:157)
at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:75)
at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:99)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:189)
at org.optaplanner.core.impl.solver.DefaultSolverJob.call(DefaultSolverJob.java:101)
at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.lang.IllegalStateException: The externalObject ({ TimeSlot: {begin:"2021-08-01T00:30:00", end:"2021-08-01T02:15:00"} }) with planningId ((class com.timetime.assistedengine.domain.TimeSlot,549290301)) has no known workingObject (null).
Maybe the workingObject was never added because the planning solution doesn't have a @ProblemFactCollectionProperty annotation on a member with instances of the externalObject's class (class com.timetime.assistedengine.domain.TimeSlot).
    at org.optaplanner.core.impl.domain.lookup.PlanningIdLookUpStrategy.lookUpWorkingObject(PlanningIdLookUpStrategy.java:66)
    at org.optaplanner.core.impl.domain.lookup.LookUpManager.lookUpWorkingObject(LookUpManager.java:76)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.lookUpWorkingObject(AbstractScoreDirector.java:512)
    at org.optaplanner.core.impl.heuristic.selector.move.generic.ChangeMove.rebase(ChangeMove.java:83)
    at org.optaplanner.core.impl.heuristic.selector.move.generic.ChangeMove.rebase(ChangeMove.java:31)
    at org.optaplanner.core.impl.heuristic.move.CompositeMove.rebase(CompositeMove.java:125)
    at org.optaplanner.core.impl.heuristic.move.CompositeMove.rebase(CompositeMove.java:38)
    at org.optaplanner.core.impl.heuristic.thread.MoveThreadRunner.run(MoveThreadRunner.java:140)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    ... 5 more

The problem seems to be that no workingObject (an internal Optaplanner concept I presume) could not be found for instances of the TimeSlot class.

When digging in the Optaplanner source code through the stack trace, it seemed to be the case that Optaplanner internally could only have knowledge of a workingObject if the corresponding value range of the class that corresponds the workingObject has a @ProblemFactProperty or a @ProblemFactCollectionProperty on it.

In my case, the value range is on the Planning Entity, and not the Planning Solution. The value range is thus different for each Planning Entity instance. I'm not sure if this is a hindrance, because labeling the value range with the above annotation (@ProblemFactCollectionProperty) doesn't help.

Other then the TimeSlot class, I have another planning value class (also with a value range on the planning entity), and a single problem fact instance on the Planning Solution. They, and the Planning Entities themselves, all have ID's annotated with @PlanningId.

1 Answers

Got this working by adding @ProblemFactCollectionProperty to List attribute of class decorated with @PlanningSolution and adding @ProblemFactCollectionProperty, @ValueRangeProvider to List attribute of class decorated with @PlanningEntity. So actual value range returned for each @PlanningEntity object and all values returned for @PlanningSolution object.

For example:

@PlanningSolution
public class Schedule {
    // no ValueRangeProvider!
    @ProblemFactCollectionProperty
    private List<Timeslot> timeslots;
}

@PlanningEntity
public class Meeting {
    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "timeslotRange")
    private List<Timeslot> suitableTimeslots;
}
Related