Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@

package ai.timefold.solver.benchmarks.examples.conferencescheduling.app;

import java.io.File;

import ai.timefold.solver.benchmarks.examples.common.app.CommonApp;
import ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceSolution;
import ai.timefold.solver.benchmarks.examples.conferencescheduling.persistence.ConferenceSchedulingSolutionFileIO;
Expand All @@ -19,7 +17,6 @@ public class ConferenceSchedulingApp
public static void main(String[] args) {
var app = new ConferenceSchedulingApp();
var solution = app.solve("216talks-18timeslots-20rooms.json");
app.createSolutionFileIO().write(solution, new File("conferencescheduling-216-18-20.json"));
System.out.println("Done: " + solution);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
@JsonIdentityInfo(generator = JacksonUniqueIdGenerator.class)
public class ConferenceConstraintProperties extends AbstractPersistable {

public static final String ROOM_UNAVAILABLE_TIMESLOT = "Room unavailable timeslot";
public static final String ROOM_CONFLICT = "Room conflict";
public static final String SPEAKER_UNAVAILABLE_TIMESLOT = "Speaker unavailable timeslot";
public static final String SPEAKER_CONFLICT = "Speaker conflict";
Expand All @@ -18,31 +17,26 @@ public class ConferenceConstraintProperties extends AbstractPersistable {
public static final String CONSECUTIVE_TALKS_PAUSE = "Consecutive talks pause";
public static final String CROWD_CONTROL = "Crowd control";

public static final String SPEAKER_REQUIRED_TIMESLOT_TAGS = "Speaker required timeslot tags";
public static final String SPEAKER_PROHIBITED_TIMESLOT_TAGS = "Speaker prohibited timeslot tags";
public static final String TALK_REQUIRED_TIMESLOT_TAGS = "Talk required timeslot tags";
public static final String TALK_PROHIBITED_TIMESLOT_TAGS = "Talk prohibited timeslot tags";
public static final String SPEAKER_REQUIRED_ROOM_TAGS = "Speaker required room tags";
public static final String SPEAKER_PROHIBITED_ROOM_TAGS = "Speaker prohibited room tags";
public static final String TALK_REQUIRED_ROOM_TAGS = "Talk required room tags";
public static final String TALK_PROHIBITED_ROOM_TAGS = "Talk prohibited room tags";

public static final String THEME_TRACK_CONFLICT = "Theme track conflict";
public static final String THEME_TRACK_ROOM_STABILITY = "Theme track room stability";
public static final String THEME_TRACK_CONFLICT_SAME_DAY_TALKS = "Same day talks 'Theme track'";
public static final String SECTOR_CONFLICT = "Sector conflict";
public static final String AUDIENCE_TYPE_DIVERSITY = "Audience type diversity";
public static final String AUDIENCE_TYPE_THEME_TRACK_CONFLICT = "Audience type theme track conflict";
public static final String AUDIENCE_LEVEL_DIVERSITY = "Audience level diversity";
public static final String CONTENT_AUDIENCE_LEVEL_FLOW_VIOLATION = "Content audience level flow violation";
public static final String CONTENT_CONFLICT = "Content conflict";
public static final String CONTENT_CONFLICT_SAME_DAY_TALKS = "Same day talks 'Content'";
public static final String LANGUAGE_DIVERSITY = "Language diversity";
public static final String SAME_DAY_TALKS = "Same day talks";
public static final String POPULAR_TALKS = "Popular talks";

public static final String SPEAKER_PREFERRED_TIMESLOT_TAGS = "Speaker preferred timeslot tags";
public static final String SPEAKER_UNDESIRED_TIMESLOT_TAGS = "Speaker undesired timeslot tags";
public static final String TALK_PREFERRED_TIMESLOT_TAGS = "Talk preferred timeslot tags";
public static final String TALK_UNDESIRED_TIMESLOT_TAGS = "Talk undesired timeslot tags";
public static final String SPEAKER_PREFERRED_ROOM_TAGS = "Speaker preferred room tags";
public static final String SPEAKER_UNDESIRED_ROOM_TAGS = "Speaker undesired room tags";
public static final String TALK_PREFERRED_ROOM_TAGS = "Talk preferred room tags";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,6 @@ public Integer getDurationInMinutes() {
return timeslot == null ? null : timeslot.getDurationInMinutes();
}

public boolean overlapsTime(Talk other) {
return timeslot != null && other.getTimeslot() != null && timeslot.overlapsTime(other.getTimeslot());
}

public int overlappingDurationInMinutes(Talk other) {
if (timeslot == null) {
return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ai.timefold.solver.benchmarks.examples.conferencescheduling.score;

import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.*;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.AUDIENCE_LEVEL_DIVERSITY;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.AUDIENCE_TYPE_DIVERSITY;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.AUDIENCE_TYPE_THEME_TRACK_CONFLICT;
Expand All @@ -10,7 +11,6 @@
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.LANGUAGE_DIVERSITY;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.POPULAR_TALKS;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.ROOM_CONFLICT;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.SAME_DAY_TALKS;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.SECTOR_CONFLICT;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.SPEAKER_CONFLICT;
import static ai.timefold.solver.benchmarks.examples.conferencescheduling.domain.ConferenceConstraintProperties.SPEAKER_MAKESPAN;
Expand All @@ -33,6 +33,9 @@
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.countBi;
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.max;
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.min;
import static ai.timefold.solver.core.api.score.stream.Joiners.containedIn;
import static ai.timefold.solver.core.api.score.stream.Joiners.containing;
import static ai.timefold.solver.core.api.score.stream.Joiners.containingAnyOf;
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;
import static ai.timefold.solver.core.api.score.stream.Joiners.greaterThan;
Expand Down Expand Up @@ -89,7 +92,8 @@ public Constraint[] defineConstraints(ConstraintFactory factory) {
contentAudienceLevelFlowViolation(factory),
contentConflict(factory),
languageDiversity(factory),
sameDayTalks(factory),
contentConflictSameDay(factory),
themeTrackConflictSameDay(factory),
popularTalks(factory),
speakerPreferredTimeslotTags(factory),
speakerUndesiredTimeslotTags(factory),
Expand Down Expand Up @@ -117,8 +121,8 @@ Constraint speakerUnavailableTimeslot(ConstraintFactory factory) {
return factory.forEachIncludingUnassigned(Talk.class)
.filter(talk -> talk.getTimeslot() != null)
.join(Speaker.class,
filtering((talk, speaker) -> talk.hasSpeaker(speaker)
&& speaker.getUnavailableTimeslotSet().contains(talk.getTimeslot())))
containing(Talk::getSpeakerList, speaker -> speaker),
containedIn(Talk::getTimeslot, Speaker::getUnavailableTimeslotSet))
.penalize(HardSoftScore.ofHard(100), (talk, speaker) -> talk.getDurationInMinutes())
.asConstraint(SPEAKER_UNAVAILABLE_TIMESLOT);
}
Expand All @@ -127,7 +131,8 @@ Constraint speakerConflict(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
.join(Speaker.class,
filtering((talk1, talk2, speaker) -> talk1.hasSpeaker(speaker) && talk2.hasSpeaker(speaker)))
containing((talk1, talk2) -> talk1.getSpeakerList(), speaker -> speaker),
containing((talk1, talk2) -> talk2.getSpeakerList(), speaker -> speaker))
.penalize(HardSoftScore.ofHard(10), (talk1, talk2, speaker) -> talk2.overlappingDurationInMinutes(talk1))
.asConstraint(SPEAKER_CONFLICT);
}
Expand All @@ -136,18 +141,18 @@ Constraint talkPrerequisiteTalks(ConstraintFactory factory) {
return factory.forEach(Talk.class)
.join(Talk.class,
greaterThan(t -> t.getTimeslot().getEndDateTime(), t -> t.getTimeslot().getStartDateTime()),
filtering((talk1, talk2) -> talk2.getPrerequisiteTalkSet().contains(talk1)))
containedIn(talk -> talk, Talk::getPrerequisiteTalkSet))
.penalize(HardSoftScore.ofHard(10), Talk::combinedDurationInMinutes)
.asConstraint(TALK_PREREQUISITE_TALKS);
}

Constraint talkMutuallyExclusiveTalksTags(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
.expand((talk1, talk2) -> talk2.overlappingMutuallyExclusiveTalksTagCount(talk1))
.filter((talk1, talk2, overlappingTagCount) -> overlappingTagCount > 0)
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()),
containingAnyOf(Talk::getMutuallyExclusiveTalksTagSet))
.penalize(HardSoftScore.ofHard(1),
(talk1, talk2, overlappingTagCount) -> overlappingTagCount * talk1.overlappingDurationInMinutes(talk2))
(talk1, talk2) -> talk2.overlappingMutuallyExclusiveTalksTagCount(talk1)
* talk1.overlappingDurationInMinutes(talk2))
.asConstraint(TALK_MUTUALLY_EXCLUSIVE_TALKS_TAGS);
}

