# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_advisor_workload():
    """Optimize the allocation of student advisors to minimize maximum workload."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("advisor_workload_optimization")
    
    # Data from the database
    students = [1, 2, 3]
    advisors = [201, 202, 203]
    max_students = {201: 5, 202: 6, 203: 7}
    max_weights = {201: 100.0, 202: 120.0, 203: 150.0}
    pet_weights = {201: 50.0, 202: 60.0, 203: 70.0}
    
    # Validate data lengths
    assert len(max_students) == len(max_weights) == len(pet_weights) == len(advisors), "Array length mismatch"
    
    # 2. VARIABLES
    x = model.addVars(students, advisors, vtype=GRB.BINARY, name="x")
    w = model.addVars(advisors, vtype=GRB.CONTINUOUS, name="w", lb=0)
    M = model.addVar(vtype=GRB.CONTINUOUS, name="M", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(M, GRB.MINIMIZE)
    
    # 4. CONSTRAINTS
    
    # Maximum workload constraint
    model.addConstrs((M >= gp.quicksum(x[i, j] for i in students) + w[j] for j in advisors), name="max_workload")
    
    # Student assignment constraint
    model.addConstrs((gp.quicksum(x[i, j] for j in advisors) == 1 for i in students), name="student_assignment")
    
    # Advisor capacity constraints
    model.addConstrs((gp.quicksum(x[i, j] for i in students) <= max_students[j] for j in advisors), name="max_students")
    model.addConstrs((w[j] <= max_weights[j] for j in advisors), name="max_weight")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for j in advisors:
            assigned_students = [i for i in students if x[i, j].x > 0.5]
            print(f"Advisor {j} is assigned students: {assigned_students} with total pet weight: {w[j].x:.2f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_advisor_workload()