Search code examples
optaplanner

Optaplanner: Define constraints dynamically based on PlanningSolution property


Assume I have this implementation of a ConstraintProvider.

package com.example.solver;

import com.example.domain.Lesson;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

public class TimeTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                roomConflict(constraintFactory),
                teacherConflict(constraintFactory),
        };
    }

    private Constraint roomConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getRoom),
                        Joiners.lessThan(Lesson::getId))
                .penalize("Room conflict", HardSoftScore.ONE_HARD);
    }

    private Constraint teacherConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Lesson.class)
                .join(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getTeacher),
                        Joiners.lessThan(Lesson::getId))
                .penalize("Teacher conflict", HardSoftScore.ONE_HARD);
    }

Assume that I only want to constrain with roomConflict in case my PlanningSolution has boolean property considerRoomConflict. Likewise, I only want to use roomConflict in case my PlanningSolution has boolean property considerTeacherConflict.

Assume that I would have 5 such conditionals. What would be a good implementation hereof?


Solution

  • Use @ConstraintConfiguration with @ConstraintWeight. Some of the optaplanner-examples use it.

    @ConstraintWeight("Teacher conflict")
    // To disable, change to HardSoftScore.ZERO
    private HardSoftScore teacherConflict = HardSoftScore.ONE_HARD;
    

    Then use penalizeConfigurable() instead of penalize():

    constraintFactory.from(Lesson.class)
    .join(...)
    .penalizeConfigurable("Teacher conflict")