Expand Down Expand Up @@ -231,21 +236,19 @@ Constraint themeTrackConflict(ConstraintFactory factory) {
Constraint themeTrackRoomStability(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
equal(talk -> talk.getTimeslot().getStartDateTime().toLocalDate()),
containingAnyOf(Talk::getThemeTrackTagSet),
filtering((talk1, talk2) -> !talk1.getRoom().equals(talk2.getRoom())))
.expand((talk1, talk2) -> talk2.overlappingThemeTrackCount(talk1))
.penalize(HardSoftScore.ofSoft(10),
(talk1, talk2, overlappingTrackCount) -> overlappingTrackCount * talk1.combinedDurationInMinutes(talk2))
(talk1, talk2) -> talk2.overlappingThemeTrackCount(talk1) * talk1.combinedDurationInMinutes(talk2))
.asConstraint(THEME_TRACK_ROOM_STABILITY);
}

Constraint sectorConflict(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
.expand((talk1, talk2) -> talk2.overlappingSectorCount(talk1))
.filter((talk1, talk2, overlappingSectorCount) -> overlappingSectorCount > 0)
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()),
containingAnyOf(Talk::getSectorTagSet))
.penalize(HardSoftScore.ofSoft(10),
(talk1, talk2, overlappingSectorCount) -> overlappingSectorCount
* talk1.overlappingDurationInMinutes(talk2))
(talk1, talk2) -> talk2.overlappingSectorCount(talk1) * talk1.overlappingDurationInMinutes(talk2))
.asConstraint(SECTOR_CONFLICT);
}

