SWI Prolog CLP(FD)计划

时间:2019-04-09 13:03:30

标签: prolog scheduling swi-prolog clpfd clp

我正在使用CLPFD库解决SWI Prolog中的调度任务。由于这是我第一次解决比情感问题更严重的事情,因此我可能需要经验丰富的用户提供一些好的建议。让我简要地描述领域/任务。

我有一个月的“日历”。每天一整天有2个,整夜有2个(长期12小时服务)。另外,只有周一至周五10个工人8小时(短期服务)。

域约束显然是:

  1. 没有连续服务(晚上无夜服务,晚上无夜服务)
  2. 工作人员连续最多可以连续2个夜间服务
  3. 每个工人一个月的工作时间有限
  4. 有19名工人可用

我的方法如下:

变量

对于日历中的每个字段,我都有一个定义的变量:

  • DxD_y,其中x是一天中的数字,y是1或2,用于全天候服务
  • DxN_y,其中x是白天的数字,y是1或2的长夜服务
  • DxA_y,其中x是日数,y是0 .. 9是短日服务
  • SUM_x,其中x是代表工作时间的员工数(1..19)

每个D变量都有一个域1..19。为了简单起见,请为每个SUM_X #=< 200使用X

约束

  • all_distinct()对于同一天的每个变量-每个工人每天只能提供一项服务
  • global_cardinality()为带有短服务和长服务的列表计算每个数字1..19的出现次数-这定义了变量LSUM_XSSUM_X-工人的出现次数{ XL园艺服务中的{1}}
  • S每个工人
  • SUM_X #= 12*LSUM_X + 8*SSUM_X-避免在一夜之后进行全天服务
    • 具有类似上述约束的束,以覆盖所有域约束
  • DxN_y #\= Dx+1D_z-为避免连续三天的夜间服务,DxNy #= Dx+1Ny #==> DxNy #\= Dx+2Nyx的每种组合都有约束条件

注释

所有变量和约束条件都直接在pl脚本中说明。我不使用prolog谓词来生成约束-因为我在.NET应用程序(前端)中有一个模型,并且可以轻松地将所有内容从.NET代码生成为prolog代码。

我认为我的方法总体上是好的。在一些较小的示例上运行调度程序效果很好(7天,4个长期服务,1个短期服务,8个工人)。同样,在完整的案例中,我也能得到一些有效的结果-30天,19名工人,每天4个长期服务和10个短期服务。

但是,我对当前状态并不完全满意。让我解释一下原因。

问题

  1. 我阅读了一些有关对调度问题进行建模的文章,其中一些文章使用了一些不同的方法-仅对变量(日历字段)和worker的每种组合引入布尔变量,以标记是否将worker分配给了特定的日历字段。这是更好的方法吗?
  2. 如果计算日历中的总工作量限制和总时数,您会发现工人没有100%被利用。但是求解器很可能以这种方式创建解决方案:y。因此,解决方案中的SUM看起来像utilize the first worker for 100% and then grab the next one。如果工人或多或少地得到同等的利用,我将感到高兴。是否有一些简单/有效的方法来实现这一目标?我曾考虑过定义一些定义,例如定义工作人员之间的差异并将其最小化,但这对我来说听起来很复杂,而且我担心我会花很多时间来计算出来。我使用[200,200,200...200,160,140,80,50,0,],但是它只对变量重新排序,因此仍然存在这些差距,只是顺序不同。可能我希望labeling([random_variable(29)], Vars)过程将以除labelingup之外的其他顺序(以某种伪随机方式)获取值。
  3. 我应该如何订购约束?我认为约束顺序与标签效率有关。
  4. 如何调试/优化标签性能?我希望解决这种类型的任务将花费几秒钟或最多几分钟的时间,以防求和条件非常严格。例如,使用down选项标记需要很长时间。

如果需要,我可以提供更多代码示例。

1 个答案:

答案 0 :(得分:2)

有很多问题,让我尝试解决一些问题。

  

...对于我的变量(日历字段)和worker的每种组合,仅引入布尔变量以标记是否将worker分配给特定的日历字段。这是更好的方法吗?

通常在使用MILP(混合整数线性规划)求解器时必须完成,其中必须将更高级的概念(例如alldifferent等表示为线性不等式)。这样的公式通常需要大量的布尔变量。约束编程在这里更加灵活,并且提供了更多的建模选择,但是不幸的是,没有简单的答案,这取决于问题。您对变量的选择会影响表达问题约束的难度和解决效率。

  

因此,解决方案中的SUM看起来像[200,200,200 ... 200,160,140,​​80,50,0,]。如果工人或多或少地得到同等的利用,我将感到高兴。有没有简单/有效的方法来实现这一目标?

您已经提到了最小化差异的想法,这就是通常会实现这种平衡要求的方式。它不需要复杂。如果最初我们有这个不平衡的第一个解决方案:

?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, labeling(Xs).
Xs = [0, 0, 2, 9, 9]

然后简单地最小化列表元素的最大值将已经为您(结合总和约束)提供了一个平衡的解决方案:

?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, Cost#=max(Xs), minimize(labeling(Xs),Cost).
Xs = [4, 4, 4, 4, 4]
Cost = 4

您还可以最小化最大值和最小值之间的差异:

?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, Cost#=max(Xs)-min(Xs), minimize(labeling(Xs),Cost).
Xs = [4, 4, 4, 4, 4]
Cost = 0

,甚至平方和。 [抱歉,我的示例是针对ECLiPSe的,而不是SWI / clpfd的示例,但应显示出大致的思路。]

  

我应该如何订购约束?我认为约束顺序与标签效率有关。

您不必为此担心。尽管它可能会产生一些影响,但是它太不可预测了,并且在很大程度上取决于实现细节,因此无法提出任何一般性建议。这确实是求解器实现者的工作。

  

如何调试/优化标签性能?

对于实际问题,您通常需要(a)针对特定问题的标签启发式方法,以及(b)一些不完整的搜索。 搜索树或搜索进度的可视化可以帮助定制启发式方法。您可以在chapter 6 of this online course中找到有关这些问题的一些讨论。