# Complete Optimization Problem and Solution: movie_1

## 1. Problem Context and Goals

### Context  
A movie streaming platform is focused on enhancing viewer satisfaction by strategically recommending movies based on user ratings. The platform must decide how many times each movie should be recommended to each reviewer to maximize overall satisfaction. The satisfaction is measured by the sum of star ratings associated with the recommendations. 

The platform operates under specific operational constraints:  
1. Each movie has a maximum number of times it can be recommended across all reviewers.  
2. Each reviewer has a maximum number of recommendations they can receive in total.  
3. Recommendations must be non-negative and integer-valued.  

The star ratings provided by reviewers for movies serve as the key metric for determining the value of each recommendation. The platform’s goal is to allocate recommendations in a way that maximizes the total satisfaction while respecting these operational limits.  

### Goals  
The primary goal of this optimization problem is to maximize the total satisfaction derived from movie recommendations. This is achieved by strategically assigning the number of times each movie is recommended to each reviewer, weighted by the star ratings provided by the reviewers. Success is measured by the total sum of star ratings across all recommendations, ensuring that the platform’s operational constraints are fully respected.  

## 2. Constraints  

The optimization problem is subject to the following constraints:  
1. **Movie Recommendation Limits**: The total number of times a movie is recommended across all reviewers must not exceed the maximum allowed for that movie.  
2. **Reviewer Recommendation Limits**: The total number of recommendations received by a reviewer must not exceed the maximum allowed for that reviewer.  
3. **Non-Negative Recommendations**: The number of times a movie is recommended to a reviewer must be a non-negative integer.  

These constraints ensure that the platform’s operational limits are respected while maximizing viewer satisfaction.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 2 Database Schema
-- Objective: Added a new table for star ratings to address missing objective coefficients, updated business configuration logic, and ensured all mappings are complete.

CREATE TABLE MovieRecommendationLimits (
  movie_id INTEGER,
  max_recommendations INTEGER
);

CREATE TABLE ReviewerRecommendationLimits (
  reviewer_id INTEGER,
  max_recommendations INTEGER
);

CREATE TABLE RecommendationAssignments (
  reviewer_id INTEGER,
  movie_id INTEGER,
  recommendation_count INTEGER
);

CREATE TABLE MovieRatings (
  reviewer_id INTEGER,
  movie_id INTEGER,
  star_rating INTEGER
);
```

### Data Dictionary  
- **MovieRecommendationLimits**:  
  - **Purpose**: Defines the maximum number of recommendations allowed for each movie.  
  - **Columns**:  
    - `movie_id`: Unique identifier for the movie.  
    - `max_recommendations`: Maximum number of recommendations allowed for the movie.  

- **ReviewerRecommendationLimits**:  
  - **Purpose**: Defines the maximum number of recommendations allowed for each reviewer.  
  - **Columns**:  
    - `reviewer_id`: Unique identifier for the reviewer.  
    - `max_recommendations`: Maximum number of recommendations allowed for the reviewer.  

- **RecommendationAssignments**:  
  - **Purpose**: Tracks the number of times each movie is recommended to each reviewer.  
  - **Columns**:  
    - `reviewer_id`: Unique identifier for the reviewer receiving the recommendation.  
    - `movie_id`: Unique identifier for the movie being recommended.  
    - `recommendation_count`: Number of times the movie is recommended to the reviewer.  

- **MovieRatings**:  
  - **Purpose**: Stores the star ratings given by reviewers to movies.  
  - **Columns**:  
    - `reviewer_id`: Unique identifier for the reviewer providing the rating.  
    - `movie_id`: Unique identifier for the movie being rated.  
    - `star_rating`: Star rating given by the reviewer to the movie.  

### Current Stored Values  
```sql
-- Iteration 2 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic business scenarios, ensuring that the optimization problem remains meaningful and solvable. Constraints were respected, and relationships between tables were maintained to ensure consistency.

