## 4. Mathematical Optimization Formulation

#### Decision Variables
Let \( x_{ij} \) be the integer decision variable representing the number of pieces performed by member \( i \) in performance \( j \).

#### Objective Function
Maximize the total number of pieces performed across all performances:
\[ \text{Maximize } \sum_{i,j} x_{ij} \]

#### Constraints
1. **Performance Attendance Constraints**: The total number of pieces performed in any given performance cannot exceed the maximum attendance capacity for that performance.
   \[
   \sum_{i} x_{ij} \leq \text{Attendance}_j \quad \forall j
   \]
   - Data Source Verification: `performance.Attendance`

2. **Member Availability Constraints**: Each member has a limit on the number of pieces they can perform, based on their availability.
   \[
   \sum_{j} x_{ij} \leq \text{Availability}_i \quad \forall i
   \]
   - Data Source Verification: `member_availability.Availability`

3. **Non-negativity and Integer Constraints**: Each decision variable must be a non-negative integer.
   \[
   x_{ij} \geq 0 \quad \text{and integer} \quad \forall i, j
   \]

This linear model can be solved using standard linear or mixed-integer programming solvers to determine the optimal allocation of members to performances, maximizing the total number of pieces performed while respecting all constraints.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_performance_attendance():
    """Optimize the allocation of theater members to performances."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("performance_attendance")
    
    # Data from the database schema
    member_ids = [1, 2, 3]
    performance_ids = [101, 102, 103]
    
    # Attendance limits for each performance
    attendance_limits = {101: 50, 102: 60, 103: 70}
    
    # Member availability
    member_availability = {1: 5, 2: 10, 3: 15}
    
    # Validate data lengths
    assert len(member_ids) == len(member_availability), "Member data length mismatch"
    assert len(performance_ids) == len(attendance_limits), "Performance data length mismatch"
    
    # 2. VARIABLES
    # Decision variables: number of pieces performed by member i in performance j
    x = model.addVars(member_ids, performance_ids, vtype=GRB.INTEGER, name="x", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total number of pieces performed
    model.setObjective(gp.quicksum(x[i, j] for i in member_ids for j in performance_ids), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Performance Attendance Constraints
    for j in performance_ids:
        model.addConstr(gp.quicksum(x[i, j] for i in member_ids) <= attendance_limits[j], name=f"attendance_limit_{j}")
    
    # Member Availability Constraints
    for i in member_ids:
        model.addConstr(gp.quicksum(x[i, j] for j in performance_ids) <= member_availability[i], name=f"availability_limit_{i}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in member_ids:
            for j in performance_ids:
                if x[i, j].x > 1e-6:
                    print(f"x[{i},{j}] = {x[i, j].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_performance_attendance()
```

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

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation

from docplex.mp.model import Model

def optimize_performance_attendance():
    """Optimize the allocation of theater members to performances."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="performance_attendance")
    
    # Data from the database
    member_ids = [1, 2, 3]
    performance_ids = [101, 102, 103]
    attendance_limits = [50, 60, 70]  # Corresponds to performance.Attendance
    member_availabilities = [5, 10, 15]  # Corresponds to member_availability.Availability
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(performance_ids) == len(attendance_limits), "Performance data length mismatch"
    assert len(member_ids) == len(member_availabilities), "Member data length mismatch"
    
    # Safe indexing
    safe_member_range = range(len(member_ids))
    safe_performance_range = range(len(performance_ids))
    
    # 2. VARIABLES
    # Decision variables: number of pieces performed by member i in performance j
    x = {(i, j): mdl.integer_var(name=f"x_{member_ids[i]}_{performance_ids[j]}", lb=0)
         for i in safe_member_range for j in safe_performance_range}
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total number of pieces performed across all performances
    objective = mdl.sum(x[i, j] for i in safe_member_range for j in safe_performance_range)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Performance Attendance Constraints
    for j in safe_performance_range:
        mdl.add_constraint(mdl.sum(x[i, j] for i in safe_member_range) <= attendance_limits[j],
                           ctname=f"attendance_limit_{performance_ids[j]}")
    
    # Member Availability Constraints
    for i in safe_member_range:
        mdl.add_constraint(mdl.sum(x[i, j] for j in safe_performance_range) <= member_availabilities[i],
                           ctname=f"availability_limit_{member_ids[i]}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in safe_member_range:
            for j in safe_performance_range:
                value = solution.get_value(x[i, j])
                if value > 1e-6:
                    print(f"x[{member_ids[i]}, {performance_ids[j]}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
optimize_performance_attendance()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 30.0
**Execution Time**: 8.58 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution, but with a longer execution time compared to Gurobipy, suggesting it is reliable but less efficient.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

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

def performance_attendance_optimization():
    """Optimize the allocation of theater members to performances"""

    # 1. MODEL CREATION
    model = pyo.ConcreteModel()

    # 2. DATA SETUP
    # Example data based on the provided schema and realistic data
    member_ids = [1, 2, 3]
    performance_ids = [101, 102, 103]
    attendance_limits = {101: 50, 102: 60, 103: 70}
    member_availability = {1: 5, 2: 10, 3: 15}

    # CRITICAL: Validate array lengths before indexing
    assert len(member_ids) == len(member_availability), "Array length mismatch for members"
    assert len(performance_ids) == len(attendance_limits), "Array length mismatch for performances"

    # 3. SETS
    model.M = pyo.Set(initialize=member_ids)
    model.P = pyo.Set(initialize=performance_ids)

    # 4. PARAMETERS
    model.attendance_limit = pyo.Param(model.P, initialize=attendance_limits)
    model.availability = pyo.Param(model.M, initialize=member_availability)

    # 5. VARIABLES
    model.x = pyo.Var(model.M, model.P, within=pyo.NonNegativeIntegers)

    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.x[m, p] for m in model.M for p in model.P)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)

    # 7. CONSTRAINTS

    # Performance Attendance Constraints
    def performance_constraint_rule(model, p):
        return sum(model.x[m, p] for m in model.M) <= model.attendance_limit[p]
    model.performance_constraints = pyo.Constraint(model.P, rule=performance_constraint_rule)

    # Member Availability Constraints
    def member_constraint_rule(model, m):
        return sum(model.x[m, p] for p in model.P) <= model.availability[m]
    model.member_constraints = pyo.Constraint(model.M, rule=member_constraint_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)

    # 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)}")

        # Extract variable values
        print("\nVariable values:")
        for m in model.M:
            for p in model.P:
                x_val = pyo.value(model.x[m, p])
                if x_val > 1e-6:  # Only print non-zero values
                    print(f"x[{m},{p}] = {x_val:.3f}")

    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
performance_attendance_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 30.0
**Execution Time**: 3.85 seconds
**Reliability**: high
**Analysis**: Pyomo achieved an optimal solution with a moderate execution time, showing a balance between efficiency and reliability.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 30.00 | 0.23s | N/A | N/A |
| Docplex | OPTIMAL | 30.00 | 8.58s | N/A | N/A |
| Pyomo | OPTIMAL | 30.00 | 3.85s | N/A | N/A |

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

### Final Recommendation
**Recommended Optimal Value**: 30.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy is recommended due to its high reliability and the shortest execution time, making it the most efficient choice for solving this problem.

### Business Interpretation
**Overall Strategy**: The optimal solution indicates the maximum number of pieces that can be performed across all performances, ensuring full utilization of member availability and performance capacity.
**Objective Value Meaning**: The optimal objective value of 30 indicates the total number of pieces performed, maximizing the use of available resources.
**Resource Allocation Summary**: Members should be allocated to performances in a way that maximizes the total number of pieces performed, respecting both attendance and availability constraints.
**Implementation Recommendations**: Implement the solution by scheduling members according to the optimal allocation, ensuring all constraints are met and resources are utilized efficiently.