from ortools.linear_solver import pywraplp def solve_program(preference_mat:list, depart_mat:list, want_num_array:list, is_new_array:list, num_min=None, num_max=None, num_tech_old_min=None, num_tech_old_max=None, num_old_min=None, num_old_max=None, num_new_min=None, num_new_max=None, weights=[1.0, 1.0, 1.0, 0.5, 0.5], exempt_shifts=None ): is_old_array = [not is_new for is_new in is_new_array] is_tech_array = [depart_mat[i][0] == 1 or depart_mat[i][1] == 1 for i in range(len(depart_mat))] is_hr_array = [depart_mat[i][2] == 1 for i in range(len(depart_mat))] exempt_set = set(exempt_shifts or []) # 使用 SCIP 求解器求解组合优化问题 solver = pywraplp.Solver.CreateSolver("SCIP") if not solver: return N = len(preference_mat) # 学生人数 M = len(preference_mat[0]) # 班次数 avg_num = sum(want_num_array) / M print(f"平均人数:{avg_num}") # 各个目标的期望值/归一化系数 m = [2.5, 3, 4, 1, 8] # [班次人数偏差, 技术部老人, 技术部小朋友, 人资部小朋友, 部门均衡] # 创建决策变量:variables[i][j] 表示第 i 个学生是否选择第 j 个班次 variables = [[solver.IntVar(0.0, 1.0, f"choice_{i}_{j}") for j in range(M)] for i in range(N)] # 创建辅助变量用于优化目标1(均衡班次人数) aux_vars_x1 = [solver.NumVar(0.0, solver.infinity(), f"aux_x1_{j}") for j in range(M)] # 创建辅助变量用于优化目标2(每班次技术部老人数量缺口) aux_vars_x2 = [solver.NumVar(0.0, solver.infinity(), f"aux_x2_{j}") for j in range(M)] # 创建辅助变量用于优化目标3(每班次技术部小朋友数量缺口) aux_vars_x3 = [solver.NumVar(0.0, solver.infinity(), f"aux_x3_{j}") for j in range(M)] # 创建辅助变量用于优化目标4(每班次人资部小朋友数量缺口) aux_vars_x4 = [solver.NumVar(0.0, solver.infinity(), f"aux_x4_{j}") for j in range(M)] # 创建辅助变量用于优化目标5(均衡各部门人数) # 如果提供了部门信息,则创建相应的辅助变量 aux_vars_x5 = [] num_departments = 5 # 电脑部、电器部、人资部、财外部、文宣部 # 为每个班次的每个部门创建辅助变量 aux_vars_x5 = [[solver.NumVar(0.0, solver.infinity(), f"aux_x5_{j}_{k}") for k in range(num_departments)] for j in range(M)] print("Number of variables =", solver.NumVariables()) # 约束:每个同学意愿一定要满足 for i in range(N): for j in range(M): solver.Add(variables[i][j] <= preference_mat[i][j]) # 约束:辅助变量 X1 用于计算与平均人数的偏差(绝对值) for j in range(M): actual_num = sum(variables[i][j] for i in range(N)) solver.Add(aux_vars_x1[j] >= actual_num - avg_num) solver.Add(aux_vars_x1[j] >= avg_num - actual_num) # 约束:辅助变量 X2 用于计算每班次技术部老人数量超出期望值的部分 # 辅助变量表示超出期望值(期望每班有m2个技术部老人,超过即计入) # 目标是让尽可能多的班次达到但不超过这个期望值 tech_old_target = m[1] # 使用归一化系数作为期望值 for j in range(M): tech_old_count = sum(variables[i][j] * is_old_array[i] * is_tech_array[i] for i in range(N)) # 辅助变量捕获超出部分和不足部分 solver.Add(aux_vars_x2[j] >= tech_old_count - tech_old_target) solver.Add(aux_vars_x2[j] >= tech_old_target - tech_old_count) # 约束:辅助变量 X3 用于计算每班次技术部小朋友数量超出期望值的部分 tech_new_target = m[2] # 使用归一化系数作为期望值 for j in range(M): tech_new_count = sum(variables[i][j] * is_new_array[i] * is_tech_array[i] for i in range(N)) solver.Add(aux_vars_x3[j] >= tech_new_count - tech_new_target) solver.Add(aux_vars_x3[j] >= tech_new_target - tech_new_count) # 约束:辅助变量 X4 用于计算每班次人资部小朋友数量超出期望值的部分 hr_new_target = m[3] # 使用归一化系数作为期望值 for j in range(M): hr_new_count = sum(variables[i][j] * is_new_array[i] * is_hr_array[i] for i in range(N)) solver.Add(aux_vars_x4[j] >= hr_new_count - hr_new_target) solver.Add(aux_vars_x4[j] >= hr_new_target - hr_new_count) # 约束:辅助变量 X5 用于计算各部门人数与平均值的偏差(绝对值) if aux_vars_x5 is not None: for j in range(M): # 计算第 j 班次的总人数 shift_total = sum(variables[i][j] for i in range(N)) # 计算平均每个部门应有的人数 avg_dept = shift_total / num_departments # 对每个部门 k for k in range(num_departments): # 计算第 j 班次中第 k 个部门的实际人数 dept_count = sum(variables[i][j] * depart_mat[i][k] for i in range(N)) # 添加绝对值约束 solver.Add(aux_vars_x5[j][k] >= dept_count - avg_dept) solver.Add(aux_vars_x5[j][k] >= avg_dept - dept_count) # 约束:满足每位同学的意愿班次数 for i in range(N): total_shifts = sum(variables[i]) if want_num_array[i] == 3: solver.Add(total_shifts >= 2) solver.Add(total_shifts <= 3) else: solver.Add(total_shifts == want_num_array[i]) # 添加约束的辅助函数(exempt_set 中的班次跳过下限约束) def add_shift_constraint(array, min_val, max_val): for j in range(M): shift_count = sum(variables[i][j] * array[i] for i in range(N)) if min_val is not None and j not in exempt_set: solver.Add(shift_count >= min_val) if max_val is not None: solver.Add(shift_count <= max_val) # 约束:每个班次的总人数 add_shift_constraint([1] * N, num_min, num_max) # 约束:每班次电脑或电器的老人数量 tech_old_array = [is_old_array[i] * is_tech_array[i] for i in range(N)] add_shift_constraint(tech_old_array, num_tech_old_min, num_tech_old_max) # 约束:每班次老人数量 add_shift_constraint(is_old_array, num_old_min, num_old_max) # 约束:每班次小朋友数量 add_shift_constraint(is_new_array, num_new_min, num_new_max) # 构建多目标优化函数 m1, m2, m3, m4, m5 = m[0], m[1], m[2], m[3], m[4] # X1: 每班次人数与平均人数的偏差 x1 = sum(aux_vars_x1) obj_x1 = x1 / (m1 * M) # 归一化处理,防止数值过大 # X2: 每班次技术部老人数量的缺口总和(优化目标是让每班都达到目标值) x2 = sum(aux_vars_x2) obj_x2 = x2 / (m2 * M) # 归一化处理,缺口越小越好 # X3: 每班次技术部小朋友数量的缺口总和 x3 = sum(aux_vars_x3) obj_x3 = x3 / (m3 * M) # 归一化处理,缺口越小越好 # X4: 每班次人资部小朋友数量的缺口总和 x4 = sum(aux_vars_x4) obj_x4 = x4 / (m4 * M) # 归一化处理,缺口越小越好 # X5: 每班次各部门人数的平均程度 obj_x5 = 0 if aux_vars_x5: x5 = sum(sum(aux_vars_x5[j]) for j in range(M)) obj_x5 = x5 / (m5 * M) # 归一化处理 # 线性加权组合所有目标 total_objective = ( weights[0] * obj_x1 + weights[1] * obj_x2 + weights[2] * obj_x3 + weights[3] * obj_x4 + weights[4] * obj_x5 ) print(f"优化目标权重: X1={weights[0]}, X2={weights[1]}, X3={weights[2]}, X4={weights[3]}, X5={weights[4]}") solver.Minimize(total_objective) # 求解 status = solver.Solve() if status == pywraplp.Solver.OPTIMAL: print("Optimal solution found:") variables_return = [[variables[i][j].solution_value() for j in range(M)] for i in range(N)] aux_vars_x1_return = sum([aux_vars_x1[j].solution_value() for j in range(M)]) / M print(f"Optimized objective value: {solver.Objective().Value()}") print(f"X1 (每班次人数平均偏差): {aux_vars_x1_return}") # 输出各个优化目标的详细信息 tech_old_array = [is_old_array[i] * is_tech_array[i] for i in range(N)] tech_new_array = [is_new_array[i] * is_tech_array[i] for i in range(N)] hr_new_array = [is_new_array[i] * is_hr_array[i] for i in range(N)] x2_value = sum(sum(variables[i][j].solution_value() * tech_old_array[i] for i in range(N)) for j in range(M)) / M x3_value = sum(sum(variables[i][j].solution_value() * tech_new_array[i] for i in range(N)) for j in range(M)) / M x4_value = sum(sum(variables[i][j].solution_value() * hr_new_array[i] for i in range(N)) for j in range(M)) / M aux_vars_x2_return = sum([aux_vars_x2[j].solution_value() for j in range(M)]) / M aux_vars_x3_return = sum([aux_vars_x3[j].solution_value() for j in range(M)]) / M aux_vars_x4_return = sum([aux_vars_x4[j].solution_value() for j in range(M)]) / M print(f"X2 (每班平均技术部老人): {x2_value}, 缺口平均: {aux_vars_x2_return}") print(f"X3 (每班平均技术部小朋友): {x3_value}, 缺口平均: {aux_vars_x3_return}") print(f"X4 (每班平均人资部小朋友): {x4_value}, 缺口平均: {aux_vars_x4_return}") if aux_vars_x5: x5_value = sum(sum(aux_vars_x5[j][k].solution_value() for k in range(num_departments)) for j in range(M)) print(f"X5 (部门人数偏差): {x5_value}") return variables_return else: print("No solution found.") return None