This is a follow up / updated question of May planning entites reference other planning entities?
I'm working on a school schedule solver.
In order to support "groups of lectures" with a common start time (important for "break out subjects" such as language classes, where spanish, german, french must be taught at the same time for the schedule to make sense), I have now created the following structure:
@PlaningEntity
class LectureGroup {
@PlanningVariable
Timeslot timeslot;
List<Lecture> lectures;
// ...some other properties...
}
@PlanningEntity
class Lecture {
@PlanningVariable
Room room;
int lectureLength;
LectureGroup myLectureGroup; // LectureGroup in which this lecture is part of
// ...some other properties...
Timeslot getStartTime() {
return myLectureGroup.timeslot;
}
Timeslot getEndTime() {
return myLectureGroup.timeslot.plus(lectureLength);
}
}
@PlanningSolution
class Schedule {
@PlanningEntityCollectionProperty
List<LectureGroups> lectureGroups;
@PlanningEntityCollectionProperty
List<Lecture> lectures; // always equal to flattened list of lectures from the lectureGroups
// ...
}
To keep things simple, all Lecture
s are wrapped in a LectureGroup
, even if the LectureGroup
only contains one lecture.
My Question: (which is basically very similar to my previous one)
What's the recommended way to express, in this case, a room conflict constraint? A naive solution could look like:
UniConstraintStream<Lecture> lecturesWithRooms = cf.forEach(Lecture.class)
.filter(Lecture::hasRoom);
return lecturesWithRooms.join(lecturesWithRooms,
Joiners.equals(Lecture::getRoom),
Joiners.overlapping(Lecture::getStartTime, Lecture::getEndTime)
Joiners.lessThan(Lecture::getLectureId))
.penalize(...)
.justifyWith(...)
.asConstraint(...);
As I understand, updates to the timeslot
of a LectureGroup
will not trigger proper reevaluation.
A refined solution could be to start with...
UniConstraintStream<Lecture> lecturesWithRooms = cf.forEach(LectureGroup.class)
.filter(LectureGroup::hasTimeslot)
.flattenLast(LectureGroup::getLectures)
.filter(Lecture::hasRoom);
...but then I suspect direct updates to Lecture.room
will not trigger reevaluation.
Reading the answer to my previous question, maybe I have to use both variants of lecturesWithRooms
above, and join them with Function.identity
in order to retrigger evaluation regardless if the time (in LectureGroup
) or the room (in Lecture
) is updated?
Any thoughts welcome here.
Update: Here's my current attempt:
UniConstraintStream<Lecture> lecturesFromLectures =
cf.forEachIncludingNullVars(Lecture.class);
UniConstraintStream<Lecture> lecturesFromGroups =
cf.forEachIncludingNullVars(LectureGroup.class).flattenLast(LectureGroup::getLectures);
UniConstraintStream<Lecture> lectures = lecturesFromLectures
.join(lecturesFromGroups, Joiners.equal(Function.identity()))
.groupBy((l1, l2) -> l1);
Performance considerations aside, this should work:
BiConstraintStream<LectureGroup, Lecture> lectures =
cf.forEachIncludingNullVars(LectureGroup.class)
.join(Lecture.class, Joiners.filtering((group, lecture) -> group.contains(lecture)));