EVA_duty_arrange_tool/README.md

101 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# EVA 值班排班工具
## 环境配置
本项目使用 `uv` 进行包管理。
安装项目中所需要的所有包的最新版本。其中 `pyside6` 是一个前端库,`pyinstaller` 用于打包项目,`ortools` 是一个高效的组合优化求解器,`pandas` 用于处理表格数据:
```bash
uv sync
```
## 项目结构
```
EVA_duty_arrange_tool/
├─ main.py // 主函数,定义了前端界面和组件的回调函数
├─ solve.py // 定义了值班排班问题的求解函数
├─ utils.py // 定义了读取、写入 excel 的函数
├─ pics // 储存了说明文档中用到的图片
│ ├─ *.jpg/*.png
├─ *.md // 说明文档
├─ *.xlsx // 测试用例
```
## 项目运行 & 打包
项目运行方式:
```bash
uv run main.py
```
本项目使用 `pyinstaller` 工具进行打包。如果要打包,请确保能够正常运行项目。打包命令如下:
```bash
uvx pyinstaller --onefile --windowed --name=EVA_duty_arrange_tool main.py
```
## 数学原理
本项目将值班排班问题建模为了一个[组合优化](https://zh.wikipedia.org/wiki/%E7%BB%84%E5%90%88%E4%BC%98%E5%8C%96)问题。
在本问题中,优化目标是:
- 一个涉及多个方面的量化指标
- 每一班同学数量的平均程度
- 每一班包含技术部老人的个数
- 每一班包含技术部小朋友的个数
- 每一班包含人资部小朋友的个数
在本问题中,约束是:
1. 让每位同学每周的班次数符合意愿
2. 让每位同学在自己想要的时间段值班
3. 每班次至少(多)包含若干位技术部成员
4. 等等...
下面我们将用数学语言建模以上定义的优化目标和约束。设一共有 $n$ 位同学,$m$ 个值班的班次,协会共有 $t$ 个部门,定义以下符号:
- 令 $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$ 班
- 令 $\text{old}_{i} \in \{0,1\}, \quad i=1,2\dots n$ 表示第 $i$ 位同学是否是老人
- 令 $d_{ik}\in\{0,1\} \quad i=1,2\dots n,\quad k=1,2\dots t$ 表示第 $i$ 位同学是否属于第 $k$ 个部门,目前顺序为电脑部、电器部、人资部、财外部、文宣部
以上符号中,只有 $x_{ij}$ 是待求解的变量,其余均为已知量。
则优化目标为:
1. 每一班的值班人数与平均每一班值班人数之差的绝对值尽可能小
$$ X_1=\sum\left | M_j - \frac{\sum_{i=1}^{n}Ni }{m} \right | $$
> 在组合优化问题的定义中,只能定义线性的式子,是不允许出现“绝对值”运算的。所以需要引入辅助变量 $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$。那么在优化的过程中,$a_j$ 就会逐渐趋向 $X_1$。
2. 每一班技术部老人数量要达到一定值
$$ X_2=\sum x_{ij}\cdot(d_{i1}+d_{i2})\cdot\text{old}_i $$
3. 每一班技术部小朋友数量要达到一定值
$$ X_3=\sum x_{ij}\cdot(d_{i1}+d_{i2})\cdot(1-\text{old}_i) $$
4. 每一班人资部小朋友数量要达到一定值
$$ X_4=\sum x_{ij}\cdot d_{i3}\cdot(1-\text{old}_i) $$
5. 每一班各个部门的人数尽可能平均,这里使用每一班中人数最多部门与人数最少部门的差
> 引入辅助变量 $ C_{jk}=\sum_{i=1}^{n}x_{ij}\cdot d_{ik} $
$$ X_5=\sum $$
接下来定义约束:
1. 让每位同学每周的班次数符合意愿
$$\sum_{j=1}^{m}x_{ij}=N_i, \quad i=1,2\dots n$$
2. 让每位同学在自己想要的时间段值班
$$x_{ij} \le v_{ij}, \quad i=1,2\dots n,\quad j=1,2\dots m$$
3. 每班次至少包含 $t_{min}$ 位技术部成员,至多包含 $t_{max}$ 位技术部成员
$$t_{min} \le \sum_{i=1}^{n}x_{ij}\cdot tech_{i} \le t_{max}, \quad j=1,2\dots m$$
4. 其他更多的限制也是类似的,这里就略过了。
以上完成了整个排班问题的建模。建模完成后,用任何组合优化求解器都能都求解问题。在本项目中,我们使用了 `ortools` 这个谷歌开发的组合优化求解器。`ortools` 支持 `C++``Python``C#``Java` 等多种语言,也有跨平台支持。
## 维护指南
- 如果你想更改 Excel 的读取、写入相关的功能,应该修改 `utils.py` 中的相关函数。
- 如果你想更改软件的前端界面,应该修改 `main.py``MyWidget` 这个类相关的代码。
- 如果你想更换排班问题的建模方式、更换求解器、增减限制条件,应该修改 `solve.py` 中的相关代码。