有n种被切割的原材料,需要切割成m种不同规格的成品,需求方对不同规格的成品有不同的需求,如10mm的需要10根,30mm的需要20根等等,这里我们就需要针对不同的情况进行讨论。
这里我主要针对几种常见的下料问题进行分析并给出相应的数学模型和python代码,由于这里的计算只是通过python的相关库进行计算,并没有涉及到算法时间度的优化,所以针对后续的几种更加困难的情况,读者可以根据此文章的分析进行深入研究。
这里所有的原料尺寸为n,所需成品数的尺寸为5种,分别为L1,L2,L3,L4,L5,目标函数暂定为考虑总的余料损失最少。
分析: 确定决策变量和目标函数,目标函数题目已经给出,决策变量为一根尺寸为n的原料切割成成品的切割种数,如20mm的原料切割成5mm,8mm,10mm,11mm,13mm原料的切割种数为9种,但如果是换成30mm的原料切割的话,则切割的种数有23种。
模型建立:
决策变量:不同切割方式所用原料的根数: x i x_i xi
目标函数系数:不同切割方式所剩的余料数: c i c_i ci( c i c_i ci
一家船舶零件加工厂需要加工5种不同尺寸的零件,其尺寸分别为5404,2000,1740,1350,1200(单位:mm),目前加工厂暂时只有长度为6000mm的原材料。现船舶公司需要这五种尺寸的数量分别为:124,135,45,100,50(单位:根),加工厂即要满足船舶公司的需求,又要保证损失的原料最少。
问题分析:
解决该问题需要最关键的两步,第一步求出各种切割方式所能切割出的成品数量和每种切割方式所产生的余料损失,第二步调用简单的模型求解该线性优化问题。
第一步:
求出不同的切割方式所能切割出不同成品的数量,可以参照如下代码执行
print("---------------------------")
print("n={}".format(n))
cnt = 0
for fen5404 in range(n // 5404, -1, -1):
for fen2000 in range(n // 2000, -1, -1):
for fen1740 in range(n // 1740, -1, -1):
for fen1350 in range(n // 1350, -1, -1):
for fen1200 in range(n // 1200, -1, -1):
h = fen5404 * 5404 + fen2000 * 2000 + fen1740 * 1740 + fen1350 * 1350 + fen1200 * 1200
if (n - h < 1200 and n - h > -1):
print(
'fen5404:{:d}, fen2000:{:d}, fen1740:{:d}, fen1350:{:d}, fen1200:{:d}, 余料:{:d}, total:{:d}'.format(
fen5404, fen2000, fen1740, fen1350, fen1200, n - h,
fen5404 + fen2000 + fen1740 + fen1350 + fen1200))
cnt = cnt + 1
print('count = {:d}'.format(cnt))
可以求出23种不同的切割方案,每种切割方案的余料也能相应求出,如下表所示
fen5404 | fen2000 | fen1740 | fen1350 | fen1200 | 余料 | |
---|---|---|---|---|---|---|
x1 | 1 | 0 | 0 | 0 | 0 | 596 |
x2 | 0 | 3 | 0 | 0 | 0 | 0 |
x3 | 0 | 2 | 1 | 0 | 0 | 260 |
x4 | 0 | 2 | 0 | 1 | 0 | 650 |
x5 | 0 | 2 | 0 | 0 | 1 | 800 |
x6 | 0 | 1 | 2 | 0 | 0 | 520 |
x7 | 0 | 1 | 1 | 1 | 0 | 910 |
x8 | 0 | 1 | 1 | 0 | 1 | 1060 |
x9 | 0 | 1 | 0 | 2 | 1 | 100 |
x10 | 0 | 1 | 0 | 1 | 2 | 250 |
x11 | 0 | 1 | 0 | 0 | 3 | 400 |
x12 | 0 | 0 | 3 | 0 | 0 | 780 |
x13 | 0 | 0 | 2 | 1 | 0 | 1170 |
x14 | 0 | 0 | 2 | 0 | 2 | 120 |
x15 | 0 | 0 | 1 | 3 | 0 | 210 |
x16 | 0 | 0 | 1 | 2 | 1 | 360 |
x17 | 0 | 0 | 1 | 1 | 2 | 510 |
x18 | 0 | 0 | 1 | 0 | 3 | 660 |
x19 | 0 | 0 | 0 | 4 | 0 | 600 |
x20 | 0 | 0 | 0 | 3 | 1 | 750 |
x21 | 0 | 0 | 0 | 2 | 2 | 900 |
x22 | 0 | 0 | 0 | 1 | 3 | 1050 |
x23 | 0 | 0 | 0 | 0 | 5 | 0 |
可以将该例子的数学模型写成矩阵形式 A = − [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 2 1 1 0 0 0 3 2 2 1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 2 1 0 0 1 0 3 2 1 0 4 3 2 1 0 0 0 0 0 1 0 0 1 1 2 3 0 0 2 0 1 2 3 0 1 2 3 5 ] b = [ − 124 − 135 − 45 − 100 − 50 ] T c = [ 596 0 260 650 800 520 910 1060 100 250 400 780 1170 120 210 360 510 660 600 750 900 1050 0 ] x = [ x 1 , x 2 , . . . , x 23 ] T A=-\begin{bmatrix} 1&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0\\0&3&2&2&2&1&1&1&1&1&1&0&0&0&0&0&0&0&0&0&0&0&0\\0&0&1&0&0&2&1&1&0&0&0&3&2&2&1&1&1&1&0&0&0&0&0\\0&0&0&1&0&0&1&0&2&1&0&0&1&0&3&2&1&0&4&3&2&1&0\\0&0&0&0&1&0&0&1&1&2&3&0&0&2&0&1&2&3&0&1&2&3&5\\ \end{bmatrix} \\\\\\b=\begin{bmatrix}-124&-135&-45&-100&-50\end{bmatrix}^T\\c=\begin{bmatrix}596&0&260&650&800&520&910&1060&100&250&400&780&1170&120&210&360&510&660&600&750&900&1050&0 \end{bmatrix} \\ x=[x_1,x_2,...,x_{23}]^T A=− 1000003000021000201002001012000111001101010210101201003003000021000202001300012100112001030004000031000220001300005 b=[−124−135−45−100−50]Tc=[59602606508005209101060100250400780117012021036051066060075090010500]x=[x1,x2,...,x23]T
由于在代码中求解线性规划中约束条件的标准为“≤”,所以我们将约束系数矩阵和右端值改为负,不过此修改对问题求解没有太大影响。
这里针对模型的求解可以调用python程序进行
import cvxpy as cp
from numpy import array
optimal_min = 100000000
z = [6000,]
l = [[], [], [], [], []] # 约束系数矩阵
c = [] # 目标函数系数矩阵
b = [-124, -135, -45, -100, -50] #各成品的需求量,为了满足python中标准线性模型求解,全部置为负数
#求出每种原料用于切割成成品的不同切割种数
for n in z:
# print("---------------------------")
# print("n={}".format(n))
# cnt = 0
for fen5404 in range(n // 5404, -1, -1):
for fen2000 in range(n // 2000, -1, -1):
for fen1740 in range(n // 1740, -1, -1):
for fen1350 in range(n // 1350, -1, -1):
for fen1200 in range(n // 1200, -1, -1):
h = fen5404 * 5404 + fen2000 * 2000 + fen1740 * 1740 + fen1350 * 1350 + fen1200 * 1200
if (n - h < 1200 and n - h > -1):
l[0].append(-fen5404)
l[1].append(-fen2000)
l[2].append(-fen1740)
l[3].append(-fen1350)
l[4].append(-fen1200)
c.append(n - h)
# print('fen5404:{:d}, fen2000:{:d}, fen1740:{:d}, fen1350:{:d}, fen1200:{:d}, 余料:{:d}, total:{:d}'.format(fen5404, fen2000, fen1740, fen1350, fen1200, n - h,fen5404 + fen2000 + fen1740 + fen1350 + fen1200))
# cnt = cnt + 1
# print('count = {:d}'.format(cnt))
norma1_c = c
c = array(c) # 定义目标向量
a = array(l) # 定义约束矩阵
b = array(b) # 定义约束条件的右边向量
x = cp.Variable(len(c), integer=True) # 定义两个整数决策变量
obj = cp.Minimize(c @ x) # 构造目标函数
cons = [a @ x <= b, x >= 0] # 构造约束条件
prob = cp.Problem(obj, cons) # 构建问题模型
prob.solve(solver='GLPK_MI', verbose=True) # 求解问题
# print("最优值为:", prob.value)
# print("最优解为:\n", x.value)
min_min = prob.value
optimal_min = min_min
optimal_x = x.value
optimal_class = z
optimal_fenge = l
normal_c = norma1_c
print('---------------------------------------------------------------------------------------------------------------')
print('耗费的材料最少={:}'.format(optimal_min))
print('分割种类最优解={:}'.format(optimal_x))
#print('原材料规格种数为{:d}时,最优的切割规格为:'.format(2), end=' ')
#print(optimal_class)
'''print('模型最优解为时的约束系数矩阵:')
for i in range(len(optimal_fenge)):
for j in range(len(optimal_x)):
optimal_fenge[i][j] = -int(optimal_fenge[i][j])
print(optimal_fenge[i])'''
for i in range(len(optimal_x)):
if (optimal_x[i] > 0):
m = int(optimal_fenge[0][i]) * 5404 + int(optimal_fenge[1][i]) * 2000 + int(optimal_fenge[2][i]) * 1740 + int(
optimal_fenge[3][i]) * 1350 + int(optimal_fenge[4][i]) * 1200 + int(normal_c[i])
print('将{:d}根{:d}的原料切割成如下:'.format(int(optimal_x[i]), n))
print(
'fen5404:{:d}, fen2000:{:d}, fen1740:{:d}, fen1350:{:d}, fen1200:{:d}, 余料:{:d},'.format(optimal_fenge[0][i],
optimal_fenge[1][i],
optimal_fenge[2][i],
optimal_fenge[3][i],
optimal_fenge[4][i],
normal_c[i]))
上述基本就完成简单的下料问题求解
再进一步我们考虑若可以由2种不同长度的原材料组成,原材料长度为100的倍数,介于6000与8000mm之间,那么是否会减少损耗呢,这里可以通过下面的代码进行验证
import cvxpy as cp
from numpy import array
optimal_min = 100000000
n = []
z = []
z=[]
m=0
for i in range(6000,8000,100):
for j in range(6000,8000,100):
if i!=j:
z.append([])
z[m].append(i)
z[m].append(j)
m+=1
for u in range(0, len(z), 1):
l = [[], [], [], [], []] # 约束系数矩阵
c = [] # 目标函数系数矩阵
b = [-124, -135, -45, -100, -50]
for n in z[u]:
for fen5404 in range(n // 5404, -1, -1):
for fen2000 in range(n // 2000, -1, -1):
for fen1740 in range(n // 1740, -1, -1):
for fen1350 in range(n // 1350, -1, -1):
for fen1200 in range(n // 1200, -1, -1):
h = fen5404 * 5404 + fen2000 * 2000 + fen1740 * 1740 + fen1350 * 1350 + fen1200 * 1200
if (n - h < 1200 and n - h > -1):
l[0].append(-fen5404)#将每种切割方案可以产生5404mm规格零件的数量存入l[0]
l[1].append(-fen2000)#将每种切割方案可以产生2000mm规格零件的数量存入l[1]
l[2].append(-fen1740)#将每种切割方案可以产生1740mm规格零件的数量存入l[2]
l[3].append(-fen1350)#将每种切割方案可以产生1350mm规格零件的数量存入l[3]
l[4].append(-fen1200)#将每种切割方案可以产生1200mm规格零件的数量存入l[4]
c.append(n - h)#将每种切割方案所产生的余料存入列表c中,方便后续转换成目标函数中的价值系数向量
norma1_c = c
c = array(c) # 定义目标向量
a = array(l) # 定义约束矩阵
b = array(b) # 定义约束条件的右边向量
x = cp.Variable(len(c), integer=True) # 定义两个整数决策变量
obj = cp.Minimize(c @ x) # 构造目标函数
cons = [a @ x <= b, x >= 0] # 构造约束条件
prob = cp.Problem(obj, cons) # 构建问题模型
prob.solve(solver='GLPK_MI', verbose=True) # 求解问题
min_min = prob.value
if (min_min < optimal_min):
optimal_min = min_min
optimal_x = x.value
optimal_class = z[u]
optimal_fenge = l
normal_c = norma1_c
del c
del l
del b
for n in optimal_class:
print("####################不同规格原材料的切割方式可行方案#############################################")
print("n={}".format(n))
cnt = 0
for fen5404 in range(n // 5404, -1, -1):
for fen2000 in range(n // 2000, -1, -1):
for fen1740 in range(n // 1740, -1, -1):
for fen1350 in range(n // 1350, -1, -1):
for fen1200 in range(n // 1200, -1, -1):
h = fen5404 * 5404 + fen2000 * 2000 + fen1740 * 1740 + fen1350 * 1350 + fen1200 * 1200
if (n - h < 1200 and n - h > -1):
print(
'fen5404:{:d}, fen2000:{:d}, fen1740:{:d}, fen1350:{:d}, fen1200:{:d}, 余料:{:d}, total:{:d}'.format(
fen5404, fen2000, fen1740, fen1350, fen1200, n - h,
fen5404 + fen2000 + fen1740 + fen1350 + fen1200))
cnt = cnt + 1
print('count = {:d}'.format(cnt))
print('################################最优方案详细###########################################'.format(optimal_min))
print('耗费的材料最少={:}'.format(optimal_min))
print('原材料规格种数为{:d}时,最优的切割规格为:'.format(2), end=' ')
print(optimal_class)
for i in range(len(optimal_x)):
if (optimal_x[i] > 0):
m = int(-optimal_fenge[0][i]) * 5404 + int(-optimal_fenge[1][i]) * 2000 + int(-optimal_fenge[2][i]) * 1740 + int(
-optimal_fenge[3][i]) * 1350 + int(-optimal_fenge[4][i]) * 1200 + int(normal_c[i])
print('将{:d}根{:d}的原料切割成如下:'.format(int(optimal_x[i]), m))
print(
'fen5404:{:d}, fen2000:{:d}, fen1740:{:d}, fen1350:{:d}, fen1200:{:d}, 余料:{:d},'.format(-optimal_fenge[0][i],
-optimal_fenge[1][i],
-optimal_fenge[2][i],
-optimal_fenge[3][i],
-optimal_fenge[4][i],
normal_c[i]))
是通过求解,可以求出选择6300,6800同时切割时,可以使余料的浪费降低。
代码块都是自己根据一个案例编写,针对下料问题本文分析到此,读者可以继续深入研究,寻找更多种原料组合在一起时会不会使原料的浪费更少,同时目标函数也可以换一下,可以考虑不同规格原料的成本,余料所产生的剩余价值,将会使最优方案产生怎样的变化,都是可以继续研究的问题。