# Complete Optimization Problem and Solution: cinema

## 1. Problem Context and Goals

### Context  
A cinema chain is focused on maximizing its revenue by optimizing the scheduling of films across its cinemas. The key decision involves determining the number of showings per film per cinema per day, which directly impacts revenue. The cinema operates under specific operational parameters, including the price per showing, the capacity of each cinema, and the maximum number of showings allowed per day per cinema. These parameters are critical in ensuring that the scheduling aligns with both business goals and operational constraints.

The business configuration includes two key scalar parameters:  
1. **Maximum number of showings allowed per day per cinema**: This parameter ensures that the total number of showings per day does not exceed a realistic limit, which is set to 12 based on typical cinema operating hours.  
2. **Total capacity of the cinema per day**: This parameter ensures that the total number of seats available across all showings in a day does not exceed the cinema's daily capacity, which is calculated based on the cinema's seating capacity and the maximum number of showings.

The optimization problem is designed to maximize revenue by leveraging these parameters in a linear manner, ensuring that the relationships between decision variables and constraints remain straightforward and avoid any nonlinear complexities.

### Goals  
The primary goal of this optimization problem is to maximize the total revenue generated from film showings across all cinemas. Revenue is calculated by multiplying the price per showing, the number of showings per film per cinema per day, and the capacity of the cinema. Success is measured by achieving the highest possible revenue while adhering to the operational constraints, such as the maximum number of showings and the total capacity of the cinema. The optimization process ensures that these goals are met through a linear formulation, avoiding any nonlinear relationships that could complicate the decision-making process.

## 2. Constraints  

The optimization problem is subject to the following constraints, which are designed to reflect realistic operational limitations:  
1. **Maximum showings per day per cinema**: The total number of showings per day in a cinema cannot exceed the maximum allowed, which is set to 12. This ensures that the cinema's operating hours are not overextended.  
2. **Total capacity per day**: The total number of seats available across all showings in a day must not exceed the cinema's daily capacity. This ensures that the cinema does not overbook its available seating.  
3. **Minimum showings per film**: Each film must be shown at least once per day in each cinema. This ensures that all films receive adequate exposure and that the cinema's schedule remains balanced.  

These constraints are designed to align with the linear nature of the optimization problem, ensuring that the relationships between decision variables and constraints remain straightforward and avoid any nonlinear complexities.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include adding tables for missing constraints and updating configuration logic to handle scalar parameters and formulas. Business configuration logic now includes maximum show times and total capacity per day.

CREATE TABLE schedule (
  Price FLOAT,
  Show_times_per_day INTEGER
);

