## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be the number of screenings for film \( j \) in cinema \( i \).
  - \( x_{ij} \) is an integer variable representing the decision of how many times film \( j \) is shown in cinema \( i \).

#### Objective Function
Maximize the total revenue from all screenings across all cinemas:
\[
\text{Maximize } Z = \sum_{i,j} \text{Price}_{ij} \times x_{ij}
\]
where \(\text{Price}_{ij}\) is the price per screening for film \( j \) in cinema \( i \).

Data Source Verification:
- \(\text{Price}_{ij}\) comes from the `film_pricing.Price` column.

#### Constraints
1. **Maximum Screenings per Cinema:**
   - For each cinema \( i \), the total number of screenings must not exceed the maximum allowed:
   \[
   \sum_{j} x_{ij} \leq \text{Max\_Screenings\_Per\_Day}_i
   \]
   Data Source Verification:
   - \(\text{Max\_Screenings\_Per\_Day}_i\) comes from the `cinema.Max_Screenings_Per_Day` column.

2. **Seating Capacity Constraint:**
   - For each cinema \( i \) and film \( j \), the expected number of attendees per screening must not exceed the cinema's capacity. Assuming average attendance per screening is a known parameter \( \text{Avg\_Attendance}_{ij} \):
   \[
   \text{Avg\_Attendance}_{ij} \times x_{ij} \leq \text{Capacity}_i
   \]
   Data Source Verification:
   - \(\text{Capacity}_i\) comes from the `cinema.Capacity` column.
   - \(\text{Avg\_Attendance}_{ij}\) is assumed to be a known parameter from business configuration or historical data.

3. **Non-negativity and Integer Constraints:**
   - \( x_{ij} \geq 0 \) and integer for all \( i, j \).

This linear programming model is designed to maximize the total revenue from film screenings while adhering to the operational constraints of each cinema. The decision variables, objective function, and constraints are all expressed in linear terms, ensuring the model is suitable for linear or mixed-integer programming solvers.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_cinema_schedule():
    """Optimize cinema film scheduling to maximize revenue"""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("cinema_schedule")
    
    # Data from the database
    cinema_data = [
        {"Cinema_ID": 1, "Capacity": 120, "Max_Screenings_Per_Day": 5},
        {"Cinema_ID": 2, "Capacity": 180, "Max_Screenings_Per_Day": 6},
        {"Cinema_ID": 3, "Capacity": 250, "Max_Screenings_Per_Day": 7}
    ]
    
    film_pricing_data = [
        {"Cinema_ID_Film_ID": 101, "Price": 12.0},
        {"Cinema_ID_Film_ID": 102, "Price": 15.0},
        {"Cinema_ID_Film_ID": 103, "Price": 10.0}
    ]
    
    avg_attendance = {
        101: 100,  # Example average attendance for film 101
        102: 150,  # Example average attendance for film 102
        103: 200   # Example average attendance for film 103
    }
    
    # Validate data lengths
    assert len(cinema_data) == 3, "Cinema data length mismatch"
    assert len(film_pricing_data) == 3, "Film pricing data length mismatch"
    
    # 2. VARIABLES
    # Decision variables: number of screenings for each film in each cinema
    x = {}
    for cinema in cinema_data:
        for film in film_pricing_data:
            x[(cinema["Cinema_ID"], film["Cinema_ID_Film_ID"])] = model.addVar(
                vtype=GRB.INTEGER, name=f"x_{cinema['Cinema_ID']}_{film['Cinema_ID_Film_ID']}", lb=0
            )
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total revenue
    model.setObjective(
        gp.quicksum(
            film["Price"] * x[(cinema["Cinema_ID"], film["Cinema_ID_Film_ID"])]
            for cinema in cinema_data
            for film in film_pricing_data
        ),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    # Maximum screenings per cinema
    for cinema in cinema_data:
        model.addConstr(
            gp.quicksum(
                x[(cinema["Cinema_ID"], film["Cinema_ID_Film_ID"])]
                for film in film_pricing_data
            ) <= cinema["Max_Screenings_Per_Day"],
            name=f"max_screenings_{cinema['Cinema_ID']}"
        )
    
    # Seating capacity constraint
    for cinema in cinema_data:
        for film in film_pricing_data:
            model.addConstr(
                avg_attendance[film["Cinema_ID_Film_ID"]] * x[(cinema["Cinema_ID"], film["Cinema_ID_Film_ID"])] <= cinema["Capacity"],
                name=f"capacity_{cinema['Cinema_ID']}_{film['Cinema_ID_Film_ID']}"
            )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for cinema in cinema_data:
            for film in film_pricing_data:
                var = x[(cinema["Cinema_ID"], film["Cinema_ID_Film_ID"])]
                if var.x > 1e-6:
                    print(f"x[{cinema['Cinema_ID']}, {film['Cinema_ID_Film_ID']}] = {var.x:.3f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_cinema_schedule()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 88.0
**Execution Time**: 0.51 seconds
**Reliability**: high
**Analysis**: Gurobipy found an optimal solution with a high objective value of 88.0, indicating a reliable result given its widespread use and efficiency in solving linear and mixed-integer programming problems.

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation

from docplex.mp.model import Model

def cinema_optimization():
    """Optimize cinema film scheduling to maximize revenue"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="cinema_optimization")
    
    # Data from the problem context
    cinema_data = [
        {"Cinema_ID": 1, "Capacity": 120, "Max_Screenings_Per_Day": 5},
        {"Cinema_ID": 2, "Capacity": 180, "Max_Screenings_Per_Day": 6},
        {"Cinema_ID": 3, "Capacity": 250, "Max_Screenings_Per_Day": 7}
    ]
    
    film_schedule_data = [
        {"Cinema_ID_Film_ID": 101, "Show_Times": 3},
        {"Cinema_ID_Film_ID": 102, "Show_Times": 4},
        {"Cinema_ID_Film_ID": 103, "Show_Times": 2}
    ]
    
    film_pricing_data = [
        {"Cinema_ID_Film_ID": 101, "Price": 12.0},
        {"Cinema_ID_Film_ID": 102, "Price": 15.0},
        {"Cinema_ID_Film_ID": 103, "Price": 10.0}
    ]
    
    # Assumed average attendance per screening
    avg_attendance = {
        101: 100,
        102: 150,
        103: 200
    }
    
    # Extracting data into lists for processing
    cinema_ids = [c["Cinema_ID"] for c in cinema_data]
    capacities = [c["Capacity"] for c in cinema_data]
    max_screenings = [c["Max_Screenings_Per_Day"] for c in cinema_data]
    
    film_ids = [f["Cinema_ID_Film_ID"] for f in film_schedule_data]
    prices = [p["Price"] for p in film_pricing_data]
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(cinema_ids) == len(capacities) == len(max_screenings), "Cinema data length mismatch"
    assert len(film_ids) == len(prices), "Film data length mismatch"
    
    safe_range_cinema = range(len(cinema_ids))
    safe_range_film = range(len(film_ids))
    
    # 2. VARIABLES
    x = {j: mdl.integer_var(name=f"x_{film_ids[j]}", lb=0) for j in safe_range_film}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(prices[j] * x[j] for j in safe_range_film)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    # Maximum screenings per cinema
    for i in safe_range_cinema:
        cinema_id = cinema_ids[i]
        mdl.add_constraint(
            mdl.sum(x[j] for j in safe_range_film if film_ids[j] // 100 == cinema_id) <= max_screenings[i],
            ctname=f"max_screenings_cinema_{cinema_id}"
        )
    
    # Seating capacity constraints
    for j in safe_range_film:
        cinema_id = film_ids[j] // 100
        avg_att = avg_attendance[film_ids[j]]
        capacity = next(c["Capacity"] for c in cinema_data if c["Cinema_ID"] == cinema_id)
        mdl.add_constraint(
            avg_att * x[j] <= capacity,
            ctname=f"capacity_constraint_{film_ids[j]}"
        )
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for j in safe_range_film:
            value = solution.get_value(x[j])
            if value > 1e-6:
                print(f"x[{film_ids[j]}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
cinema_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 12.0
**Execution Time**: 7.74 seconds
**Reliability**: low
**Analysis**: DOCplex also reported an optimal status but with a significantly lower objective value of 12.0, which raises concerns about its reliability in this instance. The discrepancy suggests potential issues with model formulation or solver settings.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def cinema_optimization():
    """Cinema scheduling optimization using Pyomo"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Assuming data is provided as lists or dictionaries
    cinema_ids = [1, 2, 3]
    film_ids = [101, 102, 103]
    
    # Cinema data
    capacities = {1: 120, 2: 180, 3: 250}
    max_screenings_per_day = {1: 5, 2: 6, 3: 7}
    
    # Film pricing data
    prices = {101: 12.0, 102: 15.0, 103: 10.0}
    
    # Average attendance per screening (assumed data)
    avg_attendance = {101: 100, 102: 150, 103: 200}
    
    # CRITICAL: Validate data lengths before indexing
    assert len(capacities) == len(max_screenings_per_day) == len(cinema_ids), "Cinema data length mismatch"
    assert len(prices) == len(avg_attendance) == len(film_ids), "Film data length mismatch"
    
    # 3. SETS
    model.C = pyo.Set(initialize=cinema_ids)
    model.F = pyo.Set(initialize=film_ids)
    
    # 4. PARAMETERS
    model.capacity = pyo.Param(model.C, initialize=capacities)
    model.max_screenings = pyo.Param(model.C, initialize=max_screenings_per_day)
    model.price = pyo.Param(model.F, initialize=prices)
    model.avg_attendance = pyo.Param(model.F, initialize=avg_attendance)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.C, model.F, within=pyo.NonNegativeIntegers)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.price[j] * model.x[i, j] for i in model.C for j in model.F)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Maximum screenings per cinema
    def max_screenings_rule(model, i):
        return sum(model.x[i, j] for j in model.F) <= model.max_screenings[i]
    model.max_screenings_constraint = pyo.Constraint(model.C, rule=max_screenings_rule)
    
    # Seating capacity constraint
    def capacity_rule(model, i, j):
        return model.avg_attendance[j] * model.x[i, j] <= model.capacity[i]
    model.capacity_constraint = pyo.Constraint(model.C, model.F, rule=capacity_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("\nScreening schedule:")
        for i in model.C:
            for j in model.F:
                x_val = pyo.value(model.x[i, j])
                if x_val > 0:  # Only print non-zero values
                    print(f"Cinema {i}, Film {j}: {x_val} screenings")
        
    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
cinema_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 88.0
**Execution Time**: 4.29 seconds
**Reliability**: high
**Analysis**: Pyomo achieved an optimal solution with the same objective value as Gurobipy, reinforcing the reliability of this result. Pyomo's flexibility and compatibility with various solvers contribute to its high reliability.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 88.00 | 0.51s | N/A | N/A |
| Docplex | OPTIMAL | 12.00 | 7.74s | N/A | N/A |
| Pyomo | OPTIMAL | 88.00 | 4.29s | N/A | N/A |

### Solver Consistency Analysis
**Result**: Solvers produced inconsistent results
**Consistent Solvers**: gurobipy, pyomo
**Inconsistent Solvers**: docplex
**Potential Issues**:
- DOCplex may have encountered a different local optimum or misinterpreted constraints.
- Possible data input errors or solver configuration issues specific to DOCplex.
**Majority Vote Optimal Value**: 88.0

### Final Recommendation
**Recommended Optimal Value**: 88.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: multiple
**Reasoning**: Both Gurobipy and Pyomo provided consistent and high objective values, indicating a reliable solution. Using multiple solvers can validate results and ensure robustness.

### Business Interpretation
**Overall Strategy**: The optimal value of 88.0 suggests a robust strategy for maximizing revenue across cinemas, aligning with business goals.
**Objective Value Meaning**: The optimal objective value of 88.0 represents the maximum achievable revenue from film screenings, given the constraints.
**Resource Allocation Summary**: Allocate screenings to maximize revenue while respecting cinema capacity and screening limits.
**Implementation Recommendations**: Ensure accurate data input and solver configuration. Consider using both Gurobipy and Pyomo for validation. Regularly update parameters based on historical data and market trends.