# Complete Optimization Problem and Solution: roller_coaster

## 1. Problem Context and Goals

### Context  
A global theme park chain is focused on enhancing visitor satisfaction by strategically distributing roller coasters across its parks. The decision involves determining the number of roller coasters of each type to install in each park, considering visitor satisfaction scores, budget limits, available space, and maximum roller coaster capacity per park. Each roller coaster type has a fixed cost and space requirement, which are critical for ensuring that the installation plans are financially viable and physically feasible. The goal is to make operational decisions that maximize visitor satisfaction while adhering to these constraints.  

The business configuration includes the cost of each roller coaster type, set at $500,000, and the space required for each roller coaster type, set at 2,000 square units. These parameters are used to calculate budget and space constraints, ensuring that the optimization problem remains linear and aligned with real-world operational limitations.  

### Goals  
The primary goal of this optimization problem is to maximize the total visitor satisfaction across all parks. This is achieved by determining the optimal number of roller coasters of each type to install in each park, weighted by their respective visitor satisfaction scores. Success is measured by the overall increase in visitor satisfaction, which is directly influenced by the number and type of roller coasters installed. The optimization ensures that the decisions are made within the bounds of budget, space, and maximum roller coaster capacity for each park.  

## 2. Constraints  

The optimization problem is subject to the following constraints:  
1. **Budget Constraint**: The total cost of installing roller coasters in each park must not exceed the park’s allocated budget. The cost of each roller coaster type is fixed at $500,000, and the total cost is calculated by multiplying this cost by the number of roller coasters of each type installed in the park.  
2. **Space Constraint**: The total space occupied by roller coasters in each park must not exceed the park’s available space. Each roller coaster type requires 2,000 square units of space, and the total space is calculated by multiplying this requirement by the number of roller coasters of each type installed in the park.  
3. **Maximum Roller Coasters Constraint**: The total number of roller coasters installed in each park must not exceed the park’s maximum allowed capacity. This ensures that the park’s infrastructure and operational capabilities are not overwhelmed.  

These constraints ensure that the optimization problem remains linear and aligned with the operational realities of the theme parks.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for visitor satisfaction scores, budget limits, available space, and maximum roller coasters per park. Configuration logic updates include scalar parameters for cost and space requirements of roller coaster types.

CREATE TABLE visitor_satisfaction_scores (
  park_id INTEGER,
  coaster_type STRING,
  score FLOAT,
  num_coasters INTEGER
);

CREATE TABLE park_budgets (
  park_id INTEGER,
  budget INTEGER
);

CREATE TABLE park_available_space (
  park_id INTEGER,
  space INTEGER
);

