I'm creating a school schedule generator and I'm getting an exception from one of my constraints which checks that all students have lunch breaks every day.
I'm using the constraint stream Java API. My constraint compiles (obviously) and looks like this:
public Constraint studentsShouldHaveLunchEveryDay(ConstraintFactory cf) {
return cf.from(RecurringLecture.class)
.filter(RecurringLecture::isScheduled) // [Lecture]...
.groupBy(l -> l, l -> l.getCourseRound().getStudents()) // [Lecture, Set<Student>]...
.flattenLast(students -> students) // [Lecture, Student]...
.groupBy((l, s) -> s, // [Student, Map<Day, Set<Lecture>>]...
ConstraintCollectors.toMap(
(l, s) -> l.getStartTimeslot().getDay(),
(l, s) -> l))
//.flattenLast(Map::values) // [Student, Set<Lecture>]...
//.filter((student, dailyLectures) -> !LunchBreakAnalyzer.hasLunchBreak(dailyLectures))
.penalize(StudentLunchProblem.class.getName(), HardSoftScore.ONE_HARD);
}
I have temporarily commented out the flattenLast
and filter
for debugging reasons, and the problem still reproduces. If I comment out the last groupBy
though, the issue does not seem to reproduce.
For some reason, the framework tries to cast a Student
(ImmutableStudent
to be precise) to an Object[]
.
The line in the framework that throws is in ArrayElementReader
and looks like this:
public Object getValue(InternalWorkingMemory workingMemory,
Object object) {
Object[] array = (Object[]) this.arrayReadAccessor.getValue( workingMemory,
object );
return array[this.index];
}
The full exception looks as follows:
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: The move thread with moveThreadIndex (1) has thrown an exception. Relayed here in the parent thread.
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at java.desktop/javax.swing.SwingWorker.get(SwingWorker.java:613)
at vngschedules.ui.automation.AutomationPanel$1$1.done(AutomationPanel.java:44)
at java.desktop/javax.swing.SwingWorker$5.run(SwingWorker.java:750)
at java.desktop/javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.run(SwingWorker.java:847)
at java.desktop/sun.swing.AccumulativeRunnable.run(AccumulativeRunnable.java:112)
at java.desktop/javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.actionPerformed(SwingWorker.java:857)
at java.desktop/javax.swing.Timer.fireActionPerformed(Timer.java:317)
at java.desktop/javax.swing.Timer$DoPostEvent.run(Timer.java:249)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.IllegalStateException: The move thread with moveThreadIndex (1) 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.localsearch.decider.MultiThreadedLocalSearchDecider.forageResult(MultiThreadedLocalSearchDecider.java:189)
at org.optaplanner.core.impl.localsearch.decider.MultiThreadedLocalSearchDecider.decideNextStep(MultiThreadedLocalSearchDecider.java:160)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:95)
at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:99)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:192)
Caused by: java.lang.IllegalStateException: The move thread with moveThreadIndex (1) has thrown an exception. Relayed here in the parent thread.
at vngschedules.ui.automation.SolverSwingWorker.doInBackground(SolverSwingWorker.java:29)
at vngschedules.ui.automation.SolverSwingWorker.doInBackground(SolverSwingWorker.java:10)
at java.desktop/javax.swing.SwingWorker$1.call(SwingWorker.java:304)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.desktop/javax.swing.SwingWorker.run(SwingWorker.java:343)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ClassCastException: class vngschedules.schedule.ImmutableStudent cannot be cast to class [Ljava.lang.Object; (vngschedules.schedule.ImmutableStudent is in unnamed module of loader 'app'; [Ljava.lang.Object; is in module java.base of loader 'bootstrap')
at org.drools.core.base.extractors.ArrayElementReader.getValue(ArrayElementReader.java:162)
at org.drools.core.base.extractors.ArrayElementReader.getValue(ArrayElementReader.java:279)
at org.drools.modelcompiler.KiePackagesBuilder.lambda$getBindingFunction$66de2761$1(KiePackagesBuilder.java:794)
at org.drools.modelcompiler.constraints.LambdaReadAccessor.getValue(LambdaReadAccessor.java:43)
at org.drools.core.rule.Declaration.getValue(Declaration.java:257)
at org.optaplanner.core.impl.score.stream.drools.common.AbstractAccumulator.extractValue(AbstractAccumulator.java:42)
Caused by: java.lang.ClassCastException: class vngschedules.schedule.ImmutableStudent cannot be cast to class [Ljava.lang.Object; (vngschedules.schedule.ImmutableStudent is in unnamed module of loader 'app'; [Ljava.lang.Object; is in module java.base of loader 'bootstrap')
at org.optaplanner.core.impl.score.stream.drools.common.BiAccumulator.accumulate(BiAccumulator.java:55)
at org.drools.core.rule.SingleAccumulate.accumulate(SingleAccumulate.java:96)
at org.drools.modelcompiler.constraints.LambdaGroupByAccumulate.accumulate(LambdaGroupByAccumulate.java:121)
at org.drools.modelcompiler.constraints.LambdaGroupByAccumulate.accumulate(LambdaGroupByAccumulate.java:114)
at org.drools.core.phreak.PhreakAccumulateNode.addMatch(PhreakAccumulateNode.java:736)
at org.drools.core.phreak.PhreakAccumulateNode.doRightInserts(PhreakAccumulateNode.java:253)
at org.drools.core.phreak.PhreakAccumulateNode.doNode(PhreakAccumulateNode.java:99)
at org.drools.core.phreak.RuleNetworkEvaluator.switchOnDoBetaNode(RuleNetworkEvaluator.java:586)
at org.drools.core.phreak.RuleNetworkEvaluator.evalBetaNode(RuleNetworkEvaluator.java:555)
at org.drools.core.phreak.RuleNetworkEvaluator.evalNode(RuleNetworkEvaluator.java:382)
at org.drools.core.phreak.RuleNetworkEvaluator.innerEval(RuleNetworkEvaluator.java:342)
at org.drools.core.phreak.RuleNetworkEvaluator.evalStackEntry(RuleNetworkEvaluator.java:240)
at org.drools.core.phreak.RuleNetworkEvaluator.outerEval(RuleNetworkEvaluator.java:183)
at org.drools.core.phreak.RuleNetworkEvaluator.evaluateNetwork(RuleNetworkEvaluator.java:136)
at org.drools.core.phreak.RuleExecutor.reEvaluateNetwork(RuleExecutor.java:235)
at org.drools.core.phreak.RuleExecutor.evaluateNetworkAndFire(RuleExecutor.java:91)
at org.drools.core.concurrent.AbstractRuleEvaluator.internalEvaluateAndFire(AbstractRuleEvaluator.java:33)
at org.drools.core.concurrent.SequentialRuleEvaluator.evaluateAndFire(SequentialRuleEvaluator.java:43)
at org.drools.core.common.DefaultAgenda.fireLoop(DefaultAgenda.java:869)
at org.drools.core.common.DefaultAgenda.internalFireAllRules(DefaultAgenda.java:816)
at org.drools.core.common.DefaultAgenda.fireAllRules(DefaultAgenda.java:808)
at org.drools.core.impl.StatefulKnowledgeSessionImpl.internalFireAllRules(StatefulKnowledgeSessionImpl.java:1343)
at org.drools.core.impl.StatefulKnowledgeSessionImpl.fireAllRules(StatefulKnowledgeSessionImpl.java:1334)
at org.drools.core.impl.StatefulKnowledgeSessionImpl.fireAllRules(StatefulKnowledgeSessionImpl.java:1326)
at org.optaplanner.core.impl.score.director.stream.DroolsConstraintStreamScoreDirector.calculateScore(DroolsConstraintStreamScoreDirector.java:90)
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:220)
at org.optaplanner.core.impl.heuristic.thread.MoveThreadRunner.run(MoveThreadRunner.java:147)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
... 3 more
I have commented out all other constraint streams in my program.
Happy to provide more types if needed, but RecurringLecture
is quite simple really, and Student
is an Immutables interface compiled into an ImmutableStudent
.
I'm using OptaPlanner 8.12.0.Final and Java 11.
I tried rewriting the constraint stream as follows:
public Constraint studentsShouldHaveLunchEveryDay(ConstraintFactory cf) {
return cf.from(RecurringLecture.class)
.filter(RecurringLecture::isScheduled) // [Lecture]...
.groupBy(l -> l, l -> l.getCourseRound().getStudents()) // [Lecture, Set<Student>]...
.flattenLast(students -> students) // [Lecture, Student]...
.groupBy((l, s) -> ImmutableStudentAndDay.of(s, l.getStartTimeslot().getDay()),
ConstraintCollectors.toSet((l, s) -> l))
.filter((student, dailyLectures) -> !LunchBreakAnalyzer.hasLunchBreak(dailyLectures))
.penalize(StudentLunchProblem.class.getName(), HardSoftScore.ONE_HARD);
}
This avoids the toMap
collector. I still run into the exact same issue however.
Generally speaking, if your constraint compiles and the code still throws ClassCastException
at runtime, you should expect the bug to be on the OptaPlanner side. Unless you are willing to look at the Drools executable model, there is nothing for you to debug there.
I recommend refactoring the constraint to look like this:
public Constraint studentsShouldHaveLunchEveryDay(ConstraintFactory cf) {
return cf.from(RecurringLecture.class)
.filter(RecurringLecture::isScheduled)
.join(Student.class,
Joiners.filtering((l, s) -> l.getCourseRound().getStudents().contains(s)))
.groupBy((l, s) -> ImmutableStudentAndDay.of(s, l.getStartTimeslot().getDay()),
ConstraintCollectors.toSet((l, s) -> l))
.filter((student, dailyLectures) -> !LunchBreakAnalyzer.hasLunchBreak(dailyLectures))
.penalize(StudentLunchProblem.class.getName(), HardSoftScore.ONE_HARD);
}
I expect this to perform much better, possibly also getting rid of the exception. Regardless, the exception is still a problem and I will investigate it further if you provide your simplified executable reproducer.