diff --git a/Constants/Teachers_tt.py b/Constants/Teachers_tt.py new file mode 100644 index 0000000..30c4d7b --- /dev/null +++ b/Constants/Teachers_tt.py @@ -0,0 +1,87 @@ +import json +from Samples.samples import (SubjectTeacherMap, WorkingDays, SampleChromosome) + +class TeacherTimetable: + def __init__(self): + # Create timetable for each teacher + self.teacher_timetable = { + teacher: {day: [] for day in WorkingDays.days} + for teachers in SubjectTeacherMap.subject_teacher_map.values() + for teacher in teachers + } + self.teacher_assignments = {teacher: {} for teachers in SubjectTeacherMap.subject_teacher_map.values() for teacher in teachers} + + def generate_teacher_timetable(self, chromosome): + """ + Populate the teacher's timetable based on the provided chromosome schedule. + Now includes the course (Week 1, Week 2, etc.) information. + """ + for week, days in chromosome.items(): + for day, sections in days.items(): + for section, classes in sections.items(): + for entry in classes: + teacher_id = entry["teacher_id"] + subject_id = entry["subject_id"] + time_slot = entry["time_slot"] + classroom_id = entry["classroom_id"] + + # Conflict check: Ensure the teacher is not already assigned at this time slot + if teacher_id in self.teacher_assignments and time_slot in self.teacher_assignments[teacher_id]: + continue # Skip this assignment if there's a conflict + + # Add the class to the teacher's timetable + self.teacher_timetable[teacher_id][day].append({ + "course": week, # Include course information (Week 1, Week 2) + "section": section, + "subject_id": subject_id, + "time_slot": time_slot, + "classroom_id": classroom_id, + }) + + # Update the teacher's assignments to reflect the added class + if teacher_id not in self.teacher_assignments: + self.teacher_assignments[teacher_id] = {} + self.teacher_assignments[teacher_id][time_slot] = { + "course": week, + "section": section, + "subject_id": subject_id, + "classroom_id": classroom_id, + "day": day + } + + return self.teacher_timetable + + def save_timetable_to_json(self, file_path="Constants/teacher_timetable.json"): + """ + Create a dictionary and save the teacher's timetable to a JSON file. + """ + try: + # Create a dictionary explicitly from the teacher timetable + timetable_dict = { + teacher: { + day: classes + for day, classes in days.items() + } + for teacher, days in self.teacher_timetable.items() + } + + # Save the dictionary to a JSON file + with open(file_path, "w") as json_file: + json.dump(timetable_dict, json_file, indent=4) + + print(f"Timetable successfully saved to '{file_path}'.") + except Exception as e: + print(f"Error saving timetable to '{file_path}': {e}") + +if __name__ == "__main__": + teacher_timetable = TeacherTimetable() + # Generate timetable from the sample chromosome (Week 1 and Week 2) + w = { + "Week 2": SampleChromosome.schedule2, + "Week 1": SampleChromosome.schedule1 + } + + teacher_tt=teacher_timetable.generate_teacher_timetable(w) + print(teacher_tt) + # Save timetable to a JSON file + teacher_timetable.save_timetable_to_json() diff --git a/Constants/section_allocation.py b/Constants/section_allocation.py index 4c866a2..417b905 100644 --- a/Constants/section_allocation.py +++ b/Constants/section_allocation.py @@ -1,11 +1,12 @@ import random from collections import defaultdict from typing import List, Dict -from Constants.constant import SectionsConstants +from Constants.constant import SectionsConstants, Defaults +from Samples.Adhocs.common import generate_students class StudentScorer: - def __init__(self, attribute_weights: Dict[str, int] = None): + def __init__(self, students: list, attribute_weights: Dict[str, int] = None): """ Initialize the scorer with attribute weights. @@ -13,18 +14,19 @@ def __init__(self, attribute_weights: Dict[str, int] = None): """ self.attribute_weights = attribute_weights or SectionsConstants.ATTRIBUTE_WEIGHTS + self.students = students def calculate_dynamic_cgpa_threshold(self, students: List[Dict], top_percentage: int = 30) -> float: """ - Calculate the CGPA threshold for the top X% of students. + Calculate the CGPA threshold for the top X% of students. - Args: - students (List[Dict]): List of student dictionaries with 'CGPA'. - top_percentage (int): Percentage of students considered top. + Args: + students (List[Dict]): List of student dictionaries with 'CGPA'. + top_percentage (int): Percentage of students considered top. - Returns: - float: The CGPA threshold. + Returns: + float: The CGPA threshold. """ @@ -111,54 +113,39 @@ def divide_students_into_sections(self, students: List[Dict], class_strength: in return sections + def entry_point_for_section_divide(self): + # Calculate the dynamic CGPA threshold + cgpa_threshold_calc = self.calculate_dynamic_cgpa_threshold( + self.students, + top_percentage=30 + ) + # Assign conditions and scores + self.assign_dynamic_conditions(cgpa_threshold_calc) + students_with_scores = self.assign_scores_to_students(self.students) + # Divide students into sections + class_strength = Defaults.max_class_capacity + sections = self.divide_students_into_sections( + students_with_scores, + class_strength + ) + section_allocated_students = list() -def generate_students(num_students: int = 500) -> List[Dict]: - """ - Generate a list of random students with CGPA and hostler status. + for i, section in enumerate(sections, 1): + for student in section: + student["section"] = chr(64 + i) + section_allocated_students.append(student) - Args: - num_students (int): Number of students to generate. + return section_allocated_students - Returns: - List[Dict]: List of student dictionaries. - """ - return [ - { - 'ID': i, - 'CGPA': round(random.uniform(6.0, 9.8), 2), - 'Hostler': random.choice([True, False]) - } - for i in range(1, num_students + 1) - ] if __name__ == "__main__": - # Initialize constants and scorer - scorer = StudentScorer() - students = generate_students(num_students=500) - - # Calculate the dynamic CGPA threshold - cgpa_threshold = scorer.calculate_dynamic_cgpa_threshold(students, top_percentage=30) - print(f"Dynamic CGPA Threshold (Top 30%): {cgpa_threshold}") - - # Assign conditions and scores - scorer.assign_dynamic_conditions(cgpa_threshold) - students_with_scores = scorer.assign_scores_to_students(students) - - # Divide students into sections - class_strength = 50 - sections = scorer.divide_students_into_sections(students_with_scores, class_strength) - - # Display the sections - for i, section in enumerate(sections, 1): - print(f"Section {i} (Total Students: {len(section)}):") - for student in section: - print( - f" Student ID: {student['ID']}, CGPA: {student['CGPA']}, " - f"Hostler: {student['Hostler']}, score: {student['score']}" - ) + + scorer = StudentScorer(generate_students(num_students=500)) + from icecream import ic + ic(scorer.entry_point_for_section_divide()) diff --git a/Constants/teacher_timetable.json b/Constants/teacher_timetable.json new file mode 100644 index 0000000..42c705c --- /dev/null +++ b/Constants/teacher_timetable.json @@ -0,0 +1,1050 @@ +{ + "AB01": { + "Monday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-531", + "time_slot": "2:15 - 3:10", + "classroom_id": "R2" + }, + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-531", + "time_slot": "1:20 - 2:15", + "classroom_id": "R4" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-531", + "time_slot": "9:00 - 9:55", + "classroom_id": "R1" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "TCS-531", + "time_slot": "9:55 - 10:50", + "classroom_id": "R2" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-531", + "time_slot": "11:10 - 12:05", + "classroom_id": "R3" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-531", + "time_slot": "3:30 - 4:25", + "classroom_id": "R3" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-531", + "time_slot": "12:05 - 1:00", + "classroom_id": "R3" + } + ], + "Saturday": [] + }, + "PK02": { + "Monday": [], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-531", + "time_slot": "12:05 - 1:00", + "classroom_id": "R3" + }, + { + "course": "Week 1", + "section": "C", + "subject_id": "TCS-531", + "time_slot": "1:20 - 2:15", + "classroom_id": "R3" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "TCS-531", + "time_slot": "2:15 - 3:10", + "classroom_id": "R4" + } + ], + "Thursday": [ + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-531", + "time_slot": "9:55 - 10:50", + "classroom_id": "R4" + } + ], + "Friday": [], + "Saturday": [] + }, + "SS03": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-502", + "time_slot": "3:30 - 4:25", + "classroom_id": "R1" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "R1" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "TCS-502", + "time_slot": "9:00 - 9:55", + "classroom_id": "R2" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-502", + "time_slot": "9:55 - 10:50", + "classroom_id": "R2" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-502", + "time_slot": "2:15 - 3:10", + "classroom_id": "R1" + } + ], + "Friday": [], + "Saturday": [] + }, + "AA04": { + "Monday": [], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-502", + "time_slot": "3:30 - 4:25", + "classroom_id": "R3" + } + ], + "Wednesday": [], + "Thursday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-502", + "time_slot": "9:00 - 9:55", + "classroom_id": "R2" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "TCS-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Saturday": [] + }, + "AC05": { + "Monday": [], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-503", + "time_slot": "9:55 - 10:50", + "classroom_id": "R3" + }, + { + "course": "Week 2", + "section": "D", + "subject_id": "TCS-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Wednesday": [ + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-503", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-503", + "time_slot": "1:20 - 2:15", + "classroom_id": "R3" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TCS-503", + "time_slot": "9:00 - 9:55", + "classroom_id": "R3" + } + ], + "Saturday": [] + }, + "SP06": { + "Monday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-503", + "time_slot": "1:20 - 2:15", + "classroom_id": "R2" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-503", + "time_slot": "11:10 - 12:05", + "classroom_id": "R1" + }, + { + "course": "Week 1", + "section": "C", + "subject_id": "TCS-503", + "time_slot": "2:15 - 3:10", + "classroom_id": "R3" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "PCS-503", + "time_slot": "9:55 - 10:50", + "classroom_id": "L4" + }, + { + "course": "Week 2", + "section": "D", + "subject_id": "PCS-503", + "time_slot": "9:00 - 9:55", + "classroom_id": "L4" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-503", + "time_slot": "3:30 - 4:25", + "classroom_id": "R1" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-503", + "time_slot": "12:05 - 1:00", + "classroom_id": "R1" + } + ], + "Saturday": [] + }, + "DP07": { + "Monday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-503", + "time_slot": "12:05 - 1:00", + "classroom_id": "L4" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-503", + "time_slot": "11:10 - 12:05", + "classroom_id": "L4" + }, + { + "course": "Week 1", + "section": "C", + "subject_id": "TCS-503", + "time_slot": "9:00 - 9:55", + "classroom_id": "R3" + } + ], + "Tuesday": [], + "Wednesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-503", + "time_slot": "2:15 - 3:10", + "classroom_id": "L1" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-503", + "time_slot": "1:20 - 2:15", + "classroom_id": "L1" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-503", + "time_slot": "9:55 - 10:50", + "classroom_id": "R2" + } + ], + "Friday": [], + "Saturday": [] + }, + "AD08": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "PCS-506", + "time_slot": "12:05 - 1:00", + "classroom_id": "L5" + }, + { + "course": "Week 2", + "section": "A", + "subject_id": "PCS-506", + "time_slot": "11:10 - 12:05", + "classroom_id": "L5" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "PCS-506", + "time_slot": "9:55 - 10:50", + "classroom_id": "L3" + }, + { + "course": "Week 2", + "section": "D", + "subject_id": "PCS-506", + "time_slot": "9:00 - 9:55", + "classroom_id": "L3" + } + ], + "Wednesday": [], + "Thursday": [], + "Friday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-506", + "time_slot": "2:15 - 3:10", + "classroom_id": "L5" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-506", + "time_slot": "1:20 - 2:15", + "classroom_id": "L5" + } + ], + "Saturday": [] + }, + "RD09": { + "Monday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-506", + "time_slot": "9:55 - 10:50", + "classroom_id": "L5" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "PCS-506", + "time_slot": "9:00 - 9:55", + "classroom_id": "L5" + } + ], + "Tuesday": [], + "Wednesday": [], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "BJ10": { + "Monday": [ + { + "course": "Week 1", + "section": "A", + "subject_id": "TMA-502", + "time_slot": "1:20 - 2:15", + "classroom_id": "R1" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TMA-502", + "time_slot": "2:15 - 3:10", + "classroom_id": "R3" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TMA-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "R2" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TMA-502", + "time_slot": "11:10 - 12:05", + "classroom_id": "R1" + } + ], + "Friday": [ + { + "course": "Week 1", + "section": "B", + "subject_id": "TMA-502", + "time_slot": "9:00 - 9:55", + "classroom_id": "R2" + } + ], + "Saturday": [] + }, + "RS11": { + "Monday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "PCS-503", + "time_slot": "9:55 - 10:50", + "classroom_id": "L3" + }, + { + "course": "Week 2", + "section": "B", + "subject_id": "PCS-503", + "time_slot": "9:00 - 9:55", + "classroom_id": "L3" + } + ], + "Tuesday": [], + "Wednesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "PCS-503", + "time_slot": "12:05 - 1:00", + "classroom_id": "L3" + }, + { + "course": "Week 2", + "section": "A", + "subject_id": "PCS-503", + "time_slot": "11:10 - 12:05", + "classroom_id": "L3" + } + ], + "Thursday": [ + { + "course": "Week 1", + "section": "B", + "subject_id": "TMA-502", + "time_slot": "1:20 - 2:15", + "classroom_id": "R2" + } + ], + "Friday": [ + { + "course": "Week 1", + "section": "B", + "subject_id": "PCS-503", + "time_slot": "2:15 - 3:10", + "classroom_id": "L3" + } + ], + "Saturday": [] + }, + "JM12": { + "Monday": [], + "Tuesday": [], + "Wednesday": [ + { + "course": "Week 1", + "section": "D", + "subject_id": "TMA-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "TMA-502", + "time_slot": "9:55 - 10:50", + "classroom_id": "R3" + }, + { + "course": "Week 1", + "section": "D", + "subject_id": "TMA-502", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + } + ], + "Friday": [], + "Saturday": [] + }, + "NJ13": { + "Monday": [], + "Tuesday": [], + "Wednesday": [], + "Thursday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "TMA-502", + "time_slot": "9:00 - 9:55", + "classroom_id": "R4" + } + ], + "Friday": [], + "Saturday": [] + }, + "PM14": { + "Monday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "PMA-502", + "time_slot": "9:55 - 10:50", + "classroom_id": "L1" + }, + { + "course": "Week 2", + "section": "D", + "subject_id": "PMA-502", + "time_slot": "9:00 - 9:55", + "classroom_id": "L1" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "PMA-502", + "time_slot": "12:05 - 1:00", + "classroom_id": "L4" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "PMA-502", + "time_slot": "11:10 - 12:05", + "classroom_id": "L4" + } + ], + "Tuesday": [], + "Wednesday": [], + "Thursday": [], + "Friday": [ + { + "course": "Week 1", + "section": "C", + "subject_id": "PMA-502", + "time_slot": "2:15 - 3:10", + "classroom_id": "L5" + }, + { + "course": "Week 1", + "section": "C", + "subject_id": "PMA-502", + "time_slot": "1:20 - 2:15", + "classroom_id": "L5" + } + ], + "Saturday": [] + }, + "AA15": { + "Monday": [], + "Tuesday": [], + "Wednesday": [], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "SJ16": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-509", + "time_slot": "9:55 - 10:50", + "classroom_id": "R1" + }, + { + "course": "Week 1", + "section": "A", + "subject_id": "TCS-509", + "time_slot": "2:15 - 3:10", + "classroom_id": "R1" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-509", + "time_slot": "3:30 - 4:25", + "classroom_id": "R1" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-509", + "time_slot": "9:00 - 9:55", + "classroom_id": "R1" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "TCS-509", + "time_slot": "12:05 - 1:00", + "classroom_id": "R1" + } + ], + "Friday": [], + "Saturday": [] + }, + "AB17": { + "Monday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-509", + "time_slot": "12:05 - 1:00", + "classroom_id": "R2" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "TCS-509", + "time_slot": "9:55 - 10:50", + "classroom_id": "R2" + } + ], + "Tuesday": [], + "Wednesday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "TCS-509", + "time_slot": "11:10 - 12:05", + "classroom_id": "R2" + }, + { + "course": "Week 1", + "section": "B", + "subject_id": "TCS-509", + "time_slot": "9:00 - 9:55", + "classroom_id": "R2" + } + ], + "Thursday": [], + "Friday": [ + { + "course": "Week 1", + "section": "C", + "subject_id": "TCS-509", + "time_slot": "3:30 - 4:25", + "classroom_id": "R3" + } + ], + "Saturday": [] + }, + "HP18": { + "Monday": [ + { + "course": "Week 1", + "section": "C", + "subject_id": "TCS-509", + "time_slot": "9:55 - 10:50", + "classroom_id": "R3" + } + ], + "Tuesday": [], + "Wednesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "TCS-509", + "time_slot": "3:30 - 4:25", + "classroom_id": "R4" + } + ], + "Thursday": [], + "Friday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "TCS-509", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + }, + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-509", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Saturday": [] + }, + "SG19": { + "Monday": [ + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-509", + "time_slot": "2:15 - 3:10", + "classroom_id": "R4" + } + ], + "Tuesday": [ + { + "course": "Week 1", + "section": "D", + "subject_id": "TCS-509", + "time_slot": "9:00 - 9:55", + "classroom_id": "R4" + } + ], + "Wednesday": [], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "DT20": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "XCS-501", + "time_slot": "1:20 - 2:15", + "classroom_id": "R1" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "XCS-501", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + }, + { + "course": "Week 1", + "section": "D", + "subject_id": "XCS-501", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "XCS-501", + "time_slot": "9:55 - 10:50", + "classroom_id": "R1" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "XCS-501", + "time_slot": "9:00 - 9:55", + "classroom_id": "R3" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "XCS-501", + "time_slot": "3:30 - 4:25", + "classroom_id": "R3" + } + ], + "Saturday": [] + }, + "PA21": { + "Monday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "XCS-501", + "time_slot": "3:30 - 4:25", + "classroom_id": "R2" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "XCS-501", + "time_slot": "9:55 - 10:50", + "classroom_id": "R2" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "XCS-501", + "time_slot": "12:05 - 1:00", + "classroom_id": "R3" + } + ], + "Thursday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "XCS-501", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + } + ], + "Friday": [], + "Saturday": [] + }, + "NB22": { + "Monday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "XCS-501", + "time_slot": "11:10 - 12:05", + "classroom_id": "R4" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "XCS-501", + "time_slot": "9:00 - 9:55", + "classroom_id": "R3" + }, + { + "course": "Week 1", + "section": "C", + "subject_id": "XCS-501", + "time_slot": "9:55 - 10:50", + "classroom_id": "R3" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "D", + "subject_id": "XCS-501", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "AK23": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "CSP-501", + "time_slot": "9:00 - 9:55", + "classroom_id": "R1" + }, + { + "course": "Week 1", + "section": "D", + "subject_id": "CSP-501", + "time_slot": "3:30 - 4:25", + "classroom_id": "R4" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "C", + "subject_id": "CSP-501", + "time_slot": "11:10 - 12:05", + "classroom_id": "R3" + } + ], + "Wednesday": [], + "Thursday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "CSP-501", + "time_slot": "1:20 - 2:15", + "classroom_id": "R1" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "CSP-501", + "time_slot": "2:15 - 3:10", + "classroom_id": "R3" + }, + { + "course": "Week 2", + "section": "D", + "subject_id": "CSP-501", + "time_slot": "12:05 - 1:00", + "classroom_id": "R4" + } + ], + "Friday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "CSP-501", + "time_slot": "9:55 - 10:50", + "classroom_id": "R1" + } + ], + "Saturday": [] + }, + "AP24": { + "Monday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "SCS-501", + "time_slot": "2:15 - 3:10", + "classroom_id": "R1" + }, + { + "course": "Week 2", + "section": "B", + "subject_id": "SCS-501", + "time_slot": "11:10 - 12:05", + "classroom_id": "R2" + } + ], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "SCS-501", + "time_slot": "9:55 - 10:50", + "classroom_id": "R1" + }, + { + "course": "Week 2", + "section": "C", + "subject_id": "SCS-501", + "time_slot": "1:20 - 2:15", + "classroom_id": "R3" + } + ], + "Wednesday": [ + { + "course": "Week 2", + "section": "B", + "subject_id": "SCS-501", + "time_slot": "9:00 - 9:55", + "classroom_id": "R2" + } + ], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "VD25": { + "Monday": [], + "Tuesday": [], + "Wednesday": [], + "Thursday": [], + "Friday": [], + "Saturday": [] + }, + "AK26": { + "Monday": [], + "Tuesday": [ + { + "course": "Week 2", + "section": "A", + "subject_id": "Placement_Class", + "time_slot": "2:15 - 3:10", + "classroom_id": "R1" + } + ], + "Wednesday": [], + "Thursday": [], + "Friday": [], + "Saturday": [] + } +} \ No newline at end of file diff --git a/GA/__init__.py b/GA/__init__.py index dcb9319..33aa32a 100644 --- a/GA/__init__.py +++ b/GA/__init__.py @@ -1,5 +1,3 @@ -# This is the Orchestrator file, which will govern the flow. - from Constants.constant import Defaults from GA.mutation import TimeTableMutation, TimeTableCrossOver from GA.selection import TimeTableSelection @@ -12,7 +10,8 @@ SubjectWeeklyQuota, Classrooms, Sections, - + Depatments, + InterDepartment ) @@ -26,12 +25,15 @@ def timetable_generation(): teacher_preferences=TeacherWorkload.teacher_preferences, teacher_weekly_workload=TeacherWorkload.Weekly_workLoad, special_subjects=SpecialSubjects.special_subjects, + labs=SpecialSubjects.Labs, subject_quota_limits=SubjectWeeklyQuota.subject_quota, labs_list=Classrooms.labs, teacher_duty_days=TeacherWorkload.teacher_duty_days, + teacher_availability_matrix=InterDepartment.teacher_availability_matrix, + teacher_department_mapping=Depatments.teacher_department_mapping, ) - - timetable = timetable_generator.create_timetable(Defaults.initial_no_of_chromosomes) + timetable, teacher_availability_matrix = timetable_generator.create_timetable(Defaults.initial_no_of_chromosomes) + # Fitness of each Chromosome fitness_calculator = TimetableFitnessEvaluator( timetable, @@ -48,7 +50,6 @@ def timetable_generation(): fitness_scores = fitness_calculator.evaluate_timetable_fitness() - # Selection of all Chromosomes selection_object = TimeTableSelection() selected_chromosomes = selection_object.select_chromosomes(fitness_scores[1]) @@ -70,13 +71,13 @@ def timetable_generation(): crossover_chromosomes.append(child1) crossover_chromosomes.append(child2) - # Mutate all crossover Chromosomes mutation_object = TimeTableMutation() mutated_chromosomes = [ mutation_object.mutate_schedule_for_week(chromosome) for chromosome in crossover_chromosomes ] + best_chromosome_score = -1 best_chromosome = dict() @@ -84,12 +85,20 @@ def timetable_generation(): if int(week_score) > best_chromosome_score: best_chromosome_score = int(week_score) best_chromosome = timetable[week_no] - return best_chromosome + + return best_chromosome, teacher_availability_matrix def run_timetable_generation(): + best_chromosome = None + teacher_availability_matrix = None + for generation in range(Defaults.total_no_of_generations): - best_chromosome = timetable_generation() - return best_chromosome - -run_timetable_generation() + best_chromosome, teacher_availability_matrix = timetable_generation() + + return best_chromosome, teacher_availability_matrix + +if __name__ == "__main__": + print("Running Timetable Generation...") + best_chromosome, teacher_availability_matrix = run_timetable_generation() + # print("Timetable Generation Completed.",best_chromosome) \ No newline at end of file diff --git a/GA/chromosome.py b/GA/chromosome.py index 74c86b4..7f90987 100644 --- a/GA/chromosome.py +++ b/GA/chromosome.py @@ -17,9 +17,12 @@ def __init__( teacher_preferences: dict, teacher_weekly_workload: dict, special_subjects: dict, + labs: dict, subject_quota_limits: dict, labs_list: list, teacher_duty_days: dict, + teacher_availability_matrix=None, + teacher_department_mapping=dict, ): self.sections_manager = Sections(total_sections) self.classrooms_manager = Classrooms(total_classrooms, total_labs) @@ -29,14 +32,19 @@ def __init__( self.weekdays = Defaults.working_days self.subject_teacher_mapping = teacher_subject_mapping self.subject_quota_limits = subject_quota_limits - self.lab_subject_list = labs_list + self.lab_subject_list = labs self.special_subject_list = special_subjects self.teacher_availability_preferences = teacher_preferences self.available_time_slots = TimeIntervalConstant.time_slots self.teacher_duty_days = teacher_duty_days self.weekly_workload = teacher_weekly_workload - self.teacher_assignment_tracker = {teacher: 0 for teacher in teacher_weekly_workload.keys()} + self.teacher_assignment_tracker = { + teacher: 0 for teacher in teacher_weekly_workload.keys() + } + + # Initialize teacher availability matrix with proper structure + self.teacher_availability_matrix = teacher_availability_matrix self._map_sections_to_classrooms() self._teacher_section() @@ -60,11 +68,7 @@ def _map_sections_to_classrooms(self): self.section_to_classroom_map[section] = classroom sorted_classrooms.remove(classroom) break - else: - raise ValueError( - f"No classroom can accommodate section {section} with strength {self.room_capacity_manager.section_strength[section]}." - ) - + def _teacher_section(self): """Map sections to teachers and subjects, ensuring balanced assignments.""" self.teacher_section_map = { @@ -86,51 +90,13 @@ def _initialize_teacher_workload_tracker(self): """Initialize a tracker for teacher workloads.""" return {teacher: 0 for teacher in self.weekly_workload.keys()} - def _generate_section_schedule( - self, section, half_day_section_list, section_subject_usage_tracker, teacher_workload_tracker - ): - """Generate a schedule for an individual section.""" - section_schedule = [] - subjects_scheduled_today = set() - assigned_classroom = self.section_to_classroom_map[section] - total_slots_for_section = 4 if section in half_day_section_list else 7 - - for slot_index, current_time_slot in enumerate(self.available_time_slots.values(), start=1): - if slot_index > total_slots_for_section: - break - - assigned_teacher, selected_subject, assigned_room = self._assign_subject_and_teacher( - section, - slot_index, - subjects_scheduled_today, - assigned_classroom, - section_subject_usage_tracker, - teacher_workload_tracker, - ) - section_schedule.append( - { - "teacher_id": assigned_teacher, - "subject_id": selected_subject, - "classroom_id": assigned_room, - "time_slot": current_time_slot, - } - ) - if selected_subject != "Library": - section_subject_usage_tracker[section][selected_subject] += 1 - - # Handle double-slot subjects - if selected_subject in self.lab_subject_list and slot_index + 1 <= total_slots_for_section: - next_time_slot = self.available_time_slots.get(slot_index + 1) - section_schedule.append( - { - "teacher_id": assigned_teacher, - "subject_id": selected_subject, - "classroom_id": assigned_room, - "time_slot": next_time_slot, - } - ) - section_subject_usage_tracker[section][selected_subject] += 1 - return section_schedule + def _get_available_subjects(self, section, section_subject_usage_tracker): + """Get a list of subjects available for a specific section.""" + return [ + subject + for subject in self.subject_teacher_mapping.keys() + if section_subject_usage_tracker[section][subject] < self.subject_quota_limits.get(subject, 0) + ] def _assign_subject_and_teacher( self, @@ -140,8 +106,11 @@ def _assign_subject_and_teacher( assigned_classroom, section_subject_usage_tracker, teacher_workload_tracker, + teacher_availability_matrix, + index, + total_slots_for_section ): - """Assign a subject and teacher for a specific slot.""" + """Assign a subject and teacher for a specific slot, respecting teacher preferences.""" available_subjects = self._get_available_subjects(section, section_subject_usage_tracker) random.shuffle(available_subjects) @@ -157,67 +126,137 @@ def _assign_subject_and_teacher( if subject not in subjects_scheduled_today: teachers_for_subject = self.subject_teacher_mapping[subject] + preferred_teachers = [ + teacher + for teacher in teachers_for_subject + if self.teacher_availability_preferences.get(teacher, []) + ] teachers_with_least_load = sorted( - teachers_for_subject, key=lambda teacher: teacher_workload_tracker[teacher] - ) - assigned_teacher = teachers_with_least_load[0] - teacher_workload_tracker[assigned_teacher] += 1 - selected_subject = subject - subjects_scheduled_today.add(subject) - assigned_room = ( - self.classrooms_manager.labs[slot_index % len(self.classrooms_manager.labs)] - if subject in self.lab_subject_list - else assigned_classroom + preferred_teachers, key=lambda teacher: teacher_workload_tracker[teacher] ) - break - + # print(preferred_teachers) + for teacher in teachers_with_least_load: + # Ensure valid index access + if (teacher in teacher_availability_matrix and + len(teacher_availability_matrix[teacher]) > index and + len(teacher_availability_matrix[teacher][index]) > (slot_index - 1) and + teacher_availability_matrix[teacher][index][slot_index - 1]): + assigned_teacher = teacher + teacher_workload_tracker[assigned_teacher] += 1 + selected_subject = subject + subjects_scheduled_today.add(subject) + assigned_room = ( + self.classrooms_manager.labs[slot_index % len(self.classrooms_manager.labs)] + if subject in self.lab_subject_list + else assigned_classroom + ) + break + if not assigned_teacher: selected_subject = "Library" assigned_teacher = "None" assigned_room = assigned_classroom return assigned_teacher, selected_subject, assigned_room + + def _generate_section_schedule( + self, + section, + half_day_section_list, + section_subject_usage_tracker, + teacher_workload_tracker, + teacher_availability_matrix, + index + ): + """Generate a schedule for an individual section and update teacher_availability_matrix immediately with index.""" + section_schedule = [] + subjects_scheduled_today = set() + assigned_classroom = self.section_to_classroom_map[section] + total_slots_for_section = 4 if section in half_day_section_list else 7 - def _get_available_subjects(self, section, section_subject_usage_tracker): - """Get a list of subjects available for a specific section.""" - return [ - subject - for subject in self.subject_teacher_mapping.keys() - if section_subject_usage_tracker[section][subject] < self.subject_quota_limits.get(subject, 0) - ] + for slot_index, current_time_slot in enumerate(self.available_time_slots.values(), start=1): + if slot_index > total_slots_for_section: + break + + assigned_teacher, selected_subject, assigned_room = self._assign_subject_and_teacher( + section, + slot_index, + subjects_scheduled_today, + assigned_classroom, + section_subject_usage_tracker, + teacher_workload_tracker, + teacher_availability_matrix, + index, + total_slots_for_section + ) + # if assigned_teacher != "None": + # teacher_availability_matrix[assigned_teacher][index][slot_index - 1] = False - def generate_daily_schedule(self, half_day_section_list, section_subject_usage_tracker): - """Generate the daily schedule for all sections.""" + section_schedule.append( + { + "teacher_id": assigned_teacher, + "subject_id": selected_subject, + "classroom_id": assigned_room, + "time_slot": current_time_slot, + } + ) + if selected_subject != "Library": + section_subject_usage_tracker[section][selected_subject] += 1 + + if selected_subject in self.lab_subject_list or selected_subject in self.special_subject_list: + next_time_slot = self.available_time_slots.get(slot_index + 1) + if next_time_slot and slot_index + 1 <= total_slots_for_section: + section_schedule.append( + { + "teacher_id": assigned_teacher, + "subject_id": selected_subject, + "classroom_id": assigned_room, + "time_slot": next_time_slot, + } + ) + section_subject_usage_tracker[section][selected_subject] += 1 + slot_index += 1 + return section_schedule, teacher_availability_matrix + + + def generate_daily_schedule(self, half_day_section_list, section_subject_usage_tracker, index): + """Generate the daily schedule for all sections and update teacher_availability_matrix after each section.""" daily_schedule = {} teacher_workload_tracker = self._initialize_teacher_workload_tracker() for section in self.sections_manager.sections: - section_schedule = self._generate_section_schedule( - section, half_day_section_list, section_subject_usage_tracker, teacher_workload_tracker + section_schedule, self.teacher_availability_matrix = self._generate_section_schedule( + section, half_day_section_list, section_subject_usage_tracker, + teacher_workload_tracker, self.teacher_availability_matrix, index ) daily_schedule[section] = section_schedule + # print(f"Daily Schedule for {section}: {section_schedule}") + return daily_schedule, section_subject_usage_tracker, self.teacher_availability_matrix - return daily_schedule, section_subject_usage_tracker def _generate_weekly_schedule(self): - """Generate a schedule for a single week.""" + """Generate a schedule for a single week and update teacher_availability_matrix daily.""" week_schedule = {} section_subject_usage_tracker = { section: {subject: 0 for subject in self.subject_teacher_mapping.keys()} for section in self.sections_manager.sections } - for week_day in self.weekdays: + + for index, week_day in enumerate(self.weekdays): half_day_sections = self.sections_manager.sections[: len(self.sections_manager.sections) // 2] - daily_schedule, section_subject_usage_tracker = self.generate_daily_schedule( - half_day_sections, section_subject_usage_tracker + daily_schedule, section_subject_usage_tracker, self.teacher_availability_matrix = self.generate_daily_schedule( + half_day_sections, section_subject_usage_tracker, index ) week_schedule[week_day] = daily_schedule - return week_schedule, section_subject_usage_tracker + + return week_schedule, section_subject_usage_tracker, self.teacher_availability_matrix + def create_timetable(self, num_weeks): - """Create the timetable for the given number of weeks.""" + """Create the timetable for the given number of weeks and track teacher_availability_matrix.""" timetable = {} + for week in range(1, num_weeks + 1): - week_schedule, section_subject_usage_tracker = self._generate_weekly_schedule() + week_schedule, section_subject_usage_tracker, self.teacher_availability_matrix = self._generate_weekly_schedule() timetable[f"Week {week}"] = week_schedule - return timetable + return timetable, self.teacher_availability_matrix # Return final updated matrix diff --git a/GA/fitness.py b/GA/fitness.py index 7dcdb65..ca8c78e 100644 --- a/GA/fitness.py +++ b/GA/fitness.py @@ -38,6 +38,7 @@ def evaluate_timetable_fitness(self): daily_section_fitness_scores = {} weekly_fitness_scores = {} teacher_workload_tracking = {} + for week, week_schedule in self.timetable.items(): weekly_fitness = 0 daily_section_fitness_scores[week] = {} diff --git a/Samples/Adhocs/common.py b/Samples/Adhocs/common.py new file mode 100644 index 0000000..dd72e78 --- /dev/null +++ b/Samples/Adhocs/common.py @@ -0,0 +1,22 @@ +import random + + +def generate_students(num_students: int = 500) -> list: + """ + Generate a list of random students with CGPA and hostler status. + + Args: + num_students (int): Number of students to generate. + + Returns: + List[Dict]: List of student dictionaries. + """ + + return [ + { + 'ID': i, + 'CGPA': round(random.uniform(6.0, 9.8), 2), + 'Hostler': random.choice([True, False]) + } + for i in range(1, num_students + 1) + ] diff --git a/Samples/Fitness_Files/LabConstraints.py b/Samples/Fitness_Files/LabConstraints.py new file mode 100644 index 0000000..32896c1 --- /dev/null +++ b/Samples/Fitness_Files/LabConstraints.py @@ -0,0 +1,167 @@ +import random +import json +from constants.time_intervals import TimeIntervalConstant + +# Constants +LABS = ["Lab1", "Lab2", "Lab3", "Lab4"] +CLASSES = ["Class1", "Class2", "Class3", "Class4"] +TEACHERS = ["Teacher1", "Teacher2", "Teacher3", "Teacher4"] + +# Constraints +MAX_LAB_COUNT = 2 # Max labs per week for a teacher +MAX_CLASS_COUNT = 3 # Max classes per week for a teacher +MAX_TEACHER_WORKLOAD = 5 # Max hours a teacher can work in a week +MAX_CONTINUOUS_CLASSES = 2 # Max consecutive hours for a teacher + +# Workload for subjects +SUBJECT_WORKLOAD = { + "TCS-531": 3, + "TCS-502": 3, + "TCS-503": 3, + "PCS-506": 1, + "PCS-503": 1, + "TMA-502": 3, + "PMA-502": 1, + "TCS-509": 3, + "XCS-501": 2, + "CSP-501": 1, + "SCS-501": 1, + "Placement_Class": 1 +} + +# Teacher availability and subjects they teach +TEACHER_SUBJECTS = { + "TCS-531": ["AB01", "PK02"], + "TCS-502": ["SS03", "AA04", "AC05"], + "TCS-503": ["SP06", "DP07", "AC05"], + "PCS-506": ["AD08", "RD09"], + "TMA-502": ["BJ10", "RS11", "JM12", "NJ13"], + "PMA-502": ["PM14", "AD08", "AA15"], + "TCS-509": ["SJ16", "AB17", "HP18", "SG19"], + "XCS-501": ["DT20", "PA21", "NB22"], + "CSP-501": ["AK23"], + "SCS-501": ["AP24"], + "PCS-503": ["RS11", "DP07", "SP06", "VD25"], + "Placement_Class": ["AK26"] +} + +# Initialize available time slots +TimeSlots = TimeIntervalConstant.time_slots # Assuming TimeIntervalConstant has a list of time intervals + + +class TimetableFitness: + def __init__(self): + self.teachers = TEACHERS + self.labs = LABS + self.classes = CLASSES + self.subject_workload = SUBJECT_WORKLOAD + self.teacher_subjects = TEACHER_SUBJECTS + self.teacher_schedule = {teacher: {} for teacher in self.teachers} + self.section_strength = {cls: random.randint(40, 60) for cls in self.classes} + + def assign_classes_and_labs(self): + """ + Assign classes and labs to teachers based on constraints and time slots. + """ + for teacher in self.teachers: + subjects = [subject for subject, teachers in TEACHER_SUBJECTS.items() if teacher in teachers] + for subject in subjects: + time_slot = random.choice(TimeSlots) # Randomly select an available time slot + if time_slot not in self.teacher_schedule[teacher]: + self.teacher_schedule[teacher][time_slot] = subject + + def LabNo(self): + """ + Check if the number of labs assigned is within the constraints. + """ + for teacher, schedule in self.teacher_schedule.items(): + lab_count = sum(1 for subject in schedule.values() if subject in self.labs) + if lab_count > MAX_LAB_COUNT: + return False + return True + + def ClassNo(self): + """ + Check if the number of classes assigned is within the constraints. + """ + for teacher, schedule in self.teacher_schedule.items(): + class_count = sum(1 for subject in schedule.values() if subject in self.classes) + if class_count > MAX_CLASS_COUNT: + return False + return True + + def TeacherWorkload(self): + """ + Ensure total workload per teacher is within the limit. + """ + for teacher, schedule in self.teacher_schedule.items(): + total_hours = sum(SUBJECT_WORKLOAD.get(subject, 0) for subject in schedule.values()) + if total_hours > MAX_TEACHER_WORKLOAD: + return False + return True + + def ContinuousClasses(self): + """ + Ensure teachers do not have more than the allowed continuous classes. + """ + for teacher, schedule in self.teacher_schedule.items(): + sorted_slots = sorted(schedule.keys()) + continuous_count = 0 + for i in range(len(sorted_slots) - 1): + if TimeSlots.index(sorted_slots[i + 1]) - TimeSlots.index(sorted_slots[i]) == 1: + continuous_count += 1 + if continuous_count > MAX_CONTINUOUS_CLASSES: + return False + else: + continuous_count = 0 + return True + + def fitnessFunc(self): + """ + Calculate fitness score based on all constraints. + """ + fitness_score = 0 + + if self.LabNo(): + fitness_score += 10 + else: + fitness_score -= 10 + + if self.ClassNo(): + fitness_score += 10 + else: + fitness_score -= 10 + + if self.TeacherWorkload(): + fitness_score += 10 + else: + fitness_score -= 10 + + if self.ContinuousClasses(): + fitness_score += 10 + else: + fitness_score -= 10 + + return fitness_score + + +# Main Execution +timetable_fitness = TimetableFitness() + +# Assign classes and labs to teachers +timetable_fitness.assign_classes_and_labs() + +# Calculate fitness score +fitness_score = timetable_fitness.fitnessFunc() + +# Output +print(f"Final Fitness Score: {fitness_score}") + +# Save results to a JSON file +output = { + "teacher_schedule": timetable_fitness.teacher_schedule, + "fitness_score": fitness_score, + "section_strength": timetable_fitness.section_strength +} +with open("Timetable_Fitness_Result.json", "w") as f: + json.dump(output, f, indent=4) diff --git a/Samples/T. time table.py b/Samples/T. time table.py new file mode 100644 index 0000000..b1bcef8 --- /dev/null +++ b/Samples/T. time table.py @@ -0,0 +1,61 @@ +from samples import SubjectTeacherMap, WorkingDays, SampleChromosome + + +class TeacherTimetable: + def __init__(self): + # Creating timetable for each teacher + self.teacher_timetable = { + teacher: {day: [] for day in WorkingDays.days} + for teachers in SubjectTeacherMap.subject_teacher_map.values() # Iterate over list of teachers + for teacher in teachers # Iterate over individual teachers in the list + } + + + def generate_teacher_timetable(self, chromosome): + """ + Populate the teacher's timetable based on the provided chromosome schedule. + Now includes the course (Week 1, Week 2, etc.) information. + """ + for week, days in chromosome.items(): # week corresponds to "Week 1", "Week 2", etc. + for day, sections in days.items(): # day corresponds to Monday, Tuesday, etc. + for section, classes in sections.items(): + for entry in classes: + teacher_id = entry["teacher_id"] + subject_id = entry["subject_id"] + time_slot = entry["time_slot"] + classroom_id = entry["classroom_id"] + + # Add this slot to the teacher's timetable + self.teacher_timetable[teacher_id][day].append({ + "course": week, # Include the course name (Week 1, Week 2, etc.) + "section": section, + "subject_id": subject_id, + "time_slot": time_slot, + "classroom_id": classroom_id, + }) + + + def display_timetable(self): + for teacher, days in self.teacher_timetable.items(): + print(f"Teacher: {teacher}") + for day, classes in days.items(): + print(f" {day}:") + for class_entry in classes: + print(f" {class_entry['course']} - Section: {class_entry['section']}, Subject: {class_entry['subject_id']}, Time: {class_entry['time_slot']}, Classroom: {class_entry['classroom_id']}") + + +# Main Code +if __name__ == "__main__": + # Initialize timetable + teacher_timetable = TeacherTimetable() + + # Generate timetable from the sample chromosome + w = { + "Week 1": SampleChromosome.schedule1, + "Week 2": SampleChromosome.schedule1 + } + teacher_timetable.generate_teacher_timetable(w) + + # Display the teacher-specific timetable + teacher_timetable.display_timetable() + diff --git a/Samples/Teacher's chromo.json b/Samples/Teacher's chromo.json new file mode 100644 index 0000000..aa5f1ea --- /dev/null +++ b/Samples/Teacher's chromo.json @@ -0,0 +1,277 @@ +{ + "Teacher1": { + "Monday": [ + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "10:00 - 10:55", + "section": "A" + } + ], + "Tuesday": [ + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "9:00 - 9:55", + "section": "B" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "11:00 - 11:55", + "section": "A" + } + ], + "Wednesday": [ + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "10:00 - 10:55", + "section": "B" + } + ], + "Thursday": [ + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "11:00 - 11:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "2:00 - 2:55", + "section": "B" + } + ], + "Friday": [ + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "9:00 - 9:55", + "section": "B" + }, + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "11:00 - 11:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "2:00 - 2:55", + "section": "A" + } + ] + }, + "Teacher2": { + "Tuesday": [ + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "9:00 - 9:55", + "section": "B" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "11:00 - 11:55", + "section": "A" + } + ], + "Wednesday": [ + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "10:00 - 10:55", + "section": "B" + } + ], + "Thursday": [ + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "11:00 - 11:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "2:00 - 2:55", + "section": "B" + } + ], + "Friday": [ + { + "course_name": "BCA", + "subject_id": "CSP-501", + "classroom_name": "L2", + "time_slot": "9:00 - 9:55", + "section": "B" + }, + { + "course_name": "MBA", + "subject_id": "MKT-202", + "classroom_name": "R3", + "time_slot": "11:00 - 11:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "2:00 - 2:55", + "section": "A" + } + ], + "Saturday": [ + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "BTech", + "subject_id": "TCS-509", + "classroom_name": "R1", + "time_slot": "10:00 - 10:55", + "section": "A" + } + ] + }, + "Teacher3": { + "Monday": [ + { + "course_name": "BBA", + "subject_id": "HRM-301", + "classroom_name": "L3", + "time_slot": "9:00 - 9:55", + "section": "C" + }, + { + "course_name": "MBA", + "subject_id": "FIN-305", + "classroom_name": "R4", + "time_slot": "11:00 - 11:55", + "section": "A" + } + ], + "Tuesday": [ + { + "course_name": "BTech", + "subject_id": "TCS-510", + "classroom_name": "R2", + "time_slot": "10:00 - 10:55", + "section": "A" + }, + { + "course_name": "BCA", + "subject_id": "CSP-502", + "classroom_name": "L1", + "time_slot": "2:00 - 2:55", + "section": "B" + } + ], + "Wednesday": [ + { + "course_name": "MBA", + "subject_id": "MKT-203", + "classroom_name": "R3", + "time_slot": "10:00 - 10:55", + "section": "A" + }, + { + "course_name": "BBA", + "subject_id": "HRM-301", + "classroom_name": "L3", + "time_slot": "2:00 - 2:55", + "section": "C" + } + ] + }, + "Teacher4": { + "Thursday": [ + { + "course_name": "BCA", + "subject_id": "CSP-503", + "classroom_name": "L1", + "time_slot": "9:00 - 9:55", + "section": "B" + }, + { + "course_name": "BTech", + "subject_id": "TCS-511", + "classroom_name": "R2", + "time_slot": "11:00 - 11:55", + "section": "C" + } + ], + "Friday": [ + { + "course_name": "MBA", + "subject_id": "FIN-306", + "classroom_name": "R4", + "time_slot": "9:00 - 9:55", + "section": "A" + }, + { + "course_name": "BBA", + "subject_id": "HRM-302", + "classroom_name": "L3", + "time_slot": "10:00 - 10:55", + } + ] + } +} \ No newline at end of file diff --git a/Samples/chromosome.json b/Samples/chromosome.json new file mode 100644 index 0000000..e1ba7c0 --- /dev/null +++ b/Samples/chromosome.json @@ -0,0 +1,1274 @@ +{ + "Monday": { + "A": [ + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "PM14", + "subject_id": "PMA-502", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "RS11", + "subject_id": "PCS-503", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "DP07", + "subject_id": "PCS-503", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "VD25", + "subject_id": "PCS-503", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AC05", + "subject_id": "TCS-503", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AD08", + "subject_id": "PMA-502", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "AB17", + "subject_id": "TCS-509", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "HP18", + "subject_id": "TCS-509", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "PA21", + "subject_id": "XCS-501", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "RD09", + "subject_id": "PCS-506", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "AA15", + "subject_id": "PMA-502", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SG19", + "subject_id": "TCS-509", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AD08", + "subject_id": "PCS-506", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "JM12", + "subject_id": "TMA-502", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "RS11", + "subject_id": "PCS-503", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + }, + "Tuesday": { + "A": [ + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "RS11", + "subject_id": "PCS-503", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "PA21", + "subject_id": "XCS-501", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "JM12", + "subject_id": "TMA-502", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "NJ13", + "subject_id": "TMA-502", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AC05", + "subject_id": "TCS-502", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AD08", + "subject_id": "PCS-506", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "AB17", + "subject_id": "TCS-509", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "RS11", + "subject_id": "TMA-502", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "PM14", + "subject_id": "PMA-502", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "HP18", + "subject_id": "TCS-509", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "NB22", + "subject_id": "XCS-501", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "VD25", + "subject_id": "PCS-503", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "JM12", + "subject_id": "TMA-502", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "AK23", + "subject_id": "CSP-501", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + }, + "Wednesday": { + "A": [ + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AD08", + "subject_id": "PCS-506", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "PM14", + "subject_id": "PMA-502", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "PA21", + "subject_id": "XCS-501", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "RS11", + "subject_id": "TMA-502", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AA15", + "subject_id": "PMA-502", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "NB22", + "subject_id": "XCS-501", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AC05", + "subject_id": "TCS-503", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "JM12", + "subject_id": "TMA-502", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AB17", + "subject_id": "TCS-509", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "NJ13", + "subject_id": "TMA-502", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AK26", + "subject_id": "Placement_Class", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "HP18", + "subject_id": "TCS-509", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + }, + "Thursday": { + "A": [ + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AD08", + "subject_id": "PCS-506", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AB17", + "subject_id": "TCS-509", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "DT20", + "subject_id": "XCS-501", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "RD09", + "subject_id": "PCS-506", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "PA21", + "subject_id": "XCS-501", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "RS11", + "subject_id": "TMA-502", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AK26", + "subject_id": "Placement_Class", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "AC05", + "subject_id": "TCS-502", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "JM12", + "subject_id": "TMA-502", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "HP18", + "subject_id": "TCS-509", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "AK26", + "subject_id": "Placement_Class", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + }, + "Friday": { + "A": [ + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "AB17", + "subject_id": "TCS-509", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AA04", + "subject_id": "TCS-502", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "DP07", + "subject_id": "TCS-503", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "AC05", + "subject_id": "TCS-502", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "HP18", + "subject_id": "TCS-509", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "PK02", + "subject_id": "TCS-531", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "SS03", + "subject_id": "TCS-502", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + }, + "Saturday": { + "A": [ + { + "teacher_id": "BJ10", + "subject_id": "TMA-502", + "classroom_id": "R1", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AP24", + "subject_id": "SCS-501", + "classroom_id": "R1", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R1", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R1", + "time_slot": "12:05 - 1:00" + } + ], + "B": [ + { + "teacher_id": "SP06", + "subject_id": "TCS-503", + "classroom_id": "R2", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R2", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R2", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R2", + "time_slot": "12:05 - 1:00" + } + ], + "C": [ + { + "teacher_id": "SJ16", + "subject_id": "TCS-509", + "classroom_id": "R3", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "AB01", + "subject_id": "TCS-531", + "classroom_id": "R3", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R3", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R3", + "time_slot": "12:05 - 1:00" + } + ], + "D": [ + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R4", + "time_slot": "3:30 - 4:25" + } + ], + "E": [ + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R5", + "time_slot": "3:30 - 4:25" + } + ], + "F": [ + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "9:00 - 9:55" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "9:55 - 10:50" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "11:10 - 12:05" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "12:05 - 1:00" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "1:20 - 2:15" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "2:15 - 3:10" + }, + { + "teacher_id": "None", + "subject_id": "Library", + "classroom_id": "R6", + "time_slot": "3:30 - 4:25" + } + ] + } +} \ No newline at end of file diff --git a/Samples/samples.py b/Samples/samples.py index f4c1b39..e996b16 100644 --- a/Samples/samples.py +++ b/Samples/samples.py @@ -69,6 +69,64 @@ class PenaltyConstants: PENALTY_UN_PREFERRED_SLOT = 5 PENALTY_OVERLOAD_TEACHER = 10 +class Depatments: + teacher_department_mapping = { + "AB01": ["CSE"], + "PK02": ["CSE", "Management", "SOC"], + "SS03": ["CSE"], + "AA04": ["CSE", "Management"], + "AC05": ["CSE", "SOC"], + "SP06": ["CSE", "Management"], + "DP07": ["CSE", "SOC"], + "AD08": ["CSE", "Management"], + "RD09": ["CSE"], + "BJ10": ["CSE", "Management", "SOC"], + "RS11": ["CSE"], + "JM12": ["CSE", "SOC"], + "NJ13": ["CSE", "Management"], + "PM14": ["CSE"], + "AA15": ["CSE", "SOC"], + "SJ16": ["CSE", "Management"], + "AB17": ["CSE", "SOC"], + "HP18": ["CSE", "Management"], + "SG19": ["CSE", "SOC"], + "DT20": ["CSE", "Management"], + "PA21": ["CSE", "SOC"], + "NB22": ["CSE", "Management"], + "AK23": ["CSE"], + "AP24": ["CSE", "SOC"], + "VD25": ["CSE", "Management"], + "AK26": ["CSE"], +} +class InterDepartment: + teacher_availability_matrix = { + "AB01": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "PK02": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "SS03": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AA04": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AC05": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "SP06": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "DP07": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AD08": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "RD09": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "BJ10": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "RS11": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "JM12": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "NJ13": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "PM14": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AA15": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "SJ16": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AB17": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "HP18": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "SG19": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "DT20": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "PA21": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "NB22": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AK23": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AP24": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "VD25": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + "AK26": [[True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True], [True, True, True, True, True, True, True]], + } class TeacherWorkload: Weekly_workLoad = { diff --git a/__init__.py b/__init__.py index 90a99a7..1721540 100644 --- a/__init__.py +++ b/__init__.py @@ -11,7 +11,7 @@ SpecialSubjects, SubjectWeeklyQuota, Classrooms, - Sections, + Sections, InterDepartment, ) @@ -28,12 +28,12 @@ def timetable_generation(): special_subjects=SpecialSubjects.special_subjects, subject_quota_limits=SubjectWeeklyQuota.subject_quota, labs_list=Classrooms.labs, + labs=SpecialSubjects.Labs, teacher_duty_days=TeacherWorkload.teacher_duty_days, + teacher_availability_matrix=InterDepartment.teacher_availability_matrix, ) - timetable = timetable_generator.create_timetable(Defaults.initial_no_of_chromosomes) - from icecream import ic - ic(timetable) + timetable, teacher_avail_matrix = timetable_generator.create_timetable(Defaults.initial_no_of_chromosomes) # Fitness of each Chromosome fitness_calculator = TimetableFitnessEvaluator( timetable, @@ -54,7 +54,7 @@ def timetable_generation(): # Selection of all Chromosomes selection_object = TimeTableSelection() selected_chromosomes = selection_object.select_chromosomes(fitness_scores[1]) - ic(len(selected_chromosomes)) + # ic(len(selected_chromosomes)) # Crossover for all selected Chromosomes crossover_object = TimeTableCrossOver() @@ -82,10 +82,10 @@ def timetable_generation(): ] - ic(mutated_chromosomes) - ic(selected_chromosomes) - - ic(selected_chromosomes) + # ic(mutated_chromosomes) + # ic(selected_chromosomes) + # + # ic(selected_chromosomes) # Store best of Chromosomes best_chromosome_score = -1 best_chromosome = dict() @@ -94,11 +94,15 @@ def timetable_generation(): if int(week_score) > best_chromosome_score: best_chromosome_score = int(week_score) best_chromosome = timetable[week_no] + return best_chromosome - ic(f"Best Chromosome: {best_chromosome}") def run_timetable_generation(): for generation in range(Defaults.total_no_of_generations): best_chromosome = timetable_generation() return best_chromosome + + +tt = run_timetable_generation() +# ic(f"Best Chromosome: {tt}") \ No newline at end of file diff --git a/test_suite/Crossover_Test.py b/test_suite/Crossover_Test.py new file mode 100644 index 0000000..bce6513 --- /dev/null +++ b/test_suite/Crossover_Test.py @@ -0,0 +1,629 @@ +#CROSSOVER + +#1. def create_chromosome(self): + +import random +import unittest + +# TimetableScheduler Class +class TimetableScheduler: + def __init__(self): + self.sections = ["A", "B", "C", "D"] + self.time_slots = [1, 2, 3, 4, 5, 6, 7] # Time slots per day + self.days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + self.subject_teacher_map = { + "TCS-531": ["AB01", "PK02"], + "TCS-502": ["SS03", "AA04", "AC05"], + "TCS-503": ["SP06", "DP07", "AC05"], + "PCS-506": ["AD08", "RD09"], + "TMA-502": ["BJ10", "RS11", "JM12", "NJ13"], + "PMA-502": ["PM14", "AD08", "AA15"], + "TCS-509": ["SJ16", "AB17", "HP18", "SG19"], + "XCS-501": ["DT20", "PA21", "NB22"], + "CSP-501": ["AK23"], + "SCS-501": ["AP24"] + } + self.classrooms = ["R1", "R2", "R3", "R4", "R5"] + self.room_capacity = {"R1": 200, "R2": 230, "R3": 240, "R4": 250, "R5": 250} + self.section_strength = {"A": 200, "B": 200, "C": 200, "D": 100} + + def create_chromosome(self): + schedule = {} + teacher_schedule = {slot: set() for slot in self.time_slots} + room_schedule = {slot: {} for slot in self.time_slots} + + for day in self.days: + schedule[day] = {} + for section in self.sections: + schedule[day][section] = [] + used_time_slots = set() + + while len(schedule[day][section]) < 7: + subject = random.choice(list(self.subject_teacher_map.keys())) + teacher = random.choice(self.subject_teacher_map[subject]) + classroom = random.choice(self.classrooms) + + # Find an available time slot + available_slots = [ + slot for slot in self.time_slots if slot not in used_time_slots + ] + if not available_slots: + break + + time_slot = random.choice(available_slots) + + # Check for conflicts + if (teacher not in teacher_schedule[time_slot] and + section not in room_schedule[time_slot].get(classroom, [])): + + entry = { + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": classroom, + "time_slot": time_slot + } + schedule[day][section].append(entry) + used_time_slots.add(time_slot) + + # Update schedules to avoid future conflicts + teacher_schedule[time_slot].add(teacher) + if classroom not in room_schedule[time_slot]: + room_schedule[time_slot][classroom] = [] + room_schedule[time_slot][classroom].append(section) + + # Ensure the section has all 7 slots for the day + if len(schedule[day][section]) < 7: + raise RuntimeError( + f"Failed to generate a complete schedule for section {section} on {day}." + ) + + return schedule + + +# Unit Test Class for TimetableScheduler +class TestTimetableScheduler(unittest.TestCase): + def setUp(self): + self.scheduler = TimetableScheduler() + + def test_schedule_for_all_days_and_sections(self): + schedule = self.scheduler.create_chromosome() + + # Check every day has all sections with 7 time slots + for day in self.scheduler.days: + self.assertIn(day, schedule) + for section in self.scheduler.sections: + self.assertIn(section, schedule[day]) + self.assertEqual(len(schedule[day][section]), 7) + + def test_conflict_free_teacher_allocation(self): + schedule = self.scheduler.create_chromosome() + + # Check for teacher conflicts + for day in self.scheduler.days: + teacher_schedule = {slot: set() for slot in self.scheduler.time_slots} + for section in self.scheduler.sections: + for entry in schedule[day][section]: + teacher = entry["teacher_id"] + time_slot = entry["time_slot"] + self.assertNotIn(teacher, teacher_schedule[time_slot]) + teacher_schedule[time_slot].add(teacher) + + def test_room_capacity(self): + schedule = self.scheduler.create_chromosome() + + # Ensure room capacity meets section strength + for day in self.scheduler.days: + for section in self.scheduler.sections: + for entry in schedule[day][section]: + classroom = entry["classroom_id"] + self.assertGreaterEqual( + self.scheduler.room_capacity[classroom], + self.scheduler.section_strength[section] + ) + + def test_time_slots_coverage(self): + schedule = self.scheduler.create_chromosome() + + # Check if all time slots are covered per section per day + for day in self.scheduler.days: + for section in self.scheduler.sections: + time_slots = [entry["time_slot"] for entry in schedule[day][section]] + self.assertEqual(len(set(time_slots)), 7) # Ensure exactly 7 slots + + +if __name__ == "__main__": + unittest.main() + +#2. def create_multiple_chromosomes(self, num_chromosomes): + +import random +import unittest +import time + +class TimetableScheduler: + def __init__(self, subjects, rooms, days, sections, max_attempts=10): + self.subjects = subjects # List of subject codes + self.rooms = rooms # List of available rooms + self.days = days # List of days in the week + self.sections = sections # List of sections + self.max_attempts = max_attempts # Max attempts to find a valid slot + + self.schedule = {} # Schedule dictionary to store the timetable + + def generate_random_schedule(self): + """ Generate a random schedule for each subject and section. """ + timetable = {} + for subject in self.subjects: + for section in self.sections: + attempts = 0 + while attempts < self.max_attempts: + # Randomly select a day, room, and time slot + day = random.choice(self.days) + room = random.choice(self.rooms) + time_slot = f"{day}_{room}" + + # Check if the time_slot is available (no conflicts) + if self.is_slot_available(subject, section, time_slot): + timetable[subject] = (section, day, room, time_slot) + break + else: + attempts += 1 + if attempts == self.max_attempts: + raise RuntimeError( + f"Failed to assign a valid slot for {subject} on {day} for {section} after {attempts} attempts." + ) + return timetable + + def is_slot_available(self, subject, section, time_slot): + """ Check if a slot is available by ensuring no conflicts. """ + # Check if any other subject has been scheduled in the same time slot + for key, value in self.schedule.items(): + if value[3] == time_slot: # Slot conflict + return False + return True + + def create_chromosome(self): + """ Create a chromosome that represents a valid timetable. """ + attempts = 0 + while attempts < self.max_attempts: + try: + timetable = self.generate_random_schedule() + self.schedule = timetable # Assign the generated schedule to the global timetable + return timetable + except RuntimeError as e: + print(f"Error: {e}") + attempts += 1 + if attempts == self.max_attempts: + raise RuntimeError("Failed to generate a valid timetable after multiple attempts.") + # Reset and retry + self.schedule = {} + + return None + + def create_multiple_chromosomes(self, num_chromosomes): + """ Create multiple chromosomes (timetables). """ + chromosomes = [] + for _ in range(num_chromosomes): + chromosome = self.create_chromosome() + if chromosome: + chromosomes.append(chromosome) + else: + print("Failed to create a valid chromosome.") + return chromosomes + + +class TestTimetableScheduler(unittest.TestCase): + + def setUp(self): + """ Setup the test case environment. """ + self.subjects = ['PCS-506', 'XCS-501', 'TCS-502', 'CSP-501', 'TCS-531'] + self.rooms = ['Room 101', 'Room 102', 'Room 103'] + self.days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] + self.sections = ['A', 'B', 'C'] + + self.scheduler = TimetableScheduler(self.subjects, self.rooms, self.days, self.sections) + + def test_chromosome_structure(self): + """ Test the structure of each chromosome. """ + num_chromosomes = 3 + start_time = time.time() + chromosomes = self.scheduler.create_multiple_chromosomes(num_chromosomes) + end_time = time.time() + + self.assertEqual(len(chromosomes), num_chromosomes) + print(f"Ran {num_chromosomes} tests in {end_time - start_time:.4f} seconds") + + for idx, chromosome in enumerate(chromosomes): + print(f"Chromosome {idx+1}:") + for subject, (section, day, room, time_slot) in chromosome.items(): + print(f"{subject} - {section} - {day} - {room} - {time_slot}") + print() + + def test_conflict_free_chromosomes(self): + """ Test that chromosomes are conflict-free. """ + num_chromosomes = 3 + start_time = time.time() + chromosomes = self.scheduler.create_multiple_chromosomes(num_chromosomes) + end_time = time.time() + + self.assertEqual(len(chromosomes), num_chromosomes) + print(f"Ran {num_chromosomes} tests in {end_time - start_time:.4f} seconds") + + for idx, chromosome in enumerate(chromosomes): + for subject, (section, day, room, time_slot) in chromosome.items(): + # Check for conflicts (same time slot for different subjects) + for other_subject, other_values in chromosome.items(): + if subject != other_subject and time_slot == other_values[3]: + self.fail(f"Conflict detected: {subject} and {other_subject} in the same time slot: {time_slot}") + + def test_create_multiple_chromosomes_count(self): + """ Test if the correct number of chromosomes is generated. """ + num_chromosomes = 5 + start_time = time.time() + chromosomes = self.scheduler.create_multiple_chromosomes(num_chromosomes) + end_time = time.time() + + self.assertEqual(len(chromosomes), num_chromosomes) + print(f"Ran {num_chromosomes} tests in {end_time - start_time:.4f} seconds") + + def test_room_capacity_in_multiple_chromosomes(self): + """ Test room capacity constraint across multiple chromosomes. """ + num_chromosomes = 3 + start_time = time.time() + chromosomes = self.scheduler.create_multiple_chromosomes(num_chromosomes) + end_time = time.time() + + self.assertEqual(len(chromosomes), num_chromosomes) + print(f"Ran {num_chromosomes} tests in {end_time - start_time:.4f} seconds") + + # Test room capacity constraints for each timetable (e.g., number of rooms used) + for idx, chromosome in enumerate(chromosomes): + room_usage = {} + for subject, (section, day, room, time_slot) in chromosome.items(): + room_usage[room] = room_usage.get(room, 0) + 1 + print(f"Chromosome {idx+1} Room Usage: {room_usage}") + + +# Run the tests +if __name__ == "__main__": + unittest.main() + +#3. def crossover(self, parent1, parent2): + +import random +import unittest + +class TimetableScheduler: + def __init__(self, subjects, rooms, days, sections): + self.subjects = subjects # List of subject codes + self.rooms = rooms # List of available rooms + self.days = days # List of days in the week + self.sections = sections # List of sections + + def crossover(self, parent1, parent2): + """Perform single-point crossover on two parent chromosomes.""" + offspring1 = {} + offspring2 = {} + + days = self.days + crossover_point = random.randint(1, len(days) - 1) + + for i, day in enumerate(days): + if i < crossover_point: + offspring1[day] = parent1[day] + offspring2[day] = parent2[day] + else: + offspring1[day] = parent2[day] + offspring2[day] = parent1[day] + + return offspring1, offspring2 + +# Unit test for the crossover function +class TestCrossoverMethod(unittest.TestCase): + + def setUp(self): + # Example data to simulate chromosomes for testing + self.subjects = ['PCS-506', 'XCS-501', 'TCS-502', 'CSP-501', 'TCS-531'] + self.rooms = ['Room 101', 'Room 102', 'Room 103'] + self.days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] + self.sections = ['A', 'B', 'C'] + + # Example chromosomes (parents) + self.parent1 = { + 'Monday': 'PCS-506', + 'Tuesday': 'XCS-501', + 'Wednesday': 'TCS-502', + 'Thursday': 'CSP-501', + 'Friday': 'TCS-531' + } + self.parent2 = { + 'Monday': 'TCS-502', + 'Tuesday': 'PCS-506', + 'Wednesday': 'CSP-501', + 'Thursday': 'TCS-531', + 'Friday': 'XCS-501' + } + + # Create an instance of the TimetableScheduler + self.scheduler = TimetableScheduler(self.subjects, self.rooms, self.days, self.sections) + + def test_crossover_correct_number_of_offspring(self): + """Test if crossover produces exactly two offspring.""" + offspring1, offspring2 = self.scheduler.crossover(self.parent1, self.parent2) + self.assertEqual(len(offspring1), len(self.days)) + self.assertEqual(len(offspring2), len(self.days)) + + def test_crossover_correct_assignment(self): + """Test if crossover correctly assigns the parts from parents.""" + offspring1, offspring2 = self.scheduler.crossover(self.parent1, self.parent2) + + # Get the crossover point from the offspring's first day in the timetable + crossover_point = next(i for i, day in enumerate(self.days) if offspring1[self.days[i]] != self.parent1[self.days[i]]) + + # Check the assignment from parent1 and parent2 in the offspring + for i in range(crossover_point): + self.assertEqual(offspring1[self.days[i]], self.parent1[self.days[i]]) + self.assertEqual(offspring2[self.days[i]], self.parent2[self.days[i]]) + + for i in range(crossover_point, len(self.days)): + self.assertEqual(offspring1[self.days[i]], self.parent2[self.days[i]]) + self.assertEqual(offspring2[self.days[i]], self.parent1[self.days[i]]) + + def test_crossover_randomness(self): + """Test if the crossover point is truly random by checking different results.""" + offspring1_a, offspring2_a = self.scheduler.crossover(self.parent1, self.parent2) + offspring1_b, offspring2_b = self.scheduler.crossover(self.parent1, self.parent2) + + # Check that the offspring are not the same (i.e., crossover point was different) + self.assertNotEqual(offspring1_a, offspring1_b) + self.assertNotEqual(offspring2_a, offspring2_b) + + def test_crossover_empty_parent(self): + """Test if crossover handles empty parents gracefully.""" + empty_parent = {} + with self.assertRaises(KeyError): + self.scheduler.crossover(empty_parent, self.parent2) + + with self.assertRaises(KeyError): + self.scheduler.crossover(self.parent1, empty_parent) + + +if __name__ == '__main__': + unittest.main() + +#4. def mutate_timetable(self, chromosome, mutation_rate=0.7): + +import random +import unittest + +class TimetableScheduler: + def __init__(self, classrooms, sections, time_slots): + self.classrooms = classrooms # List of classrooms available for assignment + self.sections = sections # List of sections + self.time_slots = time_slots # List of time slots available for scheduling + + def mutate_timetable(self, chromosome, mutation_rate=0.7): + """Applies mutation to a timetable chromosome.""" + classrooms = self.classrooms + sections = self.sections + mutated_chromosome = chromosome.copy() + + for day, section_schedule in mutated_chromosome.items(): + for section, entries in section_schedule.items(): + for entry in entries: + if random.random() < mutation_rate: + entry["time_slot"] = random.choice(self.time_slots) + entry["classroom_id"] = random.choice(classrooms) + entry["section"] = random.choice(sections) + + return mutated_chromosome + + +class TestMutateTimetable(unittest.TestCase): + + def setUp(self): + # Example data to simulate a timetable chromosome + self.classrooms = ['Room 101', 'Room 102', 'Room 103'] + self.sections = ['A', 'B', 'C'] + self.time_slots = ['9:00 AM', '10:00 AM', '11:00 AM', '1:00 PM', '2:00 PM'] + + # Example timetable (chromosome) + self.chromosome = { + 'Monday': { + 'A': [{'time_slot': '9:00 AM', 'classroom_id': 'Room 101', 'section': 'A'}], + 'B': [{'time_slot': '10:00 AM', 'classroom_id': 'Room 102', 'section': 'B'}], + 'C': [{'time_slot': '11:00 AM', 'classroom_id': 'Room 103', 'section': 'C'}], + }, + 'Tuesday': { + 'A': [{'time_slot': '9:00 AM', 'classroom_id': 'Room 101', 'section': 'A'}], + 'B': [{'time_slot': '10:00 AM', 'classroom_id': 'Room 102', 'section': 'B'}], + 'C': [{'time_slot': '11:00 AM', 'classroom_id': 'Room 103', 'section': 'C'}], + } + } + + # Create an instance of the TimetableScheduler + self.scheduler = TimetableScheduler(self.classrooms, self.sections, self.time_slots) + + def test_mutation_occurrence(self): + """Test if the mutation occurs with the expected frequency.""" + mutation_rate = 0.7 # Mutation rate is set to 70% + + # Run multiple mutations to get more stable results + num_runs = 10 # You can try more runs for higher accuracy + mutation_count = 0 + total_entries = sum(len(entries) for day in self.chromosome.values() for section, entries in day.items()) + + for _ in range(num_runs): + mutated_chromosome = self.scheduler.mutate_timetable(self.chromosome, mutation_rate) + + # Count how many entries were mutated + mutated_count = 0 + for day, section_schedule in mutated_chromosome.items(): + for section, entries in section_schedule.items(): + for entry in entries: + # Check if the mutation applied (i.e., values changed) + if entry["time_slot"] != '9:00 AM' or entry["classroom_id"] != 'Room 101' or entry["section"] != 'A': + mutated_count += 1 + + mutation_percentage = mutated_count / total_entries + mutation_count += mutation_percentage # Accumulate mutation percentages + + # Calculate average mutation rate across runs + average_mutation_percentage = mutation_count / num_runs + print(f"Average Mutation Rate: {average_mutation_percentage}") # To check the mutation rate in the output + + # Assert that mutation occurs in the expected range + self.assertGreaterEqual(average_mutation_percentage, 0.5) # At least 50% mutation + self.assertLessEqual(average_mutation_percentage, 1.0) # No more than 100% mutation + + def test_mutate_timetable(self): + """Test if mutation correctly applies changes to the timetable.""" + mutated_chromosome = self.scheduler.mutate_timetable(self.chromosome, mutation_rate=1.0) + + # Check if mutation has applied changes + for day, section_schedule in mutated_chromosome.items(): + for section, entries in section_schedule.items(): + for entry in entries: + # Check if time_slot, classroom_id, and section have been mutated + self.assertIn(entry["time_slot"], self.time_slots) + self.assertIn(entry["classroom_id"], self.classrooms) + self.assertIn(entry["section"], self.sections) + + def test_mutation_validity(self): + """Test if the mutated timetable remains valid.""" + mutated_chromosome = self.scheduler.mutate_timetable(self.chromosome, mutation_rate=1.0) + + # Validate that the mutation results in valid values (time_slot, classroom_id, section) + for day, section_schedule in mutated_chromosome.items(): + for section, entries in section_schedule.items(): + for entry in entries: + # Ensure the mutated values are within the expected lists + self.assertIn(entry["time_slot"], self.time_slots) + self.assertIn(entry["classroom_id"], self.classrooms) + self.assertIn(entry["section"], self.sections) + + def test_mutate_empty_chromosome(self): + """Test mutation with an empty chromosome.""" + empty_chromosome = {} + mutated_chromosome = self.scheduler.mutate_timetable(empty_chromosome, mutation_rate=1.0) + self.assertEqual(mutated_chromosome, empty_chromosome) # Should remain empty + + +if __name__ == '__main__': + unittest.main() + +#5.def print_chromosomes(self, chromosomes): + +import unittest +from unittest.mock import patch +from io import StringIO + +class TimetableScheduler: + def __init__(self, classrooms, sections, teachers, subjects, time_slots): + self.classrooms = classrooms # List of classrooms available for assignment + self.sections = sections # List of sections + self.teachers = teachers # List of teacher IDs + self.subjects = subjects # List of subject IDs + self.time_slots = time_slots # List of time slots available for scheduling + + def print_chromosomes(self, chromosomes): + for idx, chromosome in enumerate(chromosomes, 1): + print(f"\nChromosome {idx}:") + for day, sections in chromosome.items(): + print(f" {day}:") + for section, schedule in sections.items(): + print(f" Section {section}:") + for entry in schedule: + print(f" Time Slot: {entry['time_slot']}") + print(f" Teacher ID: {entry['teacher_id']}") + print(f" Subject ID: {entry['subject_id']}") + print(f" Classroom ID: {entry['classroom_id']}") + print() + + +class TestTimetableScheduler(unittest.TestCase): + + def setUp(self): + # Example data to simulate a timetable chromosome + self.classrooms = ['Room 101', 'Room 102', 'Room 103'] + self.sections = ['A', 'B', 'C'] + self.teachers = ['T1', 'T2', 'T3'] + self.subjects = ['S1', 'S2', 'S3'] + self.time_slots = ['9:00 AM', '10:00 AM', '11:00 AM', '1:00 PM', '2:00 PM'] + + # Example timetable chromosomes + self.chromosomes = [ + { + 'Monday': { + 'A': [{'time_slot': '9:00 AM', 'teacher_id': 'T1', 'subject_id': 'S1', 'classroom_id': 'Room 101'}], + 'B': [{'time_slot': '10:00 AM', 'teacher_id': 'T2', 'subject_id': 'S2', 'classroom_id': 'Room 102'}], + 'C': [{'time_slot': '11:00 AM', 'teacher_id': 'T3', 'subject_id': 'S3', 'classroom_id': 'Room 103'}], + }, + 'Tuesday': { + 'A': [{'time_slot': '9:00 AM', 'teacher_id': 'T1', 'subject_id': 'S1', 'classroom_id': 'Room 101'}], + 'B': [{'time_slot': '10:00 AM', 'teacher_id': 'T2', 'subject_id': 'S2', 'classroom_id': 'Room 102'}], + 'C': [{'time_slot': '11:00 AM', 'teacher_id': 'T3', 'subject_id': 'S3', 'classroom_id': 'Room 103'}], + } + } + ] + + # Create an instance of the TimetableScheduler + self.scheduler = TimetableScheduler(self.classrooms, self.sections, self.teachers, self.subjects, self.time_slots) + + @patch('sys.stdout', new_callable=StringIO) + def test_print_chromosomes(self, mock_stdout): + """Test if print_chromosomes correctly prints the chromosome information.""" + self.scheduler.print_chromosomes(self.chromosomes) + + # Get the printed output + output = mock_stdout.getvalue() + + # Expected output for the given chromosomes + expected_output = """ +Chromosome 1: + Monday: + Section A: + Time Slot: 9:00 AM + Teacher ID: T1 + Subject ID: S1 + Classroom ID: Room 101 + + Section B: + Time Slot: 10:00 AM + Teacher ID: T2 + Subject ID: S2 + Classroom ID: Room 102 + + Section C: + Time Slot: 11:00 AM + Teacher ID: T3 + Subject ID: S3 + Classroom ID: Room 103 + + Tuesday: + Section A: + Time Slot: 9:00 AM + Teacher ID: T1 + Subject ID: S1 + Classroom ID: Room 101 + + Section B: + Time Slot: 10:00 AM + Teacher ID: T2 + Subject ID: S2 + Classroom ID: Room 102 + + Section C: + Time Slot: 11:00 AM + Teacher ID: T3 + Subject ID: S3 + Classroom ID: Room 103 +""" + # Clean up any extra whitespace from the actual output + output = output.strip() + + # Assert if the output matches the expected output + self.assertEqual(output, expected_output.strip()) + +if __name__ == '__main__': + unittest.main() diff --git a/test_suite/Fitness_Test.py b/test_suite/Fitness_Test.py new file mode 100644 index 0000000..43b709b --- /dev/null +++ b/test_suite/Fitness_Test.py @@ -0,0 +1,692 @@ +# FITNESS + +#1. def __init__(self, sections): + +import unittest + +class Sections: + def __init__(self, num_sections): + # Generate sections as sectionA, sectionB, sectionC, etc. + self.sections = [f"section{chr(65 + i)}" for i in range(num_sections)] + + def __iter__(self): + # Make the class iterable by returning an iterator over the sections list + return iter(self.sections) + + def __len__(self): + # Optionally, define the length of the sections + return len(self.sections) + + def __getitem__(self, index): + # Optional: Enable indexing for the sections + return self.sections[index] + + +# TimetableFitness Class (to manage and calculate timetable fitness) +class TimetableFitness: + def __init__(self, sections): + # Ensure `self.sections` is iterable (convert Sections object to a list if needed) + if isinstance(sections, Sections): + self.sections = list(sections) # Convert to list if it is a custom iterable object + else: + self.sections = sections # Assume it is already a list or other iterable + + # Mocking additional data attributes for the sake of this example + self.days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + self.time_slots = ["9-10 AM", "10-11 AM", "11-12 PM", "1-2 PM", "2-3 PM"] + + # Teacher-subject mapping + self.subject_teacher_map = {"TCS-531": ["Teacher1", "Teacher2"], "TCS-502": ["Teacher3", "Teacher4"]} + + # Classroom and capacity details + self.classrooms = ["Room1", "Room2", "Room3"] + self.room_capacity = {"Room1": 30, "Room2": 25, "Room3": 40} + + # Data structures for tracking schedules + self.teacher_schedule = {slot: {} for slot in self.time_slots} + self.room_schedule = {slot: {} for slot in self.time_slots} + self.assigned_teachers = {section: {} for section in self.sections} + self.section_rooms = {section: self.classrooms[i % len(self.classrooms)] for i, section in enumerate(self.sections)} + + # Subject-specific constraints + self.subject_weekly_quota = { + "TCS-531": 3, + "TCS-502": 3, + "Placement_Class": 1 + } + + self.weekly_assignments = {section: {subject: 0 for subject in self.subject_weekly_quota} for section in self.sections} + + # Teacher constraints + self.teacher_preferences = {teacher_id: [1, 2, 3, 4, 5, 6, 7] for teacher_id in ["Teacher1", "Teacher2", "Teacher3", "Teacher4"]} + self.teacher_work_load = {teacher: 5 for teacher in self.teacher_preferences} + self.teacher_assignments = {} + + +# Unit Test Class +class TestTimetableFitness(unittest.TestCase): + def setUp(self): + # Create an instance of Sections with 5 sections + self.sections = Sections(5) + self.timetable_fitness = TimetableFitness(self.sections) + + def test_sections_iterable(self): + # Test that Sections object is iterable + sections_list = list(self.sections) + expected_sections = ["sectionA", "sectionB", "sectionC", "sectionD", "sectionE"] + self.assertEqual(sections_list, expected_sections) + + def test_section_rooms_mapping(self): + # Test that each section is mapped to a classroom + section_rooms = self.timetable_fitness.section_rooms + self.assertEqual(len(section_rooms), len(self.sections)) + self.assertTrue(all(section in section_rooms for section in self.sections)) + + def test_assigned_teachers_structure(self): + # Test that assigned teachers structure is initialized correctly + assigned_teachers = self.timetable_fitness.assigned_teachers + self.assertEqual(len(assigned_teachers), len(self.sections)) + self.assertTrue(all(section in assigned_teachers for section in self.sections)) + + +if __name__ == "__main__": + unittest.main() + +#2. def generate_day_schedule(self, day, half_day_sections, week_number): + +import random +from collections import defaultdict + +class TimetableGenerator: + def __init__(self, sections, time_slots, subject_teacher_map, section_rooms, lab): + self.sections = sections + self.time_slots = time_slots + self.subject_teacher_map = subject_teacher_map + self.section_rooms = section_rooms + self.lab = lab + self.weekly_assignments = defaultdict(lambda: defaultdict(int)) + self.teacher_schedule = defaultdict(dict) + self.room_schedule = defaultdict(dict) + self.teacher_assignments = defaultdict(dict) + + def generate_day_schedule(self, day, half_day_sections, week_number): + day_schedule = {} + subject_teacher_usage = {subject: iter(teachers) for subject, teachers in self.subject_teacher_map.items()} + + for section in self.sections: + section_schedule = [] + subjects_used_today = set() + current_room = self.section_rooms[section] + num_slots = 4 if section in half_day_sections else 7 + + index = 1 + while index <= num_slots: + time_slot = self.time_slots[index] + + # Skip if time_slot is already used + if any(item['time_slot'] == time_slot for item in section_schedule): + index += 1 + continue + + available_subjects = list(self.subject_teacher_map.keys()) + subject, teacher = None, None + is_lab = False + + while available_subjects: + subject = random.choice(available_subjects) + + # Handle special rules for Placement_Class and labs + if subject == "Placement_Class" and index != 6: + available_subjects.remove(subject) + continue + + if "Lab" in subject: + if index + 1 > num_slots: # Ensure space for double slot + available_subjects.remove(subject) + continue + is_lab = True # Mark this as a lab + + if subject not in subjects_used_today: + teacher_iter = subject_teacher_usage[subject] + try: + teacher = next(teacher_iter) + except StopIteration: + teacher_iter = iter(self.subject_teacher_map[subject]) + teacher = next(teacher_iter) + subject_teacher_usage[subject] = teacher_iter + break + + available_subjects.remove(subject) + + if subject is None or teacher is None: + subject, teacher = "Library", "None" + + if subject != "Library": + self.weekly_assignments[section][subject] += 1 + + subjects_used_today.add(subject) + + # Assign rooms based on whether it's a lab + assigned_room = random.choice(self.lab) if is_lab else current_room + self.teacher_schedule[time_slot][teacher] = section + self.room_schedule[time_slot][assigned_room] = section + + # Handle double slot allocation for labs + if is_lab: + next_slot_index = index + 1 + if next_slot_index <= num_slots: + next_time_slot = self.time_slots[next_slot_index] + section_schedule.append({ + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": next_time_slot + }) + self.room_schedule[next_time_slot][assigned_room] = section + self.teacher_schedule[next_time_slot][teacher] = section + index += 1 # Skip the next slot for lab + + section_schedule.append({ + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": time_slot + }) + + index += 1 + + day_schedule[section] = section_schedule + return day_schedule +import unittest + +class TestGenerateDaySchedule(unittest.TestCase): + def setUp(self): + self.sections = ["A", "B", "C"] + self.time_slots = { + 1: "9:00-10:00", + 2: "10:00-11:00", + 3: "11:00-12:00", + 4: "12:00-1:00", + 5: "2:00-3:00", + 6: "3:00-4:00", + 7: "4:00-5:00" + } + self.subject_teacher_map = { + "Math": ["T1", "T2"], + "Physics": ["T3"], + "Chemistry Lab": ["T4"], + "Placement_Class": ["T5"], + } + self.section_rooms = {"A": "R1", "B": "R2", "C": "R3"} + self.lab = ["Lab1", "Lab2"] + self.tt = TimetableGenerator( + self.sections, self.time_slots, self.subject_teacher_map, self.section_rooms, self.lab + ) + + def test_schedule_structure(self): + schedule = self.tt.generate_day_schedule("Monday", ["A"], 1) + self.assertIsInstance(schedule, dict) + for section, section_schedule in schedule.items(): + self.assertIsInstance(section_schedule, list) + for entry in section_schedule: + self.assertIn("teacher_id", entry) + self.assertIn("subject_id", entry) + self.assertIn("classroom_id", entry) + self.assertIn("time_slot", entry) + + def test_half_day_slots(self): + schedule = self.tt.generate_day_schedule("Monday", ["A"], 1) + for section, section_schedule in schedule.items(): + if section == "A": + self.assertEqual(len(section_schedule), 4) + else: + self.assertEqual(len(section_schedule), 7) + + def test_no_repeated_time_slots(self): + schedule = self.tt.generate_day_schedule("Monday", ["A"], 1) + for section, section_schedule in schedule.items(): + time_slots = [entry["time_slot"] for entry in section_schedule] + self.assertEqual(len(time_slots), len(set(time_slots))) + + def test_lab_double_slots(self): + schedule = self.tt.generate_day_schedule("Monday", ["A"], 1) + for section, section_schedule in schedule.items(): + for i, entry in enumerate(section_schedule): + if "Lab" in entry['subject_id']: + current_slot = entry['time_slot'] + current_index = list(self.tt.time_slots.values()).index(current_slot) + + # Check if the lab occupies the next time slot + if current_index + 1 < len(self.tt.time_slots): + next_slot = section_schedule[i + 1]['time_slot'] + expected_next_slot = self.tt.time_slots[current_index + 1] + self.assertEqual( + next_slot, + expected_next_slot, + f"Lab for section {section} did not occupy consecutive slots as expected." + ) + + def test_teacher_and_room_assignments(self): + schedule = self.tt.generate_day_schedule("Monday", ["A"], 1) + for section, section_schedule in schedule.items(): + for entry in section_schedule: + teacher = entry['teacher_id'] + room = entry['classroom_id'] + time_slot = entry['time_slot'] + self.assertIn(teacher, self.tt.teacher_schedule[time_slot]) + self.assertIn(room, self.tt.room_schedule[time_slot]) + + +if __name__ == "__main__": + unittest.main() + +#3. def create_timetable(self, num_weeks=1): + +import unittest +from collections import defaultdict +import random + +class TimetableGenerator: + def __init__(self, sections, time_slots, subject_teacher_map, section_rooms, lab): + self.sections = sections + self.time_slots = time_slots + self.subject_teacher_map = subject_teacher_map + self.section_rooms = section_rooms + self.lab = lab + self.days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] # Ensure days are initialized + self.weekly_assignments = defaultdict(lambda: defaultdict(int)) + self.teacher_schedule = defaultdict(dict) + self.room_schedule = defaultdict(dict) + self.teacher_assignments = defaultdict(dict) + + def generate_day_schedule(self, day, half_day_sections, week_number): + day_schedule = {} + subject_teacher_usage = {subject: iter(teachers) for subject, teachers in self.subject_teacher_map.items()} + + for section in self.sections: + section_schedule = [] + subjects_used_today = set() + current_room = self.section_rooms[section] + num_slots = 4 if section in half_day_sections else 7 + + for index in range(1, num_slots + 1): + time_slot = self.time_slots[index] + + # Skip if time_slot is already used + if any(item["time_slot"] == time_slot for item in section_schedule): + continue + + available_subjects = list(self.subject_teacher_map.keys()) + subject, teacher = None, None + is_lab = False + + while available_subjects: + subject = random.choice(available_subjects) + + # Handle special rules for Placement_Class and labs + if subject == "Placement_Class" and index <= 4: # Placement classes are after lunch + available_subjects.remove(subject) + continue + + if "Lab" in subject: + if index + 1 > num_slots: # Ensure space for double slot + available_subjects.remove(subject) + continue + is_lab = True # Mark this as a lab + + if subject not in subjects_used_today: + teacher_iter = subject_teacher_usage[subject] + try: + teacher = next(teacher_iter) + self.teacher_assignments.setdefault(section, {})[subject] = teacher + except StopIteration: + teacher_iter = iter(self.subject_teacher_map[subject]) + teacher = next(teacher_iter) + subject_teacher_usage[subject] = teacher_iter + self.teacher_assignments.setdefault(section, {})[subject] = teacher + + break + + available_subjects.remove(subject) + + if subject is None or teacher is None: + subject, teacher = "Library", "None" + + if subject != "Library": + self.weekly_assignments[section][subject] += 1 + + subjects_used_today.add(subject) + + # Assign rooms based on whether it's a lab + if is_lab: + assigned_room = random.choice(self.lab) + else: + assigned_room = current_room + + self.teacher_schedule[time_slot][teacher] = section + self.room_schedule[time_slot][assigned_room] = section + + # Handle double slot allocation for labs + if is_lab: + next_slot_index = index + 1 + if next_slot_index <= num_slots: + next_time_slot = self.time_slots[next_slot_index] + + # Assign consecutive slot to the lab + section_schedule.append( + { + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": next_time_slot, + } + ) + self.room_schedule[next_time_slot][assigned_room] = section + self.teacher_schedule[next_time_slot][teacher] = section + index += 1 # Skip the next slot for lab + + section_schedule.append( + { + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": time_slot, + } + ) + + day_schedule[section] = section_schedule + return day_schedule + + def create_timetable(self, num_weeks=1): + timetable = {} + for week in range(1, num_weeks + 1): + week_schedule = {} + for week_day in self.days: + half_day_sections = random.sample(self.sections, len(self.sections) // 2) + week_schedule[week_day] = self.generate_day_schedule(week_day, half_day_sections, week) + timetable[f"Week {week}"] = week_schedule + return timetable + + +class TestTimetableGenerator(unittest.TestCase): + def setUp(self): + self.sections = ["A", "B", "C"] + self.time_slots = { + 1: "9:00-10:00", + 2: "10:00-11:00", + 3: "11:00-12:00", + 4: "12:00-1:00", + 5: "2:00-3:00", + 6: "3:00-4:00", + 7: "4:00-5:00" + } + self.subject_teacher_map = { + "Math": ["T1", "T2"], + "Physics": ["T3"], + "Chemistry Lab": ["T4"], + "Placement_Class": ["T5"], + } + self.section_rooms = {"A": "R1", "B": "R2", "C": "R3"} + self.lab = ["Lab1", "Lab2"] + self.tt = TimetableGenerator( + self.sections, self.time_slots, self.subject_teacher_map, self.section_rooms, self.lab + ) + + def test_create_timetable(self): + timetable = self.tt.create_timetable(num_weeks=2) + self.assertIsInstance(timetable, dict) + self.assertGreater(len(timetable), 0) + self.assertIn("Week 1", timetable) + self.assertIn("Week 2", timetable) + + # Verify timetable structure + for week, week_schedule in timetable.items(): + self.assertIsInstance(week_schedule, dict) + for day, day_schedule in week_schedule.items(): + self.assertIsInstance(day_schedule, dict) + for section, section_schedule in day_schedule.items(): + self.assertIsInstance(section_schedule, list) + for entry in section_schedule: + self.assertIn("teacher_id", entry) + self.assertIn("subject_id", entry) + self.assertIn("classroom_id", entry) + self.assertIn("time_slot", entry) + +if __name__ == "__main__": + unittest.main() + +#4. def calculate_fitness(self, chromosome): + +import unittest +from collections import defaultdict +import random + +class TimetableGenerator: + def __init__(self, sections, time_slots, subject_teacher_map, section_rooms, lab, section_strength, room_capacity, teacher_preferences, teacher_work_load): + self.sections = sections + self.time_slots = time_slots + self.subject_teacher_map = subject_teacher_map + self.section_rooms = section_rooms + self.lab = lab + self.section_strength = section_strength + self.room_capacity = room_capacity + self.teacher_preferences = teacher_preferences + self.teacher_work_load = teacher_work_load + self.days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + self.weekly_assignments = defaultdict(lambda: defaultdict(int)) + self.teacher_schedule = defaultdict(dict) + self.room_schedule = defaultdict(dict) + self.teacher_assignments = defaultdict(dict) + + def generate_day_schedule(self, day, half_day_sections, week_number): + day_schedule = {} + subject_teacher_usage = {subject: iter(teachers) for subject, teachers in self.subject_teacher_map.items()} + + for section in self.sections: + section_schedule = [] + subjects_used_today = set() + current_room = self.section_rooms[section] + num_slots = 4 if section in half_day_sections else 7 + + for index in range(1, num_slots + 1): + time_slot = self.time_slots[index] + + if any(item["time_slot"] == time_slot for item in section_schedule): + continue + + available_subjects = list(self.subject_teacher_map.keys()) + subject, teacher = None, None + is_lab = False + + while available_subjects: + subject = random.choice(available_subjects) + + if subject == "Placement_Class" and index <= 4: + available_subjects.remove(subject) + continue + + if "Lab" in subject: + if index + 1 > num_slots: + available_subjects.remove(subject) + continue + is_lab = True + + if subject not in subjects_used_today: + teacher_iter = subject_teacher_usage[subject] + try: + teacher = next(teacher_iter) + self.teacher_assignments.setdefault(section, {})[subject] = teacher + except StopIteration: + teacher_iter = iter(self.subject_teacher_map[subject]) + teacher = next(teacher_iter) + subject_teacher_usage[subject] = teacher_iter + self.teacher_assignments.setdefault(section, {})[subject] = teacher + + break + + available_subjects.remove(subject) + + if subject is None or teacher is None: + subject, teacher = "Library", "None" + + if subject != "Library": + self.weekly_assignments[section][subject] += 1 + + subjects_used_today.add(subject) + + if is_lab: + assigned_room = random.choice(self.lab) + else: + assigned_room = current_room + + self.teacher_schedule[time_slot][teacher] = section + self.room_schedule[time_slot][assigned_room] = section + + if is_lab: + next_slot_index = index + 1 + if next_slot_index <= num_slots: + next_time_slot = self.time_slots[next_slot_index] + section_schedule.append( + { + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": next_time_slot, + } + ) + self.room_schedule[next_time_slot][assigned_room] = section + self.teacher_schedule[next_time_slot][teacher] = section + index += 1 + + section_schedule.append( + { + "teacher_id": teacher, + "subject_id": subject, + "classroom_id": assigned_room, + "time_slot": time_slot, + } + ) + + day_schedule[section] = section_schedule + return day_schedule + + def calculate_fitness(self, chromosome): + week_fitness_scores = [] + + for week_num, (week, week_schedule) in enumerate(chromosome.items(), start=1): + overall_fitness_score = 0 + section_fitness_scores = {} + + for day, day_schedule in week_schedule.items(): + section_fitness_scores[day] = {} + + for section, section_schedule in day_schedule.items(): + section_score = 100 + teacher_time_slots = {} + classroom_time_slots = {} + teacher_load = {} + + for item in section_schedule: + teacher = item['teacher_id'] + classroom = item['classroom_id'] + time_slot = item['time_slot'] + strength = self.section_strength.get(section, 0) + + if "Break" in time_slot: + continue + + if (teacher, time_slot) in teacher_time_slots: + section_score -= 30 + else: + teacher_time_slots[(teacher, time_slot)] = section + + if (classroom, time_slot) in classroom_time_slots: + section_score -= 20 + else: + classroom_time_slots[(classroom, time_slot)] = section + + if teacher not in teacher_load: + teacher_load[teacher] = [] + teacher_load[teacher].append(time_slot) + + if strength > self.room_capacity.get(classroom, 0): + section_score -= 25 + + preferred_slots = self.teacher_preferences.get(teacher, []) + if time_slot not in preferred_slots: + section_score -= 5 + + for teacher, time_slots in teacher_load.items(): + if len(time_slots) > self.teacher_work_load.get(teacher, 5): + section_score -= 10 + + section_fitness_scores[day][section] = section_score + overall_fitness_score += section_score + + week_fitness_scores.append({ + "week": f"Week {week_num}", + "score": overall_fitness_score + }) + + return week_fitness_scores, section_fitness_scores + + +class TestTimetableGenerator(unittest.TestCase): + def setUp(self): + self.sections = ["A", "B", "C"] + self.time_slots = { + 1: "9:00-10:00", + 2: "10:00-11:00", + 3: "11:00-12:00", + 4: "12:00-1:00", + 5: "2:00-3:00", + 6: "3:00-4:00", + 7: "4:00-5:00" + } + self.subject_teacher_map = { + "Math": ["T1", "T2"], + "Physics": ["T3"], + "Chemistry Lab": ["T4"], + "Placement_Class": ["T5"], + } + self.section_rooms = {"A": "R1", "B": "R2", "C": "R3"} + self.lab = ["Lab1", "Lab2"] + self.section_strength = {"A": 40, "B": 35, "C": 50} + self.room_capacity = {"R1": 40, "R2": 35, "R3": 50} + self.teacher_preferences = {"T1": ["9:00-10:00", "3:00-4:00"], "T2": ["10:00-11:00"]} + self.teacher_work_load = {"T1": 10, "T2": 15, "T3": 8, "T4": 12, "T5": 6} + + self.tt = TimetableGenerator( + self.sections, self.time_slots, self.subject_teacher_map, self.section_rooms, + self.lab, self.section_strength, self.room_capacity, self.teacher_preferences, self.teacher_work_load + ) + + def test_calculate_fitness(self): + # Create a mock chromosome with timetable data + chromosome = { + "Week 1": { + "Monday": { + "A": [{"teacher_id": "T1", "subject_id": "Math", "classroom_id": "R1", "time_slot": "9:00-10:00"}], + "B": [{"teacher_id": "T3", "subject_id": "Physics", "classroom_id": "R2", "time_slot": "10:00-11:00"}], + }, + "Tuesday": { + "C": [{"teacher_id": "T4", "subject_id": "Chemistry Lab", "classroom_id": "Lab1", "time_slot": "11:00-12:00"}], + } + } + } + + week_fitness_scores, section_fitness_scores = self.tt.calculate_fitness(chromosome) + + # Test the overall fitness score for Week 1 + self.assertIsInstance(week_fitness_scores, list) + self.assertGreater(len(week_fitness_scores), 0) + self.assertEqual(week_fitness_scores[0]["week"], "Week 1") + self.assertIn("score", week_fitness_scores[0]) + + # Test the section fitness score for a specific day and section + self.assertIn("Monday", section_fitness_scores) + self.assertIn("A", section_fitness_scores["Monday"]) + self.assertIsInstance(section_fitness_scores["Monday"]["A"], int) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/test_suite/Mutation_Test.py b/test_suite/Mutation_Test.py new file mode 100644 index 0000000..8e0e1aa --- /dev/null +++ b/test_suite/Mutation_Test.py @@ -0,0 +1,366 @@ +#MUTATION TESTS + +#1. def mutate_time_slots_in_section(self, schedule, section): + +import unittest +import random + +class TestMutateTimeSlots(unittest.TestCase): + def setUp(self): + # Initialize a mock schedule for testing + self.schedule = { + "SectionA": [ + {"subject": "Math", "time_slot": "9:00-10:00"}, + {"subject": "English", "time_slot": "10:00-11:00"}, + {"subject": "Science", "time_slot": "11:00-12:00"} + ], + "SectionB": [ + {"subject": "History", "time_slot": "9:00-10:00"}, + ], + "SectionC": [] # Empty section + } + + def mutate_time_slots_in_section(self, schedule, section): + """ + Mutates the time slots within a particular section by shuffling them. + + Args: + schedule (dict): The schedule containing sections and their data. + section (str): The section in which mutation should occur. + + Returns: + bool: True if mutation was performed, False otherwise. + """ + if section in schedule: + section_list = schedule[section] + + if len(section_list) < 2: + return False + + time_slots = [entry["time_slot"] for entry in section_list] + random.shuffle(time_slots) + + for i, entry in enumerate(section_list): + entry["time_slot"] = time_slots[i] + + return True + return False + + def test_valid_mutation(self): + # Test a valid mutation for SectionA + original_schedule = [entry["time_slot"] for entry in self.schedule["SectionA"]] + result = self.mutate_time_slots_in_section(self.schedule, "SectionA") + self.assertTrue(result, "Mutation should occur with valid input") + mutated_schedule = [entry["time_slot"] for entry in self.schedule["SectionA"]] + + # Check if time slots were shuffled + self.assertNotEqual(original_schedule, mutated_schedule, "Schedules should differ after mutation") + self.assertCountEqual(original_schedule, mutated_schedule, "Schedules should have the same elements after mutation") + + def test_not_enough_time_slots(self): + # Test mutation for SectionB (not enough time slots to shuffle) + result = self.mutate_time_slots_in_section(self.schedule, "SectionB") + self.assertFalse(result, "Mutation should not occur for single-element sections") + + def test_empty_section(self): + # Test mutation for SectionC (empty section) + result = self.mutate_time_slots_in_section(self.schedule, "SectionC") + self.assertFalse(result, "Mutation should not occur for empty sections") + + def test_non_existent_section(self): + # Test mutation for a section not in the schedule + result = self.mutate_time_slots_in_section(self.schedule, "SectionD") + self.assertFalse(result, "Mutation should not occur for non-existent sections") + +if __name__ == "__main__": + unittest.main(verbosity=2) + + + +#2. def mutate_schedule_for_week(self, weekly_schedule): + +import unittest +import random +import copy +from unittest.mock import patch + + +class Mutator: + def __init__(self, mutation_rate=0.5): + self.mutation_rate = mutation_rate + + def mutate_time_slots_in_section(self, schedule, section): + """ + Mutates the time slots within a particular section by shuffling them. + + Args: + schedule (dict): The schedule containing sections and their data. + section (str): The section in which mutation should occur. + + Returns: + bool: True if mutation was performed, False otherwise. + """ + if section in schedule: + section_list = schedule[section] + if len(section_list) < 2: + return False # Not enough time slots to shuffle. + + original_slots = [entry["time_slot"] for entry in section_list] + mutated_slots = original_slots[:] + attempt_count = 0 + + while mutated_slots == original_slots and attempt_count < 5: # Ensure mutation occurs + random.shuffle(mutated_slots) + attempt_count += 1 + + if mutated_slots != original_slots: + for i, entry in enumerate(section_list): + entry["time_slot"] = mutated_slots[i] + return True + + return False + + def mutate_schedule_for_week(self, weekly_schedule): + """ + Mutates the time slots for all weekdays in the weekly schedule. + + Args: + weekly_schedule (dict): The weekly schedule containing days, sections, and data. + + Returns: + dict: The mutated weekly schedule. + """ + mutated_weekly_schedule = copy.deepcopy(weekly_schedule) + has_mutation = False # Track if any mutation occurs + + for day, day_schedule in mutated_weekly_schedule.items(): + total_sections = list(day_schedule.keys()) + num_to_mutate = max(1, int(self.mutation_rate * len(total_sections))) + + sections_to_mutate = random.sample(total_sections, num_to_mutate) + + for section in sections_to_mutate: + if self.mutate_time_slots_in_section(day_schedule, section): + has_mutation = True + + if not has_mutation: # Ensure at least one mutation occurs + random_section = random.choice(list(mutated_weekly_schedule.values())) + random_section_name = random.choice(list(random_section.keys())) + self.mutate_time_slots_in_section(random_section, random_section_name) + + return mutated_weekly_schedule + + def print_weekly_schedule(self, weekly_schedule): + """ + Prints the weekly schedule in a readable format. + + Args: + weekly_schedule (dict): The weekly schedule to print. + """ + print("\nWeekly Schedule:") + print("=" * 50) + for day, day_schedule in weekly_schedule.items(): + print(f"Day: {day}") + print("-" * 30) + for section, entries in day_schedule.items(): + print(f" Section {section}:") + for entry in entries: + print( + f" Teacher: {entry['teacher_id']}, Subject: {entry['subject_id']}, " + f"Time Slot: {entry['time_slot']}" + ) + print("=" * 50) + + +class TestMutation(unittest.TestCase): + def setUp(self): + self.weekly_schedule = { + "Monday": { + "A": [ + {"teacher_id": "T1", "subject_id": "Math", "time_slot": "9:00 - 9:55"}, + {"teacher_id": "T2", "subject_id": "Physics", "time_slot": "9:55 - 10:50"}, + ], + "B": [ + {"teacher_id": "T3", "subject_id": "Chemistry", "time_slot": "11:10 - 12:05"}, + {"teacher_id": "T4", "subject_id": "Biology", "time_slot": "12:05 - 1:00"}, + ], + }, + "Tuesday": { + "A": [ + {"teacher_id": "T5", "subject_id": "History", "time_slot": "1:20 - 2:15"}, + ] + }, + } + self.mutator = Mutator() + + def test_default_mutation_rate(self): + self.assertEqual(self.mutator.mutation_rate, 0.5) + + def test_mutate_time_slots_in_section(self): + section_schedule = [ + {"teacher_id": "T1", "subject_id": "Math", "time_slot": "9:00 - 9:55"}, + {"teacher_id": "T2", "subject_id": "Physics", "time_slot": "9:55 - 10:50"}, + ] + original_schedule = copy.deepcopy(section_schedule) + result = self.mutator.mutate_time_slots_in_section({"A": section_schedule}, "A") + self.assertTrue(result) + mutated_schedule = section_schedule + self.assertNotEqual( + [entry["time_slot"] for entry in original_schedule], + [entry["time_slot"] for entry in mutated_schedule], + "Time slots were not mutated", + ) + + @patch("random.shuffle") + def test_mutate_schedule_for_week(self, mock_shuffle): + # Mock shuffle to ensure mutation logic works. + def mock_shuffle_fn(lst): + lst.reverse() + + mock_shuffle.side_effect = mock_shuffle_fn + + mutated_schedule = self.mutator.mutate_schedule_for_week(self.weekly_schedule) + self.assertIsInstance(mutated_schedule, dict) + + # Ensure the time slots are not identical + original_time_slots = [ + entry["time_slot"] + for day in self.weekly_schedule.values() + for section in day.values() + for entry in section + ] + mutated_time_slots = [ + entry["time_slot"] + for day in mutated_schedule.values() + for section in day.values() + for entry in section + ] + self.assertNotEqual(original_time_slots, mutated_time_slots, "Mutation did not occur.") + + def test_print_weekly_schedule(self): + # This is primarily for manual verification; ensure no exceptions occur + self.mutator.print_weekly_schedule(self.weekly_schedule) + + +if __name__ == "__main__": + unittest.main() + + +#3. def print_weekly_schedule(self, weekly_schedule): + +import unittest +from unittest.mock import patch +import random +import copy +from Samples.Mutation_Files.mutation import Mutation + +class TestMutation(unittest.TestCase): + + def setUp(self): + """Setup a basic schedule for testing.""" + self.schedule = { + "Monday": { + "A": [ + {"teacher_id": "AD08", "subject_id": "PCS-506", "classroom_id": "L5", "time_slot": "9:55 - 10:50"}, + {"teacher_id": "AD08", "subject_id": "PCS-506", "classroom_id": "L5", "time_slot": "9:00 - 9:55"} + ] + }, + "Tuesday": { + "A": [{"teacher_id": "AK22", "subject_id": "CSP-501", "classroom_id": "R1", "time_slot": "9:00 - 9:55"}], + "B": [{"teacher_id": "BJ16", "subject_id": "TMA-502", "classroom_id": "R2", "time_slot": "9:55 - 10:50"}] + } + } + + def test_mutate_schedule_for_week(self): + """ + Test mutation of the entire week's schedule. + """ + mutation_rate = 0.5 # For testing, we can use a fixed mutation rate (50%) + mutator = Mutation(mutation_rate=mutation_rate) + + # Make a deep copy of the original schedule to compare later + original_schedule = copy.deepcopy(self.schedule) + + # Perform mutation + mutated_schedule = mutator.mutate_schedule_for_week(self.schedule) + + # Test if the mutated schedule is different from the original schedule + self.assertNotEqual(self.schedule, mutated_schedule, "The mutated schedule should be different from the original schedule.") + + # Test that at least one section was mutated + for day in mutated_schedule: + for section in mutated_schedule[day]: + # Check if time slots were shuffled (i.e., they're different from the original time slots) + for original_entry, mutated_entry in zip(original_schedule[day][section], mutated_schedule[day][section]): + self.assertNotEqual(original_entry['time_slot'], mutated_entry['time_slot'], + f"Time slot for {section} on {day} should be mutated.") + + def test_mutate_schedule_with_no_mutation(self): + """ + Test if mutation does not occur when the mutation rate is 0. + """ + mutation_rate = 0 # No mutation expected + mutator = Mutation(mutation_rate=mutation_rate) + + # Make a deep copy of the original schedule to compare later + original_schedule = copy.deepcopy(self.schedule) + + # Perform mutation + mutated_schedule = mutator.mutate_schedule_for_week(self.schedule) + + # Test that the mutated schedule is the same as the original schedule + self.assertEqual(self.schedule, mutated_schedule, "The schedule should remain the same if mutation rate is 0.") + + def test_mutate_schedule_with_max_mutation(self): + """ + Test if mutation affects all sections when mutation rate is 1. + """ + mutation_rate = 1 # Full mutation expected + mutator = Mutation(mutation_rate=mutation_rate) + + # Make a deep copy of the original schedule to compare later + original_schedule = copy.deepcopy(self.schedule) + + # Perform mutation + mutated_schedule = mutator.mutate_schedule_for_week(self.schedule) + + # Test that the mutated schedule is different from the original schedule + self.assertNotEqual(self.schedule, mutated_schedule, "The mutated schedule should be different from the original schedule.") + + # Check that all time slots were mutated + for day in mutated_schedule: + for section in mutated_schedule[day]: + for original_entry, mutated_entry in zip(original_schedule[day][section], mutated_schedule[day][section]): + self.assertNotEqual(original_entry['time_slot'], mutated_entry['time_slot'], + f"Time slot for {section} on {day} should be mutated.") + + @patch("random.sample") # Mock random.sample to control which sections get mutated + def test_mutate_schedule_with_controlled_mutation(self, mock_sample): + """ + Test mutation when we control which sections are selected for mutation. + """ + mutation_rate = 0.5 + mutator = Mutation(mutation_rate=mutation_rate) + + # Mock random.sample to return a controlled selection of sections to mutate + mock_sample.return_value = ["A"] # Only mutate section A + + # Make a deep copy of the original schedule to compare later + original_schedule = copy.deepcopy(self.schedule) + + # Perform mutation + mutated_schedule = mutator.mutate_schedule_for_week(self.schedule) + + # Test that only section A was mutated, and others remain the same + for day in mutated_schedule: + for section in mutated_schedule[day]: + for original_entry, mutated_entry in zip(original_schedule[day][section], mutated_schedule[day][section]): + if section == "A": + self.assertNotEqual(original_entry['time_slot'], mutated_entry['time_slot'], + f"Time slot for {section} on {day} should be mutated.") + else: + self.assertEqual(original_entry['time_slot'], mutated_entry['time_slot'], + f"Time slot for {section} on {day} should not be mutated.") + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/test_suite/ReadMe.md b/test_suite/ReadMe.md new file mode 100644 index 0000000..4759136 --- /dev/null +++ b/test_suite/ReadMe.md @@ -0,0 +1,7 @@ +This folder will contain the test for all the +different flows we have. + +Along with, + +1. unit test -> these will test individual components, like functionalities of Fitness, Crossover, Mutation, etc. +2. functional test -> collectively working of these modules. \ No newline at end of file diff --git a/test_suite/Selection_Tests.py b/test_suite/Selection_Tests.py new file mode 100644 index 0000000..f16f42f --- /dev/null +++ b/test_suite/Selection_Tests.py @@ -0,0 +1,298 @@ +# SELECTION TESTS + +#1.def get_all_time_slots(): + +import unittest +from Constants.time_intervals import TimeIntervalConstant +class TestTimeIntervalConstant(unittest.TestCase): + + def test_get_all_time_slots(self): + expected_time_slots = ["9:00 - 9:55", "9:55 - 10:50", "11:10 - 12:05", "12:05 - 1:00", "1:20 - 2:15", "2:15 - 3:10", "3:30 - 4:25"] + self.assertEqual(TimeIntervalConstant.get_all_time_slots(), expected_time_slots) + +if __name__ == "__main__": + unittest.main() + +import unittest +from Samples.Selection_Files.selection import Timetable # Correct import +from unittest import mock + +class TestTimetable(unittest.TestCase): + + def setUp(self): + # Initialize the object to be tested + self.timetable = Timetable() # Initialize the correct class + + def test_generate_day_schedule_structure(self): + # Call the method + day_schedule = self.timetable.generate_day_schedule() + + # Check if the returned day_schedule is a dictionary with sections + self.assertIsInstance(day_schedule, dict) + self.assertGreater(len(day_schedule), 0) + + for section, schedule in day_schedule.items(): + self.assertIn(section, self.timetable.sections) # Ensure sections are correct + self.assertIsInstance(schedule, list) + self.assertGreater(len(schedule), 0) + + def test_teacher_and_classroom_usage(self): + # Call the method + day_schedule = self.timetable.generate_day_schedule() + + # Check that no teacher is assigned to the same time slot more than once + time_slot_teacher_usage = {time_slot: set() for time_slot in self.timetable.time_slots} + time_slot_classroom_usage = {time_slot: set() for time_slot in self.timetable.time_slots} + + for section, schedule in day_schedule.items(): + for schedule_item in schedule: + if "Break" not in schedule_item["time_slot"]: + self.assertNotIn(schedule_item["teacher_id"], time_slot_teacher_usage[schedule_item["time_slot"]]) + self.assertNotIn(schedule_item["classroom_id"], time_slot_classroom_usage[schedule_item["time_slot"]]) + + # Track teacher and classroom usage + time_slot_teacher_usage[schedule_item["time_slot"]].add(schedule_item["teacher_id"]) + time_slot_classroom_usage[schedule_item["time_slot"]].add(schedule_item["classroom_id"]) + + def test_break_periods(self): + # Call the method + day_schedule = self.timetable.generate_day_schedule() + + # Check that "Break" is assigned correctly + break_found = False + for section, schedule in day_schedule.items(): + for schedule_item in schedule: + if "Break" in schedule_item["subject_id"]: + break_found = True + self.assertEqual(schedule_item["teacher_id"], "None") + self.assertEqual(schedule_item["classroom_id"], "N/A") + + self.assertTrue(break_found, "No break periods found in the schedule") + +if __name__ == "__main__": + unittest.main() + + +#2. def create_timetable(self): + +import unittest +from Samples.Selection_Files.selection import Timetable +from unittest import mock + +class TestTimetable(unittest.TestCase): + + def setUp(self): + # Initialize the Timetable object + self.timetable = Timetable() + + def test_create_timetable_structure(self): + # Call the method + timetable = self.timetable.create_timetable() + + # Check if the timetable is a dictionary with days as keys + self.assertIsInstance(timetable, dict) + self.assertGreater(len(timetable), 0) # Ensure timetable is not empty + + # Verify that all days are present + for day in self.timetable.days: + self.assertIn(day, timetable) + + # Verify that each day contains a schedule (which is a dictionary) + for day, day_schedule in timetable.items(): + self.assertIsInstance(day_schedule, dict) + self.assertGreater(len(day_schedule), 0) + + def test_generate_day_schedule_called(self): + # Mock the generate_day_schedule method to check if it's called for each day + with mock.patch.object(self.timetable, 'generate_day_schedule', return_value={}) as mock_method: + self.timetable.create_timetable() + + # Check if generate_day_schedule was called for each day + self.assertEqual(mock_method.call_count, len(self.timetable.days)) + + def test_timetable_content(self): + # Call the method + timetable = self.timetable.create_timetable() + + # Check that each day's schedule contains sections + for day, day_schedule in timetable.items(): + for section, schedule in day_schedule.items(): + self.assertIn(section, self.timetable.sections) # Ensure valid sections + self.assertIsInstance(schedule, list) + self.assertGreater(len(schedule), 0) + +if __name__ == "__main__": + unittest.main() + +#3. def create_multiple_timelines(self, num_chromosomes): + +import unittest +from Samples.Selection_Files.selection import Timetable # Adjust the import as necessary + +class TestTimetable(unittest.TestCase): + + def setUp(self): + # Initialize the object to be tested + self.timetable = Timetable() # Ensure this is the correct class that has create_multiple_timelines method + + def test_create_multiple_timelines(self): + num_chromosomes = 5 # You can change this number to test with different sizes + + # Call the method + timelines = self.timetable.create_multiple_timelines(num_chromosomes) + + # Check if the returned value is a list + self.assertIsInstance(timelines, list) + + # Check if the list has the correct number of timetables + self.assertEqual(len(timelines), num_chromosomes) + + # Optionally, check if each element in the list is a timetable (or has the correct structure) + for timetable in timelines: + self.assertIsInstance(timetable, dict) # Assuming the timetable is a dictionary as in your previous methods + # Check that the timetable contains valid data, like sections + for section, schedule in timetable.items(): + self.assertIn(section, self.timetable.sections) + self.assertIsInstance(schedule, list) + self.assertGreater(len(schedule), 0) + +if __name__ == "__main__": + unittest.main() + +#4. def calculate_fitness(self, chromosome): + +import unittest +from Samples.Selection_Files.selection import Timetable # Adjust the import path + +class TestTimetableFitness(unittest.TestCase): + + def setUp(self): + # Initialize the object to be tested + self.timetable = Timetable() # Ensure this is the class that has calculate_fitness method + + def test_fitness_calculation_valid_chromosome(self): + # Create a valid chromosome structure + chromosome = { + "Monday": { + "A": [ + {"teacher_id": "T1", "classroom_id": "R1", "time_slot": "9:00-10:00", "subject_id": "S1"}, + {"teacher_id": "T2", "classroom_id": "R2", "time_slot": "10:00-11:00", "subject_id": "S2"}, + {"teacher_id": "T3", "classroom_id": "R3", "time_slot": "11:00-12:00", "subject_id": "S3"}, + {"teacher_id": "None", "classroom_id": "N/A", "time_slot": "Break", "subject_id": "Break"}, + ], + "B": [ + {"teacher_id": "T4", "classroom_id": "R1", "time_slot": "9:00-10:00", "subject_id": "S4"}, + {"teacher_id": "T5", "classroom_id": "R2", "time_slot": "10:00-11:00", "subject_id": "S5"}, + {"teacher_id": "T1", "classroom_id": "R3", "time_slot": "11:00-12:00", "subject_id": "S6"}, + {"teacher_id": "None", "classroom_id": "N/A", "time_slot": "Break", "subject_id": "Break"}, + ], + } + } + + # Calculate fitness + fitness_score = self.timetable.calculate_fitness(chromosome) + + # Assert fitness score is within expected range + self.assertIsInstance(fitness_score, int) + self.assertGreaterEqual(fitness_score, 0) # Fitness should not be negative + + def test_fitness_calculation_with_violations(self): + # Create a chromosome with violations (e.g., overlapping time slots) + chromosome = { + "Monday": { + "A": [ + {"teacher_id": "T1", "classroom_id": "R1", "time_slot": "9:00-10:00", "subject_id": "S1"}, + {"teacher_id": "T1", "classroom_id": "R2", "time_slot": "9:00-10:00", "subject_id": "S2"}, # Teacher overlap + {"teacher_id": "T3", "classroom_id": "R3", "time_slot": "11:00-12:00", "subject_id": "S3"}, + ], + "B": [ + {"teacher_id": "T4", "classroom_id": "R1", "time_slot": "9:00-10:00", "subject_id": "S4"}, + {"teacher_id": "T5", "classroom_id": "R1", "time_slot": "9:00-10:00", "subject_id": "S5"}, # Room overlap + ], + } + } + + # Calculate fitness + fitness_score = self.timetable.calculate_fitness(chromosome) + + # Assert fitness score accounts for penalties + self.assertIsInstance(fitness_score, int) + self.assertLess(fitness_score, 100) # Expect reduced fitness score due to violations + + def test_fitness_calculation_with_over_capacity(self): + # Create a chromosome where room capacity is exceeded + chromosome = { + "Monday": { + "A": [ + {"teacher_id": "T1", "classroom_id": "R3", "time_slot": "9:00-10:00", "subject_id": "S1"}, # Over capacity + ] + } + } + + # Calculate fitness + fitness_score = self.timetable.calculate_fitness(chromosome) + + # Assert fitness score is reduced due to capacity violation + self.assertIsInstance(fitness_score, int) + self.assertLess(fitness_score, 100) # Expect reduced fitness score due to capacity penalty + + def test_fitness_calculation_no_timetable(self): + # Test with an empty chromosome + chromosome = {} + + # Calculate fitness + fitness_score = self.timetable.calculate_fitness(chromosome) + + # Assert fitness score is zero + self.assertEqual(fitness_score, 0) + +if __name__ == "__main__": + unittest.main() + +#5. def select_top_chromosomes(self, population: list, percentage=0.30) -> list: + +import random +def select_top_chromosomes(self, population: list, percentage=0.30) -> list: + num_to_select = int(len(population) * percentage) + fitness_scores = [(chromosome, self.calculate_fitness(chromosome)) for chromosome in population] + sorted_chromosomes = sorted(fitness_scores, key=lambda x: x[1], reverse=True) + + best_num = int(num_to_select * 0.20) + worst_num = int(num_to_select * 0.10) + middle_num = num_to_select - (best_num + worst_num) + + best_chromosomes = sorted_chromosomes[:best_num] + worst_chromosomes = sorted_chromosomes[-worst_num:] + middle_chromosomes = sorted_chromosomes[best_num:best_num + middle_num] + + roulette_num = int(middle_num * 0.70) + rank_num = middle_num - roulette_num + + # Handle empty middle_chromosomes for roulette selection + if middle_chromosomes: + total_fitness = sum(fitness for _, fitness in middle_chromosomes) + roulette_chromosomes = random.choices( + middle_chromosomes, + weights=[fitness / total_fitness for _, fitness in middle_chromosomes], + k=roulette_num + ) + else: + roulette_chromosomes = [] + + if middle_chromosomes: + total_rank = sum(range(1, len(middle_chromosomes) + 1)) + rank_probabilities = [i / total_rank for i in range(1, len(middle_chromosomes) + 1)] + rank_chromosomes = random.choices( + middle_chromosomes, + weights=rank_probabilities, + k=rank_num + ) + else: + rank_chromosomes = [] + + selected_chromosomes = [chromosome for chromosome, _ in best_chromosomes] + selected_chromosomes += [chromosome for chromosome, _ in worst_chromosomes] + selected_chromosomes += [chromosome for chromosome, _ in roulette_chromosomes] + selected_chromosomes += [chromosome for chromosome, _ in rank_chromosomes] + + return selected_chromosomes \ No newline at end of file diff --git a/test_suite/unit_test.py b/test_suite/unit_test.py new file mode 100644 index 0000000..e69de29