OptaPlanner中时序链式任务的动态依赖关系建模问询
摘要
如何建模一个任务时长依赖前置任务结束时间的时序链式流程?详情请阅读以下描述:
规划问题描述
首先说明我的规划问题:
约束条件
- 一台机器拥有x个槽位,每个槽位可生产或冷却不同类型产品。
- 机器不能同时进行生产和冷却,但可先生产后冷却。
- 机器同一时间只能生产一种产品,但可依次生产不同类型产品。
- 机器可同时生产多个同类型产品。
- 机器可同时冷却不同类型产品。
- 产品需在目标时间被消耗。
- 生产出的产品必须冷却至目标时间。
规划目标
- 最小化生产时间
- 确定生产必须启动的最晚时间
有效流程示例
下图展示了两个有效的生产流程。由于规划实体从目标时间开始反向按时间链式排列,机器位于流程的右端。
领域模型
由于生产任务的最短时长不足一分钟,我采用时序链式模式建模该问题,需以秒为单位规划,这对于时间粒度模式来说可能过于精细。
以下是我的领域模型片段:抽象类JobOrSlot用于建模以槽位为锚点的任务链。
@PlanningEntity public abstract class JobOrSlot { @Id private UUID id = UUID.randomUUID(); @InverseRelationShadowVariable(sourceVariableName = "previousJobOrSlot") protected Job nextJob; public abstract Integer getStartOffset(); }
@PlanningEntity public class Job extends JobOrSlot { @PlanningVariable(valueRangeProviderRefs = { "slotRange", "jobRange" }, graphType = PlanningVariableGraphType.CHAINED) private JobOrSlot previousJobOrSlot; @AnchorShadowVariable(sourceVariableName = "previousJobOrSlot") private Slot slot; @ShadowVariable(variableListenerClass = EndTimeUpdatingVariableListener.class, sourceVariableName = "previousJobOrSlot") private Integer startOffset; @PiggybackShadowVariable(shadowVariableName = "startOffset") private Integer endOffset; // problem facts @Nullable private Job predecessor; @Nullable private Job successor; private Duration duration; private JobType type; // produce, hold // ... 省略无关的问题事实、getter和setter }
EndTimeUpdatingVariableListener.class会遍历任务链,根据任务时长和其在链中的位置计算任务的开始和结束时间。
问题难点
挑战在于冷却任务的时长在规划开始前是未知的,只有当生产任务被安排后才能计算,即targetTime - cookingJob.endTime。因此安排生产任务必须改变冷却任务的时长,我无法找到在OptaPlanner中建模此场景的理想方案。我评估了两种可能的解决方案,但或许有更好的实现思路:
- 一种方案是在
EndTimeUpdatingVariableListener中,每当生产任务发生变化时更新关联冷却任务的开始和结束时间,但这可能引发难以处理的问题。例如,在生产任务和冷却任务之间插入其他任务(如produce A -> someOtherJob -> cool A -> Slot)会导致无限更新循环。可通过实现MoveFilter禁止此类无效移动,但仍较为繁琐。 - 另一种方案是实现CustomMoves,在规划问题中添加或移除冷却任务。每次添加冷却任务时,为其设置当前有效的时长,这样实体的时长在规划过程中保持固定。
这两种实现都相当复杂。是否有更好的方法在OptaPlanner中建模此类规划实体依赖关系?
优化建模方案建议
1. 绑定生产-冷却任务为逻辑单元
将生产任务和对应的冷却任务视为一个不可拆分的ProductionCoolingPair规划实体,而非两个独立任务。这样可以确保两者始终连续排列,避免中间插入其他任务的问题:
- 新增
ProductionCoolingPair类,包含生产任务、冷却任务的属性,将其作为链式规划的基本单元。 - 冷却任务的时长可在生产任务结束时间确定后,直接通过
targetTime - productionEndTime计算并存储在ProductionCoolingPair中,无需单独更新。
2. 调整ShadowVariable计算逻辑
若坚持分开建模生产和冷却任务,可修改EndTimeUpdatingVariableListener的逻辑:
- 为冷却任务添加
linkedProductionJob属性,关联对应的生产任务,确保仅当关联生产任务变化时才更新冷却任务时长。 - 变量更新时,先判断当前任务是否为冷却任务,若是则通过关联生产任务的结束时间计算自身时长,再推导开始/结束偏移量。同时添加硬约束,禁止生产与冷却任务之间插入其他任务,比MoveFilter更可靠。
3. 使用动态问题事实特性
将冷却任务的时长定义为动态问题事实,而非规划实体的固定属性:
- 通过
ProblemFactChange机制,在生产任务安排发生变化时,触发冷却任务时长的更新。该机制会在规划稳定阶段执行,可避免无限循环问题。
方案对比
- 绑定任务对的方案实现最简单,逻辑清晰,适合生产与冷却必须连续的场景,能有效避免无效移动和循环更新。
- 调整ShadowVariable逻辑的方案保留了任务独立性,需额外约束校验,适合后续可能扩展任务类型的场景。
- 动态问题事实的方案灵活性最高,但实现复杂度略高,适合任务依赖关系更复杂的场景。
内容的提问来源于stack exchange,提问作者Jonas Erbe
相关产品推荐
相关产品推荐

