01 / 06 / 06
ProblemChange 与实时增量更新
用 ProblemChange 接口和 ProblemChangeDirector 在不重启 Solver 的情况下增删改问题事实和规划实体,支持插单、取消、资源变更等实时场景
30 秒速览#
ProblemChange 是 Timefold 的实时增量更新机制。求解过程中不重启 Solver,直接向 working solution 增/删/改问题事实和规划实体。常用于插单、订单取消、设备故障等场景。
ProblemChange 接口#
public interface ProblemChange<Solution_> {
void doChange(Solution_ workingSolution, ProblemChangeDirector problemChangeDirector);
}java实现 doChange,在 working solution 上做修改,通知 director 你改了什么。
三种提交入口#
| 入口 | 场景 |
|---|---|
Solver.addProblemChange(change) | 直接使用 Solver 同步求解 |
SolverManager.addProblemChange(problemId, change) | 服务化异步求解,按 problemId 定位 |
SolverJob.addProblemChange(change) | 持有 SolverJob 引用时使用 |
批量提交用 addProblemChanges(List<ProblemChange>),减少通知开销。
ProblemChangeDirector 的作用#
Director 负责在 working solution 中查找对象并通知 Solver 变更:
// 核心方法:查找 working clone 中的对应对象
<T> T lookUpWorkingObject(T externalObject);java查找依赖实体上的 @PlanningId。所以需要被查找的实体必须标注 @PlanningId:
@PlanningEntity
public class Task {
@PlanningId
private Long id;
// ...
}javaDirector 的其他方法(1.x 文档未详细列出所有签名,以下是常用模式):
- 添加/移除问题事实 → director 知道后增量更新受影响约束。
- 添加/移除规划实体 → director 知道后更新搜索空间。
- 变更问题事实 → 更新约束中对该事实的引用。
标准流程#
public class AddTaskProblemChange implements ProblemChange<ProductionSchedule> {
private final Task newTask;
public AddTaskProblemChange(Task newTask) {
this.newTask = newTask;
}
@Override
public void doChange(ProductionSchedule workingSolution,
ProblemChangeDirector director) {
workingSolution.getTasks().add(newTask);
// director 自动感知实体集合变化,无需手动 notify
}
}
// 提交
solverManager.addProblemChange(scheduleId, new AddTaskProblemChange(urgentTask));java关键规则#
- 只改 workingSolution:
doChange的参数是 Solver 内部的工作克隆,不是你提交时的原对象。 - 问题事实必须先克隆:官方明确指出规划克隆不会克隆问题事实和问题事实集合。如果要在
ProblemChange中修改问题事实,先做一份克隆再挂到 working solution,否则 working solution 和 best solution 会共享同一个事实对象,引发竞态条件。 - 用
lookUpWorkingObject找对象:不要用你持有的外部对象引用。传入的newTask和 working solution 中的 Task 不是同一个实例。 - 批量提交:连续多个变更尽量用
addProblemChanges批量提交。 - 部分初始化解是可接受的:前提是第一个求解阶段是 Construction Heuristic。
典型场景#
插单#
class InsertOrderChange implements ProblemChange<ProductionSchedule> {
private final Order newOrder;
@Override
public void doChange(ProductionSchedule ws, ProblemChangeDirector director) {
Task task = Task.fromOrder(newOrder);
ws.getTasks().add(task);
}
}java订单取消#
class CancelOrderChange implements ProblemChange<ProductionSchedule> {
private final long orderId;
@Override
public void doChange(ProductionSchedule ws, ProblemChangeDirector director) {
ws.getTasks().removeIf(t -> t.getOrderId() == orderId);
}
}java设备故障(资源不可用)#
class MachineDownChange implements ProblemChange<ProductionSchedule> {
private final String lineId;
@Override
public void doChange(ProductionSchedule ws, ProblemChangeDirector director) {
Line line = director.lookUpWorkingObject(
ws.getLines().stream().filter(l -> l.getId().equals(lineId)).findFirst().orElse(null));
if (line != null) {
line.setAvailable(false);
}
}
}java与 pinned / 连续规划的区别#
| 机制 | 适用场景 |
|---|---|
| ProblemChange | 求解进行中,动态增删改对象 |
| pinned 实体 | 固定某些实体不动,其余重排 |
| 热启动 + 重新求解 | 全面重新规划,不关心求解过程的中间状态 |
这三种不互斥,可以组合使用。