# Complete Optimization Problem and Solution: party_host

## 1. Problem Context and Goals

### Context  
A party hosting company is tasked with efficiently assigning hosts to parties while minimizing the total cost of hosting. Each host has an associated cost, and the company must ensure that each party has a sufficient number of hosts, does not exceed the maximum allowed hosts, and that the hosts assigned have expertise that matches the party theme. The decision to assign a host to a party is binary, meaning a host is either assigned or not assigned to a specific party.  

The company operates with the following operational parameters:  
- **Cost per host**: Each host has a specific cost associated with their services, which directly impacts the total cost of hosting parties.  
- **Minimum hosts per party**: Each party requires a minimum number of hosts to ensure adequate service.  
- **Maximum hosts per party**: Each party has a limit on the number of hosts that can be assigned to avoid overstaffing.  
- **Expertise matching**: A host can only be assigned to a party if their expertise aligns with the party’s theme.  

These parameters are critical to the decision-making process and are used to define the objective and constraints of the optimization problem. The goal is to make these assignments in a way that minimizes total costs while adhering to the operational requirements.  

### Goals  
The primary goal of this optimization problem is to minimize the total cost of hosting parties by assigning hosts to parties in the most cost-effective manner. This is achieved by considering the cost associated with each host and ensuring that the assignments respect the minimum and maximum host requirements for each party, as well as the expertise matching criteria.  

Success is measured by the ability to reduce the total cost of hosting while maintaining operational feasibility. The optimization process ensures that the company’s resources are used efficiently, balancing cost savings with the need to meet party hosting requirements.  

## 2. Constraints  

The optimization problem must adhere to the following constraints:  
1. **Minimum hosts per party**: Each party must have at least the minimum number of hosts required to ensure adequate service. This ensures that parties are not understaffed.  
2. **Maximum hosts per party**: Each party cannot exceed the maximum number of hosts allowed. This prevents overstaffing and unnecessary costs.  
3. **Expertise matching**: A host can only be assigned to a party if their expertise matches the party’s theme. This ensures that the hosts assigned are qualified to meet the party’s needs.  

These constraints ensure that the assignments are operationally feasible and align with the company’s business requirements.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 2 Database Schema
-- Objective: Added decision variable table 'assign' to address missing optimization requirement. Updated business configuration logic to include scalar parameters and formulas. Ensured schema follows normalization principles and industry standards.

CREATE TABLE cost_per_host (
  Host_ID INTEGER,
  cost INTEGER
);

CREATE TABLE min_hosts (
  Party_ID INTEGER,
  min_hosts INTEGER
);

CREATE TABLE max_hosts (
  Party_ID INTEGER,
  max_hosts INTEGER
);

CREATE TABLE expertise_match (
  Party_ID INTEGER,
  Host_ID INTEGER,
  match BOOLEAN
);

CREATE TABLE assign (
  Party_ID INTEGER,
  Host_ID INTEGER,
  assign BOOLEAN
);
```

### Data Dictionary  
The following tables and columns are used in the optimization problem:  
- **cost_per_host**: Contains the cost associated with each host. This data is used to calculate the total cost of hosting parties.  
  - *Host_ID*: Unique identifier for each host.  
  - *cost*: The cost of the host, used as a coefficient in the objective function.  
- **min_hosts**: Specifies the minimum number of hosts required for each party. This data is used to set the lower bound for the number of hosts assigned to a party.  
  - *Party_ID*: Unique identifier for each party.  
  - *min_hosts*: The minimum number of hosts required for the party, used as a constraint bound.  
- **max_hosts**: Specifies the maximum number of hosts allowed for each party. This data is used to set the upper bound for the number of hosts assigned to a party.  
  - *Party_ID*: Unique identifier for each party.  
  - *max_hosts*: The maximum number of hosts allowed for the party, used as a constraint bound.  
- **expertise_match**: Indicates whether a host’s expertise matches a party’s theme. This data is used to ensure that only qualified hosts are assigned to parties.  
  - *Party_ID*: Unique identifier for each party.  
  - *Host_ID*: Unique identifier for each host.  
  - *match*: A binary indicator (true/false) that determines if the host’s expertise matches the party’s theme, used as a constraint bound.  
- **assign**: Represents the decision to assign a host to a party. This table is used to store the optimization results.  
  - *Party_ID*: Unique identifier for each party.  
  - *Host_ID*: Unique identifier for each host.  
  - *assign*: A binary decision variable (true/false) indicating whether the host is assigned to the party.  

### Current Stored Values  
```sql
-- Iteration 2 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical party hosting scenarios, ensuring realistic costs, host availability, and expertise matching. The number of hosts per party was set to reflect common party sizes, and costs were varied to represent different host experience levels.

