说明
和上一篇的假设一样,不过这次我们要根据sku的重量结合车的载重做到不超载。
内容
1 背景
- 1 每个行向量代表了一个方案
- 2 每个方案由n个元素组成
- 3 每个元素由 timeslot、qty和vehicle(N.O)组成
2 数据构造
在上一篇的基础上进行具有一般普适性的构造。还是按之前简单的假设:
- 1 两家商店s1和s2
- 2 两家店允许的送货次数是2次和1次
- 3 s1需要2个sku, s2需要3个sku(所以配货方案会有7个元素)
未来在实际使用的时候可以按格式化批量生成,个例手工修改
的方式来完成完整的声明和修改。
2.1 两家商店运行的收货次数
s_allow_times = [2,1]
2.2 每天的收货时间窗口和多次送货的时间间隔
# 指定的间隔,会根据允许的配货次数均匀配货,这个会生成lb和ub
time_interval = 144 * 3
s_allow_window = [(72,144), (120,144)]
2.3 每个商店的订单表
# 先将配货的sku表格处理,按照每个商店一个df的方式组成列表
s1_orders = fs.from_pickle('sample_s1_order_2recs')
s2_orders = fs.from_pickle('sample_s2_order_3recs')
# 两家店的订单数
s_orders = [s1_orders, s2_orders]
2.3.1 每家店的sku数
# 每家店sku的数量
s_skus = [len(x) for x in s_orders]
---
[2, 3]
2.3.2 每个sku的重量
# 每个sku的重量
s_sku_weight_list = [list(x['weight']) for x in s_orders]
[[14.25, 16.35], [17.3, 17.3, 13.58]]
2.3.3 每个sku的需求量区间
# 每个sku的需求量
s_sku_num_list = [list(zip([0] * len(list(x['发货数量'])), list(x['发货数量']))) for x in s_orders]
---
[[(0, 11.0), (0, 16.0)], [(0, 9.0), (0, 5.0), (0, 5.0)]]
3 计算
根据原始的数据生成遗传算法需要的约束
3.1 时隙范围约束
时隙列表的计算(stores > times > skus)
timeslot_lb_list, timeslot_ub_list = make_store_time_slot_bounds(s_allow_times, s_skus ,s_allow_window)
timeslot_lb_list:[72, 72, 504, 504, 120, 120, 120]
timeslot_ub_list:[144, 144, 576, 576, 144, 144, 144]
3.2 sku配送数量的约束
sku_lb_list, sku_ub_list = make_store_tuple_list_bounds(s_allow_times, s_sku_num_list)
sku_lb_list:[0, 0, 0, 0, 0, 0, 0]
sku_ub_list:[11.0, 16.0, 11.0, 16.0, 9.0, 5.0, 5.0]
3.3 生成一个完整的方案范围约束
# 车次号按0~5安排,生成lb和ub
lb = list(zip(timeslot_lb_list, sku_lb_list, [0] * len(sku_lb_list)))
ub = list(zip(timeslot_ub_list, sku_ub_list, [5] * len(sku_ub_list)))
---
lb = list(np.array(lb).ravel())
ub = list(np.array(ub).ravel())
lb:[72, 0, 0, 72, 0, 0, 504, 0, 0, 504, 0, 0, 120, 0, 0, 120, 0, 0, 120, 0, 0]
ub:[144,11,5, 144,16,5, 576,11,5, 576,16,5, 144,9,5, 144,5,5, 144,5,5]
3.4 生成符合方案形状的sku重量列表
store_weight_list = make_store_simeple_list(s_allow_times, s_sku_weight_list)
[14.25, 16.35, 14.25, 16.35, 17.3, 17.3, 13.58]
3.5 计算载重约束
和之前一样,不满足条件=满足约束(cv)的结果为True
挑一个个体(indi)看一下,这个个体是算法刚开始生成的,质量不高,正好用在这里:
# 货车的价格和载重量可以单独定义
car_price_list = [1000,2000,3000,4000,5000,6000]
car_weight_ability_list = [10,20,30,40,50,60]
car_list = np.array([0,1,2,3,4,5])
test_indi
array([118., 11., 0., 129., 0., 2., 73., 8., 1., 142., 8.,
4., 122., 9., 4., 121., 5., 4., 128., 5., 2.])
# 配货方案中没有用到3和5车
use_car_list = test_indi[2::3]
[0., 2., 1., 4., 4., 4., 2.]
sku_num_list = test_indi[1::3]
[11., 0., 8., 8., 9., 5., 5.]
sku_weight_list = store_weight_list * sku_num_list
[156.75, 0. , 114. , 130.8 , 155.7 , 86.5 , 67.9 ]
# 将车次列表升维,以便比较
car_list_dim2 = car_list.reshape(-1, 1)
array([[0],
[1],
[2],
[3],
[4],
[5]])
car_select_mat = use_car_list == car_list_dim2
[[ True, False, False, False, False, False, False],
[False, False, True, False, False, False, False],
[False, True, False, False, False, False, True],
[False, False, False, False, False, False, False],
[False, False, False, True, True, True, False],
[False, False, False, False, False, False, False]]
验算4车
130.8 + 155.7 + 86.5 -> 373.0
car_loads_list = np.dot(sku_weight_list,car_select_mat.T )
[156.75, 114. , 67.9 , 0. , 373. , 0. ]
# 只有两个没载货的车是正常的(我之前设置每辆车的载重很少)
car_loads_list <= car_weight_ability_list
[False, False, False, True, False, True]
调用过程如下
np_vehicle_capacity_check(test_indi, store_sku_weight_list=store_weight_list, car_weight_ability_list=car_weight_ability_list,car_list = car_list)
True
3.6 批量计算载重约束
使用偏函数将一些参数固定
from functools import partial
the_func = partial(np_vehicle_capacity_check,store_sku_weight_list=store_weight_list, car_weight_ability_list=car_weight_ability_list,car_list = car_list )
在axis=1轴进行广播,全部命中约束
np.apply_along_axis(the_func,1, test_phen_cars)
---
array([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
...
现在我把车的载重加大一点
car_weight_ability_list = [10,20,30,40,50,60]
car_weight_ability_list = np.array(car_weight_ability_list) * 7
the_func = partial(np_vehicle_capacity_check,store_sku_weight_list=store_weight_list, car_weight_ability_list=car_weight_ability_list,car_list = car_list )
np.apply_along_axis(the_func,1, test_phen_cars)
array([ True, False, True, True, True, True, True, True, True,
True, True, True, True, True, False, True, True, True,
True, True, True, True, True, False, True, True, True,
True, True, True, True, True, True, True, True, False,
True, True, True, True, True, True, True, False, True,
True, True, True, True, True, True, True, False, True,
True, True, True, False, True, True, True, True, True,
True, True, True, True, True, True, True, False, False,
...
这时候看起来就比较好了。在实际使用时还需要升维(因为cv矩阵要横向拼接)。x.reshape(-1,1)
附函数
- 1 函数1
# 根据商店允许的时间段加上指定的时间间隔制作¨timeslot的lb和ub
def make_store_time_slot_bounds(store_allow_times_list,store_sku_num_list, store_allow_window_list, time_interval = 3*144):
s_allow_times = store_allow_times_list
s_allow_window = store_allow_window_list
timeslot_lb_list = []
timeslot_ub_list = []
for i in range(len(s_allow_times)):
deliver_times = s_allow_times[i]
cur_window = s_allow_window[i]
sku_nums = store_sku_num_list[i]
for j in range(deliver_times):
for k in range(sku_nums):
time_slot_bias = j * time_interval
daily_window_start, daily_window_end = cur_window
daily_window_start += time_slot_bias
daily_window_end += time_slot_bias
timeslot_lb_list.append(daily_window_start)
timeslot_ub_list.append(daily_window_end)
return timeslot_lb_list, timeslot_ub_list
- 2 函数2
# 根据商店允许的配货次数列表和商店某个指标的tuple_list制作lb_list和ub_list (store -> times -> skus)
# cv = Constraint Violation
# tuple = (low, high)
def make_store_tuple_list_bounds(store_allow_times_list, store_tuple_list):
tem_lb_list = []
tem_ub_list = []
for i in range(len(store_allow_times_list)):
deliver_times = store_allow_times_list[i]
cur_tuple_list = store_tuple_list[i]
for j in range(deliver_times):
for cur_tuple in cur_tuple_list:
cur_lb, cur_ub = cur_tuple
tem_lb_list.append(cur_lb)
tem_ub_list.append(cur_ub)
return tem_lb_list, tem_ub_list
- 3 函数3
# 将每个根据 store-> times -> sku方式展开向量,根据sku的明细获得一个对应的明细列表
def make_store_simeple_list(store_allow_times_list, store_detail_list):
res_list = []
for i in range(len(store_allow_times_list)):
deliver_times = store_allow_times_list[i]
cur_detail_list = store_detail_list[i]
for j in range(deliver_times):
for detail in cur_detail_list:
res_list.append(detail)
return res_list
- 4 函数4
import numpy as np
# 计算一个方案中各货车的重量是否超限。默认元素 timeslot, qty, vehicle
# 对应的向量起始位置:qty =1, vehicle=2
# 默认的组长度 3
# car_list是车的编号列表,car_weight_ability_list是车的载重能力列表
# sku_weight_list 对应的每个sku单位重量
def np_vehicle_capacity_check(some_indi,store_sku_weight_list = None,car_weight_ability_list=None, car_list = None, qty_pos = 1, vehicle_pos = 2, group_num=3 ):
# 使用的车
use_car_list = some_indi[vehicle_pos::group_num]
# 对应的sku数量
sku_num_list = some_indi[qty_pos::group_num]
# 计算每个sku的实际重量
sku_weight_list = sku_num_list * store_sku_weight_list
# 将所有可用的车型列表升维,以便广播比较
car_list_dim2 = car_list.reshape(-1, 1)
# 锁定每个sku使用的车
car_select_mat = use_car_list == car_list_dim2
# 计算每辆车的载重
car_loads_list = np.dot(sku_weight_list,car_select_mat.T )
return not all(car_loads_list <= car_weight_ability_list)