首页 > 解决方案 > Optaplanner:在简单的自定义移动中调用 scoreDirector.beforeVariableChanged 时出现 NullPointerException

问题描述

我正在使用 Time Windows 构建一个有容量的车辆路由问题,但与文档中的示例中提供的问题相比有一点不同:我没有仓库。相反,每个订单在两个不同的位置都有一个取货步骤和一个交付步骤。

问题模型

(就像文档中的 Vehicle Routing 示例一样,previousStep 计划变量具有 CHAINED 图形类型,其 valueRangeProviderRefs 包括 Drivers 和 Steps)

这种差异增加了一些限制:

在尝试了约束之后,我发现实现两种类型的自定义移动会更有效:

  1. 将订单的两个步骤分配给司机
  2. 重新安排司机的脚步

我目前正在实施第一个自定义动作。我的求解器的配置如下所示:

SolverFactory<RoutingProblem> solverFactory = SolverFactory.create(
 new SolverConfig()
     .withSolutionClass(RoutingProblem.class)
     .withEntityClasses(Step.class, StepList.class)
     .withScoreDirectorFactory(new ScoreDirectorFactoryConfig()
         .withConstraintProviderClass(Constraints.class)
     )
     .withTerminationConfig(new TerminationConfig()
         .withSecondsSpentLimit(60L)
     )
     .withPhaseList(List.of(
         new LocalSearchPhaseConfig()
             .withMoveSelectorConfig(CustomMoveListFactory.getConfig())
     ))
);

我的 CustomMoveListFactory 看起来像这样(我计划稍后将其迁移到 MoveIteratorFactory,但目前,这更易于阅读和编写):

public class CustomMoveListFactory implements MoveListFactory<RoutingProblem> {
 public static MoveListFactoryConfig getConfig() {
     MoveListFactoryConfig result = new MoveListFactoryConfig();
     result.setMoveListFactoryClass(CustomMoveListFactory.class);
     return result;
 }

 @Override
 public List<? extends Move<RoutingProblem>> createMoveList(RoutingProblem routingProblem) {
     List<Move<RoutingProblem>> moves = new ArrayList<>();

     // 1. Assign moves
     for (Order order : routingProblem.getOrders()) {
         Driver currentDriver = order.getDriver();

         for (Driver driver : routingProblem.getDrivers()) {
             if (!driver.equals(currentDriver)) {
                 moves.add(new AssignMove(order, driver));
             }
         }
     }

     // 2. Rearrange moves
     // TODO

     return moves;
 }
}

最后,移动本身看起来像这样(暂时不要考虑撤消或 isDoable):

@Override
protected void doMoveOnGenuineVariables(ScoreDirector<RoutingProblem> scoreDirector) {
     assignStep(scoreDirector, order.getPickupStep());
     assignStep(scoreDirector, order.getDeliveryStep());
}

private void assignStep(ScoreDirector<RoutingProblem> scoreDirector, Step step) {
     StepList beforeStep = step.getPreviousStep();
     Step afterStep = step.getNextStep();

     // 1. Insert step at the end of the driver's step list
     StepList lastStep = driver.getLastStep();

     scoreDirector.beforeVariableChanged(step, "previousStep"); // NullPointerException here
     step.setPreviousStep(lastStep);
     scoreDirector.afterVariableChanged(step, "previousStep");

     // 2. Remove step from current chained list
     if (afterStep != null) {
         scoreDirector.beforeVariableChanged(afterStep, "previousStep");
         afterStep.setPreviousStep(beforeStep);
         scoreDirector.afterVariableChanged(afterStep, "previousStep");
     }
}

这个想法是我在任何时候都在做一个无效的链表操作:

链表操作

但是,正如标题和代码注释所示,当我调用 scoreDirector.beforeVariableChanged 时,我得到了 NullPointerException。我的变量都不是空的(我已经打印了它们以确保)。NullPointerException 没有出现在我的代码中,但在 Optaplanner 的内部工作原理的深处,让我很难修复它:

Exception in thread "main" java.lang.NullPointerException
    at org.drools.core.common.NamedEntryPoint.update(NamedEntryPoint.java:353)
    at org.drools.core.common.NamedEntryPoint.update(NamedEntryPoint.java:338)
    at org.drools.core.impl.StatefulKnowledgeSessionImpl.update(StatefulKnowledgeSessionImpl.java:1579)
    at org.drools.core.impl.StatefulKnowledgeSessionImpl.update(StatefulKnowledgeSessionImpl.java:1551)
    at org.optaplanner.core.impl.score.stream.drools.DroolsConstraintSession.update(DroolsConstraintSession.java:49)
    at org.optaplanner.core.impl.score.director.stream.ConstraintStreamScoreDirector.afterVariableChanged(ConstraintStreamScoreDirector.java:137)
    at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.retract(SingletonInverseVariableListener.java:96)
    at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.beforeVariableChanged(SingletonInverseVariableListener.java:46)
    at org.optaplanner.core.impl.domain.variable.listener.support.VariableListenerSupport.beforeVariableChanged(VariableListenerSupport.java:170)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.beforeVariableChanged(AbstractScoreDirector.java:430)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.beforeVariableChanged(AbstractScoreDirector.java:390)
    at test.optaplanner.solver.AssignMove.assignStep(AssignMove.java:98)
    at test.optaplanner.solver.AssignMove.doMoveOnGenuineVariables(AssignMove.java:85)
    at org.optaplanner.core.impl.heuristic.move.AbstractMove.doMove(AbstractMove.java:35)
    at org.optaplanner.core.impl.heuristic.move.AbstractMove.doMove(AbstractMove.java:30)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:187)
    at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:132)
    at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:116)
    at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:70)
    at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:98)
    at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:189)
    at test.optaplanner.OptaPlannerService.testOptaplanner(OptaPlannerService.java:68)
    at test.optaplanner.App.main(App.java:13)

是不是我做错了什么?似乎我非常密切地关注自定义移动的文档,除了我使用专门的 Java 代码而不是流口水这一事实之外。

我提供给求解器的初始解决方案具有分配给单个驱动程序的所有步骤。有15个司机和40个订单。

为了绕过这个错误,我尝试了许多不同的方法:

但是如果没有更有用的错误消息,我想不出其他可以尝试的方法。

谢谢您的帮助

标签: optaplanner

解决方案


推荐阅读