File size: 4,191 Bytes
8385c96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872925c
 
8385c96
 
 
 
 
 
872925c
 
8385c96
 
 
872925c
8385c96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61ca3d8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# solver.py

from typing import List, Dict, Optional, Any, Tuple
from ortools.sat.python import cp_model

from models import Task, Faculty, Section, Room
from constraint_engine import ConstraintEngine

class TimetableSolver:

    def __init__(self, tasks, faculties, sections, rooms):
        self.tasks     = tasks
        self.faculties = faculties
        self.sections  = sections
        self.rooms     = rooms
        self.model     = cp_model.CpModel()
        self.solver    = cp_model.CpSolver()
        self.constraint_engine = None
        self.objective_engine  = None

    def solve(
        self,
        time_limit_seconds: int = 60,
        enable_soft_constraints: bool = True,
        soft_constraint_weights: Dict[str, int] = None,
        log_search_progress: bool = True,
        num_workers: int = 8,
        slm_constraints: List[Dict[str, Any]] = None,
        scheduling_rules: List[Dict[str, Any]] = None,
    ) -> Tuple[str, Optional[Dict[str, Any]]]:

        # 1. Hard constraints
        print("Initializing Constraint Engine...")
        self.constraint_engine = ConstraintEngine(
            model=self.model, tasks=self.tasks, faculties=self.faculties,
            sections=self.sections, rooms=self.rooms,
            slm_constraints=slm_constraints, scheduling_rules=scheduling_rules
        )
        self.constraint_engine.apply_all_constraints()

        # 2. Soft constraints
        if enable_soft_constraints:
            print("Initializing Objective Engine...")
            from objective_engine import ObjectiveEngine
            self.objective_engine = ObjectiveEngine(
                model=self.model,
                constraint_engine=self.constraint_engine,
                tasks=self.tasks, faculties=self.faculties,
                sections=self.sections, rooms=self.rooms,
                weights=soft_constraint_weights
            )
            self.objective_engine.build_objective()

        # 4. Configure solver
        self.solver.parameters.max_time_in_seconds  = time_limit_seconds
        self.solver.parameters.log_search_progress  = log_search_progress
        self.solver.parameters.num_search_workers   = num_workers

        # 5. Solve
        print(f"Starting solver (Limit: {time_limit_seconds}s)...")
        status_val = self.solver.Solve(self.model)

        status_map = {
            cp_model.OPTIMAL:       "OPTIMAL",
            cp_model.FEASIBLE:      "FEASIBLE",
            cp_model.INFEASIBLE:    "INFEASIBLE",
            cp_model.MODEL_INVALID: "MODEL_INVALID",
            cp_model.UNKNOWN:       "UNKNOWN"
        }
        status_str = status_map.get(status_val, "UNKNOWN")
        print(f"Solver finished: {status_str}")

        solution = None
        if status_val in (cp_model.OPTIMAL, cp_model.FEASIBLE):
            solution = self._extract_solution()
            print(f"Solution found! Objective: {self.solver.ObjectiveValue()}")
        else:
            print("No solution found.")

        return status_str, solution

    def _extract_solution(self):
        import constants as const
        schedule = {}
        for task in self.tasks:
            start_var, _, _, room_var = self.constraint_engine.task_vars[task.task_id]
            start_slot   = self.solver.Value(start_var)
            room_index   = self.solver.Value(room_var)
            assigned_room = self.rooms[room_index]
            day_index    = start_slot // const.NUM_TEACHING_SLOTS_PER_DAY
            period_index = start_slot  % const.NUM_TEACHING_SLOTS_PER_DAY
            schedule[task.task_id] = {
                "task_obj":     task,
                "start_slot":   start_slot,
                "day_index":    day_index,
                "day_name":     const.DAYS[day_index],
                "period_index": period_index,
                "room_id":      assigned_room.room_id,
                "room_name":    f"{assigned_room.room_id} ({assigned_room.building})",
                "faculty_name": task.faculty.name,
                "subject_code": task.subject.subject_code,
                "section_id":   task.section.section_id,
                "duration":     task.duration
            }
        return schedule