使用CVXPY

时间:2019-01-16 23:01:56

标签: mathematical-optimization linear-programming integer-programming cvxpy

我正在cvxpy中使用python解决特定类型的分配问题。我想以最小化成本的方式将M个人分配给N个组,但对组有以下限制:

  1. 组的成员不能超过J个
  2. 如果填充了一个组,则它必须至少具有K个成员,否则一个组可以具有零个成员。

当然,J <=K。我可以解决上述#2的问题。在下面的示例中,M = 6,N = 3,J =3。理想情况下,我想设置K =2。我生成首选项,以便每个人都喜欢组1(成本函数中的第1列),然后大多数人更喜欢第2组,但有人更喜欢第3组而不是第2组:

import numpy as np import cvxpy as cp

preference = np.array([[1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,3,2]])

groupmax = np.array([3,3,3])

selection = cp.Variable(shape=preference.shape,boolean=True)

group_constraint_1 = cp.sum(selection,axis=0) <= groupmax

assignment_constraint = cp.sum(selection,axis=1) == 1

cost = cp.sum(cp.multiply(preference,selection))

constraints = [group_constraint_1,assignment_constraint]

assign_prob = cp.Problem(cp.Minimize(cost),constraints)

assign_prob.solve(solver=cp.GLPK_MI)

print(selection.value)

解决方案/分配是:

[[1. 0. 0.]
 [1. 0. 0.]
 [1. 0. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

也就是说,我有一个最大尺寸为3的组,另一个最大尺寸为2的组和一个最大尺寸为1的组。在理想的设置中,一个1的组(第3组)太小了,那个人会分配给第2组。请注意,如果我仅将最小组大小设为2,则改为获得3组,每组2个:

import numpy as np import cvxpy as cp

preference = np.array([[1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,3,2]])

groupmax = np.array([3,3,3])

groupmin = np.array([2,2,2])

selection = cp.Variable(shape=preference.shape,boolean=True)

group_constraint_1 = cp.sum(selection,axis=0) <= groupmax

group_constraint_2 = cp.sum(selection,axis=0) => groupmin

assignment_constraint = cp.sum(selection,axis=1) == 1

cost = cp.sum(cp.multiply(preference,selection))

constraints = [group_constraint_1,group_constraint_2,assignment_constraint]

assign_prob = cp.Problem(cp.Minimize(cost),constraints)

assign_prob.solve(solver=cp.GLPK_MI)

print(selection.value)

现在的解决方案是:

[[1. 0. 0.]
 [1. 0. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 0. 1.]]

我尝试了以下解决方法,但是下面的第三个约束被cvxpy拒绝,因为问题不再是DCP。我认为问题在于我要在约束中将一个变量乘以另一个变量。我想不出另一种方法来使一个组中的总人数大于2或正好为零:

import numpy as np
import cvxpy as cp

preference = np.array([[1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,3,2]])

groupmax = np.array([3,3,3])

selection = cp.Variable(shape=preference.shape,boolean=True)

switch_1 = cp.Variable(shape=preference.shape[1],boolean=True)

switch_2 = cp.Variable(shape=preference.shape[1],boolean=True)

group_constraint_1 = cp.sum(selection,axis=0) <= groupmax

group_constraint_2 = cp.sum(selection,axis=0) - 2 * switch_1 >= 0

group_constraint_3 = cp.sum(selection,axis=0) * switch_2 == 0

switch_constraint = switch_1 + switch_2 == 1

assignment_constraint = cp.sum(selection,axis=1) == 1

cost = cp.sum(cp.multiply(preference,selection))

constraints = [group_constraint_1,group_constraint_2,group_constraint_3,
               switch_constraint,assignment_constraint]

assign_prob = cp.Problem(cp.Minimize(cost),
                         constraints)

assign_prob.solve(solver=cp.GLPK_MI)

print(selection.value)

我现在收到以下错误:DCPError: Problem does not follow DCP rules.

有没有办法纳入这种非标准约束?另外,如果我可以使用上述约束,则可以解决问题,但是如果您可以告诉我如何合并如下约束,则可以更轻松地解决问题:

  1. 组大小必须为零,J或K的倍数。

1 个答案:

答案 0 :(得分:0)

经过四处搜寻,我找到了解决自己问题的方法。以下解决了该问题:

import numpy as np
import cvxpy as cp

preference = np.array([[1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,2,3],
                       [1,3,2]])

groupmax = np.array([3,3,3])

selection = cp.Variable(shape=preference.shape,boolean=True)

bind_2 = cp.Variable(shape=preference.shape[1],boolean=True)

bind_3 = cp.Variable(shape=preference.shape[1],boolean=True)

group_constraint_1 = cp.sum(selection,axis=0) <= groupmax

group_constraint_2 = (1 - bind_2) * 2 >= 2 - cp.sum(selection,axis=0)

group_constraint_3 = (1 - bind_3) * 4 >= cp.sum(selection,axis=0)

bind_constraint = bind_2 + bind_3 == 1

assignment_constraint = cp.sum(selection,axis=1) == 1

cost = cp.sum(cp.multiply(preference,selection))

constraints = [group_constraint_1,group_constraint_2,group_constraint_3,
               bind_constraint,assignment_constraint]

assign_prob = cp.Problem(cp.Minimize(cost),constraints)

assign_prob.solve(solver=cp.GLPK_MI)

print(selection.value)

现在,我得到以下解决方案:

[[1. 0. 0.]
 [0. 1. 0.]
 [1. 0. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [1. 0. 0.]]

说明:

  1. 两个bind变量是二进制变量,表示约束2和3是否绑定
  2. 当bind_2 = 0时,约束2无论如何都成立,因为右侧的第二项为非负数。当bind_2 = 1时,只有在右边的第二项大于或等于2时,才能满足约束。
  3. 当bind_3 = 3时,约束3无论如何都成立,因为根据约束1,右边的项以3为边界。当bind_3 = 1时,仅当右边的项为零时,约束才能满足。 (该术语为非负数)。
  4. 第四个约束仅将两个bind变量之一限制为等于1。因此,每个组的总数可以大于2或正好为零。

到目前为止,我无法扩展到组大小需要为零,J或K的倍数的情况。原因是我无法在cvxpy中使用mod函数。

解决方案的想法来自here