Skip to main content
Glama

Constrained Optimization MCP Server

chained_solvers.py13.6 kB
""" Chained Solvers Optimization Example This module demonstrates how to chain multiple optimization solvers to solve a complex restaurant optimization problem. The problem is solved in two stages: 1. Table layout optimization using OR-Tools (combinatorial optimization) 2. Staff scheduling optimization using CVXPY (convex optimization) The restaurant owner needs to: - Optimize table layout to maximize seating capacity given space constraints - Optimize staff scheduling based on the resulting capacity to minimize labor costs This showcases how combinatorial and convex optimization can work together to solve multi-stage business optimization problems. """ import numpy as np from returns.result import Success from usolver_mcp.models.cvxpy_models import ( CVXPYConstraint, CVXPYObjective, CVXPYProblem, CVXPYVariable, ) from usolver_mcp.models.cvxpy_models import ( ObjectiveType as CVXPYObjectiveType, ) from usolver_mcp.models.ortools_models import ( Constraint, Objective, ObjectiveType, Variable, VariableType, ) from usolver_mcp.models.ortools_models import ( Problem as ORToolsProblem, ) from usolver_mcp.solvers.cvxpy_solver import solve_cvxpy_problem from usolver_mcp.solvers.ortools_solver import solve_problem as solve_ortools_problem def create_table_layout_problem(): """ Create the table layout optimization problem using OR-Tools. The problem determines how many of each table size to use to maximize seating capacity while respecting space and operational constraints. Returns: ORToolsProblem: The table layout optimization problem """ variables = [ Variable( name="tables_2_seater", type=VariableType.INTEGER, domain=(0, 15), description="Number of 2-seater tables", ), Variable( name="tables_4_seater", type=VariableType.INTEGER, domain=(0, 12), description="Number of 4-seater tables", ), Variable( name="tables_6_seater", type=VariableType.INTEGER, domain=(0, 8), description="Number of 6-seater tables", ), ] constraints = [ # Space constraint (floor area in square meters) Constraint( expression="model.add(4*tables_2_seater + 6*tables_4_seater + 9*tables_6_seater <= 150)", description="Total floor space constraint (150 sq meters)", ), # Maximum number of tables (operational constraint) Constraint( expression="model.add(tables_2_seater + tables_4_seater + tables_6_seater <= 20)", description="Maximum number of tables constraint", ), # Ensure a minimum mix of table sizes for variety Constraint( expression="model.add(tables_2_seater >= 2)", description="Minimum number of 2-seater tables", ), Constraint( expression="model.add(tables_4_seater >= 3)", description="Minimum number of 4-seater tables", ), Constraint( expression="model.add(tables_6_seater >= 1)", description="Minimum number of 6-seater tables", ), ] # Objective: Maximize total seating capacity objective = Objective( type=ObjectiveType.MAXIMIZE, expression="2*tables_2_seater + 4*tables_4_seater + 6*tables_6_seater", ) return ORToolsProblem( variables=variables, constraints=constraints, objective=objective, description="Restaurant table layout optimization to maximize seating capacity", ) def create_staff_scheduling_problem(total_seats): """ Create the staff scheduling problem using CVXPY. Args: total_seats: Total seating capacity from the table layout solution Returns: CVXPYProblem: The staff scheduling optimization problem """ # Operating hours (12-hour day) periods = list(range(12)) n_periods = len(periods) # Define variables variables = [ CVXPYVariable( name="staff_per_hour", shape=n_periods, ) ] # Define constraints constraints = [] # Staffing capacity: each staff member can handle up to 20 seats during peak for i in range(n_periods): constraints.append( CVXPYConstraint( expression=f"20 * staff_per_hour[{i}] >= {total_seats} * demand_factor[{i}]", description=f"Staff capacity constraint for hour {i}", ) ) # Minimum staff requirements (always need at least 2 staff) constraints.append( CVXPYConstraint( expression=f"staff_per_hour[{i}] >= 2", description=f"Minimum staff requirement for hour {i}", ) ) # Smooth staffing transitions (max 2 people change between hours) for i in range(n_periods - 1): constraints.append( CVXPYConstraint( expression=f"cp.abs(staff_per_hour[{i+1}] - staff_per_hour[{i}]) <= 2", description=f"Smooth staff transition between hours {i} and {i+1}", ) ) # Define objective: minimize total labor cost objective = CVXPYObjective( type=CVXPYObjectiveType.MINIMIZE, expression="cp.sum(cp.multiply(staff_per_hour, hourly_wage))", ) # Demand patterns throughout the day (as fraction of capacity) demand_pattern = [ 0.4, 0.4, 0.6, 0.8, 1.0, 1.0, # Hours 0-5: gradual morning buildup 0.8, 0.6, 0.8, 1.0, 0.8, 0.4, # Hours 6-11: lunch rush, dinner rush, wind down ] return CVXPYProblem( variables=variables, constraints=constraints, objective=objective, parameters={ "demand_factor": demand_pattern, "hourly_wage": [25.0] * n_periods, # $25/hour per staff member }, description="Restaurant staff scheduling optimization to minimize labor costs", ) def solve_chained_optimization(): """ Solve the chained optimization problem and return results. Returns: dict: Combined results from both optimization stages """ # Stage 1: Solve table layout problem table_problem = create_table_layout_problem() table_result = solve_ortools_problem(table_problem) match table_result: case Success(table_solution): if table_solution.is_feasible: # Extract table layout results solution_values = table_solution.values total_seats = ( 2 * solution_values["tables_2_seater"] + 4 * solution_values["tables_4_seater"] + 6 * solution_values["tables_6_seater"] ) table_results = { "status": "optimal", "tables_2_seater": solution_values["tables_2_seater"], "tables_4_seater": solution_values["tables_4_seater"], "tables_6_seater": solution_values["tables_6_seater"], "total_seats": total_seats, "objective_value": table_solution.objective_value, } # Stage 2: Solve staff scheduling problem using table layout results staff_problem = create_staff_scheduling_problem(total_seats) staff_result = solve_cvxpy_problem(staff_problem) match staff_result: case Success(staff_solution): if staff_solution.status == "optimal": staff_results = { "status": "optimal", "staff_per_hour": staff_solution.values[ "staff_per_hour" ], "total_daily_cost": staff_solution.objective_value, } return { "overall_status": "optimal", "table_layout": table_results, "staff_scheduling": staff_results, } else: return { "overall_status": "staff_scheduling_failed", "table_layout": table_results, "error": f"Staff scheduling failed: {staff_solution.status}", } case _: return { "overall_status": "staff_scheduling_error", "table_layout": table_results, "error": "Error solving staff scheduling problem", } else: return { "overall_status": "table_layout_failed", "error": f"Table layout optimization failed: {table_solution.status}", } case _: return { "overall_status": "table_layout_error", "error": "Error solving table layout problem", } def print_results(results) -> None: """Print chained optimization results in a formatted way.""" print("Chained Solvers Optimization Results") print("=" * 60) if results["overall_status"] != "optimal": print(f"Overall Status: {results['overall_status']}") if "error" in results: print(f"Error: {results['error']}") return # Print table layout results table_results = results["table_layout"] print("\n1. Table Layout Optimization (OR-Tools):") print("-" * 45) print(f"2-seater tables: {table_results['tables_2_seater']}") print(f"4-seater tables: {table_results['tables_4_seater']}") print(f"6-seater tables: {table_results['tables_6_seater']}") print(f"Total seating capacity: {table_results['total_seats']} people") # Print staff scheduling results staff_results = results["staff_scheduling"] print("\n2. Staff Scheduling Optimization (CVXPY):") print("-" * 45) staff_per_hour = staff_results["staff_per_hour"] for hour, staff in enumerate(staff_per_hour): time_label = f"{hour}:00-{hour+1}:00" print(f"Hour {time_label}: {staff:.1f} staff members") print(f"\nTotal daily labor cost: ${staff_results['total_daily_cost']:.2f}") # Calculate some summary statistics avg_staff = np.mean(staff_per_hour) max_staff = np.max(staff_per_hour) min_staff = np.min(staff_per_hour) print("\nStaffing Summary:") print(f"Average staff per hour: {avg_staff:.1f}") print(f"Peak staffing: {max_staff:.1f}") print(f"Minimum staffing: {min_staff:.1f}") def validate_solution(results) -> bool: """Validate that the chained solution is consistent and feasible.""" if results["overall_status"] != "optimal": return False table_results = results["table_layout"] staff_results = results["staff_scheduling"] # Validate table layout total_tables = ( table_results["tables_2_seater"] + table_results["tables_4_seater"] + table_results["tables_6_seater"] ) if total_tables > 20: # Max tables constraint return False # Validate minimum table requirements if ( table_results["tables_2_seater"] < 2 or table_results["tables_4_seater"] < 3 or table_results["tables_6_seater"] < 1 ): return False # Validate staff scheduling staff_per_hour = staff_results["staff_per_hour"] # Check minimum staffing (allow small numerical error) if any(staff < 1.99 for staff in staff_per_hour): return False # Check smooth transitions (max 2 people change) for i in range(len(staff_per_hour) - 1): if ( abs(staff_per_hour[i + 1] - staff_per_hour[i]) > 2.01 ): # Allow small numerical error return False return True def main() -> None: """Main function to run the chained solvers example.""" print(__doc__) results = solve_chained_optimization() print_results(results) # Validate the solution if validate_solution(results): print("\n✓ Solution validation passed!") else: print("\n✗ Solution validation failed!") def test_chained_solvers() -> None: """Test function for pytest.""" results = solve_chained_optimization() # Test that we get an optimal solution assert results["overall_status"] == "optimal" # Test table layout results table_results = results["table_layout"] assert table_results["status"] == "optimal" assert table_results["total_seats"] > 0 # Test minimum table requirements assert table_results["tables_2_seater"] >= 2 assert table_results["tables_4_seater"] >= 3 assert table_results["tables_6_seater"] >= 1 # Test staff scheduling results staff_results = results["staff_scheduling"] assert staff_results["status"] == "optimal" assert staff_results["total_daily_cost"] > 0 # Test staff constraints (allow small numerical error) staff_per_hour = staff_results["staff_per_hour"] assert all(staff >= 1.99 for staff in staff_per_hour) # Minimum 2 staff # Test solution validation assert validate_solution(results) if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Sharmarajnish/MCP-Constrained-Optimization'

If you have feedback or need assistance with the MCP directory API, please join our Discord server