说明

和上一篇的假设一样,不过这次我们要根据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)