TimeFold AI调度:如何让序列末尾无法调度的任务留空?
基于TimeFold的任务调度末尾未分配实现方案
一、核心模型调整
1. 新增任务序列序号字段
给Schedule规划实体添加固定的任务序号字段taskOrder,用来明确任务的业务序列顺序(如1到N),该字段为非规划变量,初始化时按业务要求的序列顺序赋值:
public class Schedule { // 任务序列序号,1到N,固定值,初始化时赋值 private Integer taskOrder; @PlanningVariable(allowsUnassigned = true, valueRangeProviderRefs = "userRange") private User user; @PlanningVariable(allowsUnassigned = true, valueRangeProviderRefs = "timeRange") private OffsetDateTime start; @ShadowVariable(sourceVariableName = "start", variableListenerClass = EndTimeListener.class) private OffsetDateTime end; // 依赖的前置任务 private Schedule dependency; // Getter、Setter方法 }
2. 规划变量配置
保持allowsUnassigned=true,允许任务未分配,但通过后续约束限制未分配任务的位置。
二、约束规则设计
1. 硬约束:强制未分配任务为连续末尾序列
这是实现需求的核心约束,确保只要有任务未分配,所有序号更大的任务必须同样未分配,禁止中间任务留空:
constraintFactory.from(Schedule.class) // 筛选未分配任务(user或start为null) .filter(task -> task.getUser() == null || task.getStart() == null) // 关联序号更大的任务 .join(Schedule.class, Joiners.greaterThan(Schedule::getTaskOrder)) // 若更大序号的任务已分配,则违反硬约束 .filter((unassignedTask, laterTask) -> laterTask.getUser() != null && laterTask.getStart() != null) .penalize("禁止中间任务未分配", HardSoftScore.ONE_HARD) .asConstraint();
2. 调整依赖约束
仅对已分配的任务对生效,避免未分配任务干扰依赖检查:
constraintFactory.from(Schedule.class) .filter(task -> task.getDependency() != null // 当前任务已分配 && task.getUser() != null && task.getStart() != null // 依赖任务必然已分配(因依赖任务序号更小,受上述硬约束限制) && task.getDependency().getEnd() != null) // 检查当前任务起始时间是否晚于依赖任务结束时间 .filter(task -> task.getStart().isBefore(task.getDependency().getEnd())) .penalize("依赖任务未完成不能开始", HardSoftScore.ONE_HARD) .asConstraint();
3. 保留并适配原有硬约束
避免用户任务重叠
仅检查已分配任务:
constraintFactory.from(Schedule.class) .filter(task -> task.getUser() != null && task.getStart() != null) .join(Schedule.class, Joiners.equal(Schedule::getUser), Joiners.overlapping(Schedule::getStart, Schedule::getEnd)) // 避免重复检查同一任务对 .filter((task1, task2) -> task1.getTaskOrder() < task2.getTaskOrder()) .penalize("用户任务时间重叠", HardSoftScore.ONE_HARD) .asConstraint();
角色兼容约束
仅检查已分配任务:
constraintFactory.from(Schedule.class) .filter(task -> task.getUser() != null && task.getStart() != null) .filter(task -> !task.getUser().getRoles().contains(task.getRequiredRole())) .penalize("用户角色不兼容", HardSoftScore.ONE_HARD) .asConstraint();
4. 软约束:最大化已分配任务数量
驱动求解器尽量减少未分配任务的数量:
constraintFactory.from(Schedule.class) .filter(task -> task.getUser() != null && task.getStart() != null) .reward("已分配任务奖励", HardSoftScore.ONE_SOFT) .asConstraint();
三、求解器配置优化
1. 自定义初始解构建器
优先给序号靠前的任务分配资源,降低求解器的搜索成本:
public class ScheduleInitialSolutionBuilder implements InitialSolutionBuilder<ScheduleSolution> { @Override public ScheduleSolution buildInitialSolution(ScheduleSolution problem, Random random) { List<Schedule> tasks = problem.getTaskList(); // 按taskOrder升序排序 tasks.sort(Comparator.comparingInt(Schedule::getTaskOrder)); // 尝试为前序任务分配资源(可根据业务逻辑实现更智能的初始分配) // 示例逻辑:先全部分配,若出现冲突则移除末尾任务 return problem; } }
在@PlanningSolution中配置:
@PlanningSolution public class ScheduleSolution { // 规划实体集合 private List<Schedule> taskList; // 值范围提供者 private List<User> userList; private List<OffsetDateTime> timeList; @PlanningSolver( initialSolutionBuilder = ScheduleInitialSolutionBuilder.class, termination = @Termination(secondsSpentLimit = 30) ) private HardSoftScore score; // Getter、Setter方法 }
2. 合理设置终止条件
根据任务规模设置足够的求解时间,确保求解器能找到符合硬约束的最优解。
四、常见问题排查
- 依赖顺序不遵守:检查依赖约束是否仅针对已分配任务,同时确认
taskOrder的赋值是否符合业务序列顺序(依赖任务的序号必须小于当前任务)。 - 任务起始时间重复:检查重叠约束的时间比较逻辑是否正确(
OffsetDateTime的isBefore/isAfter判断是否准确)。 - 未分配任务非末尾:检查“禁止中间任务未分配”的硬约束是否正确,确认
taskOrder的赋值无重复或顺序错误。
内容的提问来源于stack exchange,提问作者denk182
相关产品推荐
相关产品推荐

