运输问题的表上作业法(二):利用位势法判断当前解的最优性

上期谈到用Python实现求解运输问题 (Transportation Problem, TP) 的表上作业法的第一步——利用Vogel法寻找初始基可行解:

运输问题的表上作业法(一):利用伏格尔 (Vogel) 法寻找初始基可行解

这期来讲讲找到初始基可行解之后怎样判断当前解是否是最优解。如果当前解已达到最优,那么无需再进行操作;如果当前解非最优,那么还要对当前解进行调整以达到最优。调整解的操作放到下一期再讲,本期先谈谈怎样判断最优性。

文章目录

  • 位势法(对偶变量法)
  • 位势法应用实例
  • 位势法的Python语句

通常判断TP问题解的最优性有两种方法:闭回路法位势法。考虑到两种方法代码的复杂程度,我们重点讲讲更容易实现的位势法。位势法(Potentials)又称对偶变量法,是利用线性规划的对偶原理计算解的检验数,从而通过检验数判断最优性的方法。关于对偶原理,请参考运筹学相关书籍,这里不进行阐述,而只对位势法原理进行简单介绍。

位势法(对偶变量法)

位势法原理
位势法原理
位势法原理
位势法原理

位势法应用实例

下面通过一个运输问题的例题进一步说明位势法原理。
Example:
已知该TP问题的运输(成本)表如下(将中间3x4的成本表记为表1):
运输表

  1. 通过最小元素法找到一组初始基可行解:
    初始基可行解
  2. 把初始基可行解中非零元素对应位置的成本系数剥离出来,并添加上位势(图中u1-u3、v1-v4),记为表2:
    成本表
  3. 构建位势方程组(需要注意的是,TP问题的可行解中非零变量个数为m+n-1,其中m、n分别是成本矩阵的行数和列数(本例中m=3,n=4),所以位势方程组应该含有m+n-1个方程;但是位势的个数为m+n,即方程组有m+n个未知量,多于方程的个数,可知方程组是解不出来的,因此我们随便令某个位势=0,即可求解这个方程组,并且此操作对最后检验数无影响):
    位势方程组
  4. 求出位势后,将ui+vj填入成本表中对应解变量为0的位置,记为表3:
    填入位势的成本表
  5. 表1-表3(σij=cij-(ui+vj),对应位置元素相减),得到检验数表σ:
    检验数表
  6. 判断最优性:由于本例是极小化问题,如果检验数表中存在σij<0,说明当前解未达到最优,需要对解进行进一步的调整;否则,当前解达到最优(本例中当前初始基可行解并非最优解,因为σ24=-1<0)。如果采用的检验数σij=(ui+vj)-cij,则全部反向判断即可。

位势法的Python语句

同样使用上面的例题,但在寻找初始基可行解时运用Vogel法,编制Python程序进行求解。
运输表

首先将运输表写入EXCEL表格并保存,文件名为TP_PPT_Sample1.xlsx:
运输表的EXCEL表格
然后执行以下代码:

