# Complete Optimization Problem and Solution: city_record

## 1. Problem Context and Goals

### Context  
A sports organization is tasked with selecting the optimal set of host cities for a series of matches in a given year. The decision-making process is guided by a set of key factors: the regional population of each city, its GDP, and the average temperature during the match month. These factors are weighted to reflect their relative importance in determining the overall benefit of selecting a city as a host. The weights are as follows: 40% for population, 30% for GDP, and 30% for temperature. The organization must also adhere to several operational constraints: the total number of matches to be hosted is capped at 10, the total budget for hosting cannot exceed $1,000,000, and no single city can host more than 2 matches. The goal is to maximize the overall benefit of the selected host cities while respecting these constraints.

### Goals  
The primary goal of this optimization problem is to maximize the total benefit derived from selecting host cities for the matches. This benefit is calculated as a weighted sum of each city's regional population, GDP, and average temperature during the match month. The weights assigned to these factors ensure that the selection process aligns with the organization's priorities. Success is measured by the ability to select a set of cities that collectively provide the highest possible benefit without exceeding the total number of matches, the budget, or the maximum number of matches per city.

## 2. Constraints    

The optimization process must respect the following constraints:  
1. **Total Matches Constraint**: The total number of matches hosted across all selected cities must not exceed 10.  
2. **Budget Constraint**: The total cost of hosting matches in the selected cities must not exceed the allocated budget of $1,000,000.  
3. **Maximum Matches Per City Constraint**: No single city can host more than 2 matches.  

These constraints ensure that the selection process remains feasible and aligns with the organization's operational limitations.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating tables for city data, match constraints, and objective coefficients. Configuration logic updates include scalar parameters for weights and constraints, and formulas for benefit calculation.

CREATE TABLE city_data (
  city_id INTEGER,
  population INTEGER,
  gdp FLOAT,
  avg_temperature FLOAT,
  hosting_cost FLOAT
);

CREATE TABLE match_constraints (
  constraint_id INTEGER,
  max_matches_per_city INTEGER,
  total_budget FLOAT
);

