供应链物流是货品的供应商采购、仓库存储、仓间库存调拨、履约送货等一系列货品流转到用户的过程,其中各个环节会涉及到成本、时效等优化。供应链智能补货项目是货品从供应商采购货品的环节,主要考虑的是货品的缺货成本和持货成本平衡的问题,两者常用的考量分别是周转和缺货率。当库存金额保持在一个较高水位的时候,缺货率就会比较低,这样会减少由于缺货导致的销售损失,但是较高的库存金额会造成库存的积压、采购资金的占用及仓库的管理费用增加;相反,当库存水位较低,缺货率会升高进而影响相应的销售。所以,周转和缺货在补货问题中是一对平衡指标,找到合理的补货策略和相应的参数,使得周转、缺货得到我们理想的最大值是库存优化需要解决的问题。
一、供应链库存策略(inventory policy)
供应链是一个充满各种不确定性的系统,但在库存策略中一般关注两个不确定性因素:需求和订货提前期。根据需求和订货提前期两个不确定性因素,可以将供应链分为:
(1)确定性供应链:需求和订货提前期都是固定不变的;
(2)不确定性供应链:需求随机但订货提前期固定、需求和订货提前期都随机。
我们将从最简单的确定性供应链开始,假设需求和订货提前期都固定,介绍确定性供应链的库存策略,再逐步放宽假设条件介绍不确定性供应链的库存策略。供应链的库存策略(inventory policy)决策主要考虑两个问题:订货多少?什么时候订货?根据库存盘点的完成时间,库存策略分为两种类型:连续盘点(Continuous review policies)库存策略和周期盘点(Periodic review policies)库存策略。
1.1 连续盘点库存策略
连续盘点(Continuous review policies)库存策略:基于1个固定阈值(threshold)补货,只要产品库存下降至(或低于)阈值,就向供应商订货一定数量的产品。这个阈值称为再订货点(Reorder Point,ROP)。这种固定再订货点的库存策略记为(s,Q),s表示再订货点,Q表示固定订货数量。
固定再订货点(s,Q)策略的特点是订货数量Q固定。如下图所示,假设不存在订货提前期(即一下订单就即刻到货),再订货点s=3表示只要库存数量降至3时就进行补货,每次订货数量Q=10,库存数量即刻就增加到s+Q=3+10=13。
连续盘点库存策略的优势是可以随时订货,发生缺货的风险很低;对于需要密切关注的重要产品的库存管理,连续盘点是一个很好的库存策略。但其劣势则是较难整合多个产品或供应商进行订货,以及随时可以向供应商订货、客户每次购买一种产品的假设在实践中通常不成立。
1.2 周期盘点库存策略
周期盘点(Periodic review policies)库存策略:根据固定时间周期盘点库存,每次向供应商订货时,将产品库存数量补货到最大订货水平(Order Up-to Level)。周期盘点库存策略通常记为固定周期补货(R,S)策略,其中R是补货周期,S是最大订货水平。
固定周期补货(R,S)策略的特点是每两次补货订单的间隔时间R相同。如下图所示,假设不存在订货提前期(即一下订单就即刻到货),每间隔R=5个时期就补货一次,每次都将库存数量补货到最大订货水平S=13。
周期盘点库存策略是最常见的库存策略,它的优势是有助于简化企业和供应商的运营,是很多ERP/DRP计划都采用的库存策略。但其劣势是发生缺货的风险更大,因为如果发生缺货的话,企业不能在两个补货周期间隔内进行补货,必须等到下个补货周期的时间点才能补货。
1.3 订货提前期
对于连续盘点和周期盘点库存策略而言,订货提前期都是影响什么时候订货以及订货多少的关键因素。订货提前期(Lead Time)是指企业向供应商下单订货之后,需要等待一定时间才能收到订单的货物,只有订单到货后才可以满足客户需求。
二、确定性供应链的固定再订货点(s,Q)策略
现在分析确定性供应链的固定再订货点(s,Q)策略。假设每个时期的需求固定(如每天的需求都是10),以及订货提前期固定不变(如每次订货提前都是10天)。有了这2个假设,我们就构建了一个具有确定需求和提前期的简单供应链。
2.1 再订货点s
再订货点(s,Q)库存策略需要决策的问题之一是:什么时候订货?定义\(L\)(天)为订货提前期,由于假设每天的需求\(d\)都是固定不变的,那么提前期\(L\)内的需求就等于\(d_L=L\times d\) ,意味着库存数量降至提前期内的需求\(d_L\)时就必须订货Q,所以再订货点\(s=d_L\)。再订货点同时也是最低库存。最低库存是一种周转库存,是为满足需求必须持有的最低库存,是理论上肯定会用到的库存。
$$最低库存=再订货点s=提前期需求d_L$$
2.2 订货数量Q
EOQ(经济批量)公式
\[Q^*=\sqrt{\frac{2kD}{h}} \]其中表示\(Q/2\)为平均库存,\(h\)表示单位库存持有成本,\(K\)表示单次订货的交易成本,\(c_T\) 表示单位库存的采购成本。因为假设需求\(D\)固定,那么一定时期内的订货总次数就是\(D/Q\)次。
由于EOQ模型的假设性太强,实践中不建议使用EOQ模型计算最优订货数量,常常使用最高库存定量法计算订货数量,设定一个企业可以承受的最高库存数量S,则
如企业将可承受的最高库存设定为最低库存的2倍,那么最高库存计算如下:
\[最高库存S=最低库存×2=再订货点s+最低库存 \]二、供应链(s,S)管理策略理论
考虑单产品多周期的库存管理问题,寻求最优的订货策略,使得总体成本最低。在每个周期,如果订货过多,导致库存多余,会产生库存持有成本;如果订货较少,导致缺货,会有延迟交货成本(这里假设缺货的情况下不会丢失订单)。
2.1 模型符号说明
参数 | 含义 |
---|---|
\(c_H\) | 单位产品库存持有成本 |
\(c_P\) | 单位产品延迟交付成本 |
\(c\) | 单位产品订货成本 |
\(D\) | 单周期内需求数,一个随机变量 |
\(\Phi\) | 单周期内需求分布,密度函数为\(\phi\),均值为\(\mu\) |
\(v_T\) | 最后一个周期\(N\) 的成本函数 |
\(\alpha\) | 折扣因子 |
合理假设 | 含义 |
---|---|
\(c_P>(1-\alpha)c\) | 避免一直不订货成为最优策略 |
\(c_H+(1-\alpha)c>0\) | 技术要求,这个通常是满足的,因为正常情况下都有 \(c_H \ge 0\) |
持有/缺货成本函数:
\[\mathcal{L}(x):=c_Hx^++c_P(-x)^+ \]动态规划模型
要素 | 变量 | 含义 |
---|---|---|
状态 | \(x\) | 周期 \(t\) 开始时的库存水平 |
策略 | \(y\) | 周期 \(t\) 订货后的库存水平 |
迁移状态 | \(x_{t+1}\) | \(x_{t+1}=y-D\),下一阶段开始时的库存水平 |
Bellman 方程:
\[f_t(x)=\min_{y\ge x} \{ G_t(y) - cx \} \tag{1} \]其中
\[G_t(y) = cy + E_{\Phi}\mathcal{L}(y-D)+\alpha E_{\Phi}f_{t+1}(y-D) \tag{2} \]2.2 证明路线图
\[f_{t+1} \text{ 凸} \stackrel{(T_1)}\Longrightarrow G_t \text{ 凸} \stackrel{(T_2)}\Longrightarrow f_t \text{ 凸} \tag{text{preservation}} \]\[G_t \text{ 凸} \stackrel{(T_3)}\Longrightarrow \text{base stock policy 在周期 } t \text{ 最优} \tag{text{attainment}} \]命题 | 思路 |
---|---|
\(T_1\) | $c_H+c_P\ge0 $ 保证了分段线性函数\(\mathcal{L}(y-D)\)是凸的;而凸函数的期望又是凸的。这样\(G_t\) 就是三个凸函数之和 |
\(T_2\) | 这里用到了一个 Minimization 保持凸性的定理。 \(\max\)保持凸性比较简单,任意多个凸函数,对其取 \(\max\)结果都是凸的;但对于\(\min\)就没那么简单了,差别就在于取\(\min\)的范围是跟 \(x\) 有关的。具体来讲,每次取\(\min\)都是在\(x\)相关的一个凸集上取的, \(f(x)=\mathop{\min}\limits_{y \in Y(x)}g(x,y)\),其中\(Y(x)\)是一个与 \(x\)相关的凸集(比如这里的 \(y\ge x\)),而对 \(\max\)而言这里 \(Y(x)=Y\)与\(x\)无关。 |
\(T_3\) | 这个比较简单,\(arg \mathop{\min}\limits\_y G_t(y)\)就是 base stock level (凸性保证了它的存在唯一性),注意它是与\(x\)无关的 |
由上面的技术路线图,通过迭代法,就可以证明:在每个周期 t t t,base stock policy 都是最优的。
2.3 (s,S)-policy
在上面的模型中,如果考虑订货的固定成本,则 base stock policy 就将变为\((s,S)-policy\)。假设 每次订单的固定成本为\(K\),则此情形下\(V∗: K-凸函数\), \(\Delta^*:(s,S)-policy\)。
当模型中每笔订单具有固定成本\(K\)时,\(x<S=\min G_t(y)\) 时, 最佳的策略就不是将库存量增加到\(S\),需要比较节约的成本是否要比订货固定成本 \(K\)要大。此时,如果就要求\(G_t(y)\) 存在一个点\(s<S\),使得\(G_t(s)=G_t(S) + K\),并且满足\(K-凸时,对于\) x<s \(,对应的最佳策略就是将库存量增加到\)S$,否则不下订单。
此时的最优方程为:
\[f_t(x)=-cx +\min_{y\ge x} \{ G_t(x), K+G_t(y) \} \]如果 \(G_t(y)\)是 \(K-凸\)的,可以证明\(f_t(x)\)也是\(K-凸\)的,并且此时 \((s,S)-policy\)是最优的。
三、Python仿真固定再订货点(s,Q)策略
我们将按两种思路仿真固定再订货点(s,S)策略:(1)根据历史需求数据拟合每天的固定需求\(D\),每天的在库库存按日均需求\(D\)消耗;(2)每天的在库库存按照每天的真实需求消耗。这样对比,可以分析每天需求固定的假设对固定再订货点(s,Q)策略的影响。
3.1 固定再订货点(s,Q)策略仿真
固定再订货点(s,Q)策略仿真思路如图所示
第1步,根据不同区域不同产品的历史需求数据,拟合每天的固定需求D,具体方法为:以提前期L(天)计算历史需求数据的L天移动平均需求,然后对所有的L天移动平均需求计算平均值,得到的平均值就作为每天的日均需求D。
第2步,计算再订货点s(最低库存)=日均需求D×提前期L。
第3步,计算可承受的最高库存为再订货点s(最低库存)的2倍,即最高库存=最低库存×2=再订货点s+最低库存。
第4步,如果按固定的日均需求D消耗库存:
(1)当在库存期末库存低于再订货点s时,进行订货;
(2)订货数量Q等于最高库存(也可以是最高库存-在库期末库存)
第5步,如果按每日真实需求d消耗库存:
(1)当在库存期末库存低于再订货点s时,上期期末库存>0且当天期末库存<0,以及上期期末库存<-再订货点s且当期期末库存>-最高库存,都进行订货;考虑后两种情形的订货,是因为按真实需求d销售库存必定会出现缺货情况(在库期末库存为负),如果这两种情况不进行订货的话,就会出现后面所有的库存都为负的情形,仿真就没有意义。
(2)订货数量Q等于最高库存-在库期末库存,即订货时要把前面的缺货数量也考虑一起补货。
第6步,计算在库库存:假设初始库存等于最高库存,第1天的在库期初库存等于最高库存,在库期末库存等于在库期末库存-日均需求D(或者每天真实需求d);第2天开始的在库期初库存等于上一天的期末库存+当天的到货数量-日均需求,在库期末库存等于在库期末库存-日均需求D(或者每天真实需求d)。
第7步,计算库存水平,它和在库库存不同,包含了在库期末库存+订货数量Q,即某天下订单订货数量Q但还没到货,将其视为在途库存Q加入到库存水平中。每天的库存水平=上期库存水平+当期订货数量Q-日均需求D(或者每天真实需求d)。
3.2 固定再订货点(s,Q)Python仿真
数据文件下载https://wwxh.lanzoum.com/iEy9V0w50lti密码:7kzz
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
import warnings
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings('ignore')
#导入数据
ori_data = pd.read_excel("InventoryData_Outlier.xlsx")
ori_data = ori_data.fillna(0)
# 添加“年份”字段
ori_data['年份'] = ori_data.loc[:,'日期'].dt.year
#生成不同区域不同产品的日均需求数据
# 参数product表示产品:产品A、产品B,参数warehouse表示区域:华北、华南、华东,参数LD:表示订货提前期,为大于等于0的正数
def data_sim(product,warehouse,LD):
#筛选不同区域不同产品的历史销售数据
data_sim = ori_data[(ori_data.产品==product) & (ori_data.区域仓==warehouse)]
# 新增“移动平均需求”字段:以订货提前期LD为长度计算LD天的移动平均需求,并将NA值填充为0
data_sim['移动平均需求'] = data_sim.loc[:,'需求'].rolling(window=LD).mean().fillna(0)
# 新增“日均需求”字段:对所有的LD天移动平均需求进行平均,作为每天的日均需求D
data_sim['日均需求'] = round(data_sim.loc[:,'移动平均需求'].mean(),0)
# 新增“再订货点s”字段:日均需求D乘以订货提前期LD
data_sim['再订货点s'] = data_sim.loc[:,'日均需求']*LD
# 新增“最高库存”字段:最高库存是再订货点s(最低库存)的2倍
data_sim['最高库存'] = data_sim.loc[:,'再订货点s']*2
# 将“日期”字段设置为索引下标index
data_sim.set_index('日期',inplace=True)
# 筛选相关字段
return data_sim.filter(items=['产品','区域仓','年份','需求','日均需求','再订货点s','最高库存'])
# 定义order_sim函数生成不同区域不同产品,任意提订货提前期下的订货数据
# 参数product表示产品:产品A、产品B,参数warehouse表示区域:华北、华南、华东,参数LD:表示订货提前期,为大于等于0的正数
# 参数Dtype表示库存消耗的类型:默认'simud’表示库存按拟合的日均需求D消耗,'actud'表示库存按每天真实需求d消耗
def order_sim(product,warehouse,LD,Dtype='simud'):
# 调用函数data_sim()生成不同区域不同产品,任意提订货提前期下的日均需求、再订货点s和最高库存数据
df = data_sim(product,warehouse,LD)
# 条件判断,若参数Dtype == 'simud',采取库存按固定不变的日均需求D消耗的逻辑进行仿真
if Dtype == 'simud':
# 遍历每天的数据
for num in range(0,len(df.index)):
# 如果是第1天,则
if num == 0:
# 初始在库期初库存等于第1天的最高库存
df.loc[df.index[num],'在库期初库存'] = df.最高库存[num]
# 第1天的在库期末库存=第1天的在库期初库存减去当天的日均需求D
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] - df.日均需求[num]
# 第1天的库存肯定是足够的,不会降至再订货点s,所以不订货,添加字段“是否订货”为0表示第1天不订货
df.loc[df.index[num],'是否订货'] = 0
# 若不订货,则添加第1天的订货批量Q为0
df.loc[df.index[num],'订货批量Q'] = 0
# 第1天的订货批量Q为0,则经过订货提前期LD天后的到货数量也为0
df.loc[df.index[num+LD],'到货数量'] = 0
# 添加第1天的“库存水位”,等于第1天的订货批量Q+第1天的在库期初库存0-第1天的日均需求D;库存水位表示在库库存和在途库存的总和
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.在库期初库存[num] - df.日均需求[num]
# 如果是第2天,直至倒数LD天(数据表长度减去LD,是因为最后LD行的数据无法计算到货,为NA值,故直接截去)
elif 0 < num < len(df.index)-LD:
# 在第num天添加字段“在库期初库存”:当天(num)的在库期初库存等于上一天(num-1)的期末库存
df.loc[df.index[num],'在库期初库存'] = df.在库期末库存[num-1]
# 在第num天添加字段“在库期末库存”:当天(num)的在库期初库存+当天(num)的到货数量-当天(num)的日均需求D
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] + df.到货数量[num]- df.日均需求[num]
# 判断上一天的在库期末库存是否等于再订货点s,且当天的在库期末库存是否小于再订货点s,如果是则订货
if (df.在库期末库存[num-1] == df.再订货点s[num-1])&(df.在库期末库存[num] < df.再订货点s[num]):
# 在第num天添加字段"'是否订货",值为1表示订货
df.loc[df.index[num],'是否订货'] = 1
# 在第num天添加字段"订货批量Q",订货数量等于最高库存
df.loc[df.index[num],'订货批量Q'] = df.最高库存[num]
# 在第num+LD天添加字段"到货数量",表示经过订货提前期LD天后收到订货批量Q
df.loc[df.index[num+LD],'到货数量'] = df.订货批量Q[num]
# 在第num天添加字段"库存水位":当天的订货批量Q+上一天的库存水位-当天的日均需求;表示在库库存和在途库存总和
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.日均需求[num]
# 其他情况则不订货,是否订货、订货批量Q和到货数量都为0
else:
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.日均需求[num]
# 将到货数量为na的值替换为0
df['到货数量'] = df.loc[:,'到货数量'].fillna(0)
# 条件判断,若参数Dtype == 'actud',采取库存按每天真实需求d消耗的逻辑进行仿真
# 计算逻辑基本和Dtype == 'simud'的逻辑相同
elif Dtype == 'actud':
for num in range(0,len(df.index)):
if num == 0:
df.loc[df.index[num],'在库期初库存'] = df.最高库存[num]
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] - df.需求[num]
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.在库期初库存[num] - df.需求[num]
elif 0 < num < len(df.index)-LD:
df.loc[df.index[num],'在库期初库存'] = df.在库期末库存[num-1]
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] + df.到货数量[num]- df.需求[num]
# 发生订货的情况有3种,(1)上一天的在库期末库存大于再订货点s,且上当天的在库期末库存小于等于再订货点s;
# (2)上一天的在库期末库存大于0,且当天的在库期末库存小于等于0,表示开始发生缺货的当天进行1次补货;
# (3)上一天的在库期末库存小于-再订货点s,且当天的在库期末库存大于-最高库存,也在当天进行订货,这种情况是因为需求
# 不确定,有可能连续好多天都缺货,所以除了缺货开始当天进行补货外,还要在缺货数量继续扩大的情况下再进行订货。
if ((df.在库期末库存[num-1] > df.再订货点s[num-1])&(df.在库期末库存[num] <= df.再订货点s[num]))|\
((df.在库期末库存[num-1] > 0)&(df.在库期末库存[num] <= 0))|\
((df.在库期末库存[num-1] < -df.再订货点s[num-1])&(df.在库期末库存[num] > -df.最高库存[num])):
df.loc[df.index[num],'是否订货'] = 1
# 订货批量Q等于最高库存-在库期末库存
df.loc[df.index[num],'订货批量Q'] = df.最高库存[num] - df.在库期末库存[num]
df.loc[df.index[num+LD],'到货数量'] = df.订货批量Q[num]
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.需求[num]
else:
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.需求[num]
df['到货数量'] = df.loc[:,'到货数量'].fillna(0)
# 新增“订货批次”字段:先对“是否订货”字段累计求和,再把订货批量Q<=0的订货批次值替换为0
df['订货批次'] = df.loc[:,'是否订货'].cumsum()
df['订货批次'] = np.where(df.loc[:,'订货批量Q'] > 0,df.loc[:,'订货批次'],0)
# 将最后LD天数据截去
table = df.iloc[0:len(df.index)-LD]
return table# 定义order_sim函数生成不同区域不同产品,任意提订货提前期下的订货数据
# 参数product表示产品:产品A、产品B,参数warehouse表示区域:华北、华南、华东,参数LD:表示订货提前期,为大于等于0的正数
# 参数Dtype表示库存消耗的类型:默认'simud’表示库存按拟合的日均需求D消耗,'actud'表示库存按每天真实需求d消耗
def order_sim(product,warehouse,LD,Dtype='simud'):
# 调用函数data_sim()生成不同区域不同产品,任意提订货提前期下的日均需求、再订货点s和最高库存数据
df = data_sim(product,warehouse,LD)
# 条件判断,若参数Dtype == 'simud',采取库存按固定不变的日均需求D消耗的逻辑进行仿真
if Dtype == 'simud':
# 遍历每天的数据
for num in range(0,len(df.index)):
# 如果是第1天,则
if num == 0:
# 初始在库期初库存等于第1天的最高库存
df.loc[df.index[num],'在库期初库存'] = df.最高库存[num]
# 第1天的在库期末库存=第1天的在库期初库存减去当天的日均需求D
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] - df.日均需求[num]
# 第1天的库存肯定是足够的,不会降至再订货点s,所以不订货,添加字段“是否订货”为0表示第1天不订货
df.loc[df.index[num],'是否订货'] = 0
# 若不订货,则添加第1天的订货批量Q为0
df.loc[df.index[num],'订货批量Q'] = 0
# 第1天的订货批量Q为0,则经过订货提前期LD天后的到货数量也为0
df.loc[df.index[num+LD],'到货数量'] = 0
# 添加第1天的“库存水位”,等于第1天的订货批量Q+第1天的在库期初库存0-第1天的日均需求D;库存水位表示在库库存和在途库存的总和
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.在库期初库存[num] - df.日均需求[num]
# 如果是第2天,直至倒数LD天(数据表长度减去LD,是因为最后LD行的数据无法计算到货,为NA值,故直接截去)
elif 0 < num < len(df.index)-LD:
# 在第num天添加字段“在库期初库存”:当天(num)的在库期初库存等于上一天(num-1)的期末库存
df.loc[df.index[num],'在库期初库存'] = df.在库期末库存[num-1]
# 在第num天添加字段“在库期末库存”:当天(num)的在库期初库存+当天(num)的到货数量-当天(num)的日均需求D
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] + df.到货数量[num]- df.日均需求[num]
# 判断上一天的在库期末库存是否等于再订货点s,且当天的在库期末库存是否小于再订货点s,如果是则订货
if (df.在库期末库存[num-1] == df.再订货点s[num-1])&(df.在库期末库存[num] < df.再订货点s[num]):
# 在第num天添加字段"'是否订货",值为1表示订货
df.loc[df.index[num],'是否订货'] = 1
# 在第num天添加字段"订货批量Q",订货数量等于最高库存
df.loc[df.index[num],'订货批量Q'] = df.最高库存[num]
# 在第num+LD天添加字段"到货数量",表示经过订货提前期LD天后收到订货批量Q
df.loc[df.index[num+LD],'到货数量'] = df.订货批量Q[num]
# 在第num天添加字段"库存水位":当天的订货批量Q+上一天的库存水位-当天的日均需求;表示在库库存和在途库存总和
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.日均需求[num]
# 其他情况则不订货,是否订货、订货批量Q和到货数量都为0
else:
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.日均需求[num]
# 将到货数量为na的值替换为0
df['到货数量'] = df.loc[:,'到货数量'].fillna(0)
# 条件判断,若参数Dtype == 'actud',采取库存按每天真实需求d消耗的逻辑进行仿真
# 计算逻辑基本和Dtype == 'simud'的逻辑相同
elif Dtype == 'actud':
for num in range(0,len(df.index)):
if num == 0:
df.loc[df.index[num],'在库期初库存'] = df.最高库存[num]
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] - df.需求[num]
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.在库期初库存[num] - df.需求[num]
elif 0 < num < len(df.index)-LD:
df.loc[df.index[num],'在库期初库存'] = df.在库期末库存[num-1]
df.loc[df.index[num],'在库期末库存'] = df.在库期初库存[num] + df.到货数量[num]- df.需求[num]
# 发生订货的情况有3种,(1)上一天的在库期末库存大于再订货点s,且上当天的在库期末库存小于等于再订货点s;
# (2)上一天的在库期末库存大于0,且当天的在库期末库存小于等于0,表示开始发生缺货的当天进行1次补货;
# (3)上一天的在库期末库存小于-再订货点s,且当天的在库期末库存大于-最高库存,也在当天进行订货,这种情况是因为需求
# 不确定,有可能连续好多天都缺货,所以除了缺货开始当天进行补货外,还要在缺货数量继续扩大的情况下再进行订货。
if ((df.在库期末库存[num-1] > df.再订货点s[num-1])&(df.在库期末库存[num] <= df.再订货点s[num]))|\
((df.在库期末库存[num-1] > 0)&(df.在库期末库存[num] <= 0))|\
((df.在库期末库存[num-1] < -df.再订货点s[num-1])&(df.在库期末库存[num] > -df.最高库存[num])):
df.loc[df.index[num],'是否订货'] = 1
# 订货批量Q等于最高库存-在库期末库存
df.loc[df.index[num],'订货批量Q'] = df.最高库存[num] - df.在库期末库存[num]
df.loc[df.index[num+LD],'到货数量'] = df.订货批量Q[num]
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.需求[num]
else:
df.loc[df.index[num],'是否订货'] = 0
df.loc[df.index[num],'订货批量Q'] = 0
df.loc[df.index[num+LD],'到货数量'] = 0
df.loc[df.index[num],'库存水位'] = df.订货批量Q[num] + df.库存水位[num-1] - df.需求[num]
df['到货数量'] = df.loc[:,'到货数量'].fillna(0)
# 新增“订货批次”字段:先对“是否订货”字段累计求和,再把订货批量Q<=0的订货批次值替换为0
df['订货批次'] = df.loc[:,'是否订货'].cumsum()
df['订货批次'] = np.where(df.loc[:,'订货批量Q'] > 0,df.loc[:,'订货批次'],0)
# 将最后LD天数据截去
table = df.iloc[0:len(df.index)-LD]
return table
# 定义order_plot函数画出不同区域不同产品,任意订货提前期LD的再订货点(s,Q)策略库存仿真图
def order_plot(product,warehouse,LD,Dtype='simud'):
# 生成订货提前期LD天的订货数据
plotdata_1LD = order_sim(product,warehouse,LD,Dtype)
# 生成2倍订货提前期LD天的订货数据
plotdata_2LD = order_sim(product,warehouse,2*LD,Dtype)
# 生成3倍订货提前期LD天的订货数据
plotdata_3LD = order_sim(product,warehouse,3*LD,Dtype)
plt.figure(figsize=(16,18),dpi=100)
ax1 = plt.subplot(3,1,1)
ax1.plot(plotdata_1LD.index,plotdata_1LD.loc[:,'在库期末库存'],color='gray',linewidth=1.5,label='在库期末库存')
ax1.plot(plotdata_1LD.index,plotdata_1LD.loc[:,'库存水位'],color='steelblue',linewidth=1.5,label='库存水位(在库+在途)')
ax1.plot(plotdata_1LD.index,plotdata_1LD.loc[:,'再订货点s'],color='red',alpha=0.7,linestyle='--',label='再订货点s')
ax1.text(plotdata_1LD.index[-30],plotdata_1LD.loc[:,'再订货点s'][-30],
f'再订货点{plotdata_1LD.再订货点s[-30]}',fontdict={'fontsize':12},color='red')
ax1.set_ylim(plotdata_1LD.loc[:,'在库期末库存'].min()*0.8,plotdata_1LD.loc[:,'库存水位'].max()*1.2)
plt.xticks(color='white',fontsize = 12)
plt.yticks(color='white',fontsize = 12)
plt.title(f'固定订货点(s,Q)补货策略:提前期{LD}天',fontdict={'fontsize':16},color='white')
ax1.legend(loc='upper right',frameon=True)
ax2 = plt.subplot(3,1,2)
ax2.plot(plotdata_2LD.index,plotdata_2LD.loc[:,'在库期末库存'],color='gray',linewidth=1.5,label='在库期末库存')
ax2.plot(plotdata_2LD.index,plotdata_2LD.loc[:,'库存水位'],color='steelblue',linewidth=1.5,label='库存水位(在库+在途)')
ax2.plot(plotdata_2LD.index,plotdata_2LD.loc[:,'再订货点s'],color='red',alpha=0.7,linestyle='--',label='再订货点s')
ax2.text(plotdata_2LD.index[-30],plotdata_2LD.loc[:,'再订货点s'][-30],
f'再订货点{plotdata_2LD.再订货点s[-30]}',fontdict={'fontsize':12},color='red')
ax2.set_ylim(plotdata_2LD.loc[:,'在库期末库存'].min()*0.8,plotdata_2LD.loc[:,'库存水位'].max()*1.2)
plt.xticks(color='white',fontsize = 12)
plt.yticks(color='white',fontsize = 12)
plt.title(f'固定订货点(s,Q)补货策略:提前期{2*LD}天',fontdict={'fontsize':16},color='white')
ax2.legend(loc='upper right',frameon=True)
ax3 = plt.subplot(3,1,3)
ax3.plot(plotdata_3LD.index,plotdata_3LD.loc[:,'在库期末库存'],color='gray',linewidth=1.5,label='在库期末库存')
ax3.plot(plotdata_3LD.index,plotdata_3LD.loc[:,'库存水位'],color='steelblue',linewidth=1.5,label='库存水位(在库+在途)')
ax3.plot(plotdata_3LD.index,plotdata_3LD.loc[:,'再订货点s'],color='red',alpha=0.7,linestyle='--',label='再订货点s')
ax3.text(plotdata_3LD.index[-30],plotdata_3LD.loc[:,'再订货点s'][-30],
f'再订货点{plotdata_3LD.再订货点s[-30]}',fontdict={'fontsize':12},color='red')
ax3.set_ylim(plotdata_3LD.loc[:,'在库期末库存'].min()*0.8,plotdata_3LD.loc[:,'库存水位'].max()*1.2)
plt.xticks(color='white',fontsize = 12)
plt.yticks(color='white',fontsize = 12)
plt.title(f'固定订货点(s,Q)补货策略:提前期{3*LD}天',fontdict={'fontsize':16},color='white')
ax3.legend(loc='upper right',frameon=True)
df=order_sim('产品A','华北',7,'simud')
order_plot('产品A','华北',7,'simud')
plt.show()
print(df)
参考文献
- Nicolas Vandeput, Inventory Optimization: Models and Simulations. Deutsche Nationalbibliothek,2020.
- 【Porteus】S-policy 和 (s,S)-policy
- Python玩转供应链|009 确定性供应链:固定再订货点(s,Q)库存策略
- Porteus E L. Foundations of stochastic inventory theory[M]. Stanford University Press, 2002.