optaplanner重复规划
1. 重复规划简介
在执行解决方案之前或期间,用于创建解决方案的问题事实可能会发生变化。为了降低问题事实变化的风险而延迟规划并不理想,因为不完整的计划比没有计划更可取。
以下示例演示了需要根据不可预测的变化修改规划解决方案的情况:
- 未预料到的事实变化
- 被安排到班次的员工请病假。
- 预定起飞的飞机遇到技术故障延误。
- 机器或车辆之一损坏。
无法立即分配所有实体
- 留下一些未分配,例如:
- 同一时间有10个班次需要分配,但只有九个员工可以处理班次。
对于这种类型的规划,使用超约束规划。
未知的长期未来事实
- 例如:
- 下两周的医院入院人数可靠,但第三周和第四周不太可靠,第五周及以后还不值得进行规划。
这个问题可以通过连续规划来解决。
问题事实不断变化
- 使用实时规划。
更多的CPU时间会得到更好的规划解决方案。
OptaPlanner允许您在遇到不可预见变化时尽早开始规划,因为优化算法支持部分规划的解决方案。这就是所谓的重复规划。
2. 备份规划
备份规划通过添加额外的得分约束来为出现问题时提供规划空间。这样,在计划内创建备份计划。
备份规划的示例如下:
- 创建额外的得分约束。例如:
- 将员工指定为备用员工(每个同时有10个班次)。
- 在每个部门保留一张病床。
当发生不可预见事件时,更改规划问题。例如,如果一个员工请病假:
- 删除病假员工并将其班次保持未分配状态。
- 重新启动规划,从该解决方案开始,此时解决方案的得分已经发生了变化。
- 构造启发式方法填充新创建的间隙(可能使用备用员工),元启发式方法进一步优化。
3. 超约束规划
当无法分配所有规划实体时,最好尽可能多地分配实体而不违反硬约束。这称为超约束规划。
默认情况下,OptaPlanner会分配所有规划实体,超载规划值,因此会违反硬约束。有两种方法可以避免这种情况:
- 使用可空的规划变量,使一些实体未分配。
- 添加虚拟值以捕获未分配的实体。
3.1. 使用可空变量的超约束规划
如果我们使用可空变量处理超约束规划,则超载的实体将保持未分配状态。
为了实现这一点:
- 切换得分类型,添加一个得分级别(通常介于硬和软级别之间)。
- 将规划变量设置为可空。
- 在新级别上添加得分约束(通常是中等约束),以惩罚未分配实体的数量(或其加权和)。
3.2. 使用虚拟值的超约束规划
在超约束规划中,了解缺少哪些资源通常是很有用的。在具有虚拟值的超约束规划中,解决方案指示需要购买哪些资源。
为了实现这一点:
- 通过切换得分类型添加额外的得分级别(通常是硬级别和软级别之间的中等级别)。
- 添加一定数量的虚拟值。确定好计算该数量的公式可能比较困难:
- 不要添加太多,否则会降低求解器的效率。
- 重要的是不要添加太少,否则会导致无法实现的解决方案。
- 在新级别上添加得分约束(通常是中等约束),以惩罚虚拟分配实体的数量(或其加权和)。
- 可选地,将所有软约束更改为忽略虚拟分配实体。
4. 连续规划(窗口规划)
连续规划是一种技术,可以同时规划一个或多个即将到来的规划周期,并每月、每周、每天、每小时甚至更频繁地重复该过程。然而,由于时间是无限的,规划所有未来时间段是不可能的。
在上面的员工排班示例中,我们每四天重新规划一次。每次,我们实际上规划了12天的窗口,但只发布了前四天的计划,这足够稳定,可以与员工共享,以便他们可以相应地安排社交生活。
在上面的医院床位规划示例中,请注意11月1日的原始规划和11月5日的新规划之间的差异:其中一些问题事实(F、H、I、J、K)在此期间发生了变化,从而导致不相关的规划实体(G)也发生了变化。
规划窗口可以分为几个阶段:
-
历史
- 不可变的过去时间段。它只包含固定的实体。
- 最近的历史实体也可能影响适用于可移动实体的得分约束。例如,在护士排班中,连续工作了三个历史周末的护士不应该再连续被安排到三个周末,因为她每月需要一个休息周末。
- 不要将所有历史实体加载到内存中:尽管固定实体不会影响求解性能,但当数据增长到数年时,它们可能会导致内存问题。只加载那些可能仍然影响当前约束的实体,确保有足够的安全边界。
-
已发布
- 已发布的即将到来的时间段。它们只包含固定和/或半可移动的规划实体。
- 已发布计划已与业务共享。例如,在护士排班中,护士将使用此计划来安排个人生活,因此他们需要提前大约3周的发布通知。普通规划不会更改计划的这部分。
- 更改此计划后会带来不便,但是由于异常情况,我们仍然需要做出这些更改(例如有人请病假),在最小化不可中断的重新规划的同时更改计划。
-
草稿
- 已发布时间段之后的即将到来的时间段,可以自由更改。它们包含可移动的规划实体,除非有其他原因(例如用户指定)而被固定。
- 草稿的第一部分称为最终草稿,并将其发布,因此这些规划实体可以最后一次更改。发布频率(例如每周一次)确定了从草稿到已发布的时间段数量。
- 草稿的后期时间段可能会在以后的规划中再次发生变化,特别是如果其中一些问题事实在那时发生了变化(例如护士Ann不想在其中某天工作)。
- 尽管这些后期规划实体可能会再次发生很大变化,但我们不能将它们留到以后,因为那样我们可能会陷入困境。例如,在员工排班中,我们可能会让所有罕见的技能员工在最后5天工作,这不会降低那一周的得分,但会使我们无法提供下周的可行计划。因此,草稿长度需要比首先发布的部分更长。
- 通常情况下,不会与业务共享草稿,因为它太不稳定,并且只会产生虚假的期望。然而,它存储在数据库中,并用作下一个求解器的起点。
-
未规划(超出范围)
- 不在当前规划窗口中的规划实体。
- 如果规划窗口太小以规划所有实体,则需要处理超约束规划。
- 如果时间是一个规划变量,则规划窗口的大小是动态确定的,此时未规划阶段不适用。
4.1. 固定规划实体
固定规划实体在求解过程中不会更改。用户常常使用固定规划实体将一个或多个特定分配固定下来,并强制OptaPlanner围绕这些固定分配进行调度。
4.1.1. 使用@PlanningPin固定规划实体
要固定一些规划实体,请在规划实体类的布尔型getter或字段上添加@PlanningPin注释。如果实体被固定到其当前规划值,则该布尔值为true,否则为false。
在布尔型上添加@PlanningPin注释:
@PlanningEntity
public class Lecture {
private boolean pinned;
...
@PlanningPin
public boolean isPinned() {
return pinned;
}
...
}
上面的示例中,如果pinned为true,则不会将讲座分配给其他时间段或教室(即使当前的period和room字段为null)。
4.1.2. 配置PinningFilter
要固定一些规划实体,请添加一个PinningFilter,如果实体被固定,则返回true;如果可移动,则返回false。这比@PlanningPin方法更灵活、更冗长。
例如,在护士排班示例中:
添加PinningFilter:
public class ShiftAssignmentPinningFilter implements PinningFilter<NurseRoster, ShiftAssignment> {