# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_gymnast_allocation():
    """Optimize gymnast allocation to maximize total points scored in events."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("gymnast_allocation")
    
    # Data from the database
    points = [
        [12, 10, 14, 15, 11, 9],  # Gymnast 1
        [18, 16, 17, 19, 14, 13], # Gymnast 2
        [14, 12, 15, 17, 13, 11]  # Gymnast 3
    ]
    
    max_events_per_gymnast = 3
    max_gymnasts_per_event = 5
    
    n_gymnasts = len(points)
    n_events = len(points[0])
    
    # CRITICAL: Validate array lengths before loops
    assert all(len(row) == n_events for row in points), "Array length mismatch in points data"
    
    # 2. VARIABLES
    # Binary decision variables for participation
    x = model.addVars(n_gymnasts, n_events, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total points scored
    model.setObjective(
        gp.quicksum(points[i][j] * x[i, j] for i in range(n_gymnasts) for j in range(n_events)),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Maximum events per gymnast
    model.addConstrs(
        (gp.quicksum(x[i, j] for j in range(n_events)) <= max_events_per_gymnast for i in range(n_gymnasts)),
        name="max_events_per_gymnast"
    )
    
    # Maximum gymnasts per event
    model.addConstrs(
        (gp.quicksum(x[i, j] for i in range(n_gymnasts)) <= max_gymnasts_per_event for j in range(n_events)),
        name="max_gymnasts_per_event"
    )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in range(n_gymnasts):
            for j in range(n_events):
                if x[i, j].x > 1e-6:
                    print(f"Gymnast {i+1} participates in event {j+1}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_gymnast_allocation()