EVA_duty_arrange_tool/main.py

293 lines
12 KiB
Python

import sys
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtGui import QFont
from utils import read_excel, save_to_excel
from solve import solve_program
from datetime import datetime
import traceback
# 约束条件配置
CONSTRAINTS_CONFIG = [
("每班次人数:", "4", "8"),
("每班次电脑或电器的老人数:", "", "2"),
("每班次老人数:", "1", ""),
("每班次人资小朋友数:", "1", ""),
("每班次小朋友数:", "2", "")
]
# 参数调优顺序和尝试值
PARAM_CONFIGS = [
('num_tech_min', [1, 0]),
('num_tech_max', [1, 2, 3, 4, 5, 6, 7, 8]),
('num_old_min', [2, 1, 0]),
('num_old_max', [1, 2, 3, 4, 5, 6, 7, 8]),
('num_min', [4, 3, 2, 1]),
('num_max', [8, 9, 10, 11, 12, 13, 14, 15]),
('num_new_min', [1, 0]),
('num_hr_min', [1, 0]),
('num_hr_max', [1, 2, 3, 4, 5, 6, 7, 8])
]
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.main_layout = QtWidgets.QVBoxLayout(self)
bold_font = QFont()
bold_font.setBold(True)
thin_font = QFont()
thin_font.setBold(False)
# 选择文件部分
self._excel_dir = None
self.group_box_1 = QtWidgets.QGroupBox("Step 1. 选择问卷星导出的表格文件")
self.group_box_1.setFont(bold_font)
self.openfile_layout = QtWidgets.QVBoxLayout()
self.button_openfile = QtWidgets.QPushButton("选择文件")
self.button_openfile.setFont(thin_font)
self.button_openfile.clicked.connect(self.open_file)
self.label_openfile = QtWidgets.QLabel("未选择文件")
self.label_openfile.setFont(thin_font)
self.openfile_layout.addWidget(self.button_openfile)
self.openfile_layout.addWidget(self.label_openfile)
self.group_box_1.setLayout(self.openfile_layout)
self.main_layout.addWidget(self.group_box_1)
# 限制条件部分
self.group_box_2 = QtWidgets.QGroupBox("Step 2. 输入限制条件")
self.group_box_2.setFont(bold_font)
self.cond_layout_overall = QtWidgets.QVBoxLayout()
self.group_box_2.setLayout(self.cond_layout_overall)
# 自动模式开关
self._auto_mode = True
self.cond_layout_0 = QtWidgets.QVBoxLayout()
self.auto_switch = QtWidgets.QCheckBox("自动调参模式")
self.auto_switch.setTristate(False) # 禁用三态模式
self.auto_switch.setChecked(True)
self.auto_switch.stateChanged.connect(self.on_switch_toggled)
self.auto_switch.setFont(thin_font)
self.cond_layout_0.addWidget(self.auto_switch)
self.cond_layout_overall.addLayout(self.cond_layout_0)
# 创建限制条件输入框
self.constraint_widgets = {}
for i, (label_text, min_value, max_value) in enumerate(CONSTRAINTS_CONFIG, 1):
layout, min_edit, max_edit = self._create_constraint_row(label_text, min_value, max_value, thin_font)
self.constraint_widgets[f'layout_{i}'] = layout
self.constraint_widgets[f'min_{i}'] = min_edit
self.constraint_widgets[f'max_{i}'] = max_edit
self.cond_layout_overall.addLayout(layout)
self.main_layout.addWidget(self.group_box_2)
# 处理文件部分
self.group_box_3 = QtWidgets.QGroupBox("Step 3. 获取最优结果")
self.group_box_3.setFont(bold_font)
self.button_solve = QtWidgets.QPushButton("开始排班!")
self.button_solve.setFont(thin_font)
self.text_solve = QtWidgets.QTextEdit(self)
self.text_solve.setFont(thin_font)
self.text_solve.setReadOnly(True) # 设置为只读模式
# 创建 QScrollArea 并将 QTextEdit 添加进去
self.scroll_area = QtWidgets.QScrollArea(self)
self.scroll_area.setWidget(self.text_solve) # 将 QTextEdit 设置为 QScrollArea 的内容
self.scroll_area.setWidgetResizable(True) # 允许 QTextEdit 根据 QScrollArea 的大小自动调整
self.button_solve.clicked.connect(self.magic)
self.solve_layout = QtWidgets.QVBoxLayout()
self.solve_layout.addWidget(self.scroll_area)
self.solve_layout.addWidget(self.button_solve)
self.group_box_3.setLayout(self.solve_layout)
self.main_layout.addWidget(self.group_box_3)
def _create_constraint_row(self, label_text, min_value, max_value, font):
"""创建一行限制条件输入框"""
layout = QtWidgets.QHBoxLayout()
label = QtWidgets.QLabel(label_text, self)
label.setFont(font)
# 创建输入框的辅助函数
def create_input(value):
edit = QtWidgets.QLineEdit(self)
edit.setFont(font)
edit.setValidator(QtGui.QIntValidator())
edit.setPlaceholderText("无限制")
if value:
edit.setText(value)
edit.setEnabled(False)
return edit
min_edit = create_input(min_value)
to_label = QtWidgets.QLabel("", self)
to_label.setFont(font)
max_edit = create_input(max_value)
layout.addWidget(label)
layout.addWidget(min_edit)
layout.addWidget(to_label)
layout.addWidget(max_edit)
return layout, min_edit, max_edit
def open_file(self):
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "打开文件", "", "所有文件 (*.*);;文本文件 (*.txt)")
if file_path:
self.label_openfile.setText(f"选中文件: {file_path}")
self._excel_dir = file_path
def on_switch_toggled(self, state):
"""处理开关状态的变化事件"""
enabled = state == 0
self._auto_mode = not enabled
for i in range(1, 6):
self.constraint_widgets[f'min_{i}'].setEnabled(enabled)
self.constraint_widgets[f'max_{i}'].setEnabled(enabled)
@QtCore.Slot()
def magic(self):
try:
self.text_solve.append(f"******** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ********")
# 读取文件
if self._excel_dir is None:
self.text_solve.append("请先选择文件!")
return
self.text_solve.append("开始排班...")
self.text_solve.append("读取文件中...")
all_data, index_to_name_dict, preference_mat, want_num_array, is_new_array, is_tech_array, is_hr_array = read_excel(self._excel_dir)
self.text_solve.append("读取文件成功!")
# 计算并打印统计信息
stu_num = len(index_to_name_dict)
class_num = len(preference_mat[0])
tech_sum = sum(want_num_array[i] for i in range(len(preference_mat))
if not is_new_array[i] and is_tech_array[i])
all_sum = sum(want_num_array)
min_prefer = min(sum(preference_mat[i][j] for i in range(len(preference_mat)))
for j in range(class_num))
self.text_solve.append("信息统计:")
self.text_solve.append(f"\t学生总人数:{stu_num}")
self.text_solve.append(f"\t班次总数:{class_num}")
self.text_solve.append(f"\t电脑部或电气部的所有老人的意愿班次之和:{tech_sum}")
self.text_solve.append(f"\t所有人的意愿班次之和:{all_sum}")
self.text_solve.append(f"\t平均每班人数:{all_sum/20}")
self.text_solve.append(f"\t所有班次中最少拥有意愿数:{min_prefer}")
vars = []
if self._auto_mode:
self.text_solve.append(f"自动调参开始...")
# 自动调参配置
auto_params: dict[str, int | None] = {
'num_min': None, 'num_max': None,
'num_tech_min': None, 'num_tech_max': None,
'num_old_min': None, 'num_old_max': None,
'num_hr_min': None, 'num_hr_max': None,
'num_new_min': None, 'num_new_max': None
}
# 逐步优化参数
for param_name, try_values in PARAM_CONFIGS:
value = None
vars = None
for try_value in try_values:
test_params = auto_params.copy()
test_params[param_name] = try_value
vars = solve_program(
preference_mat=preference_mat,
want_num_array=want_num_array,
is_new_array=is_new_array,
is_tech_array=is_tech_array,
is_hr_array=is_hr_array,
**test_params
)
if vars:
value = try_value
break
if value:
auto_params[param_name] = value
else:
break
if vars is None:
self.text_solve.append("自动求解失败!请尝试手动调参!")
return
else:
self.text_solve.append("自动计算成功!")
self.text_solve.append("参数:")
self.text_solve.append(f"\t每班人数:[{auto_params['num_min']}, {auto_params['num_max']}]")
self.text_solve.append(f"\t每班电脑或电器的老人数:[{auto_params['num_tech_min']}, {auto_params['num_tech_max']}]")
self.text_solve.append(f"\t每班老人数:[{auto_params['num_old_min']}, {auto_params['num_old_max']}]")
self.text_solve.append(f"\t每班人资部小朋友数:[{auto_params['num_hr_min']}, {auto_params['num_hr_max']}]")
self.text_solve.append(f"\t每班小朋友数:[{auto_params['num_new_min']}, inf]")
else:
# 读取限制条件
def get_value(key):
text = self.constraint_widgets[key].text()
return int(text) if text else None
manual_params = {
'num_min': get_value('min_1'),
'num_max': get_value('max_1'),
'num_tech_min': get_value('min_2'),
'num_tech_max': get_value('max_2'),
'num_old_min': get_value('min_3'),
'num_old_max': get_value('max_3'),
'num_hr_min': get_value('min_4'),
'num_hr_max': get_value('max_4'),
'num_new_min': get_value('min_5'),
'num_new_max': get_value('max_5')
}
self.text_solve.append("计算最优解中...")
vars = solve_program(
preference_mat=preference_mat,
want_num_array=want_num_array,
is_new_array=is_new_array,
is_tech_array=is_tech_array,
is_hr_array=is_hr_array,
**manual_params
)
if vars is None:
self.text_solve.append("在目前限制条件下无解!请尝试更改限制条件!")
return
else:
self.text_solve.append("计算最优解成功!")
# 保存结果到 excel
self.text_solve.append("保存结果中...")
save_dir = f"result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
save_to_excel(vars, all_data, index_to_name_dict, save_dir)
self.text_solve.append(f"保存结果成功!保存路径:{save_dir}")
except Exception:
self.text_solve.append("程序出现严重错误,请联系开发者解决问题!!!")
self.text_solve.append(f"Error Details:\n{traceback.format_exc()}")
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = MyWidget()
widget.setWindowTitle("EVA 值班排班软件")
widget.resize(600, 600)
widget.show()
sys.exit(app.exec())