# Complete Optimization Problem and Solution: soccer_2

## 1. Problem Context and Goals

### Context  
A college soccer team is preparing for tryouts to select players for the upcoming season. The goal is to maximize the overall skill level of the selected players while adhering to specific constraints on the number of players per position and the total number of players. The decision-making process involves determining whether each player should be selected for tryouts, represented by a binary decision variable. The skill level of each player is a key factor in this decision, as it directly contributes to the overall quality of the team.  

The team must respect the following operational limits:  
- **Total Players Limit**: No more than 20 players can be selected for tryouts, ensuring a manageable team size.  
- **Forwards Limit**: A maximum of 5 forwards can be selected to maintain a balanced offensive lineup.  
- **Midfielders Limit**: Up to 7 midfielders can be chosen to cover various roles in the midfield.  
- **Defenders Limit**: No more than 6 defenders can be selected to ensure a strong defensive setup.  
- **Goalkeepers Limit**: A maximum of 2 goalkeepers can be chosen, which is standard for tryouts.  

These limits are based on typical college soccer team sizes and position distributions, ensuring realistic and practical constraints. The decision to select a player is directly tied to their skill level, and the overall objective is to maximize the sum of the skill levels of the selected players.

### Goals  
The primary goal of this optimization problem is to maximize the total skill level of the players selected for tryouts. This is achieved by selecting players whose combined skill levels are as high as possible, while ensuring that the selection adheres to the constraints on the number of players per position and the total number of players. Success is measured by the overall skill level of the selected team, which directly impacts the team's performance potential.  

## 2. Constraints  

The selection of players for tryouts is subject to the following constraints:  
1. **Total Players Constraint**: The total number of players selected for tryouts cannot exceed 20. This ensures the team size remains manageable and within operational limits.  
2. **Forwards Constraint**: The number of forwards selected must not exceed 5. This maintains a balanced offensive lineup and prevents overstaffing in this position.  
3. **Midfielders Constraint**: The number of midfielders selected must not exceed 7. This ensures sufficient coverage of various midfield roles without overloading the team.  
4. **Defenders Constraint**: The number of defenders selected must not exceed 6. This ensures a strong defensive setup while avoiding redundancy.  
5. **Goalkeepers Constraint**: The number of goalkeepers selected must not exceed 2. This aligns with standard practices for tryouts and ensures adequate coverage for this specialized position.  

These constraints are designed to ensure a balanced and effective team composition while respecting operational and practical limitations.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating tables for player skills and position limits, and moving scalar parameters to business configuration logic.