CREATE TABLE cinema (
  Capacity INTEGER
);
```

### Data Dictionary  
The data dictionary provides a clear mapping of the tables and columns to their business purposes and optimization roles:  
- **schedule**: This table stores information about film showings, including the price per showing and the number of showings per film per cinema per day.  
  - **Price**: Represents the price per showing of a film. This value is used as a coefficient in the revenue calculation.  
  - **Show_times_per_day**: Represents the number of showings per film per cinema per day. This is the primary decision variable in the optimization problem.  
- **cinema**: This table stores information about cinemas, including their seating capacity.  
  - **Capacity**: Represents the seating capacity of the cinema. This value is used as a coefficient in the revenue calculation.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on industry standards, typical cinema operations, and ensuring the optimization problem remains solvable. Cinema capacities and show times were aligned with real-world scenarios, while prices were set to reflect average ticket costs.

-- Realistic data for schedule
INSERT INTO schedule (Price, Show_times_per_day) VALUES (12.99, 3);
INSERT INTO schedule (Price, Show_times_per_day) VALUES (9.99, 2);
INSERT INTO schedule (Price, Show_times_per_day) VALUES (7.99, 1);

-- Realistic data for cinema
INSERT INTO cinema (Capacity) VALUES (150);
INSERT INTO cinema (Capacity) VALUES (200);
INSERT INTO cinema (Capacity) VALUES (100);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
Let \( x_{f,c} \) be the number of showings per day for film \( f \) in cinema \( c \).  
This is the primary decision variable, representing the number of showings per film per cinema per day.

#### Objective Function
Maximize the total revenue:  
\[
\text{Maximize } \sum_{f} \sum_{c} (\text{Price}_f \times \text{Capacity}_c \times x_{f,c})
\]  
Where:  
- \( \text{Price}_f \) is the price per showing for film \( f \) (from `schedule.Price`).  
- \( \text{Capacity}_c \) is the seating capacity of cinema \( c \) (from `cinema.Capacity`).  
- \( x_{f,c} \) is the number of showings per day for film \( f \) in cinema \( c \).

#### Constraints
1. **Maximum showings per day per cinema**:  
\[
\sum_{f} x_{f,c} \leq 12 \quad \forall c
\]  
This ensures the total number of showings per day in each cinema does not exceed 12.

2. **Total capacity per day**:  
\[
\sum_{f} (\text{Capacity}_c \times x_{f,c}) \leq \text{Capacity}_c \times 12 \quad \forall c
\]  
This ensures the total number of seats available across all showings in a day does not exceed the cinema's daily capacity.

3. **Minimum showings per film**:  
\[
x_{f,c} \geq 1 \quad \forall f, c
\]  
This ensures each film is shown at least once per day in each cinema.

#### Data Source Verification
- \( \text{Price}_f \): From `schedule.Price`.  
- \( \text{Capacity}_c \): From `cinema.Capacity`.  
- Maximum showings per day per cinema: Business configuration parameter (12).  
- Minimum showings per film: Business constraint (1).  

This is 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 12.0.2 Implementation for Cinema Scheduling Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def cinema_scheduling_optimization():
    """Optimize cinema scheduling to maximize revenue."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("cinema_scheduling")
    
    # Data from the database
    prices = [12.99, 9.99, 7.99]  # Price per showing for each film
    capacities = [150, 200, 100]  # Capacity of each cinema
    max_showings_per_day = 12     # Maximum showings per day per cinema
    min_showings_per_film = 1     # Minimum showings per film per cinema per day
    
    # Number of films and cinemas
    n_films = len(prices)
    n_cinemas = len(capacities)
    
    # CRITICAL: Validate array lengths before loops
    assert len(prices) == n_films, "Price array length mismatch"
    assert len(capacities) == n_cinemas, "Capacity array length mismatch"
    
    # 2. VARIABLES
    # Decision variable: x[f, c] = number of showings per day for film f in cinema c
    x = model.addVars(n_films, n_cinemas, vtype=GRB.INTEGER, name="x", lb=min_showings_per_film)
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total revenue: sum over films and cinemas of (Price_f * Capacity_c * x[f, c])
    model.setObjective(
        gp.quicksum(prices[f] * capacities[c] * x[f, c] for f in range(n_films) for c in range(n_cinemas)),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Constraint 1: Maximum showings per day per cinema
    for c in range(n_cinemas):
        model.addConstr(
            gp.quicksum(x[f, c] for f in range(n_films)) <= max_showings_per_day,
            name=f"max_showings_cinema_{c}"
        )
    
    # Constraint 2: Total capacity per day
    for c in range(n_cinemas):
        model.addConstr(
            gp.quicksum(capacities[c] * x[f, c] for f in range(n_films)) <= capacities[c] * max_showings_per_day,
            name=f"total_capacity_cinema_{c}"
        )
    
    # Constraint 3: Minimum showings per film per cinema per day (handled by variable lower bound)
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal:.2f}")
        for f in range(n_films):
            for c in range(n_cinemas):
                if x[f, c].x > 1e-6:
                    print(f"Film {f} in Cinema {c}: {x[f, c].x:.0f} showings")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
if __name__ == "__main__":
    cinema_scheduling_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 66546.0
**Execution Time**: 0.20 seconds
**Reliability**: high
**Analysis**: Gurobipy successfully found an optimal solution with a high level of reliability. The execution time was the fastest among the solvers, indicating efficient performance.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCplex implementation for cinema scheduling optimization problem
"""

from docplex.mp.model import Model

def cinema_scheduling_optimization():
    """Optimize cinema scheduling to maximize revenue"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="cinema_scheduling")
    
    # Data from the database
    prices = [12.99, 9.99, 7.99]  # Price per showing for each film
    show_times_per_day = [3, 2, 1]  # Show times per day for each film
    capacities = [150, 200, 100]  # Capacity of each cinema
    
    # Business configuration parameters
    max_showings_per_day = 12  # Maximum showings per day per cinema
    min_showings_per_film = 1  # Minimum showings per film per cinema per day
    
    # CRITICAL: Validate array lengths to prevent IndexError
    n_films = len(prices)
    n_cinemas = len(capacities)
    assert len(prices) == len(show_times_per_day) == n_films, "Film data length mismatch"
    assert len(capacities) == n_cinemas, "Cinema data length mismatch"
    
    # Safe indexing ranges
    film_range = range(n_films)
    cinema_range = range(n_cinemas)
    
    # 2. VARIABLES
    # Decision variable: x[f][c] = number of showings per day for film f in cinema c
    x = {(f, c): mdl.continuous_var(name=f"x_{f}_{c}", lb=min_showings_per_film) 
         for f in film_range for c in cinema_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total revenue: sum over films and cinemas of (price * capacity * x)
    revenue = mdl.sum(prices[f] * capacities[c] * x[(f, c)] 
                      for f in film_range for c in cinema_range)
    mdl.maximize(revenue)
    
    # 4. CONSTRAINTS
    
    # Constraint 1: Maximum showings per day per cinema
    for c in cinema_range:
        total_showings = mdl.sum(x[(f, c)] for f in film_range)
        mdl.add_constraint(total_showings <= max_showings_per_day, 
                          ctname=f"max_showings_cinema_{c}")
    
    # Constraint 2: Total capacity per day
    for c in cinema_range:
        total_capacity = mdl.sum(capacities[c] * x[(f, c)] for f in film_range)
        mdl.add_constraint(total_capacity <= capacities[c] * max_showings_per_day, 
                          ctname=f"total_capacity_cinema_{c}")
    
    # Constraint 3: Minimum showings per film per cinema per day
    for f in film_range:
        for c in cinema_range:
            mdl.add_constraint(x[(f, c)] >= min_showings_per_film, 
                              ctname=f"min_showings_film_{f}_cinema_{c}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for f in film_range:
            for c in cinema_range:
                value = solution.get_value(x[(f, c)])
                print(f"Film {f} in Cinema {c}: {value:.2f} showings")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
if __name__ == "__main__":
    cinema_scheduling_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 66546.0
**Execution Time**: 1.44 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution with the same objective value as Gurobipy. However, the execution time was significantly longer, suggesting less efficiency compared to Gurobipy.

## 7. Pyomo Implementation

```python
#!/usr/bin/env python3
"""
Pyomo 6.9.2 Implementation for Cinema Scheduling Optimization
"""

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

