# Complete Optimization Problem and Solution: network_2

## 1. Problem Context and Goals

### Context  
The social network aims to maximize the number of friendships formed among its users while ensuring a balanced and inclusive network. Each user can form friendships with others, but the network enforces specific rules to maintain balance and prevent overloading any single user. 

Key operational parameters include:  
- **Maximum number of friendships per person**: Each user is allowed to have up to 15 friendships. This ensures that no single user is overwhelmed with too many connections.  
- **Minimum number of friendships per age group**: Each user must have at least 3 friendships with users from different age groups. This promotes diversity and inclusivity across the network.  

The network also ensures that friendships are mutual, meaning if person i is friends with person j, then person j must also be friends with person i. This mutual relationship is a fundamental requirement for all friendships in the network.  

The goal is to maximize the total number of friendships while adhering to these constraints, ensuring a balanced and sustainable social network.

### Goals  
The primary optimization goal is to maximize the total number of friendships formed across the network. Success is measured by the total count of friendships, ensuring that the network grows while maintaining balance and inclusivity.  

The network aims to achieve this by:  
- Encouraging as many friendships as possible within the allowed limits.  
- Ensuring that each user has a minimum number of friendships across different age groups.  
- Maintaining mutual friendships to foster genuine connections.  

This goal aligns with the operational parameters and ensures that the network remains diverse, inclusive, and scalable.

## 2. Constraints  

The optimization problem is subject to the following constraints:  
1. **Minimum friendships per age group**: Each user must have at least 3 friendships with users from each defined age group. This ensures that users have diverse connections across different age ranges.  
2. **Maximum friendships per person**: No user can have more than 15 friendships. This prevents any single user from being overwhelmed with too many connections.  
3. **Mutual friendships**: If person i is friends with person j, then person j must also be friends with person i. This ensures that all friendships are mutual and genuine.  

These constraints ensure that the network remains balanced, inclusive, and sustainable while maximizing the total number of friendships.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include creating tables for decision variables, age groups, and friendships. Configuration logic updates include scalar parameters for max friendships per person and min friendships per age group, and a formula for friendship balance.

CREATE TABLE friendships (
  person_i INTEGER,
  person_j INTEGER,
  is_friends BOOLEAN
);

