Optaplanner: исключение NullPointerException при вызове ScoreDirector.beforeVariableChanged в простом пользовательском движении - PullRequest
2 голосов
/ 28 января 2020

Я создаю проблему маршрутизации емкостного транспортного средства со временем Windows, но с одним небольшим отличием по сравнению с приведенным в примерах из документации: у меня нет депо. Вместо этого каждый заказ имеет этап получения и доставки в двух разных местах.

problem model

(как и в примере Маршрутизации транспортных средств из документации, переменная планирования previousStep имеет тип графика CHAINED, а ее valueRangeProviderRefs включает в себя как драйверы, так и Шаги)

Эта разница добавляет пару ограничений:

  • шаги по доставке и доставке данного заказа должны выполняться одним и тем же драйвером

  • получение должно быть до поставки

После экспериментов с ограничениями я обнаружил, что было бы эффективнее реализовать два типа пользовательских перемещений:

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

Идея состоит в том, что я ни в коем случае не делаю неверный связанный список манипулирование:

chained list manipulation

Однако, как показывают заголовок и комментарий к коду, я получаю исключение 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 вместо этого, но все равно появляется.
  • ряд других вещей, которые были, вероятно, не очень умные

Но без более полезного сообщения об ошибке, я не могу придумать ничего другого, чтобы попробовать.

Спасибо за вашу помощь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...