OptaPlanner约束流如何显式声明依赖的规划实体以避免高成本连接
问题原因
你遇到的分数损坏报错,核心是OptaPlanner约束流的增量计算机制要求:所有参与分数计算的规划实体,必须被显式加入约束流的匹配元组。你通过timeSlot.getOverlappingTimeSlots()取到的重叠时间段实体,是通过已选实体的引用间接获取的,没有被显式选择,OptaPlanner不会监听这些实体的规划变量变更,因此会触发增量计算的一致性校验报错。
可能是分数约束未选择其依赖的所有实体,仅通过已选实体的引用获取部分实体,这会破坏增量分数计算,因为未被选择的实体发生变更时约束不会被重新评估。
你用到的flattenLast操作只会展开集合内容,不会自动将展开的实体识别为需要监听的规划实体,因此你的写法无法满足增量计算的要求。
解决方案
因为你的时间段时间属性固定,重叠时段对在优化过程中完全不变,可以通过预生成重叠对作为*问题事实(ProblemFact)*的方式实现,完全避免动态连接的性能损耗,同时符合约束流的监听要求:
步骤1:定义重叠对问题事实类
public class OverlappingTimeSlotPair { private final TimeSlot slotA; private final TimeSlot slotB; // 构造时通过id排序避免生成重复对(比如slotA和slotB反过来的重复情况) public OverlappingTimeSlotPair(TimeSlot slotA, TimeSlot slotB) { if (slotA.getId().compareTo(slotB.getId()) < 0) { this.slotA = slotA; this.slotB = slotB; } else { this.slotA = slotB; this.slotB = slotA; } } // 仅需要getter方法 public TimeSlot getSlotA() { return slotA; } public TimeSlot getSlotB() { return slotB; } // 重写equals和hashCode方法,保证问题事实的唯一性 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OverlappingTimeSlotPair that = (OverlappingTimeSlotPair) o; return slotA.equals(that.slotA) && slotB.equals(that.slotB); } @Override public int hashCode() { return java.util.Objects.hash(slotA, slotB); } }
步骤2:预生成重叠对集合并加入问题类
在你的规划问题类中,添加OverlappingTimeSlotPair集合的属性,标注@ProblemFactCollectionProperty,在问题初始化阶段就生成所有固定的重叠对:
@PlanningSolution public class YourPlanningSolution { // 其他属性(时间段列表、规划实体列表等)省略 @ProblemFactCollectionProperty private List<OverlappingTimeSlotPair> overlappingPairs; // 初始化时生成所有重叠对 public void initOverlappingPairs() { List<TimeSlot> timeSlots = getTimeSlotList(); this.overlappingPairs = new ArrayList<>(); for (int i = 0; i < timeSlots.size(); i++) { TimeSlot slot1 = timeSlots.get(i); for (int j = i + 1; j < timeSlots.size(); j++) { TimeSlot slot2 = timeSlots.get(j); if (slot1.isOverlapping(slot2)) { // 你的重叠判断逻辑 overlappingPairs.add(new OverlappingTimeSlotPair(slot1, slot2)); } } } } }
步骤3:编写约束流
直接从重叠对事实类构建约束流,通过join将两个时间段显式加入匹配元组,OptaPlanner会自动监听它们的规划变量变更,不会触发分数损坏:
return constraintFactory.from(OverlappingTimeSlotPair.class) // 显式join两个时间段,让OptaPlanner监听它们的变量变更 .join(TimeSlot.class, equal(OverlappingTimeSlotPair::getSlotA, Function.identity())) .join(TimeSlot.class, equal((pair, slotA) -> pair.getSlotB(), Function.identity())) // 此处添加你的规划变量冲突判断逻辑 .filter((pair, slotA, slotB) -> /* 你的冲突判断逻辑 */) .impact(/* 你的分数配置 */);
性能优势
这个方案的重叠对计算仅在问题初始化阶段执行一次,优化过程中不需要再做任何重叠判断或高成本的过滤连接,性能远高于动态计算重叠对的实现方式,同时完全符合OptaPlanner的增量计算要求。
内容的提问来源于stack exchange,提问作者FrederikVds

