# Complete Optimization Problem and Solution: restaurants

## 1. Problem Context and Goals

### Context  
A restaurant chain is strategically planning the placement of new restaurants across various cities to maximize overall customer satisfaction. Customer satisfaction is measured by the restaurant's rating, which serves as a key indicator of success. The decision to place a new restaurant in a specific city is represented by a binary choice: either to place the restaurant or not. The goal is to ensure that the total customer satisfaction across all new placements is maximized while adhering to several operational constraints.  

The business has established specific configuration parameters to guide this decision-making process:  
- **Maximum number of new restaurants allowed per city**: To prevent over-saturation, no more than 2 new restaurants can be placed in any single city.  
- **Minimum number of new restaurants required per region**: To maintain a balanced distribution, at least 1 new restaurant must be placed in each geographical region.  
- **Total budget constraint for the number of new restaurants**: The total number of new restaurants placed across all cities cannot exceed 5, ensuring financial feasibility.  

These parameters ensure that the optimization problem remains linear and avoids scenarios requiring complex nonlinear relationships, such as variable products or divisions.  

### Goals  
The primary goal of this optimization problem is to maximize the total customer satisfaction derived from the placement of new restaurants. This is achieved by selecting the optimal set of cities for new restaurant placements, where each placement contributes to the overall satisfaction based on the restaurant's rating. Success is measured by the sum of the ratings of all selected restaurants, ensuring that the highest possible customer satisfaction is achieved within the defined constraints.  

## 2. Constraints  

The optimization problem is subject to the following constraints, which ensure that the solution aligns with the business's operational and financial limitations:  
1. **Total budget constraint**: The total number of new restaurants placed across all cities must not exceed the budget of 5. This ensures that the financial resources are not overextended.  
2. **Regional distribution constraint**: Each geographical region must have at least 1 new restaurant placed. This ensures a fair and balanced distribution of new restaurants across all regions.  
3. **City-level placement constraint**: No more than 2 new restaurants can be placed in any single city. This prevents over-saturation and ensures a diverse placement strategy.  

These constraints are designed to naturally lead to linear mathematical forms, avoiding any complexity that would require nonlinear relationships.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for decision variables and constraint bounds, moving scalar parameters to business configuration logic, and updating the data dictionary to reflect optimization mappings.

CREATE TABLE RESTAURANT (
  RATING FLOAT,
  REGION STRING
);

