# Complete Optimization Problem and Solution: debate

## 1. Problem Context and Goals

### Context  
A debate organization is focused on maximizing audience engagement by strategically assigning participants to debates. Participants come from various districts and political parties, and the organization aims to ensure diversity and fairness in representation. The key operational decision involves assigning participants to specific debates and sides (Affirmative or Negative) while adhering to constraints such as limiting the number of debates each participant can join and ensuring a balance between sides in each debate.  

The organization uses the number of audience members for each debate as a weighting factor to prioritize debates with higher engagement. Additionally, participant age is considered to limit the number of debates they can participate in, ensuring fairness and preventing overcommitment. The business configuration includes scalar parameters such as the typical audience size for debates and a limit on the number of debates a participant can join. These parameters are critical for weighting the objective function and enforcing constraints.  

### Goals  
The primary goal is to maximize total audience engagement across all debates. This is achieved by assigning participants to debates in a way that prioritizes debates with larger audiences. Success is measured by the weighted sum of audience sizes for the debates to which participants are assigned. The optimization process ensures that the assignments respect constraints on participant availability, side balance, and age-based limitations.  

## 2. Constraints  

The optimization problem must adhere to the following constraints:  
1. **Participant Limit**: Each participant can be assigned to no more than one debate. This ensures fairness and prevents overcommitment.  
2. **Side Balance**: For each debate, the number of participants assigned to the Affirmative side must equal the number assigned to the Negative side. This ensures fairness and balance in the debate structure.  
3. **Minimum Participation**: Each debate must have at least one participant assigned to it. This ensures that all debates are conducted.  
4. **Age-Based Constraint**: The number of debates a participant can join is limited by their age, specifically by dividing their age by 25. This ensures that younger participants are not overburdened.  

These constraints are designed to maintain fairness, balance, and operational feasibility while aligning with the linear structure of the optimization problem.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 2 Database Schema
-- Objective: Schema changes include creating a new table for audience size, updating business configuration logic to include missing scalar parameters, and ensuring all optimization requirements are mapped correctly.

CREATE TABLE participant_assignment (
  Debate_ID INTEGER,
  People_ID INTEGER,
  Side STRING
);

CREATE TABLE people (
  People_ID INTEGER,
  District STRING,
  Party STRING,
  Age INTEGER
);

CREATE TABLE audience_size (
  Debate_ID INTEGER,
  Num_of_Audience INTEGER
);
```

### Data Dictionary  
- **participant_assignment**: Represents the assignment of participants to debates on specific sides.  
  - *Debate_ID*: Identifier for the debate.  
  - *People_ID*: Identifier for the participant.  
  - *Side*: The side (Affirmative/Negative) the participant is assigned to.  
- **people**: Represents the participants in the debates.  
  - *People_ID*: Identifier for the participant.  
  - *District*: The district the participant represents.  
  - *Party*: The party the participant represents.  
  - *Age*: The age of the participant.  
- **audience_size**: Represents the number of audience members for each debate.  
  - *Debate_ID*: Identifier for the debate.  
  - *Num_of_Audience*: Number of audience members for the debate.  

### Current Stored Values  
```sql
-- Iteration 2 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic scenarios for a debate organization, considering audience sizes, participant demographics, and debate constraints.

-- Realistic data for participant_assignment
INSERT INTO participant_assignment (Debate_ID, People_ID, Side) VALUES (1, 101, 'Affirmative');
INSERT INTO participant_assignment (Debate_ID, People_ID, Side) VALUES (1, 102, 'Negative');
INSERT INTO participant_assignment (Debate_ID, People_ID, Side) VALUES (2, 103, 'Affirmative');

-- Realistic data for people
INSERT INTO people (People_ID, District, Party, Age) VALUES (101, 'District A', 'Party X', 25);
INSERT INTO people (People_ID, District, Party, Age) VALUES (102, 'District B', 'Party Y', 30);
INSERT INTO people (People_ID, District, Party, Age) VALUES (103, 'District C', 'Party Z', 35);

