diff --git a/README.md b/README.md index ea4adae..63d59f8 100644 --- a/README.md +++ b/README.md @@ -29,71 +29,135 @@ uv run main.py 本项目使用 `pyinstaller` 工具进行打包。如果要打包,请确保能够正常运行项目。打包命令如下: ```bash -uvx pyinstaller --onefile --windowed --name=EVA_duty_arrange_tool main.py +uv run 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)问题。 +本项目将值班排班问题建模为了一个[组合优化](https://zh.wikipedia.org/wiki/%E7%BB%84%E5%90%88%E4%BC%98%E5%8C%96)问题,使用 Google 的 OR-Tools 求解器中的 SCIP 求解器进行求解。 -在本问题中,优化目标是: -- 一个涉及多个方面的量化指标 - - 每一班同学数量的平均程度 - - 每一班包含技术部老人的个数 - - 每一班包含技术部小朋友的个数 - - 每一班包含人资部小朋友的个数 +### 问题描述 -在本问题中,约束是: -1. 让每位同学每周的班次数符合意愿 -2. 让每位同学在自己想要的时间段值班 -3. 每班次至少(多)包含若干位技术部成员 -4. 等等... +在本问题中,优化目标是一个涉及多个方面的量化指标: +- **目标1**:每一班同学数量的平均程度 +- **目标2**:每一班技术部老人数量接近期望值 +- **目标3**:每一班技术部小朋友数量接近期望值 +- **目标4**:每一班人资部小朋友数量接近期望值 +- **目标5**:每一班各部门人数的平均程度 -下面我们将用数学语言建模以上定义的优化目标和约束。设一共有 $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$ 个部门,目前顺序为电脑部、电器部、人资部、财外部、文宣部 +在本问题中,主要约束是: +1. 让每位同学每周的班次数符合意愿(特别地,选择3次的同学可以安排2-3次) +2. 让每位同学只在自己有空的时间段值班 +3. 每班次的总人数在指定范围内 +4. 每班次技术部老人数量在指定范围内 +5. 每班次老人总数在指定范围内 +6. 每班次小朋友总数在指定范围内 +### 数学建模 -以上符号中,只有 $x_{ij}$ 是待求解的变量,其余均为已知量。 +设一共有 $n$ 位同学,$m$ 个值班的班次,协会共有 $t=5$ 个部门(电脑部、电器部、人资部、财外部、文宣部),定义以下符号: -则优化目标为: +**决策变量:** +- $x_{ij} \in \{0,1\}, \quad i=1,2,\dots,n,\quad j=1,2,\dots,m$ 表示第 $i$ 位同学是否值第 $j$ 班 -1. 每一班的值班人数与平均每一班值班人数之差的绝对值尽可能小 +**已知参数:** +- $N_i \in \mathbb{N}, \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$ 个部门 +- $\text{tech}_i = d_{i1} + d_{i2} \in \{0,1\}$ 表示第 $i$ 位同学是否属于技术部(电脑部或电器部) +- $\text{hr}_i = d_{i3} \in \{0,1\}$ 表示第 $i$ 位同学是否属于人资部 - $$ X_1=\sum\left | M_j - \frac{\sum_{i=1}^{n}Ni }{m} \right | $$ +**派生量:** +- $M_j = \sum_{i=1}^{n} x_{ij}, \quad j=1,2,\dots,m$ 表示第 $j$ 班次实际安排的值班人数 +- $\bar{M} = \frac{\sum_{i=1}^{n}N_i}{m}$ 表示平均每班的人数 - > 在组合优化问题的定义中,只能定义线性的式子,是不允许出现“绝对值”运算的。所以需要引入辅助变量 $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. 每一班技术部老人数量要达到一定值 +为了处理多目标优化问题,我们采用**线性加权法**将多个目标组合成单一目标函数。引入归一化系数 $m_1=2.5, m_2=3, m_3=4, m_4=1, m_5=8$ 和权重 $w_1, w_2, w_3, w_4, w_5$,总目标函数为: - $$ X_2=\sum x_{ij}\cdot(d_{i1}+d_{i2})\cdot\text{old}_i $$ +$$\min \quad Z = w_1 \cdot \frac{X_1}{m_1 \cdot M} + w_2 \cdot \frac{X_2}{m_2 \cdot M} + w_3 \cdot \frac{X_3}{m_3 \cdot M} + w_4 \cdot \frac{X_4}{m_4 \cdot M} + w_5 \cdot \frac{X_5}{m_5 \cdot M}$$ -3. 每一班技术部小朋友数量要达到一定值 +各目标定义如下: - $$ X_3=\sum x_{ij}\cdot(d_{i1}+d_{i2})\cdot(1-\text{old}_i) $$ +**目标1:均衡班次人数** -4. 每一班人资部小朋友数量要达到一定值 +$$X_1 = \sum_{j=1}^{m} a_j^{(1)}$$ - $$ X_4=\sum x_{ij}\cdot d_{i3}\cdot(1-\text{old}_i) $$ +其中辅助变量 $a_j^{(1)} \in [0,+\infty)$ 满足: +$$a_j^{(1)} \ge M_j - \bar{M}, \quad a_j^{(1)} \ge \bar{M} - M_j, \quad \forall j$$ -5. 每一班各个部门的人数尽可能平均,这里使用每一班中人数最多部门与人数最少部门的差 - > 引入辅助变量 $ C_{jk}=\sum_{i=1}^{n}x_{ij}\cdot d_{ik} $ - $$ X_5=\sum $$ +> 注:在组合优化问题中只能定义线性约束,不能直接使用绝对值。因此引入辅助变量 $a_j^{(1)}$ 并添加两个不等式约束,使得在优化过程中 $a_j^{(1)}$ 自动收敛到 $|M_j - \bar{M}|$。 -接下来定义约束: +**目标2:每班技术部老人数量接近期望值** -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. 其他更多的限制也是类似的,这里就略过了。 +期望每班有 $m_2 = 3$ 位技术部老人,定义缺口: -以上完成了整个排班问题的建模。建模完成后,用任何组合优化求解器都能都求解问题。在本项目中,我们使用了 `ortools` 这个谷歌开发的组合优化求解器。`ortools` 支持 `C++`,`Python`,`C#`,`Java` 等多种语言,也有跨平台支持。 +$$X_2 = \sum_{j=1}^{m} a_j^{(2)}$$ + +其中辅助变量 $a_j^{(2)} \in [0,+\infty)$ 满足: +$$a_j^{(2)} \ge \sum_{i=1}^{n} x_{ij} \cdot \text{old}_i \cdot \text{tech}_i - m_2$$ +$$a_j^{(2)} \ge m_2 - \sum_{i=1}^{n} x_{ij} \cdot \text{old}_i \cdot \text{tech}_i, \quad \forall j$$ + +**目标3:每班技术部小朋友数量接近期望值** + +期望每班有 $m_3 = 4$ 位技术部小朋友,定义缺口: + +$$X_3 = \sum_{j=1}^{m} a_j^{(3)}$$ + +其中辅助变量 $a_j^{(3)} \in [0,+\infty)$ 满足: +$$a_j^{(3)} \ge \sum_{i=1}^{n} x_{ij} \cdot (1-\text{old}_i) \cdot \text{tech}_i - m_3$$ +$$a_j^{(3)} \ge m_3 - \sum_{i=1}^{n} x_{ij} \cdot (1-\text{old}_i) \cdot \text{tech}_i, \quad \forall j$$ + +**目标4:每班人资部小朋友数量接近期望值** + +期望每班有 $m_4 = 1$ 位人资部小朋友,定义缺口: + +$$X_4 = \sum_{j=1}^{m} a_j^{(4)}$$ + +其中辅助变量 $a_j^{(4)} \in [0,+\infty)$ 满足: +$$a_j^{(4)} \ge \sum_{i=1}^{n} x_{ij} \cdot (1-\text{old}_i) \cdot \text{hr}_i - m_4$$ +$$a_j^{(4)} \ge m_4 - \sum_{i=1}^{n} x_{ij} \cdot (1-\text{old}_i) \cdot \text{hr}_i, \quad \forall j$$ + +**目标5:均衡各部门人数分布** + +对每个班次 $j$ 和每个部门 $k$,定义该班次该部门的人数偏差: + +$$X_5 = \sum_{j=1}^{m} \sum_{k=1}^{t} a_{jk}^{(5)}$$ + +其中 $\bar{M}_j = \frac{M_j}{t}$ 表示第 $j$ 班次各部门的平均人数,$C_{jk} = \sum_{i=1}^{n} x_{ij} \cdot d_{ik}$ 表示第 $j$ 班次第 $k$ 部门的实际人数,辅助变量 $a_{jk}^{(5)} \in [0,+\infty)$ 满足: + +$$a_{jk}^{(5)} \ge C_{jk} - \bar{M}_j, \quad a_{jk}^{(5)} \ge \bar{M}_j - C_{jk}, \quad \forall j,k$$ + +### 约束条件 + +**基本约束:** + +1. **意愿班次约束**:每位同学的值班次数必须符合其意愿 + $$\sum_{j=1}^{m}x_{ij} = N_i, \quad \forall i \text{ where } N_i \neq 3$$ + $$2 \le \sum_{j=1}^{m}x_{ij} \le 3, \quad \forall i \text{ where } N_i = 3$$ + +2. **时间可行性约束**:只在有空的时间段排班 + $$x_{ij} \le v_{ij}, \quad \forall i,j$$ + +**班次人数约束:** + +3. **总人数约束**:每班次人数在 $[n_{\min}, n_{\max}]$ 范围内 + $$n_{\min} \le \sum_{i=1}^{n} x_{ij} \le n_{\max}, \quad \forall j$$ + +4. **技术部老人约束**:每班次技术部老人数在范围内 + $$n_{\text{tech\_old\_min}} \le \sum_{i=1}^{n} x_{ij} \cdot \text{old}_i \cdot \text{tech}_i \le n_{\text{tech\_old\_max}}, \quad \forall j$$ + +5. **老人总数约束**:每班次老人总数在范围内 + $$n_{\text{old\_min}} \le \sum_{i=1}^{n} x_{ij} \cdot \text{old}_i \le n_{\text{old\_max}}, \quad \forall j$$ + +6. **小朋友总数约束**:每班次小朋友总数在范围内 + $$n_{\text{new\_min}} \le \sum_{i=1}^{n} x_{ij} \cdot (1-\text{old}_i) \le n_{\text{new\_max}}, \quad \forall j$$ + +### 求解器 + +以上完成了整个排班问题的建模。本项目使用 Google OR-Tools 中的 **SCIP 求解器**(Solving Constraint Integer Programs)进行求解。SCIP 是一个强大的混合整数规划(MIP)求解器,特别适合处理这类组合优化问题。 + +OR-Tools 支持 `C++`、`Python`、`C#`、`Java` 等多种语言,并提供跨平台支持。在本项目中,我们使用其 Python 接口 `ortools.linear_solver.pywraplp` 进行建模和求解。当找到最优解时,求解器会输出详细的目标值和各个优化指标的统计信息。 ## 维护指南 - 如果你想更改 Excel 的读取、写入相关的功能,应该修改 `utils.py` 中的相关函数。 diff --git a/pics/screen.jpg b/pics/screen.jpg deleted file mode 100644 index 30ceae4..0000000 Binary files a/pics/screen.jpg and /dev/null differ diff --git a/pics/screen.png b/pics/screen.png new file mode 100644 index 0000000..8f86ff0 Binary files /dev/null and b/pics/screen.png differ diff --git a/pyproject.toml b/pyproject.toml index 2d0c5ed..5e0c2da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "eva-duty-arrange-tool" -version = "1.2.0" +version = "2.0.0" description = "EVA 值班排班工具" readme = "README.md" requires-python = ">=3.12" diff --git a/uv.lock b/uv.lock index 769b958..e7cd98d 100644 --- a/uv.lock +++ b/uv.lock @@ -31,7 +31,7 @@ wheels = [ [[package]] name = "eva-duty-arrange-tool" -version = "1.2.0" +version = "2.0.0" source = { virtual = "." } dependencies = [ { name = "openpyxl" }, diff --git a/使用手册.md b/使用手册.md index aa26e45..b81b46a 100644 --- a/使用手册.md +++ b/使用手册.md @@ -1,4 +1,4 @@ -# EVA 值班排班工具使用手册 - v1.2.0 +# EVA 值班排班工具使用手册 - v2.0.0 ## 前言 这是浙江大学学生E志者协会“排班工具软件”的使用手册,将简要地介绍该软件的使用方法和注意事项。请注意,这是面向使用者而非开发者的手册,如果想了解该工具的开发流程和所使用的技术,请移步至[此仓库](https://git.zjueva.net/happywind/EVA_duty_arrange_tool)的说明手册。 @@ -6,7 +6,14 @@ ## 软件运行环境 & 基本情况 - 本软件只能在 Windows 系统上运行。 - 本软件在部分版本的 Win10 和 Win11 系统上均测试过,能够正常运行。但是不排除可能出现缺少相关 dll 文件而无法运行的情况,如果出现请联系开发者解决。 -- 本软件将排班问题建模为组合优化问题,以“尽可能让各个班次的人数平均”作为优化目标。在指定的限制条件下,若有解,则得到的一定是最优解。 +- 本软件将排班问题建模为**多目标组合优化问题**,使用 Google OR-Tools 的 SCIP 求解器求解。 +- 优化目标包括: + 1. 各班次人数尽可能平均 + 2. 每班技术部老人数量接近期望值(3人) + 3. 每班技术部小朋友数量接近期望值(4人) + 4. 每班人资部小朋友数量接近期望值(1人) + 5. 每班各部门人数尽可能均衡 +- 在指定的限制条件下,若有解,则得到的一定是**全局最优解**。 ## 软件使用方法 @@ -53,14 +60,41 @@ 如果出现了**两位同学撞名的情况**,需要在填表时“姓名”后加上后缀以作区分,比如“王五1”和“王五2”,否则就会当作一个人来看待。 -2. 输入想要的限制条件。(**若勾选“自动模式”则跳过这一步**) +2. 设置优化参数 - 本软件是在“限制条件必须满足”的条件下,以“每班次人数尽可能平均”作为最终目的来求解的。设置的限制条件太紧的话,最后可能导致无解(比如要求每班有 4 人,但实际上有一班选的人数就只有 3 人),这时候就需要手动宽松下限制参数。 + 软件界面的 Step 2 部分包含两类参数: + + ### 2.1 权重参数(始终可设置) + + 软件提供了 5 个优化目标的权重设置,用于调整各个目标的重要程度: + - **目标 1 权重**:每班人数平均程度(默认 1.0) + - **目标 2 权重**:每班技术部老人数量(默认 1.0) + - **目标 3 权重**:每班技术部小朋友数量(默认 1.0) + - **目标 4 权重**:每班人资部小朋友数量(默认 0.5) + - **目标 5 权重**:每班部门平均程度(默认 0.5) + + 权重值范围建议为 0.0 - 5.0,权重越大表示该目标越重要。所有权重参数均已归一化处理。 + + ### 2.2 约束条件(自动/手动模式) + + **自动调参模式(推荐):** + - 勾选"自动调参模式"复选框 + - 软件会自动尝试不同的约束参数组合,实时显示尝试过程 + - 从严格到宽松逐步调整参数,找到第一个可行解 + - 无需手动设置约束条件,约束输入框会被禁用 + + **手动模式:** + - 取消勾选"自动调参模式" + - 可手动设置以下约束条件: + - **每班次人数**:最少和最多人数(建议 5-8 人) + - **每班次电脑或电器的老人数**:技术部老人数量范围(建议最多 2 人) + - **每班次老人数**:所有老人数量范围(建议最少 1 人) + - **每班次小朋友数**:所有小朋友数量范围(建议最少 2 人) + - 留空表示该项无限制 + - **注意**:约束条件设置过严可能导致无解,需要适当放宽限制 - 本软件提供了自动模式来帮你尝试不同的参数并选择一个局部最优解。在自动模式下,用户无法自主设置参数。 +3. 开始排班并查看进度 -3. 开始排班 - - 点击“开始排班!”按钮以开始排班,输出结果会以 Excel 表格的形式输出,输出的文件名称是`result_<当前时间戳>.xlsx` - - **在输出结果中,组长会被标黄。** + 点击"开始排班!"按钮开始计算: + - 输出结果以 Excel 表格形式保存,文件名为 `result_<当前时间戳>.xlsx` + - **在输出结果中,组长会被标黄**