javaspring-bootconstraintsoptaplannertimefold

How to force grouping the lessons for the same series of students in the same Timeslot and the same Room Timefold/Optaplanner?


I want to enforce the student groups from the same series of students to be in the same Timeslot and in the same Room for a Course Type.

This is my attempt:

Constraint seminarsGroupedInTheSameTimeslot(ConstraintFactory constraintFactory){
        return constraintFactory
                //select each 2 pair of different lessons
                .forEachUniquePair(Lesson.class,
                        //with the same group
                        Joiners.equal((lesson) -> lesson.getStudentGroup().getGroup()),
                        //with the same series
                        Joiners.equal((lesson) -> lesson.getStudentGroup().getName()),
                        //with the same subject
                        Joiners.equal(Lesson::getSubject),
                        //with the same Seminar type
                        Joiners.equal((lesson) -> lesson.getType().equals(LessonType.SEMINAR))
                )
                //check if the lessons have different timeslots and rooms
                .filter(((lesson, lesson2) -> {
                    return !(lesson.getTimeslot().equals(lesson2.getTimeslot()) &&
                            lesson.getRoom().equals(lesson2.getRoom()));
                }))
                .penalize(HardSoftScore.ONE_HARD)                
                .asConstraint("Seminars should be grouped in the same timeslot and room");
    }

I know this is not the best solution because is not scalable and it gives penalty to every pair of lessons that breaks the constraint (i.e. in case of 10 lessons that are analyzed and only one instance of the lessons is breaking the constraint, the score will be -9 instead of -1).

I tried to add a ifNotExists() method to take into account only consecutive lessons id, but I received the following warning: Unchecked generics array creation for varargs parameter.

How can I write this kind of constraint better?


Solution

  • The simplest I could think of would be something like this:

    forEach(Lesson.class)
    .filter(lesson -> lesson.getType() == SEMINAR)
    .groupBy(
        Lesson::getStudentGroup, 
        Lesson::getSubject, 
        countDistinct(lesson -> Pair.of(lesson.getTimeslot(), lesson.getRoom())))
    .filter((group, subject, timeslotAndRoomCount) -> timeslotAndRoomCount > 1)
    .penalize(ONE_HARD, 
         (group, subject, timeslotAndRoomCount) -> timeslotAndRoomCount - 1)
    ...
    

    The key part to understand is groupBy - your studentGroup and subject together form a group key. All lessons with the same key fall into the same group. Within that group, the countDistinct collector will compute how many different instances there are of pairs of timeslot and room. (Note: You have to implement Pair yourself. It could be as simple as one-line Java record.)

    You may be tempted to replace countDistinct by toList, giving you access to the actual pairs. I wouldn't. Collections are not incremental, and incrementality is key to solver performance.