CREATE TABLE player_skills (
  player_id INTEGER,
  skill_level FLOAT,
  select BOOLEAN
);
```

### Data Dictionary  
The **player_skills** table contains the following columns, each with a specific business purpose and optimization role:  
- **player_id**: A unique identifier for each player. This serves as the index for decision variables in the optimization model.  
- **skill_level**: Represents the skill level of the player. This value is used as the coefficient in the objective function, directly contributing to the total skill level of the selected team.  
- **select**: A binary indicator of whether the player is selected for tryouts. This is the decision variable in the optimization model, determining the final team composition.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical college soccer team sizes and position distributions, ensuring realistic constraints and skill levels.

-- Realistic data for player_skills
INSERT INTO player_skills (player_id, skill_level, select) VALUES (1, 8.5, False);
INSERT INTO player_skills (player_id, skill_level, select) VALUES (2, 7.2, False);
INSERT INTO player_skills (player_id, skill_level, select) VALUES (3, 9.0, False);
INSERT INTO player_skills (player_id, skill_level, select) VALUES (4, 6.8, False);
INSERT INTO player_skills (player_id, skill_level, select) VALUES (5, 7.9, False);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_i \) be a binary decision variable where:
  - \( x_i = 1 \) if player \( i \) is selected for tryouts.
  - \( x_i = 0 \) if player \( i \) is not selected for tryouts.

#### Objective Function
Maximize the total skill level of the selected players:
\[
\text{Maximize } Z = \sum_{i} \text{skill_level}_i \times x_i
\]
where \( \text{skill_level}_i \) is the skill level of player \( i \).

#### Constraints
1. **Total Players Constraint**:
\[
\sum_{i} x_i \leq 20
\]
2. **Forwards Constraint**:
\[
\sum_{i \in \text{Forwards}} x_i \leq 5
\]
3. **Midfielders Constraint**:
\[
\sum_{i \in \text{Midfielders}} x_i \leq 7
\]
4. **Defenders Constraint**:
\[
\sum_{i \in \text{Defenders}} x_i \leq 6
\]
5. **Goalkeepers Constraint**:
\[
\sum_{i \in \text{Goalkeepers}} x_i \leq 2
\]

#### Data Source Verification
- **skill_level_i**: The skill level of player \( i \) is obtained from the `player_skills.skill_level` column.
- **Total Players Limit**: The constant 20 is derived from the business configuration for the total number of players allowed.
- **Forwards Limit**: The constant 5 is derived from the business configuration for the maximum number of forwards allowed.
- **Midfielders Limit**: The constant 7 is derived from the business configuration for the maximum number of midfielders allowed.
- **Defenders Limit**: The constant 6 is derived from the business configuration for the maximum number of defenders allowed.
- **Goalkeepers Limit**: The constant 2 is derived from the business configuration for the maximum number of goalkeepers allowed.

This formulation provides a complete, immediately solvable LINEAR mathematical model with all numerical coefficients derived from the provided data.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Soccer Team Selection Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_soccer_selection():
    """Optimize soccer team selection based on skill levels and position constraints."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("soccer_selection")
    
    # Example data (replace with actual data from database)
    player_ids = [1, 2, 3, 4, 5]
    skill_levels = [8.5, 7.2, 9.0, 6.8, 7.9]
    positions = ['Forward', 'Midfielder', 'Forward', 'Defender', 'Goalkeeper']
    
    # Position limits
    total_players_limit = 20
    forwards_limit = 5
    midfielders_limit = 7
    defenders_limit = 6
    goalkeepers_limit = 2
    
    # CRITICAL: Validate array lengths before loops
    assert len(player_ids) == len(skill_levels) == len(positions), "Array length mismatch"
    
    # 2. VARIABLES
    # Binary decision variables for each player
    x = {i: model.addVar(vtype=GRB.BINARY, name=f"x_{player_ids[i]}") 
         for i in range(len(player_ids))}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total skill level of selected players
    model.setObjective(gp.quicksum(skill_levels[i] * x[i] for i in range(len(player_ids))), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Total Players Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(len(player_ids))) <= total_players_limit, name="total_players_limit")
    
    # Forwards Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(len(player_ids)) if positions[i] == 'Forward') <= forwards_limit, name="forwards_limit")
    
    # Midfielders Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(len(player_ids)) if positions[i] == 'Midfielder') <= midfielders_limit, name="midfielders_limit")
    
    # Defenders Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(len(player_ids)) if positions[i] == 'Defender') <= defenders_limit, name="defenders_limit")
    
    # Goalkeepers Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(len(player_ids)) if positions[i] == 'Goalkeeper') <= goalkeepers_limit, name="goalkeepers_limit")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in range(len(player_ids)):
            if x[i].x > 0.5:
                print(f"Player {player_ids[i]} selected with skill level {skill_levels[i]}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
if __name__ == "__main__":
    optimize_soccer_selection()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 39.4
**Execution Time**: 0.19 seconds
**Reliability**: high
**Analysis**: Gurobipy achieved the optimal solution efficiently with the fastest execution time.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCPLEX implementation for soccer team selection optimization problem.
"""

from docplex.mp.model import Model

def soccer_team_selection():
    """Optimize soccer team selection to maximize total skill level."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="soccer_team_selection")
    
    # Player data
    player_ids = [1, 2, 3, 4, 5]
    skill_levels = [8.5, 7.2, 9.0, 6.8, 7.9]
    positions = ['Forward', 'Midfielder', 'Defender', 'Goalkeeper', 'Midfielder']  # Example positions
    
    # Position limits
    total_players_limit = 20
    forwards_limit = 5
    midfielders_limit = 7
    defenders_limit = 6
    goalkeepers_limit = 2
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(player_ids) == len(skill_levels) == len(positions), "Array length mismatch"
    safe_range = range(min(len(player_ids), len(skill_levels), len(positions)))  # Safe indexing
    
    # 2. VARIABLES
    # Binary decision variables for player selection
    x = {i: mdl.binary_var(name=f"x_{player_ids[i]}") for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total skill level of selected players
    objective = mdl.sum(skill_levels[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Total Players Constraint
    total_players = mdl.sum(x[i] for i in safe_range)
    mdl.add_constraint(total_players <= total_players_limit, ctname="total_players_limit")
    
    # Position Constraints
    forwards = mdl.sum(x[i] for i in safe_range if positions[i] == 'Forward')
    mdl.add_constraint(forwards <= forwards_limit, ctname="forwards_limit")
    
    midfielders = mdl.sum(x[i] for i in safe_range if positions[i] == 'Midfielder')
    mdl.add_constraint(midfielders <= midfielders_limit, ctname="midfielders_limit")
    
    defenders = mdl.sum(x[i] for i in safe_range if positions[i] == 'Defender')
    mdl.add_constraint(defenders <= defenders_limit, ctname="defenders_limit")
    
    goalkeepers = mdl.sum(x[i] for i in safe_range if positions[i] == 'Goalkeeper')
    mdl.add_constraint(goalkeepers <= goalkeepers_limit, ctname="goalkeepers_limit")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_range:
            if solution.get_value(x[i]) > 0.5:  # Selected players
                print(f"Player {player_ids[i]} (Position: {positions[i]}, Skill: {skill_levels[i]}) selected")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Execute the optimization
if __name__ == "__main__":
    soccer_team_selection()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 39.4
**Execution Time**: 1.45 seconds
**Reliability**: high
**Analysis**: DOCplex also achieved the optimal solution but with a longer execution time compared to Gurobipy.

## 7. Pyomo Implementation

```python
#!/usr/bin/env python3
"""
Pyomo 6.9.2 Implementation for Soccer Team Selection Optimization
"""

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

