由于 groupBy 操作,OptaPlanner 中的 ClassCastException

ClassCastException in OptaPlanner due to a groupBy operation

我正在创建一个学校时间表生成器,但我的一个约束出现异常,该约束检查所有学生每天都有午休时间。

我正在使用约束流 Java API。我的约束编译(显然)并且看起来像这样:

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);
}

为了调试暂时把flattenLastfilter注释掉了,问题还是重现。如果我注释掉最后一个 groupBy,问题似乎不会重现。

出于某种原因,框架试图将 Student(准确地说是 ImmutableStudent)转换为 Object[]

框架中抛出的行在 ArrayElementReader 中,看起来像这样:

public Object getValue(InternalWorkingMemory workingMemory,
                       Object object) {
    Object[] array = (Object[]) this.arrayReadAccessor.getValue( workingMemory,
                                                                 object );
    return array[this.index];
}

完整的异常如下所示:

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.done(AutomationPanel.java:44)
    at java.desktop/javax.swing.SwingWorker.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.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue.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.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$getBindingFunctionde2761(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

我已经注释掉了我程序中的所有其他约束流。

问题

  1. 这看起来像是我的代码中的错误,还是 OptaPlanner 的问题?
  2. 如果它在我的代码中,是否有任何调试技巧?我怀疑我的约束被编译成 Drools-something。有没有办法跳过这个编译,让异常可以追溯到我的代码? (截至目前,异常中没有任何行可以追溯到我的代码。)

很高兴在需要时提供更多类型,但 RecurringLecture 确实非常简单,Student 是一个编译成 ImmutableStudent.

的不可变接口

我正在使用 OptaPlanner 8.12.0.Final 和 Java 11.

更新

我尝试重写约束流如下:

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);
}

这避免了 toMap 收集器。然而,我仍然 运行 遇到完全相同的问题。

一般来说,如果您的约束编译成功并且代码在运行时仍然抛出 ClassCastException,您应该认为该错误出在 OptaPlanner 方面。除非你愿意查看 Drools 可执行模型,否则那里没有任何东西可供你调试。

我建议将约束重构为如下所示:

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);
}

我希望它能表现得更好,也可能会消除异常。无论如何,异常仍然是一个问题,如果您提供简化的可执行复制器,我将进一步调查它。