Skip to content

Commit 172cd1c

Browse files
committed
chore: address comments
1 parent 423624b commit 172cd1c

5 files changed

Lines changed: 120 additions & 32 deletions

File tree

java/food-packaging/src/main/java/org/acme/foodpackaging/domain/Job.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable;
88
import ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable;
99
import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable;
10+
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;
1011
import com.fasterxml.jackson.annotation.JsonIgnore;
1112

1213
import java.time.Duration;
@@ -33,6 +34,14 @@ public class Job {
3334

3435
@InverseRelationShadowVariable(sourceVariableName = "jobs")
3536
private Line line;
37+
@ShadowVariable(
38+
variableListenerClass = LineOperatorUpdatingVariableListener.class,
39+
sourceEntityClass = Line.class,
40+
sourceVariableName = "operator")
41+
@ShadowVariable(
42+
variableListenerClass = JobOperatorUpdatingVariableListener.class,
43+
sourceVariableName = "line")
44+
private Operator lineOperator;
3645
@JsonIgnore
3746
@PreviousElementShadowVariable(sourceVariableName = "jobs")
3847
private Job previousJob;
@@ -129,7 +138,11 @@ public void setLine(Line line) {
129138
}
130139

131140
public Operator getLineOperator() {
132-
return line != null ? line.getOperator() : null;
141+
return lineOperator;
142+
}
143+
144+
public void setLineOperator(Operator lineOperator) {
145+
this.lineOperator = lineOperator;
133146
}
134147

135148
public Job getPreviousJob() {
@@ -201,12 +214,4 @@ private void updateStartCleaningDateTime() {
201214
var endTime = startProduction == null ? null : startProduction.plus(getDuration());
202215
setEndDateTime(endTime);
203216
}
204-
205-
public Duration getCleanupProductionDurationDiff(Job otherJob) {
206-
var start = startCleaningDateTime.isAfter(otherJob.getStartCleaningDateTime()) ?
207-
startCleaningDateTime : otherJob.getStartCleaningDateTime();
208-
var end = startProductionDateTime.isBefore(otherJob.getStartProductionDateTime()) ?
209-
startProductionDateTime : otherJob.getStartProductionDateTime();
210-
return Duration.between(start, end);
211-
}
212217
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.acme.foodpackaging.domain;
2+
3+
import ai.timefold.solver.core.api.domain.variable.VariableListener;
4+
import ai.timefold.solver.core.api.score.director.ScoreDirector;
5+
import org.jspecify.annotations.NonNull;
6+
7+
import java.util.Objects;
8+
9+
public class JobOperatorUpdatingVariableListener implements VariableListener<PackagingSchedule, Job> {
10+
11+
private static final String LINE_OPERATOR_FIELD = "lineOperator";
12+
13+
@Override
14+
public void beforeVariableChanged(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
15+
// Empty method
16+
}
17+
18+
@Override
19+
public void afterVariableChanged(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
20+
if (job.getLine() == null && job.getLineOperator() != null) {
21+
scoreDirector.beforeVariableChanged(job, LINE_OPERATOR_FIELD);
22+
job.setLineOperator(null);
23+
scoreDirector.afterVariableChanged(job, LINE_OPERATOR_FIELD);
24+
} else if (!Objects.equals(job.getLineOperator(), job.getLine().getOperator())) {
25+
scoreDirector.beforeVariableChanged(job, LINE_OPERATOR_FIELD);
26+
job.setLineOperator(job.getLine().getOperator());
27+
scoreDirector.afterVariableChanged(job, LINE_OPERATOR_FIELD);
28+
}
29+
}
30+
31+
@Override
32+
public void beforeEntityAdded(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
33+
// Empty method
34+
}
35+
36+
@Override
37+
public void afterEntityAdded(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
38+
// Empty method
39+
}
40+
41+
@Override
42+
public void beforeEntityRemoved(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
43+
// Empty method
44+
}
45+
46+
@Override
47+
public void afterEntityRemoved(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Job job) {
48+
// Empty method
49+
}
50+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.acme.foodpackaging.domain;
2+
3+
import ai.timefold.solver.core.api.domain.variable.VariableListener;
4+
import ai.timefold.solver.core.api.score.director.ScoreDirector;
5+
import org.jspecify.annotations.NonNull;
6+
7+
import java.util.Objects;
8+
9+
public class LineOperatorUpdatingVariableListener implements VariableListener<PackagingSchedule, Line> {
10+
@Override
11+
public void beforeVariableChanged(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
12+
// Empty method
13+
}
14+
15+
@Override
16+
public void afterVariableChanged(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
17+
for (var job : line.getJobs()) {
18+
if (!Objects.equals(job.getLineOperator(), line.getOperator())) {
19+
scoreDirector.beforeVariableChanged(job, "lineOperator");
20+
job.setLineOperator(line.getOperator());
21+
scoreDirector.afterVariableChanged(job, "lineOperator");
22+
}
23+
}
24+
}
25+
26+
@Override
27+
public void beforeEntityAdded(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
28+
// Empty method
29+
}
30+
31+
@Override
32+
public void afterEntityAdded(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
33+
// Empty method
34+
}
35+
36+
@Override
37+
public void beforeEntityRemoved(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
38+
// Empty method
39+
}
40+
41+
@Override
42+
public void afterEntityRemoved(@NonNull ScoreDirector<PackagingSchedule> scoreDirector, @NonNull Line line) {
43+
// Empty method
44+
}
45+
}

java/food-packaging/src/main/java/org/acme/foodpackaging/solver/FoodPackagingConstraintProvider.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
import ai.timefold.solver.core.api.score.stream.Joiners;
1010

1111
import org.acme.foodpackaging.domain.Job;
12-
import org.acme.foodpackaging.domain.Line;
1312

1413
public class FoodPackagingConstraintProvider implements ConstraintProvider {
1514

1615
@Override
1716
public Constraint[] defineConstraints(ConstraintFactory factory) {
18-
return new Constraint[]{
17+
return new Constraint[] {
1918
// Hard constraints
2019
maxEndDateTime(factory),
2120
// Medium constraints
@@ -56,14 +55,18 @@ protected Constraint idealEndDateTime(ConstraintFactory factory) {
5655

5756
// TODO Currently dwarfed by minimizeAndLoadBalanceMakeSpan in the same score level, because that squares
5857
protected Constraint operatorCleaningConflict(ConstraintFactory factory) {
59-
return factory.forEach(Line.class)
60-
.flattenLast(Line::getJobs)
61-
.join(factory.forEach(Line.class).flattenLast(Line::getJobs),
62-
Joiners.equal(Job::getLineOperator),
58+
return factory.forEach(Job.class)
59+
.filter(job ->job.getLine() != null)
60+
.join(factory.forEach(Job.class).filter(job ->job.getLine() != null),
61+
Joiners.equal(job -> job.getLine().getOperator()),
6362
Joiners.overlapping(Job::getStartCleaningDateTime, Job::getStartProductionDateTime),
6463
Joiners.lessThan(Job::getId))
65-
.penalizeLong(HardMediumSoftLongScore.ONE_SOFT, (job1, job2) ->
66-
job1.getCleanupProductionDurationDiff(job2).toMinutes())
64+
.penalizeLong(HardMediumSoftLongScore.ONE_SOFT, (job1, job2) -> Duration.between(
65+
(job1.getStartCleaningDateTime().compareTo(job2.getStartCleaningDateTime()) > 0)
66+
? job1.getStartCleaningDateTime() : job2.getStartCleaningDateTime(),
67+
(job1.getStartProductionDateTime().compareTo(job2.getStartProductionDateTime()) < 0)
68+
? job1.getStartProductionDateTime() : job2.getStartProductionDateTime()
69+
).toMinutes())
6770
.asConstraint("Operator cleaning conflict");
6871
}
6972

java/food-packaging/src/test/java/org/acme/foodpackaging/solver/FoodPackagingConstraintProviderTest.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import org.acme.foodpackaging.domain.Operator;
99
import org.acme.foodpackaging.domain.PackagingSchedule;
1010
import org.acme.foodpackaging.domain.Product;
11-
import org.junit.jupiter.api.Disabled;
1211
import org.junit.jupiter.api.Test;
1312

1413
import java.time.Duration;
@@ -122,20 +121,6 @@ void minimizeMakespan() {
122121
.penalizesBy(100L * 100L + 1250L * 1250L);
123122
}
124123

125-
@Test
126-
@Disabled("The constraint is currently commented out.")
127-
void minimizeCleaningDuration() {
128-
Job job1 = new Job("1", "job1", PRODUCT_A_SMALL, Duration.ofMinutes(6000), DAY_START_TIME, null, null, 1, false);
129-
Job job2 = new Job("2", "job2", PRODUCT_A_SMALL, Duration.ofMinutes(200), DAY_START_TIME, null, null, 1, false,
130-
DAY_START_TIME, DAY_START_TIME);
131-
Job job3 = new Job("3", "job3", PRODUCT_A_SMALL, Duration.ofMinutes(150), DAY_START_TIME, null, null, 1, false,
132-
DAY_START_TIME.plusMinutes(30), DAY_START_TIME.plusMinutes(40));
133-
134-
constraintVerifier.verifyThat(FoodPackagingConstraintProvider::minimizeCleaningDuration)
135-
.given(job1, job2, job3)
136-
.penalizesBy(10L);
137-
}
138-
139124
// ************************************************************************
140125
// Helper methods
141126
// ************************************************************************

0 commit comments

Comments
 (0)