def soccer_team_selection():
    """Optimization model for selecting soccer players for tryouts"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Example data for player skills and positions
    player_ids = [1, 2, 3, 4, 5]
    skill_levels = [8.5, 7.2, 9.0, 6.8, 7.9]
    positions = ['Forward', 'Midfielder', 'Defender', 'Goalkeeper', 'Midfielder']
    
    # Validate array lengths before indexing
    assert len(player_ids) == len(skill_levels) == len(positions), "Array length mismatch"
    
    # 3. SETS (Pyomo way to define indices)
    model.I = pyo.Set(initialize=player_ids)  # Set of players
    
    # 4. PARAMETERS (data containers)
    model.skill_level = pyo.Param(model.I, initialize={i: skill_levels[idx] for idx, i in enumerate(player_ids)})
    model.position = pyo.Param(model.I, initialize={i: positions[idx] for idx, i in enumerate(player_ids)})
    
    # 5. VARIABLES
    # Binary decision variable for player selection
    model.x = pyo.Var(model.I, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    # Maximize the total skill level of selected players
    def obj_rule(model):
        return sum(model.skill_level[i] * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Total Players Constraint
    def total_players_rule(model):
        return sum(model.x[i] for i in model.I) <= 20
    model.total_players_constraint = pyo.Constraint(rule=total_players_rule)
    
    # Forwards Constraint
    def forwards_rule(model):
        return sum(model.x[i] for i in model.I if model.position[i] == 'Forward') <= 5
    model.forwards_constraint = pyo.Constraint(rule=forwards_rule)
    
    # Midfielders Constraint
    def midfielders_rule(model):
        return sum(model.x[i] for i in model.I if model.position[i] == 'Midfielder') <= 7
    model.midfielders_constraint = pyo.Constraint(rule=midfielders_rule)
    
    # Defenders Constraint
    def defenders_rule(model):
        return sum(model.x[i] for i in model.I if model.position[i] == 'Defender') <= 6
    model.defenders_constraint = pyo.Constraint(rule=defenders_rule)
    
    # Goalkeepers Constraint
    def goalkeepers_rule(model):
        return sum(model.x[i] for i in model.I if model.position[i] == 'Goalkeeper') <= 2
    model.goalkeepers_constraint = pyo.Constraint(rule=goalkeepers_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("\nSelected Players:")
        for i in model.I:
            if pyo.value(model.x[i]) > 0.5:  # Only print selected players
                print(f"Player {i} (Position: {model.position[i]}, Skill Level: {model.skill_level[i]})")
        
    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

# Run the optimization
if __name__ == "__main__":
    soccer_team_selection()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 39.4
**Execution Time**: 1.14 seconds
**Reliability**: high
**Analysis**: Pyomo achieved the optimal solution with an execution time between Gurobipy and DOCplex.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 39.40 | 0.19s | N/A | N/A |
| Docplex | OPTIMAL | 39.40 | 1.45s | N/A | N/A |
| Pyomo | OPTIMAL | 39.40 | 1.14s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 39.4
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its fastest execution time while still achieving the optimal solution.

### Business Interpretation
**Overall Strategy**: The optimal solution suggests selecting a combination of players that maximizes the total skill level while adhering to the constraints on the number of players in each position.
**Objective Value Meaning**: The optimal total skill level of 39.4 indicates the highest possible combined skill level achievable under the given constraints.
**Resource Allocation Summary**: Allocate resources to select up to 20 players, with no more than 5 forwards, 7 midfielders, 6 defenders, and 2 goalkeepers.
**Implementation Recommendations**: Proceed with the player selection as per the optimal solution, ensuring that the constraints on the number of players in each position are strictly followed.