# Complete Optimization Problem and Solution: party_host

## 1. Problem Context and Goals

### Context  
The company is focused on organizing themed parties and aims to efficiently allocate hosts to these events. The primary decision involves determining whether a specific host is assigned to a particular party. This decision is represented by a binary choice, where each host can either be assigned or not assigned to a party. The operational goal is to minimize the total number of hosts required across all parties. Each party must have at least one main host to ensure smooth operations, and no party should exceed a predefined maximum number of hosts. This maximum is set to ensure effective management and avoid overcrowding. The business configuration includes a scalar parameter that specifies the maximum number of hosts allowed per party, which is crucial for maintaining the balance between adequate staffing and resource efficiency.

### Goals  
The optimization goal is to minimize the total number of hosts assigned to parties. The metric for optimization is the total count of host assignments, with the aim of reducing this number while still meeting the operational requirements of each party. Success is measured by achieving the minimum possible number of host assignments, ensuring that each party is adequately staffed without exceeding the maximum host limit. The focus is on a linear optimization approach, where the objective is to streamline host allocation in a straightforward and efficient manner.

## 2. Constraints    

The constraints for this optimization problem are straightforward and align with the linear mathematical formulation. Each party must have at least one host assigned to it, ensuring that all parties are adequately staffed. Additionally, the number of hosts assigned to any party should not exceed the maximum number of hosts allowed, as specified in the business configuration. These constraints ensure that the staffing levels are both sufficient and efficient, adhering to the operational limits set by the company.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for decision variables and objective coefficients, modifying existing tables to fill mapping gaps, and updating business configuration logic for scalar parameters and formulas.

CREATE TABLE party_host_assignment (
  party_id INTEGER,
  host_id INTEGER,
  is_assigned BOOLEAN
);

