EVA_duty_arrange_tool/solve.py

212 lines
10 KiB
Python
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.

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