Spaces:
Sleeping
Sleeping
from .Constant import Constant | |
from .CourseClass import CourseClass | |
from .Reservation import Reservation | |
from .Criteria import Criteria | |
from collections import deque | |
from random import randrange | |
import numpy as np | |
# Schedule chromosome | |
class Schedule: | |
# Initializes chromosomes with configuration block (setup of chromosome) | |
def __init__(self, configuration): | |
self._configuration = configuration | |
# Fitness value of chromosome | |
self._fitness = 0 | |
# Time-space slots, one entry represent one hour in one classroom | |
slots_length = Constant.DAYS_NUM * Constant.DAY_HOURS * self._configuration.numberOfRooms | |
self._slots = [[] for _ in range(slots_length)] | |
# Class table for chromosome | |
# Used to determine first time-space slot used by class | |
self._classes = {} | |
# Flags of class requirements satisfaction | |
self._criteria = np.zeros(self._configuration.numberOfCourseClasses * len(Criteria.weights), dtype=bool) | |
self._diversity = 0.0 | |
self._rank = 0 | |
self._convertedObjectives = [] | |
self._objectives = [] | |
def copy(self, c, setup_only): | |
if not setup_only: | |
self._configuration = c.configuration | |
# copy code | |
self._slots, self._classes = [row[:] for row in c.slots], {key: value for key, value in c.classes.items()} | |
# copy flags of class requirements | |
self._criteria = c.criteria[:] | |
# copy fitness | |
self._fitness = c.fitness | |
return self | |
return Schedule(c.configuration) | |
# Makes new chromosome with same setup but with randomly chosen code | |
def makeNewFromPrototype(self, positions = None): | |
# make new chromosome, copy chromosome setup | |
new_chromosome = self.copy(self, True) | |
new_chromosome_slots, new_chromosome_classes = new_chromosome._slots, new_chromosome._classes | |
# place classes at random position | |
classes = self._configuration.courseClasses | |
nr = self._configuration.numberOfRooms | |
DAYS_NUM, DAY_HOURS = Constant.DAYS_NUM, Constant.DAY_HOURS | |
for c in classes: | |
# determine random position of class | |
dur = c.Duration | |
day = randrange(DAYS_NUM) | |
room = randrange(nr) | |
time = randrange(DAY_HOURS - dur) | |
reservation = Reservation.getReservation(nr, day, time, room) | |
if positions is not None: | |
positions.append(day) | |
positions.append(room) | |
positions.append(time) | |
reservation_index = hash(reservation) | |
# fill time-space slots, for each hour of class | |
for i in range(dur - 1, -1, -1): | |
new_chromosome_slots[reservation_index + i].append(c) | |
# insert in class table of chromosome | |
new_chromosome_classes[c] = reservation_index | |
new_chromosome.calculateFitness() | |
return new_chromosome | |
# Performs crossover operation using to chromosomes and returns pointer to offspring | |
def crossover(self, parent, numberOfCrossoverPoints, crossoverProbability): | |
# check probability of crossover operation | |
if randrange(100) > crossoverProbability: | |
# no crossover, just copy first parent | |
return self.copy(self, False) | |
# new chromosome object, copy chromosome setup | |
n = self.copy(self, True) | |
n_classes, n_slots = n._classes, n._slots | |
classes = self._classes | |
course_classes = tuple(classes.keys()) | |
parent_classes = parent.classes | |
parent_course_classes = tuple(parent.classes.keys()) | |
# number of classes | |
size = len(classes) | |
cp = size * [False] | |
# determine crossover point (randomly) | |
for i in range(numberOfCrossoverPoints, 0, -1): | |
check_point = False | |
while not check_point: | |
p = randrange(size) | |
if not cp[p]: | |
cp[p] = check_point = True | |
# make new code by combining parent codes | |
first = randrange(2) == 0 | |
for i in range(size): | |
if first: | |
course_class = course_classes[i] | |
dur = course_class.Duration | |
reservation_index = classes[course_class] | |
# insert class from first parent into new chromosome's class table | |
n_classes[course_class] = reservation_index | |
# all time-space slots of class are copied | |
for j in range(dur - 1, -1, -1): | |
n_slots[reservation_index + j].append(course_class) | |
else: | |
course_class = parent_course_classes[i] | |
dur = course_class.Duration | |
reservation_index = parent_classes[course_class] | |
# insert class from second parent into new chromosome's class table | |
n_classes[course_class] = reservation_index | |
# all time-space slots of class are copied | |
for j in range(dur - 1, -1, -1): | |
n_slots[reservation_index + j].append(course_class) | |
# crossover point | |
if cp[i]: | |
# change source chromosome | |
first = not first | |
n.calculateFitness() | |
# return smart pointer to offspring | |
return n | |
# Performs crossover operation using to chromosomes and returns pointer to offspring | |
def crossovers(self, parent, r1, r2, r3, etaCross, crossoverProbability): | |
# number of classes | |
size = len(self._classes) | |
jrand = randrange(size) | |
nr = self._configuration.numberOfRooms | |
DAY_HOURS, DAYS_NUM = Constant.DAY_HOURS, Constant.DAYS_NUM | |
# make new chromosome, copy chromosome setup | |
new_chromosome = self.copy(self, True) | |
new_chromosome_slots, new_chromosome_classes = new_chromosome._slots, new_chromosome._classes | |
classes = self._classes | |
course_classes = tuple(classes.keys()) | |
parent_classes = parent.classes | |
parent_course_classes = tuple(parent.classes.keys()) | |
for i in range(size): | |
if randrange(100) > crossoverProbability or i == jrand: | |
course_class = course_classes[i] | |
reservation1, reservation2 = Reservation.parse(r1.classes[course_class]), Reservation.parse(r2.classes[course_class]) | |
reservation3 = Reservation.parse(r3.classes[course_class]) | |
dur = course_class.Duration | |
day = int(reservation3.Day + etaCross * (reservation1.Day - reservation2.Day)) | |
if day < 0: | |
day = 0 | |
elif day >= DAYS_NUM: | |
day = DAYS_NUM - 1 | |
room = int(reservation3.Room + etaCross * (reservation1.Room - reservation2.Room)) | |
if room < 0: | |
room = 0 | |
elif room >= nr: | |
room = nr - 1 | |
time = int(reservation3.Time + etaCross * (reservation1.Time - reservation2.Time)) | |
if time < 0: | |
time = 0 | |
elif time >= (DAY_HOURS - dur): | |
time = DAY_HOURS - 1 - dur | |
reservation = Reservation.getReservation(nr, day, time, room) | |
reservation_index = hash(reservation) | |
# fill time-space slots, for each hour of class | |
for j in range(dur - 1, -1, -1): | |
new_chromosome_slots[reservation_index + j].append(course_class) | |
# insert in class table of chromosome | |
new_chromosome_classes[course_class] = reservation_index | |
else: | |
course_class = parent_course_classes[i] | |
dur = course_class.Duration | |
reservation = parent_classes[course_class] | |
reservation_index = hash(reservation) | |
# all time-space slots of class are copied | |
for j in range(dur - 1, -1, -1): | |
new_chromosome_slots[reservation_index + j].append(course_class) | |
# insert class from second parent into new chromosome's class table | |
new_chromosome_classes[course_class] = reservation_index | |
new_chromosome.calculateFitness() | |
# return smart pointer to offspring | |
return new_chromosome | |
def repair(self, cc1: CourseClass, reservation1_index: int, reservation2: Reservation): | |
nr = self._configuration.numberOfRooms | |
DAY_HOURS, DAYS_NUM = Constant.DAY_HOURS, Constant.DAYS_NUM | |
slots = self._slots | |
dur = cc1.Duration | |
for j in range(dur): | |
# remove class hour from current time-space slot | |
cl = slots[reservation1_index + j] | |
while cc1 in cl: | |
cl.remove(cc1) | |
# determine position of class randomly | |
if reservation2 is None: | |
day = randrange(DAYS_NUM) | |
room = randrange(nr) | |
time = randrange(DAY_HOURS - dur) | |
reservation2 = Reservation.getReservation(nr, day, time, room) | |
reservation2_index = hash(reservation2) | |
for j in range(dur): | |
# move class hour to new time-space slot | |
slots[reservation2_index + j].append(cc1) | |
# change entry of class table to point to new time-space slots | |
self._classes[cc1] = reservation2_index | |
# Performs mutation on chromosome | |
def mutation(self, mutationSize, mutationProbability): | |
# check probability of mutation operation | |
if randrange(100) > mutationProbability: | |
return | |
classes = self._classes | |
# number of classes | |
numberOfClasses = len(classes) | |
course_classes = tuple(classes.keys()) | |
configuration = self._configuration | |
nr = configuration.numberOfRooms | |
# move selected number of classes at random position | |
for i in range(mutationSize, 0, -1): | |
# select ranom chromosome for movement | |
mpos = randrange(numberOfClasses) | |
# current time-space slot used by class | |
cc1 = course_classes[mpos] | |
reservation1_index = classes[cc1] | |
self.repair(cc1, reservation1_index, None) | |
self.calculateFitness() | |
# Calculates fitness value of chromosome | |
def calculateFitness(self): | |
# increment value when criteria violation occurs | |
self._objectives = np.zeros(len(Criteria.weights)) | |
# chromosome's score | |
score = 0 | |
criteria, configuration = self._criteria, self._configuration | |
items, slots = self._classes.items(), self._slots | |
numberOfRooms = configuration.numberOfRooms | |
DAY_HOURS, DAYS_NUM = Constant.DAY_HOURS, Constant.DAYS_NUM | |
daySize = DAY_HOURS * numberOfRooms | |
ci = 0 | |
getRoomById = configuration.getRoomById | |
# check criteria and calculate scores for each class in schedule | |
for cc, reservation_index in items: | |
reservation = Reservation.parse(reservation_index) | |
# coordinate of time-space slot | |
day, time, room = reservation.Day, reservation.Time, reservation.Room | |
dur = cc.Duration | |
ro = Criteria.isRoomOverlapped(slots, reservation, dur) | |
# on room overlapping | |
criteria[ci + 0] = not ro | |
r = getRoomById(room) | |
# does current room have enough seats | |
criteria[ci + 1] = Criteria.isSeatEnough(r, cc) | |
# does current room have computers if they are required | |
criteria[ci + 2] = Criteria.isComputerEnough(r, cc) | |
# check overlapping of classes for professors | |
timeId = day * daySize + time | |
po, go = Criteria.isOverlappedProfStudentGrp(slots, cc, numberOfRooms, timeId) | |
# professors have no overlapping classes? | |
criteria[ci + 3] = not po | |
# student groups has no overlapping classes? | |
criteria[ci + 4] = not go | |
for i in range(len(self._objectives)): | |
if criteria[ci + i]: | |
score += 1 | |
else: | |
score += Criteria.weights[i] | |
self._objectives[i] += 1 if Criteria.weights[i] > 0 else 2 | |
ci += len(Criteria.weights) | |
# calculate fitness value based on score | |
self._fitness = score / len(criteria) | |
def getDifference(self, other): | |
return (self._criteria ^ other.criteria).sum() | |
def extractPositions(self, positions): | |
i = 0 | |
items = self._classes.items() | |
for cc, reservation_index in items: | |
reservation = Reservation.parse(reservation_index) | |
positions[i] = reservation.Day | |
i += 1 | |
positions[i] = reservation.Room | |
i += 1 | |
positions[i] = reservation.Time | |
i += 1 | |
def updatePositions(self, positions): | |
DAYS_NUM, DAY_HOURS = Constant.DAYS_NUM, Constant.DAY_HOURS | |
nr = self._configuration.numberOfRooms | |
i = 0 | |
items = self._classes.items() | |
for cc, reservation1_index in items: | |
dur = cc.Duration | |
day = abs(int(positions[i]) % DAYS_NUM) | |
room = abs(int(positions[i + 1]) % nr) | |
time = abs(int(positions[i + 2]) % (DAY_HOURS - dur)) | |
reservation2 = Reservation.getReservation(nr, day, time, room) | |
self.repair(cc, reservation1_index, reservation2) | |
positions[i] = reservation2.Day | |
i += 1 | |
positions[i] = reservation2.Room | |
i += 1 | |
positions[i] = reservation2.Time | |
i += 1 | |
self.calculateFitness() | |
# Returns fitness value of chromosome | |
def fitness(self): | |
return self._fitness | |
def configuration(self): | |
return self._configuration | |
# Returns reference to table of classes | |
def classes(self): | |
return self._classes | |
# Returns array of flags of class requirements satisfaction | |
def criteria(self): | |
return self._criteria | |
# Return reference to array of time-space slots | |
def slots(self): | |
return self._slots | |
def diversity(self): | |
return self._diversity | |
def diversity(self, new_diversity): | |
self._diversity = new_diversity | |
def rank(self): | |
return self._rank | |
def rank(self, new_rank): | |
self._rank = new_rank | |
def convertedObjectives(self): | |
return self._convertedObjectives | |
def objectives(self): | |
return self._objectives | |
def resizeConvertedObjectives(self, numObj): | |
self._convertedObjectives = numObj * [0] | |
def clone(self): | |
return self.copy(self, False) | |
def dominates(self, other): | |
better = False | |
for f, obj in enumerate(self.objectives): | |
if obj > other.objectives[f]: | |
return False | |
if obj < other.objectives[f]: | |
better = True | |
return better | |