如何使用 Python 库 PuLP 在线性规划中执行等配给

How to perform equal rationing in Linear Programming using Python library PuLP

我将借助以下示例来解释我的问题。

在这个问题中,我有 4 个播放器,数量显示为数量,价格显示为价格。我也有估计的数量作为估计。目标是使用四名玩家的可用数量来完成估计数量。并且选择政策是,价格低的玩家优先供应数量。

price = [140, 50, 80, 60]

quantity = [100, 150, 200, 400]

estimate = 400

所以,对于这道题,每个玩家提供的数量是,

supply = [0, 150, 0, 250]

这只是一个简单的例子,实际数组的大小不止于此。我正在使用 Python 的 PuLP 库来解决这个线性规划问题。

但问题是当所有价格都相等时,它会根据索引给出答案,即index0 的玩家将首先提供,然后是index1 的玩家,依此类推。但是,我的实际要求是在平等定价的情况下,将估计数量平均分配给所有玩家。看例子,

price = [60, 60, 60, 60]

quantity = [100, 150, 200, 400]

estimate = 400

输出是,

supply = [100 , 150, 150, 0]

而我的要求是,

supply = [100, 100, 100, 100]

那么,我的问题是,如何解决均等配给问题?

两个想法:

LP

将加权 penalty-component 引入 objective,它是 supply-vars 的所有 pairwise-differences(absolute-value!)的总和。

优点:

  • 可以公式化为LP

缺点:

  • 这个惩罚的权重是 problem-dependent,需要调整!
  • 对数呈指数增长(关于#supplies)
    • 导致 auxiliary-variables 和约束
    • 给出一个快速公式,求解器将开始与 N >> 100 作斗争(例如 N=150 需要 10 秒 solve-time;取决于 bigM)
  • 需要 absolute-value re-formulation (lpsolve docs)
    • 需要在纸浆中手动完成

SOCP

将加权 penalty-component 引入 objective,即 supply-vars 的 euclidean-norm。

优点:

  • 变量/约束没有指数增长

缺点:

  • 这个惩罚的权重是 problem-dependent,需要调整!
  • 不再是线性的:SOCP-solver 需要(不太稳健)
  • 需要一个 norm-formulation(但如果可以访问 SOCP-solver,这是很自然的)

我没有使用太多的纸浆,并且会从概念上(bigM 未调整;缓慢制定)使用 cvxpy 展示这两种方法,这会自动给我们 absnorm 函数(重新表述)。

代码

import numpy as np
import cvxpy as cvx
from itertools import combinations
np.set_printoptions(suppress=True, precision=6)

# price = np.array([140, 50, 80, 60])
# quantity = np.array([100, 150, 200, 400])

price = np.array([60, 60, 60, 60])
quantity = np.array([100, 150, 200, 400])

estimate = 400

# LP approach
def solve_lp(penalty=True):
    bigM = 1e-3

    N = len(price)
    pairs = list(combinations(range(N), 2))

    x = cvx.Variable(N)
    constraints = [x >= 0,
                   cvx.sum_entries(x) >= estimate,
                   x <= quantity]
    obj_costs = price * x
    obj_penalty = sum([cvx.abs(x[a] - x[b]) for (a,b) in pairs])
    objective = obj_costs + bigM * float(penalty) * obj_penalty

    problem = cvx.Problem(cvx.Minimize(objective), constraints)
    problem.solve(solver='CBC')  # Warning: solver not shipped by default
                                 #          same solver as shipped by pulp
                                 #          = Clp/Cbc
    print(problem.status)
    print(problem.value)
    print(x.value.T)

# QP approach
def solve_socp(penalty=True):
    bigM = 1e-3

    N = len(price)
    est_avg = estimate / N

    x = cvx.Variable(N)
    constraints = [x >= 0,
                   cvx.sum_entries(x) >= estimate,
                   x <= quantity]
    obj_costs = price * x
    obj_penalty = cvx.norm(x - est_avg)
    objective = obj_costs + bigM * float(penalty) * obj_penalty

    problem = cvx.Problem(cvx.Minimize(objective), constraints)
    problem.solve(solver='ECOS')
    print(problem.status)
    print(problem.value)
    print(x.value.T)

print('LP a')
solve_lp(penalty=False)
print('LP b')
solve_lp(penalty=True)
print('SOCP a')
solve_socp(penalty=False)
print('SOCP b')
solve_socp(penalty=True)

输出

LP a
optimal
24000.0
[[100. 100. 200.   0.]]
LP b
optimal
24000.0
[[100. 100. 100. 100.]]
SOCP a
optimal
24000.000032013406
[[ 44.92762   69.401923  93.878797 191.791661]]
SOCP b
optimal
24000.000001071123
[[ 99.999423  99.999973  99.999959 100.000645]]

备注:SOCP 是通过interior-point 方法求解的,该方法近似解(理论上:尽可能准确)。因此值不完全是 100。