Download
from Numberjack import *
from validator import read_instance_day3, MAX_NUMBER_INTERVIEWS, MAX_ACCEPTABLE_PREF_VALUE
import csv


def get_day2_model(all_student_preferences, company_data):
    num_students = len(all_student_preferences)
    assert num_students > 0
    num_companies = len(all_student_preferences[0])
    assert len(company_data) == num_companies

    model = Model()
    all_student_rows, all_student_variables = [], []
    obj1_variables, obj1_coefficients = [], []

    for student_id, student_preferences in enumerate(all_student_preferences):
        num_expected_interviews = MAX_NUMBER_INTERVIEWS
        num_valid_preferences = sum(
            1 if pref_val < MAX_ACCEPTABLE_PREF_VALUE else 0 for pref_val in student_preferences
        )
        num_expected_interviews = min(num_expected_interviews, num_valid_preferences)

        if num_expected_interviews == 0:
            print "Ignoring student", student_id+1
            all_student_rows.append([])
            continue

        # FD variable 0..num_companies-1 deciding which company this student interviews, for
        # each one of their 'num_expected_interviews'
        student_variables = VarArray(
            num_expected_interviews, num_companies, "student%d" % (student_id+1)
        )
        all_student_rows.append(student_variables)
        all_student_variables.extend([x for x in student_variables])

        # Each of the assignments for a student must be different, redundant with ordering below
        model += AllDiff(student_variables)

        # Break symmetry by enforcing ordering on the row
        for i in range(len(student_variables)-1):
            model += student_variables[i] < student_variables[i+1]

        student_regret_vars, student_regret_coeffs = [], []
        for company_id, preference_value in enumerate(student_preferences):
            for cell in student_variables:
                obj1_variables.append(cell == company_id)
                obj1_coefficients.append(preference_value)

                student_regret_vars.append(cell == company_id)
                student_regret_coeffs.append(preference_value)

        # Enforce that the regret is 1 (i.e. the sum of pref == vbregret+1)
        virtual_best_regret = sum(sorted(student_preferences)[:num_expected_interviews])
        model += Sum(student_regret_vars, student_regret_coeffs) <= virtual_best_regret + 1

    # Cardinality constraint limiting the number of students assigned to each company
    gcc_cap_limits = {}
    for company_id, (dissappointment_cost, min_assignment, max_assignment, attendance_cost) in \
            enumerate(company_data):
        gcc_cap_limits[company_id] = [min_assignment, max_assignment]
    model += Gcc(all_student_variables, gcc_cap_limits)

    obj = Sum(obj1_variables, obj1_coefficients)
    model += Minimise(obj)

    return model, all_student_rows, obj


def output_assignments(all_student_rows, filename):
    with open(filename, "wt") as f:
        writer = csv.writer(f)
        writer.writerow(["StudentNr", "CompanyNr"])
        for student_id, student_row in enumerate(all_student_rows):
            for assigned_variable in student_row:
                # +1 for one-based indexing instead of zero-based
                writer.writerow([student_id + 1, assigned_variable.get_value() + 1])


def main(param):
    all_student_preferences, company_data = read_instance_day3(param['instance'])

    model, all_student_rows, obj = get_day2_model(all_student_preferences, company_data)

    if param['verbosity'] >= 3:
        print model

    solver = model.load(param['solver'])
    solver.setVerbosity(param['verbosity'])
    solver.setTimeLimit(param['timelimit'])
    solver.solve()

    if solver.is_sat():
        print "SAT"
        print "Obj:", obj.get_value()
        for row in all_student_rows:
            print row
        output_assignments(all_student_rows, param['solution'])
    elif solver.is_unsat():
        print "UNSAT"
    else:
        print "Unknown"


if __name__ == '__main__':
    default = {
        'instance': 'testproblems/2/preferences.csv',
        'solution': 'njsolution-day2.csv',
        'solver': 'CPLEX',
        'verbosity': 1,
        'timelimit': 60,
    }
    param = input(default)
    main(param)