# Complete Optimization Problem and Solution: wrestler

## 1. Problem Context and Goals

### Context  
In the context of organizing a wrestling event, the goal is to select a team of wrestlers that maximizes the total number of days their reigns have been held, while ensuring a balanced and competitive match. The selection process involves making binary decisions for each wrestler—whether to include them in the event or not. The operational parameters guiding this decision include the number of days each wrestler has held their reign, the maximum number of wrestlers that can be selected, and the maximum number of eliminations allowed across all selected wrestlers.  

The business configuration sets a limit of six wrestlers that can be selected for the event, ensuring the match remains manageable and competitive. Additionally, the total number of eliminations by the selected wrestlers must not exceed thirty, maintaining a feasible upper limit for competitive dynamics. The selection must also ensure an equal number of wrestlers from Team A and Team B, promoting a balanced team composition.  

### Goals  
The primary optimization goal is to maximize the total number of days the selected wrestlers have held their reigns. This is achieved by selecting wrestlers whose combined days held contribute the most to the overall total, while adhering to the constraints on the number of wrestlers, eliminations, and team balance. Success is measured by the total days held by the selected wrestlers, ensuring the event features wrestlers with the most significant and impactful reigns.  

## 2. Constraints  

The selection process is subject to the following constraints:  
1. **Total Wrestlers Constraint**: The total number of wrestlers selected for the event must not exceed the maximum allowed limit of six. This ensures the event remains manageable and competitive.  
2. **Total Eliminations Constraint**: The total number of eliminations by the selected wrestlers must not exceed the maximum allowed limit of thirty. This maintains a feasible upper limit for competitive dynamics.  
3. **Team Balance Constraint**: The number of wrestlers selected from Team A must equal the number of wrestlers selected from Team B. This ensures a balanced and fair team composition for the event.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for team assignments and eliminations, modifying the wrestler table to include optimization-relevant data, and adding business configuration logic for scalar parameters and formulas.

CREATE TABLE wrestler (
  Days_held INTEGER,
  selection_status BOOLEAN
);

CREATE TABLE wrestler_team (
  wrestler_id INTEGER,
  team STRING
);

