aboutsummaryrefslogblamecommitdiffstats
path: root/assignment.py
blob: 61f7d620e544ef67fe0e5491011589702e9e8df9 (plain) (tree)
















































































































































                                                                                                                                                                   
#!/bin/python
import glpk
import os
import re
import toml
import sys
import math
import random

random.seed(1409)
# 1 input parameter, the input filename

if len(sys.argv) < 2:
    print("Usage: assignment.py <configuration.toml>")
    sys.exit(-1)

with open(sys.argv[1]) as f:
    tm = toml.load(f)

if not ("students" in tm):
    print(f"Missing table students with preferences in {sys.argv[1]}, e.g.\n[students]\n# Student a@abc.de preferes topic 2 over 6 over 4.\n\"a@abc.de\": [2,6,4]")
    sys.exit(-1)

students = tm['students']
print("#Students = ",len(students))
m = 0
mlen = 0
for s in students.keys():
    m = max(m,max(students[s])+1)
    mlen = max(mlen, len(students[s])-1)

topics = [x for x in range(0,m)]

interest_decay = lambda x,y: x/math.pow(2,y)
factor = math.pow(2,mlen)
function="exponential"
if "settings" in tm:
    if "interest_decay" in tm["settings"]:
        if tm["settings"]["interest_decay"] == "lin":
            interest_decay = lambda x,y: x-y
            factor = mlen+1
            function="linear"


lp = glpk.LPX()                         
lp.name = 'assignment'                  
lp.obj.maximize = True                  
lp.rows.add(len(students)+len(topics))
objective=[]
constraints=[]
for idx,(s,r) in enumerate(zip(students.keys(),lp.rows[:len(students)])):
    r.name = 'student %s' % s
    # Each student gets exactly one topic
    r.bounds = 1.0,1.0
    last = 0
    beg = 0
    while beg<idx*len(topics):
        constraints.append(0.0)
        beg+=1
    comp = [(idx,val) for idx,val in enumerate(students[s])]
    comp = sorted(comp, key=lambda x: x[1])
    for (dv,k) in comp:
        while last < k:
            objective.append(0.0)
            constraints.append(0.0)
            last+=1
        objective.append(interest_decay(factor, dv))
        constraints.append(1.0)
        last+=1
    while last < len(topics):
        objective.append(0.0)
        constraints.append(0.0)
        last+=1
    beg = (idx+1)*len(topics)
    while beg < len(students)*len(topics):
        constraints.append(0.0)
        beg+=1

for e,r in enumerate(lp.rows[len(students):]):                       
    r.name = 'topic %s' % e
    # subject must be chosen at most 1 time.
    r.bounds = 0.0,1.0
    beg = 0
    while beg < len(students)*len(topics):
        if beg%len(topics) == e:
            constraints.append(1.0)
        else:
            constraints.append(0.0)
        beg+=1
lp.cols.add(len(students)*len(topics))                          
for c in lp.cols:                       
    c.name = 'x_%d_%d' % (c.index//len(topics),c.index%len(topics))            
    c.bounds = 0.0, 1.0
lp.obj[:] = objective
iteration = 0
optimal_solutions = []
best_solution = 0
while True:
    lp.matrix = constraints
    retval = lp.simplex()
    assert retval is None
    if lp.status != 'opt':
        break
    for col in lp.cols:
        col.kind = int

    retval = lp.integer()

    assert retval is None

    if lp.status != 'opt':
        break
    if lp.obj.value < best_solution:
        break
    else:
        best_solution = lp.obj.value
    next_constraint = []
    current_solution = []
    for c in lp.cols:
        if c.primal == 1:
            first = c.name.find("_")
            end = c.name.find("_", first+1)
            idx = int(c.name[first+1:end])
            next_constraint.append(1.0)
            current_solution.append((list(students.keys())[idx], c.name[end+1:]))
        else:
            next_constraint.append(0.0)
    lp.rows.add(1)
    r = lp.rows[-1]
    r.name = 'exclude %s' % iteration
    r.bounds = 0.0,len(students)-1
    iteration+=1
    # Add cutting plane
    constraints.extend(next_constraint)
    optimal_solutions.append(current_solution)

if len(optimal_solutions) == 0:
    print("Unsolvable assignment!")
    sys.exit(-1)
print("#Optimal Solutions = {0}; objective weight = {1:.6}; {2} scoring".format(len(optimal_solutions), best_solution, function))
print("Uniform choice among optimal solutions:")
opt = random.choice(optimal_solutions)
for (stud,choice) in opt:
    print("{0:<40} Topic {1}".format(stud, choice))