#Vogel法寻找初始基可行解+位势法判断解的最优性
import numpy as np
import copy
import pandas as pddef TP_split_matrix(mat):c=mat[:-1,:-1]a=mat[:-1,-1]b=mat[-1,:-1]return (c,a,b)def TP_vogel(var): #Vogel法代码,变量var可以是以numpy.ndarray保存的运输表,或以tuple或list保存的(成本矩阵,供给向量,需求向量)import numpytypevar1=type(var)==numpy.ndarraytypevar2=type(var)==tupletypevar3=type(var)==listif typevar1==False and typevar2==False and typevar3==False:print('>>>非法变量<<<')(cost,x)=(None,None)else:if typevar1==True:[c,a,b]=TP_split_matrix(var)elif typevar2==True or typevar3==True:[c,a,b]=varcost=copy.deepcopy(c)x=np.zeros(c.shape)M=pow(10,9)for factor in c.reshape(1,-1)[0]:while int(factor)!=M:if np.all(c==M):breakelse:print('c:\n',c)#获取行/列最小值数组row_mini1=[]row_mini2=[]for row in range(c.shape[0]):Row=list(c[row,:])row_min=min(Row)row_mini1.append(row_min)Row.remove(row_min)row_2nd_min=min(Row)row_mini2.append(row_2nd_min)#print(row_mini1,'\n',row_mini2)r_pun=[row_mini2[i]-row_mini1[i] for i in range(len(row_mini1))]print('行罚数:',r_pun)#计算列罚数col_mini1=[]col_mini2=[]for col in range(c.shape[1]):Col=list(c[:,col])col_min=min(Col)col_mini1.append(col_min)Col.remove(col_min)col_2nd_min=min(Col)col_mini2.append(col_2nd_min)c_pun=[col_mini2[i]-col_mini1[i] for i in range(len(col_mini1))]print('列罚数:',c_pun)pun=copy.deepcopy(r_pun)pun.extend(c_pun)print('罚数向量:',pun)max_pun=max(pun)max_pun_index=pun.index(max(pun))max_pun_num=max_pun_index+1print('最大罚数:',max_pun,'元素序号:',max_pun_num)if max_pun_num<=len(r_pun):row_num=max_pun_numprint('对第',row_num,'行进行操作:')row_index=row_num-1catch_row=c[row_index,:]print(catch_row)min_cost_colindex=int(np.argwhere(catch_row==min(catch_row)))print('最小成本所在列索引:',min_cost_colindex)if a[row_index]<=b[min_cost_colindex]:x[row_index,min_cost_colindex]=a[row_index]c1=copy.deepcopy(c)c1[row_index,:]=[M]*c1.shape[1]b[min_cost_colindex]-=a[row_index]a[row_index]-=a[row_index]else:x[row_index,min_cost_colindex]=b[min_cost_colindex]c1=copy.deepcopy(c)c1[:,min_cost_colindex]=[M]*c1.shape[0]a[row_index]-=b[min_cost_colindex]b[min_cost_colindex]-=b[min_cost_colindex]else:col_num=max_pun_num-len(r_pun)col_index=col_num-1print('对第',col_num,'列进行操作:')catch_col=c[:,col_index]print(catch_col)#寻找最大罚数所在行/列的最小成本系数min_cost_rowindex=int(np.argwhere(catch_col==min(catch_col)))print('最小成本所在行索引:',min_cost_rowindex)#计算将该位置应填入x矩阵的数值(a,b中较小值)if a[min_cost_rowindex]<=b[col_index]:x[min_cost_rowindex,col_index]=a[min_cost_rowindex]c1=copy.deepcopy(c)c1[min_cost_rowindex,:]=[M]*c1.shape[1]b[col_index]-=a[min_cost_rowindex]a[min_cost_rowindex]-=a[min_cost_rowindex]else:x[min_cost_rowindex,col_index]=b[col_index]#填入后删除已满足/耗尽资源系数的行/列,得到剩余的成本矩阵,并改写资源系数c1=copy.deepcopy(c)c1[:,col_index]=[M]*c1.shape[0]a[min_cost_rowindex]-=b[col_index]b[col_index]-=b[col_index]c=c1print('本次迭代后的x矩阵:\n',x)print('a:',a)print('b:',b)print('c:\n',c)if np.all(c==M):print('【迭代完成】')print('-'*60)else:print('【迭代未完成】')print('-'*60)total_cost=np.sum(np.multiply(x,cost))if np.all(a==0):if np.all(b==0):print('>>>供求平衡<<<')else:print('>>>供不应求,需求方有余量<<<')elif np.all(b==0):print('>>>供大于求,供给方有余量<<<')else:print('>>>无法找到初始基可行解<<<')print('>>>初始基本可行解x*:\n',x)print('>>>当前总成本:',total_cost)[m,n]=x.shapevarnum=np.array(np.nonzero(x)).shape[1]if varnum!=m+n-1:print('【注意:问题含有退化解】')return (cost,x)def create_c_nonzeros(c,x):import numpy as npimport copynonzeros=copy.deepcopy(x)for i in range(x.shape[0]):for j in range(x.shape[1]):if x[i,j]!=0:nonzeros[i,j]=1#print(nonzeros)c_nonzeros=np.multiply(c,nonzeros)return c_nonzerosdef if_dsquare(a,b):print('a:',a.shape,'\n','b:',b.shape)correct='>>>位势方程组可解<<<'error='>>>位势方程组不可解,请检查基变量个数是否等于(m+n-1)及位势未知量个数是否等于(m+n)<<<'if len(a.shape)==2:if len(b.shape)==2:if a.shape[0]==a.shape[1] and a.shape==b.shape:print(correct)if_dsquare=Trueelse:print(error)if_dsquare=Falseelif len(b.shape)==1 and b.shape[0]!=0:if a.shape[0]==a.shape[1] and a.shape[0]==b.shape[0]:print(correct)if_dsquare=Trueelse:print(error)if_dsquare=Falseelse:print(error)if_dsquare=Falseelif len(a.shape)==1:if len(b.shape)==2:if b.shape[0]==b.shape[1] and a.shape[0]==b.shape[0]:print('>>>位势方程组系数矩阵与方程组值向量位置错误<<<')if_dsquare='True but not solvable'else:print(error)if_dsquare=Falseelif len(b.shape)==1:print(error)if_dsquare=Falseelse:print(error)if_dsquare=Falseelse:print(error)if_dsquare=Falsereturn if_dsquaredef TP_potential(cost,x):[m,n]=x.shapevarnum=np.array(np.nonzero(x)).shape[1]if varnum!=m+n-1:sigma=Noneprint('【问题含有退化解,暂时无法判断最优性】')else:#print(c_nonzeros.shape)c_nonzeros=create_c_nonzeros(c,x)cc_nonzeros=np.array(np.nonzero(c_nonzeros))A=[]B=[]length=c_nonzeros.shape[0]+c_nonzeros.shape[1]zeros=np.zeros((1,length))[0]for i in range(cc_nonzeros.shape[1]):zeros=np.zeros((1,length))[0]zeros[cc_nonzeros[0,i]]=1zeros[cc_nonzeros[1,i]+c_nonzeros.shape[0]]=1A.append(zeros)B.append(c_nonzeros[cc_nonzeros[0,i],cc_nonzeros[1,i]])l=[1]for j in range(length-1):l.append(0) #补充一个x1=0的方程以满足求解条件A.append(l)B.append(0)#print(A)#print(B)A=np.array(A)B=np.array(B)judge=if_dsquare(A,B)if judge==True:sol=np.linalg.solve(A,B) #求解条件:A的行数(方程个数)=A的列数(变量个数)=B的个数(方程结果个数)才能解#print(sol)var=[] #创建位势名称数组for i in range(c_nonzeros.shape[0]):var.append('u'+str(i+1))for j in range(c_nonzeros.shape[1]):var.append('v'+str(j+1))#print(var)solset=dict(zip(var,sol))print('>>>当前位势:\n',solset)u=[]v=[][m,n]=c_nonzeros.shapezero_places=np.transpose(np.argwhere(c_nonzeros==0))for i in range(m):u.append(sol[i])for j in range(n):v.append(sol[j+m])for k in range(zero_places.shape[1]):c_nonzeros[zero_places[0,k],zero_places[1,k]]=u[zero_places[0,k]]+v[zero_places[1,k]]#print(c_nonzeros)sigma=cost-c_nonzerosprint('>>>检验表σ:\n',sigma)if np.all(sigma>=0):print('>>>TP已达到最优解<<<')else:print('>>>TP未达到最优解<<<')else:sigma=Noneprint('>>>位势方程组不可解<<<')return sigmapath=r'C:\Users\spurs\Desktop\MCM_ICM\Data files\TP_PPT_Sample1.xlsx'
mat=pd.read_excel(path,header=None).values
#c=np.array([[3,11,3,10],[1,9,2,8],[7,4,10,5]])
#a=np.array([7,4,9])
#b=np.array([3,6,5,6])
[c,x]=TP_vogel(mat)
#[c,x]=TP_vogel([c,a,b])
sigma=TP_potential(c,x)

