|
| 1 | +# 并行机器调度问题的Gurobi实现 |
| 2 | + |
| 3 | +## 目标函数: |
| 4 | + |
| 5 | +最小化所有任务的总拖期(Total Tardiness),其数学表达式为: |
| 6 | + |
| 7 | +$$ |
| 8 | +\text{最小化} \quad \sum_{j=1}^{n} \max(0, C_j - d_j) |
| 9 | +$$ |
| 10 | + |
| 11 | +其中: |
| 12 | + |
| 13 | +- \( C_j \):任务 \( J_j \) 的完成时间。 |
| 14 | +- \( d_j \):任务 \( J_j \) 的交付期限(Due Date)。 |
| 15 | + |
| 16 | +## 约束条件: |
| 17 | + |
| 18 | +### 1. 每个任务必须分配给一台机器: |
| 19 | + |
| 20 | +$$ |
| 21 | +\sum_{i \in M} Y_{ij} = 1, \quad \forall j \in J |
| 22 | +$$ |
| 23 | + |
| 24 | +其中: |
| 25 | + |
| 26 | +- \( Y_{ij} \):二进制变量,表示任务 \( J_j \) 是否分配给机器 \( M_i \) (1为分配,0为不分配)。 |
| 27 | + |
| 28 | +### 2. 前后任务顺序约束(每个任务在其分配的机器上只能有一个前序任务和一个后序任务): |
| 29 | + |
| 30 | +$$ |
| 31 | +Y_{ig} = \sum_{j \in J', j \neq g} X_{ijg}, \quad \forall g \in J, \forall i \in M |
| 32 | +$$ |
| 33 | + |
| 34 | +$$ |
| 35 | +Y_{ij} = \sum_{g \in J', g \neq j} X_{ijg}, \quad \forall j \in J, \forall i \in M |
| 36 | +$$ |
| 37 | + |
| 38 | +其中: |
| 39 | + |
| 40 | +- \( X_{ijg} \):二进制变量,表示任务 \( J_j \) 是否排在任务 \( J_g \) 之前。 |
| 41 | + |
| 42 | +### 3. 每台机器只能有一个首个任务: |
| 43 | + |
| 44 | +$$ |
| 45 | +\sum_{j \in J} X_{i0j} \leq 1, \quad \forall i \in M |
| 46 | +$$ |
| 47 | + |
| 48 | +### 4. 任务的完成时间和设置时间约束: |
| 49 | + |
| 50 | +确保任务只能在其释放时间后开始加工,且考虑到家族设置时间的影响: |
| 51 | + |
| 52 | +$$ |
| 53 | +C_j + V (1 - Y_{ij}) \geq \frac{p_j}{v_i} + r_j, \quad \forall i \in M, \forall j \in J |
| 54 | +$$ |
| 55 | + |
| 56 | +$$ |
| 57 | +C_g - C_j + V (1 - X_{ijg}) \geq S_{jg} + \frac{p_g}{v_i}, \quad \forall j \in J', \forall g \in J, j \neq g, \forall i \in M |
| 58 | +$$ |
| 59 | + |
| 60 | +其中: |
| 61 | + |
| 62 | +- \( r_j \):任务的释放时间。 |
| 63 | +- \( S_{jg} \):家族设置时间(如果任务 \( J_j \) 和 \( J_g \) 来自不同家族,则有设置时间)。 |
| 64 | + |
| 65 | +### 5. 每台机器的初始状态: |
| 66 | + |
| 67 | +$$ |
| 68 | +C_0 = 0 |
| 69 | +$$ |
| 70 | + |
| 71 | +### 6. 变量定义: |
| 72 | + |
| 73 | +$$ |
| 74 | +X_{ijg} \in \{0, 1\}, \quad Y_{ij} \in \{0, 1\}, \quad C_j > 0 |
| 75 | +$$ |
| 76 | + |
| 77 | +- \( X_{ijg} \):二进制变量,表示任务 \( J_j \) 是否排在任务 \( J_g \) 之前。 |
| 78 | +- \( Y_{ij} \):二进制变量,表示任务 \( J_j \) 是否分配给机器 \( M_i \)。 |
| 79 | +- \( C_j \):任务 \( J_j \) 的完成时间。 |
| 80 | + |
| 81 | +## 总结 |
| 82 | + |
| 83 | +通过上述目标函数和约束条件,你可以使用 Gurobi 来实现并行机器调度问题的求解。 |
| 84 | + |
| 85 | +``` |
| 86 | +# PMSP调度问题 |
| 87 | +""" |
| 88 | +目标函数: |
| 89 | +- 最小化总延迟 |
| 90 | +""" |
| 91 | +
|
| 92 | +# |
| 93 | +""" |
| 94 | +约束条件: |
| 95 | +- 每个作业只能分配给一个机器 |
| 96 | +- 每个机器上有且仅有一个前驱作业 |
| 97 | +- 每个机器上有且仅有一个后继作业 |
| 98 | +- 每台机器上最多只能有一个作业作为第一个作业 |
| 99 | +- 作业只能在到达时间之后开始处理 |
| 100 | +- 作业之间的重叠和设置时间的约束,我这里默认任何一个作业到另一个作业的转换设置时间都为1 |
| 101 | +- 每台机器上的作业的完成时间初始值都是0 |
| 102 | +""" |
| 103 | +# |
| 104 | +""" |
| 105 | +决策变量: |
| 106 | +- 作业Jj完成的时间 |
| 107 | +- 如果作业Jj是作业Jg在机器Mi上的前驱,则为1,否则为0 |
| 108 | +- 如果作业Jj被分配给机器Mi,则为1,否则为0 |
| 109 | +""" |
| 110 | +from dataclasses import dataclass |
| 111 | +from gurobipy import GRB |
| 112 | +import gurobipy |
| 113 | +import numpy |
| 114 | +
|
| 115 | +V = 0xffffff |
| 116 | +
|
| 117 | +@dataclass |
| 118 | +class Job: |
| 119 | + jobId : int |
| 120 | + processTime : int |
| 121 | + arrivalTime : int |
| 122 | + endTime : int |
| 123 | +
|
| 124 | + def __post_init__(self): |
| 125 | + self.name = "Job%d" % self.jobId |
| 126 | +
|
| 127 | +@dataclass |
| 128 | +class Machine: |
| 129 | + machId : int |
| 130 | + speed : int |
| 131 | +
|
| 132 | + def __post_init__(self): |
| 133 | + self.name = "Machine%d" % self.machId |
| 134 | +
|
| 135 | +# 机器 |
| 136 | +machines = [ |
| 137 | + Machine(0, 1), |
| 138 | + Machine(1, 2), |
| 139 | + Machine(2, 1), |
| 140 | + Machine(3, 3), |
| 141 | + Machine(4, 2) |
| 142 | +] |
| 143 | +
|
| 144 | +# 工作 |
| 145 | +jobs = [ |
| 146 | + Job(0, 3, 0, 7), |
| 147 | + Job(1, 2, 1, 4), |
| 148 | + Job(2, 4, 2, 20), |
| 149 | + Job(3, 1, 3, 15), |
| 150 | + Job(4, 3, 5, 10), |
| 151 | + Job(5, 2, 5, 15), |
| 152 | + Job(6, 4, 7, 18), |
| 153 | + Job(7, 3, 10, 25), |
| 154 | + Job(8, 2, 11, 25), |
| 155 | + Job(9, 1, 13, 26), |
| 156 | + Job(10, 5, 14, 30), |
| 157 | + Job(11, 3, 15, 35), |
| 158 | + Job(12, 2, 16, 40) |
| 159 | +] |
| 160 | +
|
| 161 | +num_jobs = len(jobs) |
| 162 | +num_machines = len(machines) |
| 163 | +
|
| 164 | +# 创建模型 |
| 165 | +model = gurobipy.Model("Model") |
| 166 | +
|
| 167 | +# 决策变量 |
| 168 | +# 如果作业Jj是作业Jg在机器Mi上的前驱,则为1,否则为0 |
| 169 | +# X = model.addVars(num_machines, num_jobs, num_jobs, vtype=GRB.BINARY, name='X') |
| 170 | +X = {} |
| 171 | +for i in range(num_machines): |
| 172 | + for j in range(num_jobs): |
| 173 | + for g in range(num_jobs): |
| 174 | + if j != g: |
| 175 | + X[i, j, g] = model.addVar(vtype=GRB.BINARY) |
| 176 | +
|
| 177 | +# 如果作业Jj被分配给机器Mi,则为1,否则为0 |
| 178 | +Y = model.addVars(num_machines, num_jobs, vtype=GRB.BINARY, name='Y') |
| 179 | +# 作业Jj完成的时间 |
| 180 | +C = model.addVars(num_jobs, vtype=GRB.CONTINUOUS, name='C') |
| 181 | +# S是Jg紧接着转向Jj作业之后设置的时间,设置了1 |
| 182 | +S = numpy.full([num_jobs, num_jobs], 1) |
| 183 | +
|
| 184 | +# 传递变量 |
| 185 | +# for i in range(num_jobs): |
| 186 | +# model.addConstr(C[i] == 0) |
| 187 | +
|
| 188 | +# 1.每个作业只能分配给一个机器 |
| 189 | +for j in range(num_jobs): |
| 190 | + model.addConstr( |
| 191 | + gurobipy.quicksum(Y[i, j] for i in range(num_machines)) == 1 |
| 192 | + ) |
| 193 | +
|
| 194 | +# 2.每个机器上有且仅有一个前驱作业 |
| 195 | +for i in range(num_machines): |
| 196 | + for g in range(num_jobs): |
| 197 | + model.addConstr( |
| 198 | + gurobipy.quicksum( |
| 199 | + X[i, j, g] for j in range(num_jobs) if j != g |
| 200 | + ) == Y[i, g] |
| 201 | + ) |
| 202 | +
|
| 203 | +# 3.每个机器上有且仅有一个后继作业 |
| 204 | +for i in range(num_machines): |
| 205 | + for j in range(num_jobs): |
| 206 | + model.addConstr( |
| 207 | + gurobipy.quicksum( |
| 208 | + X[i, j, g] for g in range(num_jobs) if g != j |
| 209 | + ) == Y[i, j] |
| 210 | + ) |
| 211 | +
|
| 212 | +# 4.每台机器上最多只能有一个作业作为第一个作业 |
| 213 | +for i in range(num_machines): |
| 214 | + model.addConstr( |
| 215 | + gurobipy.quicksum( |
| 216 | + X[i, 0, j] for j in range(num_jobs) if j != 0 |
| 217 | + ) <= 1 |
| 218 | + ) |
| 219 | +
|
| 220 | +# 5.作业只能在到达时间之后开始处理 |
| 221 | +for j in range(num_jobs): |
| 222 | + for i in range(num_machines): |
| 223 | + model.addConstr( |
| 224 | + C[j] + V * (1 - Y[i, j]) >= jobs[j].arrivalTime + jobs[j].processTime / machines[i].speed |
| 225 | + ) |
| 226 | +
|
| 227 | +# 6.作业之间的重叠和设置时间的约束,我这里默认任何一个作业到另一个作业的转换设置时间都为1 |
| 228 | +for i in range(num_machines): |
| 229 | + for g in range(num_jobs): |
| 230 | + for j in range(num_jobs): |
| 231 | + if j != g: |
| 232 | + model.addConstr( |
| 233 | + C[g] - C[j] + V * (1 - X[i, j, g]) >= S[g][j] + jobs[g].processTime / machines[i].speed |
| 234 | + ) |
| 235 | +
|
| 236 | +# 7.作业的完成时间也需要更新,这应该是最后一个约束公式想表达的意思 |
| 237 | +for i in range(num_machines): |
| 238 | + for j in range(num_jobs): |
| 239 | + for g in range(num_jobs): |
| 240 | + if j != g: |
| 241 | + model.addConstr( |
| 242 | + C[g] >= C[j] + jobs[j].processTime / machines[i].speed - V * (1 - X[i, j, g]) |
| 243 | + ) |
| 244 | +
|
| 245 | +# 目标函数:最小化总延迟 |
| 246 | +Total_times = [] |
| 247 | +
|
| 248 | +# 遍历每个作业,计算完成时间与截止时间之间的延迟 |
| 249 | +for j in range(num_jobs): |
| 250 | + diff_var = model.addVar(vtype=GRB.CONTINUOUS) # 常量不能直接和变量参与计算,通过变换到变量约束 |
| 251 | + model.addConstr(diff_var == C[j] - jobs[j].endTime) |
| 252 | + delay_var = model.addVar(vtype=GRB.CONTINUOUS) |
| 253 | + model.addGenConstrMax(delay_var, [0, diff_var]) |
| 254 | + Total_times.append(delay_var) |
| 255 | +
|
| 256 | +model.setObjective(gurobipy.quicksum(Total_times), gurobipy.GRB.MINIMIZE) |
| 257 | +
|
| 258 | +model.update() |
| 259 | +model.optimize() |
| 260 | +
|
| 261 | +if model.status == GRB.OPTIMAL: |
| 262 | + print("Get Optimal Solution:") |
| 263 | + for j in range(num_jobs): |
| 264 | + for i in range(num_machines): |
| 265 | + if Y[i, j].x > 0: # 判断作业是否分配给机器 |
| 266 | + print(f"Job {j} assigned to Machine {i}, Completion Time: {C[j].x}") |
| 267 | +else: |
| 268 | + print("No optimal solution found.") |
| 269 | +# Get Optimal Solution: |
| 270 | +# Job 0 assigned to Machine 4, Completion Time: 7.0 |
| 271 | +# Job 1 assigned to Machine 0, Completion Time: 4.0 |
| 272 | +# Job 2 assigned to Machine 3, Completion Time: 20.0 |
| 273 | +# Job 3 assigned to Machine 1, Completion Time: 15.0 |
| 274 | +# Job 4 assigned to Machine 0, Completion Time: 10.0 |
| 275 | +# Job 5 assigned to Machine 0, Completion Time: 15.0 |
| 276 | +# Job 5 assigned to Machine 1, Completion Time: 15.0 |
| 277 | +# Job 5 assigned to Machine 2, Completion Time: 15.0 |
| 278 | +# Job 5 assigned to Machine 3, Completion Time: 15.0 |
| 279 | +# Job 5 assigned to Machine 4, Completion Time: 15.0 |
| 280 | +# Job 6 assigned to Machine 3, Completion Time: 18.0 |
| 281 | +# Job 7 assigned to Machine 2, Completion Time: 25.0 |
| 282 | +# Job 8 assigned to Machine 1, Completion Time: 25.0 |
| 283 | +# Job 9 assigned to Machine 4, Completion Time: 26.0 |
| 284 | +# Job 10 assigned to Machine 3, Completion Time: 30.0 |
| 285 | +# Job 11 assigned to Machine 2, Completion Time: 35.0 |
| 286 | +# Job 12 assigned to Machine 4, Completion Time: 40.0 |
| 287 | +``` |
0 commit comments