I use the Chained Though Time pattern for task scheduling (or named job scheduling) scenes. There are predecessor/successor tasks (for example, tasks formed by two processes before and after the same product) tasks, that is, a task can only start after its predecessor tasks are completed. What I do is: Based on the TaskAssigning example, add a planning variable - delay, to the class - Task, which is the gap between a task and its predecessor task on the same employee. The startTime of a task is the completion time of its predecessor task, plus delay. When a task starts earlier than the completion time of its predecessor task, a penalty constraint will be triggered to keep the delay at an appropriate value to ensure that the task It must be started after its predecessor tasks are completed. But a Score Corruption throw for this constraint. I have carefully checked the logic of the constraints and no problems. Is there anything missing in this approach?
Following is the exception message:
Exception in thread "main" java.lang.IllegalStateException: Score corruption (420medium): the workingScore (0hard/0medium/0soft) is not the uncorruptedScore (0hard/-420medium/0soft) after completedAction (M1J1[R2] {9 -> 9}): Score corruption analysis: The corrupted scoreDirector has no ConstraintMatch(s) which are in excess. The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing: com.easyplan.domain/jobStartTimeLimitation/[M1J2[R1]]=0hard/-420medium/0soft Maybe there is a bug in the score constraints of those ConstraintMatch(s). Maybe a score constraint doesn't select all the entities it depends on, but finds some through a reference in a selected entity. This corrupts incremental score calculation, because the constraint is not re-evaluated if such a non-selected entity changes. Shadow variable corruption in the corrupted scoreDirector: None at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertScoreFromScratch(AbstractScoreDirector.java:627) at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertWorkingScoreFromScratch(AbstractScoreDirector.java:603) at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:210) at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:117) at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:101) at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:72) at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:83) at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:193) at com.easyplan.Main.main(Main.java:299)
// method in ConstraintProvider
protected Constraint jobStartBeforePredecessorEnd(ConstraintFactory factory) {
return factory.forEach(Job.class)
.filter(job -> job.getResource() != null && job.startedTooEarly() > 0)
.penalizeLong(HardMediumSoftLongScore.ONE_MEDIUM,
Job::startedTooEarly)
.asConstraint("jobStartTimeLimitation");
}
// method in class Job
public Long startedTooEarly() {
if(this.previousJobOrResource == null) {
return 0L;
}
if(this.predecessorJobList != null && !this.predecessorJobList.isEmpty()) {
LocalDateTime latestPredecessorEndTime = this.startTime;
for (Job predecessorJob : this.predecessorJobList) {
if(predecessorJob.getPreviousJobOrResource() == null) continue;
if (predecessorJob.getEndTime().isAfter(latestPredecessorEndTime)) {
latestPredecessorEndTime = predecessorJob.getEndTime();
}
}
if (latestPredecessorEndTime.isAfter(this.startTime)) {
Duration leadingTime = Duration.between(this.startTime, latestPredecessorEndTime);
return leadingTime.toMinutes();
} else {
return 0L;
}
}
return 0L;
}
The reason why I use Chained Through Time instead of PlanningListVariable is because my system requires real-time planning, and PlanningVarialbe still does not provide this feature.
Thank you in advance for your suggestion!