运行结果:

c:[[ 3. 11.  3. 10.][ 1.  9.  2.  8.][ 7.  4. 10.  5.]]
行罚数: [0.0, 1.0, 1.0]
列罚数: [2.0, 5.0, 1.0, 3.0]
罚数向量: [0.0, 1.0, 1.0, 2.0, 5.0, 1.0, 3.0]
最大罚数: 5.0 元素序号: 5
对第 2 列进行操作:
[11.  9.  4.]
最小成本所在行索引: 2
本次迭代后的x矩阵:[[0. 0. 0. 0.][0. 0. 0. 0.][0. 6. 0. 0.]]
a: [7. 4. 3.]
b: [3. 0. 5. 6.]
c:[[3.e+00 1.e+09 3.e+00 1.e+01][1.e+00 1.e+09 2.e+00 8.e+00][7.e+00 1.e+09 1.e+01 5.e+00]]
【迭代未完成】
------------------------------------------------------------
c:[[3.e+00 1.e+09 3.e+00 1.e+01][1.e+00 1.e+09 2.e+00 8.e+00][7.e+00 1.e+09 1.e+01 5.e+00]]
行罚数: [0.0, 1.0, 2.0]
列罚数: [2.0, 0.0, 1.0, 3.0]
罚数向量: [0.0, 1.0, 2.0, 2.0, 0.0, 1.0, 3.0]
最大罚数: 3.0 元素序号: 7
对第 4 列进行操作:
[10.  8.  5.]
最小成本所在行索引: 2
本次迭代后的x矩阵:[[0. 0. 0. 0.][0. 0. 0. 0.][0. 6. 0. 3.]]
a: [7. 4. 0.]
b: [3. 0. 5. 3.]
c:[[3.e+00 1.e+09 3.e+00 1.e+01][1.e+00 1.e+09 2.e+00 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
【迭代未完成】
------------------------------------------------------------
c:[[3.e+00 1.e+09 3.e+00 1.e+01][1.e+00 1.e+09 2.e+00 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
行罚数: [0.0, 1.0, 0.0]
列罚数: [2.0, 0.0, 1.0, 2.0]
罚数向量: [0.0, 1.0, 0.0, 2.0, 0.0, 1.0, 2.0]
最大罚数: 2.0 元素序号: 4
对第 1 列进行操作:
[3.e+00 1.e+00 1.e+09]
最小成本所在行索引: 1
本次迭代后的x矩阵:[[0. 0. 0. 0.][3. 0. 0. 0.][0. 6. 0. 3.]]
a: [7. 1. 0.]
b: [0. 0. 5. 3.]
c:[[1.e+09 1.e+09 3.e+00 1.e+01][1.e+09 1.e+09 2.e+00 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
【迭代未完成】
------------------------------------------------------------
c:[[1.e+09 1.e+09 3.e+00 1.e+01][1.e+09 1.e+09 2.e+00 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
行罚数: [7.0, 6.0, 0.0]
列罚数: [0.0, 0.0, 1.0, 2.0]
罚数向量: [7.0, 6.0, 0.0, 0.0, 0.0, 1.0, 2.0]
最大罚数: 7.0 元素序号: 1
对第 1 行进行操作:
[1.e+09 1.e+09 3.e+00 1.e+01]
最小成本所在列索引: 2
本次迭代后的x矩阵:[[0. 0. 5. 0.][3. 0. 0. 0.][0. 6. 0. 3.]]
a: [2. 1. 0.]
b: [0. 0. 0. 3.]
c:[[1.e+09 1.e+09 1.e+09 1.e+01][1.e+09 1.e+09 1.e+09 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
【迭代未完成】
------------------------------------------------------------
c:[[1.e+09 1.e+09 1.e+09 1.e+01][1.e+09 1.e+09 1.e+09 8.e+00][1.e+09 1.e+09 1.e+09 1.e+09]]
行罚数: [999999990.0, 999999992.0, 0.0]
列罚数: [0.0, 0.0, 0.0, 2.0]
罚数向量: [999999990.0, 999999992.0, 0.0, 0.0, 0.0, 0.0, 2.0]
最大罚数: 999999992.0 元素序号: 2
对第 2 行进行操作:
[1.e+09 1.e+09 1.e+09 8.e+00]
最小成本所在列索引: 3
本次迭代后的x矩阵:[[0. 0. 5. 0.][3. 0. 0. 1.][0. 6. 0. 3.]]
a: [2. 0. 0.]
b: [0. 0. 0. 2.]
c:[[1.e+09 1.e+09 1.e+09 1.e+01][1.e+09 1.e+09 1.e+09 1.e+09][1.e+09 1.e+09 1.e+09 1.e+09]]
【迭代未完成】
------------------------------------------------------------
c:[[1.e+09 1.e+09 1.e+09 1.e+01][1.e+09 1.e+09 1.e+09 1.e+09][1.e+09 1.e+09 1.e+09 1.e+09]]
行罚数: [999999990.0, 0.0, 0.0]
列罚数: [0.0, 0.0, 0.0, 999999990.0]
罚数向量: [999999990.0, 0.0, 0.0, 0.0, 0.0, 0.0, 999999990.0]
最大罚数: 999999990.0 元素序号: 1
对第 1 行进行操作:
[1.e+09 1.e+09 1.e+09 1.e+01]
最小成本所在列索引: 3
本次迭代后的x矩阵:[[0. 0. 5. 2.][3. 0. 0. 1.][0. 6. 0. 3.]]
a: [0. 0. 0.]
b: [0. 0. 0. 0.]
c:[[1.e+09 1.e+09 1.e+09 1.e+09][1.e+09 1.e+09 1.e+09 1.e+09][1.e+09 1.e+09 1.e+09 1.e+09]]
【迭代完成】
------------------------------------------------------------
>>>供求平衡<<<
>>>初始基本可行解x*[[0. 0. 5. 2.][3. 0. 0. 1.][0. 6. 0. 3.]]
>>>当前总成本: 85.0
>>>位势方程组可解<<<
>>>当前位势:{'u1': 0.0, 'u2': -2.0, 'u3': -5.0, 'v1': 3.0, 'v2': 9.0, 'v3': 3.0, 'v4': 10.0}
>>>检验数表σ:[[ 0.  2.  0.  0.][ 0.  2.  1.  0.][ 9.  0. 12.  0.]]
>>>TP已达到最优解<<<

可见,这次我们用Vogel法找到的初始基可行解已经是最优解(检验数表σ中元素均大于0),再次证明Vogel法比最小元素法求解效率更高。

对于存在退化现象的问题,由于笔者能力原因,暂时没有找到最优解决方案,因此程序会提示暂时无法判断最优性:

Example:

退化解案例
Result:

>>>供求平衡<<<
>>>初始基本可行解x*[[1. 0. 0. 0. 0.][0. 0. 1. 0. 0.][0. 0. 0. 1. 0.][0. 0. 0. 0. 1.][0. 1. 0. 0. 0.]]
>>>当前总成本: 0.8999999999999999
【注意:问题含有退化解】
【问题含有退化解,暂时无法判断最优性】

对于此类问题的解决方案笔者会在将来进行补充。

函数使用方法:
TP_potential (c,x): c是成本矩阵,x是当前可行解矩阵。
本函数返回的是当前解的检验数矩阵。

感谢阅读,欢迎指正。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部