覆盖所有班次的约束

Constraint for covering all shifts

在一个简单的排班问题中,我希望 OptaPlanner return "holes" 在我的日程安排中,以便在没有员工具备所需技能的情况下发现某些班次。

假设 3 个基本 类 并且我只想用 HardMediumSoftScore.ONE_SOFT 来惩罚未覆盖的班次。

如何写这样的约束?

Employee.java

public class Employee {

    private long id = 0L;

    private List<Skill> skills;

Shift.java

@PlanningEntity
public class Shift {

    private RequiredSkills

    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;

    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
    private OffsetDateTime start;

    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
    private OffsetDateTime end;

Schedule.java

@PlanningSolution
public class Schedule {

    @PlanningEntityCollectionProperty
    private List<Shift> shifts;

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employees;

进一步假设一个简单的 ConstraintProvider

public class ScheduleConstraints implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {

        return new Constraint[]{
            requiredSkill(constraintFactory),
        };
    }

    private Constraint requiredSkill(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Shift.class)
                .filter(shift -> {
                    return !shift.getEmployee().hasSkills(shift.getSkills());
                }).penalize("Required skill", HardMediumSoftScore.ONE_HARD);
    }

这是我的尝试。好吃吗?

private Constraint allShiftsMustBeCovered(ConstraintFactory constraintFactory) {
        return constraintFactory.fromUnfiltered(Shift.class)
                .filter(shift-> {
                    return shift.getEmployee().getId() == 0L;
                }).penalize("All shifts must be covered", HardMediumSoftScore.ONE_SOFT);
    }

除非我弄错了,否则您的代码将抛出 NullPointerExceptions。如果不包括班次,则 Shift.employeenull。您的约束假定 Employee 永远不会 null - 您必须解决这个问题,也许是这样的:

// I added a null check as from() only excludes uninitialized entities.
// If employee is nullable, from() will still return Shifts with null employees.
private Constraint requiredSkill(ConstraintFactory constraintFactory) {
    return constraintFactory.from(Shift.class)
            .filter(shift -> shift.getEmployee() != null)
            .filter(shift -> {
                return !shift.getEmployee().hasSkills(shift.getSkills());
            }).penalize("Required skill", HardMediumSoftScore.ONE_HARD);
}

private Constraint allShiftsMustBeCovered(ConstraintFactory constraintFactory) {
    return constraintFactory.fromUnfiltered(Shift.class)
            .filter(shift-> shift.getEmployee() == null || 
                shift.getEmployee().getId() == 0L
            ).penalize("All shifts must be covered", HardMediumSoftScore.ONE_SOFT);
}

由于 null 是此处的有效值,请确保 Shift.employee variable is nullable。这种方法在文档中称为 过度约束规划

我为此使用 "fake employee"。允许假员工在任何班次工作,但对这些班次进行惩罚,以便始终在假员工之前选择真正的员工。