
在OptaPlanner中,通过PlanningListVariable建模作业序列,可自然获取前后作业关系,从而高效实现“相邻作业宽度不同时施加软分惩罚”的业务规则。
在optaplanner中,通过planninglistvariable建模作业序列,可自然获取前后作业关系,从而高效实现“相邻作业宽度不同时施加软分惩罚”的业务规则。
在作业调度场景中,减少设备重配置(如更换模具、调整辊距)是关键优化目标。当相邻两个作业的产品宽度不一致时,需触发一次重配置,产生额外时间或成本。因此,我们希望在软分数中对此类“宽度切换”行为施加可量化的惩罚。
直接对单个Job使用forEach(Job.class)无法访问其“下一个作业”,因为标准实体间无顺序上下文。正确解法是采用列表变量(PlanningListVariable)建模:将作业序列作为机器(Machine)的规划属性,由OptaPlanner自动维护元素顺序及前后关系。
✅ 正确建模示例(领域模型片段):
public class Machine {
@PlanningId
private Long id;
@PlanningListVariable
private List<job> jobList; // OptaPlanner自动管理顺序与邻接关系
// getter/setter...
}
public class Job {
@PlanningId
private Long id;
private int width; // 产品宽度,单位:mm
// 其他字段...
}</job>
✅ 对应的软约束定义(推荐写法):
private Constraint widthChangePenalty(ConstraintFactory constraintFactory) {
return constraintFactory.forEachUniquePair(Machine.class,
// 获取机器及其作业列表
Joiners.equal(Machine::getJobList, (m1, m2) -> m1 == m2))
.flattenLast(machine -> machine.getJobList().stream())
.filter((machine, job) -> {
int index = machine.getJobList().indexOf(job);
// 确保 job 不是最后一个,且 nextJob 存在且宽度不同
if (index <p>⚠️ 更高效替代方案(推荐用于大规模数据):<br>
利用<code>Joiners.lessThan()</code> + 索引映射避免<code>indexOf()</code>线性查找:</p><pre class="brush:php;toolbar:false;">private Constraint widthChangePenaltyOptimized(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Machine.class)
.expand(machine -> IntStream.range(0, machine.getJobList().size() - 1)
.mapToObj(i -> Tuple2.of(machine.getJobList().get(i),
machine.getJobList().get(i + 1))))
.filter(tuple -> tuple._1().getWidth() != tuple._2().getWidth())
.penalize(HardSoftScore.ONE_SOFT)
.asConstraint("Width change penalty (optimized)");
}? 注意事项:
- 切勿在约束流中调用
List.indexOf()处理大型序列——它导致O(n²)复杂度;优先使用expand或预计算索引; - 若业务还需考虑“首作业切换”(如从空状态到某宽度),可在约束中单独添加对第一个作业的判断;
- 所有涉及
jobList的操作必须确保该列表已由OptaPlanner完成初始化(即在求解阶段有效),不可在未规划状态下直接访问; - 惩罚力度(如
ONE_SOFT)应与硬约束权重、其他软约束保持数量级协调,建议通过小规模实例校准。
通过PlanningListVariable建模,不仅解决了邻接关系表达问题,还为后续扩展(如最小批量长度、换型时间窗口、跨机器迁移惩罚)提供了清晰、可维护的架构基础。