-- Realistic data for cost_per_host
INSERT INTO cost_per_host (Host_ID, cost) VALUES (1, 50);
INSERT INTO cost_per_host (Host_ID, cost) VALUES (2, 70);
INSERT INTO cost_per_host (Host_ID, cost) VALUES (3, 60);

-- Realistic data for min_hosts
INSERT INTO min_hosts (Party_ID, min_hosts) VALUES (1, 2);
INSERT INTO min_hosts (Party_ID, min_hosts) VALUES (2, 3);
INSERT INTO min_hosts (Party_ID, min_hosts) VALUES (3, 4);

-- Realistic data for max_hosts
INSERT INTO max_hosts (Party_ID, max_hosts) VALUES (1, 4);
INSERT INTO max_hosts (Party_ID, max_hosts) VALUES (2, 5);
INSERT INTO max_hosts (Party_ID, max_hosts) VALUES (3, 6);

-- Realistic data for expertise_match
INSERT INTO expertise_match (Party_ID, Host_ID, match) VALUES (1, 1, True);
INSERT INTO expertise_match (Party_ID, Host_ID, match) VALUES (1, 2, False);
INSERT INTO expertise_match (Party_ID, Host_ID, match) VALUES (2, 2, True);

-- Realistic data for assign
INSERT INTO assign (Party_ID, Host_ID, assign) VALUES (1, 1, True);
INSERT INTO assign (Party_ID, Host_ID, assign) VALUES (1, 2, False);
INSERT INTO assign (Party_ID, Host_ID, assign) VALUES (2, 2, True);
```

## 4. Mathematical Optimization Formulation

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

#### Objective Function
Minimize the total cost of hosting parties:
\[
\text{Minimize} \quad \sum_{p \in P} \sum_{h \in H} c_h \cdot x_{p,h}
\]
where:
- \( c_h \) is the cost of host \( h \) (from `cost_per_host.cost`),
- \( P \) is the set of all parties,
- \( H \) is the set of all hosts.

#### Constraints
1. **Minimum hosts per party**:
   \[
   \sum_{h \in H} x_{p,h} \geq m_p \quad \forall p \in P
   \]
   where \( m_p \) is the minimum number of hosts required for party \( p \) (from `min_hosts.min_hosts`).

2. **Maximum hosts per party**:
   \[
   \sum_{h \in H} x_{p,h} \leq M_p \quad \forall p \in P
   \]
   where \( M_p \) is the maximum number of hosts allowed for party \( p \) (from `max_hosts.max_hosts`).

3. **Expertise matching**:
   \[
   x_{p,h} \leq e_{p,h} \quad \forall p \in P, \forall h \in H
   \]
   where \( e_{p,h} \) is a binary indicator (1 if host \( h \)’s expertise matches party \( p \)’s theme, 0 otherwise) (from `expertise_match.match`).

4. **Binary decision variables**:
   \[
   x_{p,h} \in \{0, 1\} \quad \forall p \in P, \forall h \in H
   \]

#### Data Source Verification
- \( c_h \): `cost_per_host.cost`
- \( m_p \): `min_hosts.min_hosts`
- \( M_p \): `max_hosts.max_hosts`
- \( e_{p,h} \): `expertise_match.match`

This formulation provides 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 party_host_optimization():
    # 1. MODEL & DATA SETUP
    model = gp.Model("party_host_optimization")
    
    # Data from the database
    cost_per_host = {1: 50, 2: 70, 3: 60}
    min_hosts = {1: 2, 2: 3, 3: 4}
    max_hosts = {1: 4, 2: 5, 3: 6}
    expertise_match = {(1, 1): True, (1, 2): False, (2, 2): True}
    
    # Extract sets
    parties = set(min_hosts.keys())
    hosts = set(cost_per_host.keys())
    
    # Validate array lengths
    assert len(cost_per_host) > 0, "No hosts found"
    assert len(min_hosts) > 0, "No parties found"
    assert len(max_hosts) > 0, "No max hosts data found"
    assert len(expertise_match) > 0, "No expertise match data found"
    
    # 2. VARIABLES
    assign = model.addVars(parties, hosts, vtype=GRB.BINARY, name="assign")
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(gp.quicksum(cost_per_host[h] * assign[p, h] for p in parties for h in hosts), GRB.MINIMIZE)
    
    # 4. CONSTRAINTS
    
    # Minimum hosts per party
    for p in parties:
        model.addConstr(gp.quicksum(assign[p, h] for h in hosts) >= min_hosts[p], name=f"min_hosts_{p}")
    
    # Maximum hosts per party
    for p in parties:
        model.addConstr(gp.quicksum(assign[p, h] for h in hosts) <= max_hosts[p], name=f"max_hosts_{p}")
    
    # Expertise matching
    for p in parties:
        for h in hosts:
            if (p, h) in expertise_match:
                model.addConstr(assign[p, h] <= expertise_match[p, h], name=f"expertise_{p}_{h}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for p in parties:
            for h in hosts:
                if assign[p, h].x > 0.5:
                    print(f"Host {h} assigned to Party {p}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
party_host_optimization()
```