CREATE TABLE objective_coefficients (
  coefficient_id INTEGER,
  coefficient_value FLOAT
);
```

### Data Dictionary  
The data dictionary provides a comprehensive overview of the tables and columns used in the optimization process, linking them to their business purposes and roles in the optimization model:

- **Party Host Assignment Table**: This table tracks which hosts are assigned to which parties. It includes:
  - **Party ID**: An identifier for each party, linking host assignments to specific events.
  - **Host ID**: An identifier for each host, ensuring that assignments are linked to the correct personnel.
  - **Is Assigned**: A binary indicator showing whether a host is assigned to a party, serving as the decision variable in the optimization model.

- **Objective Coefficients Table**: This table stores the coefficients used in the optimization objective function. It includes:
  - **Coefficient ID**: A unique identifier for each coefficient, ensuring clarity in the optimization process.
  - **Coefficient Value**: The value of the coefficient, used in calculating the objective function to minimize host assignments.

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical party hosting scenarios, ensuring each party has a reasonable number of hosts and that the constraints are neither too tight nor too loose.

-- Realistic data for party_host_assignment
INSERT INTO party_host_assignment (party_id, host_id, is_assigned) VALUES (1, 101, True);
INSERT INTO party_host_assignment (party_id, host_id, is_assigned) VALUES (1, 102, True);
INSERT INTO party_host_assignment (party_id, host_id, is_assigned) VALUES (2, 103, True);
INSERT INTO party_host_assignment (party_id, host_id, is_assigned) VALUES (2, 104, True);
INSERT INTO party_host_assignment (party_id, host_id, is_assigned) VALUES (3, 105, True);

-- Realistic data for objective_coefficients
INSERT INTO objective_coefficients (coefficient_id, coefficient_value) VALUES (1, 1.0);
INSERT INTO objective_coefficients (coefficient_id, coefficient_value) VALUES (2, 1.0);
INSERT INTO objective_coefficients (coefficient_id, coefficient_value) VALUES (3, 1.0);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be a binary decision variable where \( x_{ij} = 1 \) if host \( j \) is assigned to party \( i \), and \( x_{ij} = 0 \) otherwise.

#### Objective Function
Minimize the total number of host assignments across all parties:
\[
\text{Minimize } \sum_{i,j} x_{ij}
\]
Data Source Verification:
- The coefficient for each \( x_{ij} \) is implicitly 1, as indicated by the `objective_coefficients.coefficient_value` in the `objective_coefficients` table.

#### Constraints
1. **Each party must have at least one host:**
   \[
   \sum_{j} x_{ij} \geq 1 \quad \forall i
   \]
   Data Source Verification:
   - This constraint ensures that each party \( i \) has at least one host assigned.

2. **No party should exceed the maximum number of hosts allowed:**
   \[
   \sum_{j} x_{ij} \leq \text{max\_hosts\_per\_party} \quad \forall i
   \]
   Data Source Verification:
   - The parameter `max_hosts_per_party` is a scalar from the business configuration, specifying the maximum number of hosts allowed per party.

3. **Binary nature of decision variables:**
   \[
   x_{ij} \in \{0, 1\} \quad \forall i, j
   \]
   Data Source Verification:
   - The binary nature of \( x_{ij} \) is derived from the `party_host_assignment.is_assigned` column, which indicates whether a host is assigned to a party.

This formulation provides a complete linear model that can be solved using standard linear or mixed-integer programming solvers to determine the optimal assignment of hosts to parties, minimizing the total number of hosts while satisfying all constraints.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_party_host_assignment():
    """Optimize host assignments to parties using Gurobi."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("party_host")
    
    # Example data setup
    parties = [1, 2, 3]  # Party IDs
    hosts = [101, 102, 103, 104, 105]  # Host IDs
    max_hosts_per_party = 2  # Maximum number of hosts allowed per party
    
    # Validate array lengths before loops
    assert len(parties) > 0, "No parties available"
    assert len(hosts) > 0, "No hosts available"
    
    # 2. VARIABLES
    # Binary decision variables for host assignments
    x = {(i, j): model.addVar(vtype=GRB.BINARY, name=f"x_{i}_{j}") 
         for i in parties for j in hosts}
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total number of host assignments
    model.setObjective(gp.quicksum(x[i, j] for i in parties for j in hosts), GRB.MINIMIZE)
    
    # 4. CONSTRAINTS
    
    # Each party must have at least one host
    model.addConstrs((gp.quicksum(x[i, j] for j in hosts) >= 1 for i in parties), name="min_one_host")
    
    # No party should exceed the maximum number of hosts allowed
    model.addConstrs((gp.quicksum(x[i, j] for j in hosts) <= max_hosts_per_party for i in parties), name="max_hosts")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in parties:
            for j in hosts:
                if x[i, j].x > 1e-6:
                    print(f"Host {j} assigned to Party {i}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_party_host_assignment()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 3.0
**Execution Time**: 0.73 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 optimize_party_host_assignment():
    """Optimize host assignments to parties using DOCplex"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="party_host_assignment")
    
    # Example data setup
    parties = [1, 2, 3]  # Party IDs
    hosts = [101, 102, 103, 104, 105]  # Host IDs
    max_hosts_per_party = 2  # Maximum number of hosts allowed per party
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(parties) > 0 and len(hosts) > 0, "Parties or hosts list is empty"
    
    # 2. VARIABLES
    # Binary decision variables for host assignments
    x = {(i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in parties for j in hosts}
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total number of host assignments
    objective = mdl.sum(x[i, j] for i in parties for j in hosts)
    mdl.minimize(objective)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Each party must have at least one host
    for i in parties:
        mdl.add_constraint(mdl.sum(x[i, j] for j in hosts) >= 1, ctname=f"min_hosts_{i}")
    
    # No party should exceed the maximum number of hosts allowed
    for i in parties:
        mdl.add_constraint(mdl.sum(x[i, j] for j in hosts) <= max_hosts_per_party, ctname=f"max_hosts_{i}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in parties:
            for j in hosts:
                if solution.get_value(x[i, j]) > 1e-6:
                    print(f"Host {j} is assigned to Party {i}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
optimize_party_host_assignment()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 3.0
**Execution Time**: 3.69 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution, albeit 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 party_host_optimization():
    """Optimize host assignments to parties"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Example data setup
    parties = [1, 2, 3]
    hosts = [101, 102, 103, 104, 105]
    max_hosts_per_party = 2
    
    # CRITICAL: Validate array lengths before indexing
    assert len(parties) > 0 and len(hosts) > 0, "Parties or hosts list is empty"
    
    # 3. SETS
    model.I = pyo.Set(initialize=parties)
    model.J = pyo.Set(initialize=hosts)
    
    # 4. VARIABLES
    model.x = pyo.Var(model.I, model.J, within=pyo.Binary)
    
    # 5. 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.minimize)
    
    # 6. CONSTRAINTS
    
    # Each party must have at least one host
    def min_host_rule(model, i):
        return sum(model.x[i, j] for j in model.J) >= 1
    model.min_host_constraint = pyo.Constraint(model.I, rule=min_host_rule)
    
    # No party should exceed the maximum number of hosts allowed
    def max_host_rule(model, i):
        return sum(model.x[i, j] for j in model.J) <= max_hosts_per_party
    model.max_host_constraint = pyo.Constraint(model.I, rule=max_host_rule)
    
    # 7. 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
    
    # 8. 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("\nHost assignments:")
        for i in model.I:
            for j in model.J:
                x_val = pyo.value(model.x[i, j])
                if x_val > 0.5:  # Only print assigned hosts
                    print(f"Party {i} is assigned Host {j}")
        
    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
party_host_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 3.0
**Execution Time**: 1.81 seconds
**Reliability**: high
**Analysis**: Pyomo achieved an optimal solution with a moderate execution time, demonstrating reliability and consistency with other solvers.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 3.00 | 0.73s | N/A | N/A |
| Docplex | OPTIMAL | 3.00 | 3.69s | N/A | N/A |
| Pyomo | OPTIMAL | 3.00 | 1.81s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 3.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its fastest execution time and high reliability, making it suitable for time-sensitive applications.

### Business Interpretation
**Overall Strategy**: The optimal solution indicates that the minimum number of hosts required to cover all parties is 3, ensuring efficient resource allocation.
**Objective Value Meaning**: The optimal objective value of 3 means that only 3 hosts are needed to cover all parties, minimizing costs and maximizing efficiency.
**Resource Allocation Summary**: Allocate 3 hosts strategically to ensure each party has at least one host, without exceeding the maximum allowed.
**Implementation Recommendations**: Implement the solution by assigning the identified hosts to parties as per the solver's output, ensuring compliance with constraints.