solve_mixed_integer_program
Define variables, constraints, and objective to solve mixed-integer programming problems with support for multiple solvers like SCIP, CBC, GUROBI, and CPLEX.
Instructions
Solve Mixed-Integer Programming (MIP) problems with integer, binary, and continuous variables.
Args:
variables: List of variable definitions with bounds and types
constraints: List of constraint definitions with coefficients and bounds
objective: Objective function definition with coefficients and direction
solver_name: Solver to use ("SCIP", "CBC", "GUROBI", "CPLEX")
time_limit_seconds: Maximum solving time in seconds (default: 30.0)
Returns:
Optimization result with optimal variable values and objective
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| variables | Yes | ||
| constraints | Yes | ||
| objective | Yes | ||
| solver_name | No | SCIP | |
| time_limit_seconds | No |
Implementation Reference
- MCP tool handler for solve_mixed_integer_program. Decorated with @mcp.tool(), it accepts variables, constraints, objective, solver_name, and time_limit_seconds, builds input_data dict, and delegates to solve_integer_program().
@mcp.tool() def solve_mixed_integer_program( variables: list[dict[str, Any]], constraints: list[dict[str, Any]], objective: dict[str, Any], solver_name: str = "SCIP", time_limit_seconds: float = 30.0, ) -> dict[str, Any]: """Solve Mixed-Integer Programming (MIP) problems with integer, binary, and continuous variables. Args: variables: List of variable definitions with bounds and types constraints: List of constraint definitions with coefficients and bounds objective: Objective function definition with coefficients and direction solver_name: Solver to use ("SCIP", "CBC", "GUROBI", "CPLEX") time_limit_seconds: Maximum solving time in seconds (default: 30.0) Returns: Optimization result with optimal variable values and objective """ input_data = { "variables": variables, "constraints": constraints, "objective": objective, "solver_name": solver_name, "time_limit_seconds": time_limit_seconds, } result = solve_integer_program(input_data) result_dict: dict[str, Any] = result.model_dump() return result_dict - Wrapper function solve_mixed_integer_program that takes a dict input_data and delegates to solve_integer_program, returning a dict result.
def solve_mixed_integer_program(input_data: dict[str, Any]) -> dict[str, Any]: """Solve Mixed-Integer Programming Problem (alias for solve_integer_program). Args: input_data: Mixed-integer programming problem specification Returns: Dictionary with optimization result """ result = solve_integer_program(input_data) result_dict: dict[str, Any] = result.model_dump() return result_dict - Pydantic input schemas used by solve_integer_program (the core solver). IntegerVariable, IntegerConstraint, IntegerObjective, IntegerProgramInput define the input format.
class IntegerVariable(BaseModel): """Integer variable definition.""" name: str type: str = Field(pattern="^(integer|binary|continuous)$") lower: float | None = None upper: float | None = None @field_validator("upper") @classmethod def validate_upper(cls, v: float | None, info: ValidationInfo) -> float | None: if v is not None and info.data and "lower" in info.data and v < info.data["lower"]: raise ValueError("upper bound must be >= lower bound") return v class IntegerConstraint(BaseModel): """Integer programming constraint.""" name: str | None = None expression: dict[str, float] # variable_name -> coefficient operator: str = Field(pattern="^(<=|>=|==)$") rhs: float class IntegerObjective(BaseModel): """Integer programming objective.""" sense: str = Field(pattern="^(minimize|maximize)$") coefficients: dict[str, float] # variable_name -> coefficient @field_validator("coefficients") @classmethod def validate_coefficients(cls, v: dict[str, float]) -> dict[str, float]: if not v: raise ValueError("Objective must have at least one coefficient") return v - src/mcp_optimizer/mcp_server.py:13-145 (registration)Registration in mcp_server.py: imports and calls register_integer_programming_tools(mcp) to register the tool with the MCP server.
from mcp_optimizer.tools.integer_programming import register_integer_programming_tools from mcp_optimizer.tools.knapsack import register_knapsack_tools from mcp_optimizer.tools.linear_programming import register_linear_programming_tools from mcp_optimizer.tools.production import register_production_tools from mcp_optimizer.tools.routing import register_routing_tools from mcp_optimizer.tools.scheduling import register_scheduling_tools from mcp_optimizer.tools.validation import register_validation_tools from mcp_optimizer.utils.resource_monitor import ( get_resource_status, reset_resource_stats, resource_monitor, ) logger = logging.getLogger(__name__) # Server start time for uptime calculation _server_start_time = time.time() def get_health() -> dict[str, Any]: """Get server health status and resource information.""" try: resource_status = get_resource_status() status = "healthy" messages = [] # Check memory usage current_memory = resource_status.get("current_memory_mb", 0) max_memory = resource_status.get("max_memory_mb", 1024) memory_usage_pct = (current_memory / max_memory) * 100 if max_memory > 0 else 0 if memory_usage_pct > 90: status = "critical" messages.append(f"High memory usage: {memory_usage_pct:.1f}%") elif memory_usage_pct > 75: status = "warning" messages.append(f"Elevated memory usage: {memory_usage_pct:.1f}%") # Check active requests active_requests = resource_status.get("active_requests", 0) max_requests = resource_status.get("max_concurrent_requests", 10) if active_requests >= max_requests: status = "warning" messages.append("At maximum concurrent request limit") health_info = { "status": status, "version": get_version("mcp-optimizer"), "uptime": time.time() - _server_start_time, "requests_processed": resource_monitor.total_requests, "resource_stats": resource_monitor.get_stats(), } return { "status": status, "version": get_version("mcp-optimizer"), "uptime": time.time() - _server_start_time, "message": "; ".join(messages), "resource_status": resource_status, "health_info": health_info, } except ImportError as e: return { "status": "error", "message": f"Health check failed: {e}", "resource_status": {}, } def get_resource_stats() -> dict[str, Any]: """Get detailed resource usage statistics.""" return get_resource_status() def reset_resource_statistics() -> dict[str, str]: """Reset resource monitoring statistics.""" reset_resource_stats() return {"status": "reset", "message": "Resource statistics have been reset"} def get_server_info() -> dict[str, Any]: """Get comprehensive server information.""" return { "name": "MCP Optimizer", "version": get_version("mcp-optimizer"), "description": "Mathematical optimization server with multiple solvers", "uptime": time.time() - _server_start_time, "capabilities": { "linear_programming": True, "integer_programming": True, "mixed_integer_programming": True, "assignment_problems": True, "transportation_problems": True, "knapsack_problems": True, "routing_problems": True, "scheduling_problems": True, "portfolio_optimization": True, "production_planning": True, "input_validation": True, }, "solvers": { "pulp": "Linear/Integer Programming", "ortools": "Routing, Scheduling, Assignment", "native": "Portfolio, Production Planning", }, "configuration": { "max_solve_time": settings.max_solve_time, "max_memory_mb": settings.max_memory_mb, "max_concurrent_requests": settings.max_concurrent_requests, "log_level": settings.log_level.value, "debug": settings.debug, }, } def create_mcp_server() -> FastMCP[dict[str, str]]: """Create and configure the MCP server with optimization tools.""" # Create MCP server mcp: FastMCP[dict[str, str]] = FastMCP("MCP Optimizer") # Register all optimization tools register_linear_programming_tools(mcp) register_integer_programming_tools(mcp) register_assignment_tools(mcp) register_knapsack_tools(mcp) register_routing_tools(mcp) register_scheduling_tools(mcp) register_financial_tools(mcp) register_production_tools(mcp) register_validation_tools(mcp) - Core solver function using OR-Tools (pywraplp). Handles SCIP/CBC/GUROBI/CPLEX solvers, creates variables (integer/binary/continuous), constraints, objective, solves, and returns OptimizationResult.
@with_resource_limits(timeout_seconds=90.0, estimated_memory_mb=150.0) def solve_integer_program(input_data: dict[str, Any]) -> OptimizationResult: """Solve Integer Programming Problem using OR-Tools. Args: input_data: Integer programming problem specification Returns: OptimizationResult with optimal solution """ if not ORTOOLS_AVAILABLE: return OptimizationResult( status=OptimizationStatus.ERROR, objective_value=None, variables={}, execution_time=0.0, error_message="OR-Tools is not available. Please install it with 'pip install ortools'", ) start_time = time.time() try: # Parse and validate input ip_input = IntegerProgramInput(**input_data) # Create solver solver_name = ip_input.solver if solver_name == "SCIP": solver = pywraplp.Solver.CreateSolver("SCIP") elif solver_name == "CBC": solver = pywraplp.Solver.CreateSolver("CBC") elif solver_name == "GUROBI": solver = pywraplp.Solver.CreateSolver("GUROBI_MIXED_INTEGER_PROGRAMMING") elif solver_name == "CPLEX": solver = pywraplp.Solver.CreateSolver("CPLEX_MIXED_INTEGER_PROGRAMMING") else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Unsupported solver: {solver_name}", execution_time=time.time() - start_time, ) if not solver: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Could not create {solver_name} solver", execution_time=time.time() - start_time, ) # Set time limit if ip_input.time_limit_seconds: solver.SetTimeLimit(int(ip_input.time_limit_seconds * 1000)) # milliseconds # Create variables variables = {} for var_name, var_def in ip_input.variables.items(): lower = var_def.lower if var_def.lower is not None else -solver.infinity() upper = var_def.upper if var_def.upper is not None else solver.infinity() if var_def.type == "continuous": var = solver.NumVar(lower, upper, var_name) elif var_def.type == "integer": var = solver.IntVar( int(lower) if lower != -solver.infinity() else -2147483648, int(upper) if upper != solver.infinity() else 2147483647, var_name, ) elif var_def.type == "binary": var = solver.BoolVar(var_name) else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Unknown variable type: {var_def.type}", execution_time=time.time() - start_time, ) variables[var_name] = var # Add constraints constraints = [] for i, constraint_def in enumerate(ip_input.constraints): constraint_name = constraint_def.name or f"constraint_{i}" # Determine bounds based on operator if constraint_def.operator == "<=": lower_bound = -solver.infinity() upper_bound = constraint_def.rhs elif constraint_def.operator == ">=": lower_bound = constraint_def.rhs upper_bound = solver.infinity() elif constraint_def.operator == "==": lower_bound = constraint_def.rhs upper_bound = constraint_def.rhs else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Unknown constraint operator '{constraint_def.operator}' in constraint '{constraint_name}'", execution_time=time.time() - start_time, ) # Build constraint with proper bounds constraint = solver.Constraint(lower_bound, upper_bound, constraint_name) for var_name, coeff in constraint_def.expression.items(): if var_name in variables: constraint.SetCoefficient(variables[var_name], coeff) else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Unknown variable '{var_name}' in constraint '{constraint_name}'", execution_time=time.time() - start_time, ) constraints.append(constraint) # Set objective objective = solver.Objective() for var_name, coeff in ip_input.objective.coefficients.items(): if var_name in variables: objective.SetCoefficient(variables[var_name], coeff) else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Unknown variable '{var_name}' in objective", execution_time=time.time() - start_time, ) if ip_input.objective.sense == "maximize": objective.SetMaximization() else: objective.SetMinimization() # Set gap tolerance if specified if ip_input.gap_tolerance is not None: solver.SetSolverSpecificParametersAsString(f"limits/gap={ip_input.gap_tolerance}") # Solve status = solver.Solve() # Process results if status == pywraplp.Solver.OPTIMAL: solution_status = OptimizationStatus.OPTIMAL elif status == pywraplp.Solver.FEASIBLE: solution_status = OptimizationStatus.FEASIBLE elif status == pywraplp.Solver.INFEASIBLE: solution_status = OptimizationStatus.INFEASIBLE elif status == pywraplp.Solver.UNBOUNDED: solution_status = OptimizationStatus.UNBOUNDED else: solution_status = OptimizationStatus.ERROR execution_time = time.time() - start_time if status in [pywraplp.Solver.OPTIMAL, pywraplp.Solver.FEASIBLE]: # Extract solution solution_variables = {} for var_name, var in variables.items(): solution_variables[var_name] = var.solution_value() # Calculate constraint violations (for debugging) constraint_info = [] for i, (_constraint, constraint_def) in enumerate( zip(constraints, ip_input.constraints, strict=False) ): lhs_value = sum( coeff * variables[var_name].solution_value() for var_name, coeff in constraint_def.expression.items() ) constraint_info.append( { "name": constraint_def.name or f"constraint_{i}", "lhs_value": lhs_value, "operator": constraint_def.operator, "rhs_value": constraint_def.rhs, "slack": constraint_def.rhs - lhs_value if constraint_def.operator == "<=" else lhs_value - constraint_def.rhs, } ) return OptimizationResult( status=solution_status, objective_value=solver.Objective().Value(), variables=solution_variables, execution_time=execution_time, solver_info={ "solver_name": solver_name, "iterations": solver.iterations() if hasattr(solver, "iterations") else None, "nodes": solver.nodes() if hasattr(solver, "nodes") else None, "gap": (solver.Objective().BestBound() - solver.Objective().Value()) / abs(solver.Objective().Value()) if solver.Objective().Value() != 0 and hasattr(solver.Objective(), "BestBound") else 0, "constraint_info": constraint_info, }, ) else: error_messages = { pywraplp.Solver.INFEASIBLE: "Problem is infeasible", pywraplp.Solver.UNBOUNDED: "Problem is unbounded", pywraplp.Solver.ABNORMAL: "Solver encountered an error", pywraplp.Solver.NOT_SOLVED: "Problem not solved", } return OptimizationResult( status=solution_status, error_message=error_messages.get(status, f"Unknown solver status: {status}"), execution_time=execution_time, solver_info={"solver_name": solver_name}, ) except Exception as e: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Integer programming error: {str(e)}", execution_time=time.time() - start_time, )