Daniel
Daniel

Reputation: 311

How to combine planning entitiy's ConstraintStreams with related problem fact properties?

I started to implemented a school timetable scheduling application using OptaPlanner.

Similar to the lesson scheduling example in the documentation, I'd like to optimize a school schedule including constraints such as "A teacher, who works/has less than N hours (per week) should ideally have one (or two, depending on N) days off".

To phrase it differently: Lessons of teachers should not be distributed across the entire week, but their quantity should rather be maximized per day.

Lesson is my only planning entity, the teacher is a problem fact (I guess). Maybe I need to model it in a different way to access the overall distribution of a teacher's timeslots (more specifically: of all the lesson's timeslots belonging to a teacher).

How can I combine the usual constraints, i.e., a teacher can't do two different lessons at the same time etc., defined using the ConstraintFactory creating ConstraintStreams (for Lessons) with a more global property such as the "lesson compactness" for teachers (full days better than distributed over the entire week) who work part time? I can't imagine how to calculate that score by just having access to a constraint stream of lessons.

Upvotes: 0

Views: 193

Answers (1)

Christopher Chianelli
Christopher Chianelli

Reputation: 2013

The normal way to use global information in a constraint is to create a singleton problem fact that stores global information, and use that in your constraint. For your example, create a ScheduleGlobalProperties with a partTimeTeacherSet field:

public class ScheduleGlobalProperties {
    Set<String> partTimeTeacherSet;
    // other global properties, constructors, getters...
}

Add it as a @ProblemFactProperty to your @PlanningSolution class:

@PlanningSolution
public class Schedule {
    @ProblemFactProperty
    ScheduleGlobalProperties scheduleGlobalProperties;
    // Other fields, constructors, methods...
}

Then in your constraint, you join on the global properties problem fact to access the global properties:

Constraint lessonCompactness(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Lesson.class)
        .groupBy(Lesson::getTeacher,
                 ConstraintCollectors.countDistinct(
                     lesson -> lesson.getTimeslot().getDay()
                 ))
        .join(ScheduleGlobalProperties.class)
        .filter((teacher, dayCount, globalProperties) -> globalProperties.getPartTimeTeacherSet().contains(teacher))
        .penalize(HardSoftScore.ONE_SOFT, (teacher, dayCount, globalProperties) -> dayCount)
        .asConstraint("Lesson Compactness");
}

I don't see how the constraint you specified needed global information though. An example that uses global information is nurse rostering; NurseRosterParametrization is the singleton global properties class (with first and last shift date fields). In the nurse rostering example, the global properties are used in the consecutiveFreeDays constraint to determine how many free days an employee have before their first shift and after their last shift. See the nurse rostering source code for the full example.

Upvotes: 2

Related Questions