Optaplanner 惩罚不使用可空变量

Optaplanner Penalty Not Working With a Nullable Variable

我将 OptaPlanner 与两个规划变量一起使用,其中一个是用 nullable=true 定义的。 按照会议示例(为简单起见),假设房间可以为空,但时间不能为空。

我在 non-null、time 变量上定义了一个约束,但似乎惩罚只在可空 room 变量不为空时有效,否则失败。

下面是我的代码片段:

@PlanningEntity
public class Meeting {

   @PlanningVariable(valueRangeProviderRefs = "time")
   private LocalDateTime time;

   @PlanningVariable(valueRangeProviderRefs = "availableRooms", nullable = true)
   private Room room;

   private long personId;
   ...
}

在我的约束提供者 class 中,我定义了以下约束以确保一个人不能参加两个单独的会议:[​​=20=]

protected Constraint samePersonAndTimeConflict(ConstraintFactory constraintFactory) {
        return constraintFactory
                // Select each pair of 2 different meetings ...
                .forEachUniquePair(Meeting.class,
                        // ... for the same person ...
                        equal(Meeting::getPersonId),
                        // ... in the same time ...
                        equal(Meeting::getDateTime))
                // ... and penalize each pair with a hard weight.
                .penalize(SAME_PERSON_AND_TIME_CONFLICT, HardSoftScore.ONE_HARD);
    } 

当创建两个具有 non-null 值的 object 时,如果同一个人应该同时参加两个单独的会议,则此约束将正常工作并以一个硬分作为惩罚。但是,当使用可空房间变量确实为空的 object 时,没有惩罚。结果是我得到了一个解决方案,其中许多实例对同一个人具有相同的时间分配。

我也尝试以其他方式操纵约束,例如使用“forEachIncludingNullVars”和“forEach”,但我看到了相同的结果:

protected Constraint samePersonAndTimeConflict(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEachIncludingNullVars(Meeting.class)
                //.filter(meeting -> meeting.getDateTime() != null)
                .join(Meeting.class,
                        lessThan(Meeting::getId),
                        equal(Meeting::getTime),
                        equal(Meeting::getPersonId)) //,
                .penalize(SAME_PERSON_AND_TIME_CONFLICT, HardSoftScore.ONE_HARD);

我也尝试将分数 class 更改为 HardMediumSoftScore 并受到 ONE_MEDIUM 的惩罚。对于空房间的会议仍然没有惩罚。

OptaPlanner 似乎根本无法正常工作。 在这一点上,我不知道我还能尝试什么。请指教

*** 编辑中 ***

按照下面关于使用带有附加 forEachIncludingNullVars 子句的嵌套 constraintFactory 的建议,我最终按如下方式实现了我的约束 - 现在它起作用了:

protected Constraint samePersonAndTimeConflict(ConstraintFactory constraintFactory) {
        final Constraint constraint = constraintFactory.forEachIncludingNullVars(Meeting.class)
                .join(
                        constraintFactory.forEachIncludingNullVars(Meeting.class)
                                .filter(meeting -> meeting.getTime() != null),
                        lessThan(WorkDay::getId),
                        equal(WorkDay::getEmployeeId),
                        equal(WorkDay::getDate))
                .penalize("Same person and time conflict", HardMediumSoftScore.ONE_HARD);
        return constraint;
    }

forEach() 在这里不起作用,因为它只会为您提供 none 变量为空的实体。 forEachIncludingNullVars() 是去这里的路。但您还需要了解 joinforEach 具有相同的行为 - 它不包括具有空变量的实体。

考虑到这一点,您需要加入嵌套流,如下所示:

protected Constraint samePersonAndTimeConflict(ConstraintFactory constraintFactory) {
    return constraintFactory
            .forEachIncludingNullVars(Meeting.class)
            .join(
                constraintFactory.forEachIncludingNullVars(Meeting.class),
                    // add your joiners here
            ).penalize(SAME_PERSON_AND_TIME_CONFLICT, HardSoftScore.ONE_HARD);
}

这样,联接将创建所有实体的 cross-product,无论它们的任何变量是否为空。然后就变成了 filter 剔除您不希望看到空值的部分的问题。