-- Realistic data for MovieRecommendationLimits
INSERT INTO MovieRecommendationLimits (movie_id, max_recommendations) VALUES (1, 10);
INSERT INTO MovieRecommendationLimits (movie_id, max_recommendations) VALUES (2, 7);
INSERT INTO MovieRecommendationLimits (movie_id, max_recommendations) VALUES (3, 5);

-- Realistic data for ReviewerRecommendationLimits
INSERT INTO ReviewerRecommendationLimits (reviewer_id, max_recommendations) VALUES (1, 5);
INSERT INTO ReviewerRecommendationLimits (reviewer_id, max_recommendations) VALUES (2, 4);
INSERT INTO ReviewerRecommendationLimits (reviewer_id, max_recommendations) VALUES (3, 3);

-- Realistic data for RecommendationAssignments
INSERT INTO RecommendationAssignments (reviewer_id, movie_id, recommendation_count) VALUES (1, 1, 2);
INSERT INTO RecommendationAssignments (reviewer_id, movie_id, recommendation_count) VALUES (2, 2, 1);
INSERT INTO RecommendationAssignments (reviewer_id, movie_id, recommendation_count) VALUES (3, 3, 1);

-- Realistic data for MovieRatings
INSERT INTO MovieRatings (reviewer_id, movie_id, star_rating) VALUES (1, 1, 5);
INSERT INTO MovieRatings (reviewer_id, movie_id, star_rating) VALUES (2, 2, 4);
INSERT INTO MovieRatings (reviewer_id, movie_id, star_rating) VALUES (3, 3, 3);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
Let \( x_{i,j} \) be the number of times movie \( j \) is recommended to reviewer \( i \), where \( i \) is the reviewer ID and \( j \) is the movie ID.  
\( x_{i,j} \) is a non-negative integer variable.

#### Objective Function
Maximize the total satisfaction derived from movie recommendations:  
\[
\text{Maximize } Z = \sum_{i} \sum_{j} (\text{star\_rating}_{i,j} \times x_{i,j})
\]  
where \( \text{star\_rating}_{i,j} \) is the star rating given by reviewer \( i \) to movie \( j \).

#### Constraints
1. **Movie Recommendation Limits**: The total number of times a movie \( j \) is recommended across all reviewers must not exceed its maximum allowed recommendations:  
\[
\sum_{i} x_{i,j} \leq \text{max\_recommendations}_j \quad \forall j
\]  
2. **Reviewer Recommendation Limits**: The total number of recommendations received by a reviewer \( i \) must not exceed their maximum allowed recommendations:  
\[
\sum_{j} x_{i,j} \leq \text{max\_recommendations}_i \quad \forall i
\]  
3. **Non-Negative Recommendations**: The number of recommendations must be non-negative integers:  
\[
x_{i,j} \geq 0 \quad \forall i, j
\]  
\[
x_{i,j} \in \mathbb{Z} \quad \forall i, j
\]

#### Data Source Verification
- \( \text{star\_rating}_{i,j} \): From `MovieRatings.star_rating`.  
- \( \text{max\_recommendations}_j \): From `MovieRecommendationLimits.max_recommendations`.  
- \( \text{max\_recommendations}_i \): From `ReviewerRecommendationLimits.max_recommendations`.  

#### Numerical Example
Using the provided data:  
- \( \text{star\_rating}_{1,1} = 5 \), \( \text{star\_rating}_{2,2} = 4 \), \( \text{star\_rating}_{3,3} = 3 \).  
- \( \text{max\_recommendations}_1 = 10 \), \( \text{max\_recommendations}_2 = 7 \), \( \text{max\_recommendations}_3 = 5 \).  
- \( \text{max\_recommendations}_1 = 5 \), \( \text{max\_recommendations}_2 = 4 \), \( \text{max\_recommendations}_3 = 3 \).  

The objective function becomes:  
\[
\text{Maximize } Z = 5x_{1,1} + 4x_{2,2} + 3x_{3,3}
\]  

