MIP Python School Teacher Timetabling problem - 如何检查变量数是否为零等于某个数

MIP Python School Teacher Timetabling problem - how to check if the number of variables equals zero is equal to a certain number

我正在尝试解决学校教师时间问题。我的情况很好,但现在我被困在如何模拟每个老师在一周内有一定数量的空闲天数,例如每个老师的零节课天数之和等于一定数

基本问题是:如何添加一个约束来计算变量等于 0.0 的次数?

y 计算老师每天教课的小时数。 或多或少应该是这样的(但当然行不通)

for t in teacher:
    model += xsum( y[t, d] == 0.0 for d in day ) == 1.0

我也尝试使用变量的实际值:

for t in teacher:
    model += xsum( y[t, d].x == 0.0 for d in day ) == 1.0

和:

for t in teacher:
    model += xsum( y[t, d] for d in day if y[t, d] == 0.0 ) == 1.0

但 none 似乎有效。

我可以分享一些代码,但实际上所有内容都是用意大利语编写的,所以我试图直截了当。

假设您想使用(线性)MIP 求解器解决此问题,您的尝试违反了线性。以下是您可以尝试的方法:

y[t,d] 为教师 td 天的小时数。假设它是一个整型变量。然后我们可以定义二进制变量:

 x[t,d] = 1  if t works at d
        = 0  is t is off at d

x和y之间的link可以建模为:

 x[t,d] <= y[t,d]     (y[t,d]=0 => x[t,d]=0)
 8*x[t,d] >= y[t,d]   (y[t,d]>0 => x[t,d]=1, assuming 8 hours is the maximum per day)  

现在我们可以说(用伪代码):

 sum(d, 1-x[t,d]) = 1   (exactly one day off)

或者也许:

 sum(d, x[t,d]) = 4     (exactly 4 days at work)

@erwin 解决方案基于理论 POV,所以我接受它,但我无法在 MIP Python 代码中进行转换。

所以我找到了一些其他的解决方案,在某些方面并不完美,但我放在这里供其他小伙伴参考。 当然,我会在翻译成英文后立即分享代码。顺便说一句,我正在寻找学术界的学术团队伙伴来撰写科学文章(@erwin?)或媒体文章或两者兼而有之,并以开源形式发布所有代码。

让我们回到问题。 我找到了 3 个硬约束解决方案和 1 个软约束解决方案。我展示了所有解决方案,以便有人可以使用它

HC 解决方案 1 - 优雅的解决方案

我可以简单地为老师的组合添加一个 y[t,d] <= 0.0 的硬约束,我想在一周中的哪一天休息。

for t in self.teacher:
    for d in self.day:
        if t,d in self.desiderata:
            self.model += self.y[t, d] <= 0.0

(顺便说一句,这是一个简化版本,因为 desiderata 是一个结构化词典,但我认为你明白了)

切中要害,效果太棒了!

但我知道可以做得更好,我也可以避免使用 y[t,d] 因为它是一个导数变量所以我可以避免它并提高性能。

HC 解决方案 2 - 性能更好的解决方案

因为 y[t,d] 实际上是原始变量 x[t, g, m, d, h] 的派生变量,其中:

self.x = {
    (t, g, m, d, h): self.model.add_var(
        name="x({},{},{},{},{})".format(t, g, m, d, h),
        var_type=BINARY
    )
    for t in self.teacher
    for g in self.grade
    for m in self.matter
    for d in self.day
    for h in self.hour
}

和:

self.y = {
    (t, d): self.model.add_var(
        name="y({},{})".format(t, d),
        var_type=BINARY
    )
    for t in self.teacher
    for d in self.day
}

for t in self.teacher:
    for d in self.day:
        self.y[t,d] = xsum( self.x[t, g, m, d, h] 
                                for h in self.hour
                                for m in self.matter
                                for g in self.grade)

所以基本上,y[t,d] 总结了教师 t 在特定日期 d 工作的小时数。 实际上我不需要 y[t,d] 因为我可以用这种方式直接写约束:

for t in teacher:
    for d in self.day:
        if t,d in self.desiderata:
            self.model += xsum(self.x[t, g, m, d, h] 
                               for g in self.grade 
                               for m in self.matter 
                               for h in self.hour) <= 0.0

还有 .....天哪,这也行!太棒了

这应该会带来更好的性能,因为 NP-Complete 中的问题因此减少变量数量可以减少解决时间。

但我确信我可以改进它。 为什么我还要为那个老师放假的 t、d 的组合添加变量 x[t, g, m, d, h]? 如果我不生成 x[t, g, m, d, h] 问题默认解决,没有任何其他限制。让我们试试:

HC 解决方案 3 - 更快的解决方案

基本上,变量 x 的生成考虑了每位教师的休息日:

self.x = {
    (t, g, m, d, h): self.model.add_var(
        name="x({},{},{},{},{})".format(t, g, m, d, h),
        var_type=BINARY
    )
    for t in self.teacher
    for g in self.grade
    for m in self.matter
    for d in self.day
    for h in self.hour
    if not (t,d in self.desiderata) # Each teacher not work in days off
}

并且在不添加任何其他限制的情况下,这也有效!!!开箱即用!

这导致我有 18% 的变量没有生成。请记住,NP-Complete 中的问题,所以这是一个巨大的改进!

所以我有 3 个不同的完全有效的硬约束解决方案,但有一个问题,因为如果休息日冲突太多,这种方法很容易导致不可行的解决方案。

有了代码,我可以一天一天地放松下来,但这样就不再是纯粹的MIP问题了。

为什么不使用 Objective 功能?

所以让我们在 Objective 函数中插入匹配的休假天数。

SC - 尽量延长休息日

为了清楚起见,我们使用 y[t,d],但考虑到我们可以放弃它并在需要时使用 x。

self.model.objective = minimize( 
    xsum( self.y[t,d] for t in self.teacher for d in self.day if t,d in self.desiderata )
)

而且效果也很好!!!

在我将尽快分享的代码中,有一些代码优化主要是为了减少变量的数量,但概念是在上面表达的。

感谢您阅读这么长的回复。