210 lines
10 KiB
Python
210 lines
10 KiB
Python
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]
|
||
):
|
||
|
||
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))]
|
||
|
||
# 使用 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])
|
||
|
||
# 添加约束的辅助函数
|
||
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:
|
||
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
|
||
|