def cinema_scheduling_optimization():
    """Optimize cinema scheduling to maximize revenue"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Films data (Price, Show_times_per_day)
    films_data = [
        (12.99, 3),
        (9.99, 2),
        (7.99, 1)
    ]
    
    # Cinemas data (Capacity)
    cinemas_data = [150, 200, 100]
    
    # Maximum showings per day per cinema
    max_showings_per_day = 12
    
    # Validate array lengths
    assert len(films_data) > 0, "No films data provided"
    assert len(cinemas_data) > 0, "No cinemas data provided"
    
    # 3. SETS
    model.F = pyo.RangeSet(1, len(films_data))  # Films set
    model.C = pyo.RangeSet(1, len(cinemas_data))  # Cinemas set
    
    # 4. PARAMETERS
    # Price per showing for each film
    model.price = pyo.Param(model.F, initialize={i+1: films_data[i][0] for i in range(len(films_data))})
    
    # Capacity of each cinema
    model.capacity = pyo.Param(model.C, initialize={i+1: cinemas_data[i-1] for i in model.C})
    
    # 5. VARIABLES
    # Number of showings per day for film f in cinema c
    model.x = pyo.Var(model.F, model.C, within=pyo.NonNegativeIntegers, bounds=(1, max_showings_per_day))
    
    # 6. OBJECTIVE FUNCTION
    # Maximize total revenue
    def obj_rule(model):
        return sum(model.price[f] * model.capacity[c] * model.x[f, c] for f in model.F for c in model.C)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    # Maximum showings per day per cinema
    def max_showings_rule(model, c):
        return sum(model.x[f, c] for f in model.F) <= max_showings_per_day
    model.max_showings_constraint = pyo.Constraint(model.C, rule=max_showings_rule)
    
    # Total capacity per day
    def total_capacity_rule(model, c):
        return sum(model.capacity[c] * model.x[f, c] for f in model.F) <= model.capacity[c] * max_showings_per_day
    model.total_capacity_constraint = pyo.Constraint(model.C, rule=total_capacity_rule)
    
    # Minimum showings per film
    def min_showings_rule(model, f, c):
        return model.x[f, c] >= 1
    model.min_showings_constraint = pyo.Constraint(model.F, model.C, rule=min_showings_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("\nNumber of showings per film per cinema:")
        for f in model.F:
            for c in model.C:
                x_val = pyo.value(model.x[f, c])
                print(f"Film {f} in Cinema {c}: {x_val} showings")
        
    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__":
    cinema_scheduling_optimization()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/tmp/tmpmpjr9sds.py", line 103, in <module>
    cinema_scheduling_optimization()
  File "/tmp/tmpmpjr9sds.py", line 42, in cinema_scheduling_optimization
    model.capacity = pyo.Param(model.C, initialize={i+1: cinemas_data[i-1] for i in model.C})
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
    self.add_component(name, val)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 1101, in add_component
    val.construct(data)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/param.py", line 850, in construct
    self._construct_from_rule_using_setitem()
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/indexed_component.py", line 772, in _construct_from_rule_using_setitem
    self[index] = rule(block, index)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/indexed_component.py", line 692, in __setitem__
    index = self._validate_index(index)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/indexed_component.py", line 870, in _validate_index
    raise KeyError(
KeyError: "Index '4' is not valid for indexed component 'capacity'"

**Analysis**: Pyomo encountered an error during execution, specifically a KeyError related to the indexed component 'capacity'. This indicates a potential issue with the data handling or model setup in Pyomo.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 66546.00 | 0.20s | N/A | N/A |
| Docplex | OPTIMAL | 66546.00 | 1.44s | N/A | N/A |
| Pyomo | ERROR | N/A | 0.74s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 66546.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its optimal solution, high reliability, and fastest execution time. DOCplex also found the same optimal solution but was less efficient. Pyomo is not recommended due to its execution error.

### Business Interpretation
**Overall Strategy**: The optimal revenue achievable under the given constraints is $66,546. This represents the maximum possible revenue from film showings across all cinemas.
**Objective Value Meaning**: The optimal objective value of $66,546 represents the maximum daily revenue achievable from film showings across all cinemas, considering the constraints on showings and capacity.
**Resource Allocation Summary**: Resources should be allocated to ensure each film is shown at least once per day in each cinema, while maximizing the number of showings for higher-revenue films in larger cinemas.
**Implementation Recommendations**: Implement the optimal schedule as determined by Gurobipy. Ensure that each cinema does not exceed 12 showings per day and that the total seating capacity is not exceeded. Monitor the schedule for any deviations and adjust as necessary.