项目-线性规划&Python矩阵求解,在供应链供需产能问题中的应用一.基础方法 1.线性规划 田忌赛马的故事大家都听过,
一.基础方法
1.线性规划
田忌赛马的故事大家都听过,这其实是古人(孙膑)在运筹学上的实践。实际业务场景中,线性规划的方法也有重要应用。
2.举个简单例子
(1)基本情况
目前想在上海工厂、湖州工厂、昆明工厂生产A、B两个产品。 已知A、B产品每批次的生产量为:
已知各个工厂生产A、B产品,每个批次所需要耗费的时长是:
基于现实问题,目前约束条件是:1.本月需求量,A至少100,B至少200。2.上海工厂最多只能投入30个时长,湖州工厂最多只能投入60个时长,昆明工厂最多只能投入80个时长。
另外,各个工厂生产A、B的成本是:
求解:需要生产多少批次?在哪个工厂生产?花费的费用最少?
(2)分析求解
假设生产批次为X,需要列矩阵的方式来求解各个工厂生产各个产品,则需要设6个X(X1,X2,X3,X4,X5,X6)。 我们需要列出aX>y的不等式,目前X设好了,就需要对照上面的已知条件,用列矩阵的方式,找出每个X的系数。 如图,按照产量+时长的约束条件,一一列好每个工厂生产A、B产品所需要的系数。
(3)列不等式
矩阵里面,每一行可以看作系数a,目标值的结果放在黄色位置,在excel里面可以用sumproduct作数组乘法运算。
可以利用WPS的规划求解来计算目标X.
(4) 结果
最终结果如下,各个工厂的建议生产批次
以上就是用Excel完成的简单线性规划问题的求解,实际上我们每个工厂需要生产的产品很多,那我们列的系数矩阵会很大(大概1000行*1000列),最后矩阵的列举与求解,也需要很大计算量。因此用Python去作计算。
3.实际业务需求与数据
3.1 业务背景
(1).有一份各个工厂、各产线能生产不同产品的产能表。
(2).已经根据目前订单情况,预测了未来三个月各个产品的缺口情况。
(3).根据这份需求缺口,需要求解出各个产品分别在哪个工厂生产,才能使生产成本最小。假设生产批次为X,A为每批次的产量,B为需求量,cost_xs为生产一个批次产品的成本系数。则需要计算满足不等式AX>=B(简化为,-AX<=-B)的情况下,使 生产成本=cost_xs*X 最小。
3.2 数据计算
(1) 读取数据
#引入包
import pandas as pd
import numpy as np
import pulp
import time
import sys
import datetime
from dateutil.relativedelta import relativedelta
#读取数据
path='C:/Users/****/案例/'
data=pd.read_excel(path+"数据集.xlsx", sheet_name='源数据集')
data_ys=pd.read_excel(path+"数据集.xlsx", sheet_name='约束')
基础数据是722*7列,各个工厂生产各个产品的产量信息、成本信息
(2) 按时间需求扩展行列
目前需求缺口的数据是9月,10月,11月的,因此需要对现有的生产数据进行扩展。
#分月标识赋值
a=[9.1, 9.2, 10.1, 10.2, 11.1, 11.2] #需要预测9月上下旬,10月上下旬,11月上下旬的生产批次
for i in range(len(a)):
data_dp.iloc[int((len(data_dp)/6)*i):int((len(data_dp)/6)*(i+1)),1]=a[i]
#月份赋值
data_dp["月份"]=data_dp["分月标识"].map(lambda x:round(x))
data_dp["矩阵类型"]="单品生产矩阵"
纵向扩展后的数据表如下
(3) 搭建生产矩阵的表头
##得出第一个矩阵,data_dp
##第二个转置矩阵
data_zz=data_dp.T.reset_index(drop=True)
data_zz
需要扩展行列都是4332*4332的矩阵。
#创建初始大矩阵函数
#第一个参数为:转置矩阵 第二个参数为:原矩阵
def cjjz(dataframe_1,dataframe_2): #(data_zz,data_dp)
#创建一个数据表
li = dataframe_2.columns.tolist()
data_jiu=pd.DataFrame(0,index=li,columns=li).reset_index(drop=True)
#左右连接
data_jiu=pd.concat([data_jiu,data_zz],axis=1).reset_index(drop=True)
#上下连接
data_jiu=pd.concat([data_jiu,data_dp],axis=0).reset_index(drop=True)
#重置列名
data_jiu.columns=list(range(data_jiu.shape[1]))
#矩阵内容填充为0
data_jiu.iloc[dataframe_1.shape[0]:,dataframe_2.shape[1]:]=0
return data_jiu
接下来创建初始矩阵。
#调用函数,创建初始矩阵
data_xin=cjjz(data_zz,data_dp)
data_xin.iloc[8:,8:]=data_xin.iloc[8:,8:].astype(int)
data_xin1=np.array(data_xin)
data_xin2=np.array(data_xin)
最终搭建的初始矩阵如图。
(4) 求生产矩阵的系数A
生产矩阵的系数计算,就是当行列的产品标识信息,月份信息相等时,才有产量/批次信息。另外,np.array的数值类型的计算速度要比dataFrame快很多,所以用np.array的数据类型作计算。
for i in data_xin.index.tolist(): # 遍历han
for j in data_xin.columns.tolist(): # 遍历lie,columns
# 假设列和行索引都是基于1开始的,因此需要减去1来获取正确的索引位置
if (data_xin1[i][2]<=data_xin1[2][j]) and (data_xin1[i][5]==data_xin1[5][j]): #横着月份小于等于竖着&产品代码相等
# 如果月份和产品代码相等,取第8列的值(列索引为7),并取负数
data_xin2[i][j]=data_xin[8][j]*(-1)
else:
# 如果不匹配,将当前位置的值设置为0
data_xin2[i][j]=0
将计算结果赋值给原有的矩阵。
##赋值刚才求解的数
data_xin.iloc[10:,10:]=pd.DataFrame(data_xin2).iloc[10:,10:]
(5) 需求缺口矩阵B
将每种物料的需求缺口匹配进来。
#匹配每一行物料代码的需求缺口
data_ys_a = pd.merge(data_xin.iloc[:,:10],data_ys, how='left',left_on=[2,5],right_on=['月份','产品编码'])
(6) 整理列不等式的数据
目前需求缺口矩阵、成本系数矩阵、系数A都有了。
将数据列转换成np.array的数据格式,以供后续进行计算。
# 使用pulp,求最小值
prob = pulp.LpProblem("min_zhi", sense=pulp.LpMinimize)
#数据准备,系数A,B A*X<=B
A = np.array(data_xin.iloc[10:,10:]) ##(-A*X)<=(-B), 系数A
B = np.array(data_ys_a.iloc[10:,15:])
cost_xs = np.array(data_xin.iloc[10:,9]) ##成本=cost_xs*X , 系数cost_xs
#给B降维
B=B.flatten()#需求缺口
#构建X
var=[]
for i in range(len(data_ys_a.iloc[10:,15:].index)):#遍历行
var.append(pulp.LpVariable(f'x{i}', lowBound=0,cat='Integer'))
var_sz=np.array(var)
最终的数据列如下。
(7) 列不等式,求解
遍历数据行,让每一行的产量>缺口,并保证目标函数成本最小。
#目标函数 lpDot可以将两个列表的对应位相乘再相加
prob += pulp.lpDot(cost_xs,var_sz) ##所有工厂生产的成本*批次之和最小
#计算式1,产能
jss_1=np.dot(A,var_sz)
for i in range(jss_1.shape[0]):
prob+=jss_1[i]<=B[i]
# 求解 使用默认的CBC求解器求解问题
prob.solve(pulp.PULP_CBC_CMD(timeLimit=600))
print(f'优化结果:{pulp.value(prob.objective)}')
最终的优化的目标函数最小值
(8) 导出X,即每个产品的最小生产批次
#计算结果
x_sx=[]
x_xsz=[]
for v in prob.variables():
x_sx.append(v.name)
x_xsz.append(v.varValue)
#列表转为dataframe
test = pd.DataFrame({'x顺序':x_sx,'x值':x_xsz})
#修改字符类型
test["x顺序"]= test["x顺序"].astype('string')
#创建未知数表
shuchu=pd.DataFrame(data=var,columns=["x顺序"],dtype=str)
#连接表
data_aa=pd.merge(shuchu,test,how="left",left_on=['x顺序'],right_on=['x顺序'])
#最初数据源与结果拼接
data_result=pd.concat([data_dp,data_aa],axis=1)
最终的计算结果如下。各个工厂、各个月份需要生产的产品批次数。
4.结语
以上是个简单案例,实际业务情况会更复杂,会涉及更多数据清洗、列更多的不等式,拼接更多矩阵以计算系数。
转载自:https://juejin.cn/post/7405503376698228747