Optaplanner 本地搜索产生 InverseRelationShadowVariable 注释字段的不一致状态

问题描述 投票:0回答:1

我的代码或 optaplanner 中遇到了错误,但我似乎无法理解它。

错误描述

我正在使用“时间链”模式与以下课程

  • Employee
    (扩展
    TaskOrEmployee
    )-问题事实
  • Task
    (扩展
    TaskOrEmployee
    )(包含
    @PlanningVariable TaskOrEmployee previousTaskOrEmployee
    字段)(由
    @PlanningEntity
    注释)
  • TaskOrEmployee
    (包含
    @InverseRelationShadowVariable Task nextTask
    字段)(由
    @PlanningEntity
    注释)

Optaplanner 版本:

8.44.0.Final
,java 11

构造启发式工作正常,但本地搜索抛出异常,表示解决方案状态不一致,因为

task.getPreviousTaskOrEmployee().getNextTask() != task
,即使我没有传递已经初始化的解决方案,并且我从不手动设置
nextTask
previousTaskOrEmployee

不知道该怎么办,我什至验证了构造启发式生成的解决方案的状态在

SolverManager#solveAndListen
bestSolutionConsumer
内部是一致的。

错误堆栈跟踪

2024-01-06 23:48:57 DEBUG   DefaultConstructionHeuristicPhase#stepEnded     CH step (1503), time spent (1256), score (0hard/-319262medium/494035696soft), selected move count (1), picked move (<263/22.6 [2]> {null -> <263/22.6 [1]>}).
2024-01-06 23:48:57 INFO    DefaultConstructionHeuristicPhase#phaseEnded    Construction Heuristic phase (0) ended: time spent (1266), best score (0hard/-319262medium/494035696soft), score calculation speed (1525/sec), step total (1504).
2024-01-06 23:48:57 ERROR   DefaultSolverManager#lambda$new$0   Solving failed for problemId (1).
java.lang.IllegalStateException: The entity (<276/22.1 [7]>) has a variable (previousTaskOrEmployee) with value (<276/22.1 [6]>) which has a sourceVariableName variable (nextTask) with a value (null) which is not that entity.
Verify the consistency of your input problem for that sourceVariableName variable.
    at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.retract(SingletonInverseVariableListener.java:76) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.beforeVariableChanged(SingletonInverseVariableListener.java:32) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.listener.support.VariableChangedNotification.triggerBefore(VariableChangedNotification.java:15) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.listener.support.VariableChangedNotification.triggerBefore(VariableChangedNotification.java:6) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.listener.support.AbstractNotifiable.triggerBefore(AbstractNotifiable.java:70) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.listener.support.VariableListenerNotifiable.notifyBefore(VariableListenerNotifiable.java:26) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.domain.variable.listener.support.VariableListenerSupport.beforeVariableChanged(VariableListenerSupport.java:161) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.beforeVariableChanged(AbstractScoreDirector.java:423) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.changeVariableFacade(AbstractScoreDirector.java:435) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.heuristic.selector.move.generic.chained.ChainedChangeMove.doMoveOnGenuineVariables(ChainedChangeMove.java:60) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.heuristic.move.AbstractMove.doMoveOnly(AbstractMove.java:26) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.heuristic.move.AbstractMove.doMove(AbstractMove.java:20) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.heuristic.move.AbstractMove.doMove(AbstractMove.java:15) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:212) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:117) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:101) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:72) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:83) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:193) ~[optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at org.optaplanner.core.impl.solver.DefaultSolverJob.call(DefaultSolverJob.java:109) [optaplanner-core-impl-8.44.0.Final.jar:8.44.0.Final]
    at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
    at java.lang.Thread.run(Thread.java:829) [?:?]

通过查看此堆栈跟踪,我可以告诉您提到的两个实体(<276/22.1 [7]> 和 <276/22.1 [6]>)都是 Task 类的实例,而不是 Employee (域如下所述)。

域名:

简化事情:企业希望将任务分配给员工。我已经实现了文档中描述的“通过时间链接”模式。

// solution class
@PlanningSolution
public class TaskAssigningSolution implements Serializable {

    @ValueRangeProvider(id = "employeeRange")
    @ProblemFactCollectionProperty
    List<Employee> employees;

    // When the initial (uninitialized) solution is created, these tasks are sorted in an order in which the construction heuristic should append them to the chain
    @ValueRangeProvider(id = "taskRange")
    @PlanningEntityCollectionProperty
    List<Task> tasks;

    @PlanningScore
    HardMediumSoftScore score;
}

// superclass to Employee and Task
@PlanningEntity
public abstract class TaskOrEmployee implements Serializable {

    // https://www.optaplanner.org/docs/optaplanner/latest/shadow-variable/shadow-variable.html#bidirectionalVariable
    @InverseRelationShadowVariable(sourceVariableName = "previousTaskOrEmployee")
    Task nextTask;
}

// problem fact, chain anchor
public class Employee extends TaskOrEmployee {

    @PlanningId
    String employeeId;

    // removed bunch of fields which are irrelevant to the problem - they're readonly and used only by StartAndEndTimeUpdatingPlanningVariableListener
}

@PlanningEntity
public class Task extends TaskOrEmployee {

    // https://www.optaplanner.org/docs/optaplanner/latest/design-patterns/design-patterns.html#chainedThroughTimePattern
    @PlanningVariable(valueRangeProviderRefs = { "employeeRange", "taskRange" }, graphType = CHAINED)
    TaskOrEmployee previousTaskOrEmployee;

    @AnchorShadowVariable(sourceVariableName = "previousTaskOrEmployee")
    Employee employee;

    @ShadowVariable(sourceVariableName = "previousTaskOrEmployee", variableListenerClass = StartAndEndTimeUpdatingPlanningVariableListener.class)
    Long startTime;

    @ShadowVariable(sourceVariableName = "previousTaskOrEmployee", variableListenerClass = StartAndEndTimeUpdatingPlanningVariableListener.class)
    Long endTime;

    @PlanningId
    String taskId;

    // removed bunch of fields which are irrelevant to the problem - they're readonly and used only by StartAndEndTimeUpdatingPlanningVariableListener
}

我认为发布整个 StartAndEndTimeUpdatingPlanningVariableListener 是没有必要的,我可以向您保证它不会以任何方式改变

nextTask
字段。它只是设置这两个字段:

scoreDirector.beforeVariableChanged(task, "startTime");
task.setStartTime(resultTuple.startTime);
scoreDirector.afterVariableChanged(task, "startTime");

scoreDirector.beforeVariableChanged(task, "endTime");
task.setEndTime(resultTuple.endTime);
scoreDirector.afterVariableChanged(task, "endTime");

配置

<?xml version="1.0" encoding="UTF-8"?>
<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">

    <environmentMode>REPRODUCIBLE</environmentMode>
    <solutionClass>package.TaskAssigningSolution</solutionClass>
    <entityClass>package.TaskOrEmployee</entityClass>
    <entityClass>package.Task</entityClass>
    <domainAccessType>REFLECTION</domainAccessType>

    <scoreDirectorFactory>
        <constraintProviderClass>package.TaskAssigningConstraintProvider</constraintProviderClass>
        <!--
            Same error happens on drools
        -->
        <constraintStreamImplType>BAVET</constraintStreamImplType>
    </scoreDirectorFactory>

    <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
        <changeMoveSelector>
        <!--
            This filter checks whether the achor of a chain that this move is trying to append the Task to (Employee)
            is capable of completing that Task (whether it has required skillset or not)
        -->
        <filterClass>package.TaskChangeMoveSelectionFilter</filterClass>
        <valueSelector>
            <!-- 
                This filter checks whether the value is an end of a chain (it's nextTask is equal to null).
                Otherwise construction heuristic would be running for hours on very large datasets.
            -->
            <filterClass>package.ConstructionHeuristicValueSelectionFilter</filterClass>
        </valueSelector>
        </changeMoveSelector>
    </constructionHeuristic>

    <localSearch>
        <localSearchType>TABU_SEARCH</localSearchType>
        <!--
            Single changeMoveSelector is not the end goal - I'm just trying to setup localSearch
            in a way which would not cause any errors, and adding more moveSelectors won't resolve those errors
        -->
        <changeMoveSelector>
            <filterClass>package.TaskChangeMoveSelectionFilter</filterClass>
        </changeMoveSelector>
    </localSearch>
</solver>

我还尝试了不同的 localSearch 类型(TABU_SEARCH、HILL_CLIMBING、LATE_ACCEPTANCE) - HILL_CLIMBING 和 LATE_ACCEPTANCE 通常能够步进几次(根据调试日志),但它们最终也会失败并出现相同的错误。据我了解这个问题,这意味着解决方案在第 0 阶段(构造启发式)结束后尚未损坏。

编辑

我看到人们开始投票赞成结束这个问题。如果您确实认为应该关闭这个问题,您能在评论中解释一下原因吗?

java optaplanner
1个回答
0
投票

您的影子变量在输入数据中处于不一致的状态。

在 Timefold(optaplanner 的分支)中,您可以运行

SolutionManager.update(input)
来“修复该问题”,然后再将其交给
Solver.solve()
。至少使用 1.5.0。如果我没记错的话,该方法在早期版本(包括 OptaPlanner 8)中存在错误。

© www.soinside.com 2019 - 2024. All rights reserved.