The constraints are:  
1. \( x_{1,1} + x_{2,1} + x_{3,1} \leq 10 \)  
   \( x_{1,2} + x_{2,2} + x_{3,2} \leq 7 \)  
   \( x_{1,3} + x_{2,3} + x_{3,3} \leq 5 \)  
2. \( x_{1,1} + x_{1,2} + x_{1,3} \leq 5 \)  
   \( x_{2,1} + x_{2,2} + x_{2,3} \leq 4 \)  
   \( x_{3,1} + x_{3,2} + x_{3,3} \leq 3 \)  
3. \( x_{i,j} \geq 0 \) and \( x_{i,j} \in \mathbb{Z} \) for all \( i, j \).  

This is a complete, immediately solvable Mixed-Integer Linear Programming (MILP) model.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation - Retry Attempt 1

import gurobipy as gp
from gurobipy import GRB

def optimize_movie_recommendations():
    # 1. MODEL & DATA SETUP
    model = gp.Model("movie_recommendation_optimization")
    
    # Data from the database
    movie_ids = [1, 2, 3]
    reviewer_ids = [1, 2, 3]
    
    # Star ratings
    star_ratings = {
        (1, 1): 5,
        (2, 2): 4,
        (3, 3): 3
    }
    
    # Movie recommendation limits
    movie_limits = {
        1: 10,
        2: 7,
        3: 5
    }
    
    # Reviewer recommendation limits
    reviewer_limits = {
        1: 5,
        2: 4,
        3: 3
    }
    
    # 2. VARIABLES
    x = model.addVars(reviewer_ids, movie_ids, vtype=GRB.INTEGER, name="x", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(
        gp.quicksum(star_ratings[i, j] * x[i, j] for i in reviewer_ids for j in movie_ids if (i, j) in star_ratings),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Movie Recommendation Limits
    for j in movie_ids:
        model.addConstr(
            gp.quicksum(x[i, j] for i in reviewer_ids) <= movie_limits[j],
            name=f"movie_limit_{j}"
        )
    
    # Reviewer Recommendation Limits
    for i in reviewer_ids:
        model.addConstr(
            gp.quicksum(x[i, j] for j in movie_ids) <= reviewer_limits[i],
            name=f"reviewer_limit_{i}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in reviewer_ids:
            for j in movie_ids:
                if x[i, j].x > 1e-6:
                    print(f"x[{i},{j}] = {x[i, j].x}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
optimize_movie_recommendations()
```

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

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation - Retry Attempt 1

from docplex.mp.model import Model

def movie_recommendation_optimization():
    # 1. MODEL & DATA SETUP
    mdl = Model(name="movie_recommendation_optimization")
    
    # Data from the database
    movie_ids = [1, 2, 3]
    reviewer_ids = [1, 2, 3]
    
    # Star ratings
    star_ratings = {
        (1, 1): 5,
        (2, 2): 4,
        (3, 3): 3
    }
    
    # Movie recommendation limits
    movie_limits = {
        1: 10,
        2: 7,
        3: 5
    }
    
    # Reviewer recommendation limits
    reviewer_limits = {
        1: 5,
        2: 4,
        3: 3
    }
    
    # 2. VARIABLES
    x = {(i, j): mdl.integer_var(name=f"x_{i}_{j}", lb=0) for i in reviewer_ids for j in movie_ids}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(star_ratings.get((i, j), 0) * x[i, j] for i in reviewer_ids for j in movie_ids)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Movie Recommendation Limits
    for j in movie_ids:
        mdl.add_constraint(mdl.sum(x[i, j] for i in reviewer_ids) <= movie_limits[j], ctname=f"movie_limit_{j}")
    
    # Reviewer Recommendation Limits
    for i in reviewer_ids:
        mdl.add_constraint(mdl.sum(x[i, j] for j in movie_ids) <= reviewer_limits[i], ctname=f"reviewer_limit_{i}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in reviewer_ids:
            for j in movie_ids:
                value = solution.get_value(x[i, j])
                if value > 0:
                    print(f"x[{i},{j}] = {value}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Execute the optimization
movie_recommendation_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 50.0
**Execution Time**: 1.32 seconds
**Reliability**: high
**Retry Attempt**: 1
**Analysis**: DOCplex also found the optimal solution but took significantly longer compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation - Retry Attempt 1

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

def movie_recommendation_optimization():
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Define sets for reviewers and movies
    reviewers = [1, 2, 3]
    movies = [1, 2, 3]
    
    # Define star ratings
    star_ratings = {
        (1, 1): 5,
        (2, 2): 4,
        (3, 3): 3
    }
    
    # Define maximum recommendations for movies
    movie_max_recommendations = {
        1: 10,
        2: 7,
        3: 5
    }
    
    # Define maximum recommendations for reviewers
    reviewer_max_recommendations = {
        1: 5,
        2: 4,
        3: 3
    }
    
    # 3. SETS
    model.reviewers = pyo.Set(initialize=reviewers)
    model.movies = pyo.Set(initialize=movies)
    
    # 4. PARAMETERS
    model.star_ratings = pyo.Param(model.reviewers, model.movies, initialize=star_ratings, default=0)
    model.movie_max_recommendations = pyo.Param(model.movies, initialize=movie_max_recommendations)
    model.reviewer_max_recommendations = pyo.Param(model.reviewers, initialize=reviewer_max_recommendations)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.reviewers, model.movies, within=pyo.NonNegativeIntegers)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.star_ratings[i, j] * model.x[i, j] for i in model.reviewers for j in model.movies)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    # Movie Recommendation Limits
    def movie_limit_rule(model, j):
        return sum(model.x[i, j] for i in model.reviewers) <= model.movie_max_recommendations[j]
    model.movie_limit_constraint = pyo.Constraint(model.movies, rule=movie_limit_rule)
    
    # Reviewer Recommendation Limits
    def reviewer_limit_rule(model, i):
        return sum(model.x[i, j] for j in model.movies) <= model.reviewer_max_recommendations[i]
    model.reviewer_limit_constraint = pyo.Constraint(model.reviewers, rule=reviewer_limit_rule)
    
    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')
    results = solver.solve(model, tee=True)
    
    # 9. RESULT PROCESSING
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print(f"Optimal value: {pyo.value(model.objective)}")
    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}")

# Execute the optimization
movie_recommendation_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 50.0
**Execution Time**: 0.87 seconds
**Reliability**: high
**Retry Attempt**: 1
**Analysis**: Pyomo successfully found the optimal solution with an execution time between Gurobipy and DOCplex.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 50.00 | 0.17s | N/A | 1 |
| Docplex | OPTIMAL | 50.00 | 1.32s | N/A | 1 |
| Pyomo | OPTIMAL | 50.00 | 0.87s | N/A | 1 |

### Solver Consistency Analysis
**Result**: All solvers produced consistent results ✓
**Consistent Solvers**: gurobipy, docplex, pyomo
**Majority Vote Optimal Value**: 50.0
**Solver Retry Summary**: gurobipy: 1 attempts, docplex: 1 attempts, pyomo: 1 attempts

### Final Recommendation
**Recommended Optimal Value**: 50.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its superior performance in terms of execution time while still achieving the optimal solution.

### Business Interpretation
**Overall Strategy**: The optimal total satisfaction score of 50 indicates the maximum possible satisfaction derived from the movie recommendations given the constraints.
**Objective Value Meaning**: The optimal objective value of 50 represents the highest possible satisfaction score achievable under the given constraints, ensuring efficient use of recommendation limits.
**Resource Allocation Summary**: Resources (recommendations) should be allocated to maximize the satisfaction score, prioritizing movies with higher star ratings and adhering to individual and movie-specific recommendation limits.
**Implementation Recommendations**: Implement the recommended movie allocations to reviewers, ensuring that the constraints on maximum recommendations per movie and per reviewer are strictly followed to maintain the optimal satisfaction score.