CREATE TABLE DECISION_VARIABLES (
  X_i BOOLEAN
);
```

### Data Dictionary  
- **RESTAURANT Table**:  
  - **RATING**: Represents the customer satisfaction rating of the restaurant. This value is used as the coefficient in the objective function to maximize total satisfaction.  
  - **REGION**: Indicates the geographical region where the restaurant is located. This supports the regional distribution constraint.  

- **DECISION_VARIABLES Table**:  
  - **X_i**: A binary decision variable that indicates whether to place a new restaurant in city i. This is the key variable in the optimization model.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic business scenarios, ensuring that the optimization problem remains meaningful and solvable. The data respects the constraints and relationships defined in the schema and business configuration logic.

-- Realistic data for RESTAURANT
INSERT INTO RESTAURANT (RATING, REGION) VALUES (4.5, 'North');
INSERT INTO RESTAURANT (RATING, REGION) VALUES (3.8, 'South');
INSERT INTO RESTAURANT (RATING, REGION) VALUES (4.2, 'East');
INSERT INTO RESTAURANT (RATING, REGION) VALUES (3.9, 'West');
INSERT INTO RESTAURANT (RATING, REGION) VALUES (4.1, 'North');

-- Realistic data for DECISION_VARIABLES
INSERT INTO DECISION_VARIABLES (X_i) VALUES (True);
INSERT INTO DECISION_VARIABLES (X_i) VALUES (False);
INSERT INTO DECISION_VARIABLES (X_i) VALUES (True);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- \( x_i \): Binary decision variable indicating whether to place a new restaurant in city \( i \).  
  \( x_i \in \{0, 1\} \) for all \( i \).  
  (Source: DECISION_VARIABLES.X_i)

#### Objective Function
Maximize the total customer satisfaction:  
\[
\text{Maximize } Z = \sum_{i} (\text{RESTAURANT.RATING}_i \times x_i)
\]  
(Source: RESTAURANT.RATING)

#### Constraints
1. **Total budget constraint**: The total number of new restaurants placed across all cities must not exceed 5.  
\[
\sum_{i} x_i \leq 5
\]  
(Source: Business configuration parameter)

2. **Regional distribution constraint**: Each geographical region must have at least 1 new restaurant placed.  
\[
\sum_{i \in \text{Region } r} x_i \geq 1 \quad \forall r \in \{\text{North, South, East, West}\}
\]  
(Source: RESTAURANT.REGION)

3. **City-level placement constraint**: No more than 2 new restaurants can be placed in any single city.  
\[
x_i \leq 2 \quad \forall i
\]  
(Source: Business configuration parameter)

#### Data Source Verification
- **RESTAURANT.RATING**: Used as coefficients in the objective function.  
- **RESTAURANT.REGION**: Used to group cities by region for the regional distribution constraint.  
- **DECISION_VARIABLES.X_i**: Represents the binary decision variables.  
- **Business configuration parameters**: Used for the total budget constraint (5) and city-level placement constraint (2).

#### Numerical Example
Using the provided data:  
- **RESTAURANT.RATING**: [4.5, 3.8, 4.2, 3.9, 4.1]  
- **RESTAURANT.REGION**: ['North', 'South', 'East', 'West', 'North']  

The objective function becomes:  
\[
\text{Maximize } Z = 4.5x_1 + 3.8x_2 + 4.2x_3 + 3.9x_4 + 4.1x_5
\]

The constraints are:  
1. \( x_1 + x_2 + x_3 + x_4 + x_5 \leq 5 \)  
2. \( x_1 + x_5 \geq 1 \) (North)  
   \( x_2 \geq 1 \) (South)  
   \( x_3 \geq 1 \) (East)  
   \( x_4 \geq 1 \) (West)  
3. \( x_i \leq 2 \) for all \( i \in \{1, 2, 3, 4, 5\} \)  

This is a complete, immediately solvable linear programming (LP) model.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Restaurant Placement Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def restaurant_optimization():
    """Optimize restaurant placement to maximize customer satisfaction."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("restaurant_placement")
    
    # Data from the problem
    ratings = [4.5, 3.8, 4.2, 3.9, 4.1]
    regions = ['North', 'South', 'East', 'West', 'North']
    cities = range(len(ratings))
    unique_regions = list(set(regions))
    
    # CRITICAL: Validate array lengths before loops
    assert len(ratings) == len(regions), "Array length mismatch"
    
    # 2. VARIABLES
    # Binary decision variables for each city
    x = {i: model.addVar(vtype=GRB.BINARY, name=f"x_{i}") for i in cities}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total customer satisfaction
    model.setObjective(gp.quicksum(ratings[i] * x[i] for i in cities), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Total budget constraint: sum of x_i <= 5
    model.addConstr(gp.quicksum(x[i] for i in cities) <= 5, name="total_budget")
    
    # Regional distribution constraint: at least 1 restaurant per region
    for r in unique_regions:
        model.addConstr(gp.quicksum(x[i] for i in cities if regions[i] == r) >= 1, name=f"region_{r}")
    
    # City-level placement constraint: no more than 2 restaurants per city
    for i in cities:
        model.addConstr(x[i] <= 2, name=f"city_limit_{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 > 0:
                print(f"City {i} selected: {x[i].x}")
    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__":
    restaurant_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 20.5
**Execution Time**: 0.21 seconds
**Reliability**: high
**Analysis**: Gurobipy achieved the optimal solution with the fastest execution time (0.209 seconds). Its reliability is high due to its consistent performance and robust optimization capabilities.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCPLEX implementation for restaurant placement optimization
"""

from docplex.mp.model import Model

def restaurant_optimization():
    """Optimize restaurant placement to maximize customer satisfaction"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="restaurant_placement")
    
    # Data from the problem
    ratings = [4.5, 3.8, 4.2, 3.9, 4.1]
    regions = ['North', 'South', 'East', 'West', 'North']
    n_cities = len(ratings)
    
    # CRITICAL: Validate array lengths
    assert len(ratings) == len(regions) == n_cities, "Array length mismatch"
    safe_range = range(min(len(ratings), len(regions)))  # Safe indexing
    
    # 2. VARIABLES
    # Binary decision variables for each city
    x = {i: mdl.binary_var(name=f"x_{i}") for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total customer satisfaction
    objective = mdl.sum(ratings[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Total budget constraint: No more than 5 new restaurants
    total_restaurants = mdl.sum(x[i] for i in safe_range)
    mdl.add_constraint(total_restaurants <= 5, ctname="total_budget")
    
    # Regional distribution constraint: At least 1 restaurant per region
    unique_regions = set(regions)
    for region in unique_regions:
        region_restaurants = mdl.sum(x[i] for i in safe_range if regions[i] == region)
        mdl.add_constraint(region_restaurants >= 1, ctname=f"region_{region}")
    
    # City-level placement constraint: No more than 2 restaurants per city
    for i in safe_range:
        mdl.add_constraint(x[i] <= 2, ctname=f"city_limit_{i}")
    
    # 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"x[{i}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 20.5
**Execution Time**: 1.26 seconds
**Reliability**: medium
**Analysis**: DOCplex also achieved the optimal solution but with a longer execution time (1.255 seconds). Its reliability is medium due to slower performance compared to Gurobipy.

## 7. Pyomo Implementation

```python
#!/usr/bin/env python3
"""
Pyomo 6.9.2 Implementation for Restaurant Placement Optimization
"""

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

def restaurant_optimization():
    """Optimize restaurant placement to maximize customer satisfaction"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    ratings = [4.5, 3.8, 4.2, 3.9, 4.1]
    regions = ['North', 'South', 'East', 'West', 'North']
    
    # CRITICAL: Validate array lengths before indexing
    assert len(ratings) == len(regions), "Array length mismatch"
    
    # 3. SETS (Pyomo way to define indices)
    n_cities = len(ratings)
    model.I = pyo.RangeSet(1, n_cities)  # 1-based indexing
    
    # 4. PARAMETERS (data containers)
    model.rating = pyo.Param(model.I, initialize={i+1: ratings[i] for i in range(n_cities)})
    model.region = pyo.Param(model.I, initialize={i+1: regions[i] for i in range(n_cities)})
    
    # 5. VARIABLES
    # Binary decision variables
    model.x = pyo.Var(model.I, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.rating[i] * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Total budget constraint
    def budget_rule(model):
        return sum(model.x[i] for i in model.I) <= 5
    model.budget_constraint = pyo.Constraint(rule=budget_rule)
    
    # Regional distribution constraint
    regions_list = list(set(regions))
    def region_rule(model, r):
        return sum(model.x[i] for i in model.I if model.region[i] == r) >= 1
    model.region_constraint = pyo.Constraint(regions_list, rule=region_rule)
    
    # City-level placement constraint
    def city_rule(model, i):
        return model.x[i] <= 2
    model.city_constraint = pyo.Constraint(model.I, rule=city_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("\nSelected restaurants:")
        for i in model.I:
            if pyo.value(model.x[i]) > 0.5:  # Only print selected restaurants
                print(f"City {i}: Rating = {model.rating[i]}, Region = {model.region[i]}")
        
    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
if __name__ == "__main__":
    restaurant_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 20.5
**Execution Time**: 1.13 seconds
**Reliability**: medium
**Analysis**: Pyomo achieved the optimal solution with an execution time of 1.128 seconds. Its reliability is medium due to slower performance compared to Gurobipy.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 20.50 | 0.21s | N/A | N/A |
| Docplex | OPTIMAL | 20.50 | 1.26s | N/A | N/A |
| Pyomo | OPTIMAL | 20.50 | 1.13s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 20.5
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its faster execution time and high reliability, ensuring efficient and accurate results.

### Business Interpretation
**Overall Strategy**: The optimal solution maximizes customer satisfaction by strategically placing restaurants within the budget and regional constraints.
**Objective Value Meaning**: The optimal objective value of 20.5 represents the maximum total customer satisfaction achievable under the given constraints.
**Resource Allocation Summary**: Resources should be allocated to place restaurants in cities with the highest ratings while ensuring regional distribution and budget adherence.
**Implementation Recommendations**: 1. Prioritize cities with higher ratings. 2. Ensure at least one restaurant is placed in each region. 3. Monitor the budget to avoid exceeding the limit of 5 restaurants.