101 lines
4.9 KiB
Markdown
101 lines
4.9 KiB
Markdown
# 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` 中的相关代码。 |