CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-qpsolvers

Quadratic programming solvers in Python with a unified API

Pending
Overview
Eval results
Files

utilities.mddocs/

Utilities and Conversions

Helper functions for matrix conversions, problem transformations, error handling, debugging support, and sample problem generation. These utilities support advanced QP solving workflows and integration with different solver backends.

Capabilities

Matrix and Problem Conversions

Functions for converting between different matrix formats and problem formulations to ensure compatibility with various solvers.

def combine_linear_box_inequalities(
    G: Optional[Union[np.ndarray, spa.csc_matrix]],
    h: Optional[np.ndarray],
    lb: Optional[np.ndarray],
    ub: Optional[np.ndarray],
    n: int
) -> Tuple[Union[np.ndarray, spa.csc_matrix], np.ndarray]:
    """
    Combine linear inequality and box constraints into a single system.
    
    Converts box constraints lb <= x <= ub into linear inequalities and
    combines them with existing G x <= h constraints.
    
    Parameters:
    - G: Linear inequality constraint matrix
    - h: Linear inequality constraint vector
    - lb: Lower bound constraints (can contain -np.inf)
    - ub: Upper bound constraints (can contain +np.inf)
    - n: Problem dimension
    
    Returns:
    Tuple of (combined_G, combined_h) representing all constraints as G_new x <= h_new
    """

def linear_from_box_inequalities(
    lb: Optional[np.ndarray],
    ub: Optional[np.ndarray],
    n: int
) -> Tuple[Union[np.ndarray, spa.csc_matrix], np.ndarray]:
    """
    Convert box constraints to linear inequality format.
    
    Transforms lb <= x <= ub into -I x <= -lb and I x <= ub form.
    
    Parameters:
    - lb: Lower bound constraints (can contain -np.inf)
    - ub: Upper bound constraints (can contain +np.inf)  
    - n: Problem dimension
    
    Returns:
    Tuple of (G, h) representing box constraints as linear inequalities
    """

def ensure_sparse_matrices(
    P: Union[np.ndarray, spa.csc_matrix],
    q: np.ndarray,
    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
    h: Optional[np.ndarray] = None,
    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
    b: Optional[np.ndarray] = None,
    lb: Optional[np.ndarray] = None,
    ub: Optional[np.ndarray] = None,
) -> Tuple[
    spa.csc_matrix,
    np.ndarray,
    Optional[spa.csc_matrix],
    Optional[np.ndarray],
    Optional[spa.csc_matrix],
    Optional[np.ndarray],
    Optional[np.ndarray],
    Optional[np.ndarray],
]:
    """
    Convert all matrices to sparse CSC format for sparse solvers.
    
    Parameters:
    - P, q, G, h, A, b, lb, ub: Problem matrices and vectors
    
    Returns:
    Tuple with all matrices converted to scipy.sparse.csc_matrix format
    """

def socp_from_qp(problem: Problem) -> Dict[str, Any]:
    """
    Convert a quadratic program to second-order cone program format.
    
    Transforms QP into SOCP formulation for solvers like ECOS.
    
    Parameters:
    - problem: Quadratic program to convert
    
    Returns:
    Dictionary containing SOCP formulation matrices
    """

def split_dual_linear_box(
    z_combined: np.ndarray,
    n_linear: int,
    n_box_lower: int,
    n_box_upper: int
) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:
    """
    Split combined dual multipliers back into linear and box constraint parts.
    
    Reverses the combination performed by combine_linear_box_inequalities.
    
    Parameters:
    - z_combined: Combined dual multiplier vector
    - n_linear: Number of original linear constraints
    - n_box_lower: Number of lower bound constraints
    - n_box_upper: Number of upper bound constraints
    
    Returns:
    Tuple of (z_linear, z_box) dual multipliers
    """

Usage example:

import numpy as np
import scipy.sparse as spa
from qpsolvers.conversions import (
    combine_linear_box_inequalities,
    linear_from_box_inequalities,
    ensure_sparse_matrices
)

# Convert box constraints to linear inequalities
n = 3
lb = np.array([0.0, -np.inf, 1.0])
ub = np.array([np.inf, 2.0, 3.0])

G_box, h_box = linear_from_box_inequalities(lb, ub, n)
print(f"Box constraints as linear inequalities:")
print(f"G_box shape: {G_box.shape}")
print(f"h_box: {h_box}")

# Combine with existing linear constraints
G_linear = np.array([[1.0, 1.0, 1.0]])
h_linear = np.array([5.0])

G_combined, h_combined = combine_linear_box_inequalities(
    G_linear, h_linear, lb, ub, n
)
print(f"Combined constraints shape: {G_combined.shape}")

# Convert matrices to sparse format
P = np.eye(n)
q = np.ones(n)

P_sparse, q_out, G_sparse, h_out, A_out, b_out, lb_out, ub_out = ensure_sparse_matrices(
    P, q, G_combined, h_combined
)
print(f"P matrix format: {type(P_sparse)}")
print(f"G matrix format: {type(G_sparse)}")

Debugging and Visualization

Utilities for debugging QP problems and visualizing matrix structures.

def print_matrix_vector(
    A: Union[np.ndarray, spa.csc_matrix],
    A_label: str,
    b: np.ndarray,
    b_label: str,
    column_width: int = 24,
) -> None:
    """
    Print a matrix and vector side by side for debugging.
    
    Useful for visualizing constraint matrices and vectors together.
    
    Parameters:
    - A: Matrix to print (converted to dense if sparse)
    - A_label: Label for the matrix
    - b: Vector to print
    - b_label: Label for the vector
    - column_width: Width of output columns in characters
    """

Usage example:

import numpy as np
from qpsolvers import print_matrix_vector

# Create example constraint
G = np.array([
    [1.0, 2.0, -1.0],
    [0.0, 1.0,  2.0],
    [-1.0, 0.0, 1.0]
])
h = np.array([3.0, 2.0, 1.0])

print("Constraint visualization:")
print_matrix_vector(G, "G", h, "h")

# Output will show G and h matrices side by side:
# G =                      h =
# [[ 1.  2. -1.]          [[3.]
#  [ 0.  1.  2.]           [2.]
#  [-1.  0.  1.]]          [1.]]

Sample Problem Generation

Functions for creating test problems and benchmarks.

def get_sparse_least_squares(n: int) -> Tuple[
    spa.csc_matrix,  # R matrix
    np.ndarray,      # s vector
    spa.csc_matrix,  # G matrix
    np.ndarray,      # h vector
    spa.csc_matrix,  # A matrix
    np.ndarray,      # b vector
]:
    """
    Generate a sparse least squares test problem.
    
    Creates a problem of the form:
    minimize ||R x - s||^2
    subject to G x <= h, A x = b
    
    Parameters:
    - n: Problem dimension
    
    Returns:
    Tuple of (R, s, G, h, A, b) defining the least squares problem
    """

def get_qpsut01() -> Tuple[Problem, Solution]:
    """
    Get QPSUT01 problem and its solution.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpsut02() -> Tuple[Problem, Solution]:
    """
    Get QPSUT02 problem and its solution.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpsut03() -> Tuple[Problem, Solution]:
    """
    Get QPSUT03 problem and its solution.
    
    This problem has partial box bounds with -infinity on some lower
    bounds and +infinity on some upper bounds.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpsut04() -> Tuple[Problem, Solution]:
    """
    Get QPSUT04 problem and its solution.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpsut05() -> Tuple[Problem, Solution]:
    """
    Get QPSUT05 problem and its solution.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qptest() -> Tuple[Problem, Solution]:
    """
    Get QPTEST problem from the Maros-Meszaros test set.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpgurdu() -> Tuple[Problem, Solution]:
    """
    Get sample random problem with linear inequality constraints.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpgureq() -> Tuple[Problem, Solution]:
    """
    Get sample random problem with equality constraints.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

def get_qpgurabs() -> Tuple[Problem, Solution]:
    """
    Get sample random problem with box constraints.
    
    Returns:
    Tuple of (problem, solution) pair for testing
    """

Usage example:

from qpsolvers.problems import get_sparse_least_squares, get_qpsut01
from qpsolvers import solve_ls, solve_problem

# Generate sparse least squares test problem
n = 50
R, s, G, h, A, b, lb, ub = get_sparse_least_squares(n)

print(f"Generated {n}-dimensional sparse least squares problem")
print(f"R matrix shape: {R.shape}, sparsity: {R.nnz / (R.shape[0] * R.shape[1]):.3f}")
print(f"G matrix shape: {G.shape}")
print(f"A matrix shape: {A.shape}")

# Solve the generated problem
x = solve_ls(R, s, G, h, A, b, lb=lb, ub=ub, solver="osqp")
if x is not None:
    print(f"Solution found with residual: {np.linalg.norm(R @ x - s):.6f}")

# Use a predefined test problem
problem, expected_solution = get_qpsut01()
solution = solve_problem(problem, solver="osqp")

if solution.found:
    print(f"QPSUT01 solved successfully")
    print(f"Expected x: {expected_solution.x}")
    print(f"Computed x: {solution.x}")
    print(f"Difference: {np.linalg.norm(solution.x - expected_solution.x):.6f}")

Exception Handling

Comprehensive exception hierarchy for error handling and debugging.

class QPError(Exception):
    """
    Base class for all qpsolvers exceptions.
    
    All qpsolvers-specific exceptions inherit from this class to avoid
    abstraction leakage from underlying solver libraries.
    """

class NoSolverSelected(QPError):
    """
    Exception raised when the solver parameter is not specified.
    
    Raised by solve_qp when no solver is specified and no default is available.
    """

class ParamError(QPError):
    """
    Exception raised when solver parameters are incorrect.
    
    Indicates issues with solver-specific parameter values or combinations.
    """

class ProblemError(QPError):
    """
    Exception raised when a quadratic program is malformed.
    
    Indicates issues with problem formulation such as:
    - Inconsistent matrix dimensions
    - Invalid constraint specifications
    - Non-convex problems (when detected)
    """

class SolverNotFound(QPError):
    """
    Exception raised when a requested solver is not available.
    
    Includes suggestions for installing the missing solver.
    """

class SolverError(QPError):
    """
    Exception raised when a solver fails during execution.
    
    Wraps solver-specific errors to maintain abstraction boundaries.
    """

Usage example:

from qpsolvers import solve_qp, QPError, SolverNotFound, ProblemError
import numpy as np

def robust_solve(P, q, G=None, h=None, A=None, b=None, solvers=None):
    """Robust QP solving with comprehensive error handling."""
    
    if solvers is None:
        from qpsolvers import available_solvers
        solvers = available_solvers
    
    for solver in solvers:
        try:
            x = solve_qp(P, q, G, h, A, b, solver=solver, verbose=False)
            if x is not None:
                print(f"Successfully solved with {solver}")
                return x
                
        except SolverNotFound:
            print(f"Solver {solver} not available, trying next...")
            continue
            
        except ProblemError as e:
            print(f"Problem is malformed: {e}")
            return None
            
        except QPError as e:
            print(f"Solver {solver} failed: {e}")
            continue
    
    print("No solver succeeded")
    return None

# Example usage
P = np.eye(3)
q = np.ones(3)
result = robust_solve(P, q, solvers=["nonexistent_solver", "osqp", "cvxopt"])

Advanced Utilities

def validate_qp_solution(
    x: np.ndarray,
    P: Union[np.ndarray, spa.csc_matrix],
    q: np.ndarray,
    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
    h: Optional[np.ndarray] = None,
    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
    b: Optional[np.ndarray] = None,
    lb: Optional[np.ndarray] = None,
    ub: Optional[np.ndarray] = None,
    tolerance: float = 1e-6
) -> Dict[str, Any]:
    """
    Validate a QP solution against all constraints.
    
    Parameters:
    - x: Candidate solution
    - P, q, G, h, A, b, lb, ub: Problem definition
    - tolerance: Tolerance for constraint violations
    
    Returns:
    Dictionary with validation results and constraint violations
    """
    
    results = {
        'feasible': True,
        'violations': {},
        'objective': 0.5 * x.T @ P @ x + q.T @ x
    }
    
    # Check linear inequalities
    if G is not None and h is not None:
        violations = (G @ x - h).clip(min=0)
        max_violation = np.max(violations)
        if max_violation > tolerance:
            results['feasible'] = False
            results['violations']['linear_inequality'] = max_violation
    
    # Check linear equalities
    if A is not None and b is not None:
        violations = np.abs(A @ x - b)
        max_violation = np.max(violations)
        if max_violation > tolerance:
            results['feasible'] = False
            results['violations']['linear_equality'] = max_violation
    
    # Check box constraints
    if lb is not None:
        violations = (lb - x).clip(min=0)
        max_violation = np.max(violations)
        if max_violation > tolerance:
            results['feasible'] = False
            results['violations']['lower_bound'] = max_violation
    
    if ub is not None:
        violations = (x - ub).clip(min=0)
        max_violation = np.max(violations)
        if max_violation > tolerance:
            results['feasible'] = False
            results['violations']['upper_bound'] = max_violation
    
    return results

# Example usage
x_candidate = np.array([0.5, 0.3, 0.2])
validation = validate_qp_solution(x_candidate, P, q, G, h, A, b, lb, ub)
print(f"Solution feasible: {validation['feasible']}")
print(f"Objective value: {validation['objective']:.6f}")
if validation['violations']:
    print(f"Constraint violations: {validation['violations']}")

Install with Tessl CLI

npx tessl i tessl/pypi-qpsolvers

docs

core-solving.md

data-structures.md

index.md

solvers.md

utilities.md

tile.json