[关闭]
@zqbinggong 2018-07-20T07:00:45.000000Z 字数 20458 阅读 3192

trans_model_opt_v5 说明

览众


计算目标库存

计算需求 cal_demand

预测需求

前两周销售量加权平均的两倍
1. 计算前需要判断下是都为空值

确定需求上下限

  1. D[idx_stc]['targ_lb'] = sale_max / weight_max * weight
  2. D[idx_stc]['targ_ub'] = sale_max / weight_min * weight
  1. sale_max: skc/store的最大需求(size位于MS中)
  2. weihgt_max: skc/dist的最大prop_sales_size
  3. weight_min: skc/dist的最小prop_salse_size(不为零)
  4. weight: sks/dist的prop_salse_size
  5. 前三者初始值分别为0,0,100
    • sals_max = 0 则上下限都为0
      • weight_max/min未更新,则都设为1
      • weight若为零则设为1

优化库存 targ_milp_solver

此处输入图片的描述
此处输入图片的描述

objectice

小于上限,大于下限

  1. expr.addTerms(CDL[idx_stc]['ub'], DTU[idx_stc]) # 对超出上限的添加惩罚系数DTU
  2. expr.addTerms(CDL[idx_stc]['lb'], DTL[idx_stc]) # 对超出上限的添加惩罚系数DTL

减少转运数量

  1. expr.addTerms(CID[idx_stc], RQ[idx_stc])

constraints

C1: DTL和DTU

这里把max变成两个不等式约束,并通过obj1和2来让让其等价

  1. # DTL = max(D - IT, 0)
  2. m.addConstr(DTL[idx_stc] >= 0)
  3. m.addConstr(DTL[idx_stc] >= D[idx_stc]['targ_lb'] - IT[idx_stc])
  4. # DTU = max(IT - D, 0)
  5. m.addConstr(DTU[idx_stc] >= 0)
  6. m.addConstr(DTU[idx_stc] >= IT[idx_stc] - D[idx_stc]['targ_ub'])

C2: ID = max(I0, D)

  1. 这里比较的是目标库存和ava库存
  2. ID的引入是作为实现C3的辅助变量

C3: RQ = |IT - ID|

这里的绝对值也是变成了两个不等式约束,并通过obj3让其等价

C4: 库存数守恒,分配后门店库存不多于原有的门店仓库ava总库存

  1. m.addConstr(quicksum(IT[prod_id, color_id, size, store_id] for store_id in SI)
  2. <= quicksum(I0[prod_id, color_id, size, store_id]['ava'] for store_id in dict(SI, **WI)))

C5: 齐码(针对MS)计算

  1. 这里主要目的是为了实现:若skc在该门店有货(IT),则必须齐码,即由MS和QSF确定的尺码组中有一个是齐码的
  2. 辅助条件:
    • 先判断主要尺码是否有货: IT --> ITS <--> ITF
    • 判断各尺码是否有货 IT <--> ITB
    • 判断尺码组是否齐码: ITB --> N0S <--> F0B
  1. m.addConstr((ITF[idx_skcs] == 1) >> (quicksum(F0B.values()) >= 1))

C6: 主要尺码所有size的IT为零,则其他尺码也为零

  1. idx_skcs = (prod_id, color_id, store_id)
  2. for size in PI[prod_id][color_id]['size']:
  3. idx_stc = (prod_id, color_id, size, store_id)
  4. m.addConstr((ITF[idx_skcs] == 0) >> (IT[idx_stc] <= 0)) # # =0

数据处理部分

lsd.load_data

