import pandas as pd from ortools.sat.python import cp_model # 排课 def plan(teacher_subjects, subjects_required, teacher_required={}): # 教师 teachers_list = list(teacher_subjects.keys()) # 班级数 classes_list = list(subjects_required.keys()) # 课程 subjects_set = set() for subject_required in subjects_required.values(): subjects_set.update(subject_required.keys()) subjects_list = list(subjects_set) # 建模 model = cp_model.CpModel() # 决策变量:教师i在班级j的第k天第l个课时教授课程m x = {} for teacher in teachers_list: for class_ in classes_list: for day in range(6): for period in range(9): for subject in subjects_list: x[teacher, class_, day, period, subject] = model.NewBoolVar( f"x[{teacher}, {class_}, {day}, {period}, {subject}]" ) model.Add(x['郑成功', '高三1班', 3, 8, '体育'] == 1) # 辅助变量:是否和下一节课连续 consecutive = {} for teacher in teachers_list: for class_ in classes_list: for day in range(6): for period in range(8): for subject in subjects_list: consecutive[teacher, class_, day, period, subject] = ( model.NewBoolVar( f"consecutive[{teacher}, {class_}, {day}, {period}, {subject}]" ) ) # 辅助变量:135第一节是否上语文,246第一节是否上英语 first_lesson_preferred = {} for class_ in classes_list: for day in range(6): first_lesson_preferred[class_, day] = model.NewBoolVar(f'first_lesson_preferred_{class_}_{day}') # 辅助变量:每个老师每天的最早课时 teacher_earliest_period = {} for teacher in teachers_list: for day in range(6): teacher_earliest_period[teacher, day] = model.NewIntVar(0, 8, f'teacher_earliest_period_{teacher}_{day}') # 辅助变量:每个老师每天的最晚课时 teacher_latest_period = {} for teacher in teachers_list: for day in range(6): teacher_latest_period[teacher, day] = model.NewIntVar(0, 8, f'teacher_latest_period_{teacher}_{day}') # 辅助变量:每个老师每天的课时差 teacher_period_gap = {} for teacher in teachers_list: for day in range(6): teacher_period_gap[teacher, day] = model.NewIntVar(0, 8, f'teacher_period_gap_{teacher}_{day}') # 约束条件:指定老师 for teacher in teachers_list: for class_ in classes_list: for subject in subjects_list: if teacher_required.get(class_, {}).get(subject) == teacher: model.Add( sum( x[teacher, class_, day, period, subject] for day in range(6) for period in range(9) ) >= 1 ) # 约束条件:每个老师只教授自己的课 for teacher in teachers_list: for class_ in classes_list: for day in range(6): for period in range(9): for subject in subjects_list: if subject not in teacher_subjects[teacher]: model.Add(x[teacher, class_, day, period, subject] == 0) # 约束条件:每个班级有固定的课时数 for class_ in classes_list: for subject in subjects_list: model.Add( sum( x[teacher, class_, day, period, subject] for teacher in teachers_list for day in range(6) for period in range(9) ) == subjects_required[class_].get(subject, 0) ) # 约束条件:每个班同一个课程只能用一个老师 for class_ in classes_list: for subject in subjects_list: # 对于每个班级和课程,创建一个辅助变量来表示哪个老师教授这门课 teacher_indicator = {} for teacher in teachers_list: teacher_indicator[teacher] = model.NewBoolVar( f"teacher_indicator[{teacher}, {class_}, {subject}]" ) # 确保只有一个教师指示器为真 model.Add(sum(teacher_indicator.values()) <= 1) for teacher in teachers_list: # 如果教师指示器为真,那么这个教师必须至少教一节这门课 model.Add( sum( x[teacher, class_, day, period, subject] for day in range(6) for period in range(9) ) >= teacher_indicator[teacher] ) # 如果这个教师教了这门课,那么教师指示器必须为真 for day in range(6): for period in range(9): model.Add( x[teacher, class_, day, period, subject] <= teacher_indicator[teacher] ) # 约束条件:每个老师每天在每个班最多教两节相同的课程,如果有两节相同的课程,课程必须连着上,并且不能在第5节和第6节 for teacher in teachers_list: for class_ in classes_list: for day in range(6): for subject in subjects_list: # 计算这门课在这一天的总课程数 total_lessons = sum( x[teacher, class_, day, period, subject] for period in range(9) ) # 1. 限制每天每个班级每门课最多两节 model.Add(total_lessons <= 2) # 2. 如果有两节课,必须连续 # 创建变量,是否和下一节课连续 consecutive_sum = sum( consecutive[teacher, class_, day, period, subject] for period in range(8) ) # 2 节课,consecutive_sum 为 1 # 1 节课,consecutive_sum 为 0 # 0 节课,consecutive_sum 为 0 model.Add(consecutive_sum >= total_lessons - 1) # 连续性约束 for period in range(8): # 连续性为真时,两节课都为真 model.AddBoolAnd( [ x[teacher, class_, day, period, subject], x[teacher, class_, day, period + 1, subject], ] ).OnlyEnforceIf( consecutive[teacher, class_, day, period, subject] ) # 连续性为假时,至少有一节为假 model.AddBoolOr( [ x[teacher, class_, day, period, subject].Not(), x[teacher, class_, day, period + 1, subject].Not(), ] ).OnlyEnforceIf( consecutive[teacher, class_, day, period, subject].Not() ) # 3. 不能在第5节和第6节安排连续的两节课 model.Add(x[teacher, class_, day, 4, subject] + x[teacher, class_, day, 5, subject] <= 1) # 约束条件:每个老师在每天相同时段只能出现一次 for teacher in teachers_list: for day in range(6): for period in range(9): model.Add( sum( x[teacher, class_, day, period, subject] for class_ in classes_list for subject in subjects_list ) <= 1 ) # 约束条件:每个班级同天相同时段只能有一个课 for class_ in classes_list: for day in range(6): for period in range(9): model.Add( sum( x[teacher, class_, day, period, subject] for teacher in teachers_list for subject in subjects_list ) <= 1 ) # 添加软约束:135第一节尽可能上语文,246第一节尽可能上英语 for class_ in classes_list: for day in range(6): if day % 2 == 0: # 135 (对应 0, 2, 4) model.Add(sum(x[teacher, class_, day, 0, '语文'] for teacher in teachers_list) == 1).OnlyEnforceIf(first_lesson_preferred[class_, day]) else: # 246 (对应 1, 3, 5) model.Add(sum(x[teacher, class_, day, 0, '英语'] for teacher in teachers_list) == 1).OnlyEnforceIf(first_lesson_preferred[class_, day]) # 约束条件:体育课不能排上午前 2 节 for teacher in teachers_list: for class_ in classes_list: for day in range(6): for period in range(2): # 前两节课 model.Add(x[teacher, class_, day, period, '体育'] == 0) # 约束条件:体育课只能排在周 456 for teacher in teachers_list: for class_ in classes_list: for day in range(3): # 周一、二、三 for period in range(9): model.Add(x[teacher, class_, day, period, '体育'] == 0) # 约束条件:第一节、第二节和第六节必须排课 for class_ in classes_list: for day in range(6): for period in [0, 1, 5]: # 第一节、第二节和第六节 model.Add(sum(x[teacher, class_, day, period, subject] for teacher in teachers_list for subject in subjects_list) == 1) # 连课最小值,需求课时数 - 6 求和 min_consecutive = 0 for class_ in subjects_required: for subject in subjects_required[class_]: min_consecutive += max(0, subjects_required[class_][subject] - 6) # 约束条件:连课最小值 model.Add(sum(consecutive.values()) == min_consecutive) # 添加约束:计算每个老师每天的最早和最晚课时 for teacher in teachers_list: for day in range(6): # 创建辅助变量,表示老师在某一时段是否有课 has_class = {} for period in range(9): has_class[period] = model.NewBoolVar(f'has_class_{teacher}_{day}_{period}') model.Add(sum(x[teacher, class_, day, period, subject] for class_ in classes_list for subject in subjects_list) == 1).OnlyEnforceIf(has_class[period]) model.Add(sum(x[teacher, class_, day, period, subject] for class_ in classes_list for subject in subjects_list) == 0).OnlyEnforceIf(has_class[period].Not()) # 最早课时 model.AddMinEquality(teacher_earliest_period[teacher, day], [period * has_class[period] for period in range(9)]) # 最晚课时 model.AddMaxEquality(teacher_latest_period[teacher, day], [period * has_class[period] for period in range(9)]) # 添加约束:计算每个老师每天的课时差 for teacher in teachers_list: for day in range(6): model.Add(teacher_period_gap[teacher, day] == teacher_latest_period[teacher, day] - teacher_earliest_period[teacher, day]) # 目标函数:135语文 246英语,每个老师同一天所有课的课时差最小 model.Maximize(sum(first_lesson_preferred.values()) * 10000 - sum(teacher_period_gap.values())) # 求解 solver = cp_model.CpSolver() # 使用的线程数 # solver.parameters.num_search_workers = 1 # 限制求解时间 solver.parameters.max_time_in_seconds = 600 # 开启搜索进度 solver.parameters.log_search_progress = True status = solver.Solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: # 求解结果状态 if status == cp_model.OPTIMAL: print("Optimal solution") else: print("Feasible solution") # 求解结果 df_dict = {} # 创建星期和课时 weekdays = ["周一", "周二", "周三", "周四", "周五", "周六"] periods = ["第一节", "第二节", "第三节", "第四节", "第五节", "第六节", "第七节", "第八节", "第九节",] # 为每个班级创建一个 df for class_ in classes_list: df_dict[class_] = pd.DataFrame(index=periods, columns=weekdays) # 解析结果 for teacher in teachers_list: for class_ in classes_list: for day in range(6): for period in range(9): for subject in subjects_list: if (solver.Value(x[teacher, class_, day, period, subject]) == 1): df_dict[class_].loc[periods[period], weekdays[day]] = f"{subject}({teacher})" # 处理结果,每个 df 一个 sheet with pd.ExcelWriter("排课结果.xlsx") as writer: # 将每个DataFrame写入不同的sheet for class_, df in df_dict.items(): df.to_excel(writer, sheet_name=class_) else: print("No optimal solution found.") def main(): # 教师技能字典: teacher_subjects = { "叮铃铃": ["化学"], "代驾": ["历史"], "何萌萌": ["语文"], "余利群": ["历史"], "余响": ["地理"], "冯新新": ["地理"], "大妹妹": ["数学"], "叶真": ["英语"], "马家家": ["数学"], "周钱": ["数学"], "周无敌": ["地理"], "礼炮": ["英语"], "大奔": ["化学"], "周五六": ["生物"], "小朋": ["生物"], "小李": ["语文"], "小王": ["历史"], "李四": ["物理"], "王五": ["物理", "信息技术"], "赵六": ["数学"], "曾书书": ["物理"], "张三峰": ["地理"], "张无忌": ["英语"], "李师太": ["地理"], "徐大侠": ["物理"], "张小凡": ["美术"], "金瓶儿": ["数学"], "碧瑶": ["英语"], "鬼王": ["生物"], "幽姨": ["日语"], "张宝": ["音乐"], "孙悟空": ["生物"], "猪八戒": ["政治"], "沙僧": ["语文"], "唐僧": ["物理"], "观音": ["历史"], "玉帝": ["语文"], "大师兄": ["政治"], "小师妹": ["数学"], "王林": ["英语"], "藤化元": ["语文"], "藤一": ["化学"], "藤二": ["历史"], "藤三": ["历史"], "藤四": ["政治"], "藤五": ["政治"], "藤六": ["语文"], "羽化门": ["语文"], "青阳门": ["语文"], "焚香谷": ["数学"], "天音寺": ["政治"], "郑成功": ["体育"], "郭襄": ["政治"], "阎王": ["化学"], "牛头": ["地理"], "马面": ["生物"], "至尊宝": ["语文"], "陈真": ["语文"], "李世民": ["英语"], "杨辉": ["数学"], "袁天罡": ["数学"], "李星云": ["英语"], "李茂贞": ["数学"], } # 班级技能需求量 subjects_required = { "高三1班": { "语文": 8, "数学": 9, "英语": 8, "物理": 9, "化学": 8, "生物": 7, "体育": 1, }, "高三2班": { "语文": 8, "数学": 9, "英语": 8, "政治": 8, "历史": 8, "地理": 8, "体育": 1, }, "高三3班": { "语文": 8, "数学": 9, "英语": 8, "政治": 8, "历史": 8, "地理": 8, "体育": 1, }, } # 指定老师 teacher_required = { "高三1班": { "语文": "玉帝", "数学": "杨辉", "英语": "碧瑶", "物理": "李四", "化学": "阎王", "生物": "马面", "体育": "郑成功", }, "高三2班": { "语文": "玉帝", "数学": "杨辉", "英语": "李星云", "政治": "天音寺", "历史": "观音", "地理": "冯新新", "体育": "郑成功", }, "高三3班": { "语文": "至尊宝", "数学": "马家家", "英语": "李星云", "政治": "藤四", "历史": "余利群", "地理": "冯新新", "体育": "郑成功", }, } # 求解 plan(teacher_subjects, subjects_required, teacher_required) if __name__ == "__main__": main()