CREATE TABLE objective_coefficients (
  coefficient_id INTEGER,
  weight_population FLOAT,
  weight_gdp FLOAT,
  weight_temperature FLOAT
);
```

### Data Dictionary  
- **city_data**: Contains information about potential host cities, including their unique identifier, regional population, GDP, average temperature during the match month, and the cost of hosting a match in the city.  
  - **city_id**: Unique identifier for each city.  
  - **population**: Regional population of the city, used as a component in the benefit calculation.  
  - **gdp**: GDP of the city, used as a component in the benefit calculation.  
  - **avg_temperature**: Average temperature during the match month, used as a component in the benefit calculation.  
  - **hosting_cost**: Cost of hosting a match in the city, used to enforce the budget constraint.  

- **match_constraints**: Stores operational constraints related to hosting matches, including the maximum number of matches per city and the total budget.  
  - **constraint_id**: Unique identifier for each constraint.  
  - **max_matches_per_city**: Maximum number of matches that can be hosted in a single city.  
  - **total_budget**: Total budget allocated for hosting matches across all cities.  

- **objective_coefficients**: Contains the weights assigned to each factor in the benefit calculation.  
  - **coefficient_id**: Unique identifier for each coefficient.  
  - **weight_population**: Weight assigned to the regional population in the benefit calculation (40%).  
  - **weight_gdp**: Weight assigned to the GDP in the benefit calculation (30%).  
  - **weight_temperature**: Weight assigned to the average temperature in the benefit calculation (30%).  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic city demographics, economic data, and climate conditions, ensuring they align with the optimization problem's requirements and constraints.

-- Realistic data for city_data
INSERT INTO city_data (city_id, population, gdp, avg_temperature, hosting_cost) VALUES (1, 800000, 75000.0, 22.0, 120000.0);
INSERT INTO city_data (city_id, population, gdp, avg_temperature, hosting_cost) VALUES (2, 1200000, 110000.0, 26.0, 180000.0);
INSERT INTO city_data (city_id, population, gdp, avg_temperature, hosting_cost) VALUES (3, 600000, 60000.0, 20.0, 100000.0);

-- Realistic data for match_constraints
INSERT INTO match_constraints (constraint_id, max_matches_per_city, total_budget) VALUES (1, 2, 1000000.0);
INSERT INTO match_constraints (constraint_id, max_matches_per_city, total_budget) VALUES (2, 2, 1000000.0);
INSERT INTO match_constraints (constraint_id, max_matches_per_city, total_budget) VALUES (3, 2, 1000000.0);

-- Realistic data for objective_coefficients
INSERT INTO objective_coefficients (coefficient_id, weight_population, weight_gdp, weight_temperature) VALUES (1, 0.4, 0.3, 0.3);
INSERT INTO objective_coefficients (coefficient_id, weight_population, weight_gdp, weight_temperature) VALUES (2, 0.4, 0.3, 0.3);
INSERT INTO objective_coefficients (coefficient_id, weight_population, weight_gdp, weight_temperature) VALUES (3, 0.4, 0.3, 0.3);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_i \) be the number of matches hosted in city \( i \), where \( i \in \{1, 2, 3\} \).  
  \( x_i \) is a non-negative integer variable (MIP).

#### Objective Function
Maximize the total benefit:  
\[
\text{Maximize } Z = \sum_{i=1}^{3} \left( 0.4 \cdot \text{population}_i + 0.3 \cdot \text{gdp}_i + 0.3 \cdot \text{avg\_temperature}_i \right) \cdot x_i
\]

#### Constraints
1. **Total Matches Constraint**:  
\[
\sum_{i=1}^{3} x_i \leq 10
\]
2. **Budget Constraint**:  
\[
\sum_{i=1}^{3} \text{hosting\_cost}_i \cdot x_i \leq 1,000,000
\]
3. **Maximum Matches Per City Constraint**:  
\[
x_i \leq 2 \quad \forall i \in \{1, 2, 3\}
\]

#### Data Source Verification
- **Objective Function Coefficients**:  
  - \( 0.4 \): `objective_coefficients.weight_population`  
  - \( 0.3 \): `objective_coefficients.weight_gdp`  
  - \( 0.3 \): `objective_coefficients.weight_temperature`  
  - `population_i`, `gdp_i`, `avg_temperature_i`: `city_data.population`, `city_data.gdp`, `city_data.avg_temperature`  

- **Constraints**:  
  - **Total Matches Constraint**: Constant 10 is derived from the problem description.  
  - **Budget Constraint**: `hosting_cost_i`: `city_data.hosting_cost`; Constant 1,000,000: `match_constraints.total_budget`  
  - **Maximum Matches Per City Constraint**: Constant 2: `match_constraints.max_matches_per_city`  

#### Numerical Model
Using the provided data:  
- **Objective Function**:  
\[
\text{Maximize } Z = (0.4 \cdot 800,000 + 0.3 \cdot 75,000 + 0.3 \cdot 22) \cdot x_1 + (0.4 \cdot 1,200,000 + 0.3 \cdot 110,000 + 0.3 \cdot 26) \cdot x_2 + (0.4 \cdot 600,000 + 0.3 \cdot 60,000 + 0.3 \cdot 20) \cdot x_3
\]
Simplified:  
\[
\text{Maximize } Z = 320,000 \cdot x_1 + 480,000 \cdot x_2 + 240,000 \cdot x_3
\]

- **Constraints**:  
1. \( x_1 + x_2 + x_3 \leq 10 \)  
2. \( 120,000 \cdot x_1 + 180,000 \cdot x_2 + 100,000 \cdot x_3 \leq 1,000,000 \)  
3. \( x_1 \leq 2 \), \( x_2 \leq 2 \), \( x_3 \leq 2 \)  

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 Host City Selection Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def host_city_optimization():
    """Optimize the selection of host cities for matches"""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("host_city_selection")
    
    # City data
    cities = [1, 2, 3]
    population = {1: 800000, 2: 1200000, 3: 600000}
    gdp = {1: 75000.0, 2: 110000.0, 3: 60000.0}
    avg_temperature = {1: 22.0, 2: 26.0, 3: 20.0}
    hosting_cost = {1: 120000.0, 2: 180000.0, 3: 100000.0}
    
    # Objective coefficients
    weight_population = 0.4
    weight_gdp = 0.3
    weight_temperature = 0.3
    
    # Constraints
    max_matches_per_city = 2
    total_budget = 1000000.0
    total_matches_limit = 10
    
    # CRITICAL: Validate array lengths before loops
    assert len(cities) == len(population) == len(gdp) == len(avg_temperature) == len(hosting_cost), "Array length mismatch"
    
    # 2. VARIABLES
    x = {i: model.addVar(vtype=GRB.INTEGER, name=f"x_{i}", lb=0, ub=max_matches_per_city) for i in cities}
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(
        gp.quicksum(
            (weight_population * population[i] + weight_gdp * gdp[i] + weight_temperature * avg_temperature[i]) * x[i]
            for i in cities
        ),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Total matches constraint
    model.addConstr(
        gp.quicksum(x[i] for i in cities) <= total_matches_limit,
        name="total_matches_constraint"
    )
    
    # Budget constraint
    model.addConstr(
        gp.quicksum(hosting_cost[i] * x[i] for i in cities) <= total_budget,
        name="budget_constraint"
    )
    
    # Maximum matches per city constraint
    for i in cities:
        model.addConstr(
            x[i] <= max_matches_per_city,
            name=f"max_matches_per_city_{i}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in cities:
            if x[i].x > 1e-6:
                print(f"Matches in city {i}: {x[i].x:.0f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

if __name__ == "__main__":
    host_city_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2227040.8
**Execution Time**: 0.19 seconds
**Reliability**: high
**Analysis**: Gurobipy found the optimal solution quickly with a low execution time of 0.187 seconds. The solver is highly reliable for this type of MIP problem.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCplex Implementation for Host City Selection Optimization
"""

from docplex.mp.model import Model

def optimize_host_cities():
    """Optimize the selection of host cities for matches."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="host_city_selection")
    
    # Data from the problem description
    city_ids = [1, 2, 3]
    populations = [800000, 1200000, 600000]
    gdps = [75000.0, 110000.0, 60000.0]
    avg_temperatures = [22.0, 26.0, 20.0]
    hosting_costs = [120000.0, 180000.0, 100000.0]
    
    # Weights for the objective function
    weight_population = 0.4
    weight_gdp = 0.3
    weight_temperature = 0.3
    
    # Constraints
    max_matches_per_city = 2
    total_budget = 1000000.0
    total_matches_limit = 10
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(city_ids) == len(populations) == len(gdps) == len(avg_temperatures) == len(hosting_costs), "Array length mismatch"
    safe_range = range(min(len(city_ids), len(populations), len(gdps), len(avg_temperatures), len(hosting_costs)))  # Safe indexing
    
    # 2. VARIABLES
    # Decision variables: number of matches hosted in each city
    x = {i: mdl.integer_var(name=f"x_{city_ids[i]}", lb=0, ub=max_matches_per_city) for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Calculate the benefit for each city
    benefits = [
        (weight_population * populations[i] + weight_gdp * gdps[i] + weight_temperature * avg_temperatures[i]) 
        for i in safe_range
    ]
    
    # Maximize the total benefit
    objective = mdl.sum(benefits[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Total Matches Constraint
    total_matches = mdl.sum(x[i] for i in safe_range)
    mdl.add_constraint(total_matches <= total_matches_limit, ctname="total_matches_limit")
    
    # Budget Constraint
    total_cost = mdl.sum(hosting_costs[i] * x[i] for i in safe_range)
    mdl.add_constraint(total_cost <= total_budget, ctname="total_budget_limit")
    
    # Maximum Matches Per City Constraint (already enforced by variable bounds)
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_range:
            value = solution.get_value(x[i])
            if value > 1e-6:
                print(f"Matches in city {city_ids[i]}: {value}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2227040.8
**Execution Time**: 0.97 seconds
**Reliability**: high
**Analysis**: DOCplex also found the optimal solution but took slightly longer (0.965 seconds) compared to Gurobipy. It is still a reliable solver for this problem.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def host_city_optimization():
    """Optimization model for selecting host cities for matches."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # City data
    city_ids = [1, 2, 3]
    populations = [800000, 1200000, 600000]
    gdps = [75000.0, 110000.0, 60000.0]
    avg_temperatures = [22.0, 26.0, 20.0]
    hosting_costs = [120000.0, 180000.0, 100000.0]
    
    # Objective weights
    weight_population = 0.4
    weight_gdp = 0.3
    weight_temperature = 0.3
    
    # Constraints
    max_matches_per_city = 2
    total_budget = 1000000.0
    total_matches = 10
    
    # CRITICAL: Validate array lengths before indexing
    assert len(city_ids) == len(populations) == len(gdps) == len(avg_temperatures) == len(hosting_costs), "Array length mismatch"
    
    # 3. SETS
    model.I = pyo.Set(initialize=city_ids)  # Set of cities
    
    # 4. PARAMETERS
    model.population = pyo.Param(model.I, initialize={i: p for i, p in zip(city_ids, populations)})
    model.gdp = pyo.Param(model.I, initialize={i: g for i, g in zip(city_ids, gdps)})
    model.avg_temperature = pyo.Param(model.I, initialize={i: t for i, t in zip(city_ids, avg_temperatures)})
    model.hosting_cost = pyo.Param(model.I, initialize={i: c for i, c in zip(city_ids, hosting_costs)})
    
    # 5. VARIABLES
    model.x = pyo.Var(model.I, within=pyo.NonNegativeIntegers, bounds=(0, max_matches_per_city))  # Number of matches per city
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum((weight_population * model.population[i] + 
                    weight_gdp * model.gdp[i] + 
                    weight_temperature * model.avg_temperature[i]) * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    # Total matches constraint
    def total_matches_rule(model):
        return sum(model.x[i] for i in model.I) <= total_matches
    model.total_matches_constraint = pyo.Constraint(rule=total_matches_rule)
    
    # Budget constraint
    def budget_rule(model):
        return sum(model.hosting_cost[i] * model.x[i] for i in model.I) <= total_budget
    model.budget_constraint = pyo.Constraint(rule=budget_rule)
    
    # Maximum matches per city constraint
    def max_matches_rule(model, i):
        return model.x[i] <= max_matches_per_city
    model.max_matches_constraint = pyo.Constraint(model.I, rule=max_matches_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("\nVariable values:")
        for i in model.I:
            x_val = pyo.value(model.x[i])
            if x_val > 1e-6:  # Only print non-zero values
                print(f"x[{i}] = {x_val:.0f}")
        
    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__":
    host_city_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2227040.8
**Execution Time**: 0.88 seconds
**Reliability**: high
**Analysis**: Pyomo found the optimal solution with an execution time of 0.881 seconds. It is reliable but slightly slower than Gurobipy.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 2227040.80 | 0.19s | N/A | N/A |
| Docplex | OPTIMAL | 2227040.80 | 0.97s | N/A | N/A |
| Pyomo | OPTIMAL | 2227040.80 | 0.88s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 2227040.8
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its faster execution time and high reliability. All solvers produced consistent results, but Gurobipy performed the best in terms of speed.

### Optimal Decision Variables
- **x_1** = 2.000
  - *Business Meaning*: Hosting 2 matches in City 1 maximizes the benefit from its population, GDP, and temperature.
- **x_2** = 2.000
  - *Business Meaning*: Hosting 2 matches in City 2 maximizes the benefit from its population, GDP, and temperature.
- **x_3** = 2.000
  - *Business Meaning*: Hosting 2 matches in City 3 maximizes the benefit from its population, GDP, and temperature.

### Business Interpretation
**Overall Strategy**: The optimal solution suggests hosting 2 matches in each city to maximize the total benefit, which is a combination of population, GDP, and average temperature. This allocation ensures the budget is fully utilized while adhering to the constraints.
**Objective Value Meaning**: The optimal objective value of 2,227,040.8 represents the total benefit achieved by hosting 2 matches in each city, considering the weighted contributions of population, GDP, and temperature.
**Resource Allocation Summary**: The budget of 1,000,000 is fully utilized by hosting 2 matches in each city, with hosting costs of 120,000, 180,000, and 100,000 respectively. This ensures the maximum benefit is achieved within the constraints.
**Implementation Recommendations**: 1. Allocate resources to host 2 matches in each city. 2. Ensure the budget is not exceeded by monitoring hosting costs. 3. Verify that the maximum matches per city constraint is adhered to.