# Complete Optimization Problem and Solution: voter_1

## 1. Problem Context and Goals

### Context  
In a competitive voting scenario, contestants receive votes from various states. The primary business decision involves determining the number of votes each contestant receives from each state. The operational objective is to maximize the total number of votes received by all contestants. This is achieved by ensuring that each contestant receives a minimum number of votes from each state, while also adhering to a cap on the total number of votes a contestant can receive. The business configuration includes two critical parameters: the maximum number of votes a contestant can receive, and the minimum number of votes each contestant must receive from each state. These parameters are essential for maintaining a fair and competitive environment, ensuring that contestants have a baseline level of support across all states.

### Goals  
The optimization goal is to maximize the total number of votes received by all contestants. The metric for optimization is the sum of votes received by each contestant from all states. Success is measured by achieving the highest possible total vote count while respecting the constraints set by the business configuration parameters. The focus is on maximizing the total votes, ensuring that the optimization process aligns with the linear objective of increasing the overall vote tally.

## 2. Constraints    

The constraints are designed to ensure fair competition and adherence to business rules. Each contestant's total votes from all states must not exceed the maximum number of votes allowed per contestant. Additionally, each contestant must receive at least a minimum number of votes from each state. These constraints are critical for maintaining a balanced distribution of votes and ensuring that each contestant has a fair chance of success. The constraints are linear in nature, focusing on the sum of votes and ensuring minimum thresholds are met.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for missing optimization requirements and updating business configuration logic for scalar parameters and formulas.