CREATE TABLE wrestler_eliminations (
  wrestler_id INTEGER,
  eliminations INTEGER
);
```

### Data Dictionary  
- **wrestler**: Stores wrestler information relevant to optimization.  
  - **Days_held**: The number of days the wrestler's reign has been held. This is used as the coefficient for maximizing the total days held.  
  - **selection_status**: Indicates whether the wrestler is selected for the event. This is the binary decision variable in the optimization process.  
- **wrestler_team**: Contains team assignments for each wrestler.  
  - **wrestler_id**: Unique identifier for the wrestler, linking them to their team.  
  - **team**: The team assignment (Team A or Team B), used to enforce team balance constraints.  
- **wrestler_eliminations**: Tracks the number of eliminations per wrestler.  
  - **wrestler_id**: Unique identifier for the wrestler, linking them to their elimination count.  
  - **eliminations**: The number of eliminations by the wrestler, used to enforce the total eliminations constraint.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic wrestling event scenarios, ensuring a balanced team composition and feasible elimination limits. The number of wrestlers and eliminations were chosen to align with typical event sizes and competitive dynamics.

-- Realistic data for wrestler
INSERT INTO wrestler (Days_held, selection_status) VALUES (150, False);
INSERT INTO wrestler (Days_held, selection_status) VALUES (250, True);
INSERT INTO wrestler (Days_held, selection_status) VALUES (100, False);

-- Realistic data for wrestler_team
INSERT INTO wrestler_team (wrestler_id, team) VALUES (1, 'Team A');
INSERT INTO wrestler_team (wrestler_id, team) VALUES (2, 'Team B');
INSERT INTO wrestler_team (wrestler_id, team) VALUES (3, 'Team A');

-- Realistic data for wrestler_eliminations
INSERT INTO wrestler_eliminations (wrestler_id, eliminations) VALUES (1, 8);
INSERT INTO wrestler_eliminations (wrestler_id, eliminations) VALUES (2, 12);
INSERT INTO wrestler_eliminations (wrestler_id, eliminations) VALUES (3, 5);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_i \) be a binary decision variable indicating whether wrestler \( i \) is selected for the event (\( x_i = 1 \)) or not (\( x_i = 0 \)).

#### Objective Function
Maximize the total number of days held by the selected wrestlers:
\[
\text{Maximize } Z = \sum_{i} \text{Days\_held}_i \times x_i
\]
**Data Source Verification**: The coefficient \( \text{Days\_held}_i \) comes from the `wrestler.Days_held` column.

#### Constraints
1. **Total Wrestlers Constraint**: The total number of selected wrestlers must not exceed six.
\[
\sum_{i} x_i \leq 6
\]
**Data Source Verification**: The constant 6 is derived from the business configuration.

2. **Total Eliminations Constraint**: The total number of eliminations by the selected wrestlers must not exceed thirty.
\[
\sum_{i} \text{eliminations}_i \times x_i \leq 30
\]
**Data Source Verification**: The coefficient \( \text{eliminations}_i \) comes from the `wrestler_eliminations.eliminations` column.

3. **Team Balance Constraint**: The number of wrestlers selected from Team A must equal the number of wrestlers selected from Team B.
\[
\sum_{i \in \text{Team A}} x_i = \sum_{i \in \text{Team B}} x_i
\]
**Data Source Verification**: The team assignments \( \text{Team A} \) and \( \text{Team B} \) come from the `wrestler_team.team` column.

### Complete Mathematical Model
\[
\text{Maximize } Z = \sum_{i} \text{Days\_held}_i \times x_i
\]
Subject to:
\[
\sum_{i} x_i \leq 6
\]
\[
\sum_{i} \text{eliminations}_i \times x_i \leq 30
\]
\[
\sum_{i \in \text{Team A}} x_i = \sum_{i \in \text{Team B}} x_i
\]
\[
x_i \in \{0, 1\} \quad \forall i
\]

This is a complete, immediately solvable Mixed-Integer Linear Programming (MILP) model with all numerical coefficients derived from the provided data.

## 5. Gurobipy Implementation

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

import gurobipy as gp
from gurobipy import GRB

def wrestler_selection_optimization():
    """Optimize wrestler selection to maximize total days held while adhering to constraints."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("wrestler_selection")
    
    # Data from the database
    wrestler_data = [
        {'Days_held': 150, 'selection_status': False},
        {'Days_held': 250, 'selection_status': True},
        {'Days_held': 100, 'selection_status': False}
    ]
    
    wrestler_team_data = [
        {'wrestler_id': 1, 'team': 'Team A'},
        {'wrestler_id': 2, 'team': 'Team B'},
        {'wrestler_id': 3, 'team': 'Team A'}
    ]
    
    wrestler_eliminations_data = [
        {'wrestler_id': 1, 'eliminations': 8},
        {'wrestler_id': 2, 'eliminations': 12},
        {'wrestler_id': 3, 'eliminations': 5}
    ]
    
    # Extract relevant data
    days_held = [w['Days_held'] for w in wrestler_data]
    eliminations = [e['eliminations'] for e in wrestler_eliminations_data]
    team_assignments = [t['team'] for t in wrestler_team_data]
    
    n_wrestlers = len(wrestler_data)
    
    # CRITICAL: Validate array lengths before loops
    assert len(days_held) == len(eliminations) == len(team_assignments) == n_wrestlers, "Array length mismatch"
    
    # 2. VARIABLES
    x = {i: model.addVar(vtype=GRB.BINARY, name=f"x_{i}") for i in range(n_wrestlers)}
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(gp.quicksum(days_held[i] * x[i] for i in range(n_wrestlers)), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Total Wrestlers Constraint
    model.addConstr(gp.quicksum(x[i] for i in range(n_wrestlers)) <= 6, name="total_wrestlers_limit")
    
    # Total Eliminations Constraint
    model.addConstr(gp.quicksum(eliminations[i] * x[i] for i in range(n_wrestlers)) <= 30, name="total_eliminations_limit")
    
    # Team Balance Constraint
    team_a_indices = [i for i, team in enumerate(team_assignments) if team == 'Team A']
    team_b_indices = [i for i, team in enumerate(team_assignments) if team == 'Team B']
    
    model.addConstr(gp.quicksum(x[i] for i in team_a_indices) == gp.quicksum(x[i] for i in team_b_indices), name="team_balance")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in range(n_wrestlers):
            if x[i].x > 1e-6:
                print(f"Wrestler {i+1} selected: {x[i].x:.0f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 400.0
**Execution Time**: 0.19 seconds
**Reliability**: high
**Analysis**: Gurobipy found the optimal solution quickly with the lowest execution time.

## 6. DOCplex Implementation

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

from docplex.mp.model import Model

def wrestler_selection_optimization():
    """Optimize wrestler selection to maximize total days held while adhering to constraints"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="wrestler_selection")
    
    # Data from the provided SQL inserts
    days_held = [150, 250, 100]
    eliminations = [8, 12, 5]
    teams = ['Team A', 'Team B', 'Team A']
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(days_held) == len(eliminations) == len(teams), "Array length mismatch"
    safe_range = range(min(len(days_held), len(eliminations), len(teams)))  # Safe indexing
    
    # 2. VARIABLES
    # Binary decision variables for each wrestler
    x = {i: mdl.binary_var(name=f"x_{i}") for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total days held by selected wrestlers
    objective = mdl.sum(days_held[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Total Wrestlers Constraint: Select at most 6 wrestlers
    total_wrestlers = mdl.sum(x[i] for i in safe_range)
    mdl.add_constraint(total_wrestlers <= 6, ctname="total_wrestlers_limit")
    
    # Total Eliminations Constraint: Total eliminations <= 30
    total_eliminations = mdl.sum(eliminations[i] * x[i] for i in safe_range)
    mdl.add_constraint(total_eliminations <= 30, ctname="total_eliminations_limit")
    
    # Team Balance Constraint: Equal number of wrestlers from Team A and Team B
    team_a = [i for i in safe_range if teams[i] == 'Team A']
    team_b = [i for i in safe_range if teams[i] == 'Team B']
    team_a_selected = mdl.sum(x[i] for i in team_a)
    team_b_selected = mdl.sum(x[i] for i in team_b)
    mdl.add_constraint(team_a_selected == team_b_selected, ctname="team_balance")
    
    # 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:
                print(f"Wrestler {i} selected: Days held = {days_held[i]}, Eliminations = {eliminations[i]}, Team = {teams[i]}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 400.0
**Execution Time**: 1.18 seconds
**Reliability**: high
**Analysis**: DOCplex also found the optimal solution but took longer than Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def wrestler_optimization():
    """Optimization model for selecting wrestlers to maximize total days held."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Wrestler data
    wrestler_data = {
        1: {'Days_held': 150, 'eliminations': 8, 'team': 'Team A'},
        2: {'Days_held': 250, 'eliminations': 12, 'team': 'Team B'},
        3: {'Days_held': 100, 'eliminations': 5, 'team': 'Team A'}
    }
    
    # Business configuration
    max_wrestlers = 6
    max_eliminations = 30
    
    # Validate data consistency
    assert len(wrestler_data) > 0, "No wrestler data provided"
    
    # 3. SETS (Pyomo way to define indices)
    model.I = pyo.Set(initialize=wrestler_data.keys())  # Wrestler IDs
    
    # 4. PARAMETERS (data containers)
    model.Days_held = pyo.Param(model.I, initialize={i: wrestler_data[i]['Days_held'] for i in model.I})
    model.eliminations = pyo.Param(model.I, initialize={i: wrestler_data[i]['eliminations'] for i in model.I})
    model.team = pyo.Param(model.I, initialize={i: wrestler_data[i]['team'] for i in model.I})
    
    # 5. VARIABLES
    model.x = pyo.Var(model.I, within=pyo.Binary)  # Binary decision variable for selection
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.Days_held[i] * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Total Wrestlers Constraint
    def total_wrestlers_rule(model):
        return sum(model.x[i] for i in model.I) <= max_wrestlers
    model.total_wrestlers_constraint = pyo.Constraint(rule=total_wrestlers_rule)
    
    # Total Eliminations Constraint
    def total_eliminations_rule(model):
        return sum(model.eliminations[i] * model.x[i] for i in model.I) <= max_eliminations
    model.total_eliminations_constraint = pyo.Constraint(rule=total_eliminations_rule)
    
    # Team Balance Constraint
    def team_balance_rule(model):
        return sum(model.x[i] for i in model.I if model.team[i] == 'Team A') == \
               sum(model.x[i] for i in model.I if model.team[i] == 'Team B')
    model.team_balance_constraint = pyo.Constraint(rule=team_balance_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(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nSelected Wrestlers:")
        for i in model.I:
            if pyo.value(model.x[i]) > 0.5:  # Only print selected wrestlers
                print(f"Wrestler {i}: Days_held={model.Days_held[i]}, Eliminations={model.eliminations[i]}, Team={model.team[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

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 400.0
**Execution Time**: 2.11 seconds
**Reliability**: medium
**Analysis**: Pyomo found the optimal solution but had the longest execution time.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 400.00 | 0.19s | N/A | N/A |
| Docplex | OPTIMAL | 400.00 | 1.18s | N/A | N/A |
| Pyomo | OPTIMAL | 400.00 | 2.11s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 400.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its fast execution time and high reliability in finding the optimal solution.

### Business Interpretation
**Overall Strategy**: The optimal solution maximizes the total number of days held by the selected wrestlers while adhering to constraints on the number of wrestlers, eliminations, and team balance.
**Objective Value Meaning**: The optimal value of 400 indicates the maximum total number of days held by the selected wrestlers.
**Resource Allocation Summary**: Select up to six wrestlers with a balanced number from Team A and Team B, ensuring the total eliminations do not exceed thirty.
**Implementation Recommendations**: Implement the selection of wrestlers as per the optimal solution to maximize the event's success while adhering to constraints.