# Complete Optimization Problem and Solution: local_govt_in_alabama

## 1. Problem Context and Goals

### Context  
The local government in Alabama is tasked with optimizing the allocation of participants to events to ensure the highest possible satisfaction among attendees while adhering to operational constraints. The decision to assign a participant to an event is represented as a binary choice: either the participant is assigned (1) or not (0). The goal is to maximize the total satisfaction score across all assignments, where the satisfaction score reflects how much a participant is expected to enjoy a specific event. 

Each event has a predefined capacity, which limits the number of participants that can be assigned to it. This capacity is a critical operational parameter, ensuring that events are not overcrowded and remain manageable. Additionally, each participant can be assigned to at most one event, ensuring fairness and preventing overcommitment. 

The satisfaction scores are derived from historical data and participant preferences, providing a realistic measure of expected enjoyment. The default event capacity is set to 50 participants, a typical size for local government events, ensuring manageable group sizes. This configuration ensures that the optimization problem remains linear, avoiding complex scenarios such as variable products or divisions.

### Goals  
The primary goal of this optimization problem is to maximize the overall satisfaction of participants by assigning them to events in the most effective way. This is achieved by summing the satisfaction scores of all assigned participant-event pairs. Success is measured by the total satisfaction score, which directly reflects the quality of the assignments. The optimization process ensures that the assignments respect event capacities and participant availability, leading to a fair and efficient allocation.

## 2. Constraints  

The optimization problem is subject to two key constraints:  
1. **Event Capacity Constraint**: The total number of participants assigned to any single event must not exceed the event's predefined capacity. This ensures that events remain within manageable limits and do not become overcrowded.  
2. **Participant Assignment Constraint**: Each participant can be assigned to at most one event. This ensures fairness and prevents participants from being overcommitted to multiple events simultaneously.  

These constraints are designed to align with the operational realities of event planning, ensuring that the solution is both practical and effective.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Added tables for satisfaction scores and event capacities, updated Participants_in_Events table, and created business_configuration_logic.json for scalar parameters and formulas.

CREATE TABLE Satisfaction_Scores (
  Event_ID INTEGER,
  Participant_ID INTEGER,
  Score FLOAT
);

CREATE TABLE Event_Capacities (
  Event_ID INTEGER,
  Capacity INTEGER
);

CREATE TABLE Participants_in_Events (
  Event_ID INTEGER,
  Participant_ID INTEGER
);
```

### Data Dictionary  
- **Satisfaction_Scores**:  
  - **Business Purpose**: Captures the satisfaction score of each participant for each event, reflecting how much a participant is expected to enjoy a specific event.  
  - **Optimization Role**: Provides the coefficients for the objective function, representing the value of assigning a participant to an event.  
  - **Columns**:  
    - **Event_ID**: Unique identifier for the event, linking it to the optimization model.  
    - **Participant_ID**: Unique identifier for the participant, linking them to the optimization model.  
    - **Score**: The satisfaction score of the participant for the event, used as a coefficient in the objective function.  

- **Event_Capacities**:  
  - **Business Purpose**: Defines the maximum number of participants allowed in each event, ensuring events remain within manageable limits.  
  - **Optimization Role**: Provides the bounds for the event capacity constraints.  
  - **Columns**:  
    - **Event_ID**: Unique identifier for the event, linking it to the optimization model.  
    - **Capacity**: The maximum number of participants allowed in the event, used as a bound in the capacity constraint.  

- **Participants_in_Events**:  
  - **Business Purpose**: Represents the assignment of participants to events, capturing the decision to assign a participant to an event.  
  - **Optimization Role**: Represents the binary decision variables in the optimization model.  
  - **Columns**:  
    - **Event_ID**: Unique identifier for the event, linking it to the optimization model.  
    - **Participant_ID**: Unique identifier for the participant, linking them to the optimization model.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic event sizes, participant preferences, and typical satisfaction scores in local government settings.

-- Realistic data for Satisfaction_Scores
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (1, 101, 0.85);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (1, 102, 0.75);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (1, 103, 0.65);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (2, 101, 0.7);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (2, 102, 0.9);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (2, 103, 0.8);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (3, 101, 0.6);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (3, 102, 0.85);
INSERT INTO Satisfaction_Scores (Event_ID, Participant_ID, Score) VALUES (3, 103, 0.95);

-- Realistic data for Event_Capacities
INSERT INTO Event_Capacities (Event_ID, Capacity) VALUES (1, 50);
INSERT INTO Event_Capacities (Event_ID, Capacity) VALUES (2, 100);
INSERT INTO Event_Capacities (Event_ID, Capacity) VALUES (3, 75);

-- Realistic data for Participants_in_Events
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (1, 101);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (1, 102);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (1, 103);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (2, 101);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (2, 102);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (2, 103);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (3, 101);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (3, 102);
INSERT INTO Participants_in_Events (Event_ID, Participant_ID) VALUES (3, 103);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
Let \( x_{e,p} \) be a binary decision variable where:  
- \( x_{e,p} = 1 \) if participant \( p \) is assigned to event \( e \),  
- \( x_{e,p} = 0 \) otherwise.  

Here, \( e \in \{1, 2, 3\} \) (events) and \( p \in \{101, 102, 103\} \) (participants).

#### Objective Function
Maximize the total satisfaction score:  
\[
\text{Maximize } Z = \sum_{e} \sum_{p} \text{Satisfaction_Scores.Score}_{e,p} \cdot x_{e,p}
\]  
Where:  
- \( \text{Satisfaction_Scores.Score}_{e,p} \) is the satisfaction score of participant \( p \) for event \( e \).

#### Constraints
1. **Event Capacity Constraint**: The number of participants assigned to each event must not exceed its capacity:  
\[
\sum_{p} x_{e,p} \leq \text{Event_Capacities.Capacity}_e \quad \forall e
\]  
Where:  
- \( \text{Event_Capacities.Capacity}_e \) is the capacity of event \( e \).

2. **Participant Assignment Constraint**: Each participant can be assigned to at most one event:  
\[
\sum_{e} x_{e,p} \leq 1 \quad \forall p
\]

#### Data Source Verification
- **Objective Function Coefficients**: \( \text{Satisfaction_Scores.Score}_{e,p} \) comes from the `Satisfaction_Scores.Score` column.  
- **Event Capacity Constraints**: \( \text{Event_Capacities.Capacity}_e \) comes from the `Event_Capacities.Capacity` column.  

#### Complete Numerical Model
Using the provided data, the numerical model is as follows:

**Objective Function**:  
\[
\text{Maximize } Z = 0.85x_{1,101} + 0.75x_{1,102} + 0.65x_{1,103} + 0.7x_{2,101} + 0.9x_{2,102} + 0.8x_{2,103} + 0.6x_{3,101} + 0.85x_{3,102} + 0.95x_{3,103}
\]

**Constraints**:  
1. **Event Capacity Constraints**:  
\[
x_{1,101} + x_{1,102} + x_{1,103} \leq 50 \quad \text{(Event 1)}  
\]  
\[
x_{2,101} + x_{2,102} + x_{2,103} \leq 100 \quad \text{(Event 2)}  
\]  
\[
x_{3,101} + x_{3,102} + x_{3,103} \leq 75 \quad \text{(Event 3)}  
\]

2. **Participant Assignment Constraints**:  
\[
x_{1,101} + x_{2,101} + x_{3,101} \leq 1 \quad \text{(Participant 101)}  
\]  
\[
x_{1,102} + x_{2,102} + x_{3,102} \leq 1 \quad \text{(Participant 102)}  
\]  
\[
x_{1,103} + x_{2,103} + x_{3,103} \leq 1 \quad \text{(Participant 103)}  
\]

**Variable Bounds**:  
\[
x_{e,p} \in \{0, 1\} \quad \forall e, p
\]

This is a complete, immediately solvable Mixed-Integer Linear Programming (MIP) model.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Local Government Event Allocation Problem
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_event_allocation():
    """Optimize participant allocation to events to maximize satisfaction."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("local_govt_in_alabama")
    
    # Data from the problem
    events = [1, 2, 3]
    participants = [101, 102, 103]
    
    # Satisfaction scores
    satisfaction_scores = {
        (1, 101): 0.85,
        (1, 102): 0.75,
        (1, 103): 0.65,
        (2, 101): 0.7,
        (2, 102): 0.9,
        (2, 103): 0.8,
        (3, 101): 0.6,
        (3, 102): 0.85,
        (3, 103): 0.95
    }
    
    # Event capacities
    event_capacities = {
        1: 50,
        2: 100,
        3: 75
    }
    
    # CRITICAL: Validate array lengths before loops
    assert len(events) == len(event_capacities), "Event data mismatch"
    assert len(participants) == 3, "Participant data mismatch"
    
    # 2. VARIABLES
    # Binary decision variables: x[e, p] = 1 if participant p is assigned to event e
    x = model.addVars(events, participants, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total satisfaction score
    model.setObjective(gp.quicksum(satisfaction_scores[e, p] * x[e, p] for e in events for p in participants), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Event Capacity Constraints
    for e in events:
        model.addConstr(gp.quicksum(x[e, p] for p in participants) <= event_capacities[e], name=f"event_capacity_{e}")
    
    # Participant Assignment Constraints
    for p in participants:
        model.addConstr(gp.quicksum(x[e, p] for e in events) <= 1, name=f"participant_assignment_{p}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for e in events:
            for p in participants:
                if x[e, p].x > 0.5:
                    print(f"Participant {p} assigned to Event {e}")
    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__":
    optimize_event_allocation()
```

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

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCplex 2.29.245 Implementation for Local Government in Alabama Event Allocation
"""

from docplex.mp.model import Model

def optimize_event_allocation():
    """Optimize participant allocation to events to maximize satisfaction."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="local_govt_in_alabama")
    
    # Data from the problem
    events = [1, 2, 3]
    participants = [101, 102, 103]
    
    # Satisfaction scores
    satisfaction_scores = {
        (1, 101): 0.85,
        (1, 102): 0.75,
        (1, 103): 0.65,
        (2, 101): 0.7,
        (2, 102): 0.9,
        (2, 103): 0.8,
        (3, 101): 0.6,
        (3, 102): 0.85,
        (3, 103): 0.95
    }
    
    # Event capacities
    event_capacities = {
        1: 50,
        2: 100,
        3: 75
    }
    
    # 2. VARIABLES
    # Binary decision variables: x[e, p] = 1 if participant p is assigned to event e, 0 otherwise
    x = {(e, p): mdl.binary_var(name=f"x_{e}_{p}") for e in events for p in participants}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total satisfaction score
    objective = mdl.sum(satisfaction_scores[e, p] * x[e, p] for e in events for p in participants)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Event Capacity Constraint: Sum of participants assigned to each event <= event capacity
    for e in events:
        mdl.add_constraint(mdl.sum(x[e, p] for p in participants) <= event_capacities[e], ctname=f"event_capacity_{e}")
    
    # Participant Assignment Constraint: Each participant can be assigned to at most one event
    for p in participants:
        mdl.add_constraint(mdl.sum(x[e, p] for e in events) <= 1, ctname=f"participant_assignment_{p}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for e in events:
            for p in participants:
                if solution.get_value(x[e, p]) > 0.5:
                    print(f"Participant {p} assigned to Event {e}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2.7
**Execution Time**: 1.08 seconds
**Reliability**: high
**Analysis**: DOCplex also found the optimal solution but took longer compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def optimize_event_assignments():
    """Optimize participant assignments to events to maximize satisfaction."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Define sets for events and participants
    events = [1, 2, 3]
    participants = [101, 102, 103]
    
    # Satisfaction scores
    satisfaction_scores = {
        (1, 101): 0.85,
        (1, 102): 0.75,
        (1, 103): 0.65,
        (2, 101): 0.7,
        (2, 102): 0.9,
        (2, 103): 0.8,
        (3, 101): 0.6,
        (3, 102): 0.85,
        (3, 103): 0.95
    }
    
    # Event capacities
    event_capacities = {
        1: 50,
        2: 100,
        3: 75
    }
    
    # 3. SETS
    model.E = pyo.Set(initialize=events)  # Set of events
    model.P = pyo.Set(initialize=participants)  # Set of participants
    
    # 4. PARAMETERS
    model.satisfaction = pyo.Param(model.E, model.P, initialize=satisfaction_scores)
    model.capacity = pyo.Param(model.E, initialize=event_capacities)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.E, model.P, within=pyo.Binary)  # Binary decision variables
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.satisfaction[e, p] * model.x[e, p] for e in model.E for p in model.P)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Event Capacity Constraint
    def event_capacity_rule(model, e):
        return sum(model.x[e, p] for p in model.P) <= model.capacity[e]
    model.event_capacity_constraint = pyo.Constraint(model.E, rule=event_capacity_rule)
    
    # Participant Assignment Constraint
    def participant_assignment_rule(model, p):
        return sum(model.x[e, p] for e in model.E) <= 1
    model.participant_assignment_constraint = pyo.Constraint(model.P, rule=participant_assignment_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("\nOptimal Assignments:")
        for e in model.E:
            for p in model.P:
                if pyo.value(model.x[e, p]) > 0.5:  # Only print assignments
                    print(f"Participant {p} assigned to Event {e}")
        
    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__":
    optimize_event_assignments()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2.7
**Execution Time**: 0.94 seconds
**Reliability**: high
**Analysis**: Pyomo found the optimal solution but had the longest execution time among the three solvers.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 2.70 | 0.16s | N/A | N/A |
| Docplex | OPTIMAL | 2.70 | 1.08s | N/A | N/A |
| Pyomo | OPTIMAL | 2.70 | 0.94s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 2.7
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its faster execution time while still providing the same optimal solution as the other solvers.

### Business Interpretation
**Overall Strategy**: The optimal total satisfaction score of 2.7 indicates the best possible assignment of participants to events based on their satisfaction scores.
**Objective Value Meaning**: The optimal objective value of 2.7 represents the maximum total satisfaction achievable with the given constraints.
**Resource Allocation Summary**: Participants should be assigned to events as follows: Participant 101 to Event 1, Participant 102 to Event 2, and Participant 103 to Event 3.
**Implementation Recommendations**: Ensure that the assignments are communicated to participants promptly and that event capacities are not exceeded.