CREATE TABLE CONTESTANT_VOTES (
  contestant_id INTEGER,
  state_id INTEGER,
  votes INTEGER
);
```

### Data Dictionary  
The "CONTESTANT_VOTES" table serves a crucial role in the voting competition. It records the number of votes each contestant receives from each state. The table includes the following columns:

- **Contestant ID**: This is a unique identifier for each contestant, allowing the system to track votes on an individual basis.
- **State ID**: This identifier represents each state, ensuring that votes are accurately attributed to the correct geographical location.
- **Votes**: This column captures the number of votes a contestant receives from a specific state, serving as the decision variable in the optimization model.

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical voting patterns and constraints to ensure a balanced distribution of votes across contestants and states.

-- Realistic data for CONTESTANT_VOTES
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (1, 1, 120);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (1, 2, 80);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (1, 3, 100);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (2, 1, 90);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (2, 2, 110);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (2, 3, 95);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (3, 1, 85);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (3, 2, 105);
INSERT INTO CONTESTANT_VOTES (contestant_id, state_id, votes) VALUES (3, 3, 115);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{i,j} \) be the number of votes received by contestant \( i \) from state \( j \).
- \( x_{i,j} \) are integer decision variables, as votes must be whole numbers.

#### Objective Function
Maximize the total number of votes received by all contestants:
\[ \text{Maximize } \sum_{i} \sum_{j} x_{i,j} \]

#### Constraints
1. **Maximum Votes per Contestant**: Each contestant's total votes from all states must not exceed the maximum number of votes allowed per contestant.
   \[
   \sum_{j} x_{i,j} \leq \text{MaxVotes}_i \quad \forall i
   \]
   - Data Source Verification: MaxVotes is a business configuration parameter.

2. **Minimum Votes per State**: Each contestant must receive at least a minimum number of votes from each state.
   \[
   x_{i,j} \geq \text{MinVotes}_{i,j} \quad \forall i, j
   \]
   - Data Source Verification: MinVotes is a business configuration parameter.

3. **Non-negativity and Integer Constraints**: Votes must be non-negative integers.
   \[
   x_{i,j} \geq 0 \quad \text{and integer} \quad \forall i, j
   \]

Data Source Verification:
- The decision variables \( x_{i,j} \) correspond to the "votes" column in the `CONTESTANT_VOTES` table.
- The constraints use parameters MaxVotes and MinVotes, which are assumed to be defined in the business configuration, though not explicitly provided in the data.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_votes():
    """Optimize the distribution of votes among contestants and states."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("voter_1")
    
    # Example data setup
    contestants = [1, 2, 3]
    states = [1, 2, 3]
    
    # Parameters (these would typically come from business configuration)
    MaxVotes = {1: 300, 2: 300, 3: 300}  # Maximum votes per contestant
    MinVotes = {(1, 1): 50, (1, 2): 50, (1, 3): 50,
                (2, 1): 50, (2, 2): 50, (2, 3): 50,
                (3, 1): 50, (3, 2): 50, (3, 3): 50}  # Minimum votes per state
    
    # CRITICAL: Validate array lengths before loops
    assert len(contestants) > 0 and len(states) > 0, "Contestants or states list is empty"
    
    # 2. VARIABLES
    # Decision variables: votes received by contestant i from state j
    x = model.addVars(contestants, states, vtype=GRB.INTEGER, name="x", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total number of votes received by all contestants
    model.setObjective(gp.quicksum(x[i, j] for i in contestants for j in states), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Maximum Votes per Contestant
    model.addConstrs((gp.quicksum(x[i, j] for j in states) <= MaxVotes[i] for i in contestants), name="max_votes")
    
    # Minimum Votes per State
    model.addConstrs((x[i, j] >= MinVotes[i, j] for i in contestants for j in states), name="min_votes")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in contestants:
            for j in states:
                if x[i, j].x > 1e-6:
                    print(f"x[{i},{j}] = {x[i, j].x:.3f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_votes()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 900.0
**Execution Time**: 0.19 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 voter_optimization():
    """Optimization model for maximizing votes for contestants"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="voter_optimization")
    
    # Example data setup
    contestants = [1, 2, 3]
    states = [1, 2, 3]
    
    # Maximum votes per contestant (example values)
    max_votes = {1: 300, 2: 295, 3: 305}
    
    # Minimum votes per contestant per state (example values)
    min_votes = {
        (1, 1): 50, (1, 2): 40, (1, 3): 30,
        (2, 1): 45, (2, 2): 35, (2, 3): 25,
        (3, 1): 55, (3, 2): 45, (3, 3): 35
    }
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(contestants) == len(max_votes), "Array length mismatch for contestants and max_votes"
    assert len(states) * len(contestants) == len(min_votes), "Array length mismatch for min_votes"
    
    # 2. VARIABLES
    # Variable dictionaries for votes
    votes = {(i, j): mdl.integer_var(name=f"x_{i}_{j}", lb=0) for i in contestants for j in states}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total votes
    objective = mdl.sum(votes[i, j] for i in contestants for j in states)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Maximum votes per contestant
    for i in contestants:
        mdl.add_constraint(mdl.sum(votes[i, j] for j in states) <= max_votes[i], ctname=f"max_votes_{i}")
    
    # Minimum votes per contestant per state
    for i in contestants:
        for j in states:
            mdl.add_constraint(votes[i, j] >= min_votes[i, j], ctname=f"min_votes_{i}_{j}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in contestants:
            for j in states:
                value = solution.get_value(votes[i, j])
                if value > 1e-6:
                    print(f"Votes for contestant {i} from state {j}: {value:.0f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
voter_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 900.0
**Execution Time**: 2.15 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution, though 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 voting_optimization():
    """Optimize the distribution of votes among contestants"""

    # 1. MODEL CREATION
    model = pyo.ConcreteModel()

    # 2. DATA SETUP
    # Example data for demonstration purposes
    contestants = [1, 2, 3]
    states = [1, 2, 3]
    max_votes = {1: 300, 2: 295, 3: 305}  # Maximum votes per contestant
    min_votes = {(1, 1): 50, (1, 2): 40, (1, 3): 60,
                 (2, 1): 45, (2, 2): 55, (2, 3): 50,
                 (3, 1): 40, (3, 2): 50, (3, 3): 55}  # Minimum votes per state

    # 3. SETS
    model.I = pyo.Set(initialize=contestants)
    model.J = pyo.Set(initialize=states)

    # 4. PARAMETERS
    model.MaxVotes = pyo.Param(model.I, initialize=max_votes)
    model.MinVotes = pyo.Param(model.I, model.J, initialize=min_votes)

    # 5. VARIABLES
    model.x = pyo.Var(model.I, model.J, within=pyo.NonNegativeIntegers)

    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(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 Votes per Contestant
    def max_votes_rule(model, i):
        return sum(model.x[i, j] for j in model.J) <= model.MaxVotes[i]
    model.max_votes_constraint = pyo.Constraint(model.I, rule=max_votes_rule)

    # Minimum Votes per State
    def min_votes_rule(model, i, j):
        return model.x[i, j] >= model.MinVotes[i, j]
    model.min_votes_constraint = pyo.Constraint(model.I, model.J, rule=min_votes_rule)

    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')

    # Solve the model
    results = solver.solve(model, tee=True)

    # 9. RESULT PROCESSING
    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.I:
            for j in model.J:
                x_val = pyo.value(model.x[i, j])
                if x_val > 1e-6:  # Only print non-zero values
                    print(f"x[{i},{j}] = {x_val}")

    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
voting_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 900.0
**Execution Time**: 2.02 seconds
**Reliability**: high
**Analysis**: Pyomo achieved the optimal solution with a similar execution time to DOCplex, confirming the solution's reliability.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 900.00 | 0.19s | N/A | N/A |
| Docplex | OPTIMAL | 900.00 | 2.15s | N/A | N/A |
| Pyomo | OPTIMAL | 900.00 | 2.02s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 900.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is preferred due to its faster execution time, which is beneficial for large-scale problems or when quick decision-making is required.

### Business Interpretation
**Overall Strategy**: The optimal value of 900 indicates the maximum achievable votes under the given constraints, ensuring each contestant receives the maximum allowable votes while meeting minimum state requirements.
**Objective Value Meaning**: The optimal objective value of 900 represents the total votes distributed among contestants, maximizing their reach and engagement.
**Resource Allocation Summary**: Resources (votes) should be allocated to ensure each contestant meets the minimum state requirements while maximizing their total votes.
**Implementation Recommendations**: Implement the solution by configuring the voting system to adhere to the constraints, ensuring each contestant receives the optimal number of votes as calculated.