CREATE TABLE age_groups (
  age_group_id INTEGER,
  age_range STRING
);
```

### Data Dictionary  
- **Friendships Table**:  
  - **Purpose**: Tracks whether a friendship exists between two users.  
  - **Columns**:  
    - `person_i`: The ID of the first user in the friendship.  
    - `person_j`: The ID of the second user in the friendship.  
    - `is_friends`: Indicates whether the two users are friends (True or False).  

- **Age Groups Table**:  
  - **Purpose**: Defines the age groups used to ensure balanced friendships across different age ranges.  
  - **Columns**:  
    - `age_group_id`: The ID of the age group.  
    - `age_range`: The age range for the group (e.g., 18-25, 26-35).  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on realistic social network scenarios, ensuring diversity in age groups and balanced friendships while respecting constraints.

-- Realistic data for friendships
INSERT INTO friendships (person_i, person_j, is_friends) VALUES (1, 2, True);
INSERT INTO friendships (person_i, person_j, is_friends) VALUES (1, 3, False);
INSERT INTO friendships (person_i, person_j, is_friends) VALUES (2, 3, True);
INSERT INTO friendships (person_i, person_j, is_friends) VALUES (3, 4, True);
INSERT INTO friendships (person_i, person_j, is_friends) VALUES (4, 5, False);

-- Realistic data for age_groups
INSERT INTO age_groups (age_group_id, age_range) VALUES (1, 18-25);
INSERT INTO age_groups (age_group_id, age_range) VALUES (2, 26-35);
INSERT INTO age_groups (age_group_id, age_range) VALUES (3, 36-45);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be a binary decision variable where:
  - \( x_{ij} = 1 \) if person \( i \) is friends with person \( j \), and
  - \( x_{ij} = 0 \) otherwise.
- Note: \( x_{ij} = x_{ji} \) to ensure mutual friendships.

#### Objective Function
Maximize the total number of friendships:
\[
\text{Maximize} \quad \sum_{i} \sum_{j > i} x_{ij}
\]
- The objective function sums all unique friendships \( x_{ij} \) where \( j > i \) to avoid double-counting.

#### Constraints
1. **Minimum friendships per age group**:
   For each person \( i \) and each age group \( k \), ensure at least 3 friendships with users from age group \( k \):
   \[
   \sum_{j \in \text{AgeGroup}_k} x_{ij} \geq 3 \quad \forall i, k
   \]
   - \( \text{AgeGroup}_k \) represents the set of users in age group \( k \).

2. **Maximum friendships per person**:
   For each person \( i \), ensure no more than 15 friendships:
   \[
   \sum_{j \neq i} x_{ij} \leq 15 \quad \forall i
   \]

3. **Mutual friendships**:
   Ensure that friendships are mutual:
   \[
   x_{ij} = x_{ji} \quad \forall i, j
   \]

4. **Binary decision variables**:
   Ensure \( x_{ij} \) is binary:
   \[
   x_{ij} \in \{0, 1\} \quad \forall i, j
   \]

#### Data Source Verification
- **Friendships Table**: The decision variable \( x_{ij} \) corresponds to the `friendships.is_friends` column.
- **Age Groups Table**: The age group constraints use the `age_groups.age_group_id` and `age_groups.age_range` to define \( \text{AgeGroup}_k \).
- **Business Configuration**: The maximum number of friendships per person (15) and the minimum number of friendships per age group (3) are scalar parameters derived from the business context.

This formulation provides a complete, immediately solvable linear programming model for maximizing friendships while adhering to the specified constraints.

## 5. Gurobipy Implementation

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

import gurobipy as gp
from gurobipy import GRB

def optimize_friendships():
    # 1. MODEL & DATA SETUP
    model = gp.Model("social_network_friendships")
    
    # Example data
    users = [1, 2, 3, 4, 5]
    age_groups = {1: [1, 2], 2: [1, 3], 3: [2, 3], 4: [1, 3], 5: [2, 3]}
    max_friendships = 15
    min_friendships_per_age_group = 3
    
    # Validate data lengths
    assert len(users) > 0, "No users provided"
    assert all(len(age_groups[u]) > 0 for u in users), "Age groups missing for some users"
    
    # 2. VARIABLES
    friendships = model.addVars([(i, j) for i in users for j in users if i < j], vtype=GRB.BINARY, name="friendship")
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(gp.quicksum(friendships[(i, j)] for i in users for j in users if i < j), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    # Maximum friendships per person
    for i in users:
        model.addConstr(gp.quicksum(friendships[(i, j)] for j in users if i < j) +
                        gp.quicksum(friendships[(j, i)] for j in users if j < i) <= max_friendships,
                        name=f"max_friendships_{i}")
    
    # Minimum friendships per age group
    for i in users:
        for age_group in set(age_groups[i]):
            model.addConstr(gp.quicksum(friendships[(i, j)] for j in users if j > i and age_group in age_groups[j]) +
                            gp.quicksum(friendships[(j, i)] for j in users if j < i and age_group in age_groups[j]) >= min_friendships_per_age_group,
                            name=f"min_friendships_age_group_{i}_{age_group}")
    
    # Mutual friendships
    for i in users:
        for j in users:
            if i < j:
                model.addConstr(friendships[(i, j)] == friendships[(j, i)], name=f"mutual_friendship_{i}_{j}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in users:
            for j in users:
                if i < j and friendships[(i, j)].x > 0.5:
                    print(f"Friendship between {i} and {j}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
optimize_friendships()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/tmp/tmp1hgjswm9.py", line 63, in <module>
    optimize_friendships()
  File "/tmp/tmp1hgjswm9.py", line 44, in optimize_friendships
    model.addConstr(friendships[(i, j)] == friendships[(j, i)], name=f"mutual_friendship_{i}_{j}")
KeyError: (2, 1)

**Analysis**: Gurobipy encountered a KeyError when trying to access the decision variable for mutual friendships. This suggests an issue with the data structure or the way variables are being indexed. The error message indicates that the key (2, 1) was not found, which could be due to missing or incorrectly initialized variables.

## 6. DOCplex Implementation

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

from docplex.mp.model import Model

def optimize_friendships():
    # 1. MODEL & DATA SETUP
    mdl = Model(name="social_network_friendships")
    
    # Example data (replace with actual data from database)
    users = [1, 2, 3, 4, 5]
    age_groups = {1: [1, 2], 2: [3, 4], 3: [5]}  # Example mapping of age groups to users
    max_friendships = 15
    min_friendships_per_age_group = 3
    
    # 2. VARIABLES
    friendships = mdl.binary_var_dict([(i, j) for i in users for j in users if i < j], name="friendship")
    
    # 3. OBJECTIVE FUNCTION
    total_friendships = mdl.sum(friendships[(i, j)] for i in users for j in users if i < j)
    mdl.maximize(total_friendships)
    
    # 4. CONSTRAINTS
    # Constraint 1: Minimum friendships per age group
    for i in users:
        for age_group, members in age_groups.items():
            mdl.add_constraint(mdl.sum(friendships[(i, j)] for j in members if i < j) + 
                              mdl.sum(friendships[(j, i)] for j in members if j < i) >= min_friendships_per_age_group,
                              ctname=f"min_friendships_age_group_{i}_{age_group}")
    
    # Constraint 2: Maximum friendships per person
    for i in users:
        mdl.add_constraint(mdl.sum(friendships[(i, j)] for j in users if i < j) + 
                          mdl.sum(friendships[(j, i)] for j in users if j < i) <= max_friendships,
                          ctname=f"max_friendships_{i}")
    
    # Constraint 3: Mutual friendships
    for i in users:
        for j in users:
            if i < j:
                mdl.add_constraint(friendships[(i, j)] == friendships[(j, i)], ctname=f"mutual_friendship_{i}_{j}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for (i, j), value in friendships.items():
            if solution.get_value(friendships[(i, j)]) > 0.5:
                print(f"Friendship between {i} and {j}: {value}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")

# Execute the optimization
optimize_friendships()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/tmp/tmp3t71q0qw.py", line 55, in <module>
    optimize_friendships()
  File "/tmp/tmp3t71q0qw.py", line 40, in optimize_friendships
    mdl.add_constraint(friendships[(i, j)] == friendships[(j, i)], ctname=f"mutual_friendship_{i}_{j}")
KeyError: (2, 1)

**Analysis**: DOCplex also encountered a KeyError similar to Gurobipy, indicating a problem with accessing the decision variable for mutual friendships. This suggests a systemic issue with the model setup or data handling, as both solvers failed with the same error.

## 7. Pyomo Implementation

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

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

def optimize_friendships():
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Example data based on the problem context
    persons = [1, 2, 3, 4, 5]
    age_groups = [1, 2, 3]
    age_group_membership = {
        1: [1, 2],
        2: [3, 4],
        3: [5]
    }
    
    # Validate data consistency
    assert len(persons) > 0, "Persons list is empty"
    assert len(age_groups) > 0, "Age groups list is empty"
    
    # 3. SETS
    model.P = pyo.Set(initialize=persons)  # Set of persons
    model.A = pyo.Set(initialize=age_groups)  # Set of age groups
    
    # 4. PARAMETERS
    # Age group membership
    model.age_group_membership = pyo.Param(model.A, initialize=age_group_membership)
    
    # 5. VARIABLES
    # Binary decision variable for friendships
    model.x = pyo.Var(model.P, model.P, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.x[i, j] for i in model.P for j in model.P if j > i)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Constraint 1: Minimum friendships per age group
    def min_friendships_rule(model, i, k):
        return sum(model.x[i, j] for j in model.age_group_membership[k]) >= 3
    model.min_friendships = pyo.Constraint(model.P, model.A, rule=min_friendships_rule)
    
    # Constraint 2: Maximum friendships per person
    def max_friendships_rule(model, i):
        return sum(model.x[i, j] for j in model.P if j != i) <= 15
    model.max_friendships = pyo.Constraint(model.P, rule=max_friendships_rule)
    
    # Constraint 3: Mutual friendships
    def mutual_friendships_rule(model, i, j):
        return model.x[i, j] == model.x[j, i]
    model.mutual_friendships = pyo.Constraint(model.P, model.P, rule=mutual_friendships_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
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print("Optimal solution found!")
        print(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nFriendships:")
        for i in model.P:
            for j in model.P:
                if j > i and pyo.value(model.x[i, j]) > 0.5:
                    print(f"Person {i} is friends with Person {j}")
        
    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
optimize_friendships()
```

