问题描述
在上一节中我们求解了简单物流配送模型,但是在实际的物流过程中,通常物流配送是一个多阶段的过程:工厂先将产品发到各地的配送中心,然后由配送中心再将产品配送到顾客手中。但是也存在顾客需求量较大时,由工厂直发到顾客处的情况。对于多阶段的情况,我们需要进一步扩充我们的模型。多阶段物流配送问题的描述如下图:多阶段多产品物流网络示意
从工厂到顾客处有不同的路径:到有些顾客处有直通的路线,但是要求对方总需求量达到一定值才可以直发;
还可以通过配送中心对顾客进行配送,这种方式对起发值没有限制,但是配送中心的容量有限,另外经过配送中心还会产生额外的装卸费用;
此外,每条路径的配送费用和最大运力都有一定约束限制。
工厂往往能生产的也不止一类产品:每个工厂有原先就能生产的几种产品,另外还可以通过开模的方式,以付出开模费用为代价增加能生产产品的种类。但是不管生产多少类产品,工厂的总产能是固定的。
需要在这样的情况下,求出使全局费用最低(包括开模费用、配送中心装卸费用、物流费用)的生产布局和物流配送方案。
数学模型
决策变量
为了表达该物流网络的数学模型,定义如下参数:
: 工厂数量
;
: 配送中心数量
;
: 顾客数量
;
: 商品数量
;
: 工厂
的最大产能;
: 0-1变量,未新开模时,工厂是否能够生产商品,可以生产为1,否则为0;
: 商品
的开模成本;
: 商品
的单位重量;
: 配送中心
的最大容量;
: 顾客
对商品
的需求量;
: 路径
上单位重量商品的配送成本;
: 路径
的最大流量限制;
: 路径
的起送量限制;
: 经过配送中心
额外产生的单位重量装卸成本,
;
: 一个与路径配送量有数量级差距的大数;
决策变量可以设定为:
: 路径
上商品
的配送件数。
目标函数
此问题的目标函数是全局总成本,包括开模费用,配送中心装卸费用,物流费用三部分。可以表达为下式:
约束条件
工厂产能约束
满足顾客需求
路径流量约束 -- 每条路径上的配送量必须大于起送量并且小于最大运力
配送中心容量约束 -- 配送中心通过的流量小于容量限制
编程求解
在编程求解过程中,由于物流费用中逻辑条件的实施需要增加额外变量,为了降低求解时间,实际上我省去了用ceil向上取整将是否生产某产品转化为0-1变量的过程,这样虽然目标函数值与上面给出的目标函数有所出入,但是求解出的变量结果是相同的。
#---- 导入依赖库
from pulp import *
import pandas as pd
import networkx as nx
import os
import time
import matplotlib.pyplot as plt
from math import ceil
#---- 数据导入
fpath = os.path.join("data", "生产布局数据.xlsx")
factories = pd.read_excel(fpath, sheet_name="工厂")
distributors = pd.read_excel(fpath, sheet_name="配送中心")
commodities = pd.read_excel(fpath, sheet_name="商品")
customers = pd.read_excel(fpath, sheet_name="顾客")
routes = pd.read_excel(fpath, sheet_name="路线")
#---- 网络建模
# 建立节点
# 创建有向图
G = nx.DiGraph()
# 添加节点
for i, row in factories.iterrows():
G.add_node(row.工厂, node_type='工厂',
supply=row.最大产量_件,
canProduce1=row.当前能否生产产品1,
canProduce2=row.当前能否生产产品2,
canProduce3=row.当前能否生产产品3,)
for i, row in distributors.iterrows():
G.add_node(row.配送中心, node_type='配送中心',
loadingCost=row.装卸费用_元每吨,
maxCapa=row.最大容量_吨)
for i, row in customers.iterrows():
G.add_node(row.顾客, demand1=row.商品1需求量_件,
demand2=row.商品2需求量_件,
demand3=row.商品3需求量_件, node_type='顾客')
# 添加边
for i, row in routes.iterrows():
G.add_edge(row.起点, row.终点, cost=row.运输单价_元每吨, capacity=row.最大运力_吨,
directRoute=row.工厂直发路线, minAmount=row.起发值_吨)
#---- 网络可视化
pos = nx.drawing.layout.shell_layout(G)
color = []
for node in G.nodes(data=True):
if node[1]['node_type'] == "工厂":
color.append("red")
elif node[1]['node_type'] == "配送中心":
color.append("yellow")
else:
color.append("blue")
nx.draw(G, pos, node_color=color)
plt.show()
#---- 问题建模
# 建立模型
model = LpProblem("全局优化问题", LpMinimize)
# 建立决策变量 -- 路径ijk上商品n的配送量
origins = routes.起点.values
destinations = routes.终点.values
commodity_cat = range(1, 4)
shipping_amount = LpVariable.dicts(
"配送量", (origins, destinations, commodity_cat), lowBound=0)
produce_cat = LpVariable.dicts(
"是否生产", (factories.工厂, commodity_cat), cat=LpBinary) # 额外变量, 表示是否在工厂i为商品n开模
# 目标函数 -- 总费用最小
cost = [] # 总费用
M = 100000 # 大数
for(origin, destination, data) in G.edges(data=True):
for comm in commodity_cat:
# 运输费用
cost.append(data['cost']*shipping_amount[origin][destination][comm])
if origin in distributors.配送中心.values:
# 装卸费用
cost.append(G.nodes(data=True)[
origin]["loadingCost"]*shipping_amount[origin][destination][comm])
# 模具费用
for name, data in G.nodes(data=True):
if name in factories.工厂.values:
cost.append((1-data["canProduce1"]) *
lpSum([shipping_amount[name][destination][1] for _, destination in G.out_edges(name)]))
cost.append((1-data["canProduce2"]) *
lpSum([shipping_amount[name][destination][2] for _, destination in G.out_edges(name)]))
cost.append((1-data["canProduce3"]) *
lpSum([shipping_amount[name][destination][3] for _, destination in G.out_edges(name)]))
# 累加总费用
model += lpSum(cost)
#---- 约束条件
# 工厂产能约束
for name, data in G.nodes(data=True):
if data["node_type"] == "工厂":
model += lpSum([shipping_amount[name][destination][comm]
for _, destination in G.out_edges(name) for comm in commodity_cat]) <= data['supply']
# 满足顾客需求
for name, data in G.nodes(data=True):
if data["node_type"] == "顾客":
model += lpSum([shipping_amount[origin][name][1]
for origin, _ in G.in_edges(name)]) == data['demand1']
model += lpSum([shipping_amount[origin][name][2]
for origin, _ in G.in_edges(name)]) == data['demand2']
model += lpSum([shipping_amount[origin][name][3]
for origin, _ in G.in_edges(name)]) == data['demand3']
# 路径流量约束 -- 每条路径上的配送量应该大于起送量,并且小于最大运力
for (origin, destination, data) in G.edges(data=True):
model += lpSum(shipping_amount[origin][destination][comm]
for comm in commodity_cat) <= data['capacity']
model += lpSum(shipping_amount[origin][destination][comm]
for comm in commodity_cat) >= data['minAmount']
# 配送中心的输出等于输入, 同时满足配送中心最大容量要求
for name, data in G.nodes(data=True):
if data["node_type"] == "配送中心":
model += lpSum([shipping_amount[name][destination][comm] for _, destination in G.out_edges(name) for comm in commodity_cat]
) == lpSum([shipping_amount[origin][name][comm] for origin, _ in G.in_edges(name) for comm in commodity_cat])
model += lpSum([shipping_amount[origin][name][comm] for origin,
_ in G.in_edges(name) for comm in commodity_cat]) <= data['maxCapa']
#---- 求解
start_time = time.clock()
model.solve()
print("求解状态: ", LpStatus[model.status])
print("求解用时: ", time.clock()-start_time, "s")
#---- 输出求解结果
rsl = []
for origin, destination in G.edges():
val1 = value(shipping_amount[origin][destination][1])
val2 = value(shipping_amount[origin][destination][2])
val3 = value(shipping_amount[origin][destination][3])
rsl.append({"起点": origin, "终点": destination,
"商品1运输量": val1, "商品2运输量": val2, "商品3运输量": val3})
rsl_df = pd.DataFrame(rsl)
print(rsl_df)
求解结果
求解状态: Optimal
求解用时: 0.056308000000000025 s
过程中画出的有向图如下,其中红色代表工厂,黄色代表配送中心,蓝色代表顾客:networkx建立的物流网络有向图
最终求解得到的每条路上的流量分配如下:
而是否开模的结果如下: