约束规划与全局约束
当决策天然是离散的、业务规则更适合表达为关系而不是大量线性行时,约束规划(Constraint Programming,CP)很有用。CP 模型声明有限域、全局约束和目标函数;求解器结合传播、搜索和目标界来寻找或证明排程、分配、装箱或路径方案。
本页是数学教程,说明 OSPF 模型需要保持的语义;它不是 Kotlin 或 Rust API 调用清单。
1. 何时适合使用有限域 CP
当决策具有较小或明确有界的离散备选集合时,可以考虑有限域 CP:
- 以整数分钟或班次表示的开始时间;
- 从有限集合中选择工人、房间、车辆或机器;
- 工序顺序、排列位置、颜色或排名;
- 带有存在性决策的可选活动。
当许多决策通过全局规则相互作用时,CP 尤其具有表现力。一个 AllDifferent 约束可以描述排列,NoOverlap 可以描述互斥机器,Cumulative 可以描述可再生资源的负载曲线。全局形式直接表达业务规则,也可能让 CP 求解器得到比临时拼接的线性编码更强的传播。
有限并不意味着必须逐个列出所有值。
2. 核心定义
2.1 有限域变量
有限域变量
像
2.2 AllDifferent
对于变量
它常用于工人槽位、排列位置或一对一分配。它本身并不表示较大值域中的每个值都被使用,也不表示“每个任务恰好分配到一个资源”,除非变量域和其他约束共同赋予这种含义。
2.3 NoOverlap
对于持续时间为正的活动
通常把活动区间定义为半开区间
因此,一个活动恰好在时刻
2.4 Cumulative
设活动
对于整数时间,只需检查每个相关时间桶。累计资源允许活动重叠:容量为二时,两个需求为一的活动可以同时执行;若容量为二,一个需求为二的活动就不能与另一个需求为一的活动重叠。
3. 可手算的资源约束排程示例
3.1 业务描述与数据
需要安排四个作业,开始时间为整数时间单位。作业
| 作业 | 持续时间 | 电力需求 | 工人域 |
|---|---|---|---|
开始时间域为
3.2 变量、中间值与约束
决策变量为:
结束时间中间值定义为:
约束为:
最后一组约束是对所有区间结束时间的上包络。在最小化目标下,
电力约束不是第二个机器顺序约束,而是按时间检查的容量约束:单看电力时,NoOverlap 约束,也不能在容量为
3.3 一个可逐项检查的候选解
取下列数值:
| 作业 | ||||
|---|---|---|---|---|
工人值为 AllDifferent 成立。机器区间为
按时间桶检查资源曲线:
| 时间桶 | 活动作业 | 负载 |
|---|---|---|
所有负载都不超过
3.4 为什么 已经最优
机器活动存在二选一的先后关系。如果
如果
4. 全局形式的价值在于传播
求解器可能在分支之前先推导后果。在这个示例中,选择
应当区分:
- 原始全局约束,例如
NoOverlap; - 为求解而使用的后端表示;
- 返回给用户的证据,例如占用区间和最大资源负载。
如果全局约束被展开成成对约束或辅助变量,展开后的行属于实现细节。诊断报告仍应指出原始的机器规则或电力规则。
5. 在 OSPF 中的概念性组织
OSPF 模型可以按语义职责组织这个问题:
- 输入或排程上下文拥有作业持续时间、资源需求、工人可用性以及时间单位。
- 决策上下文拥有开始时间、结束时间、分配、可选存在性和使决策有意义的有限域。
- 规则上下文拥有先后关系、机器析取关系、
AllDifferent和累计容量约束,并保留每条原始业务规则的身份。 - 目标上下文拥有总工期的定义和优化方向。
- 结果上下文记录解的变量值、目标值、可行性或最优性状态,以及解释所用的界或容差。
编译边界可以在后端支持时把全局约束转换为后端原生表示,也可以在适合时使用精确的等价转换。转换必须保持区间端点、存在性语义、域以及约束身份。编译器架构 介绍语义模型与后端机制的更广泛分离;求解结果 介绍结果应该证明什么、又应该明确保留哪些未知项。这里的列表是概念上的职责划分,不表示每一项当前都有同名公共类。
在临界约束解释中,应报告原始的 NoOverlap、Cumulative 或先后关系,而不是任意的内部行。全局规则可能没有单一的标量 slack。关于活动性、有效性和相对于目标的阻塞解释之间的区别,参见临界约束分析。
6. 常见建模陷阱
6.1 混用闭区间与半开区间
在
6.2 域太小或误声明为连续
把开始时间域的上界写成
6.3 忘记可选活动的存在性语义
可选区间需要一个存在性决策。其结束时间关系、先后关系、机器占用和累计需求都应受存在性条件控制。仅仅把标签写成可空,却让约束仍然无条件生效,可能使不存在的作业继续占用容量。
6.4 把 AllDifferent 误认为分配覆盖
AllDifferent(w_i) 只表示选择的工人值彼此不同。五个作业可以使用十个工人时,它不要求每个工人都接到作业。如果一个工人可以处理多个不重叠作业,那么 AllDifferent 可能过强,应改用每个工人的资源或 NoOverlap 语义。
6.5 把累计容量当成成对不重叠
成对不重叠在某些情况下比容量曲线更强,在另一些情况下又可能漏掉容量限制。容量三允许三个需求为一的作业同时执行,但成对规则会错误地禁止它们。反过来,需求二的作业和需求一的作业可以在容量三上重叠,即使两道机器工序因为另一个业务原因仍必须使用 NoOverlap。
6.6 用目标函数补齐缺失的约束
最小化
6.7 假设每条全局约束都有有用的 slack
某条内部线性化行的数值残差不是 AllDifferent 或 NoOverlap 的通用 slack。应使用成对冲突、最大资源负载或域违规等语义证据,并把当前解的活动性与目标影响分开标记。