## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be a binary decision variable where \( x_{ij} = 1 \) if gymnast \( i \) participates in event \( j \), and \( x_{ij} = 0 \) otherwise.
  - \( i \) represents the gymnast index.
  - \( j \) represents the event index, where \( j \in \{1, 2, 3, 4, 5, 6\} \) corresponding to Floor Exercise, Pommel Horse, Rings, Vault, Parallel Bars, and Horizontal Bar, respectively.

#### Objective Function
Maximize the total points scored by all gymnasts across all events:
\[
\text{Maximize } \sum_{i} \sum_{j} p_{ij} \cdot x_{ij}
\]
where \( p_{ij} \) is the points scored by gymnast \( i \) in event \( j \).

Data Source Verification:
- \( p_{ij} \) values are sourced from the `gymnast` table columns: `Floor_Exercise_Points`, `Pommel_Horse_Points`, `Rings_Points`, `Vault_Points`, `Parallel_Bars_Points`, `Horizontal_Bar_Points`.

#### Constraints
1. **Maximum Events Per Gymnast:**
   Each gymnast can participate in at most a specified number of events:
   \[
   \sum_{j} x_{ij} \leq \text{max\_events\_per\_gymnast} \quad \forall i
   \]
   Data Source Verification:
   - `max_events_per_gymnast` is sourced from the `constraint_bounds` table.

2. **Maximum Gymnasts Per Event:**
   Each event can have at most a specified number of gymnasts:
   \[
   \sum_{i} x_{ij} \leq \text{max\_gymnasts\_per\_event} \quad \forall j
   \]
   Data Source Verification:
   - `max_gymnasts_per_event` is sourced from the `constraint_bounds` table.

3. **Binary Decision Variables:**
   \[
   x_{ij} \in \{0, 1\} \quad \forall i, j
   \]

This formulation is a Mixed-Integer Programming (MIP) model due to the binary nature of the decision variables \( x_{ij} \). The model is linear, adhering to the constraints and objective function requirements, and is immediately solvable using standard optimization solvers.

## 5. Gurobipy Implementation

```python
# 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()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 141.0
**Execution Time**: 0.96 seconds
**Reliability**: high
**Analysis**: Gurobipy found an optimal solution quickly with the shortest execution time, indicating efficient performance and reliability.

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation

from docplex.mp.model import Model

def gymnastics_optimization():
    """Optimize gymnast event participation to maximize total points."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="gymnastics_optimization")
    
    # 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 to prevent IndexError
    assert all(len(p) == n_events for p in points), "Array length mismatch"
    safe_range_gymnasts = range(n_gymnasts)
    safe_range_events = range(n_events)
    
    # 2. VARIABLES
    x = {(i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in safe_range_gymnasts for j in safe_range_events}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(points[i][j] * x[i, j] for i in safe_range_gymnasts for j in safe_range_events)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Maximum events per gymnast
    for i in safe_range_gymnasts:
        mdl.add_constraint(mdl.sum(x[i, j] for j in safe_range_events) <= max_events_per_gymnast, ctname=f"max_events_gymnast_{i}")
    
    # Maximum gymnasts per event
    for j in safe_range_events:
        mdl.add_constraint(mdl.sum(x[i, j] for i in safe_range_gymnasts) <= max_gymnasts_per_event, ctname=f"max_gymnasts_event_{j}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_range_gymnasts:
            for j in safe_range_events:
                value = solution.get_value(x[i, j])
                if value > 1e-6:
                    print(f"Gymnast {i} participates in event {j}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
gymnastics_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 141.0
**Execution Time**: 4.04 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution, albeit with a longer execution time compared to Gurobipy, but still reliable.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

import pyomo.environ as pyo
from pyomo.opt import SolverFactory

def gymnastics_optimization():
    """Optimize gymnast event participation to maximize total points."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Points scored by each gymnast in each event
    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
    ]
    
    # Constraints from the database
    max_events_per_gymnast = 3
    max_gymnasts_per_event = 5
    
    # Number of gymnasts and events
    n_gymnasts = len(points)
    n_events = len(points[0])
    
    # CRITICAL: Validate array lengths before indexing
    assert all(len(row) == n_events for row in points), "Array length mismatch in points data"
    
    # 3. SETS
    model.I = pyo.RangeSet(1, n_gymnasts)  # Gymnasts
    model.J = pyo.RangeSet(1, n_events)    # Events
    
    # 4. PARAMETERS
    model.points = pyo.Param(model.I, model.J, initialize={(i+1, j+1): points[i][j] for i in range(n_gymnasts) for j in range(n_events)})
    
    # 5. VARIABLES
    model.x = pyo.Var(model.I, model.J, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.points[i, j] * model.x[i, j] for i in model.I for j in model.J)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Maximum events per gymnast
    def max_events_rule(model, i):
        return sum(model.x[i, j] for j in model.J) <= max_events_per_gymnast
    model.max_events_constraint = pyo.Constraint(model.I, rule=max_events_rule)
    
    # Maximum gymnasts per event
    def max_gymnasts_rule(model, j):
        return sum(model.x[i, j] for i in model.I) <= max_gymnasts_per_event
    model.max_gymnasts_constraint = pyo.Constraint(model.J, rule=max_gymnasts_rule)
    
    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')
    
    # Optional: Set solver options
    solver.options['TimeLimit'] = 300  # 5 minutes
    solver.options['MIPGap'] = 0.01    # 1% gap
    
    # Solve the model
    results = solver.solve(model, tee=True)  # tee=True shows solver output
    
    # 9. RESULT PROCESSING
    # Check solver status
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print("Optimal solution found!")
        print(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nParticipation decisions:")
        for i in model.I:
            for j in model.J:
                x_val = pyo.value(model.x[i, j])
                if x_val > 0.5:  # Only print participation decisions
                    print(f"Gymnast {i} participates in event {j}")
        
    elif results.solver.termination_condition == pyo.TerminationCondition.infeasible:
        print("Problem is infeasible")
    elif results.solver.termination_condition == pyo.TerminationCondition.unbounded:
        print("Problem is unbounded")
    else:
        print(f"Solver terminated with condition: {results.solver.termination_condition}")
    
    return model

# Execute the optimization
gymnastics_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 141.0
**Execution Time**: 3.51 seconds
**Reliability**: high
**Analysis**: Pyomo achieved the optimal solution with a slightly faster execution time than DOCplex, demonstrating reliability.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 141.00 | 0.96s | N/A | N/A |
| Docplex | OPTIMAL | 141.00 | 4.04s | N/A | N/A |
| Pyomo | OPTIMAL | 141.00 | 3.51s | N/A | N/A |

### Solver Consistency Analysis
**Result**: All solvers produced consistent results ✓
**Consistent Solvers**: gurobipy, docplex, pyomo
**Majority Vote Optimal Value**: 141.0

### Final Recommendation
**Recommended Optimal Value**: 141.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is preferred due to its faster execution time and high reliability, making it suitable for time-sensitive applications.

### Business Interpretation
**Overall Strategy**: The optimal allocation of gymnasts to events maximizes the total points scored, ensuring the best performance outcome.
**Objective Value Meaning**: The optimal objective value of 141.0 represents the maximum achievable points by strategically assigning gymnasts to events.
**Resource Allocation Summary**: Allocate gymnasts to events based on their scoring potential while adhering to participation constraints.
**Implementation Recommendations**: Use Gurobipy for real-time decision-making in event planning, ensuring optimal gymnast assignments and maximizing performance.