### Execution Results
**Status**: INFEASIBLE
**Error**: Problem is infeasible
**Analysis**: Gurobipy indicates the problem is infeasible, meaning no solution exists that satisfies all constraints. This suggests a potential issue with the constraints or data inputs.

## 6. DOCplex Implementation

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

from docplex.mp.model import Model

def party_host_optimization():
    # 1. MODEL & DATA SETUP
    mdl = Model(name="party_host_optimization")
    
    # Data from the database
    cost_per_host = {1: 50, 2: 70, 3: 60}
    min_hosts = {1: 2, 2: 3, 3: 4}
    max_hosts = {1: 4, 2: 5, 3: 6}
    expertise_match = {(1, 1): True, (1, 2): False, (2, 2): True}
    
    # Extract sets of parties and hosts
    parties = set(min_hosts.keys())
    hosts = set(cost_per_host.keys())
    
    # 2. VARIABLES
    assign = {(p, h): mdl.binary_var(name=f"assign_{p}_{h}") for p in parties for h in hosts}
    
    # 3. OBJECTIVE FUNCTION
    total_cost = mdl.sum(cost_per_host[h] * assign[p, h] for p in parties for h in hosts)
    mdl.minimize(total_cost)
    
    # 4. CONSTRAINTS
    # Minimum hosts per party
    for p in parties:
        mdl.add_constraint(mdl.sum(assign[p, h] for h in hosts) >= min_hosts[p], ctname=f"min_hosts_{p}")
    
    # Maximum hosts per party
    for p in parties:
        mdl.add_constraint(mdl.sum(assign[p, h] for h in hosts) <= max_hosts[p], ctname=f"max_hosts_{p}")
    
    # Expertise matching
    for p in parties:
        for h in hosts:
            if (p, h) in expertise_match:
                mdl.add_constraint(assign[p, h] <= expertise_match[p, h], ctname=f"expertise_{p}_{h}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for p in parties:
            for h in hosts:
                if solution.get_value(assign[p, h]) > 0:
                    print(f"Party {p} assigned Host {h}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Execute the optimization
party_host_optimization()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/mfactory.py", line 596, in _to_linear_operand
    return e.to_linear_expr()
AttributeError: 'bool' object has no attribute 'to_linear_expr'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/tmpo8nquhz1.py", line 57, in <module>
    party_host_optimization()
  File "/tmp/tmpo8nquhz1.py", line 39, in party_host_optimization
    mdl.add_constraint(assign[p, h] <= expertise_match[p, h], ctname=f"expertise_{p}_{h}")
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/operand.py", line 54, in __le__
    return self._model._new_xconstraint(lhs=self, rhs=rhs, comparaison_type=ComparisonType.LE)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/model.py", line 4221, in _new_xconstraint
    return self._lfactory._new_binary_constraint(lhs, comparaison_type, rhs)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/mfactory.py", line 643, in _new_binary_constraint
    right_expr = self._to_linear_operand(rhs, msg="LinearConstraint. expects linear expressions, {0} was passed")
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/mfactory.py", line 599, in _to_linear_operand
    return self.linear_expr(e)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/mfactory.py", line 574, in linear_expr
    return LinearExpr(self._model, arg, constant, safe=safe, transient=transient)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/linear.py", line 433, in __init__
    self.fatal("Cannot convert {0!r} to docplex.mp.LinearExpr, type is {1}", e, type(e))
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/basic.py", line 51, in fatal
    self.logger.fatal(msg, args)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/docplex/mp/error_handler.py", line 210, in fatal
    raise DOcplexException(resolved_message)
docplex.mp.utils.DOcplexException: Cannot convert True to docplex.mp.LinearExpr, type is <class 'bool'>

**Analysis**: DOCplex encountered an error due to a type mismatch in the expertise matching constraint. This suggests a potential issue with the implementation or data format.

## 7. Pyomo Implementation

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

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

def party_host_optimization():
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Example data from the problem description
    cost_per_host = {1: 50, 2: 70, 3: 60}
    min_hosts = {1: 2, 2: 3, 3: 4}
    max_hosts = {1: 4, 2: 5, 3: 6}
    expertise_match = {(1, 1): True, (1, 2): False, (2, 2): True}
    
    # 3. SETS
    model.P = pyo.Set(initialize=min_hosts.keys())  # Parties
    model.H = pyo.Set(initialize=cost_per_host.keys())  # Hosts
    
    # 4. PARAMETERS
    model.cost = pyo.Param(model.H, initialize=cost_per_host)
    model.min_hosts = pyo.Param(model.P, initialize=min_hosts)
    model.max_hosts = pyo.Param(model.P, initialize=max_hosts)
    model.expertise = pyo.Param(model.P, model.H, initialize=lambda model, p, h: expertise_match.get((p, h), False))
    
    # 5. VARIABLES
    model.assign = pyo.Var(model.P, model.H, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.cost[h] * model.assign[p, h] for p in model.P for h in model.H)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.minimize)
    
    # 7. CONSTRAINTS
    # Minimum hosts per party
    def min_hosts_rule(model, p):
        return sum(model.assign[p, h] for h in model.H) >= model.min_hosts[p]
    model.min_hosts_constraint = pyo.Constraint(model.P, rule=min_hosts_rule)
    
    # Maximum hosts per party
    def max_hosts_rule(model, p):
        return sum(model.assign[p, h] for h in model.H) <= model.max_hosts[p]
    model.max_hosts_constraint = pyo.Constraint(model.P, rule=max_hosts_rule)
    
    # Expertise matching
    def expertise_rule(model, p, h):
        return model.assign[p, h] <= model.expertise[p, h]
    model.expertise_constraint = pyo.Constraint(model.P, model.H, rule=expertise_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("\nAssignment values:")
        for p in model.P:
            for h in model.H:
                if pyo.value(model.assign[p, h]) > 0.5:  # Only print assigned hosts
                    print(f"Party {p} -> Host {h}")
        
    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__":
    party_host_optimization()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/tmp/tmpnw80b9td.py", line 85, in <module>
    party_host_optimization()
  File "/tmp/tmpnw80b9td.py", line 49, in party_host_optimization
    model.expertise_constraint = pyo.Constraint(model.P, model.H, rule=expertise_rule)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
    self.add_component(name, val)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 1101, in add_component
    val.construct(data)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/constraint.py", line 722, in construct
    self._setitem_when_not_present(index, rule(block, index))
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/initializer.py", line 349, in __call__
    return self._fcn(parent, *idx)
  File "/tmp/tmpnw80b9td.py", line 48, in expertise_rule
    return model.assign[p, h] <= model.expertise[p, h]
TypeError: '<=' not supported between instances of 'VarData' and 'bool'

**Analysis**: Pyomo also encountered an error related to the expertise matching constraint, indicating a similar issue with the implementation or data format.

## 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.17s | N/A | 4 |
| Docplex | ERROR | N/A | 1.06s | N/A | 4 |
| Pyomo | ERROR | N/A | 0.84s | N/A | 4 |

### Solver Consistency Analysis
**Result**: Solvers produced inconsistent results
**Inconsistent Solvers**: gurobipy, docplex, pyomo
**Potential Issues**:
- Infeasible constraints in the problem formulation
- Type mismatch or incorrect data format in expertise matching constraint
- Potential errors in data inputs (e.g., cost_per_host, min_hosts, max_hosts, expertise_match)
**Solver Retry Summary**: gurobipy: 4 attempts, docplex: 4 attempts, pyomo: 4 attempts

### Final Recommendation
**Confidence Level**: LOW
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy provided a clear indication of infeasibility, which is a critical insight. DOCplex and Pyomo encountered implementation errors, making Gurobipy the most reliable solver for diagnosing the issue.

### Business Interpretation
**Overall Strategy**: The problem appears to be infeasible or incorrectly formulated, meaning no valid solution exists under the current constraints and data. This could lead to challenges in assigning hosts to parties efficiently.
**Objective Value Meaning**: The objective value represents the total cost of hosting parties. Since no solution exists, the cost cannot be minimized under the current constraints.
**Resource Allocation Summary**: No valid resource allocation can be determined due to infeasibility or implementation errors.
**Implementation Recommendations**: ['Review and validate the data inputs (cost_per_host, min_hosts, max_hosts, expertise_match) for accuracy and consistency.', 'Re-examine the constraints to ensure they are feasible and correctly implemented.', 'Debug the expertise matching constraint to resolve type mismatch or format issues.', 'Consider relaxing constraints or adjusting data inputs to make the problem feasible.']