用开源求解器和Pyomo实现灵活的班次安排

本文介绍了如何利用Pyomo和开源求解器glpk,结合业务需求,建立灵活的班次安排模型。该模型考虑了员工工作时间偏好、人力成本最小化、多目标优化等问题,支持自定义权重和多种约束,如工作小时数、班次间隔、员工偏好等。通过标准化输入和输出,实现了排程的自动化和优化。
摘要由CSDN通过智能技术生成

前段时间讲课时发现简单的优化问题求解还是有些业务需求的,但是无论使用商用还是开源求解器,手工建模仍然需要一定工作量。所以这几天写了个基于pyomo和开源求解器glpk的小排程程序,可以实现多人、自定义班次和排程规则的最优化班次排程(shift assignment),优化目标包含最低成本和员工最大偏好,并且支持自定义的目标权重。程序支持excel或txt表格形式的标准化输入和输出。

业务需求示例

业务部门要求将n位员工对应至m种班次,在满足每周需求,并尽量满足员工工作时间偏好的同时使人力成本最小。排程单位为小时,planning horizon为一周(168小时)。

业务部门有预先定义的每日班次记录在表格shift里,每个班次预设时长在8-12小时之间,但理论上可以用小于24小时的任何班次设置,也可以跨不同天。

业务部门有n名员工,每位员工每周的工作时间必须满足最小工时/班次数和最大工时/班次数的要求。每位员工的时薪和输出都不同。另外每位员工可以提出对一周内所有班次的偏好,记录在表格preference里。preference为0的班次原则上不安排给员工。每位员工一周内每个小时的availability另外记录在表格availability里。这张表的值为0的时间段也不会安排员工工作(比如休假)。

业务部门定义了每小时的人力需求记录在表格demand里。但每小时的需求不需要严格满足;最终只要求排程满足每周的总体需求,而每小时的需求只需要满足minDemand和maxDemand区间:这是为了保证每小时capacity的上下限。

表格regulation记录所有其他关于班次的要求,目前包含:

  • 每人每周最大/最小工作小时数/班次数
  • 每人的相邻两班次最少相隔小时数
  • 每周最少相同相邻班次个数(保证工作时段稳定性)
  • 如果有夜班,每周最少和最多相邻夜班个数(保证工作时段稳定性)
  • 属于夜班班次的时间范围
  • 基础时薪,以及夜班、周末、法定节假日加班薪酬比例

此外还有关于多目标权重的设定:

  • 排程考虑员工偏好的权重
  • 排程考虑运营成本的权重

输入格式

Demand:

字段 示例 描述
publicHoliday 0 1为公共假日,0为非公共假日
weeks 1 周index,默认只有一周排程
days 1 1-7,周一为1,周日为7
hours 1 从1开始递增,表示排程中的第n小时
hourPerDay 1 1-24,1代表第0-1小时,24代表第23-24小时
demand 2 绝对需求,以小时为单位
min 1 最小capacity
max 4 最大capacity

排程时考虑每周完成绝对demand的总量,每小时保证在最低和最高capacity之间。

Shift

字段 示例 描述
weeks 1 周index,默认只有一周排程
days 1 1-7,周一为1,周日为7
hours 1 从1开始递增,表示排程中的第n小时
hourPerDay 1 1-24,1代表第0-1小时,24代表第23-24小时
shiftA 1 ID为shiftA的占用时间,1为占用,0为不占用
shiftB 0 ID为shiftB的占用时间,1为占用,0为不占用

只包含1天内的班次,默认一周内每天的可能班次相同。在排程时会考虑跨半夜和跨周的班次连接。另外根据demand,每个班次可能排多人,也可能不会使用。

24小时shift示例:

weeks days hours hourPerDay shiftA shiftB shiftC shiftD shiftE shiftF shiftG shiftH shiftI shiftJ shiftK
1 1 1 1 1 0 0 1 0 0 1 0 0 0 0
1 1 2 2 1 0 0 1 0 0 1 0 0 0 0
1 1 3 3 1 0 0 1 0 0 1 0 0 0 0
1 1 4 4 1 0 0 1 0 0 1 0 0 1 0
1 1 5 5 1 0 0 1 0 0 1 0 0 1 0
1 1 6 6 1 0 0 1 0 0 1 0 0 1 0
1 1 7 7 0 1 0 1 1 0 1 0 1 1 0
1 1 8 8 0 1 0 1 1 0 1 0 1 1 0
1 1 9 9 0 1 0 0 1 0 1 0 1 1 0
1 1 10 10 0 1 0 0 1 0 1 0 1 1 0
1 1 11 11 0 1 0 0 1 0 1 0 1 1 0
1 1 12 12 0 1 0 0 1 0 1 0 1 0 0
1 1 13 13 0 1 0 0 1 0 0 1 1 0 0
1 1 14 14 0 1 0 0 1 0 0 1 1 0 0
1 1 15 15 0 0 1 0 1 1 0 1 1 0 1
1 1 16 16 0 0 1 0 1 1 0 1 1 0 1
1 1 17 17 0 0 1 0 0 1 0 1 1 0 1
1 1 18 18 0 0 1 0 0 1 0 1 1 0 1
1 1 19 19 0 0 1 0 0 1 0 1 0 0 1
1 1 20 20 0 0 1 0 0 1 0 1 0 0 1
1 1 21 21 0 0 1 0 0 1 0 1 0 0 1
1 1 22 22 0 0 1 0 0 1 0 1 0 0 1
1 1 23 23 1 0 0 1 0 1 0 1 0 0 0
1 1 24 24 1 0 0 1 0 1 0 1 0 0 0

Availability:

字段 示例 描述
publicHoliday 0 1为公共假日,0为非公共假日
weeks 1 周index,默认只有一周排程
days 1 1-7,周一为1,周日为7
hours 1 从1开始递增,表示排程中的第n小时
hourPerDay 1 1-24,1代表第0-1小时,24代表第23-24小时
A 1 ID为A的员工的availability,1代表available,0代表休假
B 1 ID为B的员工的availability,1代表available,0代表休假

Preference 员工期望排程:

员工ID必须对应availability的所有员工。

班次ID必须对应一周内的所有班次,数量为shift表格里班次数量 * 排程天数。

Preference数值越大代表偏好越明显。如果preference设置为0,则排程时视为不可安排。

字段 示例 描述
employees A 员工ID
employeeHourlyWage 1 员工时薪
employeeHourlyOutput 1 员工效率
employeePriority 7 员工排程优先级,越高越优先满足preference
shiftA1 1 员工对周一班次shiftA的preference,数值越高preference越高。默认取值范围0-3,但没有特殊要求
shiftB1 3 员工对周一班次shiftB的preference
shiftA2 1 员工对周二班次shiftA的preference
shiftB2 3 员工对周二班次shiftB的preference

Regulation 排程规则:

规则名称 示例 单位 描述
minWorkHour 35 hours 每周每人最少工作小时数(如果有假期会自动排除)
maxWorkHour 60 hours 每周每人最大工作小时数
minShiftsPerWeek 3 shifts 每周每人最少工作班次数(如果有假期会自动排除)
maxShiftsPerWeek 5 shifts 每周每人最大工作班次数
minHourBetweenShift 12 hours 每人的相邻两班次最少相隔小时数
minShiftContinuous 2 days 每周最少相同相邻班次个数(保证工作时段稳定性)
minNightShiftContinuous 3 days 如果有夜班,每周最少相邻夜班个数(保证工作时段稳定性)
nightShiftDefinitionStart 24 hour 属于夜班班次的时间范围(起始时间,不包含)。班次中有任何时间段落入范围,整个班次为夜班
nightShiftDefinitionEnd 4 hour 属于夜班班次的时间范围(结束时间,包含)
maxNightShiftContinuous 5 days 如果有夜班,每周最多相邻夜班个数
standardShiftCostPerPersonPerHour 1 unit 基础时薪(以1为单位,1代表100%)
standardShiftPaymentStart 0 hour 基础时薪的时间范围(起始时间,包含)
standardShiftPaymentEnd 24 hour 基础时薪的时间范围(结束时间,包含)
additionalNightShiftCost 1 unit 夜班加薪(基础时薪的百分比)
additionalNightShiftPaymentStart 22 hour 夜班加薪的时间范围(起始时间,不包含),可以不同于夜班班次时间范围,默认大于
additionalNightShiftPaymentEnd 6 hour 夜班加薪的时间范围(结束时间,包含)
additionalWeekendShiftCost 1 unit 周末加薪(基础时薪的百分比)
additionalWeekendDayStart 6 Saturday 周末时间范围(起始日,包含)
additionalWeekendDayEnd 7 Sunday 周末时间范围(结束日,包含)
additionalPublicHolidayCost 1 unit 公共假日加薪(基础时薪的百分比)
weightPreference 0.1 unit 计算排程目标时,考虑员工preference的权重,取值在0-1之间
weightCost 0.9 unit 计算排程目标时,考虑运营成本的权重,取值在0-1之间

数据检查、优化目标和约束

  1. 检查 【min(员工available时间,规定最长工作时间) * 员工单位产出】是否大于等于【客户需求demand总量】。如果最大产出都不能满足需求总量,在进行优化排程前会调整demand到产出的上线。

  2. 排程优化会使用以下硬性约束(i.e.如果不能满足,就没有优化结果):

    • 每周最大/最小工作小时数。如果员工有安排假期,则相应向下调整最小工作小时数
    • 每周最大/最小班次数。如果员工有安排假期,则相应向下调整最小班次数
    • 相邻班次间最小间隔小时数
    • 每周最多连续夜班数
    • 员工availability设为0,和员工preference设为0的位置不可以安排班次
    • 每周总产出必须大于等于总需求
    • 每小时的总产出比如在同一时间的最大和最小需求限制以内(比如如果有产能限制)
  3. 另外会考虑逻辑或的约束:

    • 每周最少连续相同/相似班次数。或员工完全不安排班次,就视为合理
    • 每周最少连续相同/相似夜班次数。或员工完全不安排夜班,就视为合理

    注意:非凸优化问题可能产生局部最优解;尤其是逻辑或约束个数很多时,结果可能只部分满足约束条件。

  4. 排程优化目标:

    • 最小化运营成本(根据标准时薪、夜班、周末、公共节假日的设定,计算不同时间段的成本)
    • 最大化员工期望的班次数

    两个目标的平衡是通过设定weightPreference 和 weightCost两个参数实现的。在数据处理过程中会对所有目标相关的数据做归一化处理。

如果设置最小班次数和最小工作小时数同时为0,在满足所有硬性约束的条件下,有可能有员工没有被安排任何班次(根据成本优先安排已经有工时的员工)。

如果有要求所有员工都必须满足最小班次数和最小工作小时数,可以通过设置最小班次数和最小工作小时数中的一项。

其他数据处理

  1. shift表格只包含24小时的班次信息。会将24小时的班次数据扩展到7天(168小时)。对于跨0时的班次,程序会自动做班次拼接的处理。

  2. 计算互斥班次:程序会计算所有不满足班次最小间隔的互斥班次。在计算过程中会自动考虑跨越周日和周一的情况(i.e. 默认所有周都是一周的重复循环)。

  3. 计算最大连续夜班班次个数:会生成所有的连续夜班组合,这些组合都比规定的最大连续夜班个数多1个班次。在排程过程中,员工的排程中间结果会与这些夜班组合逐一比较,并保证不出现与任一夜班组合重合的情况。

  4. 计算最小连续相同班次个数:会生成所有的连续相同/相似班次组合,这些组合都恰好包含规定的最小连续班次个数。在排程过程中,员工的排程中间结果会与这些连续相同班次组合逐一比较,并保证至少与其中一个组合重合。

    对于相似班次的处理:入参中可以设置considerSimilarShifts=True, 同时设置“相似”班次的标准:similarShiftHours有多少个小时重合的情况下视为相似班次。

  5. 计算最小连续相同夜班个数:同4.

建模简述

除了需要逻辑或(Logical-OR)的约束,其他约束和目标都满足线性条件,可以使用标准线性建模方法。

需要逻辑或的约束条件是通过convex hull实现的。建模使用了pyomo中的Generalized Disjunctive Programming (GDP),具体参考这里

由于逻辑或是non-convex约束,大量的逻辑或会增加模型的计算量,而且并不保证能够找到可行解。在建模时把逻辑或约束放在其他约束之前,可以在一定程度上提高解的质量。

另外通过createCombinations函数会生成hourlyOutput的不同组合作为模型的超参数输入。不同组合会生成不同的解。程序会根据逻辑或约束的满足程度对解排序,在一定程度上提高解的质量。程序的执行效率通过进程池加速。

hourlyOutput组合是所有满足hourlyOutput小于等于给定output的值的组合。所以给定各个员工的hourlyOutput越高或员工数量越大,组合数量就越大,遍历所有组合需要的时间也越长。之所以选择hourlyOutput作为可变化的超参数,是因为默认所有小于给定hourlyOutput的值也都属于合理范围,等价于牺牲成本换取对约束的满足。

如果仍然出现结果不满足逻辑或约束的情况,可以尝试放宽demand的上下限。

详细建模说明:将业务需求转化为目标和约束

决策变量

设优化问题的决策变量为 x,这个矩阵的维度是【员工人数n * 班次个数m】,取值为0或1。

优化求解的目的是找到一组x,使目标函数(偏好-成本)达到最大值。

约束

1… 最大maxshift和最小班次数minshift的约束可以表示为:

∑ j m x i j > = m i n s h i f t , i ∈ 1 , 2 , ⋯   , n \sum_j^m x_{ij} >= minshift, i \in {1,2, \cdots, n} jmxij>=minshift,i1,2,,n

∑ j m x i j &lt; = m a x s h i f t , i ∈ 1 , 2 , ⋯ &ThinSpace; , n \sum_j^m x_{ij} &lt;= maxshift, i \in {1,2, \cdots, n} jmxij<=maxshift,i1,2,,n

2… 给定每个班次所在的时间段为向量:

s h i f t j , j ∈ 1 , 2 , ⋯ &ThinSpace; , m shift_j, j \in {1,2, \cdots, m} shiftj,j1,2,,m

每个向量的长度为 t = 7*24 = 168。

则有每位员工对应的工作时间为:

o n D u t y i k = ∑ j m x i j ∗ s h i f t j k , i ∈ 1 , 2 , ⋯ &ThinSpace; , n , k ∈ 1 , 2 , ⋯ &ThinSpace; , t onDuty_{ik} = \sum_j^m x_{ij} * shift_{jk}, i \in {1,2, \cdots, n}, k \in {1,2, \cdots, t} onDutyik=jmxijshiftjk,i1,2,,n,k1,2,,t

onDuty的取值应为0或1。

3… 最大maxhour工作时长和最小工作时长minhour的约束可以表示为:

∑ k t o n D u t y i k &gt; = m i n h o u r , i ∈ 1 , 2 , ⋯ &ThinSpace; , n \sum_k^t onDuty_{ik} &gt;= minhour, i \in {1,2, \cdots, n} ktonDutyik>=minhour,i1,2,,n

∑ k t o n D u t y i k &lt; = m a x h o u r , i ∈ 1 , 2 , ⋯ &ThinSpace; , n \sum_k^t onDuty_{ik} &lt;= maxhour, i \in {1,2, \cdots, n} ktonDutyik<=maxhou

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值