# Complete Optimization Problem and Solution: cinema

## 1. Problem Context and Goals

### Context  
The cinema chain is focused on maximizing its revenue by strategically scheduling films across its various locations. Each cinema has a specific capacity and a limited number of show times available per day. The decision-making process involves determining the number of times each film should be shown in each cinema. This decision is represented by integer variables, where each variable corresponds to the number of screenings for a particular film in a specific cinema. The primary objective is to maximize the total revenue generated from these screenings. This is achieved by considering the price per screening for each film in each cinema, which serves as the coefficient in the revenue calculation. The operational parameters include the maximum number of screenings allowed per day for each cinema and the seating capacity, which must not be exceeded. These constraints ensure that the scheduling decisions remain feasible and align with the cinema's operational capabilities.

### Goals  
The primary goal of this optimization problem is to maximize the total revenue from film screenings across all cinemas. The metric for optimization is the total revenue, which is calculated by summing the product of the number of screenings and the price per screening for each film in each cinema. Success in this context is measured by the ability to achieve the highest possible revenue while adhering to the operational constraints of each cinema. The optimization goal is clearly defined in linear terms, focusing on maximizing revenue without involving complex mathematical operations.

## 2. Constraints    

The scheduling decisions are subject to several constraints that ensure the feasibility of the solution:

- Each cinema has a maximum number of screenings it can accommodate per day. The total number of screenings scheduled in a cinema must not exceed this limit.
- The number of attendees for each screening, based on average attendance, must not exceed the seating capacity of the cinema. This ensures that the cinema does not overbook and maintains a comfortable viewing experience for patrons.

These constraints are expressed in linear terms, focusing on the sum of screenings and the relationship between attendance and capacity, without involving nonlinear operations.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 2 Database Schema
-- Objective: Schema changes include adding a new table for objective coefficients, modifying existing tables to fill mapping gaps, and updating configuration logic for scalar parameters and formulas.

CREATE TABLE cinema (
  Cinema_ID INTEGER,
  Capacity INTEGER,
  Max_Screenings_Per_Day INTEGER
);

CREATE TABLE film_schedule (
  Cinema_ID_Film_ID INTEGER,
  Show_Times INTEGER
);

CREATE TABLE film_pricing (
  Cinema_ID_Film_ID INTEGER,
  Price FLOAT
);


```

### Data Dictionary  
The data dictionary provides a business-oriented view of the tables and columns, highlighting their roles in the optimization process:

- **Cinema Table**: This table contains information about each cinema, including its unique identifier, seating capacity, and the maximum number of screenings allowed per day. These attributes are crucial for defining the constraints related to capacity and scheduling limits.

- **Film Schedule Table**: This table records the number of times each film is shown in each cinema. The entries in this table represent the decision variables in the optimization problem, determining the scheduling strategy for maximizing revenue.

- **Film Pricing Table**: This table provides the pricing information for each film in each cinema. The price per screening serves as the coefficient in the revenue calculation, directly influencing the optimization objective.

### Current Stored Values  
```sql
-- Iteration 2 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical cinema operations, considering average cinema capacities, typical pricing strategies, and realistic scheduling constraints.

-- Realistic data for cinema
INSERT INTO cinema (Cinema_ID, Capacity, Max_Screenings_Per_Day) VALUES (1, 120, 5);
INSERT INTO cinema (Cinema_ID, Capacity, Max_Screenings_Per_Day) VALUES (2, 180, 6);
INSERT INTO cinema (Cinema_ID, Capacity, Max_Screenings_Per_Day) VALUES (3, 250, 7);

-- Realistic data for film_schedule
INSERT INTO film_schedule (Cinema_ID_Film_ID, Show_Times) VALUES (101, 3);
INSERT INTO film_schedule (Cinema_ID_Film_ID, Show_Times) VALUES (102, 4);
INSERT INTO film_schedule (Cinema_ID_Film_ID, Show_Times) VALUES (103, 2);

-- Realistic data for film_pricing
INSERT INTO film_pricing (Cinema_ID_Film_ID, Price) VALUES (101, 12.0);
INSERT INTO film_pricing (Cinema_ID_Film_ID, Price) VALUES (102, 15.0);
INSERT INTO film_pricing (Cinema_ID_Film_ID, Price) VALUES (103, 10.0);


```

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