Expand Down Expand Up @@ -301,11 +304,10 @@ Constraint contentAudienceLevelFlowViolation(ConstraintFactory factory) {

Constraint contentConflict(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class,
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
.expand((talk1, talk2) -> talk2.overlappingContentCount(talk1))
.filter((talk1, talk2, overlappingContentCount) -> overlappingContentCount > 0)
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()),
containingAnyOf(Talk::getContentTagSet))
.penalize(HardSoftScore.ofSoft(100),
(talk1, talk2, overlappingContentCount) -> overlappingContentCount
(talk1, talk2) -> talk2.overlappingContentCount(talk1)
* talk1.overlappingDurationInMinutes(talk2))
.asConstraint(CONTENT_CONFLICT);
}
Expand All @@ -318,18 +320,20 @@ Constraint languageDiversity(ConstraintFactory factory) {
.asConstraint(LANGUAGE_DIVERSITY);
}

Constraint sameDayTalks(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class)
Constraint contentConflictSameDay(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class, containingAnyOf(Talk::getContentTagSet))
.filter((talk1, talk2) -> !talk1.getTimeslot().isOnSameDayAs(talk2.getTimeslot()))
.penalize(HardSoftScore.ofSoft(10),
(talk1, talk2) -> talk2.overlappingContentCount(talk1) * talk1.combinedDurationInMinutes(talk2))
.asConstraint(CONTENT_CONFLICT_SAME_DAY_TALKS);
}

Constraint themeTrackConflictSameDay(ConstraintFactory factory) {
return factory.forEachUniquePair(Talk.class, containingAnyOf(Talk::getThemeTrackTagSet))
.filter((talk1, talk2) -> !talk1.getTimeslot().isOnSameDayAs(talk2.getTimeslot()))
.expand((talk1, talk2) -> {
var overlappingContentCount = talk2.overlappingContentCount(talk1);
var overlappingThemeTrackCount = talk2.overlappingThemeTrackCount(talk1);
return overlappingContentCount + overlappingThemeTrackCount;
})
.filter((talk1, talk2, overlap) -> overlap > 0)
.penalize(HardSoftScore.ofSoft(10),
(talk1, talk2, overlap) -> overlap * talk1.combinedDurationInMinutes(talk2))
.asConstraint(SAME_DAY_TALKS);
(talk1, talk2) -> talk2.overlappingThemeTrackCount(talk1) * talk1.combinedDurationInMinutes(talk2))
.asConstraint(THEME_TRACK_CONFLICT_SAME_DAY_TALKS);
}

