# 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()