pi_df

  1. prod_basic_info + size_grp_info
    • p_id, c_id, size, year, season_id, class_0, class_2, size_grp_id
    • size_order
    • join on size and size_grp_id
  2. 根据p_id, c_id, size去重且排序(去重是因为这里的数据重复是没有意义的,因为它们不涉及到数量概念,只是用于标记
  3. 将index设为p_id, c_id
  4. 表结构
(prod_id, color_id) size year '''

si_df

  1. store_basic_info
    • p_id, c_id, size, year, season_id, class_0, class_2, size_grp_id
    • where dist_id = dist_id_sel
  2. 根据store_id去重且排序(去重是因为这里的数据重复是没有意义的,因为它们不涉及到数量概念,只是用于标记
  3. 将index设为store_id
  4. 表结构
(store_id) prov_Id city_id dist_id store_lvl

wi_df

  1. wh_info
    • wh_id, prov_id
  2. 根据wh_id去重(去重是因为这里的数据重复是没有意义的,因为它们不涉及到数量概念,只是用于标记
  3. 将index设为wh_id,且排序
  4. 表结构
(wh_id) prov_id

i0_df

  1. stock_full_data ++ prod_basic_info + 特定区域的门店和仓库id
    • p_id, c_id, size, org_id, quant_stok_instore, quant_stock_onorder (后两项的空值改为0)
    • 筛选出p_id, c_id, size
    • 筛选出org_id
  2. 根据p_id, c_id, size, org_id去重(去重是因为这里的数据重复是没有意义的,因为这里的num数据的重复不存在累加的概念
  3. 将库存的负值改为0
  4. 将index设为p_id, c_id,size, org_id 并排序
  5. 表结构
(prod_id, color_id, size, org_id) quant_stock_instore quant_stock_onorder
str int,无空值,非负 int,无空值,非负

s_df

  1. sales_data ++ prod_basic_info + 特定区域的门店id
    • p_id, c_id, size, org_id, quant_sell (空值改为0)
    • 筛选出p_id, c_id, size
    • 筛选出store_id
  2. quant_sell:
    • 根据时间筛选数据: date_sell 在 (date_dec - Day(28), 零点) 到 (date_dec - Day(1),11:59:59) 之间
    • 负值改为0
  3. 将index设为p_id, c_id,size, store_id 并排序
  4. 表结构
(prod_id, color_id, size, store_id) date_sell quant_sell
str dt, 由date_dec确定范围 int,无空值,非负

tf_df

  1. edw2.fct_stock ++ 特定区域的门店和仓库id
    • org_send_id, org_rec_id, count(send_date) AS trans_freq
    • date_send 在 (date_dec - year(1), 零点) 到 (date_dec - Day(1),11:59:59) 之间
    • 筛选org_send_id, org_rec_id
  2. 将index设为org_send_id, org_rec_id 并排序
  3. 表结构
(org_send_id, org_rec_id) trans_freq
str int

it_df

  1. inv_targ_data
    • p_id, c_id, size, org_id, quant_stock_targ (空值改为0)
    • date_targ = date_dec
  2. 根据p_id, c_id, size, store_id去重
  3. 将quant_stock_targ的负值改为0
  4. 将index设为p_id, c_id,size, store_id 并排序
  5. 表结构
(prod_id, color_id, size, store_id) quant_stock_targ
str int,无空值,非负

cop.cal_sales_prop(si_df, pi_df, i0_df, s_df, len_main_size_grp)

sales_prop_skc

计算这季度这一大类某(p_id,c_id)占的销售比
1. s_df + pi_df + si_df
- 主表
- 提供season_id class_0
2. s_df.quant_sell:
- sum --> groupby(p_id, c_id,store_id)
- mean --> groupby(p_id, c_id)
3. join pi_df
- 选取p_id,c_id,season_id,class_0这4列并对其去重(先释放index)
- 将index设为p_id,c_id(以进行join)
4. 将index设为season_id,class_0,并将quant_sell的负值改为0
5. 计算这季度这一大类某(p_id,c_id)占的销售比
- quant_sell_class = quant_sell --> sum(level = season_id, class_0)
- 将quant_sell_class的非正值设为1(防止下一步分母为零)
- prop_sales_skc = quant_sell / quant_sell_class
- 设置index为p_id,c_id,去除多余列

(prod_id, color_id) prop_sales_skc

sales_prop_size

计算该size在index确定的所有门店的所有skc的平均销售额
1. s_df + pi_df + si_df
- 主表
- 提供
2. s_df.quant_sell:
- sum --> groupby(p_id, c_id, size, store_id) (这里注意到s_df没有去重,因为num数据有累加的意义
- 去掉值不大于零的行
- 得到sales_skss
- - index: (p_id, c_id, size, store_id)
- - columns: quant_sell
3. 从sales_skss中获取skc_list=(p_id, c_id)和store_list=(store_id),用以筛选si_df和pi_df中的数据;这里两个list的顺序是对应的可以保证下面的到的sales_prop_size的index与sales_skss一一对应上
- pi_sel: 用skc_list进行筛选,并选取需要的列,再加入值全为1的列col_on作为index,用以join(具体实现使用字典的方式),col=p_id,c_id,size,size_order,class_0,size_grp_id
- si_sel: 用store_list进行筛选,并选取需要的列,再加入值全为1的列col_on作为index,用以join(具体实现使用字典的方式), col=store_id,dist_id
4. sales_prop_size = pi_sel.join(si_sel)
- join sales_skss,并fillna(0), 获取quant_sell (即为sales_skss表获取相应的商品信息和门店信息
- quant_sell:
- - sum --> 所有其他列 (由于2.1已经保证了(p_id,c_id,size,store_id)是唯一的即这里的其他列也唯一,因而此处的求和显得多余
- - 去掉p_id,c_id
- - mean --> 所有其他列 (得到该size在该店不同skc的平均销售额
- - 去掉store_id
- - mean --> 所有其他列 (得到该size在这些店的平均销售额
- - index为dist_id,class_0,size_grp_id (col=size_order, quant_sell
- - 小于零的值设为0
5. 计算prop
- quant_sell_class = quant_sell --> sum(level=dist_id,class_0,size_grp_id) (即对size_order聚合,但是size-order和size一一对应,因而sum只起到qu'd)
- 将quant_sell_class的非正值设为1(防止下一步分母为零)
- prop_sales_size = quant_sell / quant_sell_class
- 排序sort_values(['dist_id', 'class_0', 'size_grp_id', 'size_order'], inplace=True)
- 设置index为'dist_id', 'class_0', 'size_grp_id', 'size',去除多余列
6. 表结构

(class_0, size_grp_id, dist_id, size) prop_salse_size
某size在index确定的所有门店的所有skc的平均销售额

sales_prop_store

某赛季某大类某门店在index确定下的平均销售额
1. s_df + pi_df
- 主表
- 提供
2. s_df.quant_sell:
- sum --> groupby(p_id, c_id, store_id)
- 得到sales_skcs
- - index: (p_id, c_id, store_id)
- - columns: quant_sell
3. sales_prop_store = sales_skcs -->
- join(pi_df[['season_id', 'class_0']].drop_duplicates())
- groupby(['season_id', 'class_0', 'store_id']).mean()该门店该赛季该大类所有skc(skc即sks之和)的平均销售额
- reset_index(['store_id']
- 将quant_sell的负值改为0
4. 计算prop
- quant_sell_class = quant_sell --> sum(level=season_id,class_0)
- 将quant_sell_class的非正值设为1(防止下一步分母为零)
- prop_sales_size = quant_sell / quant_sell_class
- 排序sort_values(['dist_id', 'class_0', 'size_grp_id', 'size_order'], inplace=True)
- 设置index为'season_id', 'class_0', 'store_id',去除多余列
5. 表结构

(season_id, class_0, store_id) prop_salse_store
某赛季某大类某门店在index确定下的平均销售额

main_size_grp

  1. sales_prop_size + len_main_size_grp
  2. main_size_grp: 由sales_prop_size 删去prop_sales_size,再加上is_main_size(=0)这一列
  3. 寻找sales_prop_size最大的尺码组
    • 先确定sales_size_grp中对应的(class_0, size_grp_id, dist_id)的size集合
    • 根据参数len_mani_size_grp的值选取size区间,找出销售比(加上附加分数)最大的那一组
    • 如果size个数小于该参数,则将整个size集合设为main_size
    • 附加分数,越中间越高分
  4. 保留主要尺码
(class_0, size_grp_id, dist_id) size
出现在主要尺码组中的尺码(个数为min(sales_prop_size里size个数,len_main_size_grp)

sales_we_sum

  1. pi_df, si_df, i0_df, sales_prop_skc, sales_prop_size, sales_prop_store
  2. i0_df.quant_stock_instore:
    • sum --> groupby(p_id, c_id)
    • 去掉值不大于零的行
    • 得到i0_skc
      • index: (p_id, c_id)
      • columns: quant_stock_instore,size, org_id

    • pi_sel: 用i0_skc.index进行筛选,并选取需要的列,再加入值全为1的列col_on作为index,用以join(具体实现使用字典的方式),col=p_id,c_id,size,season_id,class_0,size_grp_id
    • si_sel: 选取dist_id这一列,再加入值全为1的列col_on作为index,用以join(具体实现使用字典的方式), col=store_id,dist_id (不同于计算sales_prop_size,这里没有对si_df进行筛选,似乎无法保证pi_sel和si_sel能衔接上
  3. sales_we_sum = pi_sel.join(si_sel)
    • join sales_prop_skc
    • join sales_prop_size
    • join sales_prop_store
    • drop(['season_id', 'class_0', 'size_grp_id', 'dist_id']
    • fillna(0)
  4. 对是skc, size, store,以及sum_we = skc* size*store分别计算:
  1. c_min = sales_we_sum['prop_sales_skc'].min()
  2. c_max = sales_we_sum['prop_sales_skc'].max() + 1.0E-10
  3. sales_we_sum['skc_we'] = \
  4. sales_we_sum['prop_sales_skc'].map(lambda x: np.exp((x - c_min)/(c_max - c_min) - 1))
  1. 去掉多余列,表结构
(prod_id, color_id, size, store_id) skc_we size_we store_we sum_we

exd.extr_data(pi_df, si_df, wi_df, i0_df, it_df, main_size_grp)

PI

  1. pi_df + i0_df
  2. 只记录i0_df中对应的quant_stock_instore大于零的记录
  1. PI : dict
  2. Product information
  3. key : prod_id
  4. value : dict
  5. key : color_id
  6. value : dict
  7. key : size, class_0, size_grp_id

SI

  1. si_df
  1. SI : dict
  2. Store information
  3. key : store_id
  4. value : dict
  5. key : prov_id, city_id, dist_id, store_lvl

WI

  1. si_df
  1. WI : dict
  2. Warehouse information
  3. key : wh_id
  4. value : dict
  5. key : prov_id

MS : mian size

  1. PI + SI
  1. MS : dict
  2. The main sizes
  3. key : prod_id, color_id, store_id
  4. value : [sizes]

I0

  1. PI + SI + WI + i0_df

    • ava: quant_stock_instore
    • sum: quang_stock_instore + quant_stock_onorder
    • 仓库和门店
  1. I0 : dict
  2. Initial inventory
  3. key : prod_id, color_id, size, org_id
  4. value : dict
  5. key : ava, sum
  6. value : available and sum inventory

IO

  1. PI + SI + WI

    • ava: quant_stock_instore
    • sum: quang_stock_instore + quant_stock_onorder
    • 仓库和门店
  1. IO : dict
  2. Markers of existed transferring in/out
  3. key : prod_id, color_id, size, store_id
  4. value : dict
  5. key : has_in, has_out
  6. value : bool(此时值为0

IT

  1. PI + SI + it_df

    • ava: quant_stock_instore
    • sum: quang_stock_instore + quant_stock_onorder
    • 门店
  1. IT : dict
  2. Target inventory
  3. key : prod_id, color_id, size, store_id
  4. value : target inventory

cop.cal_opt_params()

QSF

确定齐码组的长度,上限是QSF_base

  1. PI, SI, WI, MS, I0, QSF_basePI, SI, WI, MS, I0, QSF_base
  1. QSF : dict
  2. The minimal continue-size length in a full-size group
  3. key : prod_id, color_id, store_id
  4. value : size number
  1. QSF记录的是(p_id,c_id)单品在该门店的main_size集合中在该store所在dist的所有门店的总sum库存和所有仓库该size的总sum库存之和大于零的个数,上限是QSF_base
    • 即QSF的值是这个main_size中库存大于零的个数,但是这里计算库存并不是只考虑了这家门店
    • store_id确定区域
    • p_id,c_id,store_id确定main_size集合

W0B

W0B = cop.mark_fullsize_init(MS, I0_aft_rep, QSF)

W0B : dict
          Whether or not initial inventory full-size group number is positive
          If initial inventory is 0, W0B is 1
          key : prod_id, color_id, store_id
          value : bool
  1. 首先统计(p_id,c_id,store_id)(取自MS的index)所有属于main_size的总sum库存,若总sum库存为零,则WOB = 1(与后面的trans优化对应
  2. 若总库存不为零,则必然是因为main_size中有库存大于零的size(s),则继续考虑由QSF确定的齐码的size个数作为长度截取main_size,在这些连续区间内,是否存在每个尺码是否sum库存都大于零的区间,是则设为1
  3. 否则为零

CTQ

  1. PI, SI, WI, sales_we_sum, CTQ_base_ws, CTQ_base_ss
  1. CTQ : dict
  2. Unit cost by transferring quantity
  3. key : prod_id, color_id, size, org_send_id, org_rec_id
  4. value : transferring quantity cost
  1. CTQ记录的是运输某单品的cost
    • 仓库到门店:CTQ[idx_rep] = CTQ_base_ws*(1 - we)
    • 门店到门店:CTQ[idx_rep] = CTQ_base_ss*(1 - we)

CTP

  1. SI, WI, tf_df, sales_we_sum, CTP_base_ws, CTP_base_ss
  1. CTP : dict
  2. Unit cost by transferring packages
  3. key : org_send_id, org_rec_id
  4. value : transferring package cost
  1. CTP记录的是运输某包裹的cost
    • 获取某门店各商品的平均销售比,sales_we_store
    • 获取CTP_ratio_site
      • 门店与门店之间,门店与仓库之间trans_freq的相对值
    • 仓库到门店:CTP[idx_ptp] = CTP_base_ws*(1 - we)ratio_cost
    • 门店到门店:CTP[idx_ptp] = CTP_base_ss(1 - we)*ratio_cost

CID

  1. PI, SI, sales_we_sum, CID_base
  1. CID : dict
  2. Unit cost by inventory mismatch
  3. key : prod_id, color_id, size, store_id
  4. value : inventory mismatch cost
    2.
  1. we = sales_we_sum['sum_we'].get(idx_stc, sales_we_sum['sum_we'].min())
  2. CID[idx_stc] = CID_base*we

CDP

  1. PI, sales_we_sum, CDP_base
  1. CDP : dict
  2. Unit cost by the maximal inventory mismatch
  3. key : prod_id, color_id, size
  4. value : the maximal inventory mismatch cost
    2.
  1. we = sales_we_sks['sks_we'].get(idx_sks, sales_we_sks['sks_we'].min())
  2. CDP[idx_sks] = CDP_base*we

CDL

  1. PI, SI, sales_we_sum, CDL_base
  1. CDL : dict
  2. Unit cost by demand lossing
  3. key : prod_id, color_id, size, store_id
  4. value : dict
  5. key : lb, ub
  6. value : demand lossing cost
    2.
  1. we = sales_we_sum['sum_we'].get(idx_stc, sales_we_sum['sum_we'].min())
  2. CDL[idx_stc]['lb'] = 5*CDL_base*we
  3. CDL[idx_stc]['ub'] = CDL_base*wec

补货优化 rep_milp_solver

参数

  1. MINGap: 0.03

variable

decision var

  1. # Q : dict
  2. # Replenishment quantity of each sks/send_warehouse/receive_store
  3. # key : prod_id, color_id, size, wh_send_id, store_rec_id
  4. # value : replenishment quantity
  5. Q[idx_rep] = m.addVar(obj=0, vtype=GRB.INTEGER, name=var_name)

assistant vars

I : dict
    End inventory of each skc/size/store
    key : prod_id, color_id, size, store_id
    value : end inventory

ID : dict
     Difference between end and target inventory of each skc/size/store
     key : prod_id, color_id, size, store_id
     value : dict
             key : in, out
             value : inventory difference

IDP : dict
      The maximal difference between end and target inventory of each sks
      key : prod_id, color_id, size
      value : dict
              key : in, out
              value : the maximal inventory difference

QSP : dict
      Sum replenishment quantity of each package
      key : wh_send_id, store_rec_id
      value : sum transferring quantity

QPB : dict
      Whether or not replenishment quantity is positive of each package
      key : wh_send_id, store_rec_id
      value : bool

obj

1. cost by replenishment quantity

idx_rep = (prod_id, color_id, size, wh_send_id, store_rec_id)
expr.addTerms(CTQ[idx_rep], Q[idx_rep])

2. cost by replenishment package

idx_ptp = (wh_send_id, store_rec_id)
expr.addTerms(CTP[idx_ptp], QPB[idx_ptp])

3. cost by end-target inventory mismatch

idx_stc = (prod_id, color_id, size, store_id)
expr.addTerms(CID[idx_stc], ID[idx_stc])

4. cost by the maximal end-target inventory mismatch

idx_sks = (prod_id, color_id, size)
expr.addTerms(CDP[idx_sks], IDP[idx_sks])

equation

1. 某门店的接收等于所有仓库的发出

  1. QIS[idx_stc] == quicksum(Q[prod_id, color_id, size, wh_send_id, store_id] for wh_send_id in WI))

2. 某仓库的发出等于所有门店的接收

  1. QOS[idx_stc] == quicksum(Q[prod_id, color_id, size, wh_send_id, store_id] for store_rec_id in SI))

3. Sum replenishment quantity of each package

  1. idx_ptp = (wh_send_id, store_rec_id)
  2. m.addConstr(QSP[idx_ptp] ==
  3. quicksum(Q[prod_id, color_id, size, wh_send_id, store_rec_id]
  4. for prod_id in PI
  5. for color_id in PI[prod_id]
  6. for size in PI[prod_id][color_id]['size']))

4. End inventory after replenishment of each sks/organization

  1. 门店
  2. idx_stc = (prod_id, color_id, size, store_id)
  3. m.addConstr(I[idx_stc] == I0[idx_stc]['sum'] + QIS[idx_stc])
  4. 仓库
  5. idx_stc = (prod_id, color_id, size, wh_id)
  6. m.addConstr(I[idx_stc] == I0[idx_stc]['ava'] - QOS[idx_stc])

Constraints

1. 仓库发货量不多于ava库存 (可以去掉)

  1. idx_stc = (prod_id, color_id, size, wh_id)
  2. m.addConstr(QOS[idx_stc] <= I0[idx_stc]['ava'])

2. 目标库存为零不补货(这里的目标库存是对size求和的单品库存)

  1. ITS = sum(IT[prod_id, color_id, size, store_id] for size in PI[prod_id][color_id]['size'])
  2. if ITS == 0:
  3. for size in PI[prod_id][color_id]['size']:
  4. idx_stc = (prod_id, color_id, size, store_id)
  5. m.addConstr(QIS[idx_stc] == 0)

3. 给 ID 和 IDP 赋值

  1. m.addConstr(ID[idx_stc] == abs_(I[idx_stc] - IT[idx_stc]))
  2. m.addConstr(IDP[idx_sks] == max_([ID[prod_id, color_id, size, store_id] for store_id in SI]))

4. 包裹的容量和数量的限制

若包裹容量为负,即QPB=0,则数量为0
若包裹容量为正,即QPB=1,则数量至少为1

  1. idx_ptp = (wh_send_id, store_rec_id)
  2. m.addConstr(QSP[idx_ptp] + M * (1 - QPB[idx_ptp]) >= 1)
  3. m.addConstr(QSP[idx_ptp] + M * QPB[idx_ptp] >= 0)
  4. m.addConstr(QSP[idx_ptp] - M * QPB[idx_ptp] <= 0)

补货优化后的参数数据更新

Updating inventory and io markers

I0

  1. 门店
    • ava = ava + QIS
    • sum = sum + QIS
  2. 仓库:
    • ava = ava - QOS
    • sum = sum - QOS

I0

  1. has_in = 1 if QIS > 0

Marking fullsize store of each sk

W0B = cop.mark_fullsize_init(MS, I0_aft_rep, QSF)

  1. W0B : dict
  2. Whether or not initial inventory full-size group number is positive
  3. If initial inventory is 0, W0B is 1
  4. key : prod_id, color_id, store_id
  5. value : bool
  1. 首先统计(p_id,c_id,store_id)对size求和的总sum库存,若总sum库存为零,则WOB = 1
  2. 若总库存不为零,则继续考虑由QSF确定的齐码组长度取判断,在这个区间内的每个尺码是否sum库存大于零,是则设为1
  3. 否则为零

constructing virtual stores

VSI = {'LZ001': {}}
1. 将虚拟门店的sum和ava库存设为0
2. 将CTQ(发送和接收)设为 1000
3. 将CTP (发送和接收)设为 1000

collecting distinct set

dist_set = set(SI中所有门店所在的dist)


同区域间调拨优化 trans_milp_solver

参数

  1. MINGap: 0.03

variable

decision var

  1. # Q : dict
  2. # Replenishment quantity of each sks/send_warehouse/receive_store
  3. # key : prod_id, color_id, size, wh_send_id, store_rec_id
  4. # value : replenishment quantity
  5. Q[idx_rep] = m.addVar(obj=0, vtype=GRB.INTEGER, name=var_name)

assistant vars

此处输入图片的描述
此处输入图片的描述

IB : dict
     Whether or not end inventory is positive of each sks/store
     key : prod_id, color_id, size, store_id
     value : bool

IS : dict
     End inventory after transferring of each skc/store
     key : prod_id, color_id, store_id
     value : end inventory

ISB : dict
      Whether or not end inventory is positive of each skc/store
      key : prod_id, color_id, store_id
      value : bool

NW : dict
     End inventory full-size group number of each skc/store
     key : prod_id, color_id, store_id
     value : full-size group number

WB : dict
     Whether or not end inventory full-size group number is positive of each skc/store.
     If end inventory is 0, WB is 1
     key : prod_id, color_id. store_id
     value : bool

obj

1. cost by replenishment quantity

idx_trans = (prod_id, color_id, size, wh_send_id, store_rec_id)
expr.addTerms(CTQ[idx_trans], Q[idx_trans])

2. cost by replenishment quantity

idx_ptp = (wh_send_id, store_rec_id)
expr.addTerms(CTP[idx_ptp], QPB[idx_ptp])

3. cost by end-target inventory mismatch

idx_stc = (prod_id, color_id, size, store_id)
expr.addTerms(CID[idx_stc], ID[idx_stc])

4. cost by the maximal end-target inventory mismatch

idx_sks = (prod_id, color_id, size)
expr.addTerms(CDP[idx_sks], IDP[idx_sks])

equation

1. 某门店的接收等于所有仓库的发出

  1. QIS[idx_stc] == quicksum(Q[prod_id, color_id, size, store_send_id, store_id] for store_send_id in DVSI if store_send_id != store_id))

2. 某仓库的发出等于所有门店的接收

  1. QOS[idx_stc] == quicksum(Q[prod_id, color_id, size, store_id, store_rec_id] for store_rec_id in DVSI if store_rec_id != store_id))

3. Sum transferring quantity of each package

  1. idx_ptp = (wh_send_id, store_rec_id)
  2. m.addConstr(QSP[idx_ptp] ==
  3. quicksum(Q[prod_id, color_id, size, store_send_id, store_rec_id]
  4. for prod_id in PI
  5. for color_id in PI[prod_id]
  6. for size in PI[prod_id][color_id]['size']))

4. End inventory after transferring of each sks/store

  1. store_id in SI:
  2. idx_stc = (prod_id, color_id, size, store_id)
  3. m.addConstr(I[idx_stc] == I0[idx_stc]['sum'] + QIS[idx_stc] - QOS[idx_stc])

5. End inventory after transferring of each sks/store

  1. idx_skcs = (prod_id, color_id, store_id)
  2. m.addConstr(IS[idx_skcs] == quicksum(I[prod_id, color_id, size, store_id]
  3. for size in PI[prod_id][color_id]['size']))

Constraints

1. 仓库发货量不多于ava库存 (可以去掉)

  1. idx_stc = (prod_id, color_id, size, wh_id)
  2. m.addConstr(QOS[idx_stc] <= I0[idx_stc]['ava'])

2. 不能同时调出调入


    • QIB = 0 --> QIS = 0
    • QIB = 1 --> QIS >= 1

    • QOB = 0 --> QOS = 0
    • QOB = 1 --> QOS >= 1

    • QIB + QOB <=1
    • QIB[idx_stc] + IO[idx_stc]['has_out'] <= 1
    • QOB[idx_stc] + IO[idx_stc]['has_in'] <= 1

3. 目标库存为零不补货(这里的目标库存是对size求和的skc库存)

  1. ITS = sum(IT[prod_id, color_id, size, store_id] for size in PI[prod_id][color_id]['size'])
  2. if ITS == 0:
  3. for size in PI[prod_id][color_id]['size']:
  4. idx_stc = (prod_id, color_id, size, store_id)
  5. m.addConstr(QIS[idx_stc] == 0)

4. 给 ID 和 IDP 赋值

  1. m.addConstr(ID[idx_stc] == abs_(I[idx_stc] - IT[idx_stc]))
  2. m.addConstr(IDP[idx_sks] == max_([ID[prod_id, color_id, size, store_id] for store_id in SI]))

5. Total full-size group number of end inventory

  1. idx_skcs = (prod_id, color_id, store_id)
  2. IB的引入是为了得到变量NS
    • IB = 0 --> I = 0
    • IB = 1 --> I >= 1
  3. ISB的引入是为了确定WB(ISB=0,WB=1)以及和WB一起确定NW
    • ISB = 0 --> IS = 0
    • ISB = 1 --> IS >= 1
  4. 增加二分量IFB,和整数变量NS
    • IFB[i] 即标记尺码组MS[i,i+QSF]是否齐码
    • NS == quicksum(IB[prod_id, color_id, size, store_id] for size in size_grp)),即上面对应的尺码组有多好个是有库存(trans后)的
  5. 简单的判断,即只有NS=QSF时,才有IFB=1
    • IFB = 0 --> NS <= QSF - 1
    • IFB = 1 --> NS = QSF
  6. NW[idx_skcs] == quicksum(IFB.values()) (num of whole_size)
  7. ISB[inx_skcs] == 1 -->
    • WB = 1 --> NW >= 1
    • WB = 0 --> NW = 0
  8. ISB[idx_skcs] == 0) >> (WB[idx_skcs] == 1))
    这里是与W0B的计算对应上

6. Initial full-size stores must remain to be full-size

  1. idx_skcs = (prod_id, color_id, store_id)
  2. m.addConstr(WB[idx_skcs] >= W0B[idx_skcs])

7. Transferring package number

*8. For each store, Transferring out and in package numbers are limited


同区域间调拨后更新数据

Updating inventory and io markers

I0

  1. ava = ava - QOS
  2. sum = sum + QIS - QOS

I0

  1. has_in = 1 if QIS > 0
  2. has_out = 1 if QOS > 0

picking out products to be transforred crossing districts(此处只涉及到调往虚拟店)

  1. I_exc = 调往virtual store 的sks的数目
  2. I_exc > 0:
    • 将sks对应的prod_id存入PI_rem: PI_rem[prod_id] = PI[prod_id]
    • 将sks对应的skc以及所有门店id组成的idx_skcs对应的MS存入MS_rem:
  1. for store_id in SI:
  2. idx_skcs = (prod_id, color_id, store_id)
  3. MS_rem[idx_skcs] = MS[idx_skcs]

updating transferring cost

将区域间发生调拨的门店间的CTP值设为0

  1. prod_id, color_id, size, store_send_id, store_rec_id = idx_trans
  2. if (Q_trans[idx_trans] > 0) and (store_send_id, store_rec_id) in CTP:
  3. CTP[store_send_id, store_rec_id] = 0

Marking fullsize store of each sk

W0B = cop.mark_fullsize_init(MS_rem, I0_aft_rep, QSF)

  1. W0B : dict
  2. Whether or not initial inventory full-size group number is positive
  3. If initial inventory is 0, W0B is 1
  4. key : prod_id, color_id, store_id
  5. value : bool
  1. 首先统计(p_id,c_id,store_id)对size求和的总sum库存,若总sum库存为零,则WOB = 1
  2. 若总库存不为零,则继续考虑由QSF确定的齐码组长度取判断,在这个区间内的每个尺码是否sum库存大于零,是则设为1
  3. 否则为零

跨区域间调拨优化及统计分析

不区分门店区域,同时将空的虚拟仓传入函数

updating transferring quantity and inventory

将两次trans优化得到的变量值进行求和得到最终的变量值,再计算库存

statistical analysis

  1. 将变量值存入df(大于零)

    • Q_rep_df, Q_trans_df
    ('prod_id', 'color_id', 'size', 'org_send_id', 'org_rec_id') quant_mov
    >0
    • IPR_df <-- I0_aft_rep
    ('prod_id', 'color_id', 'size', 'org_send_id', 'org_rec_id') quant_mov
    >0
  2. 对得到的Q_trans_df进行补充其他信息
    为收发方的store添加city_id(city_send(rec)_id)和dist_id(dist_send(rec)_id)

  3. IRP_df, ITR_df <-- I0_aft_rep/trans
    此处输入图片的描述

  4. to be continue

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注