繁体   English   中英

具有动态约束的Python Pulp线性编程

[英]Python Pulp linear programming with dynamic constraint

我目前在excel中使用求解器,以找到制造方面的最佳解决方案。 这是当前设置: 在此处输入图片说明

它涉及在旋转机器上制造鞋,也就是说,生产要重复进行。 例如,一批将是“ 10x A1”(请参阅​​表中的A1),这将产生10x尺寸36、20x尺寸37 ... 10x尺寸41。

有一些带前缀的设置。 A1,A2; R7 ...如上表所示。

然后是所requested变量(或更确切地说是变量列表),该变量基本上说出了客户要求的数量,每种尺寸的数量。

目标功能是找到一组重复,以使其与请求的数量尽可能地匹配。 因此,在求解器中(对不起,非英文屏幕截图),您可以看到目标是N21 (即每个尺寸的绝对差之和)。 变量是N2:N9这是每个设置的重复次数,唯一的约束是N2:N9是整数。

如何使用python对此行为建模? 我的开始:

from collections import namedtuple

from pulp import *


class Setup(namedtuple('IAmReallyLazy', 'name ' + ' '.join(f's{s}' for s in range(36, 47)))):
    # inits with name and sizes 's36', 's37'... 's46'
    repetitions = 0


setups = [
    Setup('A1', 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0),
    Setup('A2', 0, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0),
    Setup('R7', 0, 0, 1, 1, 1, 1, 2, 0, 0, 0, 0),
    Setup('D1', 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
    # and others
]

setup_names = [s.name for s in setups]

requested = {
    's36': 100,
    's37': 250,
    's38': 300,
    's39': 450,
    's40': 450,
    's41': 250,
    's42': 200,
}


def get_quantity_per_size(size: str) -> int:
    return sum([getattr(setup, size) * setup.repetitions for setup in setups])


def get_abs_diff(size: str) -> int:
    requested_size = requested.get(size, 0)
    return abs(get_quantity_per_size(size) - requested_size)


problem = LpProblem('Optimize Batches', LpMinimize)
# goal is to minimise the sum(get_abs_diff(f's{size}') for size in range(36, 47))
# variables are [setup.repetitions for setup in setups]
# constraints are all([isinstance(setup.repetitions, int) for setup in setups])

在理想世界中,如果有多个最佳解决方案,则应选择具有最大散布差异的一个(即,差异最小的一个)。 也就是说,如果一个解决方案的每个尺寸的Abs差异为10,尺寸为10(总计100),而另一个解决方案的Abs diff为20 + 80 = 100,则第一个解决方案对于客户而言更为理想。

另一个约束应该是min(setup.repetitions for setup in setups if setup.repetitions > 0) > 9基本上,重复约束应该是:

  • 是整数
  • 是0 还是大于9-到目前为止,根据我的理解,这在线性编程中是不可能的。

这里有几件事。 首先,如果使用abs()则问题将是非线性的。 相反,你应该引入新的变量调用,也就是说, over_mfgunder_mfg ,代表的上述目标的生产数量和目标下方的数字,分别。 您可以这样声明:

over_mfg = LpVariable.dicts("over_mfg", sizes, 0, None, LpInteger)
under_mfg = LpVariable.dicts("under_mfg", sizes, 0, None, LpInteger)

我声明了一个名为sizes的列表,该列表在上面的定义中使用:

min_size = 36
max_size = 46
sizes = ['s' + str(s) for s in range(min_size, max_size+1)]

您还需要指示每个设置重复次数的变量:

repetitions = LpVariable.dicts("repetitions", setup_names, 0, None, LpInteger)

然后将您的目标函数声明为:

problem += lpSum([over_mfg[size] + under_mfg[size] for size in sizes])

(请注意,在pulp您使用lpSum而不是sum 。)现在,您需要使用约束条件,说over_mfg是多余的,而under_mfg是不足的:

for size in sizes:
    problem += over_mfg[size] >= lpSum([repetitions[setup.name] * getattr(setup, size) for setup in setups]) - requested[size], "DefineOverMfg" + size
    problem += under_mfg[size] >= requested[size] - lpSum([repetitions[setup.name] * getattr(setup, size) for setup in setups]), "DefineUnderMfg" + size

还要注意,我没有使用您的get_quantity_per_size()get_abs_diff()函数。 这些也将混淆pulp因为它不会意识到这些是简单的线性函数。

这是我完整的代码:

from collections import namedtuple

from pulp import *


class Setup(namedtuple('IAmReallyLazy', 'name ' + ' '.join(f's{s}' for s in range(36, 47)))):
    # inits with name and sizes 's36', 's37'... 's46'
    repetitions = 0


setups = [
    Setup('A1', 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0),
    Setup('A2', 0, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0),
    Setup('R7', 0, 0, 1, 1, 1, 1, 2, 0, 0, 0, 0),
    Setup('D1', 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
    # and others
]

setup_names = [s.name for s in setups]

min_size = 36
max_size = 46
sizes = ['s' + str(s) for s in range(min_size, max_size+1)]

requested = {
    's36': 100,
    's37': 250,
    's38': 300,
    's39': 450,
    's40': 450,
    's41': 250,
    's42': 200,
    's43': 0,    # I added these for completeness
    's44': 0,
    's45': 0,
    's46': 0
}

problem = LpProblem('Optimize Batches', LpMinimize)
# goal is to minimise the sum(get_abs_diff(f's{size}') for size in range(36, 47))
# variables are [setup.repetitions for setup in setups]
# constraints are all([isinstance(setup.repetitions, int) for setup in setups])

repetitions = LpVariable.dicts("repetitions", setup_names, 0, None, LpInteger)
over_mfg = LpVariable.dicts("over_mfg", sizes, 0, None, LpInteger)
under_mfg = LpVariable.dicts("under_mfg", sizes, 0, None, LpInteger)

problem += lpSum([over_mfg[size] + under_mfg[size] for size in sizes])

for size in sizes:
    problem += over_mfg[size] >= lpSum([repetitions[setup.name] * getattr(setup, size) for setup in setups]) - requested[size], "DefineOverMfg" + size
    problem += under_mfg[size] >= requested[size] - lpSum([repetitions[setup.name] * getattr(setup, size) for setup in setups]), "DefineUnderMfg" + size

# Solve problem
problem.solve()

# Print status
print("Status:", LpStatus[problem.status])

# Print optimal values of decision variables
for v in problem.variables():
    if v.varValue is not None and v.varValue > 0:
        print(v.name, "=", v.varValue)

这是输出:

Status: Optimal
over_mfg_s38 = 62.0
over_mfg_s41 = 62.0
repetitions_A1 = 25.0
repetitions_A2 = 88.0
repetitions_D1 = 110.0
repetitions_R7 = 1.0
under_mfg_s36 = 75.0
under_mfg_s37 = 2.0
under_mfg_s40 = 25.0

因此,我们制造25个重复的A1,A2的88个,D1的110个,R7的1个。 这将得到25个单位的s36 (因此在100个目标值之下为75个单位); 248个s37单位(目标2); 362个单位的s38 (超过300个目标的62个单位); 等等。


现在,你的约束,说你要么必须产生一个设置为0 > 9,你可以引入新的二元变量表示各设置是否产生:

is_produced = LpVariable.dicts("is_produced", setup_names, 0, 1, LpInteger)

然后添加以下约束:

M = 1000
min_reps = 9
for s in setup_names:
    problem += M * is_produced[s] >= repetitions[s] # if it's produced at all, must set is_produced = 1
    problem += min_reps * (1 - is_produced[s]) + repetitions[s] >= min_reps

M是个大数字; 它应大于最大可能的重复次数,但不大于。 并且我定义了min_reps以避免约束中的“硬编码” 9。 所以,这些约束说:(1)如果repetitions[s] > 0 ,则is_produced[s]必须等于1,和(2) 任一 is_produced[s] = 1个 repetitions[s] > 9。

输出:

Status: Optimal
is_produced_A1 = 1.0
is_produced_A2 = 1.0
is_produced_D1 = 1.0
over_mfg_s38 = 63.0
over_mfg_s39 = 1.0
over_mfg_s41 = 63.0
repetitions_A1 = 25.0
repetitions_A2 = 88.0
repetitions_D1 = 112.0
under_mfg_s36 = 75.0
under_mfg_s40 = 24.0

请注意,现在我们没有任何设置> 0但重复<9。


在理想世界中,如果有多个最佳解决方案,则应选择具有最大散布差异的一个(即,差异最小的一个)。

这个问题比较棘手,并且(至少现在),我将把它留给一个理想的世界,或者另一个人的答案。


顺便说一句 :我们正在努力启动一个用于Operations ResearchStack Exchange网站 ,我们将在这里解决诸如此类的问题。 如果您有兴趣,建议您点击链接并“提交”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM