4.8 KiB
EVA 值班排班工具
环境配置
如果你有 conda,推荐新建一个虚拟环境:
conda create -n EvaDutyArrangeTool python=3.8
conda activate EvaDutyArrangeTool
如果不新建虚拟环境也可以,本项目对 python 的版本不是很敏感。
安装项目中所需要的所有包的最新版本。其中 pyside6 是一个前端库,pyinstaller 用于打包项目,ortools 是一个高效的组合优化求解器,pandas 用于处理表格数据:
pip install pyside6 pyinstaller ortools pandas
如果发现以上包的最新版本与代码不兼容,可以尝试使用我开发时使用的版本:
pip install pyside6==6.6.3.1 pyinstaller==6.11.1 ortools==9.11.4210 pandas==2.0.3
项目结构
EVA_duty_arrange_tool/
├─ main.py // 主函数,定义了前端界面和组件的回调函数
├─ solve.py // 定义了值班排班问题的求解函数
├─ utils.py // 定义了读取、写入 excel 的函数
├─ pics // 储存了说明文档中用到的图片
│ ├─ *.jpg/*.png
├─ *.md // 说明文档
├─ *.xlsx // 测试用例
项目运行&打包
项目运行方式:
python main.py
本项目使用 pyinstaller 工具进行打包。如果要打包,请确保能够正常运行项目。打包命令如下:
pyinstaller --onefile --windowed --name=EVA_duty_arrange_tool main.py
数学原理
本项目将值班排班问题建模为了一个组合优化问题。
在本问题中,优化目标是:
- 让每一班的同学数量尽可能平均
在本问题中,约束是:
- 让每位同学每周的班次数符合意愿
- 让每位同学在自己想要的时间段值班
- 每班次至少(多)包含若干位技术部老人
- 每班次至少(多)包含若干位人资部小朋友
- 等等...
下面我将用数学语言建模以上定义的优化目标和约束。设一共有 n 位同学,m 个值班的班次,定义以下符号:
- 令
x_{ij} \in \{0,1\}, \quad i=1,2\dots n,\quad j=1,2\dots m表示最终第i位同学是否值第j班 - 令
M_j = \sum_{i=1}^{n} x_{ij}, \quad j=1,2,\dots m表示第j班次实际安排的值班人数。 - 令
N_i, \quad i=1,2,\dots n表示第i个同学每周愿意值班次数 - 令
v_{ij} \in \{0,1\}, \quad i=1,2\dots n,\quad j=1,2\dots m表示第i位同学是否有空值第j班 - 令
tech_{i} \in \{0,1\}, \quad i=1,2\dots n表示第i位同学是否是技术部成员 - 令
old_{i} \in \{0,1\}, \quad i=1,2\dots n表示第i位同学是否是老人 - 令
hr_{i} \in \{0,1\}, \quad i=1,2\dots n表示第i位同学是否是人资部成员
以上符号中,只有 x_{ij} 是待求解的变量,其余均为已知量。
则优化目标为:
\min_{x_{ij}}\left | M_j - \frac{\sum_{i=1}^{n}Ni }{m} \right |
也即,最小化每一班的值班人数与平均每一班值班人数之差的绝对值。
Tips: 在组合优化问题的定义中,只能定义线性的式子,是不允许出现“绝对值”运算的。所以需要引入辅助变量
a_j \in [0,+\infty ),并额外引入两组约束:a_j \ge M_j-\frac{\sum_{i=1}^{n}Ni }{m}和a_j \ge \frac{\sum_{i=1}^{n}Ni }{m} - M_j。此时优化目标变为:\min_{x_{ij},a_j}\sum_{j=1}^{m}a_j。
接下来定义约束:
- 让每位同学每周的班次数符合意愿:
\sum_{j=1}^{m}x_{ij}=N_i, \quad i=1,2\dots n - 让每位同学在自己想要的时间段值班
x_{ij} \le v_{ij}, \quad i=1,2\dots n,\quad j=1,2\dots m - 每班次至少包含
t_{min}位技术部老人,至多包含t_{max}位技术部老人t_{min} \le \sum_{i=1}^{n}x_{ij}tech_{i}old_{i} \le t_{max}, \quad j=1,2\dots m - 每班次至少包含
h_{min}位人资部小朋友,至多包含h_{max}位人资部小朋友h_{min} \le \sum_{i=1}^{n}x_{ij}hr_{i}(1-old_{i}) \le h_{max}, \quad j=1,2\dots m - 其他更多的限制也是类似的,这里就略过了。
以上完成了整个排班问题的建模。建模完成后,用任何组合优化求解器都能都求解问题。在本项目中,我使用了 ortools 这个谷歌开发的组合优化求解器。ortools 支持 c++,python,c#,java 等多种语言,也有跨平台支持,个人感觉比较好用。
维护指南
- 如果你想更改 excel 的读取、写入相关的功能,应该修改
utils.py中的相关函数。 - 如果你想更改软件的前端界面,应该修改
main.py中MyWidget这个类相关的代码 - 如果你想更换排班问题的建模方式、更换求解器、增减限制条件,应该修改
solve.py中的相关代码