In my project, Assigning some tasks to the production line and calculated their start and end times. Here are the main logics.
- Multiple tasks belong to the same manufacturing order, and there is a sequential dependency relationship between these tasks. For instance, there are 2 tasks in a manufacturing order: Job1 and Job2. Job1 is the predecessor job of Job2, so the Job2 is the successor job of Job1. That is, Job2 must wait for Job1 to complete before starting, which means that the start time of Job2 must be later than the end time of Job1.
- The jobs in a production line must be sequenced. For instance, there are 2 jobs in a production line - Job3(its index is 0, and it does not belong to the same manufacturing order as job2) and Job2(its index is 1). So Job2's start time must be later than the end time of Job3.
So, The start time of job 2 must be later than the end time of job 1 and job 3.
I use the PlanningListVariable to implement this model. I created a planning list variable - jobList on class ProductionLine and specified the method that needs to update the start time in the Job class with the @CascadingupdateShadowVariable annotation.
The code for the Job class is as follows:
@PlanningEntity
public class Job {
@PlanningId
private Long id;
private String code;
// which production lines can this task be assigned to
private List<ProductionLine> availableProductionLineList;
// a duration map that contains job duration on each resource
private Map<ProductionLine, Duration> durationMap;
// the predecessor job list
private List<Job> predecessorJobList;
// the successor job list
private List<Job> successorJobList;
@InverseRelationShadowVariable(sourceVariableName = "jobList")
private ProductionLine productionLine;
@PreviousElementShadowVariable(sourceVariableName = "jobList")
private Job previousJob;
@NextElementShadowVariable(sourceVariableName = "jobList")
private Job nextJob;
// a method that updates the job start time
@CascadingUpdateShadowVariable(targetMethodName = "updateStartTimes")
private LocalDateTime startTime;
public Job() {
}
public void updateStartTimes() {
if (this.productionLine == null) {
if (this.getStartTime() != null) {
this.setStartTime(null);
}
} else {
this.setStartTime(this.calculateStartTime());
}
}
private LocalDateTime calculateStartTime() {
// the previous job end time
LocalDateTime previousEndTime = this.previousJob == null ? this.productionLine.getStartTime() : this.previousJob.getEndTime();
// the predecessor job end time
LocalDateTime predecessorEndTime = null;
if(!this.predecessorJobList.isEmpty()) {
predecessorEndTime = Collections.max(this.predecessorJobList, Comparatorparing(Job::getEndTime)).getEndTime();
}
// the later between predecessor end time and previous end time
return this.getMaxDateTime(predecessorEndTime, previousEndTime);
}
private LocalDateTime getMaxDateTime(LocalDateTime dateTime1, LocalDateTime dateTime2) {
if (dateTime1 == null && dateTime2 == null) {
return null;
} else if (dateTime1 == null) {
return dateTime2;
} else if (dateTime2 == null) {
return dateTime1;
} else {
return dateTime1.isAfter(dateTime2) ? dateTime1 : dateTime2;
}
}
public Duration getDuration() {
return this.durationMap.getOrDefault(this.productionLine, null);
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
if(this.startTime == null) {
return null;
}
return this.startTime.plus(this.getDuration());
}
// others getter, setter .....
}
But I got a Variable Listener disruption exception.
16:18:12.147 [main ] TRACE Model annotations parsed for solution JobScheduling:
16:18:12.148 [main ] TRACE Entity Job:
16:18:12.149 [main ] TRACE Shadow variable nextJob (reflection)
16:18:12.149 [main ] TRACE Shadow variable previousJob (reflection)
16:18:12.149 [main ] TRACE Shadow variable productionLine (reflection)
16:18:12.149 [main ] TRACE Shadow variable startTime (reflection)
16:18:12.149 [main ] TRACE Entity ProductionLine:
16:18:12.149 [main ] TRACE Genuine variable jobList (reflection)
16:18:12.259 [main ] DEBUG Constraint weights for solution (1):
16:18:12.276 [main ] DEBUG Constraint weights for solution (1):
16:18:12.296 [main ] INFO Solving started: time spent (43), best score (-4init/0hard/0medium/0soft), environment mode (TRACKED_FULL_ASSERT), move thread count (NONE), random (JDK with seed 0).
16:18:12.301 [main ] INFO Problem scale: entity count (3), variable count (3), approximate value count (4), approximate problem scale (360).
16:18:12.313 [main ] DEBUG Constraint weights for solution (1):
16:18:12.315 [main ] TRACE Move index (0), score (-3init/0hard/0medium/0soft), move (Order1,Job1[] {null -> Line1[0]}).
16:18:12.318 [main ] DEBUG Constraint weights for solution (1):
16:18:12.318 [main ] TRACE Move index (1), score (-3init/-1hard/0medium/0soft), move (Order1,Job1[] {null -> Line2[0]}).
16:18:12.319 [main ] DEBUG Constraint weights for solution (1):
16:18:12.319 [main ] TRACE Move index (2), score (-3init/-1hard/0medium/0soft), move (Order1,Job1[] {null -> Line3[0]}).
16:18:12.320 [main ] DEBUG Constraint weights for solution (1):
16:18:12.320 [main ] DEBUG CH step (0), time spent (67), score (-3init/0hard/0medium/0soft), selected move count (3), picked move (Order1,Job1[] {null -> Line1[0]}).
16:18:12.324 [main ] DEBUG Constraint weights for solution (1):
16:18:12.324 [main ] TRACE Move index (0), score (-2init/-1hard/0medium/0soft), move (Order1,Job2[] {null -> Line1[0]}).
16:18:12.326 [main ] DEBUG Constraint weights for solution (1):
16:18:12.326 [main ] TRACE Move index (1), score (-2init/0hard/0medium/0soft), move (Order1,Job2[] {null -> Line2[0]}).
16:18:12.326 [main ] DEBUG Constraint weights for solution (1):
16:18:12.327 [main ] TRACE Move index (2), score (-2init/-1hard/0medium/0soft), move (Order1,Job2[] {null -> Line3[0]}).
16:18:12.327 [main ] DEBUG Constraint weights for solution (1):
16:18:12.327 [main ] TRACE Move index (3), score (-2init/-1hard/0medium/0soft), move (Order1,Job2[] {null -> Line1[1]}).
16:18:12.328 [main ] DEBUG Constraint weights for solution (1):
16:18:12.328 [main ] DEBUG CH step (1), time spent (75), score (-2init/0hard/0medium/0soft), selected move count (4), picked move (Order1,Job2[] {null -> Line2[0]}).
16:18:12.328 [main ] DEBUG Constraint weights for solution (1):
16:18:12.329 [main ] TRACE Move index (0), score (-1init/0hard/0medium/0soft), move (Order2,Job1[] {null -> Line1[0]}).
16:18:12.329 [main ] DEBUG Constraint weights for solution (1):
16:18:12.329 [main ] TRACE Move index (1), score (-1init/-1hard/0medium/0soft), move (Order2,Job1[] {null -> Line2[0]}).
16:18:12.330 [main ] DEBUG Constraint weights for solution (1):
16:18:12.330 [main ] TRACE Move index (2), score (-1init/-1hard/0medium/0soft), move (Order2,Job1[] {null -> Line3[0]}).
16:18:12.330 [main ] DEBUG Constraint weights for solution (1):
16:18:12.330 [main ] TRACE Move index (3), score (-1init/0hard/0medium/0soft), move (Order2,Job1[] {null -> Line1[1]}).
16:18:12.331 [main ] DEBUG Constraint weights for solution (1):
16:18:12.331 [main ] TRACE Move index (4), score (-1init/-1hard/0medium/0soft), move (Order2,Job1[] {null -> Line2[1]}).
16:18:12.331 [main ] DEBUG Constraint weights for solution (1):
Exception in thread "main" java.lang.IllegalStateException: VariableListener corruption after completedAction (Order2,Job1[Line1] {null -> Line1[0]}):
The entity (Order1,Job2[Line2])'s shadow variable (Job.startTime)'s corrupted value (2024-10-01T05:00) changed to uncorrupted value (2024-10-01T10:00) after all variable listeners were triggered without changes to the genuine variables.
Maybe one of the listeners ([]) for that shadow variable (Job.startTime) fot to update it when one of its sourceVariables ([]) changed.
Or vice versa, maybe one of the listeners computes this shadow variable using a planning variable that is not declared as its source. Use the repeatable @ShadowVariable annotation for each source variable that is used to compute this shadow variable.
at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.assertShadowVariablesAreNotStale(AbstractScoreDirector.java:568)
at ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope.assertShadowVariablesAreNotStale(AbstractPhaseScope.java:186)
at ai.timefold.solver.core.impl.phase.AbstractPhase.predictWorkingStepScore(AbstractPhase.java:152)
at ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.doStep(DefaultConstructionHeuristicPhase.java:97)
at ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:83)
at ai.timefold.solver.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:82)
at ai.timefold.solver.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:200)
at com.easyplan.Main.main(Main.java:284)
And there are no complex constraints implemented in ConstraintStream, only matching the production line allocation of tasks, similar to Skill Match in TaskAssignment:
protected Constraint resourceMatch(ConstraintFactory factory) {
Constraint constraint = factory.forEach(Job.class)
.filter(job -> job.getProductionLine() != null && !job.getAvailableResourceList().contains(job.getProductionLine()))
.penalizeLong(HardMediumSoftLongScore.ONE_HARD, Job::getDefaultScore)
.asConstraint("SkillMissing");
return constraint;
}
This should be a simple problem, but I don't have any idea about this Listener corruption. Has anyone encountered this? Or any suggestions?
I have tried the Chained Through Time pattern to implement this scenario, but PlanningListVariable is simpler and more user-friendly.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745100442a4611240.html
评论列表(0条)