optaplanner - Optaplanner:在简单的自定义移动中调用 scoreDirector.beforeVariableChanged 时出现 NullPointerException
问题描述
我正在使用 Time Windows 构建一个有容量的车辆路由问题,但与文档中的示例中提供的问题相比有一点不同:我没有仓库。相反,每个订单在两个不同的位置都有一个取货步骤和一个交付步骤。
(就像文档中的 Vehicle Routing 示例一样,previousStep 计划变量具有 CHAINED 图形类型,其 valueRangeProviderRefs 包括 Drivers 和 Steps)
这种差异增加了一些限制:
给定订单的取货和送货步骤必须由同一司机处理
取货必须在交货前
在尝试了约束之后,我发现实现两种类型的自定义移动会更有效:
- 将订单的两个步骤分配给司机
- 重新安排司机的脚步
我目前正在实施第一个自定义动作。我的求解器的配置如下所示:
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个订单。
为了绕过这个错误,我尝试了许多不同的方法:
- 去掉shadow变量注解,把Driver变成问题事实,自己处理nextStep字段=>这个没区别
- 使用模拟退火+第一次拟合递减构造启发式,并从未分配给任何驱动程序的步骤开始(这是通过在此处查找示例的启发,这比文档中的示例更完整)=> NullPointerException 出现在VariableChanged之后,但它仍然出现。
- 其他一些可能不是很聪明的事情
但是如果没有更有用的错误消息,我想不出其他可以尝试的方法。
谢谢您的帮助
解决方案
推荐阅读
- javascript - jQuery:如何在追加 3 项后隐藏按钮(添加更多),如果从 3 项中删除一项,则显示按钮
- r - 以 data.table 中的第二个标识符为条件的组的平均观察次数
- javascript - 在每个按钮单击 javascript 添加到数组
- spring-boot - Postman/Insomnia 返回 200 但 Java 客户端在重新提交时返回 404
- json - 解析来自 Microsoft Graph API 的响应
- python - 为什么我在 python2.7 中导入 os.walk 时出错
- android - ExoPlayer 无法播放 RTMP 语音流
- momentjs - 如何使用momentjs找到给定月份的任何周六/周日?
- sql - 根据日期差异进行选择
- sharepoint-online - 为什么超过次要版本限制时,SharePoint Online 会删除最新的而不是最旧的次要版本?