Spaces:
Sleeping
Sleeping
| # main.py | |
| """ | |
| This is the main entry point for the VTU Automated Timetable Generator. | |
| It performs the following steps: | |
| 1. Defines sample academic data (Faculties, Subjects, Sections, Rooms). | |
| 2. Uses the data_loader to convert this data into atomic 'Task' objects. | |
| 3. Initializes the TimetableSolver. | |
| 4. Runs the solver to generate a valid timetable. | |
| 5. Prints the resulting schedule in a human-readable grid format. | |
| 6. (Optional) Demonstrates the Emergency Re-optimizer functionality. | |
| This file is a standalone script and can be replaced by a UI or API layer. | |
| """ | |
| import sys | |
| from typing import List, Dict, Any | |
| # Import project modules | |
| from models import Faculty, Subject, Section, Room, SubjectType | |
| from data_loader import Allocation, prepare_scheduling_tasks | |
| from solver import TimetableSolver | |
| from reoptimizer import EmergencyReoptimizer | |
| import constants as const | |
| def create_sample_data(): | |
| """ | |
| Creates mock data for a Computer Science department (3rd & 5th Semester). | |
| """ | |
| print("Creating sample data...") | |
| # --- 1. Faculties --- | |
| # Define a mix of senior and junior faculties | |
| faculties = [ | |
| Faculty("F01", "Dr. Alice", "Professor", max_hours_per_week=12), | |
| Faculty("F02", "Prof. Bob", "Assoc. Prof", max_hours_per_week=16), | |
| Faculty("F03", "Prof. Charlie", "Asst. Prof", max_hours_per_week=18), | |
| Faculty("F04", "Prof. Dave", "Asst. Prof", max_hours_per_week=18), | |
| Faculty("F05", "Prof. Eve", "Asst. Prof", max_hours_per_week=18), | |
| Faculty("F06", "Guest Fac", "Guest", max_hours_per_week=8), | |
| ] | |
| # --- 2. Subjects --- | |
| # Core Subjects, Labs, and Electives | |
| subjects = [ | |
| # 5th Sem | |
| Subject("CS51", "Mgmt & Entrepren", 3, SubjectType.THEORY), | |
| Subject("CS52", "Computer Networks", 4, SubjectType.THEORY, is_core=True, is_heavy=True), | |
| Subject("CS53", "Database Mgmt", 4, SubjectType.THEORY, is_core=True, is_heavy=True), | |
| Subject("CS54", "Automata Theory", 3, SubjectType.THEORY, is_core=True), | |
| Subject("CS55", "Python Elective", 3, SubjectType.THEORY), # Elective | |
| Subject("CS56", "Java Elective", 3, SubjectType.THEORY), # Elective | |
| Subject("CSL57", "Networks Lab", 1, SubjectType.LAB), # 2-hour block | |
| Subject("CSL58", "DBMS Lab", 1, SubjectType.LAB), # 2-hour block | |
| # 3rd Sem | |
| Subject("CS31", "Maths III", 3, SubjectType.THEORY, is_core=True), | |
| Subject("CS32", "Data Structures", 4, SubjectType.THEORY, is_core=True, is_heavy=True), | |
| Subject("CS33", "Analog Digital", 3, SubjectType.THEORY), | |
| Subject("CS34", "COA", 3, SubjectType.THEORY), | |
| Subject("CSL37", "DS Lab", 1, SubjectType.LAB), | |
| Subject("CSL38", "AD Lab", 1, SubjectType.LAB), | |
| ] | |
| # --- 3. Sections --- | |
| sections = [ | |
| Section("5A", 5, 60), | |
| Section("5B", 5, 60), | |
| Section("3A", 3, 65), | |
| ] | |
| # --- 4. Rooms --- | |
| rooms = [ | |
| # Classrooms | |
| Room("R101", 70, is_lab=False, building="Main Block"), | |
| Room("R102", 70, is_lab=False, building="Main Block"), | |
| Room("R103", 70, is_lab=False, building="Main Block"), | |
| # Labs | |
| Room("LAB1", 30, is_lab=True, building="Lab Block"), # Small lab | |
| Room("LAB2", 70, is_lab=True, building="Lab Block"), # Big lab | |
| ] | |
| # --- 5. Allocations (Who teaches what to whom) --- | |
| allocations = [ | |
| # --- 5th Sem Section A --- | |
| Allocation("F01", "CS51", "5A"), | |
| Allocation("F02", "CS52", "5A"), | |
| Allocation("F03", "CS53", "5A"), | |
| Allocation("F04", "CS54", "5A"), | |
| # Elective: Group 1 (Split class) | |
| Allocation("F05", "CS55", "5A", elective_group_id="ELEC_5_GRP1"), | |
| # Labs | |
| Allocation("F02", "CSL57", "5A"), | |
| Allocation("F03", "CSL58", "5A"), | |
| # --- 5th Sem Section B --- | |
| Allocation("F01", "CS51", "5B"), | |
| Allocation("F02", "CS52", "5B"), | |
| Allocation("F03", "CS53", "5B"), | |
| Allocation("F04", "CS54", "5B"), | |
| # Elective: Same Group ID to align slot (if cross-section) or different if purely parallel | |
| # Here we assume 5A and 5B might have electives at same time | |
| Allocation("F06", "CS56", "5B", elective_group_id="ELEC_5_GRP1"), | |
| # Labs | |
| Allocation("F02", "CSL57", "5B"), | |
| Allocation("F03", "CSL58", "5B"), | |
| # --- 3rd Sem Section A --- | |
| Allocation("F04", "CS31", "3A"), | |
| Allocation("F05", "CS32", "3A"), | |
| Allocation("F06", "CS33", "3A"), | |
| Allocation("F01", "CS34", "3A"), | |
| Allocation("F05", "CSL37", "3A"), | |
| Allocation("F06", "CSL38", "3A"), | |
| ] | |
| return faculties, subjects, sections, rooms, allocations | |
| def print_timetable_grid(solution: Dict[str, Any], sections: List[Section]): | |
| """ | |
| Prints the generated timetable with explicit Break and Lunch columns. | |
| """ | |
| if not solution: | |
| print("No solution to display.") | |
| return | |
| # 1. Organize data into a nested dictionary | |
| # Structure: grid[section_id][day_index][period_index] = "Subject (Faculty)" | |
| grid = {sec.section_id: {d: {} for d in range(const.NUM_WORKING_DAYS)} for sec in sections} | |
| for task_id, info in solution.items(): | |
| sec_id = info['section_id'] | |
| day = info['day_index'] | |
| start_period = info['period_index'] | |
| duration = info['duration'] | |
| # Format the label | |
| # e.g., "NLP (Anu) [R1]" | |
| label = f"{info['subject_code']} ({info['faculty_name']}) [{info['room_id']}]" | |
| for i in range(duration): | |
| current_period = start_period + i | |
| if current_period < const.NUM_TEACHING_SLOTS_PER_DAY: | |
| grid[sec_id][day][current_period] = label | |
| # 2. Print the Grid | |
| for sec in sections: | |
| print(f"\n{'='*100}") | |
| print(f"TIMETABLE FOR SECTION: {sec.section_id}") | |
| print(f"{'='*100}") | |
| # --- Build Header Row --- | |
| header = f"{'DAY':<10} |" | |
| separator = f"{'-'*10}-+" | |
| for i in range(const.NUM_TEACHING_SLOTS_PER_DAY): | |
| # Print Period Number | |
| header += f" P{i+1:<13} |" | |
| separator += f"{'-'*15}-+" | |
| # Inject Break Header | |
| if i == const.BREAK_AFTER_INDEX: | |
| header += " BREAK (15m) |" | |
| separator += f"{'-'*13}-+" | |
| # Inject Lunch Header | |
| elif i == const.LUNCH_AFTER_INDEX: | |
| header += " LUNCH (1h) |" | |
| separator += f"{'-'*13}-+" | |
| print(header) | |
| print(separator) | |
| # --- Build Data Rows --- | |
| for d_idx, day_name in enumerate(const.DAYS): | |
| row = f"{day_name:<10} |" | |
| for p_idx in range(const.NUM_TEACHING_SLOTS_PER_DAY): | |
| # Get the class info, default to empty | |
| cell_data = grid[sec.section_id][d_idx].get(p_idx, "") | |
| # Truncate to fit column | |
| row += f" {cell_data[:13]:<13} |" | |
| # Inject Break Column | |
| if p_idx == const.BREAK_AFTER_INDEX: | |
| row += f" {'***':<11} |" | |
| # Inject Lunch Column | |
| elif p_idx == const.LUNCH_AFTER_INDEX: | |
| row += f" {'---':<11} |" | |
| print(row) | |
| print(separator) | |
| def main(): | |
| # 1. Load Data | |
| faculties, subjects, sections, rooms, allocations = create_sample_data() | |
| # 2. Prepare Tasks | |
| print(f"Generating tasks from {len(allocations)} allocations...") | |
| tasks = prepare_scheduling_tasks(allocations, faculties, subjects, sections) | |
| print(f"Total atomic tasks to schedule: {len(tasks)}") | |
| # 3. Initialize Solver | |
| solver = TimetableSolver(tasks, faculties, sections, rooms) | |
| # 4. Solve | |
| print("\nRunning Solver...") | |
| status, solution = solver.solve( | |
| time_limit_seconds=10, | |
| enable_soft_constraints=True, | |
| soft_constraint_weights={ | |
| "subject_repetition": 10, | |
| "morning_core": 5, | |
| "late_heavy": 10, | |
| "faculty_gaps": 2, | |
| "campus_movement": 5, | |
| "pack_morning": 500, | |
| "student_gaps": 500 | |
| } | |
| ) | |
| if status in ["OPTIMAL", "FEASIBLE"]: | |
| # 5. Display Results | |
| print_timetable_grid(solution, sections) | |
| # 6. Emergency Re-optimization Demo | |
| print("\n" + "!"*80) | |
| print("SIMULATING EMERGENCY: Faculty 'Prof. Bob' (F02) takes leave on Tuesday.") | |
| print("!"*80) | |
| reoptimizer = EmergencyReoptimizer(tasks, faculties, sections, rooms) | |
| # Tuesday is index 1 | |
| reopt_status, new_solution = reoptimizer.reoptimize_for_faculty_leave( | |
| current_schedule=solution, | |
| faculty_id="F02", | |
| leave_day_index=1, | |
| time_limit_seconds=10 | |
| ) | |
| if reopt_status in ["OPTIMAL", "FEASIBLE"]: | |
| print("\nRe-optimized Timetable (Changes minimized):") | |
| print_timetable_grid(new_solution, sections) | |
| else: | |
| print("Failed to re-optimize.") | |
| else: | |
| print(f"Solver failed to find a solution. Status: {status}") | |
| if __name__ == "__main__": | |
| main() |