## 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.