Constraint popularTalks(ConstraintFactory factory) {
Expand Down Expand Up @@ -395,7 +399,7 @@ Constraint talkUndesiredRoomTags(ConstraintFactory factory) {
Constraint speakerMakespan(ConstraintFactory factory) {
return factory.forEach(Speaker.class)
.join(Talk.class,
filtering((speaker, talk) -> talk.hasSpeaker(speaker)))
containedIn(speaker -> speaker, Talk::getSpeakerList))
.groupBy((speaker, talk) -> speaker,
compose(
min((Speaker speaker, Talk talk) -> talk, talk -> talk.getTimeslot().getStartDateTime()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static ai.timefold.solver.core.api.score.HardSoftScore.ofSoft;
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.count;
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.countDistinct;
import static ai.timefold.solver.core.api.score.stream.Joiners.containedIn;
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;

Expand Down Expand Up @@ -154,7 +155,7 @@ Constraint curriculumCompactness(ConstraintFactory factory) {
private static BiConstraintStream<Curriculum, Lecture> curriculumLectureLeft(PrecomputeFactory factory) {
return factory.forEachUnfiltered(Curriculum.class)
.join(Lecture.class,
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)));
containedIn(curriculum -> curriculum, Lecture::getCurriculumSet));
}

Constraint roomStability(ConstraintFactory factory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.lang.annotation.RetentionPolicy;

import ai.timefold.solver.core.api.score.stream.test.ConstraintVerifier;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ void languageDiversity(
}

@ConstraintProviderTest
void sameDayTalks(
void contentConflictSameDayTalks(
ConstraintVerifier<ConferenceSchedulingConstraintProvider, ConferenceSolution> constraintVerifier) {
Room room = new Room(0);
Talk talk1 = new Talk(1)
Expand Down Expand Up @@ -598,9 +598,50 @@ void sameDayTalks(
.withTimeslot(TUESDAY_9_TO_10);

constraintVerifier.verifyThat(
ConferenceSchedulingConstraintProvider::sameDayTalks)
ConferenceSchedulingConstraintProvider::contentConflictSameDay)
.given(talk1, talk2, talk3, talk4, talk5, talk6)
.penalizesBy(960);
.penalizesBy(480);
}

@ConstraintProviderTest
void themeTrackConflictSameDayTalks(
ConstraintVerifier<ConferenceSchedulingConstraintProvider, ConferenceSolution> constraintVerifier) {
Room room = new Room(0);
Talk talk1 = new Talk(1)
.withRoom(room)
.withContentTagSet(singleton("a"))
.withThemeTrackTagSet(singleton("a"))
.withTimeslot(MONDAY_9_TO_10);
Talk talk2 = new Talk(3)
.withRoom(room)
.withContentTagSet(singleton("b"))
.withThemeTrackTagSet(singleton("a"))
.withTimeslot(TUESDAY_9_TO_10);
Talk talk3 = new Talk(4)
.withRoom(room)
.withContentTagSet(singleton("a"))
.withThemeTrackTagSet(singleton("a"))
.withTimeslot(TUESDAY_9_TO_10);
Talk talk4 = new Talk(5)
.withRoom(room)
.withContentTagSet(singleton("a"))
.withThemeTrackTagSet(singleton("b"))
.withTimeslot(MONDAY_9_TO_10);
Talk talk5 = new Talk(7)
.withRoom(room)
.withContentTagSet(singleton("b"))
.withThemeTrackTagSet(singleton("b"))
.withTimeslot(TUESDAY_9_TO_10);
Talk talk6 = new Talk(8)
.withRoom(room)
.withContentTagSet(singleton("a"))
.withThemeTrackTagSet(singleton("b"))
.withTimeslot(TUESDAY_9_TO_10);

constraintVerifier.verifyThat(
ConferenceSchedulingConstraintProvider::themeTrackConflictSameDay)
.given(talk1, talk2, talk3, talk4, talk5, talk6)
.penalizesBy(480);
}

@ConstraintProviderTest
Expand Down
Loading