运输问题和指派问题——Python实现

随着社会和经济的不断进步,现代物流业蓬勃发展,如何充分利用时间、信息、仓储、配送和联运体系创造更多的价值,是物流运作必须解决的问题。日益复杂的运输活动使得运输问题变得越来越庞杂,但是其核心思想仍然是实现现有资源的最优化配置。运输问题经常出现在计划货物配送和从某些供给地区到达需求地区之间的服务中,特别是每个供给地区(起点)的货物可获得量是有限的,每个需求地区(目的地)的货物需求量是已知的,运输问题中最常用的目标是要使货物从起点到目的地的运输成本最低。指派问题是运输问题的一种特殊情况,通常涉及到多个任务和多个执行者之间的任务分配,如将工作分配给机器,对代理分配任务,将销售人员分配给销售区域,将合同分配给投标人,等等。

一、运输问题

运输问题(transportation problem) 属于线性规划问题,可以根据模型按照线性规划的方式求解,但由于其特殊性,用常规的线性规划来求解并不是最有效的方法。lpSolve包提供了函数lp.transport() 来求解运输问题。

1.1运输问题的数学模型

一般地,产销平衡的运输问题可以表述为:设有m个地点(称为产地或发地)A1,A2,...,Am的某种物资调至n个地点(称为销地或收地)B1,B2,...,Bn,各个产地需要调出的物资量分别为ai单位,各个销地需要调进的物资量分别为bj 单位,且各个发点的供应量之和等于各个收点的需求量之和。已知每个发点Ai 到每个收点Bj 的物资单位调运价格为cij,现问如何安排调运,才能使总运费最小。
于是得产销平衡运输问题的数学模型:

minz = i=1mj=1ncijxij
j=1nxij=aii=1,2,...,m
i=1mxij=bjj=1,2,...,n
xij0i,j=1,2,...,n
不平衡运输问题只要简单地调整约束的松紧关系即得。

1.2 Python计算程序

例1: 有三个造纸厂A1、A2 和A3,造纸量分别为16 个单位、10 个单位和22 个单位,四个客户B1、B2、B3 和B4 的需求量分别为8 个单位、14 个单位、12 个单位和14 个单位。造纸厂到客户之间的单位运价如表所示,确定总运费最少的调运方案。单位运价表如下所示:

 [[ 4 12  4 11]
 [ 2 10  3  9]
 [ 8  5 11  6]]

总产量等于总销量,都为48个单位,这是一个产销平衡的运输问题。

Python代码及运行结果如下:

import pulp
import numpy as np
from pprint import pprint

def transportation_problem(costs, x_max, y_max):

    row = len(costs)
    col = len(costs[0])
    prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMinimize)
    var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
    flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
    prob += pulp.lpDot(flatten(var), costs.flatten())
    for i in range(row):
        prob += (pulp.lpSum(var[i]) == x_max[i])
    for j in range(col):
        prob += (pulp.lpSum([var[i][j] for i in range(row)]) == y_max[j])

    prob.solve()
    return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}

if __name__ == '__main__':
    costs = np.array([[4, 12, 4, 11],
                      [2, 10, 3, 9],
                      [8, 5, 11, 6]])
    max_plant = [16, 10, 22]
    max_cultivation = [8, 14, 12, 14]
    res = transportation_problem(costs, max_plant, max_cultivation)

    print(f'最小值为{res["objective"]}')
    print('各变量的取值为:')
    pprint(res['var'])

1.3 计算结果

最小值为244.0
各变量的取值为:
[[0.0, 0.0, 12.0, 4.0], [8.0, 0.0, 0.0, 2.0], [0.0, 14.0, 0.0, 8.0]]

输出结果表示问题成功解决,最少运费为244。运送方案为: A1→B3 : 12 个单位,A1→B4 : 4 个单位,A2→B1 :8 个单位,A2→B4 : 2 个单位,A3→B2 : 14 个单位,A3→B4 : 8个单位。

二、运输问题实例

例2:某制药公司在全国设有六个生产基地,这些生产基地每天将这些药分别运往八个地区的经销部门,已知从每个生产基地到各销售部门每箱药品的运价如下表所示,问该制药公司应如何调运(运输方案),使在满足各销售部门需要的情况下,总的运输费用最少?

cij B1 B2 B3 B4 B5 B6 B7 B8 产量ai
A1 6 2 6 7 4 2 5 9 60
A2 4 9 5 3 8 5 8 2 55
A3 5 2 1 9 7 4 3 3 51
A4 7 6 7 3 9 2 9 1 43
A5 2 3 9 5 7 2 6 5 41
A6 5 5 2 2 8 1 4 3 52
销量bj 35 37 22 32 41 32 43 38

该问题是供大于求的不平衡运输问题。

2.1 Python计算程序

import pulp
import numpy as np
from pprint import pprint

