# Complete Optimization Problem and Solution: network_1

## 1. Problem Context and Goals

### Context  
In a high school setting, the administration aims to enhance student social engagement by optimizing the number of mutual friendships. Each student can list a limited number of peers as friends, and the objective is to maximize the number of mutual friendships within these constraints. The decision-making process involves determining whether a student considers another student a friend, represented by a binary decision variable. The operational parameters include the maximum number of friends a student can have, which serves as a constraint in the optimization model. This approach ensures that the optimization problem remains linear, focusing on maximizing mutual friendships without involving complex relationships like products or divisions. The business configuration specifies that each student can have up to five friends, balancing social interaction with manageability.

### Goals  
The primary goal of this optimization problem is to maximize the number of mutual friendships among students. This involves maximizing the total count of pairs where both students consider each other friends. The success of this optimization is measured by the increase in mutual friendships, aligning with the expected sources of coefficients in the model. The objective is clearly defined in linear terms, focusing on enhancing social connections within the constraints of the maximum number of friends allowed per student.

## 2. Constraints    

The optimization model is subject to specific constraints that ensure it remains linear and aligned with business requirements:

- Each student can have a maximum of five friends. This constraint ensures that the number of friendships a student can list does not exceed the specified limit, maintaining manageability and fairness.
- Mutual friendships are required, meaning if student A considers student B a friend, then student B must also consider student A a friend. This constraint ensures that friendships are reciprocal, reflecting the true nature of mutual social connections.

These constraints are described in business terms that naturally lead to linear mathematical forms, avoiding any complex relationships such as variable products or divisions.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include adding a table for constraint bounds and updating configuration logic for scalar parameters. The 'max_friends' constraint is moved to configuration logic due to insufficient data for a table.

