0 问题描述

本报告用于解答如下问题:

0.1 客流条件

下表为A岛全天值机航班计划,假设上座率均为100%,如规定航班起飞前40分钟停止值机,请在前期资料检索基础上确定早上7:00-9:00的客流到达规律。

0.2 客流服务选择

假设离港客流在人工值机柜台办理值机人数占总人数的60%,每个值机柜台单位时间服务旅客数量服从负指数分布,顾客平均服务时间为3分钟(不区分行李是否托运)。

0.3 值机柜台开放策略

值机A岛柜台总数11个。整个A岛只有一个排队,排队区域采取蛇形排队。各时段柜台开放数量的方案如表所示:

0.4 服务需求

按照机场服务标准:95%的国内经济舱旅客值机手续排队及办理时间应不超过10 min。

0.5 开放问题

当前柜台开放方案,请评估其服务效用。
当前方案是否满足服务要求?如不满足,应如何优化排队(单个队伍或多个排队)和柜台开放策略(分时段开放数量),使之满足服务要求,又能够尽可能降低服务成本。(考虑分时段柜台数对顾客人数的适应关系;柜台开放数量无法实时调整,最小调整间隔为15分钟)
服务成本按照每个柜台的开放时间进行计算,结果为各柜台服务成本(单位为人*分钟)之和。

1 模拟客流到达规律

查阅资料可知,对于有固定截止时间的客流,其随时间变化的到达规律呈现先缓慢增加,到达某一时刻后增速明显加快,之后迅速增加至到达高峰,在截至时间前的某一时刻又迅速减小的趋势。

为将这一客流到达规律用分布函数拟合,以便后续建模分析,可以考虑使用Gumbel分布(I类广义极值分布)拟合乘客到达时刻与停止值机时刻的差值。Gumbel分布的概率密度函数如图1所示,和正态分布相比,其特性为呈现一定的偏态,概率密度函数的极值点一侧变化较陡,另一侧变化较缓,因此对客流到达规律可以取得不错的拟合效果。

对Gumbel分布的参数进行标定时,需要先依据经验给客流到达规律设定一些基础规则,如:平均提前60分钟办理检票(即提前100分钟到机场);只剩不到10分钟办理检票的(即提前50分钟以内到机场,易出现无法登机情况)占统计学意义上的小概率0.5%。

参数标定的过程利用蒙特卡洛模拟进行,让计算机对一定范围内的参数进行筛选,输出最符合上述基础规则的参数,最终得到的结果是位置系数μ=0.488,尺度系数β=0.193。在这一Gumbel分布的基础上建立乘客到达时间生成模型(列表arriveTimeList中存储了每一个研究对象到达机场的时刻,以早上7时作为时刻零点):

Python
# 乘客到达时间生成函数:Gumbel分布
def arriveTimeGenerate(checkTime, seatNums):
    # 构造一个默认位生成器
    rng = np.random.default_rng()
    arriveTimeList = []
    for i in range(seatNums):
        # 遵循一定的基础规则构建gumbel分布:
        # 蒙特卡洛模拟标定的结果:loc=0.488, scale=0.193
        arriveTime = rng.gumbel(loc=0.488, scale=0.193, size=None)
        # 生成时间的单位:秒
        arriveTimeList.append(checkTime - int(arriveTime * 3600))
    return arriveTimeList

在建立到达时间模型后,再将所有的研究对象以一定的格式输入模型,以获取早高峰的乘客到达时间序列:

Python
# 只研究8:20至10:50出港的航班,再往后的航班旅客在研究时间段到达率极低(经过多次模拟,其到达率低于0.5%),对结果的影响几乎不存在
departureTimeList = [820, 820, 855, 905, 910, 925, 940, 1005, 1050]
seatNumList = [180, 180, 180, 130, 180, 150, 180, 170, 170]
passengerTimeList = []
for i in range(len(departureTimeList)):
    arriveTimeList = arriveTimeGenerate(timeInvert(departureTimeList[i]), seatNumList[i])
    for item in arriveTimeList:
        # 筛出7-9点到达的乘客:班次乘客总数为1520人,这样筛选得到1190人左右
        if 0 <= item <= 7200:
            # 在人工柜台办理值机的人数占60%
            if np.random.rand() <= 0.6:
                # 利用Gumbel分布已经将登机失败的概率降到了极低(经过多次模拟,未出现登机失败的情况),可以将所有班次旅客的到达时间直接合并分析
                passengerTimeList.append(item)
# 正序排列
passengerTimeList = sorted(passengerTimeList)

2 模拟服务规律

在上一节的客流到达模拟中,已经随机生成了60%研究对象的到达时刻列表,现在往列表中添加预计服务时间的信息。由于每个值机柜台单位时间服务旅客数量服从负指数分布,考虑到旅客样本较大(约700人),为尽量保证服务时间的精确度,可以用正态分布替代泊松分布确定服务时间,将服务时间的精度用秒数表示。具体的服务时间生成模型为:

