OptaPlanner,影子变量的损坏值更改为未损坏的值



optaplanner bom版本7.45.0.最终

@PlanningEntity
public class Task {

@PlanningVariable(valueRangeProviderRefs = "timeGrainRange")
private TimeGrain startingTimeGrain;
@CustomShadowVariable(variableListenerClass = DurationUpdatingVariableListener.class,
sources = { @PlanningVariableReference(variableName = "startingTimeGrain") })
private Long durationInGrains;
......

类内DurationUpdatingVariableListener:

public void afterVariableChanged(ScoreDirector scoreDirector, Task e) { 
if (null == e.getStartingTimeGrain()) {
return;
}
Schedule s = (Schedule)scoreDirector.getWorkingSolution();
List<Task> tasksToBeUpdated = s.getTasksToBeUpdated(e);  // calculate all updates
for (Task t: tasksToBeUpdated) {
scoreDirector.beforeVariableChanged(t, NAME_DURATION);
t.setDurationInGrains(convertToGrain(t.getDurationInSecs()));
scoreDirector.afterVariableChanged(t, NAME_DURATION);
}
}

逻辑是,当一个任务的startingTimeGrain发生变化时,某些任务的持续时间会受到影响。问题是,当变量";tasksToBeUpdated";只包含一个任务,没有错误。当它包含多个任务时,得到以下错误:

ERROR 30844 --- [pool-1-thread-1] o.o.c.impl.solver.DefaultSolverManager   : Solving failed for problemId (1).
java.lang.IllegalStateException: The move thread with moveThreadIndex (3) has thrown an exception. Relayed here in the parent thread.
at org.optaplanner.core.impl.heuristic.thread.OrderByMoveIndexBlockingQueue.take(OrderByMoveIndexBlockingQueue.java:147) ~[optaplanner-core-7.45.0.Final.jar:7.45.0.Final]
at org.optaplanner.core.impl.constructionheuristic.decider.MultiThreadedConstructionHeuristicDecider.forageResult(MultiThreadedConstructionHeuristicDecider.java:186) ~[optaplanner-core-7.45.0.Final.jar:7.45.0.Final]
......
Caused by: java.lang.IllegalStateException: VariableListener corruption after completedAction (Undo(id=1, ...., startingTimeGrain=null {null -> {"grainIndex":1,"id":1}})):
The entity (id=2, ..., startingTimeGrain=1)'s shadow variable (Task.durationInGrains)'s corrupted value (4) changed to uncorrupted value (3) after all VariableListeners were triggered without changes to the genuine variables.
Maybe the VariableListener class (DurationUpdatingVariableListener) for that shadow variable (Task.durationInGrains) forgot to update it when one of its sources changed.
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertShadowVariablesAreNotStale(AbstractScoreDirector.java:545) ~[optaplanner-core-7.45.0.Final.jar:7.45.0.Final]
......

可以肯定的是,Task.setDurationInGrains((只能在afterVariableChanged((中调用。为什么会发生错误?这是Schedule.getTasksToBeUpdated(任务任务(的代码。

确保您的持续时间更新监听器能够正确地"清理";受撤消移动影响的所有任务的shadow变量。例如,如果你有:

Task(id=1, startingTimeGrain=null, durationInGrains=null)
Task(id=2, startingTimeGrain={"grainIndex":2,"id":1}, durationInGrains=3)

你做

Move(id=1, ...., startingTimeGrain=null {null -> {"grainIndex":1,"id":1}})

您的持续时间更新监听器可能会导致以下结果:

Task(id=1, startingTimeGrain={"grainIndex":1,"id":1}, durationInGrains=1)
Task(id=2, startingTimeGrain={"grainIndex":2,"id":1}, durationInGrains=4)

请注意,Task 2受到更改Task 1的移动的影响,侦听器更新了它的持续时间。

如果这是真的,那么在上面的移动被撤销之后:

Undo(id=1, ...., startingTimeGrain=null {null -> {"grainIndex":1,"id":1}})

这是为了避免侦听器损坏而必须发生的事情:

Task(id=1, startingTimeGrain=null, durationInGrains=null)
Task(id=2, startingTimeGrain={"grainIndex":2,"id":1}, durationInGrains=3)

注意,持续时间更新监听器负责:

  1. 撤消移动后,将任务1上的durationInGrains设置为null,将其从Grain 1中取消分配
  2. 正在重新计算受任务1移动影响的任务2的持续时间,并将其持续时间设置为与在任务1上移动之前相同的值(3(

最新更新