# Complete Optimization Problem and Solution: mountain_photos

## 1. Problem Context and Goals

### Context  
A photography company is in the process of curating a selection of photos for a new mountain photography book. The primary objective is to enhance the visual diversity of the book by selecting photos that showcase a variety of colors and different mountains. The decision-making process involves selecting photos, where each photo is represented by a binary decision variable. This variable indicates whether a photo is included in the final selection or not. The operational goal is to maximize the total diversity score of the selected photos. Each photo has an associated diversity score, which reflects its contribution to the overall diversity of the book.

The business configuration includes specific constraints: the total number of photos that can be included in the book is limited, and there is also a restriction on the number of photos that can be selected from each individual mountain. These constraints ensure that the book remains manageable in size and that no single mountain dominates the content, thereby promoting a balanced representation of different mountains.

### Goals  
The optimization goal is to maximize the diversity of the selected photos. This is achieved by focusing on the sum of the diversity scores of the photos that are chosen for inclusion in the book. The success of this optimization is measured by the total diversity score, which is derived from the diversity scores of the individual photos selected. The aim is to select photos in such a way that the overall diversity score is maximized, while adhering to the constraints on the total number of photos and the number of photos per mountain.

## 2. Constraints    

The selection process is subject to two primary constraints. First, there is a limit on the total number of photos that can be included in the book. This ensures that the book remains concise and focused. Second, there is a constraint on the number of photos that can be selected from each mountain. This constraint is in place to ensure that the book features a diverse range of mountains, preventing any single mountain from being overrepresented. These constraints are designed to naturally align with linear mathematical formulations, ensuring a straightforward optimization process.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating a new table for diversity scores and updating business configuration logic for constraints. These changes address the OR expert's mapping gaps and missing requirements.

CREATE TABLE photos (
  id INTEGER,
  diversity_score FLOAT
);

CREATE TABLE photo_diversity_scores (
  photo_id INTEGER,
  diversity_score FLOAT
);
```

### Data Dictionary  
The data is organized into tables that serve specific business purposes and optimization roles:

- **Photos Table**: This table contains information about each photo. It includes:
  - **ID**: A unique identifier for each photo, used as a decision variable in the optimization process.
  - **Diversity Score**: A numerical value representing the diversity of the photo, based on its color and the mountain it depicts. This score is used as an objective coefficient in the optimization.

- **Photo Diversity Scores Table**: This table stores the diversity scores for each photo, linking them to their respective photo IDs. The diversity score is crucial for determining the contribution of each photo to the overall diversity of the book.

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical constraints and diversity requirements for a photography book, ensuring a balance between diversity and selection limits.

-- Realistic data for photos
INSERT INTO photos (id, diversity_score) VALUES (1, 0.85);
INSERT INTO photos (id, diversity_score) VALUES (2, 0.75);
INSERT INTO photos (id, diversity_score) VALUES (3, 0.9);
INSERT INTO photos (id, diversity_score) VALUES (4, 0.65);
INSERT INTO photos (id, diversity_score) VALUES (5, 0.8);

-- Realistic data for photo_diversity_scores
INSERT INTO photo_diversity_scores (photo_id, diversity_score) VALUES (1, 0.85);
INSERT INTO photo_diversity_scores (photo_id, diversity_score) VALUES (2, 0.75);
INSERT INTO photo_diversity_scores (photo_id, diversity_score) VALUES (3, 0.9);
INSERT INTO photo_diversity_scores (photo_id, diversity_score) VALUES (4, 0.65);
INSERT INTO photo_diversity_scores (photo_id, diversity_score) VALUES (5, 0.8);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_i \) be a binary decision variable for each photo \( i \), where \( x_i = 1 \) if photo \( i \) is selected for the book, and \( x_i = 0 \) otherwise.

#### Objective Function
- Maximize the total diversity score of the selected photos:
  \[
  \text{Maximize } \sum_{i} \text{diversity\_score}_i \times x_i
  \]
  where \(\text{diversity\_score}_i\) is the diversity score of photo \( i \) from the `photos` table.

#### Constraints
1. **Total Number of Photos Constraint**:
   - The total number of photos selected cannot exceed a specified limit \( P \):
     \[
     \sum_{i} x_i \leq P
     \]
   - Data Source Verification: \( P \) is a business configuration parameter.

2. **Photos Per Mountain Constraint**:
   - Let \( M_j \) be the set of photos from mountain \( j \). The number of photos selected from each mountain \( j \) cannot exceed a specified limit \( M_j^{\text{max}} \):
     \[
     \sum_{i \in M_j} x_i \leq M_j^{\text{max}} \quad \forall j
     \]
   - Data Source Verification: \( M_j^{\text{max}} \) is a business configuration parameter.

#### Data Source Verification
- The diversity scores \(\text{diversity\_score}_i\) are sourced from the `photos.diversity_score` column.
- The constraints on the total number of photos \( P \) and the maximum number of photos per mountain \( M_j^{\text{max}} \) are derived from business configuration parameters, which are not explicitly provided in the data but are assumed to be part of the business rules.

This formulation provides a complete and immediately solvable linear mathematical model for the given business problem, ensuring that the selection of photos maximizes diversity while adhering to the specified constraints.

## 5. Gurobipy Implementation

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

import gurobipy as gp
from gurobipy import GRB

def optimize_mountain_photos():
    # 1. MODEL & DATA SETUP
    model = gp.Model("mountain_photos")
    
    # Data: Photo IDs and their diversity scores
    photo_ids = [1, 2, 3, 4, 5]
    diversity_scores = [0.85, 0.75, 0.9, 0.65, 0.8]
    
    # Constraints: Total number of photos and max photos per mountain
    P = 3  # Total number of photos allowed
    M_max = [2, 2, 1, 1, 1]  # Max photos per mountain (example values)
    
    # CRITICAL: Validate array lengths before loops
    assert len(photo_ids) == len(diversity_scores) == len(M_max), "Array length mismatch"
    
    # 2. VARIABLES
    # Binary decision variables for each photo
    x = {i: model.addVar(vtype=GRB.BINARY, name=f"x_{i}") for i in photo_ids}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total diversity score of the selected photos
    model.setObjective(gp.quicksum(diversity_scores[i-1] * x[i] for i in photo_ids), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Total number of photos constraint
    model.addConstr(gp.quicksum(x[i] for i in photo_ids) <= P, name="total_photos_limit")
    
    # Photos per mountain constraint
    # Assuming each photo belongs to a unique mountain for simplicity
    for i in photo_ids:
        model.addConstr(x[i] <= M_max[i-1], name=f"mountain_limit_{i}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in photo_ids:
            if x[i].x > 1e-6:
                print(f"x[{i}] = {x[i].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_mountain_photos()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2.55
**Execution Time**: 0.24 seconds
**Reliability**: high
**Retry Attempt**: 1
**Analysis**: Gurobipy found an optimal solution quickly with a short execution time, indicating efficient performance and reliability.

## 6. DOCplex Implementation

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

from docplex.mp.model import Model

def mountain_photos_optimization():
    """Optimize the selection of mountain photos for a photography book."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="mountain_photos")
    
    # Data: Photo IDs and their diversity scores
    photo_ids = [1, 2, 3, 4, 5]
    diversity_scores = [0.85, 0.75, 0.9, 0.65, 0.8]
    
    # Constraints: Total number of photos and max photos per mountain
    P = 3  # Total number of photos allowed
    M_max = [2, 2, 1, 1, 1]  # Max photos per mountain (example values)
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(photo_ids) == len(diversity_scores) == len(M_max), "Array length mismatch"
    safe_range = range(len(photo_ids))  # Safe indexing
    
    # 2. VARIABLES
    # Binary decision variables for each photo
    x = {i: mdl.binary_var(name=f"x_{photo_ids[i]}") for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total diversity score of the selected photos
    objective = mdl.sum(diversity_scores[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    # Total number of photos constraint
    mdl.add_constraint(mdl.sum(x[i] for i in safe_range) <= P, ctname="total_photos_limit")
    
    # Photos per mountain constraint
    for i in safe_range:
        mdl.add_constraint(x[i] <= M_max[i], ctname=f"mountain_limit_{photo_ids[i]}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_range:
            value = solution.get_value(x[i])
            if value > 1e-6:
                print(f"Photo {photo_ids[i]} selected with diversity score {diversity_scores[i]}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")

    return mdl

# Execute the optimization
mountain_photos_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2.55
**Execution Time**: 4.79 seconds
**Reliability**: high
**Retry Attempt**: 1
**Analysis**: DOCplex also found an optimal solution, albeit with a longer execution time compared to Gurobipy, but still reliable.

## 7. Pyomo Implementation

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

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

def mountain_photos_optimization():
    """Optimize the selection of mountain photos for maximum diversity."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Assuming the data is provided as lists for simplicity
    photo_ids = [1, 2, 3, 4, 5]
    diversity_scores = [0.85, 0.75, 0.9, 0.65, 0.8]
    
    # Constraints parameters
    P = 3  # Total number of photos allowed
    M_max = {1: 2, 2: 1, 3: 1, 4: 1, 5: 1}  # Max photos per mountain (assuming each photo is from a different mountain)
    
    # CRITICAL: Validate array lengths before indexing
    assert len(photo_ids) == len(diversity_scores), "Array length mismatch"
    
    # 3. SETS
    model.I = pyo.Set(initialize=photo_ids)
    
    # 4. PARAMETERS
    model.diversity_score = pyo.Param(model.I, initialize={photo_ids[i]: diversity_scores[i] for i in range(len(photo_ids))})
    
    # 5. VARIABLES
    model.x = pyo.Var(model.I, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.diversity_score[i] * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Total number of photos constraint
    def total_photos_rule(model):
        return sum(model.x[i] for i in model.I) <= P
    model.total_photos_constraint = pyo.Constraint(rule=total_photos_rule)
    
    # Photos per mountain constraint
    def photos_per_mountain_rule(model, i):
        return model.x[i] <= M_max[i]
    model.photos_per_mountain_constraint = pyo.Constraint(model.I, rule=photos_per_mountain_rule)
    
    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')
    
    # Solve the model
    results = solver.solve(model, tee=True)
    
    # 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):.2f}")
        
        # Extract variable values
        print("\nSelected photos:")
        for i in model.I:
            if pyo.value(model.x[i]) > 0.5:  # Binary variable, check if selected
                print(f"Photo ID {i} selected with diversity score {model.diversity_score[i]:.2f}")
        
    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
mountain_photos_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2.55
**Execution Time**: 5.08 seconds
**Reliability**: high
**Retry Attempt**: 1
**Analysis**: Pyomo achieved the optimal solution with the longest execution time among the solvers, but the result is consistent and reliable.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 2.55 | 0.24s | N/A | 1 |
| Docplex | OPTIMAL | 2.55 | 4.79s | N/A | 1 |
| Pyomo | OPTIMAL | 2.55 | 5.08s | N/A | 1 |

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

### Final Recommendation
**Recommended Optimal Value**: 2.55
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is preferred due to its faster execution time while maintaining the same optimal value as other solvers, making it efficient for large-scale problems.

### Business Interpretation
**Overall Strategy**: The optimal diversity score of 2.55 indicates the best selection of photos that maximizes diversity within the given constraints.
**Objective Value Meaning**: An optimal objective value of 2.55 reflects the maximum achievable diversity score for the selected photos, aligning with business goals of showcasing diverse mountain landscapes.
**Resource Allocation Summary**: Resources should be allocated to ensure the selected photos meet the diversity and quantity constraints, optimizing the visual appeal of the photo book.
**Implementation Recommendations**: Implement the solution using Gurobipy for efficient computation, and ensure business rules are adhered to in the selection process.