Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,17 @@ public final Solution_ solve(String datasetName) {
return solve(datasetName, 1L);
}

public final Solution_ solve(Solution_ solution) {
return solve(solution, 1L);
}

public final Solution_ solve(String datasetName, long minutesSpentLimit) {
var solutionFileIo = createSolutionFileIO();
var solution = solutionFileIo.read(Path.of("data", dataDirName, "unsolved", datasetName).toFile().getAbsoluteFile());
return solve(solution, minutesSpentLimit);
}

public final Solution_ solve(Solution_ solution, long minutesSpentLimit) {
var solverFactory = SolverFactory.<Solution_> createFromXmlResource(solverConfigResource);
var solver = solverFactory.buildSolver(new SolverConfigOverride<Solution_>()
.withTerminationConfig(new TerminationConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream;

/**
* Provides the constraints for the conference scheduling problem.
Expand Down Expand Up @@ -150,15 +152,20 @@ Constraint talkMutuallyExclusiveTalksTags(ConstraintFactory factory) {
}

Constraint consecutiveTalksPause(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
filtering((talk1, talk2) -> talk2.hasMutualSpeaker(talk1)))
return factory.precompute(this::speakerTalks)
.filter((talk1, talk2) -> talk1.getTimeslot() != null && talk2.getTimeslot() != null)
.ifExists(ConferenceConstraintProperties.class,
filtering((talk1, talk2, config) -> !talk1.getTimeslot().pauseExists(talk2.getTimeslot(),
config.getMinimumConsecutiveTalksPauseInMinutes())))
.penalize(HardSoftScore.ofHard(1), Talk::combinedDurationInMinutes)
.asConstraint(CONSECUTIVE_TALKS_PAUSE);
}

private BiConstraintStream<Talk, Talk> speakerTalks(PrecomputeFactory factory) {
return factory.forEachUnfilteredUniquePair(Talk.class)
.filter(Talk::hasMutualSpeaker);
}

Constraint crowdControl(ConstraintFactory factory) {
return factory.forEach(Talk.class)
.filter(talk -> talk.getCrowdControlRisk() > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;

import java.util.Objects;

import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.Curriculum;
import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.Lecture;
import ai.timefold.solver.benchmarks.examples.curriculumcourse.domain.UnavailablePeriodPenalty;
Expand All @@ -15,6 +17,9 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream;
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream;

public class CurriculumCourseConstraintProvider implements ConstraintProvider {

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

Constraint conflictingLecturesDifferentCourseInSamePeriod(ConstraintFactory factory) {
return factory.forEach(CourseConflict.class)
return factory.precompute(CurriculumCourseConstraintProvider::conflictingCourseLeft)
.filter(((courseConflict, lecture1, lecture2) -> Objects.equals(lecture1.getPeriod(), lecture2.getPeriod())))
.penalize(ONE_HARD,
(courseConflict, lecture1, lecture2) -> courseConflict.getConflictCount())
.asConstraint("conflictingLecturesDifferentCourseInSamePeriod");
}

private static TriConstraintStream<CourseConflict, Lecture, Lecture> conflictingCourseLeft(PrecomputeFactory factory) {
return factory.forEachUnfiltered(CourseConflict.class)
.join(Lecture.class,
equal(CourseConflict::getLeftCourse, Lecture::getCourse))
.join(Lecture.class,
equal((courseConflict, lecture1) -> courseConflict.getRightCourse(), Lecture::getCourse),
equal((courseConflict, lecture1) -> lecture1.getPeriod(), Lecture::getPeriod))
.filter(((courseConflict, lecture1, lecture2) -> lecture1 != lecture2))
.penalize(ONE_HARD,
(courseConflict, lecture1, lecture2) -> courseConflict.getConflictCount())
.asConstraint("conflictingLecturesDifferentCourseInSamePeriod");
filtering((courseConflict, lecture1, lecture2) -> lecture1 != lecture2));
}

Constraint conflictingLecturesSameCourseInSamePeriod(ConstraintFactory factory) {
Expand Down Expand Up @@ -97,9 +106,7 @@ Constraint minimumWorkingDays(ConstraintFactory factory) {
}

Constraint curriculumCompactness(ConstraintFactory factory) {
return factory.forEach(Curriculum.class)
.join(Lecture.class,
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)))
return factory.precompute(CurriculumCourseConstraintProvider::curriculumLectureLeft)
.ifNotExists(Lecture.class,
equal((curriculum, lecture) -> lecture.getDay(), Lecture::getDay),
equal((curriculum, lecture) -> lecture.getTimeslotIndex(), lecture -> lecture.getTimeslotIndex() + 1),
Expand All @@ -112,6 +119,12 @@ Constraint curriculumCompactness(ConstraintFactory factory) {
.asConstraint("curriculumCompactness");
}

private static BiConstraintStream<Curriculum, Lecture> curriculumLectureLeft(PrecomputeFactory factory) {
return factory.forEachUnfiltered(Curriculum.class)
.join(Lecture.class,
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)));
}

Constraint roomStability(ConstraintFactory factory) {
return factory.forEach(Lecture.class)
.groupBy(Lecture::getCourse, countDistinct(Lecture::getRoom))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public JobCompletionTime updateCompletionTime() {
// and a job can only start on one machine after finishing the process at the previous machine.
// The completion time of this job in the first machine depends only on the previous job completion time.
// It can only start after the previous job is completed.
var previousMachineCompletionTime = newCompletionTime.setCompletionTime(0, getPreviousCompletionTime(0) + allMachines[0].getProcessTime(id));
var previousMachineCompletionTime =
newCompletionTime.setCompletionTime(0, getPreviousCompletionTime(0) + allMachines[0].getProcessTime(id));
for (var i = 1; i < allMachines.length; i++) {
// The job execution for the following machines relies on the completion time of either the previous job
// or the previous machine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
import ai.timefold.solver.core.api.score.stream.PrecomputeFactory;
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream;

public class MeetingSchedulingConstraintProvider implements ConstraintProvider {

Expand Down Expand Up @@ -63,12 +65,9 @@ protected Constraint avoidOvertime(ConstraintFactory constraintFactory) {
}

protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(RequiredAttendance.class,
equal(RequiredAttendance::getPerson))
.join(MeetingAssignment.class,
equal((leftRequiredAttendance, rightRequiredAttendance) -> leftRequiredAttendance.getMeeting(),
MeetingAssignment::getMeeting))
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::requiredAttendanceAssignmentLeft)
.filter((leftRequiredAttendance, rightRequiredAttendance,
assignment) -> assignment.getStartingTimeGrain() != null)
.join(MeetingAssignment.class,
equal((leftRequiredAttendance, rightRequiredAttendance, leftAssignment) -> rightRequiredAttendance
.getMeeting(),
Expand All @@ -85,6 +84,15 @@ protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFact
.asConstraint("Required attendance conflict");
}

private static TriConstraintStream<RequiredAttendance, RequiredAttendance, MeetingAssignment>
requiredAttendanceAssignmentLeft(PrecomputeFactory factory) {
return factory.forEachUnfilteredUniquePair(RequiredAttendance.class,
equal(RequiredAttendance::getPerson))
.join(MeetingAssignment.class,
equal((leftRequiredAttendance, rightRequiredAttendance) -> leftRequiredAttendance.getMeeting(),
MeetingAssignment::getMeeting));
}

protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) {
return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getRequiredCapacity() > meetingAssignment.getRoomCapacity())
Expand All @@ -109,15 +117,8 @@ protected Constraint startAndEndOnSameDay(ConstraintFactory constraintFactory) {
// ************************************************************************

protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEach(RequiredAttendance.class)
.join(PreferredAttendance.class,
equal(RequiredAttendance::getPerson,
PreferredAttendance::getPerson))
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(),
MeetingAssignment::getMeeting))
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::requiredAndPreferredAttendanceAssignmentLeft)
.filter((requiredAttendance, preferredAttendance, assignment) -> assignment.getStartingTimeGrain() != null)
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((requiredAttendance, preferredAttendance, leftAssignment) -> preferredAttendance.getMeeting(),
Expand All @@ -134,14 +135,20 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co
.asConstraint("Required and preferred attendance conflict");
}

private static TriConstraintStream<RequiredAttendance, PreferredAttendance, MeetingAssignment>
requiredAndPreferredAttendanceAssignmentLeft(PrecomputeFactory factory) {
return factory.forEachUnfiltered(RequiredAttendance.class)
.join(PreferredAttendance.class,
equal(RequiredAttendance::getPerson, PreferredAttendance::getPerson))
.join(MeetingAssignment.class,
equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(),
MeetingAssignment::getMeeting));
}

protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFactory) {
return constraintFactory
.forEachUniquePair(PreferredAttendance.class,
equal(PreferredAttendance::getPerson))
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(),
MeetingAssignment::getMeeting))
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::preferredAttendanceAssignmentLeft)
.filter((leftPreferredAttendance, rightPreferredAttendance,
assignment) -> assignment.getStartingTimeGrain() != null)
.join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(),
Expand All @@ -158,6 +165,14 @@ protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFac
.asConstraint("Preferred attendance conflict");
}

private static TriConstraintStream<PreferredAttendance, PreferredAttendance, MeetingAssignment>
preferredAttendanceAssignmentLeft(PrecomputeFactory factory) {
return factory.forEachUnfilteredUniquePair(PreferredAttendance.class,
equal(PreferredAttendance::getPerson))
.join(MeetingAssignment.class,
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(), MeetingAssignment::getMeeting));
}

// ************************************************************************
// Soft constraints
// ************************************************************************
Expand Down Expand Up @@ -205,14 +220,9 @@ protected Constraint assignLargerRoomsFirst(ConstraintFactory constraintFactory)
}

protected Constraint roomStability(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Attendance.class)
.join(Attendance.class,
equal(Attendance::getPerson),
filtering((leftAttendance,
rightAttendance) -> leftAttendance.getMeeting() != rightAttendance.getMeeting()))
.join(MeetingAssignment.class,
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(),
MeetingAssignment::getMeeting))
return constraintFactory.precompute(MeetingSchedulingConstraintProvider::roomStabilityJoin)
.filter((leftAttendance, rightAttendance, assignment) -> assignment.getStartingTimeGrain() != null
&& assignment.getRoom() != null)
.join(MeetingAssignment.class,
equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(),
MeetingAssignment::getMeeting),
Expand All @@ -227,4 +237,15 @@ protected Constraint roomStability(ConstraintFactory constraintFactory) {
.penalize(HardMediumSoftScore.ONE_SOFT)
.asConstraint("Room stability");
}

private static TriConstraintStream<Attendance, Attendance, MeetingAssignment> roomStabilityJoin(PrecomputeFactory factory) {
return factory.forEachUnfiltered(Attendance.class)
.join(Attendance.class,
equal(Attendance::getPerson),
filtering((leftAttendance,
rightAttendance) -> leftAttendance.getMeeting() != rightAttendance.getMeeting()))
.join(MeetingAssignment.class,
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(), MeetingAssignment::getMeeting));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ Constraint oneShiftPerDay(ConstraintFactory constraintFactory) {
// Soft constraints
// ############################################################################

private static <A, B, C> TriConstraintStream<A, B, C> outerJoin(BiConstraintStream<A, B> source,
Class<C> joinedClass, TriJoiner<A, B, C> joiner) {
return source.join(joinedClass, joiner)
.concat(source.ifNotExists(joinedClass, joiner));
}

Constraint minimumAndMaximumNumberOfAssignments(ConstraintFactory constraintFactory) {
var assignmentLimitedEmployeeStream = constraintFactory
.forEach(MinMaxContractLine.class)
Expand All @@ -114,6 +108,12 @@ Constraint minimumAndMaximumNumberOfAssignments(ConstraintFactory constraintFact
.asConstraint("Minimum and maximum number of assignments");
}

private static <A, B, C> TriConstraintStream<A, B, C> outerJoin(BiConstraintStream<A, B> source, Class<C> joinedClass,
TriJoiner<A, B, C> joiner) {
return source.join(joinedClass, joiner)
.concat(source.ifNotExists(joinedClass, joiner));
}

// Min/Max consecutive working days
Constraint consecutiveWorkingDays(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(MinMaxContractLine.class)
Expand Down
1 change: 0 additions & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

<root level="info">
<appender-ref ref="consoleAppender" />
<!--<appender-ref ref="fileAppender" />-->
</root>

</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected ConferenceSchedulingApp createCommonApp() {
protected Stream<TestData<HardSoftScore>> testData() {
return Stream.of(
TestData.of(UNSOLVED_DATA_FILE,
HardSoftScore.ofSoft(-1025250),
HardSoftScore.ofSoft(-1100400)));
HardSoftScore.ofSoft(-806745),
HardSoftScore.ofSoft(-811140)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected CurriculumCourseApp createCommonApp() {
protected Stream<TestData<HardSoftScore>> testData() {
return Stream.of(
TestData.of(UNSOLVED_DATA_FILE,
HardSoftScore.ofSoft(-55),
HardSoftScore.ofSoft(-64)));
HardSoftScore.ofSoft(-20),
HardSoftScore.ofSoft(-21)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected MeetingSchedulingApp createCommonApp() {
protected Stream<TestData<HardMediumSoftScore>> testData() {
return Stream.of(
TestData.of(UNSOLVED_DATA_FILE,
HardMediumSoftScore.of(-29, -344, -9227),
HardMediumSoftScore.of(-116, -143, -5094)));
HardMediumSoftScore.of(-9, -442, -9375),
HardMediumSoftScore.of(-13, -412, -8573)));
}
}
Loading
Loading