Skip to content

Commit 25120e5

Browse files
authored
chore: use precompute in some examples (#88)
1 parent 3602929 commit 25120e5

File tree

12 files changed

+113
-72
lines changed

12 files changed

+113
-72
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@
196196
<plugin>
197197
<artifactId>maven-surefire-plugin</artifactId>
198198
<version>3.5.4</version>
199+
<configuration>
200+
<redirectTestOutputToFile>true</redirectTestOutputToFile>
201+
</configuration>
199202
</plugin>
200203
<plugin>
201204
<groupId>com.diffplug.spotless</groupId>

src/main/java/ai/timefold/solver/benchmarks/examples/common/app/CommonApp.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,17 @@ public final Solution_ solve(String datasetName) {
117117
return solve(datasetName, 1L);
118118
}
119119

120+
public final Solution_ solve(Solution_ solution) {
121+
return solve(solution, 1L);
122+
}
123+
120124
public final Solution_ solve(String datasetName, long minutesSpentLimit) {
121125
var solutionFileIo = createSolutionFileIO();
122126
var solution = solutionFileIo.read(Path.of("data", dataDirName, "unsolved", datasetName).toFile().getAbsoluteFile());
127+
return solve(solution, minutesSpentLimit);
128+
}
129+
130+
public final Solution_ solve(Solution_ solution, long minutesSpentLimit) {
123131
var solverFactory = SolverFactory.<Solution_> createFromXmlResource(solverConfigResource);
124132
var solver = solverFactory.buildSolver(new SolverConfigOverride<Solution_>()
125133
.withTerminationConfig(new TerminationConfig()

src/main/java/ai/timefold/solver/benchmarks/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import ai.timefold.solver.core.api.score.stream.Constraint;
5151
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
5252
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
53+
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
54+
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream;
5355

5456
/**
5557
* Provides the constraints for the conference scheduling problem.
@@ -150,15 +152,20 @@ Constraint talkMutuallyExclusiveTalksTags(ConstraintFactory factory) {
150152
}
151153

152154
Constraint consecutiveTalksPause(ConstraintFactory factory) {
153-
return factory.forEachUniquePair(Talk.class,
154-
filtering((talk1, talk2) -> talk2.hasMutualSpeaker(talk1)))
155+
return factory.precompute(this::speakerTalks)
156+
.filter((talk1, talk2) -> talk1.getTimeslot() != null && talk2.getTimeslot() != null)
155157
.ifExists(ConferenceConstraintProperties.class,
156158
filtering((talk1, talk2, config) -> !talk1.getTimeslot().pauseExists(talk2.getTimeslot(),
157159
config.getMinimumConsecutiveTalksPauseInMinutes())))
158160
.penalize(HardSoftScore.ofHard(1), Talk::combinedDurationInMinutes)
159161
.asConstraint(CONSECUTIVE_TALKS_PAUSE);
160162
}
161163

164+
private BiConstraintStream<Talk, Talk> speakerTalks(PrecomputeFactory factory) {
165+
return factory.forEachUnfilteredUniquePair(Talk.class)
166+
.filter(Talk::hasMutualSpeaker);
167+
}
168+
162169
Constraint crowdControl(ConstraintFactory factory) {
163170
return factory.forEach(Talk.class)
164171
.filter(talk -> talk.getCrowdControlRisk() > 0)

src/main/java/ai/timefold/solver/benchmarks/examples/curriculumcourse/score/CurriculumCourseConstraintProvider.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
88
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;
99

10+
import java.util.Objects;
11+
1012
import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.Curriculum;
1113
import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.Lecture;
1214
import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.UnavailablePeriodPenalty;
@@ -15,6 +17,9 @@
1517
import ai.timefold.solver.core.api.score.stream.Constraint;
1618
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
1719
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
20+
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
21+
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream;
22+
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream;
1823

1924
public class CurriculumCourseConstraintProvider implements ConstraintProvider {
2025

@@ -37,16 +42,20 @@ public Constraint[] defineConstraints(ConstraintFactory factory) {
3742
// ************************************************************************
3843

3944
Constraint conflictingLecturesDifferentCourseInSamePeriod(ConstraintFactory factory) {
40-
return factory.forEach(CourseConflict.class)
45+
return factory.precompute(CurriculumCourseConstraintProvider::conflictingCourseLeft)
46+
.filter(((courseConflict, lecture1, lecture2) -> Objects.equals(lecture1.getPeriod(), lecture2.getPeriod())))
47+
.penalize(ONE_HARD,
48+
(courseConflict, lecture1, lecture2) -> courseConflict.getConflictCount())
49+
.asConstraint("conflictingLecturesDifferentCourseInSamePeriod");
50+
}
51+
52+
private static TriConstraintStream<CourseConflict, Lecture, Lecture> conflictingCourseLeft(PrecomputeFactory factory) {
53+
return factory.forEachUnfiltered(CourseConflict.class)
4154
.join(Lecture.class,
4255
equal(CourseConflict::getLeftCourse, Lecture::getCourse))
4356
.join(Lecture.class,
4457
equal((courseConflict, lecture1) -> courseConflict.getRightCourse(), Lecture::getCourse),
45-
equal((courseConflict, lecture1) -> lecture1.getPeriod(), Lecture::getPeriod))
46-
.filter(((courseConflict, lecture1, lecture2) -> lecture1 != lecture2))
47-
.penalize(ONE_HARD,
48-
(courseConflict, lecture1, lecture2) -> courseConflict.getConflictCount())
49-
.asConstraint("conflictingLecturesDifferentCourseInSamePeriod");
58+
filtering((courseConflict, lecture1, lecture2) -> lecture1 != lecture2));
5059
}
5160

5261
Constraint conflictingLecturesSameCourseInSamePeriod(ConstraintFactory factory) {
@@ -97,9 +106,7 @@ Constraint minimumWorkingDays(ConstraintFactory factory) {
97106
}
98107

99108
Constraint curriculumCompactness(ConstraintFactory factory) {
100-
return factory.forEach(Curriculum.class)
101-
.join(Lecture.class,
102-
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)))
109+
return factory.precompute(CurriculumCourseConstraintProvider::curriculumLectureLeft)
103110
.ifNotExists(Lecture.class,
104111
equal((curriculum, lecture) -> lecture.getDay(), Lecture::getDay),
105112
equal((curriculum, lecture) -> lecture.getTimeslotIndex(), lecture -> lecture.getTimeslotIndex() + 1),
@@ -112,6 +119,12 @@ Constraint curriculumCompactness(ConstraintFactory factory) {
112119
.asConstraint("curriculumCompactness");
113120
}
114121

122+
private static BiConstraintStream<Curriculum, Lecture> curriculumLectureLeft(PrecomputeFactory factory) {
123+
return factory.forEachUnfiltered(Curriculum.class)
124+
.join(Lecture.class,
125+
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)));
126+
}
127+
115128
Constraint roomStability(ConstraintFactory factory) {
116129
return factory.forEach(Lecture.class)
117130
.groupBy(Lecture::getCourse, countDistinct(Lecture::getRoom))

src/main/java/ai/timefold/solver/benchmarks/examples/flowshop/domain/Job.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public JobCompletionTime updateCompletionTime() {
8787
// and a job can only start on one machine after finishing the process at the previous machine.
8888
// The completion time of this job in the first machine depends only on the previous job completion time.
8989
// It can only start after the previous job is completed.
90-
var previousMachineCompletionTime = newCompletionTime.setCompletionTime(0, getPreviousCompletionTime(0) + allMachines[0].getProcessTime(id));
90+
var previousMachineCompletionTime =
91+
newCompletionTime.setCompletionTime(0, getPreviousCompletionTime(0) + allMachines[0].getProcessTime(id));
9192
for (var i = 1; i < allMachines.length; i++) {
9293
// The job execution for the following machines relies on the completion time of either the previous job
9394
// or the previous machine,

src/main/java/ai/timefold/solver/benchmarks/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import ai.timefold.solver.core.api.score.stream.Constraint;
1717
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
1818
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
19+
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
20+
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream;
1921

2022
public class MeetingSchedulingConstraintProvider implements ConstraintProvider {
2123

@@ -63,12 +65,9 @@ protected Constraint avoidOvertime(ConstraintFactory constraintFactory) {
6365
}
6466

6567
protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFactory) {
66-
return constraintFactory
67-
.forEachUniquePair(RequiredAttendance.class,
68-
equal(RequiredAttendance::getPerson))
69-
.join(MeetingAssignment.class,
70-
equal((leftRequiredAttendance, rightRequiredAttendance) -> leftRequiredAttendance.getMeeting(),
71-
MeetingAssignment::getMeeting))
68+
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::requiredAttendanceAssignmentLeft)
69+
.filter((leftRequiredAttendance, rightRequiredAttendance,
70+
assignment) -> assignment.getStartingTimeGrain() != null)
7271
.join(MeetingAssignment.class,
7372
equal((leftRequiredAttendance, rightRequiredAttendance, leftAssignment) -> rightRequiredAttendance
7473
.getMeeting(),
@@ -85,6 +84,15 @@ protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFact
8584
.asConstraint("Required attendance conflict");
8685
}
8786

87+
private static TriConstraintStream<RequiredAttendance, RequiredAttendance, MeetingAssignment>
88+
requiredAttendanceAssignmentLeft(PrecomputeFactory factory) {
89+
return factory.forEachUnfilteredUniquePair(RequiredAttendance.class,
90+
equal(RequiredAttendance::getPerson))
91+
.join(MeetingAssignment.class,
92+
equal((leftRequiredAttendance, rightRequiredAttendance) -> leftRequiredAttendance.getMeeting(),
93+
MeetingAssignment::getMeeting));
94+
}
95+
8896
protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) {
8997
return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
9098
.filter(meetingAssignment -> meetingAssignment.getRequiredCapacity() > meetingAssignment.getRoomCapacity())
@@ -109,15 +117,8 @@ protected Constraint startAndEndOnSameDay(ConstraintFactory constraintFactory) {
109117
// ************************************************************************
110118

111119
protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory constraintFactory) {
112-
return constraintFactory
113-
.forEach(RequiredAttendance.class)
114-
.join(PreferredAttendance.class,
115-
equal(RequiredAttendance::getPerson,
116-
PreferredAttendance::getPerson))
117-
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
118-
.filter(assignment -> assignment.getStartingTimeGrain() != null),
119-
equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(),
120-
MeetingAssignment::getMeeting))
120+
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::requiredAndPreferredAttendanceAssignmentLeft)
121+
.filter((requiredAttendance, preferredAttendance, assignment) -> assignment.getStartingTimeGrain() != null)
121122
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
122123
.filter(assignment -> assignment.getStartingTimeGrain() != null),
123124
equal((requiredAttendance, preferredAttendance, leftAssignment) -> preferredAttendance.getMeeting(),
@@ -134,14 +135,20 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co
134135
.asConstraint("Required and preferred attendance conflict");
135136
}
136137

138+
private static TriConstraintStream<RequiredAttendance, PreferredAttendance, MeetingAssignment>
139+
requiredAndPreferredAttendanceAssignmentLeft(PrecomputeFactory factory) {
140+
return factory.forEachUnfiltered(RequiredAttendance.class)
141+
.join(PreferredAttendance.class,
142+
equal(RequiredAttendance::getPerson, PreferredAttendance::getPerson))
143+
.join(MeetingAssignment.class,
144+
equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(),
145+
MeetingAssignment::getMeeting));
146+
}
147+
137148
protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFactory) {
138-
return constraintFactory
139-
.forEachUniquePair(PreferredAttendance.class,
140-
equal(PreferredAttendance::getPerson))
141-
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
142-
.filter(assignment -> assignment.getStartingTimeGrain() != null),
143-
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(),
144-
MeetingAssignment::getMeeting))
149+
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::preferredAttendanceAssignmentLeft)
150+
.filter((leftPreferredAttendance, rightPreferredAttendance,
151+
assignment) -> assignment.getStartingTimeGrain() != null)
145152
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
146153
.filter(assignment -> assignment.getStartingTimeGrain() != null),
147154
equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(),
@@ -158,6 +165,14 @@ protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFac
158165
.asConstraint("Preferred attendance conflict");
159166
}
160167

168+
private static TriConstraintStream<PreferredAttendance, PreferredAttendance, MeetingAssignment>
169+
preferredAttendanceAssignmentLeft(PrecomputeFactory factory) {
170+
return factory.forEachUnfilteredUniquePair(PreferredAttendance.class,
171+
equal(PreferredAttendance::getPerson))
172+
.join(MeetingAssignment.class,
173+
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(), MeetingAssignment::getMeeting));
174+
}
175+
161176
// ************************************************************************
162177
// Soft constraints
163178
// ************************************************************************
@@ -205,14 +220,9 @@ protected Constraint assignLargerRoomsFirst(ConstraintFactory constraintFactory)
205220
}
206221

207222
protected Constraint roomStability(ConstraintFactory constraintFactory) {
208-
return constraintFactory.forEach(Attendance.class)
209-
.join(Attendance.class,
210-
equal(Attendance::getPerson),
211-
filtering((leftAttendance,
212-
rightAttendance) -> leftAttendance.getMeeting() != rightAttendance.getMeeting()))
213-
.join(MeetingAssignment.class,
214-
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(),
215-
MeetingAssignment::getMeeting))
223+
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::roomStabilityJoin)
224+
.filter((leftAttendance, rightAttendance, assignment) -> assignment.getStartingTimeGrain() != null
225+
&& assignment.getRoom() != null)
216226
.join(MeetingAssignment.class,
217227
equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(),
218228
MeetingAssignment::getMeeting),
@@ -227,4 +237,15 @@ protected Constraint roomStability(ConstraintFactory constraintFactory) {
227237
.penalize(HardMediumSoftScore.ONE_SOFT)
228238
.asConstraint("Room stability");
229239
}
240+
241+
private static TriConstraintStream<Attendance, Attendance, MeetingAssignment> roomStabilityJoin(PrecomputeFactory factory) {
242+
return factory.forEachUnfiltered(Attendance.class)
243+
.join(Attendance.class,
244+
equal(Attendance::getPerson),
245+
filtering((leftAttendance,
246+
rightAttendance) -> leftAttendance.getMeeting() != rightAttendance.getMeeting()))
247+
.join(MeetingAssignment.class,
248+
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(), MeetingAssignment::getMeeting));
249+
}
250+
230251
}

src/main/java/ai/timefold/solver/benchmarks/examples/nurserostering/score/NurseRosteringConstraintProvider.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ Constraint oneShiftPerDay(ConstraintFactory constraintFactory) {
8282
// Soft constraints
8383
// ############################################################################
8484

85-
private static <A, B, C> TriConstraintStream<A, B, C> outerJoin(BiConstraintStream<A, B> source,
86-
Class<C> joinedClass, TriJoiner<A, B, C> joiner) {
87-
return source.join(joinedClass, joiner)
88-
.concat(source.ifNotExists(joinedClass, joiner));
89-
}
90-
9185
Constraint minimumAndMaximumNumberOfAssignments(ConstraintFactory constraintFactory) {
9286
var assignmentLimitedEmployeeStream = constraintFactory
9387
.forEach(MinMaxContractLine.class)
@@ -114,6 +108,12 @@ Constraint minimumAndMaximumNumberOfAssignments(ConstraintFactory constraintFact
114108
.asConstraint("Minimum and maximum number of assignments");
115109
}
116110

111+
private static <A, B, C> TriConstraintStream<A, B, C> outerJoin(BiConstraintStream<A, B> source, Class<C> joinedClass,
112+
TriJoiner<A, B, C> joiner) {
113+
return source.join(joinedClass, joiner)
114+
.concat(source.ifNotExists(joinedClass, joiner));
115+
}
116+
117117
// Min/Max consecutive working days
118118
Constraint consecutiveWorkingDays(ConstraintFactory constraintFactory) {
119119
return constraintFactory.forEach(MinMaxContractLine.class)

src/main/resources/logback.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
<root level="info">
1616
<appender-ref ref="consoleAppender" />
17-
<!--<appender-ref ref="fileAppender" />-->
1817
</root>
1918

2019
</configuration>

src/test/java/ai/timefold/solver/benchmarks/examples/conferencescheduling/app/ConferenceSchedulingSmokeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ protected ConferenceSchedulingApp createCommonApp() {
2020
protected Stream<TestData<HardSoftScore>> testData() {
2121
return Stream.of(
2222
TestData.of(UNSOLVED_DATA_FILE,
23-
HardSoftScore.ofSoft(-1025250),
24-
HardSoftScore.ofSoft(-1100400)));
23+
HardSoftScore.ofSoft(-806745),
24+
HardSoftScore.ofSoft(-811140)));
2525
}
2626
}

src/test/java/ai/timefold/solver/benchmarks/examples/curriculumcourse/app/CurriculumCourseSmokeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ protected CurriculumCourseApp createCommonApp() {
1919
protected Stream<TestData<HardSoftScore>> testData() {
2020
return Stream.of(
2121
TestData.of(UNSOLVED_DATA_FILE,
22-
HardSoftScore.ofSoft(-55),
23-
HardSoftScore.ofSoft(-64)));
22+
HardSoftScore.ofSoft(-20),
23+
HardSoftScore.ofSoft(-21)));
2424
}
2525
}

0 commit comments

Comments
 (0)