# Complete Optimization Problem and Solution: activity_1

## 1. Problem Context and Goals

### Context  
A university is managing the allocation of students and faculty to extracurricular activities with the goal of maximizing overall participation. The decision-making process involves determining which students and faculty members should participate in which activities. Each student can participate in at most one activity, and each faculty member can participate in at most two activities. Additionally, each activity has a predefined maximum number of participants that cannot be exceeded.  

The business configuration includes the following operational parameters:  
- **Faculty Availability Limit**: Faculty members are limited to participating in a maximum of two activities to balance their workload and ensure availability.  
- **Student Preference Threshold**: Students are allowed to participate in only one activity to ensure focused engagement and avoid overcommitment.  
- **Total Participation Calculation**: The total participation in an activity is calculated as the sum of student and faculty participation in that activity.  

The optimization problem is designed to ensure that these constraints are respected while maximizing the total number of participants across all activities.  

### Goals  
The primary goal of this optimization problem is to maximize the total participation in extracurricular activities by both students and faculty. Success is measured by the total number of participants across all activities, which is the sum of student and faculty participation. This goal aligns with the operational parameters and ensures that the allocation respects the constraints on faculty availability, student preferences, and activity capacity limits.  

## 2. Constraints  

The optimization problem must adhere to the following constraints:  
1. **Student Participation Limit**: Each student can participate in at most one activity. This ensures that students are not overcommitted and can focus on their chosen activity.  
2. **Faculty Participation Limit**: Each faculty member can participate in at most two activities. This constraint balances faculty workload and ensures their availability across activities.  
3. **Activity Capacity Limit**: The total number of participants in each activity, including both students and faculty, must not exceed the predefined maximum capacity for that activity. This ensures that activities are not overcrowded and can operate effectively.  

These constraints are designed to ensure that the allocation of participants is feasible and aligns with the operational capabilities of the university.  

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Added Activity_Capacity table to address missing max_participants constraint. Updated business_configuration_logic.json with scalar parameters and formulas. Ensured schema follows normalization principles and industry standards.

CREATE TABLE Participates_in (
  stuid INTEGER,
  actid INTEGER
);

CREATE TABLE Faculty_Participates_in (
  FacID INTEGER,
  actid INTEGER
);

