我的代码或 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 阶段(构造启发式)结束后尚未损坏。
编辑:
我看到人们开始投票赞成结束这个问题。如果您确实认为应该关闭这个问题,您能在评论中解释一下原因吗?
您的影子变量在输入数据中处于不一致的状态。
在 Timefold(optaplanner 的分支)中,您可以运行
SolutionManager.update(input)
来“修复该问题”,然后再将其交给 Solver.solve()
。至少使用 1.5.0。如果我没记错的话,该方法在早期版本(包括 OptaPlanner 8)中存在错误。