
本文介绍如何在 optaplanner 约束流(constraintstream)中对多个规划实体的时间序列数组进行逐元素累加,并基于累加结果实施资源超限惩罚,适用于排程、资源分配等场景。
本文介绍如何在 optaplanner 约束流(constraintstream)中对多个规划实体的时间序列数组进行逐元素累加,并基于累加结果实施资源超限惩罚,适用于排程、资源分配等场景。
在 OptaPlanner 中,当每个 Operation 实体携带一个固定长度的时间序列(如 int[] resourceUsage 或 List
核心思路是:不预先生成完整累加数组,而是对每个时间索引 t 单独建模约束,即“对每个合法时间点 t,统计所有 Operation 在该时刻的资源用量之和,若超过阈值 X,则施加一次中等惩罚”。
假设时间序列长度为 TIME_SLOT_COUNT = 24(如小时级),且 resourceUsage 是 int[] 类型,推荐实现如下:
public Constraint ResourceOveroccupation(ConstraintFactory constraintFactory) {
int timeSlotCount = 24; // 需与 PlanningEntity 中数组长度一致
return constraintFactory
.forEach(Operation.class)
.expand(operation -> IntStream.range(0, timeSlotCount)
.mapToObj(t -> new TimeSlotUsage(t, operation.resourceUsage[t]))
.toList())
.groupBy((operation, timeSlotUsage) -> timeSlotUsage.timeIndex,
(operation, timeSlotUsage) -> timeSlotUsage.usage)
.filter((timeIndex, totalUsage) -> totalUsage > MAX_RESOURCE_CAPACITY)
.penalize("Resource Overoccupation", HardMediumSoftScore.ONE_MEDIUM);
}
其中辅助类定义为:
record TimeSlotUsage(int timeIndex, int usage) {}
✅ 关键说明:
- expand(...) 将每个 Operation 展开为 timeSlotCount 个 (timeIndex, usage) 元组,使每个时间点独立参与后续分组;
- groupBy(...) 按 timeIndex 聚合所有操作在该时刻的 usage 值并自动求和;
- filter(...) 判断每时刻总用量是否超限,满足即触发一次惩罚(而非对整个数组统一判罚);
- 此方式完全兼容 ConstraintStream 的增量计算特性,性能远优于尝试在 Java 层手动构建累加数组。
⚠️ 注意事项:
- 若 resourceUsage 是 List
,请确保其大小恒定且非 null,否则需在 expand 前加空值/越界防护; - MAX_RESOURCE_CAPACITY 应定义为常量或从 @PlanningSolution 中获取(如通过 ConstraintCollectors.toMapped() 关联全局配置);
- 时间槽数量不宜过大(如 >1000),否则 expand 会产生过多元组,影响性能——此时建议改用 Joiners 多表连接或自定义 ConstraintProvider 优化。
该方案本质是将“向量累加 + 逐元素比较”的数学问题,映射为 OptaPlanner 原生支持的“按维度分组聚合 + 条件过滤”约束建模范式,兼顾表达力、可读性与运行效率。










