This is testing the get_num_rooms() function in configuration.py It checks to see if the correct number of rooms is returned Input: config file (3 rooms) Expected output: 3 """ num = config.get_num_rooms() print num """ This is testing the get_room_by_id() function in configuration.py It checks to see if the correct room name, seat number, and lab is returned Input: config file (ID: 2) Expected output: Unknown """ room = config.get_room_by_id(2) print str(room.name) + " " + str(room.seat_num) + " " + str(room.lab) """ This is testing the get_num_classes() function in configuration.py It checks to see if the correct number of classes is returned Input: config file(26 classes) Expected output: 26 """ num = config.get_num_classes() print num """ This is testing the get_class_by_course() function
class Schedule: """ This is the schedule class, used to schedule the classes. The schedule is a list of lists of chromosomes The length of the list is the number of rooms*number of days*number of hours Each chromosome has a fitness, which is determined like this: If the class uses a spare room, fitness++ If the room has enough seats for the class, fitness++ If the class needs a lab and the room has a lab, fitness++ If the professor teaching the class does not have an overlapping class \ ,fitness++ The algorithm is run, until the total fitness is = 1.0 Total fitness = combined fitness/number of rooms * 4(best fitness) Crossovers are where two schedules are combined at two random points, taking the best chromosomes from either of the schedules Mutations are where a chromosome is selected at random and moved to a random location Algorithm Logic: read in data initialize chromosome get_overall_fitness while overall fitness != 1.0 get crossover points perform crossover get mutation size perform mutations get overall fitness return schedule """ def __init__(self,len_chromo_list = None, config = None): """ Initalizes the schedule @param len_chromo_list: length of the new chromosome list @param config: config object to base the schedule on """ if len_chromo_list is None: self.chromo_list = [] else: self.chromo_list = len_chromo_list*[None] if config is None: self.config = None else: self.config = config #Holds the last location of every class in the list self.hash_map = {} #Holds the classes with fitness values of 4 self.best_of = {} self.number_chromosomes = 0 #Number of chromosomes to swap self.mutation_size = 0 #Overall fitness of the schedule self.total_fitness = 0 #Hours in the day classes can be held self.day_length = 12 #Days of the week classes can be held self.num_days = 5 #Best fitness achievable. Needs to reflect calculate_fitness() self.best_fitness = 4 def get_config(self): """ Loads the configuration of the classes, courses, rooms and professors """ if self.config is None: self.config = Configuration() #Hard coded the file for now, will change with Django interface self.config.parse_file('config') def init_chromosomes(self): """ Initalizes the schedule with random class placement """ #Checks to see chromo_list has a size if not self.chromo_list: if self.config is None: print "You need to load a config,\ before initalizing chromosomes" else: #Initializes Empty List self.chromo_list = ((self.day_length*self.num_days)*\ (self.config.get_num_rooms()))*[None] #Seeds the random with system time random.seed() for classes in self.config.classes_list: #Gets random spot for class rand = random.randint(0,len(self.chromo_list)) #Used to place classes with long durations total_duration = 0 temp_index = rand if not self.hash_map.has_key(classes): self.hash_map[classes] = temp_index #Places class in schedule while total_duration < classes.duration\ and temp_index < len(self.chromo_list): new_chromo = self.insert_chromosome(Chromosome(),\ temp_index) self.number_chromosomes += 1 #Checks to see if the class is already in the hashmap, #if not Class object is added with value being location #in list Only adds when the class starts #Assigns the class to the new chromosome new_chromo._class = classes self.calculate_fitness(new_chromo,temp_index) total_duration += 1 temp_index += 1 def calculate_fitness(self,chromo,index): """ Calculates the fitness of a chromosome @param chromo: chromosome to calculate fitness for @param index: index in self.chromo_list of the chromosome """ #Incase chromosome has been scheduled before chromo.fitness = 0 hold_index = index #Figure out which room you are in data_tuple = self.get_room_day_numbers(hold_index) room_id = data_tuple[1] #Get Room object room = self.config.get_room_by_id(room_id) course = chromo._class #Course might not overlap at current position, but could if duration is #longer than 1, this checks for that if not chromo.overlap: if course.duration > 1: count = 0 while count < (course.duration): index += 1 if index < len(self.chromo_list): check_list = self.chromo_list[index] if check_list: chromo.overlap = True count += 1 #Class does not overlap EVER if not chromo.overlap: chromo.fitness += 1 #Room is able to fit the class if course.get_room_size() <= room.get_seat_num(): chromo.fitness += 1 #Course needs lab and room has lab if course.needs_lab(): if room.lab_status(): chromo.fitness += 1 #Course doesnt need lab and room doesnt have lab if not course.needs_lab(): if not room.lab_status(): chromo.fitness += 1 #Only way a Professor will have an overlapping class is if the #class overlaps with another class if chromo.overlap: prof_overlap = False index = hold_index count = 0 while count < (course.duration): index += count check_list = self.chromo_list[index] for courses in check_list: prof_overlap = course.professor_overlap(courses._class) if prof_overlap: break if prof_overlap: break count += 1 if not prof_overlap: chromo.fitness += 1 else: chromo.fitness += 1 if chromo.fitness is self.best_fitness: self.best_of[chromo._class] = self.best_fitness def get_overall_fitness(self): """ Calculates the overall fitness of the schedule """ total_fitness = 0 print "In overall fitness!!!" for chromosome_list in self.chromo_list: if chromosome_list: for chromosomes in chromosome_list: total_fitness += chromosomes.fitness return float(total_fitness/(self.number_chromosomes*\ float(self.best_fitness))) def perform_crossover(self,start_index,end_index): """ Performs a crossover: Crossovers are where two schedules are combined at two random points,taking the best chromosomes from either of the schedules @param start_index: index of the hashmap to start the crossover @param end_index: index of the hashmap to end the crossover """ #Generates a Random Schedule based off of the current Schedule random_schedule = self.randomize_schedule() #Creats a list of hash values [(Class,index)] random_hash_list = random_schedule.hash_map.items() check_position = False while start_index < end_index: #Get the touple to get class and index swap_tuple = random_hash_list[start_index] #Gets the class object swap_class = swap_tuple[0] #Checks to see if it has best_fitness if self.best_of.has_key(swap_class): start_index += 1 #Does not have best_fitness else: #Get the old position of the class old_position = self.hash_map[swap_class] total_duration = 0 #Removes the class from its old position while total_duration < swap_class.duration and \ old_position < len(self.chromo_list): #Gets the chromosome to remove old_chromo = self.get_chromosome_from_list(swap_class,\ old_position) self.remove_chromosome(old_chromo,old_position) total_duration += 1 old_position += 1 #Getst the new Position for the Class new_position = swap_tuple[1] #Checks to see if the new position can be used while not check_position: check_position = self.check_position(swap_class,\ new_position) if not check_position: new_position += 1 #Updates the Hash Map with the new position self.hash_map[swap_class] = new_position #Inserts the class in the new position total_duration = 0 while total_duration < swap_class.duration and\ new_position < len(self.chromo_list): #Inserts the Chromosome into the new position new_chromo = self.insert_chromosome(Chromosome(),\ new_position) #Assigns the chromosome the class being moved new_chromo._class = swap_class self.calculate_fitness(new_chromo, new_position) total_duration += 1 new_position += 1 start_index += 1 def check_position(self,check_class,position): """ Checks to see if the class can have a valid insert """ duration = check_class.get_duration() + position day_number = self.get_room_day_numbers(position) old_day = day_number[0] old_room = day_number[1] while position <= duration: day_number = self.get_room_day_numbers(position) new_day = day_number[0] new_room = day_number[1] if new_day == old_day and new_room == old_room: position += 1 else: return False return True def perform_mutations(self): """ Performs mutations: Mutations are where a chromosome is selected at random and moved to a random location Based on the mutation size """ count = 0 check_position = False while count < self.mutation_size: #Get random class to mutate random_class_index = random.randint(0,\ len(self.config.classes_list)-1) random_class = self.config.classes_list[random_class_index] #Checks to see if the class has best fitness if self.best_of.has_key(random_class): count += 1 else: #Gets the new position for the random class new_position = random.randint(0,len(self.chromo_list)) #Gets the old position for the random class old_position = self.hash_map[random_class] cur_chromosome = self.get_chromosome_from_list(random_class\ ,old_position) if new_position in cur_chromosome.position: while new_position not in cur_chromosome.position: new_position = random.randint(0,len(self.chromo_list)) #Checks to see if the new position can be used while not check_position: check_position = self.check_position(random_class,\ new_position) if not check_position: new_position += 1 #Updates the hash map with new position self.hash_map[random_class] = new_position cur_chromosome.position.append(new_position) #Removes the class from the old position total_duration = 0 while total_duration < random_class.duration and\ old_position < len(self.chromo_list): #Gets the chromosome to be removed removal_chromosome = self.get_chromosome_from_list(\ random_class,old_position) self.remove_chromosome(removal_chromosome,old_position) total_duration += 1 old_position += 1 #Inserts the class into the new position total_duration = 0 while total_duration < random_class.duration and\ new_position < len(self.chromo_list): #Inserts the chromosome at the new_position new_chromo = self.insert_chromosome(Chromosome(),new_position) #Assigns the random class to the chromosome new_chromo._class = random_class self.calculate_fitness(new_chromo, new_position) total_duration += 1 new_position += 1 count += 1 def insert_chromosome(self, chromosome, index): """ Inserts a chromosome into chromo_list at the given index Checks to see if there is over lap or not @param chromosome: chromosome to be inserted into the list @param index: index where to insert the chromosome """ if chromosome is None: #No class is schedule in that time slot if self.chromo_list[index] is None: #Create new list with empty chromosome object new_list = [Chromsome()] #Assigns new_chromo the empty chromosome object new_chromo = new_list[0] #Inserts the new list into the master chromosome list self.chromo_list.insert(index,new_list) #Class is already scheduled in the time slot else: #Get the existing list exist_list = self.chromo_list[index] #Append empty chromosome object to end of list exist_list.append(Chromosome()) #Assigns new_chromo the empty chromosome object new_chromo = exist_list[-1] #Sets overlap to be true because another class is #scheduled at the same time new_chromo.overlap = True #Reassigns the list in chromo_list self.chromo_list[index] = exist_list #Returns pointer to the inserted chromosome return new_chromo else: #No class is schedule in that time slot if self.chromo_list[index] is None: #Inserts the existing chromosome into an empty list new_list = [chromosome] #Assigns new_chromo to the inserted object new_chromo = new_list[0] #Inserts the new list into the master chromosome list self.chromo_list[index] = new_list #Class is already scheduled in the time slot else: #Gets the existing list exist_list = self.chromo_list[index] #Adds the existing chromosome to the end of the existing list exist_list.append(chromosome) #Assigns new_chromo to the newly inserted chromosome new_chromo = exist_list[-1] #Inserts the existing list back into the master chromosome list self.chromo_list[index] = exist_list #Returns pointer to the chromosome that has been inserted return new_chromo def remove_chromosome(self,chromosome,index): """ Removes the chromosome from the list at position index @param chromosome: chromosome to remove from the list @param index: index where the chromosome is located """ #Gets the list at the index remove_list = self.chromo_list[index] #Uses built in python method to remove chromosome object from list remove_list.remove(chromosome) if not remove_list: remove_list = None #Reassigns the list to the master chromosome list self.chromo_list[index] = remove_list def get_chromosome_from_list(self,_class,index): """ Returns the chromosome object from the chromo_list at the given index that contains the _class. Returns None if not found @param _class: class to be searched for in the list @param index: index to search for the class """ search_list = self.chromo_list[index] #List doesnt exist if search_list is None: return None #List exists else: search_chromosome = None for chromosomes in search_list: #Class is found if chromosomes._class is _class: search_chromosome = chromosomes break #Class is not found else: search_chromosome = None return search_chromosome def algorithm(self): """ Runs the schedule algorithm. Flow of algorithm: read in data initialize chromosome get_overall_fitness while overall fitness != 1.0 get crossover points perform crossover get mutation size perform mutations get overall fitness return schedule """ #Loads the config for the rooms/classes self.get_config() #Creates the first chromosome self.init_chromosomes() print "BOOBS" self.get_overall_fitness() #Seeds the random with system time random.seed() generations = 1 print "TEST!!!" while self.get_overall_fitness() < 1.0 and generations <= 12500: #Get random points for crossover start_crossover = random.randint(0,self.config.get_num_classes()) end_crossover = random.randint(0,self.config.get_num_classes()) #Checks to see which crossover point is greater, Dont want to go #From a greater value to lesser value if start_crossover <= end_crossover: self.perform_crossover(start_crossover,end_crossover) else: self.perform_crossover(end_crossover, start_crossover) #Gets random mutation size and performs mutations self.mutation_size = random.randint(0,len(self.chromo_list)) self.perform_mutations() generations += 1 #Updates the console with Number of Generations and Perfect Chromos print_string_generation = "Generations: " + str(generations) print_string_best = " Overall Fitness: " + str(self.get_overall_fitness()) PrintStatic(print_string_generation + print_string_best) print "\nDone! Took " + str(generations) + " Generations" def print_chromosomes(self): """ Prints the chromo_list to console """ count = 0 while count < len(self.chromo_list): #Gets the list at counter chromo_list = self.chromo_list[count] if type(chromo_list) is ListType: len_list = len(chromo_list) if len_list > 1: print "CONFLICT!!" #Gets the tuple of (Day,Room) day_room = self.get_room_day_numbers(count) if chromo_list is not None: for chromosomes in chromo_list: print "Day: " + str(day_room[0]) print "Hour: " + str(count%self.day_length+1) print "Room: " + str(day_room[1]) chromosomes.print_chromo() print "\n\n" count += 1 def get_room_day_numbers(self,index): """ Returns a tuple (day_num, room_num) Based on the index given @param index: index to find day and room info """ count = self.config.get_num_rooms()*self.day_length old_count = 0 day_num = 1 found_day = False #Iterates through until the index is less than the count #Count is blocks of rooms*hours, which is days while not found_day: if index < count: found_day = True else: old_count = count count += self.config.get_num_rooms()*self.day_length day_num += 1 count = self.day_length room_num = 1 found_room = False #Iterates through until the index is less than the old_count #old_count is the starting index of the day the index is in while not found_room: old_count += count if index < old_count: found_room = True else: room_num += 1 #Creates the tuple tuple = (day_num,room_num) return tuple def randomize_schedule(self): """ Returns a random schedule based on current schedule """ #Creates a new Schedule Object new_schedule = Schedule(len(self.chromo_list),self.config) #For all of the entries in the hash map for classes,index in self.hash_map.items(): #Get New Random Position rand = random.randint(0,len(new_schedule.chromo_list)) total_duration = 0 temp_index = rand #Adds the Class for the whole Duration while total_duration < classes.duration\ and temp_index < len(new_schedule.chromo_list): new_chromo = new_schedule.insert_chromosome(Chromosome(),\ temp_index) new_schedule.number_chromosomes += 1 #Enters the new class into the hash map if not new_schedule.hash_map.has_key(classes): new_schedule.hash_map[classes] = temp_index #Assigns the class new_chromo._class = classes new_schedule.calculate_fitness(new_chromo,temp_index) total_duration += 1 temp_index += 1 return new_schedule