def transportation_problem(costs, x_max, y_max):

    row = len(costs)
    col = len(costs[0])
    prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMinimize)
    var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
    flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
    prob += pulp.lpDot(flatten(var), costs.flatten())
    for i in range(row):
        prob += (pulp.lpSum(var[i]) <= x_max[i])
    for j in range(col):
        prob += (pulp.lpSum([var[i][j] for i in range(row)]) == y_max[j])

    prob.solve()
    return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}

if __name__ == '__main__':
    costs = np.array([[6,2,6,7,4,2,5,9],
                      [4,9,5,3,8,5,8,2],
                      [5,2,1,9,7,4,3,3],
                      [7,6,7,3,9,2,7,1],
                      [2,3,9,5,7,2,6,5],
                      [5,5,2,2,8,1,4,3]])
    max_plant = [60,55,51,43,41,52]
    max_cultivation = [35,37,22,32,41,32,43,38]
    res = transportation_problem(costs, max_plant, max_cultivation)

    print(f'最小值为{res["objective"]}')
    print('各变量的取值为:')
    pprint(res['var'])

2.2 Pyhton计算结果

最小值为664.0
各变量的取值为:
[[0.0, 19.0, 0.0, 0.0, 41.0, 0.0, 0.0, 0.0],
 [1.0, 0.0, 0.0, 32.0, 0.0, 0.0, 0.0, 0.0],
 [0.0, 11.0, 0.0, 0.0, 0.0, 0.0, 40.0, 0.0],
 [0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, 38.0],
 [34.0, 7.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
 [0.0, 0.0, 22.0, 0.0, 0.0, 27.0, 3.0, 0.0]]

由R输出结果可知6个生产点8个销售点的最小运费为664,相应的运送方案为A1→B2:19个单位,A1→B5:41个单位,A2→B1:1个单位,A2→B4:32个单位,A3→B2:11个单位,A3→B7:40个单位,A4→B6:5个单位,A4→B8:38个单位,A5→B1:34个单位,A5→B2:7个单位,A6→B3:22单位,A6→B6:27个单位,A6→B7:3个单位。

三、指派问题

指派问题(assignment problem) 属于0-1 整数规划,是一种特殊的整数规划问题。指派问题的标准形式(以人和事为例) 是:有n个人和n个事,已知第i个人做第j件事的费用为Cij (i; j = 1, 2,…n),要求确定人和事之间的一一对应的指派方案,使完成n件事的总费用最少。指派问题是运输问题中的一种特殊情况,"派合适的人去做合适的事"是对该问题的最贴切描述。工人相当于产地,工作相当于销地,那么劳动就是产品,工资就是运费。所以,运输问题所拥有的特点指派问题都有,而特殊之处就是在指派问题中,各个产地和销地的产量和需求量都是"1"。这个全新的特点使得其解法在运输问题解法的基础上得到了简化。

3.1 指派问题数学模型

最优指派问题也称为最优匹配问题,它是运输问题的特殊情况。问题描述如下:
设有n个人,计划做n项工作,其中cij 表示第i个人做第j项工作的收益,现求一种指派方式,使得每个人完成一项工作,总收益最大。设决策变量为xij当第i个人做第j项工作时,决策变量xij=1;否则,决策变量xij=0,因此最优指派问题可以写成0-1规划模型:

maxz = i=1nj=1ncijxij
i=1nxij=1 j=1,2,...,n
j=1nxij=1 i=1,2,...,n
xij=1 or 0   i,j=1,2,...,n

如果cij表示第i个人做第j项工作所用的花费,则目标函数为求最小。
例3: 某工厂有6名工人 ,分别操作6台车床。已知每个工人Ai(i = 1, 2,…,6)对每台机床Bj(j = 1,2,…,6) 的每小时单产量为cij(i; j = 1,2,…,6)。每小时单产量如下表(这里-99表示不会操作),求产值最大的分配方案。

[[ 20  15  16   5   4   7]
 [ 17  15  33  12   8   6]
 [  9  12  18  16  30  13]
 [ 12   8  11  27  19  14]
 [-99   7  10  21  10  32]
 [-99 -99 -99   6  11  13]]

3.2 Python计算程序

import pulp
import numpy as np
from pprint import pprint
def assignment_problem(efficiency_matrix):
    row = len(efficiency_matrix)
    col = len(efficiency_matrix[0])
    flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
    m = pulp.LpProblem('assignment', sense=pulp.LpMaximize)
    var_x = [[pulp.LpVariable(f'x{i}{j}', cat=pulp.LpBinary) for j in range(col)] for i in range(row)]
    m += pulp.lpDot(efficiency_matrix.flatten(), flatten(var_x))
    for i in range(row):
        m += (pulp.lpDot(var_x[i], [1]*col) == 1)
    for j in range(col):
        m += (pulp.lpDot([var_x[i][j] for i in range(row)], [1]*row) == 1)
    m.solve()
    #print(m)
    return {'objective':pulp.value(m.objective), 'var': [[pulp.value(var_x[i][j]) for j in range(col)] for i in range(row)]}

efficiency_matrix = np.array([[20,15,16,5,4,7],
                             [17,15,33,12,8,6],
                            [9,12,18,16,30,13],
                           [12,8,11,27,19,14],
                           [-99,7,10,21,10,32],
                         [-99,-99,-99,6,11,13]])
res = assignment_problem(efficiency_matrix)
print(f'最大值{res["objective"]}')
print(res['var'])

3.3 Pyhton计算结果

最大值135.0
[[1.0, 0.0, 0.0, 0.0, 0.0, 0.0], 
[0.0, 0.0, 1.0, 0.0, 0.0, 0.0], 
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0], 
[0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
 [0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
 [0.0, 0.0, 0.0, 0.0, 1.0, 0.0]]

最优分配:第1个人做第1项工作;第2个人做第3项工作;第3个人做第2项工作;第4个人做第4项工作;第5个人做第6项工作;第6个人做第5项工作。

四、指派问题实例

例4: 某商业公司计划开办5家新商店。为了尽早建成营业,商业公司决定由6家建筑公司分别承建。已知建筑公司Ai(i = 1, 2,…,5) 对新商店Bj(j = 1,2,…,5) 的建造费用的报价(万元) 为cij(i; j = 1,2,…,5)。该公司应对5家建筑公司怎样分配建筑任务,才能使总的建筑费用最少?

4.1 费用矩阵

  [[ 4  8  7 18 12]
 [ 7  9 17 14 10]
 [ 6  9 12  8  7]
 [ 6  7 14  6 10]
 [ 6  9 12 10  6]]

4.2 Python计算程序

import pulp
import numpy as np
from pprint import pprint
def assignment_problem(efficiency_matrix):
    row = len(efficiency_matrix)
    col = len(efficiency_matrix[0])
    flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
    m = pulp.LpProblem('assignment', sense=pulp.LpMinimize)
    var_x = [[pulp.LpVariable(f'x{i}{j}', cat=pulp.LpBinary) for j in range(col)] for i in range(row)]
    m += pulp.lpDot(efficiency_matrix.flatten(), flatten(var_x))
    for i in range(row):
        m += (pulp.lpDot(var_x[i], [1]*col) == 1)
    for j in range(col):
        m += (pulp.lpDot([var_x[i][j] for i in range(row)], [1]*row) == 1)
    m.solve()
    #print(m)
    return {'objective':pulp.value(m.objective), 'var': [[pulp.value(var_x[i][j]) for j in range(col)] for i in range(row)]}

efficiency_matrix = np.array([[4,8,7,18,12],
                             [7,9,17,14,10],
                               [6,9,12,8,7],
                              [6,7,14,6,10],
                             [6,9,12,10,6]])
res = assignment_problem(efficiency_matrix)
print(f'最小值{res["objective"]}')
print(res['var'])

4.3 Pyhton计算结果

最小值34.0
[[0.0, 0.0, 1.0, 0.0, 0.0], 
[0.0, 1.0, 0.0, 0.0, 0.0], 
[1.0, 0.0, 0.0, 0.0, 0.0], 
[0.0, 0.0, 0.0, 1.0, 0.0], 
[0.0, 0.0, 0.0, 0.0, 1.0]]

从运行结果可以看出,最优解已经成功找到。最优指派方案是:A1 承建B3,A2 承建B2,A3 承建B1,A4 承建B4,A5 承建B5。这样安排能使总费用最少,为7 + 9 + 6 + 6 + 6 = 34 万元。

五、总结

运用运筹学的知识和方法对运输问题进行资源的优化配置和人员的合理分配,不仅可以为企业节省成本,提高效益和利润还可以节约运输的时间,为科学管理带来决策支持,为广大的消费者带来优惠和便利。指派问题是特殊的运输问题,是运输问题的离散化表达。在实际应用中,常会遇到各种非标准形式的指派问题,有时不能直接调用函数,处理方法是将它们化为标准形式(胡运权, 2007),然后再通过标准方法求解。同运输问题一样,有许多软件可以求解这一类问题,比如LINGO 在解决指派问题时,也必须通过各种命令建立数据集、模型、目标函数、约束函数等,比较繁琐,相比之下,R两三句代码就可以快速解决问题,较之LINGO 软件,的确方便快捷了许多。

参考文献

1.运筹学笔记 运输问题
2.【R语言在最优化中的应用】lpSolve包解决 指派问题和指派问题
3.运筹说 第39期 | 运输问题经典例题讲解
4.R语言与优化模型:线性规划、整数规划和运输问题
5.Python数学建模PuLP库线性规划入门示例详解
6.【数学建模】线性规划各种问题的Python调包方法

posted @   郝hai  阅读(1429)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示