Skip to main content
Glama

solve_mixed_integer_program

Optimize Mixed-Integer Programming problems with integer, binary, and continuous variables. Define variables, constraints, and objectives, then solve using specified solvers like SCIP, CBC, GUROBI, or CPLEX within a given time limit.

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

TableJSON Schema
NameRequiredDescriptionDefault
constraintsYes
objectiveYes
solver_nameNoSCIP
time_limit_secondsNo
variablesYes

Implementation Reference

  • The primary handler function for the MCP tool 'solve_mixed_integer_program'. It receives tool arguments as dicts/lists, constructs the input for the solver, calls solve_integer_program, and returns the result as a dict.
    @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
  • Registration call in the main MCP server creation function that registers the integer programming tools, including 'solve_mixed_integer_program'.
    register_integer_programming_tools(mcp)
  • Core helper function that performs the actual mixed-integer programming solving using OR-Tools solvers (SCIP, CBC, etc.). Validates input with Pydantic, builds the model, solves, and extracts results.
    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, )
  • Pydantic schema classes for validating the integer/mixed-integer program input structure used in solve_integer_program.
    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 class IntegerProgramInput(BaseModel): """Input schema for Integer Programming.""" objective: IntegerObjective variables: dict[str, IntegerVariable] constraints: list[IntegerConstraint] solver: str = Field(default="SCIP", pattern="^(SCIP|CBC|GUROBI|CPLEX)$") time_limit_seconds: float | None = Field(default=None, ge=0) gap_tolerance: float | None = Field(default=None, ge=0, le=1) @field_validator("variables") @classmethod def validate_variables(cls, v: dict[str, IntegerVariable]) -> dict[str, IntegerVariable]: if not v: raise ValueError("Must have at least one variable") return v @field_validator("constraints") @classmethod def validate_constraints(cls, v: list[IntegerConstraint]) -> list[IntegerConstraint]: if not v: raise ValueError("Must have at least one constraint") return v

Latest Blog Posts

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/dmitryanchikov/mcp-optimizer'

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