use latest algorithm

This commit is contained in:
SowinskiBraeden committed 2022-07-15 18:09:07 -07:00
1 parent 46d46c71a6
commit 0f183559e5
1 file changed
+97 -147
+97 -147
View File
@@ -2,13 +2,12 @@
import json import json
import random import random
from inspect import currentframe from inspect import currentframe
import pprint from string import hexdigits
# Import from custom utilities # Import from custom utilities
from util.mockStudents import getSampleStudents from util.mockStudents import getSampleStudents
from util.generateCourses import getSampleCourses from util.generateCourses import getSampleCourses
''' '''
Block 1-5 is first semester while Block 1-5 is first semester while
block 6-10 is second semester block 6-10 is second semester
@@ -38,7 +37,8 @@ from util.generateCourses import getSampleCourses
} }
''' '''
def getLineNumber(): return currentframe().f_back.f_lineno exists = lambda n : True if n not in ('', None) else False
getLineNumber = lambda : currentframe().f_back.f_lineno
# Takes in information to create or add a new conflict # Takes in information to create or add a new conflict
# Returns if the particular student has a previous error # Returns if the particular student has a previous error
@@ -116,7 +116,7 @@ def generateScheduleV3(
# Put # of classRunCount classes in emptyClasses # Put # of classRunCount classes in emptyClasses
for j in range(classRunCount): for j in range(classRunCount):
emptyClasses[index][f"{index}-{j}"] = { emptyClasses[index][f"{index}-{hexdigits[j]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index]["Description"],
"expectedLen": median # Number of students expected in this class / may be altered "expectedLen": median # Number of students expected in this class / may be altered
@@ -128,14 +128,14 @@ def generateScheduleV3(
while remaining > 0: while remaining > 0:
for j in range(classRunCount): for j in range(classRunCount):
if remaining == 0: break if remaining == 0: break
emptyClasses[index][f"{index}-{j}"]["expectedLen"] += 1 emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] += 1
remaining -= 1 remaining -= 1
# If we can create a class using remaining, but no other classes # If we can create a class using remaining, but no other classes
# exists, create class, and do not equalize # exists, create class, and do not equalize
elif remaining >= minReq: elif remaining >= minReq:
# Create a class using remaining # Create a class using remaining
emptyClasses[index][f"{index}-{classRunCount}"] = { emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index]["Description"],
"expectedLen": remaining "expectedLen": remaining
@@ -144,10 +144,10 @@ def generateScheduleV3(
classRunCount += 1 classRunCount += 1
if classRunCount >= 2: if classRunCount >= 2:
# Equalize (level) class expectedLen's # Equalize (level) class expectedLen's
expectedLengths = [emptyClasses[index][f"{index}-{j}"]["expectedLen"] for j in range(classRunCount)] expectedLengths = [emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] for j in range(classRunCount)]
newExpectedLens = equal(expectedLengths) newExpectedLens = equal(expectedLengths)
for j in range(len(newExpectedLens)): for j in range(len(newExpectedLens)):
emptyClasses[index][f"{index}-{j}"]["expectedLen"] = newExpectedLens[j] emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] = newExpectedLens[j]
# Else if we can't fit remaining in open slots in existing classes # Else if we can't fit remaining in open slots in existing classes
# and it is unable to create its own class, # and it is unable to create its own class,
@@ -155,12 +155,12 @@ def generateScheduleV3(
elif minReq - remaining < classRunCount * (median - minReq): elif minReq - remaining < classRunCount * (median - minReq):
# Take 1 from each class till min requirment met # Take 1 from each class till min requirment met
for j in range(classRunCount): for j in range(classRunCount):
emptyClasses[index][f"{index}-{j}"]["expectedLen"] -= 1 emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] -= 1
remaining += 1 remaining += 1
if remaining == minReq: break if remaining == minReq: break
# Create a class using remaining # Create a class using remaining
emptyClasses[index][f"{index}-{classRunCount}"] = { emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index]["Description"],
"expectedLen": remaining "expectedLen": remaining
@@ -169,10 +169,10 @@ def generateScheduleV3(
classRunCount += 1 classRunCount += 1
# Equalize (level) class expectedLen's # Equalize (level) class expectedLen's
expectedLengths = [emptyClasses[index][f"{index}-{j}"]["expectedLen"] for j in range(classRunCount)] expectedLengths = [emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] for j in range(classRunCount)]
newExpectedLens = equal(expectedLengths) newExpectedLens = equal(expectedLengths)
for j in range(len(newExpectedLens)): for j in range(len(newExpectedLens)):
emptyClasses[index][f"{index}-{j}"]["expectedLen"] = newExpectedLens[j] emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] = newExpectedLens[j]
else: else:
# In the case that the remaining requests are unable to be resolved # In the case that the remaining requests are unable to be resolved
@@ -180,8 +180,9 @@ def generateScheduleV3(
# Will need to be ignored so later we can fold them into their alternative # Will need to be ignored so later we can fold them into their alternative
# choices # choices
for j in range(classRunCount): for j in range(classRunCount):
if emptyClasses[index][f"{index}-{j}"]["expectedLen"] < classCap and remaining > 0: if remaining == 0: break
emptyClasses[index][f"{index}-{j}"]["expectedLen"] += 1 if emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] < classCap:
emptyClasses[index][f"{index}-{hexdigits[j]}"]["expectedLen"] += 1
remaining -= 1 remaining -= 1
courseRunInfo[index] = { courseRunInfo[index] = {
@@ -270,6 +271,9 @@ def generateScheduleV3(
# Return Error if code is altered to cause error # Return Error if code is altered to cause error
else: raise SystemExit(f"Invalid 'stepType' in func 'stepIndex' line {getLineNumber()}") else: raise SystemExit(f"Invalid 'stepType' in func 'stepIndex' line {getLineNumber()}")
# Create copy for step 6
courseRunInfoCopy = dict(courseRunInfo)
while len(allClassRunCounts) > 0: while len(allClassRunCounts) > 0:
# Get highest resource class (most times run) # Get highest resource class (most times run)
index = allClassRunCounts.index(min(allClassRunCounts)) index = allClassRunCounts.index(min(allClassRunCounts))
@@ -294,7 +298,7 @@ def generateScheduleV3(
# Spread classes throughout both semesters # Spread classes throughout both semesters
for i in range(courseRunInfo[course]["Total"]): for i in range(courseRunInfo[course]["Total"]):
cname = f"{course}-{i}" cname = f"{course}-{hexdigits[i]}"
classInserted = False classInserted = False
while not classInserted: while not classInserted:
@@ -334,7 +338,7 @@ def generateScheduleV3(
leastBlock = semBlocks.index(min(semBlocks)) leastBlock = semBlocks.index(min(semBlocks))
cname = f"{course}-0" cname = f"{course}-0"
running[f"block{leastBlock+offset}"][course] = { running[f"block{leastBlock+offset}"][cname] = {
"CrsNo": course, "CrsNo": course,
"Description": emptyClasses[course][cname]["Description"], "Description": emptyClasses[course][cname]["Description"],
"students": selectedCourses[cname]["students"], "students": selectedCourses[cname]["students"],
@@ -363,9 +367,8 @@ def generateScheduleV3(
studentsCritical, studentsAcceptable = 0, 0 studentsCritical, studentsAcceptable = 0, 0
for student in students: for student in students:
initialBlocks = [student["schedule"][block] for block in student["schedule"]] blocks = [student["schedule"][block] for block in student["schedule"]]
conflicts = sum(1 for b in initialBlocks if len(b)>1) hasConflicts = True if sum(1 for b in blocks if len(b)>1) > 0 else False
hasConflicts = True if conflicts > 0 else False
# If there is no conflicts # If there is no conflicts
# and classes inserted to is equal to expectedClasses # and classes inserted to is equal to expectedClasses
@@ -384,146 +387,93 @@ def generateScheduleV3(
"index": student["studentIndex"] "index": student["studentIndex"]
} }
# If we are unable to solve, we add to exceptions if hasConflicts:
# and keep trying to resolve the rest of the schedule # Clear student schedule
exceptions = [] for block in student["schedule"]:
[running[block][cname]["students"].remove(studentData) for cname in student["schedule"][block]]
student["schedule"][block] = []
while hasConflicts: # Find what class in student schedule has least run time
# Check if conflicts have been resolved classes, runCounts = [], []
blocks = [student["schedule"][block] for block in student["schedule"]] for block in blocks:
# If there is exceptions, make them look normal in blocks for cname in block:
# list to ignore them when looking for clashes, and not to classes.append(cname[:-2])
# accidently overwrite while evaluating other classes runCounts.append(courseRunInfoCopy[cname[:-2]]["Total"])
if len(exceptions) > 0:
for i in range(len(blocks)):
if i in exceptions: blocks[i] = ['EXPT']
conflicts = sum(1 for b in blocks if len(b)>1)
if conflicts == 0: break # Rebuild student schedule
availableBlocks = [f'block{i}' for i in range(1, 11)]
while len(classes) > 0:
index = runCounts.index(min(runCounts)) # Get class least run
found = False
elif conflicts > 0: # find slot for class
for block in availableBlocks:
if found: break
for cname in running[block]:
if cname[:-2] == classes[index] and len(running[block][cname]["students"]) < classCap:
running[block][cname]["students"].append(studentData)
student["schedule"][block].append(cname)
availableBlocks.remove(block)
found = True
break
blockLens = [len(block) for block in blocks] if not found:
freeBlocks = [index for index in range(len(blocks)) if len(blocks[index]) == 0] # Determine all places class exists
existsIn, existingClassNames = [], []
for block in running:
for cname in running[block]:
if cname[:-2] == classes[index] and len(running[block][cname]["students"]) < classCap:
existsIn.append(block)
existingClassNames.append(cname)
clashIndex = blockLens.index(max(blockLens)) # Attempt to fix
blockOut = f"block{clashIndex+1}" solution = False
classIndex = 0 if len(existsIn) > 0:
done, advancedConflict = False, False for i, existing in enumerate(existsIn):
if solution: break
classOut = student["schedule"][existing][0]
for block in running:
if solution: break
if block == existing or block not in availableBlocks: continue
for cname in running[block]:
if cname[:-2] == classOut[:-2] and len(running[block][cname]["students"]) < classCap:
# Move to existing class elsewhere
student["schedule"][block].append(cname)
running[block][cname]["students"].append(studentData)
while not done: # Overwrite old class
classOut = blocks[clashIndex][classIndex] running[existing][student["schedule"][existing][0]]["students"].remove(studentData)
found = False student["schedule"][existing][0] = existingClassNames[i]
for index in freeBlocks: running[existing][existingClassNames[i]]["students"].append(studentData)
if found: break
if index != clashIndex:
blockIn = list(running)[index]
for cname in running[blockIn]:
if cname[:-2] == classOut[:-2] and len(running[blockIn][cname]["students"]) < classCap:
# Update records solution = True
student["schedule"][blockOut].remove(classOut) break
student["schedule"][blockIn].append(cname)
running[blockOut][classOut]["students"].remove(studentData) if not solution:
running[blockIn][cname]["students"].append(studentData) # Try alternate
alternates = [alt["CrsNo"] for alt in students[student["studentIndex"]]["remainingAlts"] if alt["CrsNo"] not in flex and alt["CrsNo"] in courseRunInfoCopy]
if len(alternates) == 0: # If not alternates, create critical error
c_cr_count += 1
criticalCount += 1
if not newConflict(student["Pupil #"], "", "Critical", "C-CR", "Couldn't Resolve", conflictLogs): studentsCritical += 1
found = True
break
if not found:
if classIndex < len(blocks[clashIndex])-1: classIndex += 1
elif classIndex == len(blocks[clashIndex])-1:
classIndex = 0
advancedConflict = True
done = True
else: else:
print(f"Fatal error ({getLineNumber()}): Impossible error") # Get alternate least run
continue altRunCounts = [courseRunInfoCopy[alt]["Total"] for alt in alternates]
altIndex = altRunCounts.index(min(altRunCounts))
elif found: done = True # add alternate
classes.append(alternates[altIndex])
runCounts.append(altRunCounts[altIndex])
if advancedConflict: # Remove alternate from remaining alternates
attemptResolve = True for remaining in students[student["studentIndex"]]["remainingAlts"]:
classOutIndex = 0 if remaining["CrsNo"] == alternates[altIndex]:
while attemptResolve: students[student["studentIndex"]]["remainingAlts"].remove(remaining)
foundSolution = False
newClassOut = blocks[clashIndex][classOutIndex]
for blockIndex in range(len(running)):
if foundSolution: break
if blockIndex != clashIndex and blockIndex not in exceptions:
for cname in running[list(running)[blockIndex]]:
if cname[:-2] == newClassOut[:-2]:
blockIn = f"block{blockIndex+1}"
if blockIndex in freeBlocks:
if len(running[blockIn][cname]["students"]) < classCap:
# In the rare or impossible case a student was not inserted to a class
# And it weren't full, insert them into the class
student["schedule"][blockOut].remove(newClassOut)
student["schedule"][blockIn].append(cname)
running[blockOut][newClassOut]["students"].remove(studentData) # Remove class after inserted or failed to insert
running[blockIn][cname]["students"].append(studentData) classes.remove(classes[index])
foundSolution = True runCounts.remove(runCounts[index])
break
elif len(blocks[blockIndex]) == 1:
oClassOut = blocks[blockIndex][0]
found_oBlockSolution = False
for oBlockIndex in range(len(running)):
if found_oBlockSolution: break
if oBlockIndex != clashIndex and oBlockIndex not in exceptions and oBlockIndex in freeBlocks:
for ocname in running[list(running)[oBlockIndex]]:
if ocname[:-2] == oClassOut[:-2]:
oBlockIn = f"block{oBlockIndex+1}"
if len(running[oBlockIn][ocname]["students"]) < classCap:
student["schedule"][blockOut].remove(newClassOut)
student["schedule"][blockIn].append(cname)
student["schedule"][blockIn].remove(oClassOut)
student["schedule"][oBlockIn].append(ocname)
running[blockOut][newClassOut]["students"].remove(studentData)
running[blockIn][cname]["students"].append(studentData)
running[blockIn][oClassOut]["students"].remove(studentData)
running[oBlockIn][ocname]["students"].append(studentData)
found_oBlockSolution = True
break
if not found_oBlockSolution:
exceptions.append(clashIndex)
criticalCount += 1
c_cr_count += 1
if not newConflict(student["Pupil #"], "", "Critial", "C-CR", "Couldn't Resolve", conflictLogs): studentsCritical += 1
foundSolution = True
break
if not foundSolution:
if classOutIndex < len(blocks[clashIndex]) - 1: classOutIndex += 1
elif classOutIndex == len(blocks[clashIndex]) - 1:
if len(student["remainingAlts"]) > 0:
exceptions.append(clashIndex)
criticalCount += 1
c_cr_count += 1
if not newConflict(student["Pupil #"], "", "Critical", "C-CR", "Couldn't Resolve", conflictLogs): studentsCritical += 1
attemptResolve = False
# Attempt to use alt
elif len(student["remainingAlts"]) == 0:
exceptions.append(clashIndex)
criticalCount += 1
c_cr_count += 1
if not newConflict(student["Pupil #"], "", "Critical", "C-CR", "Couldn't Resolve", conflictLogs): studentsCritical += 1
attemptResolve = False
else: print("Impossible - Thanos")
elif foundSolution:
attemptResolve = False
else:
print(f"Fatal error ({getLineNumber()}): Impossible error")
continue
metSelfRequirements = True if student["classes"] == student["expectedClasses"] else False metSelfRequirements = True if student["classes"] == student["expectedClasses"] else False
while not metSelfRequirements: while not metSelfRequirements:
@@ -581,7 +531,7 @@ def generateScheduleV3(
for student in students: for student in students:
for block in student["schedule"]: for block in student["schedule"]:
if len(student["schedule"][block]) == 0: if len(student["schedule"][block]) == 0:
student["schedule"][block].append(flex[0]) if int(block[len(block)-1]) <= 5 else student["schedule"][block].append(flex[1]) student["schedule"][block].append(flex[0]) if int(block[5:]) <= 5 else student["schedule"][block].append(flex[1])
# Update Student records # Update Student records
with open(studentsDir, "w") as outfile: with open(studentsDir, "w") as outfile: