simple_highs_solver
Solve linear programming problems using a simplified HiGHs interface without complex model structures. Input objective coefficients, constraints, and variables to get optimization solutions.
Instructions
A simplified interface for solving HiGHs linear programming problems.
This tool provides a more straightforward interface for HiGHs problems,
without requiring the full HiGHSProblem model structure.
Args:
sense: Optimization sense, either "minimize" or "maximize"
objective_coeffs: List of objective function coefficients
variables: List of variable definitions with optional bounds and types
constraint_matrix: 2D list representing the constraint matrix (dense format)
constraint_senses: List of constraint directions ("<=", ">=", "=")
rhs_values: List of right-hand side values for constraints
options: Optional solver options dictionary
description: Optional description of the problem
Returns:
A list of TextContent containing the solution or an error message
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sense | Yes | ||
| objective_coeffs | Yes | ||
| variables | Yes | ||
| constraint_matrix | Yes | ||
| constraint_senses | Yes | ||
| rhs_values | Yes | ||
| options | No | ||
| description | No |
Implementation Reference
- usolver_mcp/server/main.py:253-395 (handler)MCP tool handler: validates parameters, builds HiGHSProblem from simple inputs using Pydantic models, solves with imported solve_highs_problem, formats result as TextContent.@app.tool("simple_highs_solver") async def simple_highs_solver( sense: str, objective_coeffs: list[float], variables: list[dict[str, Any]], constraint_matrix: list[list[float]], constraint_senses: list[str], rhs_values: list[float], options: dict[str, Any] | None = None, description: str = "", ) -> list[TextContent]: """A simplified interface for solving HiGHs linear programming problems. This tool provides a more straightforward interface for HiGHs problems, without requiring the full HiGHSProblem model structure. Args: sense: Optimization sense, either "minimize" or "maximize" objective_coeffs: List of objective function coefficients variables: List of variable definitions with optional bounds and types constraint_matrix: 2D list representing the constraint matrix (dense format) constraint_senses: List of constraint directions ("<=", ">=", "=") rhs_values: List of right-hand side values for constraints options: Optional solver options dictionary description: Optional description of the problem Returns: A list of TextContent containing the solution or an error message """ try: # Validate sense try: problem_sense = HiGHSSense(sense) except ValueError: return [ TextContent( type="text", text=( f"Invalid sense: {sense}. " f"Must be one of: {', '.join([s.value for s in HiGHSSense])}" ), ) ] # Create objective objective = HiGHSObjective(linear=objective_coeffs) # Create variables problem_variables = [] for i, var in enumerate(variables): var_name = var.get("name", f"x{i+1}") var_lb = var.get("lb", 0.0) var_ub = var.get("ub", None) var_type_str = var.get("type", "cont") try: var_type = HiGHSVariableType(var_type_str) except ValueError: return [ TextContent( type="text", text=( f"Invalid variable type: {var_type_str}. " f"Must be one of: {', '.join([t.value for t in HiGHSVariableType])}" ), ) ] problem_variables.append( HiGHSVariable(name=var_name, lb=var_lb, ub=var_ub, type=var_type) ) # Create constraints constraint_sense_enums = [] for sense_str in constraint_senses: try: constraint_sense_enums.append(HiGHSConstraintSense(sense_str)) except ValueError: return [ TextContent( type="text", text=( f"Invalid constraint sense: {sense_str}. " f"Must be one of: {', '.join([s.value for s in HiGHSConstraintSense])}" ), ) ] constraints = HiGHSConstraints( dense=constraint_matrix, sparse=None, sense=constraint_sense_enums, rhs=rhs_values, ) # Create problem specification problem_spec = HiGHSProblemSpec( sense=problem_sense, objective=objective, variables=problem_variables, constraints=constraints, ) # Create options if provided highs_options = None if options: highs_options = HiGHSOptions(**options) # Create full problem problem = HiGHSProblem(problem=problem_spec, options=highs_options) # Solve the problem result = solve_highs_problem(problem) match result: case Success(solution): return [ TextContent( type="text", text=json.dumps( { "status": solution.status.value, "objective_value": solution.objective_value, "solution": solution.solution, "dual_solution": solution.dual_solution, "variable_duals": solution.variable_duals, } ), ) ] case Failure(error): return [ TextContent(type="text", text=f"Error solving problem: {error}") ] case _: return [ TextContent( type="text", text="Unexpected error in simple_highs_solver", ) ] except Exception as e: return [TextContent(type="text", text=f"Error in simple_highs_solver: {e!s}")]
- usolver_mcp/server/main.py:253-253 (registration)FastMCP tool registration decorator for simple_highs_solver.@app.tool("simple_highs_solver")
- Core solver logic (imported as solve_highs_problem): interfaces with highspy.Highs to set up LP/MIP, apply options, solve, and return HiGHSOutput.def solve_problem(problem: HiGHSProblem) -> Result[HiGHSOutput, str]: """Solve a HiGHs optimization problem.""" try: # Create HiGHs instance h = highspy.Highs() # Always disable output for MCP server compatibility h.setOptionValue("output_flag", False) h.setOptionValue("log_to_console", False) # Apply options options_result = _apply_options(h, problem.options) if isinstance(options_result, Failure): return options_result problem_spec = problem.problem num_vars = len(problem_spec.variables) num_constraints = len(problem_spec.constraints.sense) # Set up objective obj_coeffs = np.array(problem_spec.objective.linear, dtype=float) if len(obj_coeffs) != num_vars: return Failure( f"Objective coefficients length ({len(obj_coeffs)}) doesn't match number of variables ({num_vars})" ) # Set up variable bounds var_lower = np.zeros(num_vars, dtype=float) var_upper = np.full(num_vars, highspy.kHighsInf, dtype=float) for i, var_spec in enumerate(problem_spec.variables): lb, ub = _get_variable_bounds(var_spec, i) var_lower[i] = lb var_upper[i] = ub # Build constraint matrix matrix_result = _build_constraint_matrix(problem_spec) if isinstance(matrix_result, Failure): return matrix_result rows, cols, values = matrix_result.unwrap() # Set up constraint bounds constraint_lower = np.zeros(num_constraints, dtype=float) constraint_upper = np.zeros(num_constraints, dtype=float) for i, (sense, rhs) in enumerate( zip( problem_spec.constraints.sense, problem_spec.constraints.rhs, strict=False, ) ): lb, ub = _convert_constraint_sense(sense, rhs) constraint_lower[i] = lb constraint_upper[i] = ub # Add variables h.addCols( num_vars, obj_coeffs, var_lower, var_upper, 0, np.array([]), np.array([]), np.array([]), ) # Set variable integrality constraints integrality = np.zeros(num_vars, dtype=int) # 0 = continuous for i, var_spec in enumerate(problem_spec.variables): if var_spec.type == HiGHSVariableType.BINARY: integrality[i] = 1 # 1 = integer (binary is integer with bounds 0-1) elif var_spec.type == HiGHSVariableType.INTEGER: integrality[i] = 1 # 1 = integer # else: continuous (already 0) # Apply integrality constraints if any variables are integer/binary if np.any(integrality > 0): h.changeColsIntegrality(num_vars, np.arange(num_vars), integrality) # Add constraints using sparse format if len(rows) > 0: # Convert to row-wise sparse format for HiGHs # Group by rows and create start array unique_rows = np.unique(rows) start_array = np.zeros(num_constraints + 1, dtype=int) for row in unique_rows: start_array[row] = np.sum(rows < row) start_array[-1] = len(rows) # Final start h.addRows( num_constraints, constraint_lower, constraint_upper, len(values), start_array, cols, values, ) else: # No constraints case h.addRows( num_constraints, constraint_lower, constraint_upper, 0, np.array([0]), np.array([]), np.array([]), ) # Set objective sense if problem_spec.sense == HiGHSSense.MAXIMIZE: h.changeObjectiveSense(highspy.ObjSense.kMaximize) else: h.changeObjectiveSense(highspy.ObjSense.kMinimize) # Solve the problem h.run() # Get results model_status = h.getModelStatus() solution = h.getSolution() info = h.getInfo() # Convert status status = _convert_status(model_status) # Extract solution values solution_values = solution.col_value if hasattr(solution, "col_value") else [] dual_values = solution.row_dual if hasattr(solution, "row_dual") else [] reduced_costs = solution.col_dual if hasattr(solution, "col_dual") else [] # Get objective value objective_value = ( info.objective_function_value if hasattr(info, "objective_function_value") else 0.0 ) return Success( HiGHSOutput( status=status, objective_value=objective_value, solution=list(solution_values), dual_solution=list(dual_values), variable_duals=list(reduced_costs), ) ) except Exception as e: return Failure(f"Error solving HiGHs problem: {e}")
- Pydantic models defining structure for HiGHS problems used internally by the handler (HiGHSProblem, HiGHSProblemSpec, HiGHSObjective, HiGHSVariable, HiGHSConstraints, etc.).class HiGHSProblem(BaseModel): """Complete HiGHS optimization problem.""" problem: HiGHSProblemSpec = Field(..., description="Problem specification") options: HiGHSOptions | None = Field(None, description="Solver options")
- Simplified highs solver wrapper (duplicated logic in MCP handler, used directly in example scripts).def simple_highs_solver( sense: str, objective_coeffs: list[float], variables: list[dict], constraint_matrix: list[list[float]], constraint_senses: list[str], rhs_values: list[float], options: dict | None = None, description: str = "", ) -> Result[HiGHSOutput, str]: """A simplified interface for solving HiGHs linear programming problems. This function provides a more straightforward interface for HiGHs problems, without requiring the full HiGHSProblem model structure. Args: sense: Optimization sense, either "minimize" or "maximize" objective_coeffs: List of objective function coefficients variables: List of variable definitions with optional bounds and types constraint_matrix: 2D list representing the constraint matrix (dense format) constraint_senses: List of constraint directions ("<=", ">=", "=") rhs_values: List of right-hand side values for constraints options: Optional solver options dictionary description: Optional description of the problem Returns: Result containing HiGHSOutput or error message """ try: # Validate sense try: problem_sense = HiGHSSense(sense) except ValueError: return Failure( f"Invalid sense: {sense}. " f"Must be one of: {', '.join([s.value for s in HiGHSSense])}" ) # Create objective objective = HiGHSObjective(linear=objective_coeffs) # Create variables problem_variables = [] for i, var in enumerate(variables): var_name = var.get("name", f"x{i+1}") var_lb = var.get("lb", 0.0) var_ub = var.get("ub", None) var_type_str = var.get("type", "cont") try: var_type = HiGHSVariableType(var_type_str) except ValueError: return Failure( f"Invalid variable type: {var_type_str}. " f"Must be one of: {', '.join([t.value for t in HiGHSVariableType])}" ) problem_variables.append( HiGHSVariable(name=var_name, lb=var_lb, ub=var_ub, type=var_type) ) # Create constraints constraint_sense_enums = [] for sense_str in constraint_senses: try: constraint_sense_enums.append(HiGHSConstraintSense(sense_str)) except ValueError: return Failure( f"Invalid constraint sense: {sense_str}. " f"Must be one of: {', '.join([s.value for s in HiGHSConstraintSense])}" ) constraints = HiGHSConstraints( dense=constraint_matrix, sparse=None, sense=constraint_sense_enums, rhs=rhs_values, ) # Create problem specification problem_spec = HiGHSProblemSpec( sense=problem_sense, objective=objective, variables=problem_variables, constraints=constraints, ) # Create options if provided highs_options = None if options: highs_options = HiGHSOptions(**options) # Create full problem problem = HiGHSProblem(problem=problem_spec, options=highs_options) # Solve the problem return solve_problem(problem) except Exception as e: return Failure(f"Error in simple_highs_solver: {e!s}")