Python
# 服务时间生成函数:泊松分布
def serveTimeGenerate(passengerTimeList):
    rng = np.random.default_rng()
    passengerList = []
    for item in passengerTimeList:
        # 用正态分布更精细地模拟泊松分布随机数,可以精确到秒
        serveTime = rng.normal(loc=3, scale=1)
        # 防止出现过大和过小的极端情况扰乱结果
        if serveTime > 5:
            serveTime = 5
        elif serveTime < 1:
            serveTime = 1
        passengerList.append([item, round(serveTime * 60 // 1)])
    return passengerList

在建立服务时间模型后,再将该特征添加至乘客到达时间序列中,得到完整的早高峰乘客特征序列:

Python
# 生成完整的乘客特征序列
passengerList = serveTimeGenerate(passengerTimeList)
# 用直方图描述乘客到达时间分布
plt.hist([passengerList[i][0] for i in range(len(passengerList))], bins=100)
plt.show()

由此生成乘客到达时间分布直方图,如图2所示。图2展示的是上述随机生成模型中绝大部分时候出现的情况。图中蓝色和橘色柱形分别为早高峰时间20等分、100等分后每个时段的到达乘客数。可以粗略看出乘客到达数最高的是7:00-7:25(图中时刻0-1500)以及7:40-8:15(图中时刻2400-4500)这两个时段。

3 模拟柜台开放策略

将原始的开放方案用矩阵形式进行表示:

Python
# 柜台开放方案更新时刻及其对应个数,把结束时间7200也写入
# 原始开放方案
planCheckPoint = [[0, 3], [1800, 5], [4800, 3], [7200, 0]]

例如:[0, 3]表示方案起始时间为0时刻(即早上7:00),一共3个柜台处于开放状态;[7200, 0]表示方案起始时间为7200时刻(即研究时段的终点9:00),一共0个柜台处于开放状态。

4 服务台数量变化的排队模型

建立排队模型的主要逻辑为:首先解析输入模型的柜台开放策略,通过策略内容划分出各时段内的乘客特征序列,再对该时段内的序列进行排队处理,计算出每个乘客的逗留时间(排队时间与服务时间之和)并输出。

    排队处理的思路为:以各服务台所有的空置开始时刻为记录节点,以乘客到达时刻为判定节点,每次乘客到达先判断是否存在空置的服务台,有则将该服务台的空置时刻变为乘客到达时刻与服务时间之和(即延后到这次服务结束),没有则把最近的空置时刻与乘客到达时刻之差作为排队时间,再将该空置时刻变为其与服务时间之和;当服务台数量发生变化时,尽量继承之前的空置时刻(即从大到小继承,减少误差)。

Python
# 排队算法
def originAnalysis(planCheckPoint, passengerList):

    # 筛选出timeRange内的到达乘客样本
    def timeSeg(passengerList, timeRange):
        passengerInRange = []
        for item in passengerList:
            if timeRange[0] <= item[0] <= timeRange[1]:
                passengerInRange.append(item)
            else:
                if item[0] > timeRange[1]:
                    break
        return passengerInRange

    # 按时间顺序进行排队和服务,返回逗留时间(排队时间 + 服务时间)
    def timePushForward(startTime, counterNum, passengerInRange):
        counterEnptyTime = [startTime for i in range(counterNum)]
        # 记录乘客的排队和服务时间之和
        sumTime = []
        for item in passengerInRange:
            # 以服务台空置时刻为核心展开算法,避免研究队长
            # 先将空置时刻列表正序排列,方便查找最小值
            counterEnptyTime = sorted(counterEnptyTime)
            # print(counterEnptyTime, item)
            # 如果存在某服务台空置时刻小于等于乘客到达时刻:不需要排队
            if counterEnptyTime[0] <= item[0]:
                # 计算该服务台的下次空置时刻
                counterEnptyTime[0] += item[1]
                sumTime.append(item[1])
            # 反之需要排队
            else:
                # 时刻推移到空置时刻出现时,计算下次空置时刻
                addTime = counterEnptyTime[0] - item[0]
                counterEnptyTime[0] += item[1]
                sumTime.append(item[1] + addTime)
        return sumTime

    passengerTimeSum = []
    for i in range(len(planCheckPoint) - 1):
        timeRange = [planCheckPoint[i][0], planCheckPoint[i + 1][0]]
        counterNum = planCheckPoint[i][1]
        passengerInRange = timeSeg(passengerList, timeRange)
        sumTime = timePushForward(timeRange[0], counterNum, passengerInRange)
        for item in sumTime:
            passengerTimeSum.append(item)
    return passengerTimeSum

5 服务效用评估

5.1 原始方案的服务效用

服务效用依照如下所示的方法进行计算:

Python
# 效用计算函数
def utilityAnalysis(passengerTimeSum, planChenkPoint):
    checkNum = 0
    for item in passengerTimeSum:
        if item <= 600:
            checkNum += 1
    print('不超过10min的比例: ', checkNum / len(passengerTimeSum))
    serveExpense = 0
    for i in range(len(planCheckPoint) - 1):
        serveExpense += (planChenkPoint[i + 1][0] - planChenkPoint[i][0]) * planChenkPoint[i][1]
    print('柜台服务成本: ', serveExpense)

由此得到原始方案的服务效用为

即:逗留时间不超过10min的比例为6.7%,柜台服务成本为27 600 / 60 = 460人分钟。因此原始方案无法满足需求。

经分析,即使在当前所有服务台均满载的情况下(即11个柜台在早高峰2小时持续工作),能提供的服务时间为1 320min,而早高峰时旅客到达人工柜台的数量取700人时,需要的服务时间期望值为2 100min,即当前要满足95%旅客逗留时间不超过10min这一需求,必须考虑适当增加柜台总数。

5.2 排队系统及服务方案优化

首先确定队伍数量。队伍数量的增加可能提高对排队空间的利用率,但在单队列采用蛇形排队的情况下,这一效果并非必要。而增加队伍数量会加大各柜台空闲时间的不均衡,对排队系统的整体服务效用为消极作用,因此仍然采用单个队列的方式组织排队系统。

    其次则是确定柜台调整时间的大致范围。根据前文对乘客到达能级的判断,必要的柜台数调整时间可能在7:20、7:40、8:15、8:45前后,这几个时间点前后的旅客到达密度发生了较大的变化。

    仍然采用计算机多次计算取得最优值的方式,获取优化后的服务效用(为加快求解时间,改变时刻均为60的倍数,即最小单位为分钟)。在满足需求(95%旅客逗留时间不超过10min)之后,通过调整柜台数量、变换时刻,并对超过30min长的时段适当进行拆分,得到最终的优化方案为:

Python
# 新开放方案
planCheckPoint = [[0, 23], [1500, 14], [2400, 27], [3540, 22], [4440, 17], [5340, 13], [6540, 4], [7200, 0]]

即:7:00:00-7:25:00设置23个柜台;7:25:01-7:40:00设置14个柜台;7:40:01-7:59:00设置27个柜台;7:59:01-8:14:00设置22个柜台;8:14:01-8:29:00设置17个柜台;8:29:01-8:49:00设置13个柜台;8:49:00以后设置4个柜台(以上数目变化的时间间隔均不小于15min)。

    该方案经过1 000次模拟,求出的平均服务效用为:

即:逗留时间不超过10min的比例为95.01%,柜台服务成本为2 187人分钟,已满足需求。

附录:测试代码

Python
if __name__ == '__main__':

    # 多次运行取平均值
    freqList = []
    serveExpense = 0

    for epoch in range(1000):
        # 只研究8:20至10:50出港的航班,再往后的航班旅客在研究时间段到达率极低,对结果的影响几乎不存在
        departureTimeList = [820, 820, 855, 905, 910, 925, 940, 1005, 1050]
        seatNumList = [180, 180, 180, 130, 180, 150, 180, 170, 170]
        passengerTimeList = []
        for i in range(len(departureTimeList)):
            arriveTimeList = arriveTimeGenerate(timeInvert(departureTimeList[i]), seatNumList[i])
            for item in arriveTimeList:
                # 筛出7-9点到达的乘客:班次乘客总数为1520人,这样筛选得到1190人左右
                if 0 <= item <= 7200:
                    # 在人工柜台办理值机的人数占60%
                    if np.random.rand() <= 0.6:
                        # 利用Gumbel分布已经将登机失败的概率降到了极低,可以将所有班次旅客的到达时间直接合并分析
                        passengerTimeList.append(item)

        # 正序排列
        passengerTimeList = sorted(passengerTimeList)
        # 生成完整的乘客特征序列
        passengerList = serveTimeGenerate(passengerTimeList)

        # 用直方图描述乘客到达时间分布
        # plt.hist([passengerList[i][0] for i in range(len(passengerList))], bins=20)
        # plt.hist([passengerList[i][0] for i in range(len(passengerList))], bins=100)
        # plt.show()

        # 柜台开放方案更新时刻及其对应个数,把结束时间7200也写入

        # 原始开放方案
        # planCheckPoint = [[0, 3], [1800, 5], [4800, 3], [7200, 0]]

        # 新开放方案
        planCheckPoint = [[0, 23], [1500, 14], [2400, 27], [3540, 22], [4440, 17], [5340, 13], [6540, 4], [7200, 0]]

        # 队伍分析
        passengerTimeSum = originAnalysis(planCheckPoint, passengerList)
        # 效用评估
        # 分析结果:原始方案是不满足要求的,会被连续的大值严重影响
        freqInDemand, serveExpense = utilityAnalysis(passengerTimeSum, planCheckPoint)
        freqList.append(freqInDemand)

    print(np.mean(freqList))
    print(serveExpense / 60)