Я создаю проблему маршрутизации емкостного транспортного средства со временем Windows, но с одним небольшим отличием по сравнению с приведенным в примерах из документации: у меня нет депо. Вместо этого каждый заказ имеет этап получения и доставки в двух разных местах.
(как и в примере Маршрутизации транспортных средств из документации, переменная планирования previousStep имеет тип графика CHAINED, а ее valueRangeProviderRefs включает в себя как драйверы, так и Шаги)
Эта разница добавляет пару ограничений:
После экспериментов с ограничениями я обнаружил, что было бы эффективнее реализовать два типа пользовательских перемещений:
- назначить оба шага заказа водителю
- изменить порядок действий водителя
В настоящее время я выполняю этот первый пользовательский шаг. Конфигурация моего решателя выглядит следующим образом:
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");
}
}
Идея состоит в том, что я ни в коем случае не делаю неверный связанный список манипулирование:
Однако, как показывают заголовок и комментарий к коду, я получаю исключение NullPointerException при вызове ScoreDirector.beforeVariableChanged. Ни одна из моих переменных не равна нулю (я напечатал их, чтобы убедиться). 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 код вместо drools.
Первоначальное решение, которое я передаю солверу, имеет все назначенные шаги одному водителю. Всего 15 драйверов и 40 заказов.
Чтобы обойти эту ошибку, я попробовал несколько разных вещей:
- удалите аннотацию теневой переменной, превратите драйвер в проблему факт, и обработайте поле nextStep самостоятельно => это не имеет значения
- используйте Simulated Annealing + First Fit Уменьшение эвристики конструкции и начните с шагов, не назначенных ни одному драйверу (это было вдохновлено поиском примера здесь , что более полно, чем приведенное в документации) => NullPointerException появляется на после VariableChanged вместо этого, но все равно появляется.
- ряд других вещей, которые были, вероятно, не очень умные
Но без более полезного сообщения об ошибке, я не могу придумать ничего другого, чтобы попробовать.
Спасибо за вашу помощь