# Complete Optimization Problem and Solution: twitter_1

## 1. Problem Context and Goals

### Context  
The business aims to maximize the reach of tweets by strategically selecting a subset of users to post tweets. The reach is determined by the total number of followers of the selected users. Each user has a specific follower count, and the decision to select a user for tweeting is binary—either they are selected or not. The selection process must adhere to two key operational constraints:  
1. **Total Tweet Limit**: The total number of tweets across all selected users cannot exceed a predefined maximum limit. This ensures that the system is not overwhelmed with tweets.  
2. **User Tweet Limit**: Each user has a maximum number of tweets they are allowed to post. This prevents any single user from being overburdened with tweeting responsibilities.  

The business configuration includes two critical scalar parameters:  
- **Maximum Total Tweets Allowed**: The system-wide limit on the total number of tweets that can be posted.  
- **Maximum Tweets Per User**: The individual limit on the number of tweets each user can post.  

These parameters are designed to balance user engagement with operational feasibility, ensuring a realistic and manageable tweet distribution strategy.

### Goals  
The primary goal of this optimization problem is to maximize the total reach of the tweets. This is achieved by selecting users whose combined follower count is as large as possible, while respecting the operational constraints. Success is measured by the total number of followers reached through the selected tweets. The optimization process ensures that the selection of users is both strategic and compliant with the predefined limits on tweet volume.

## 2. Constraints  

The optimization problem is subject to the following constraints:  
1. **Total Tweet Constraint**: The sum of tweets posted by all selected users must not exceed the maximum total number of tweets allowed. This ensures that the system operates within its capacity.  
2. **User Tweet Constraint**: Each selected user cannot post more tweets than their individual maximum limit. This prevents overloading any single user with tweeting responsibilities.  

These constraints are designed to maintain operational efficiency and user engagement, ensuring that the tweet distribution strategy is both effective and sustainable.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating new tables for decision variables and constraint bounds, and updating business configuration logic to include scalar parameters and formulas for optimization constraints.

CREATE TABLE user_profiles (
  user_id INTEGER,
  followers INTEGER,
  max_tweets_per_user INTEGER
);

CREATE TABLE tweet_selection (
  user_id INTEGER,
  is_selected BOOLEAN
);

CREATE TABLE user_tweet_limits (
  user_id INTEGER,
  max_tweets INTEGER
);
```

### Data Dictionary  
- **user_profiles**:  
  - **user_id**: Unique identifier for each user.  
  - **followers**: Number of followers of the user, used to determine the reach of their tweets.  
  - **max_tweets_per_user**: Maximum number of tweets allowed per user, used to constrain the number of tweets each user can post.  

- **tweet_selection**:  
  - **user_id**: Unique identifier for each user.  
  - **is_selected**: Binary indicator of whether the user is selected to tweet.  

- **user_tweet_limits**:  
  - **user_id**: Unique identifier for each user.  
  - **max_tweets**: Maximum number of tweets allowed per user, used to constrain the number of tweets each user can post.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic user profiles, follower counts, and tweet limits, ensuring a balance between user engagement and tweet frequency constraints.

-- Realistic data for user_profiles
INSERT INTO user_profiles (user_id, followers, max_tweets_per_user) VALUES (1, 1500, 3);
INSERT INTO user_profiles (user_id, followers, max_tweets_per_user) VALUES (2, 2500, 5);
INSERT INTO user_profiles (user_id, followers, max_tweets_per_user) VALUES (3, 1000, 2);

-- Realistic data for tweet_selection
INSERT INTO tweet_selection (user_id, is_selected) VALUES (1, False);
INSERT INTO tweet_selection (user_id, is_selected) VALUES (2, True);
INSERT INTO tweet_selection (user_id, is_selected) VALUES (3, False);

-- Realistic data for user_tweet_limits
INSERT INTO user_tweet_limits (user_id, max_tweets) VALUES (1, 3);
INSERT INTO user_tweet_limits (user_id, max_tweets) VALUES (2, 5);
INSERT INTO user_tweet_limits (user_id, max_tweets) VALUES (3, 2);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- \( x_i \): Binary decision variable indicating whether user \( i \) is selected to tweet.  
  \( x_i \in \{0, 1\} \) for all \( i \in \{1, 2, 3\} \).

#### Objective Function
Maximize the total reach of the tweets:  
\[
\text{Maximize } Z = 1500x_1 + 2500x_2 + 1000x_3
\]  
**Data Source Verification**:  
- Coefficients \( 1500, 2500, 1000 \) come from `user_profiles.followers`.

#### Constraints
1. **Total Tweet Constraint**: The total number of tweets across all selected users cannot exceed the maximum total tweets allowed.  
   \[
   3x_1 + 5x_2 + 2x_3 \leq \text{Maximum Total Tweets Allowed}
   \]  
   **Data Source Verification**:  
   - Coefficients \( 3, 5, 2 \) come from `user_profiles.max_tweets_per_user`.  
   - The right-hand side (RHS) value is a business configuration parameter (not provided in the data, but assumed to be a predefined scalar).

2. **User Tweet Constraint**: Each selected user cannot post more tweets than their individual maximum limit.  
   \[
   x_i \leq 1 \quad \text{for all } i \in \{1, 2, 3\}
   \]  
   **Data Source Verification**:  
   - This constraint ensures that \( x_i \) remains binary, as defined in the decision variables.

#### Summary
The complete linear programming formulation is:  
\[
\text{Maximize } Z = 1500x_1 + 2500x_2 + 1000x_3
\]  
Subject to:  
\[
3x_1 + 5x_2 + 2x_3 \leq \text{Maximum Total Tweets Allowed}
\]  
\[
x_1 \leq 1, \quad x_2 \leq 1, \quad x_3 \leq 1
\]  
\[
x_1, x_2, x_3 \in \{0, 1\}
\]  

This formulation is immediately solvable using linear programming techniques, with all coefficients derived from the provided data.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Twitter Reach Optimization Problem
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_twitter_reach(max_total_tweets_allowed):
    """Optimize the reach of tweets by selecting users strategically."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("twitter_reach_optimization")
    
    # Data from the problem
    user_ids = [1, 2, 3]
    followers = {1: 1500, 2: 2500, 3: 1000}
    max_tweets_per_user = {1: 3, 2: 5, 3: 2}
    
    # CRITICAL: Validate array lengths before loops
    assert len(user_ids) == len(followers) == len(max_tweets_per_user), "Array length mismatch"
    
    # 2. VARIABLES
    # Binary decision variables indicating whether a user is selected to tweet
    x = {i: model.addVar(vtype=GRB.BINARY, name=f"x_{i}") for i in user_ids}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total reach of the tweets
    model.setObjective(gp.quicksum(followers[i] * x[i] for i in user_ids), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Total Tweet Constraint: The total number of tweets across all selected users cannot exceed the maximum total tweets allowed
    model.addConstr(gp.quicksum(max_tweets_per_user[i] * x[i] for i in user_ids) <= max_total_tweets_allowed, name="total_tweet_limit")
    
    # User Tweet Constraint: Each selected user cannot post more tweets than their individual maximum limit
    # This is inherently handled by the binary nature of x_i and the max_tweets_per_user in the total tweet constraint
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in user_ids:
            if x[i].x > 1e-6:
                print(f"User {i} is selected to tweet.")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Example usage
if __name__ == "__main__":
    # Assume the maximum total tweets allowed is 6
    max_total_tweets_allowed = 6
    optimize_twitter_reach(max_total_tweets_allowed)
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2500.0
**Execution Time**: 0.19 seconds
**Reliability**: high
**Analysis**: Gurobipy found the optimal solution quickly with a low execution time of 0.19 seconds. The results are reliable and consistent with the problem constraints.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCPLEX implementation for Twitter Reach Optimization Problem
"""

from docplex.mp.model import Model

def twitter_reach_optimization(max_total_tweets_allowed):
    """Optimize tweet selection to maximize reach while respecting constraints."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="twitter_reach_optimization")
    
    # Data from the problem
    user_ids = [1, 2, 3]
    followers = [1500, 2500, 1000]
    max_tweets_per_user = [3, 5, 2]
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(user_ids) == len(followers) == len(max_tweets_per_user), "Array length mismatch"
    safe_range = range(min(len(user_ids), len(followers), len(max_tweets_per_user)))  # Safe indexing
    
    # 2. VARIABLES
    # Binary decision variables: x_i = 1 if user i is selected, 0 otherwise
    x = {i: mdl.binary_var(name=f"x_{user_ids[i]}") for i in safe_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total reach: sum of followers of selected users
    objective = mdl.sum(followers[i] * x[i] for i in safe_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Total Tweet Constraint: sum of tweets across selected users <= max_total_tweets_allowed
    total_tweets = mdl.sum(max_tweets_per_user[i] * x[i] for i in safe_range)
    mdl.add_constraint(total_tweets <= max_total_tweets_allowed, ctname="total_tweet_limit")
    
    # User Tweet Constraint: each selected user cannot post more than their individual limit
    # This is inherently handled by the binary nature of x_i and the total tweet constraint
    
    # 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 > 0:
                print(f"User {user_ids[i]} is selected (Followers: {followers[i]}, Max Tweets: {max_tweets_per_user[i]})")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Example usage
if __name__ == "__main__":
    # Assume a maximum total tweets allowed of 6 for this example
    max_total_tweets_allowed = 6
    twitter_reach_optimization(max_total_tweets_allowed)
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2500.0
**Execution Time**: 1.17 seconds
**Reliability**: high
**Analysis**: DOCplex also found the optimal solution, but with a higher execution time of 1.17 seconds. The results are reliable but slower compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def optimize_tweet_reach():
    """Optimize tweet reach by selecting users to maximize followers while respecting tweet limits."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # User data: user_id, followers, max_tweets_per_user
    user_data = {
        1: {'followers': 1500, 'max_tweets': 3},
        2: {'followers': 2500, 'max_tweets': 5},
        3: {'followers': 1000, 'max_tweets': 2}
    }
    
    # Business configuration parameter
    max_total_tweets_allowed = 6  # Example value, adjust as needed
    
    # Validate data consistency
    assert len(user_data) > 0, "User data must not be empty"
    
    # 3. SETS (Pyomo way to define indices)
    model.I = pyo.Set(initialize=user_data.keys())  # Set of users
    
    # 4. PARAMETERS (data containers)
    model.followers = pyo.Param(model.I, initialize={i: user_data[i]['followers'] for i in model.I})
    model.max_tweets_per_user = pyo.Param(model.I, initialize={i: user_data[i]['max_tweets'] for i in model.I})
    
    # 5. VARIABLES
    # Binary decision variable: x_i = 1 if user i is selected, 0 otherwise
    model.x = pyo.Var(model.I, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    # Maximize total reach (sum of followers of selected users)
    def obj_rule(model):
        return sum(model.followers[i] * model.x[i] for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Total Tweet Constraint: Sum of tweets across selected users <= max_total_tweets_allowed
    def total_tweet_rule(model):
        return sum(model.max_tweets_per_user[i] * model.x[i] for i in model.I) <= max_total_tweets_allowed
    model.total_tweet_constraint = pyo.Constraint(rule=total_tweet_rule)
    
    # User Tweet Constraint: Each selected user cannot post more than their individual limit
    # This is inherently handled by the binary variable x_i and the max_tweets_per_user parameter
    
    # 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("\nSelected users:")
        for i in model.I:
            if pyo.value(model.x[i]) > 0.5:  # Only print selected users
                print(f"User {i} is selected with {user_data[i]['followers']} followers.")
        
    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

# Execute the optimization
if __name__ == "__main__":
    optimize_tweet_reach()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 2500.0
**Execution Time**: 0.88 seconds
**Reliability**: high
**Analysis**: Pyomo found the optimal solution with an execution time of 0.88 seconds. The results are reliable but slower than Gurobipy.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 2500.00 | 0.19s | N/A | N/A |
| Docplex | OPTIMAL | 2500.00 | 1.17s | N/A | N/A |
| Pyomo | OPTIMAL | 2500.00 | 0.88s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 2500.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its faster execution time while maintaining high reliability and consistency with the other solvers.

### Optimal Decision Variables
- **x_1** = 0.000
  - *Business Meaning*: User 1 is not selected to tweet, as their reach (1500 followers) is lower than User 2.
- **x_2** = 1.000
  - *Business Meaning*: User 2 is selected to tweet, as they have the highest reach (2500 followers) and meet the constraints.
- **x_3** = 0.000
  - *Business Meaning*: User 3 is not selected to tweet, as their reach (1000 followers) is lower than User 2.

### Business Interpretation
**Overall Strategy**: The optimal solution suggests selecting User 2 to tweet, as it maximizes the total reach (2500 followers) while adhering to the constraints. Users 1 and 3 are not selected.
**Objective Value Meaning**: The optimal objective value of 2500 represents the maximum total reach achievable under the given constraints.
**Resource Allocation Summary**: Allocate the tweet opportunity to User 2, as they provide the highest reach without exceeding the total tweet limit.
**Implementation Recommendations**: Proceed with User 2 for the tweet campaign. Ensure that the total number of tweets from User 2 does not exceed their individual limit (5 tweets) and the overall maximum tweet limit.