LSQ_LINEAR

This function solves a linear least-squares optimization problem with optional lower and upper bounds on the decision variables. It finds the vector that minimizes squared residual error while respecting variable limits.

The optimization problem is:

\min_x \; \frac{1}{2}\lVert Ax - b \rVert_2^2 \quad \text{subject to} \quad l \le x \le u

where A is the coefficient matrix, b is the observation vector, and l,u are optional bounds.

Excel Usage

=LSQ_LINEAR(a_matrix, b_vector, bounds_lower, bounds_upper, lsq_linear_method, tol, max_iter)
  • a_matrix (list[list], required): Coefficient matrix A (m x n) with one row per equation.
  • b_vector (list[list], required): Observation vector b (m x 1) with one entry per row of A.
  • bounds_lower (list[list], optional, default: null): Lower bounds for each variable (1 x n row vector).
  • bounds_upper (list[list], optional, default: null): Upper bounds for each variable (1 x n row vector).
  • lsq_linear_method (str, optional, default: “trf”): Solver method to use for optimization.
  • tol (float, optional, default: 1e-10): Tolerance for termination (must be positive).
  • max_iter (int, optional, default: null): Maximum number of iterations for the solver.

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

Example 1: Identity matrix with exact solution

Inputs:

a_matrix b_vector
1 0 1
0 1 2

Excel formula:

=LSQ_LINEAR({1,0;0,1}, {1;2})

Expected output:

Result
1 2 0
Example 2: Bounded solution with constraints

Inputs:

a_matrix b_vector bounds_lower bounds_upper
1 0 5 0 -2 3 0
0 1 -1

Excel formula:

=LSQ_LINEAR({1,0;0,1}, {5;-1}, {0,-2}, {3,0})

Expected output:

Result
3 -1 2
Example 3: Overdetermined system with BVLS solver

Inputs:

a_matrix b_vector lsq_linear_method
1 1 1 bvls
1 2 2
1 3 2

Excel formula:

=LSQ_LINEAR({1,1;1,2;1,3}, {1;2;2}, "bvls")

Expected output:

Result
0.666667 0.5 0.0833333
Example 4: Custom tolerance and max iterations

Inputs:

a_matrix b_vector bounds_lower bounds_upper tol max_iter
2 -1 1 -1 -1 2 2 1e-8 200
1 1 3
1 -1 0

Excel formula:

=LSQ_LINEAR({2,-1;1,1;1,-1}, {1;3;0}, {-1,-1}, {2,2}, 1e-8, 200)

Expected output:

Result
1.35714 1.57143 0.0357143

Python Code

import numpy as np
from scipy.optimize import lsq_linear as scipy_lsq_linear

def lsq_linear(a_matrix, b_vector, bounds_lower=None, bounds_upper=None, lsq_linear_method='trf', tol=1e-10, max_iter=None):
    """
    Solve a bounded linear least-squares problem.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.lsq_linear.html

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

    Args:
        a_matrix (list[list]): Coefficient matrix A (m x n) with one row per equation.
        b_vector (list[list]): Observation vector b (m x 1) with one entry per row of A.
        bounds_lower (list[list], optional): Lower bounds for each variable (1 x n row vector). Default is None.
        bounds_upper (list[list], optional): Upper bounds for each variable (1 x n row vector). Default is None.
        lsq_linear_method (str, optional): Solver method to use for optimization. Valid options: Trust Region Reflective, Bounded Variable Least Squares. Default is 'trf'.
        tol (float, optional): Tolerance for termination (must be positive). Default is 1e-10.
        max_iter (int, optional): Maximum number of iterations for the solver. Default is None.

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

        # Normalize 2D list inputs
        a_matrix = to2d(a_matrix)
        b_vector = to2d(b_vector)

        # Validate coefficient matrix
        try:
            a_mat = np.array(a_matrix, dtype=float)
        except Exception:
            return "Error: Invalid input: a_matrix must be a 2D list of numeric values."
        if a_mat.ndim != 2:
            return "Error: Invalid input: a_matrix must be two-dimensional."

        # Validate observation vector
        try:
            b_vec = np.array(b_vector, dtype=float).flatten()
        except Exception:
            return "Error: Invalid input: b_vector must be a 2D list of numeric values."
        if b_vec.size != a_mat.shape[0]:
            return "Error: Invalid input: Length of b_vector must equal number of rows in a_matrix."

        n_vars = a_mat.shape[1]

        # Process bounds
        lower = None
        upper = None
        if bounds_lower is not None:
            bounds_lower = to2d(bounds_lower)
            try:
                lower_array = np.array(bounds_lower, dtype=float)
                lower = lower_array.flatten() if lower_array.ndim > 1 else lower_array
            except Exception:
                return "Error: Invalid input: bounds_lower must contain numeric values."
            if lower.size != n_vars:
                return "Error: Invalid input: bounds_lower must have one value per variable."
        if bounds_upper is not None:
            bounds_upper = to2d(bounds_upper)
            try:
                upper_array = np.array(bounds_upper, dtype=float)
                upper = upper_array.flatten() if upper_array.ndim > 1 else upper_array
            except Exception:
                return "Error: Invalid input: bounds_upper must contain numeric values."
            if upper.size != n_vars:
                return "Error: Invalid input: bounds_upper must have one value per variable."

        if lower is not None and upper is not None:
            if np.any(lower > upper):
                return "Error: Invalid input: Each lower bound must be less than or equal to the corresponding upper bound."

        if lower is None:
            lower = np.full(n_vars, -np.inf)
        if upper is None:
            upper = np.full(n_vars, np.inf)

        bounds = (lower, upper)

        # Validate method
        valid_methods = {'trf', 'bvls'}
        if lsq_linear_method not in valid_methods:
            return f"Error: Invalid method: {lsq_linear_method}. Must be one of: {', '.join(sorted(valid_methods))}"

        # Validate tolerance and max_iter
        try:
            tol = float(tol)
        except (TypeError, ValueError):
            return "Error: Invalid input: tol must be a float."
        if tol <= 0:
            return "Error: Invalid input: tol must be positive."

        max_iter_param = None
        if max_iter is not None:
            try:
                max_iter_param = int(max_iter)
            except (TypeError, ValueError):
                return "Error: Invalid input: max_iter must be an integer."
            if max_iter_param <= 0:
                return "Error: Invalid input: max_iter must be positive."

        try:
            result = scipy_lsq_linear(
                a_mat,
                b_vec,
                bounds=bounds,
                method=lsq_linear_method,
                tol=tol,
                max_iter=max_iter_param,
            )
        except ValueError as exc:
            return f"Error: lsq_linear failed: {exc}"
        except Exception as exc:
            return f"Error: lsq_linear failed: {exc}"

        if not result.success:
            return f"Error: lsq_linear failed: {result.message}"

        try:
            solution_vector = [float(val) for val in result.x]
        except (TypeError, ValueError):
            return "Error: Failed to convert solution vector to floats."

        cost = float(result.cost) if result.cost is not None else float(np.sum((a_mat @ result.x - b_vec) ** 2) / 2.0)

        return [solution_vector + [cost]]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Coefficient matrix A (m x n) with one row per equation.
Observation vector b (m x 1) with one entry per row of A.
Lower bounds for each variable (1 x n row vector).
Upper bounds for each variable (1 x n row vector).
Solver method to use for optimization.
Tolerance for termination (must be positive).
Maximum number of iterations for the solver.