CREATE TABLE park_max_coasters (
  park_id INTEGER,
  max_coasters INTEGER
);
```

### Data Dictionary  
- **visitor_satisfaction_scores**:  
  - **park_id**: Unique identifier for each park.  
  - **coaster_type**: Type of roller coaster (e.g., Wooden, Steel, Inverted).  
  - **score**: Visitor satisfaction score for the roller coaster type in the park.  
  - **num_coasters**: Number of roller coasters of this type in the park.  

- **park_budgets**:  
  - **park_id**: Unique identifier for each park.  
  - **budget**: Budget limit for the park.  

- **park_available_space**:  
  - **park_id**: Unique identifier for each park.  
  - **space**: Available space for roller coasters in the park.  

- **park_max_coasters**:  
  - **park_id**: Unique identifier for each park.  
  - **max_coasters**: Maximum number of roller coasters allowed in the park.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on industry standards, realistic theme park budgets, and typical roller coaster costs and space requirements. The data ensures that the optimization problem is meaningful and solvable by providing a balance between visitor satisfaction, budget constraints, and space limitations.

-- Realistic data for visitor_satisfaction_scores
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (1, 'Wooden', 8.5, 2);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (1, 'Steel', 9.0, 3);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (1, 'Inverted', 7.5, 1);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (2, 'Wooden', 8.0, 1);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (2, 'Steel', 9.5, 4);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (2, 'Inverted', 8.0, 2);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (3, 'Wooden', 7.0, 1);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (3, 'Steel', 9.0, 5);
INSERT INTO visitor_satisfaction_scores (park_id, coaster_type, score, num_coasters) VALUES (3, 'Inverted', 8.5, 3);

-- Realistic data for park_budgets
INSERT INTO park_budgets (park_id, budget) VALUES (1, 1000000);
INSERT INTO park_budgets (park_id, budget) VALUES (2, 1500000);
INSERT INTO park_budgets (park_id, budget) VALUES (3, 2000000);

-- Realistic data for park_available_space
INSERT INTO park_available_space (park_id, space) VALUES (1, 10000);
INSERT INTO park_available_space (park_id, space) VALUES (2, 15000);
INSERT INTO park_available_space (park_id, space) VALUES (3, 20000);

-- Realistic data for park_max_coasters
INSERT INTO park_max_coasters (park_id, max_coasters) VALUES (1, 5);
INSERT INTO park_max_coasters (park_id, max_coasters) VALUES (2, 7);
INSERT INTO park_max_coasters (park_id, max_coasters) VALUES (3, 10);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
Let \( x_{p,c} \) be the number of roller coasters of type \( c \) to install in park \( p \), where:
- \( p \in \{1, 2, 3\} \) (park IDs)
- \( c \in \{\text{Wooden}, \text{Steel}, \text{Inverted}\} \) (roller coaster types)

#### Objective Function
Maximize the total visitor satisfaction:
\[
\text{Maximize} \quad \sum_{p=1}^{3} \sum_{c} \text{visitor\_satisfaction\_scores.score}_{p,c} \times x_{p,c}
\]
Data Source Verification:
- Coefficients \( \text{visitor\_satisfaction\_scores.score}_{p,c} \) come from the `visitor_satisfaction_scores.score` column.

#### Constraints
1. **Budget Constraint**: The total cost of roller coasters in each park must not exceed the park’s budget.
\[
\sum_{c} 500000 \times x_{p,c} \leq \text{park\_budgets.budget}_p \quad \forall p
\]
Data Source Verification:
- Coefficient \( 500000 \) is the fixed cost per roller coaster (business configuration).
- Right-hand side \( \text{park\_budgets.budget}_p \) comes from the `park_budgets.budget` column.

2. **Space Constraint**: The total space occupied by roller coasters in each park must not exceed the park’s available space.
\[
\sum_{c} 2000 \times x_{p,c} \leq \text{park\_available\_space.space}_p \quad \forall p
\]
Data Source Verification:
- Coefficient \( 2000 \) is the fixed space requirement per roller coaster (business configuration).
- Right-hand side \( \text{park\_available\_space.space}_p \) comes from the `park_available_space.space` column.

3. **Maximum Roller Coasters Constraint**: The total number of roller coasters in each park must not exceed the park’s maximum capacity.
\[
\sum_{c} x_{p,c} \leq \text{park\_max\_coasters.max\_coasters}_p \quad \forall p
\]
Data Source Verification:
- Right-hand side \( \text{park\_max\_coasters.max\_coasters}_p \) comes from the `park_max_coasters.max_coasters` column.

4. **Non-Negativity Constraint**: The number of roller coasters cannot be negative.
\[
x_{p,c} \geq 0 \quad \forall p, c
\]

This formulation provides a complete, immediately solvable LINEAR mathematical model with all numerical coefficients derived from the provided data.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy Implementation for Roller Coaster Optimization Problem
"""

import gurobipy as gp
from gurobipy import GRB

def roller_coaster_optimization():
    """Optimize roller coaster distribution across theme parks"""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("roller_coaster_optimization")
    
    # Data from the problem
    parks = [1, 2, 3]
    coaster_types = ['Wooden', 'Steel', 'Inverted']
    
    # Visitor satisfaction scores
    satisfaction_scores = {
        (1, 'Wooden'): 8.5,
        (1, 'Steel'): 9.0,
        (1, 'Inverted'): 7.5,
        (2, 'Wooden'): 8.0,
        (2, 'Steel'): 9.5,
        (2, 'Inverted'): 8.0,
        (3, 'Wooden'): 7.0,
        (3, 'Steel'): 9.0,
        (3, 'Inverted'): 8.5
    }
    
    # Park budgets
    park_budgets = {1: 1000000, 2: 1500000, 3: 2000000}
    
    # Park available space
    park_space = {1: 10000, 2: 15000, 3: 20000}
    
    # Park maximum roller coasters
    park_max_coasters = {1: 5, 2: 7, 3: 10}
    
    # Fixed cost and space per roller coaster
    cost_per_coaster = 500000
    space_per_coaster = 2000
    
    # CRITICAL: Validate array lengths before loops
    assert len(parks) == len(park_budgets) == len(park_space) == len(park_max_coasters), "Array length mismatch"
    
    # 2. VARIABLES
    x = model.addVars(parks, coaster_types, vtype=GRB.INTEGER, name="x", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(
        gp.quicksum(satisfaction_scores[p, c] * x[p, c] for p in parks for c in coaster_types),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Budget Constraint
    for p in parks:
        model.addConstr(
            gp.quicksum(cost_per_coaster * x[p, c] for c in coaster_types) <= park_budgets[p],
            name=f"budget_{p}"
        )
    
    # Space Constraint
    for p in parks:
        model.addConstr(
            gp.quicksum(space_per_coaster * x[p, c] for c in coaster_types) <= park_space[p],
            name=f"space_{p}"
        )
    
    # Maximum Roller Coasters Constraint
    for p in parks:
        model.addConstr(
            gp.quicksum(x[p, c] for c in coaster_types) <= park_max_coasters[p],
            name=f"max_coasters_{p}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for p in parks:
            for c in coaster_types:
                if x[p, c].x > 1e-6:
                    print(f"x[{p}, {c}] = {x[p, c].x:.0f}")
    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__":
    roller_coaster_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 82.5
**Execution Time**: 0.17 seconds
**Reliability**: high
**Analysis**: Gurobipy achieved the optimal solution efficiently with the fastest execution time.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCplex implementation for roller coaster optimization problem
"""

from docplex.mp.model import Model

def roller_coaster_optimization():
    """Optimize roller coaster distribution across parks to maximize visitor satisfaction"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="roller_coaster_optimization")
    
    # Data from the problem context
    parks = [1, 2, 3]
    coaster_types = ['Wooden', 'Steel', 'Inverted']
    
    # Visitor satisfaction scores
    visitor_satisfaction = {
        (1, 'Wooden'): 8.5,
        (1, 'Steel'): 9.0,
        (1, 'Inverted'): 7.5,
        (2, 'Wooden'): 8.0,
        (2, 'Steel'): 9.5,
        (2, 'Inverted'): 8.0,
        (3, 'Wooden'): 7.0,
        (3, 'Steel'): 9.0,
        (3, 'Inverted'): 8.5
    }
    
    # Park budgets
    park_budgets = {1: 1000000, 2: 1500000, 3: 2000000}
    
    # Park available space
    park_available_space = {1: 10000, 2: 15000, 3: 20000}
    
    # Park maximum roller coasters
    park_max_coasters = {1: 5, 2: 7, 3: 10}
    
    # Fixed cost and space requirements
    cost_per_coaster = 500000
    space_per_coaster = 2000
    
    # 2. VARIABLES
    x = {(p, c): mdl.integer_var(name=f"x_{p}_{c}", lb=0) for p in parks for c in coaster_types}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(visitor_satisfaction[(p, c)] * x[(p, c)] for p in parks for c in coaster_types)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Budget Constraint
    for p in parks:
        budget_expr = mdl.sum(cost_per_coaster * x[(p, c)] for c in coaster_types)
        mdl.add_constraint(budget_expr <= park_budgets[p], ctname=f"budget_{p}")
    
    # Space Constraint
    for p in parks:
        space_expr = mdl.sum(space_per_coaster * x[(p, c)] for c in coaster_types)
        mdl.add_constraint(space_expr <= park_available_space[p], ctname=f"space_{p}")
    
    # Maximum Roller Coasters Constraint
    for p in parks:
        max_coasters_expr = mdl.sum(x[(p, c)] for c in coaster_types)
        mdl.add_constraint(max_coasters_expr <= park_max_coasters[p], ctname=f"max_coasters_{p}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for p in parks:
            for c in coaster_types:
                value = solution.get_value(x[(p, c)])
                if value > 1e-6:
                    print(f"Park {p}, Coaster Type {c}: {value:.0f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 82.5
**Execution Time**: 1.12 seconds
**Reliability**: high
**Analysis**: DOCplex also achieved the optimal solution but with a longer execution time compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def roller_coaster_optimization():
    """Optimize roller coaster distribution across theme parks to maximize visitor satisfaction."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    parks = [1, 2, 3]
    coaster_types = ['Wooden', 'Steel', 'Inverted']
    
    # Visitor satisfaction scores
    satisfaction_scores = {
        (1, 'Wooden'): 8.5,
        (1, 'Steel'): 9.0,
        (1, 'Inverted'): 7.5,
        (2, 'Wooden'): 8.0,
        (2, 'Steel'): 9.5,
        (2, 'Inverted'): 8.0,
        (3, 'Wooden'): 7.0,
        (3, 'Steel'): 9.0,
        (3, 'Inverted'): 8.5
    }
    
    # Park budgets
    park_budgets = {1: 1000000, 2: 1500000, 3: 2000000}
    
    # Park available space
    park_space = {1: 10000, 2: 15000, 3: 20000}
    
    # Park maximum roller coasters
    park_max_coasters = {1: 5, 2: 7, 3: 10}
    
    # Fixed cost and space per roller coaster
    cost_per_coaster = 500000
    space_per_coaster = 2000
    
    # 3. SETS
    model.P = pyo.Set(initialize=parks)
    model.C = pyo.Set(initialize=coaster_types)
    
    # 4. PARAMETERS
    model.satisfaction = pyo.Param(model.P, model.C, initialize=satisfaction_scores)
    model.budget = pyo.Param(model.P, initialize=park_budgets)
    model.space = pyo.Param(model.P, initialize=park_space)
    model.max_coasters = pyo.Param(model.P, initialize=park_max_coasters)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.P, model.C, within=pyo.NonNegativeIntegers)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.satisfaction[p, c] * model.x[p, c] for p in model.P for c in model.C)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Budget Constraint
    def budget_rule(model, p):
        return sum(cost_per_coaster * model.x[p, c] for c in model.C) <= model.budget[p]
    model.budget_constraint = pyo.Constraint(model.P, rule=budget_rule)
    
    # Space Constraint
    def space_rule(model, p):
        return sum(space_per_coaster * model.x[p, c] for c in model.C) <= model.space[p]
    model.space_constraint = pyo.Constraint(model.P, rule=space_rule)
    
    # Maximum Roller Coasters Constraint
    def max_coasters_rule(model, p):
        return sum(model.x[p, c] for c in model.C) <= model.max_coasters[p]
    model.max_coasters_constraint = pyo.Constraint(model.P, rule=max_coasters_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 p in model.P:
            for c in model.C:
                x_val = pyo.value(model.x[p, c])
                if x_val > 1e-6:  # Only print non-zero values
                    print(f"x[{p}, {c}] = {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__":
    roller_coaster_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 82.5
**Execution Time**: 1.03 seconds
**Reliability**: high
**Analysis**: Pyomo achieved the optimal solution with an execution time slightly better than DOCplex but still 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 | 82.50 | 0.17s | N/A | N/A |
| Docplex | OPTIMAL | 82.50 | 1.12s | N/A | N/A |
| Pyomo | OPTIMAL | 82.50 | 1.03s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 82.5
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its optimal solution and significantly faster execution time compared to DOCplex and Pyomo.

### Business Interpretation
**Overall Strategy**: The optimal visitor satisfaction score of 82.5 indicates the best possible allocation of roller coasters across the parks within the given constraints.
**Objective Value Meaning**: The optimal objective value of 82.5 represents the maximum total visitor satisfaction achievable with the given constraints.
**Resource Allocation Summary**: Resources should be allocated to install the optimal number of each type of roller coaster in each park, ensuring budget, space, and capacity constraints are not exceeded.
**Implementation Recommendations**: Proceed with the installation of roller coasters as per the optimal solution, ensuring all constraints are adhered to. Monitor the execution to ensure alignment with the planned resource allocation.