-- Realistic data for audience_size
INSERT INTO audience_size (Debate_ID, Num_of_Audience) VALUES (1, 150);
INSERT INTO audience_size (Debate_ID, Num_of_Audience) VALUES (2, 200);
INSERT INTO audience_size (Debate_ID, Num_of_Audience) VALUES (3, 100);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- \( x_{i,j,k} \): Binary variable indicating whether participant \( i \) is assigned to debate \( j \) on side \( k \).  
  - \( i \in \{101, 102, 103\} \) (People_ID)  
  - \( j \in \{1, 2, 3\} \) (Debate_ID)  
  - \( k \in \{\text{Affirmative}, \text{Negative}\} \) (Side)  

#### Objective Function
Maximize the total audience engagement:  
\[
\text{Maximize } Z = \sum_{i} \sum_{j} \sum_{k} \text{Num_of_Audience}_j \cdot x_{i,j,k}
\]  
Data Source Verification:  
- \( \text{Num_of_Audience}_j \) comes from `audience_size.Num_of_Audience`.

#### Constraints
1. **Participant Limit**: Each participant can be assigned to no more than one debate.  
\[
\sum_{j} \sum_{k} x_{i,j,k} \leq 1 \quad \forall i
\]  
Data Source Verification:  
- \( i \) comes from `people.People_ID`.

2. **Side Balance**: For each debate, the number of participants assigned to the Affirmative side must equal the number assigned to the Negative side.  
\[
\sum_{i} x_{i,j,\text{Affirmative}} = \sum_{i} x_{i,j,\text{Negative}} \quad \forall j
\]  
Data Source Verification:  
- \( j \) comes from `participant_assignment.Debate_ID`.

3. **Minimum Participation**: Each debate must have at least one participant assigned to it.  
\[
\sum_{i} \sum_{k} x_{i,j,k} \geq 1 \quad \forall j
\]  
Data Source Verification:  
- \( j \) comes from `participant_assignment.Debate_ID`.

4. **Age-Based Constraint**: The number of debates a participant can join is limited by their age, specifically by dividing their age by 25.  
\[
\sum_{j} \sum_{k} x_{i,j,k} \leq \left\lfloor \frac{\text{Age}_i}{25} \right\rfloor \quad \forall i
\]  
Data Source Verification:  
- \( \text{Age}_i \) comes from `people.Age`.

#### Data Source Verification
- \( \text{Num_of_Audience}_j \): `audience_size.Num_of_Audience`  
- \( i \): `people.People_ID`  
- \( j \): `participant_assignment.Debate_ID`  
- \( \text{Age}_i \): `people.Age`  

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

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation - Retry Attempt 4

import gurobipy as gp
from gurobipy import GRB

