CA_QUAD_PROG

Overview

The CA_QUAD_PROG function solves quadratic programming (QP) problems using CasADi, an open-source software framework for numerical optimization. Quadratic programming involves minimizing a quadratic objective function subject to linear constraints, making it fundamental for applications in portfolio optimization, model predictive control, and machine learning.

The function solves problems of the standard QP form:

\text{minimize} \quad \frac{1}{2} x^T Q x + c^T x

subject to:

A_{ub} x \leq b_{ub} \quad \text{(inequality constraints)}
A_{eq} x = b_{eq} \quad \text{(equality constraints)}
x_{lb} \leq x \leq x_{ub} \quad \text{(variable bounds)}

where Q is the Hessian matrix (must be positive semi-definite for convex problems), c is the linear coefficient vector, and the remaining parameters define the constraint structure.

This implementation uses CasADi’s qpsol interface, which provides access to multiple QP solver backends. The default solver is QRQP, a lightweight QR-factorization based solver distributed with CasADi. An alternative OSQP (Operator Splitting Quadratic Program) solver is also available for larger-scale problems. For technical details, see the CasADi QP documentation and the CasADi GitHub repository.

The function returns the optimal variable values along with the minimum objective value. If the QP is infeasible or the solver fails, an error message is returned instead.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=CA_QUAD_PROG(q_matrix, c_vector, a_ub_matrix, b_ub_vector, a_eq_matrix, b_eq_vector, bounds, qp_solver)
  • q_matrix (list[list], required): Hessian matrix Q in the objective term 0.5x’Q*x (must be square)
  • c_vector (list[list], required): Linear coefficient vector c in the linear term c’*x
  • a_ub_matrix (list[list], optional, default: null): Coefficient matrix for inequality constraints A_ub*x <= b_ub
  • b_ub_vector (list[list], optional, default: null): Right-hand side vector for inequality constraints
  • a_eq_matrix (list[list], optional, default: null): Coefficient matrix for equality constraints A_eq*x = b_eq
  • b_eq_vector (list[list], optional, default: null): Right-hand side vector for equality constraints
  • bounds (list[list], optional, default: null): Variable bounds as [min, max] pairs per variable (use null for unbounded)
  • qp_solver (str, optional, default: “qrqp”): QP solver algorithm to use

Returns (list[list]): 2D list [[x1, x2, …, objective]], or error message string.

Examples

Example 1: Bounded quadratic minimization

Inputs:

q_matrix c_vector bounds
2 0 1 -1 1
0 2 1 -1 1

Excel formula:

=CA_QUAD_PROG({2,0;0,2}, {1;1}, {-1,1;-1,1})

Expected output:

Result
-0.5 -0.5 -0.5

Example 2: With inequality constraint

Inputs:

q_matrix c_vector a_ub_matrix b_ub_vector bounds
1 0 1 1 1 2 0 10
0 1 1 0 10

Excel formula:

=CA_QUAD_PROG({1,0;0,1}, {1;1}, {1,1}, 2, {0,10;0,10})

Expected output:

Result
0 0 0

Example 3: Single variable problem

Inputs:

q_matrix c_vector bounds
2 -4 0 2

Excel formula:

=CA_QUAD_PROG(2, -4, {0,2})

Expected output:

Result
2 -4

Example 4: With equality constraint

Inputs:

q_matrix c_vector a_eq_matrix b_eq_vector
1 0 0 1 1 1
0 1 0

Excel formula:

=CA_QUAD_PROG({1,0;0,1}, {0;0}, {1,1}, 1)

Expected output:

Result
0.5 0.5 0.25

Python Code

import numpy as np
import casadi as ca

def ca_quad_prog(q_matrix, c_vector, a_ub_matrix=None, b_ub_vector=None, a_eq_matrix=None, b_eq_vector=None, bounds=None, qp_solver='qrqp'):
    """
    Solve a quadratic programming problem using CasADi's qpsol solver.

    See: https://web.casadi.org/docs/#quadratic-programming

    This example function is provided as-is without any representation of accuracy.

    Args:
        q_matrix (list[list]): Hessian matrix Q in the objective term 0.5*x'*Q*x (must be square)
        c_vector (list[list]): Linear coefficient vector c in the linear term c'*x
        a_ub_matrix (list[list], optional): Coefficient matrix for inequality constraints A_ub*x <= b_ub Default is None.
        b_ub_vector (list[list], optional): Right-hand side vector for inequality constraints Default is None.
        a_eq_matrix (list[list], optional): Coefficient matrix for equality constraints A_eq*x = b_eq Default is None.
        b_eq_vector (list[list], optional): Right-hand side vector for equality constraints Default is None.
        bounds (list[list], optional): Variable bounds as [min, max] pairs per variable (use null for unbounded) Default is None.
        qp_solver (str, optional): QP solver algorithm to use Valid options: QRQP, OSQP. Default is 'qrqp'.

    Returns:
        list[list]: 2D list [[x1, x2, ..., objective]], or error message string.
    """
    def to2d(x):
        return [[x]] if not isinstance(x, list) else x

    def conv(mat, name, cols=None):
        if mat is None:
            return None
        if not isinstance(mat, list) or len(mat) == 0:
            return f"Invalid input: {name} must be a 2D list with at least one row."
        out = []
        for ri, row in enumerate(mat):
            if not isinstance(row, list) or len(row) == 0:
                return f"Invalid input: each row in {name} must be a non-empty list."
            if cols is not None and len(row) != cols:
                return f"Invalid input: {name} must have {cols} columns to match the number of variables."
            r = []
            for ci, v in enumerate(row):
                try:
                    n = float(v)
                except Exception:
                    return f"Invalid input: {name} must be numeric. Problem at row {ri+1}, column {ci+1}."
                if not np.isfinite(n):
                    return f"Invalid input: {name} must contain finite values. Problem at row {ri+1}, column {ci+1}."
                r.append(n)
            out.append(r)
        return np.asarray(out, dtype=float)

    q_matrix = to2d(q_matrix)
    c_vector = to2d(c_vector)
    a_ub_matrix = to2d(a_ub_matrix) if a_ub_matrix is not None else None
    b_ub_vector = to2d(b_ub_vector) if b_ub_vector is not None else None
    a_eq_matrix = to2d(a_eq_matrix) if a_eq_matrix is not None else None
    b_eq_vector = to2d(b_eq_vector) if b_eq_vector is not None else None

    qm = conv(q_matrix, "q_matrix")
    if isinstance(qm, str):
        return qm
    if qm.shape[0] != qm.shape[1]:
        return "Invalid input: q_matrix must be a square matrix."
    n_vars = qm.shape[0]

    cv = conv(c_vector, "c_vector")
    if isinstance(cv, str):
        return cv
    c_vec = cv.flatten()
    if c_vec.size != n_vars:
        return "Invalid input: c_vector size must match q_matrix dimensions."

    if (a_ub_matrix is not None) != (b_ub_vector is not None):
        return "Invalid input: both a_ub_matrix and b_ub_vector must be provided together or both None."

    a_ub_m = conv(a_ub_matrix, "a_ub_matrix", cols=n_vars) if a_ub_matrix is not None else None
    if isinstance(a_ub_m, str):
        return a_ub_m
    b_ub_v = None
    if a_ub_m is not None:
        bv = conv(b_ub_vector, "b_ub_vector")
        if isinstance(bv, str):
            return bv
        if a_ub_m.shape[0] != bv.flatten().size:
            return "Invalid input: Number of rows in a_ub_matrix must equal the length of b_ub_vector."
        b_ub_v = bv.flatten()

    if (a_eq_matrix is not None) != (b_eq_vector is not None):
        return "Invalid input: both a_eq_matrix and b_eq_vector must be provided together or both None."

    a_eq_m = conv(a_eq_matrix, "a_eq_matrix", cols=n_vars) if a_eq_matrix is not None else None
    if isinstance(a_eq_m, str):
        return a_eq_m
    b_eq_v = None
    if a_eq_m is not None:
        bev = conv(b_eq_vector, "b_eq_vector")
        if isinstance(bev, str):
            return bev
        if a_eq_m.shape[0] != bev.flatten().size:
            return "Invalid input: Number of rows in a_eq_matrix must equal the length of b_eq_vector."
        b_eq_v = bev.flatten()

    bounds_lower = bounds_upper = None
    if bounds is not None:
        if not isinstance(bounds, list) or len(bounds) != n_vars:
            return "Invalid input: bounds must supply one [min, max] pair per variable."
        bl, bu = [], []
        for i, p in enumerate(bounds):
            if not isinstance(p, list) or len(p) != 2:
                return "Invalid input: each bounds entry must be a [min, max] pair."
            lo = float(p[0]) if p[0] is not None else -np.inf
            hi = float(p[1]) if p[1] is not None else np.inf
            if not np.isfinite(lo) and p[0] is not None and lo != -np.inf:
                return f"Invalid input: bounds lower value at index {i+1} must be finite or None."
            if not np.isfinite(hi) and p[1] is not None and hi != np.inf:
                return f"Invalid input: bounds upper value at index {i+1} must be finite or None."
            if np.isfinite(lo) and np.isfinite(hi) and lo > hi:
                return f"Invalid input: lower bound must not exceed upper bound for variable {i+1}."
            bl.append(lo)
            bu.append(hi)
        bounds_lower, bounds_upper = np.array(bl), np.array(bu)

    if not isinstance(qp_solver, str) or qp_solver.lower() not in ["qrqp", "osqp"]:
        return "Invalid input: qp_solver must be a string: 'qrqp' or 'osqp'."

    solver = qp_solver

    try:
        x_sym = ca.SX.sym("x", n_vars)
        f_obj = 0.5 * ca.dot(x_sym, ca.mtimes(qm, x_sym)) + ca.dot(c_vec, x_sym)

        if a_ub_m is not None or a_eq_m is not None:
            if a_ub_m is not None and a_eq_m is not None:
                a_combined = np.vstack([a_ub_m, a_eq_m])
            elif a_ub_m is not None:
                a_combined = a_ub_m
            else:
                a_combined = a_eq_m
            g_expr = ca.mtimes(a_combined, x_sym)
        else:
            g_expr = ca.SX(0, 1)

        qp_dict = {"x": x_sym, "f": f_obj, "g": g_expr}
        opts = {"print_time": 0, "print_iter": False, "print_header": False} if solver.lower() == "qrqp" else {"print_time": 0, "verbose": False}
        solver_obj = ca.qpsol("qpsol", solver.lower(), qp_dict, opts)

        solve_args = {}
        if bounds_lower is not None:
            solve_args["lbx"] = bounds_lower.tolist()
        if bounds_upper is not None:
            solve_args["ubx"] = bounds_upper.tolist()

        if a_ub_m is not None or a_eq_m is not None:
            lbc_list, ubc_list = [], []
            if a_ub_m is not None:
                lbc_list.extend([-np.inf] * len(b_ub_v))
                ubc_list.extend(b_ub_v.tolist())
            if a_eq_m is not None:
                lbc_list.extend(b_eq_v.tolist())
                ubc_list.extend(b_eq_v.tolist())
            solve_args["lbg"] = lbc_list
            solve_args["ubg"] = ubc_list

        result = solver_obj(**solve_args)
        x_opt = np.array(result["x"]).flatten()
        return [[float(xi) for xi in x_opt] + [float(result["f"])]]

    except Exception as e:
        return f"ca_quad_prog error: {e}"

Online Calculator