CREATE TABLE Friend (
  student_id INTEGER,
  friend_id INTEGER
);
```

### Data Dictionary  
The data dictionary provides a business-oriented mapping of tables and columns to their purposes and roles in the optimization process:

- **Friend Table**: Represents the friendships between students.
  - **student_id**: This column identifies the student in the friendship pair. It plays a crucial role in determining the decision variables for the optimization model.
  - **friend_id**: This column identifies the friend in the friendship pair. It complements the student_id to form the decision variables necessary for the optimization.

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were generated to reflect a realistic high school social network where each student can have up to 5 friends, ensuring a diverse set of mutual friendships.

-- Realistic data for Friend
INSERT INTO Friend (student_id, friend_id) VALUES (1, 2);
INSERT INTO Friend (student_id, friend_id) VALUES (2, 1);
INSERT INTO Friend (student_id, friend_id) VALUES (1, 3);
INSERT INTO Friend (student_id, friend_id) VALUES (3, 1);
INSERT INTO Friend (student_id, friend_id) VALUES (2, 3);
INSERT INTO Friend (student_id, friend_id) VALUES (3, 2);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be a binary decision variable where \( x_{ij} = 1 \) if student \( i \) considers student \( j \) a friend, and \( x_{ij} = 0 \) otherwise.

#### Objective Function
Maximize the number of mutual friendships:
\[
\text{Maximize } \sum_{i < j} (x_{ij} + x_{ji})
\]
This objective function aims to maximize the total number of mutual friendships, where both students \( i \) and \( j \) consider each other friends.

#### Constraints
1. **Maximum Friends Constraint**: Each student can have at most 5 friends.
   \[
   \sum_{j} x_{ij} \leq 5 \quad \forall i
   \]
   This constraint ensures that no student lists more than 5 friends.

2. **Mutual Friendship Constraint**: Friendships must be mutual.
   \[
   x_{ij} = x_{ji} \quad \forall i, j
   \]
   This constraint ensures that if student \( i \) considers student \( j \) a friend, then student \( j \) must also consider student \( i \) a friend.

3. **Binary Constraint**: The decision variables are binary.
   \[
   x_{ij} \in \{0, 1\} \quad \forall i, j
   \]
   This constraint ensures that the decision variables are binary, representing the presence or absence of a friendship.

Data Source Verification:
- The coefficients in the objective function and constraints are derived from the business configuration and the Friend table:
  - The maximum number of friends (5) is specified in the business configuration.
  - The mutual friendship requirement is derived from the nature of the Friend table, ensuring that friendships are reciprocal.

This linear mathematical model is immediately solvable using linear programming techniques, adhering to the constraints and objectives outlined in the business problem.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_friendships():
    """Optimize mutual friendships among students using Gurobi"""

    # 1. MODEL & DATA SETUP
    model = gp.Model("maximize_mutual_friendships")
    
    # Assume we have 3 students for simplicity
    n_students = 3
    
    # 2. VARIABLES
    # Binary decision variables for friendships
    x = model.addVars(n_students, n_students, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the number of mutual friendships
    model.setObjective(gp.quicksum(x[i, j] + x[j, i] for i in range(n_students) for j in range(i+1, n_students)), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Maximum Friends Constraint: Each student can have at most 5 friends
    model.addConstrs((gp.quicksum(x[i, j] for j in range(n_students)) <= 5 for i in range(n_students)), name="max_friends")
    
    # Mutual Friendship Constraint: Friendships must be mutual
    model.addConstrs((x[i, j] == x[j, i] for i in range(n_students) for j in range(n_students)), name="mutual_friendship")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in range(n_students):
            for j in range(n_students):
                if x[i, j].x > 0.5:  # Binary variable, so check if it's 1
                    print(f"Student {i} considers Student {j} a friend")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_friendships()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 6.0
**Execution Time**: 0.16 seconds
**Reliability**: high
**Analysis**: Gurobipy found an optimal solution with an objective value of 6.0, which is consistent with the problem formulation and constraints. The execution time was very efficient, indicating a reliable performance.

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation

from docplex.mp.model import Model

def optimize_mutual_friendships():
    """Optimize mutual friendships in a high school setting"""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="mutual_friendships")
    
    # Assume we have 3 students for simplicity
    n_students = 3
    
    # CRITICAL: Validate array lengths to prevent IndexError
    # Here, we assume a complete graph for simplicity, so no need for array length checks
    safe_range = range(n_students)
    
    # 2. VARIABLES
    # Binary decision variables for friendships
    x = {(i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in safe_range for j in safe_range if i != j}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the number of mutual friendships
    objective = mdl.sum(x[i, j] for i in safe_range for j in safe_range if i < j)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS - CORRECT SYNTAX PATTERNS
    
    # Maximum Friends Constraint: Each student can have at most 5 friends
    for i in safe_range:
        mdl.add_constraint(mdl.sum(x[i, j] for j in safe_range if i != j) <= 5, ctname=f"max_friends_{i}")
    
    # Mutual Friendship Constraint: Friendships must be mutual
    for i in safe_range:
        for j in safe_range:
            if i != j:
                mdl.add_constraint(x[i, j] == x[j, i], ctname=f"mutual_{i}_{j}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_range:
            for j in safe_range:
                if i != j:
                    value = solution.get_value(x[i, j])
                    if value > 1e-6:
                        print(f"x[{i},{j}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
optimize_mutual_friendships()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 3.0
**Execution Time**: 1.57 seconds
**Reliability**: medium
**Analysis**: DOCplex also found an optimal solution but with a lower objective value of 3.0. This discrepancy suggests potential issues with the solver's interpretation of the constraints or objective function. The execution time was longer compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def optimize_friendships():
    """Optimize mutual friendships in a high school setting"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Assume we have 3 students for simplicity
    n_students = 3
    
    # 3. SETS
    model.I = pyo.RangeSet(1, n_students)
    model.J = pyo.RangeSet(1, n_students)
    
    # 4. VARIABLES
    model.x = pyo.Var(model.I, model.J, within=pyo.Binary)
    
    # 5. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.x[i, j] + model.x[j, i] for i in model.I for j in model.J if i < j)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 6. CONSTRAINTS
    
    # Maximum Friends Constraint
    def max_friends_rule(model, i):
        return sum(model.x[i, j] for j in model.J if i != j) <= 5
    model.max_friends_constraint = pyo.Constraint(model.I, rule=max_friends_rule)
    
    # Mutual Friendship Constraint
    def mutual_friendship_rule(model, i, j):
        if i != j:
            return model.x[i, j] == model.x[j, i]
        else:
            return pyo.Constraint.Skip
    model.mutual_friendship_constraint = pyo.Constraint(model.I, model.J, rule=mutual_friendship_rule)
    
    # 7. 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)
    
    # 8. 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("\nMutual Friendships:")
        for i in model.I:
            for j in model.J:
                if i < j:
                    x_val = pyo.value(model.x[i, j])
                    if x_val > 0.5:  # Only print mutual friendships
                        print(f"Student {i} and Student {j} are mutual friends.")
        
    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
optimize_friendships()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 6.0
**Execution Time**: 1.61 seconds
**Reliability**: high
**Analysis**: Pyomo found an optimal solution with an objective value of 6.0, matching Gurobipy's results. The execution time was slightly longer than Gurobipy but still reasonable, indicating a reliable performance.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 6.00 | 0.16s | N/A | N/A |
| Docplex | OPTIMAL | 3.00 | 1.57s | N/A | N/A |
| Pyomo | OPTIMAL | 6.00 | 1.61s | N/A | N/A |

### Solver Consistency Analysis
**Result**: Solvers produced inconsistent results
**Consistent Solvers**: gurobipy, pyomo
**Inconsistent Solvers**: docplex
**Potential Issues**:
- DOCplex may have misinterpreted the mutual friendship constraint or there could be a data input error specific to DOCplex.
**Majority Vote Optimal Value**: 6.0

### Final Recommendation
**Recommended Optimal Value**: 6.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy provided the optimal solution with the highest reliability and efficiency. Its results are consistent with Pyomo, reinforcing confidence in its accuracy.

### Business Interpretation
**Overall Strategy**: The optimal solution indicates that the maximum number of mutual friendships that can be achieved is 6, adhering to the constraints of mutual friendships and a maximum of 5 friends per student.
**Objective Value Meaning**: The optimal objective value of 6 indicates the maximum number of mutual friendships possible under the given constraints.
**Resource Allocation Summary**: Resources should be allocated to ensure that each student can maintain up to 5 friendships, focusing on mutual relationships.
**Implementation Recommendations**: Ensure data integrity and consistency across solvers, and consider using Gurobipy for future optimization tasks due to its reliability and efficiency.