### Execution Results
**Status**: INFEASIBLE
**Error**: Problem is infeasible
**Analysis**: Pyomo reported the problem as infeasible, meaning that no solution exists that satisfies all constraints. This could be due to overly restrictive constraints, such as the minimum friendships per age group or the maximum friendships per person. Further analysis of the constraints is needed to identify potential relaxations.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | ERROR | N/A | 0.18s | N/A | 4 |
| Docplex | ERROR | N/A | 1.06s | N/A | 4 |
| Pyomo | INFEASIBLE | N/A | 1.15s | N/A | 4 |

### Solver Consistency Analysis
**Result**: Solvers produced inconsistent results
**Inconsistent Solvers**: gurobipy, docplex, pyomo
**Potential Issues**:
- Data structure issues leading to KeyError in Gurobipy and DOCplex
- Overly restrictive constraints causing infeasibility in Pyomo
- Potential errors in the model formulation or data preprocessing
**Solver Retry Summary**: gurobipy: 4 attempts, docplex: 4 attempts, pyomo: 4 attempts

### Final Recommendation
**Confidence Level**: LOW
**Preferred Solver(s)**: none
**Reasoning**: All solvers either encountered errors or reported infeasibility, suggesting that the problem lies in the model formulation or data handling rather than the solvers themselves. Further investigation and potential relaxation of constraints are needed before re-running the optimization.

### Business Interpretation
**Overall Strategy**: The current model setup does not yield a feasible solution, indicating that the constraints may be too restrictive or there are issues with the data structure. This means that the business requirements for minimum friendships per age group and maximum friendships per person may need to be revisited.
**Objective Value Meaning**: The objective value represents the total number of friendships. Since no feasible solution was found, the business cannot currently maximize friendships under the given constraints.
**Resource Allocation Summary**: Resources cannot be allocated optimally until the model constraints are adjusted or data issues are resolved.
**Implementation Recommendations**: ['Review and potentially relax the minimum friendships per age group constraint.', 'Check the data structure to ensure all necessary variables are correctly initialized.', 'Re-run the optimization with adjusted constraints and verified data.']