def optimize_debate_assignments():
    # 1. MODEL & DATA SETUP
    model = gp.Model("DebateAssignmentOptimization")
    
    # Data from the provided SQL schema and realistic data
    participants = [101, 102, 103]
    debates = [1, 2, 3]
    sides = ['Affirmative', 'Negative']
    
    audience_sizes = {1: 150, 2: 200, 3: 100}
    ages = {101: 25, 102: 30, 103: 35}
    
    # CRITICAL: Validate array lengths before loops
    assert len(participants) == len(ages), "Participants and ages length mismatch"
    assert len(debates) == len(audience_sizes), "Debates and audience sizes length mismatch"
    
    # 2. VARIABLES
    x = model.addVars(participants, debates, sides, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(
        gp.quicksum(audience_sizes[j] * x[i, j, k] for i in participants for j in debates for k in sides),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Participant Limit: Each participant can be assigned to no more than one debate
    for i in participants:
        model.addConstr(
            gp.quicksum(x[i, j, k] for j in debates for k in sides) <= 1,
            name=f"participant_limit_{i}"
        )
    
    # Side Balance: For each debate, the number of participants assigned to the Affirmative side must equal the number assigned to the Negative side
    for j in debates:
        model.addConstr(
            gp.quicksum(x[i, j, 'Affirmative'] for i in participants) ==
            gp.quicksum(x[i, j, 'Negative'] for i in participants),
            name=f"side_balance_{j}"
        )
    
    # Minimum Participation: Each debate must have at least one participant assigned to it
    for j in debates:
        model.addConstr(
            gp.quicksum(x[i, j, k] for i in participants for k in sides) >= 1,
            name=f"min_participation_{j}"
        )
    
    # Age-Based Constraint: The number of debates a participant can join is limited by their age, specifically by dividing their age by 25
    for i in participants:
        model.addConstr(
            gp.quicksum(x[i, j, k] for j in debates for k in sides) <= ages[i] // 25,
            name=f"age_constraint_{i}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in participants:
            for j in debates:
                for k in sides:
                    if x[i, j, k].x > 1e-6:
                        print(f"Participant {i} assigned to Debate {j} on {k} side")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
optimize_debate_assignments()
```

### Execution Results
**Status**: INFEASIBLE
**Error**: Problem is infeasible
**Analysis**: Gurobipy identified the problem as infeasible, indicating that no solution satisfies all constraints. This is a reliable result given Gurobipy's robust algorithms.

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation - Retry Attempt 4

from docplex.mp.model import Model

def optimize_debate_assignments():
    # 1. MODEL & DATA SETUP
    mdl = Model(name="Debate_Assignment_Optimization")
    
    # Data from the database
    participants = [101, 102, 103]
    debates = [1, 2, 3]
    sides = ['Affirmative', 'Negative']
    
    audience_sizes = {1: 150, 2: 200, 3: 100}
    ages = {101: 25, 102: 30, 103: 35}
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(participants) == len(ages), "Participants and ages length mismatch"
    assert len(debates) == len(audience_sizes), "Debates and audience sizes length mismatch"
    
    # 2. VARIABLES
    x = {(i, j, k): mdl.binary_var(name=f"x_{i}_{j}_{k}") 
         for i in participants 
         for j in debates 
         for k in sides}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(audience_sizes[j] * x[i, j, k] 
                        for i in participants 
                        for j in debates 
                        for k in sides)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Participant Limit: Each participant can be assigned to no more than one debate
    for i in participants:
        mdl.add_constraint(mdl.sum(x[i, j, k] for j in debates for k in sides) <= 1, 
                           ctname=f"participant_limit_{i}")
    
    # Side Balance: For each debate, the number of participants assigned to the Affirmative side must equal the number assigned to the Negative side
    for j in debates:
        mdl.add_constraint(mdl.sum(x[i, j, 'Affirmative'] for i in participants) == 
                           mdl.sum(x[i, j, 'Negative'] for i in participants), 
                           ctname=f"side_balance_{j}")
    
    # Minimum Participation: Each debate must have at least one participant assigned to it
    for j in debates:
        mdl.add_constraint(mdl.sum(x[i, j, k] for i in participants for k in sides) >= 1, 
                           ctname=f"min_participation_{j}")
    
    # Age-Based Constraint: The number of debates a participant can join is limited by their age, specifically by dividing their age by 25
    for i in participants:
        mdl.add_constraint(mdl.sum(x[i, j, k] for j in debates for k in sides) <= (ages[i] // 25), 
                           ctname=f"age_constraint_{i}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in participants:
            for j in debates:
                for k in sides:
                    if solution.get_value(x[i, j, k]) > 0:
                        print(f"Participant {i} assigned to Debate {j} on {k} side")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Execute the optimization
optimize_debate_assignments()
```

### Execution Results
**Status**: INFEASIBLE
**Error**: Problem is infeasible
**Analysis**: DOCplex also concluded that the problem is infeasible, aligning with Gurobipy's result. This consistency reinforces the reliability of the infeasibility conclusion.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation - Retry Attempt 4

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

def optimize_debate_assignments():
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Participants
    participants = [101, 102, 103]
    # Debates
    debates = [1, 2, 3]
    # Sides
    sides = ['Affirmative', 'Negative']
    
    # Audience sizes
    audience_sizes = {1: 150, 2: 200, 3: 100}
    # Participant ages
    ages = {101: 25, 102: 30, 103: 35}
    
    # CRITICAL: Validate array lengths before indexing
    assert len(participants) == len(ages), "Array length mismatch"
    assert len(debates) == len(audience_sizes), "Array length mismatch"
    
    # 3. SETS
    model.P = pyo.Set(initialize=participants)  # Participants
    model.D = pyo.Set(initialize=debates)      # Debates
    model.S = pyo.Set(initialize=sides)        # Sides
    
    # 4. PARAMETERS
    model.audience_size = pyo.Param(model.D, initialize=audience_sizes)
    model.age = pyo.Param(model.P, initialize=ages)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.P, model.D, model.S, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.audience_size[j] * model.x[i, j, k] for i in model.P for j in model.D for k in model.S)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Participant Limit: Each participant can be assigned to no more than one debate
    def participant_limit_rule(model, i):
        return sum(model.x[i, j, k] for j in model.D for k in model.S) <= 1
    model.participant_limit = pyo.Constraint(model.P, rule=participant_limit_rule)
    
    # Side Balance: For each debate, the number of participants assigned to the Affirmative side must equal the number assigned to the Negative side
    def side_balance_rule(model, j):
        return sum(model.x[i, j, 'Affirmative'] for i in model.P) == sum(model.x[i, j, 'Negative'] for i in model.P)
    model.side_balance = pyo.Constraint(model.D, rule=side_balance_rule)
    
    # Minimum Participation: Each debate must have at least one participant assigned to it
    def min_participation_rule(model, j):
        return sum(model.x[i, j, k] for i in model.P for k in model.S) >= 1
    model.min_participation = pyo.Constraint(model.D, rule=min_participation_rule)
    
    # Age-Based Constraint: The number of debates a participant can join is limited by their age, specifically by dividing their age by 25
    def age_constraint_rule(model, i):
        return sum(model.x[i, j, k] for j in model.D for k in model.S) <= model.age[i] // 25
    model.age_constraint = pyo.Constraint(model.P, rule=age_constraint_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("\nVariable values:")
        for i in model.P:
            for j in model.D:
                for k in model.S:
                    if pyo.value(model.x[i, j, k]) > 1e-6:  # Only print non-zero values
                        print(f"x[{i}, {j}, {k}] = {pyo.value(model.x[i, j, k])}")
        
    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__":
    optimize_debate_assignments()
```

### Execution Results
**Status**: INFEASIBLE
**Error**: Problem is infeasible
**Analysis**: Pyomo reported the problem as infeasible, consistent with the other solvers. However, Pyomo's reliability is slightly lower due to its dependency on external solvers and potential configuration issues.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | INFEASIBLE | N/A | 0.18s | N/A | 4 |
| Docplex | INFEASIBLE | N/A | 1.02s | N/A | 4 |
| Pyomo | INFEASIBLE | N/A | 0.99s | N/A | 4 |

### Solver Consistency Analysis
**Result**: All solvers produced consistent results ✓
**Consistent Solvers**: gurobipy, docplex, pyomo
**Solver Retry Summary**: gurobipy: 4 attempts, docplex: 4 attempts, pyomo: 4 attempts

### Final Recommendation
**Confidence Level**: HIGH
**Preferred Solver(s)**: multiple
**Reasoning**: All three solvers consistently reported infeasibility, providing high confidence in the result. Using multiple solvers ensures robustness and reduces the likelihood of solver-specific errors.

### Business Interpretation
**Overall Strategy**: The problem is infeasible, meaning the current constraints and data do not allow for a valid assignment of participants to debates. This could lead to operational challenges in organizing the debates.
**Objective Value Meaning**: The objective value represents total audience engagement, which cannot be maximized due to the infeasibility of the problem.
**Resource Allocation Summary**: No valid resource allocation is possible under the current constraints and data.
**Implementation Recommendations**: ['Review and relax constraints to make the problem feasible.', 'Verify and update data inputs (e.g., audience size, participant ages) to ensure consistency with constraints.', 'Consider alternative formulations or additional constraints to resolve conflicts.']