背景
现状:由dg和cq两个仓往全国31个省会城市利用空运和陆运发送货物。
规划:
在不影响时效的情况下,希望以成本最少为原则在候选wh个城市中最多选择两个中转仓中转货物
约束条件:
- 当有两个中转仓时,则dg和cq仓发往同一个地点的货物不能分别走不同的中转仓进行中转
- 当中转仓时效超过上限时,则该条线路不能选择该中转仓进行中转
- 若直发的成本较优时,则可以不选择该中转仓进行中转
- dg和cq到中转仓选择干线运输,到下游城市选择支线运输
模型构建
仓网模型伪代码
import os
import json
from dataprocessor import *
from config import Config
from gurobipy import *
import pandas as pd
class WarehouseSelection(object):
def __init__(self, data, config):
self.data = data
self.config = config
self.model = None
def run(self):
print('..')
l_wh = self.data.l_wh
l_cus = self.data.l_cus
d_line = self.data.d_line
d_cus = self.data.d_cus
M = 100000
'''#### model declare ####'''
model = Model('warehouse_location')
'''#### define decision variables ####'''
var_wh = model.addVars(l_wh, vtype=GRB.BINARY, name='wh')
var_dg_wh_cus = model.addVars(l_wh, l_cus, vtype=GRB.BINARY, name='dg_wh_cus')
var_cq_wh_cus = model.addVars(l_wh, l_cus, vtype=GRB.BINARY, name='cq_wh_cus')
var_dg_cus = model.addVars(l_cus, vtype=GRB.BINARY, name='dg_cus')
var_cq_cus = model.addVars(l_cus, vtype=GRB.BINARY, name='cq_cus')
'''#### define objective function ####'''
print('objective function')
wh_transit_cost = self.compute_wh_transit_cost(var_dg_wh_cus, var_cq_wh_cus)
src_transit_cost = self.compute_src_transit_cost(var_dg_cus, var_cq_cus)
wh_cost = self.compute_wh_cost(var_wh, var_dg_wh_cus, var_cq_wh_cus)
cost = wh_cost + wh_transit_cost + src_transit_cost
model.setObjective(cost, GRB.MINIMIZE)
'''#### define model constraints ####'''
# 1. time constraints
for wh in l_wh:
for cus in l_cus:
model.addConstr(var_dg_wh_cus[wh, cus] * d_line[wh, cus]['slv_50%'] <= d_cus[cus]['dg_wh_slv'])
model.addConstr(var_cq_wh_cus[wh, cus] * d_line[wh, cus]['slv_50%'] <= d_cus[cus]['cq_wh_slv'])
# 2.当选中一个中转仓往下游客户cus发货,那么不可能有另一个中转仓也往下游cus发货
for cus in l_cus:
for wh in l_wh:
model.addConstr(var_dg_wh_cus[wh, cus] + quicksum(var_cq_wh_cus[w, cus] for w in l_wh if w != wh) <= 1)
model.addConstr(var_cq_wh_cus[wh, cus] + quicksum(var_dg_wh_cus[w, cus] for w in l_wh if w != wh) <= 1)
# 3. dg和cq的货物发到下游每个城市只存在东莞直发或者是某个中转仓转发
for cus in l_cus:
model.addConstr(var_dg_cus[cus] + quicksum(var_dg_wh_cus[wh, cus] for wh in l_wh) == 1)
model.addConstr(var_cq_cus[cus] + quicksum(var_cq_wh_cus[wh, cus] for wh in l_wh) == 1)
# 4.若存在某个中转仓发货到下游某城市,则该中转仓必须被选中
for wh in l_wh:
model.addConstr(quicksum(var_dg_wh_cus[wh, cus] for cus in l_cus) + quicksum(var_cq_wh_cus[wh, cus] for cus in l_cus) <= M * var_wh[wh])
# 5.最多只有两个中转仓被选中
model.addConstr(quicksum(var_wh[wh] for wh in l_wh) <= 2)
# model.addConstr(quicksum(var_wh[wh] for wh in l_wh) >= 1)
'''#### solve model ####'''
model.optimize()
"""######## check the result ######### """
if model.Status == GRB.OPTIMAL:
print('the warehouse selection optimized successfully !')
print('the objective of the model is {}'.format(model.objVal))
self.model = model
print('dump model to file:\n')
model.write('warehouse selection.lp')
# get the variables
self.wh = model.getAttr('x', var_wh)
print("选中的中转仓是: " )
print(self.wh)
self.dg_cus = model.getAttr('x', var_dg_cus)
print("由dg直发到下游的城市是: ")
print(self.dg_cus)
self.cq_cus = model.getAttr('x', var_cq_cus)
print("由cq直发到下游的城市是: ")
print(self.cq_cus)
self.dg_wh_cus = model.getAttr('x', var_dg_wh_cus)
print("由dg转中转仓到下游的城市是: ")
print(self.dg_wh_cus)
self.cq_wh_cus = model.getAttr('x', var_cq_wh_cus)
print("由cq转中转仓到下游的城市是: ")
print(self.cq_wh_cus)
r_wh_transit_cost = self.compute_wh_transit_cost(self.dg_wh_cus, self.cq_wh_cus)
print("中转仓运输成本:" + str(r_wh_transit_cost))
r_src_transit_cost = self.compute_src_transit_cost(self.dg_cus, self.cq_cus)
print("dg和cq干线成本:" + str(r_src_transit_cost))
r_wh_cost = self.compute_wh_cost(self.wh, self.dg_wh_cus, self.cq_wh_cus)
print("仓储成本:" + str(r_wh_cost))
"""######## output the detail ######### """
self.compute_detail_cost()
return
def compute_detail_cost(self):
abs_filepath = os.path.abspath(os.path.join(os.getcwd(), ".."))
dg_wh_cus = self.dg_wh_cus
cq_wh_cus = self.cq_wh_cus
dg_cus = self.dg_cus
cq_cus = self.cq_cus
wh = self.wh
dg_cus_df = pd.DataFrame(dg_cus, index=['dg_city']).T
cq_cus_df = pd.DataFrame(cq_cus, index=['cq_city']).T
dg_wh_cus_df = pd.DataFrame(dg_wh_cus, index=['dg_wh_city']).T
cq_wh_cus_df = pd.DataFrame(cq_wh_cus, index=['dg_wh_city']).T
dg_cus_df.to_excel(abs_filepath + "\\data\\dg_cus_df.xlsx")
cq_cus_df.to_excel(abs_filepath + "\\data\\cq_cus_df.xlsx")
dg_wh_cus_df.to_excel(abs_filepath + "\\data\\dg_wh_cus_df.xlsx")
cq_wh_cus_df.to_excel(abs_filepath + "\\data\\cq_wh_cus_df.xlsx")
return
def compute_wh_transit_cost(self, var_dg_wh_cus, var_cq_wh_cus):
d_truck = self.data.d_truck
d_cus = self.data.d_cus
d_line = self.data.d_line
config = self.config
truck_dg_cost = 0
line_dg_cost = 0
for cus in d_cus.keys():
cus_dg_wgt = d_cus[cus]['dg_wgt']
for wh in d_truck.keys():
truck_dg_cost += d_truck[wh]['dg_truck_cost'] * cus_dg_wgt / config.wgt_per_pallet / config.pallet_per_car * var_dg_wh_cus[wh, cus]
line_dg_cost += (d_line[wh, cus]['base_price'] + (cus_dg_wgt - d_line[wh, cus]['base_wgt']) * \
d_line[wh, cus]['cont_wgt']) * var_dg_wh_cus[wh, cus]
truck_cq_cost = 0
line_cq_cost = 0
for cus in d_cus.keys():
cus_cq_wgt = d_cus[cus]['cq_wgt']
for wh in d_truck.keys():
truck_cq_cost += d_truck[wh]['cq_truck_cost'] * cus_cq_wgt /config.wgt_per_pallet / config.pallet_per_car * var_cq_wh_cus[wh, cus]
line_cq_cost += d_line[wh, cus]['cont_wgt'] * cus_cq_wgt * var_cq_wh_cus[wh, cus]
wh_transit_cost = truck_dg_cost + line_dg_cost + truck_cq_cost + line_cq_cost
return wh_transit_cost
def compute_src_transit_cost(self, var_dg_cus, var_cq_cus):
d_cus = self.data.d_cus
air_dg_cost = 0
car_dg_cost = 0
for cus in d_cus.keys():
cus_dg_wgt = d_cus[cus]['dg_wgt']
air_dg_cost += d_cus[cus]['dg_air_price'] * cus_dg_wgt * var_dg_cus[cus]
car_dg_cost += d_cus[cus]['dg_car_price'] * cus_dg_wgt * var_dg_cus[cus]
air_cq_cost = 0
car_cq_cost = 0
for cus in d_cus.keys():
cus_cq_wgt = d_cus[cus]['cq_wgt']
air_cq_cost += d_cus[cus]['cq_air_price'] * cus_cq_wgt * var_cq_cus[cus]
car_cq_cost += d_cus[cus]['cq_car_price'] * cus_cq_wgt * var_cq_cus[cus]
src_transit_cost = air_dg_cost +car_dg_cost + air_cq_cost + car_cq_cost
return src_transit_cost
def compute_wh_cost(self, var_wh, var_dg_wh_cus, var_cq_wh_cus):
config = self.config
d_cus = self.data.d_cus
d_wh = self.data.d_wh
wh_cost = 0
for wh in d_wh.keys():
wh_wgt = 0
for cus in d_cus.keys():
wh_wgt += d_cus[cus]['dg_wgt'] * var_dg_wh_cus[wh, cus]
wh_wgt += d_cus[cus]['cq_wgt'] * var_cq_wh_cus[wh, cus]
wh_cost += wh_wgt / config.inbound_days_1m / config.wgt_per_pallet * config.turnover_days \
* config.wh_coeff * d_wh[wh]
return wh_cost