CREATE TABLE Activity_Capacity (
  actid INTEGER,
  max_participants INTEGER
);
```

### Data Dictionary  
- **Participates_in**: Tracks student participation in activities.  
  - **stuid**: Unique identifier for a student. Used to determine which students participate in which activities.  
  - **actid**: Unique identifier for an activity. Used to link students to specific activities.  
- **Faculty_Participates_in**: Tracks faculty participation in activities.  
  - **FacID**: Unique identifier for a faculty member. Used to determine which faculty members participate in which activities.  
  - **actid**: Unique identifier for an activity. Used to link faculty members to specific activities.  
- **Activity_Capacity**: Defines the maximum number of participants allowed in each activity.  
  - **actid**: Unique identifier for an activity. Used to link capacity limits to specific activities.  
  - **max_participants**: The maximum number of participants allowed in the activity. Used to enforce capacity constraints.  

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical university extracurricular activity participation rates, faculty workload considerations, and activity capacity limits. The data ensures that the optimization problem is meaningful and solvable by respecting constraints and business logic.

-- Realistic data for Participates_in
INSERT INTO Participates_in (stuid, actid) VALUES (101, 1);
INSERT INTO Participates_in (stuid, actid) VALUES (102, 2);
INSERT INTO Participates_in (stuid, actid) VALUES (103, 3);

-- Realistic data for Faculty_Participates_in
INSERT INTO Faculty_Participates_in (FacID, actid) VALUES (201, 1);
INSERT INTO Faculty_Participates_in (FacID, actid) VALUES (202, 2);
INSERT INTO Faculty_Participates_in (FacID, actid) VALUES (203, 3);

-- Realistic data for Activity_Capacity
INSERT INTO Activity_Capacity (actid, max_participants) VALUES (1, 10);
INSERT INTO Activity_Capacity (actid, max_participants) VALUES (2, 15);
INSERT INTO Activity_Capacity (actid, max_participants) VALUES (3, 20);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- \( x_{s,a} \): Binary variable indicating whether student \( s \) participates in activity \( a \) (1 if yes, 0 otherwise).
- \( y_{f,a} \): Binary variable indicating whether faculty \( f \) participates in activity \( a \) (1 if yes, 0 otherwise).

#### Objective Function
Maximize the total participation across all activities:
\[
\text{Maximize } \sum_{s} \sum_{a} x_{s,a} + \sum_{f} \sum_{a} y_{f,a}
\]
- **Coefficients**: All coefficients are 1, as each participant (student or faculty) contributes equally to the total participation.

#### Constraints
1. **Student Participation Limit**: Each student can participate in at most one activity.
\[
\sum_{a} x_{s,a} \leq 1 \quad \forall s
\]
- **Data Source**: Business configuration (Student Preference Threshold).

2. **Faculty Participation Limit**: Each faculty member can participate in at most two activities.
\[
\sum_{a} y_{f,a} \leq 2 \quad \forall f
\]
- **Data Source**: Business configuration (Faculty Availability Limit).

3. **Activity Capacity Limit**: The total number of participants in each activity must not exceed its predefined maximum capacity.
\[
\sum_{s} x_{s,a} + \sum_{f} y_{f,a} \leq \text{max\_participants}_a \quad \forall a
\]
- **Data Source**: Activity_Capacity.max_participants.

4. **Binary Constraints**: Ensure decision variables are binary.
\[
x_{s,a} \in \{0, 1\} \quad \forall s, a
\]
\[
y_{f,a} \in \{0, 1\} \quad \forall f, a
\]

#### Data Source Verification
- **Objective Function Coefficients**: Derived from the goal of maximizing total participation.
- **Student Participation Limit**: Business configuration (Student Preference Threshold).
- **Faculty Participation Limit**: Business configuration (Faculty Availability Limit).
- **Activity Capacity Limit**: Activity_Capacity.max_participants.

This formulation provides a complete, immediately solvable LINEAR mathematical model with all numerical coefficients derived from the provided data.

## 5. Gurobipy Implementation

```python
#!/usr/bin/env python3
"""
Gurobipy Implementation for University Activity Allocation Problem
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_activity_allocation():
    """Optimize the allocation of students and faculty to activities."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("activity_allocation")
    
    # Example data (replace with actual data from database)
    students = [101, 102, 103]
    faculty = [201, 202, 203]
    activities = [1, 2, 3]
    
    # Activity capacities
    activity_capacity = {
        1: 10,
        2: 15,
        3: 20
    }
    
    # Validate array lengths before loops
    assert len(students) > 0, "No students provided"
    assert len(faculty) > 0, "No faculty provided"
    assert len(activities) > 0, "No activities provided"
    assert len(activity_capacity) == len(activities), "Activity capacity mismatch"
    
    # 2. VARIABLES
    # Decision variables for student participation
    x = {(s, a): model.addVar(vtype=GRB.BINARY, name=f"x_{s}_{a}") 
         for s in students for a in activities}
    
    # Decision variables for faculty participation
    y = {(f, a): model.addVar(vtype=GRB.BINARY, name=f"y_{f}_{a}") 
         for f in faculty for a in activities}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total participation
    model.setObjective(
        gp.quicksum(x[s, a] for s in students for a in activities) +
        gp.quicksum(y[f, a] for f in faculty for a in activities),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Student Participation Limit: Each student can participate in at most one activity
    for s in students:
        model.addConstr(
            gp.quicksum(x[s, a] for a in activities) <= 1,
            name=f"student_limit_{s}"
        )
    
    # Faculty Participation Limit: Each faculty member can participate in at most two activities
    for f in faculty:
        model.addConstr(
            gp.quicksum(y[f, a] for a in activities) <= 2,
            name=f"faculty_limit_{f}"
        )
    
    # Activity Capacity Limit: Total participants in each activity must not exceed its capacity
    for a in activities:
        model.addConstr(
            gp.quicksum(x[s, a] for s in students) +
            gp.quicksum(y[f, a] for f in faculty) <= activity_capacity[a],
            name=f"activity_capacity_{a}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for s in students:
            for a in activities:
                if x[s, a].x > 0.5:
                    print(f"Student {s} participates in activity {a}")
        for f in faculty:
            for a in activities:
                if y[f, a].x > 0.5:
                    print(f"Faculty {f} participates in activity {a}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
if __name__ == "__main__":
    optimize_activity_allocation()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 9.0
**Execution Time**: 0.18 seconds
**Reliability**: high
**Analysis**: Gurobipy found the optimal solution efficiently with the fastest execution time.

## 6. DOCplex Implementation

```python
#!/usr/bin/env python3
"""
DOCplex implementation for university extracurricular activity allocation problem.
"""

from docplex.mp.model import Model

def optimize_activity_allocation():
    """Optimize the allocation of students and faculty to extracurricular activities."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="activity_allocation")
    
    # Data from the problem description
    students = [101, 102, 103]
    faculty = [201, 202, 203]
    activities = [1, 2, 3]
    
    # Activity capacities
    activity_capacities = {
        1: 10,
        2: 15,
        3: 20
    }
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(students) > 0, "No students provided"
    assert len(faculty) > 0, "No faculty provided"
    assert len(activities) > 0, "No activities provided"
    assert len(activity_capacities) == len(activities), "Activity capacity mismatch"
    
    # 2. VARIABLES
    # Decision variables
    x = {(s, a): mdl.binary_var(name=f"x_{s}_{a}") for s in students for a in activities}
    y = {(f, a): mdl.binary_var(name=f"y_{f}_{a}") for f in faculty for a in activities}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total participation
    total_participation = mdl.sum(x[s, a] for s in students for a in activities) + \
                          mdl.sum(y[f, a] for f in faculty for a in activities)
    mdl.maximize(total_participation)
    
    # 4. CONSTRAINTS
    
    # Student Participation Limit: Each student can participate in at most one activity
    for s in students:
        mdl.add_constraint(mdl.sum(x[s, a] for a in activities) <= 1, ctname=f"student_limit_{s}")
    
    # Faculty Participation Limit: Each faculty member can participate in at most two activities
    for f in faculty:
        mdl.add_constraint(mdl.sum(y[f, a] for a in activities) <= 2, ctname=f"faculty_limit_{f}")
    
    # Activity Capacity Limit: Total participants in each activity must not exceed its capacity
    for a in activities:
        total_participants = mdl.sum(x[s, a] for s in students) + mdl.sum(y[f, a] for f in faculty)
        mdl.add_constraint(total_participants <= activity_capacities[a], ctname=f"capacity_limit_{a}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for s in students:
            for a in activities:
                if solution.get_value(x[s, a]) > 0:
                    print(f"Student {s} participates in activity {a}")
        for f in faculty:
            for a in activities:
                if solution.get_value(y[f, a]) > 0:
                    print(f"Faculty {f} participates in activity {a}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 9.0
**Execution Time**: 1.13 seconds
**Reliability**: high
**Analysis**: DOCplex also found the optimal solution but took longer compared to Gurobipy.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def optimize_participation():
    """Optimize the allocation of students and faculty to extracurricular activities."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Example data (replace with actual data from database)
    students = [101, 102, 103]
    faculty = [201, 202, 203]
    activities = [1, 2, 3]
    
    # Activity capacities
    activity_capacity = {
        1: 10,
        2: 15,
        3: 20
    }
    
    # Validate array lengths
    assert len(students) > 0, "No students provided"
    assert len(faculty) > 0, "No faculty provided"
    assert len(activities) > 0, "No activities provided"
    assert len(activity_capacity) == len(activities), "Activity capacity mismatch"
    
    # 3. SETS
    model.S = pyo.Set(initialize=students)  # Set of students
    model.F = pyo.Set(initialize=faculty)  # Set of faculty
    model.A = pyo.Set(initialize=activities)  # Set of activities
    
    # 4. PARAMETERS
    model.max_participants = pyo.Param(model.A, initialize=activity_capacity)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.S, model.A, within=pyo.Binary)  # Student participation
    model.y = pyo.Var(model.F, model.A, within=pyo.Binary)  # Faculty participation
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.x[s, a] for s in model.S for a in model.A) + \
               sum(model.y[f, a] for f in model.F for a in model.A)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Student Participation Limit
    def student_limit_rule(model, s):
        return sum(model.x[s, a] for a in model.A) <= 1
    model.student_limit = pyo.Constraint(model.S, rule=student_limit_rule)
    
    # Faculty Participation Limit
    def faculty_limit_rule(model, f):
        return sum(model.y[f, a] for a in model.A) <= 2
    model.faculty_limit = pyo.Constraint(model.F, rule=faculty_limit_rule)
    
    # Activity Capacity Limit
    def activity_capacity_rule(model, a):
        return sum(model.x[s, a] for s in model.S) + \
               sum(model.y[f, a] for f in model.F) <= model.max_participants[a]
    model.activity_capacity = pyo.Constraint(model.A, rule=activity_capacity_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
    # Check solver status
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nStudent Participation:")
        for s in model.S:
            for a in model.A:
                if pyo.value(model.x[s, a]) > 0:
                    print(f"Student {s} participates in activity {a}")
        
        print("\nFaculty Participation:")
        for f in model.F:
            for a in model.A:
                if pyo.value(model.y[f, a]) > 0:
                    print(f"Faculty {f} participates in activity {a}")
        
    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_participation()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 9.0
**Execution Time**: 0.94 seconds
**Reliability**: high
**Analysis**: Pyomo 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 | 9.00 | 0.18s | N/A | N/A |
| Docplex | OPTIMAL | 9.00 | 1.13s | N/A | N/A |
| Pyomo | OPTIMAL | 9.00 | 0.94s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 9.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its fastest execution time while still providing the optimal solution.

### Business Interpretation
**Overall Strategy**: The optimal solution indicates that a total of 9 participants (students and faculty) can be allocated across activities without violating any constraints.
**Objective Value Meaning**: The optimal objective value of 9 means that the maximum number of participants (students and faculty) that can be accommodated across all activities is 9, given the constraints.
**Resource Allocation Summary**: Students should be assigned to at most one activity, and faculty to at most two activities, ensuring no activity exceeds its capacity.
**Implementation Recommendations**: Implement the solution by assigning students and faculty to activities as per the optimal decision variables, ensuring all constraints are respected.