LSQ_LINEAR
Overview
The LSQ_LINEAR function solves linear least-squares problems with bound constraints by wrapping scipy.optimize.lsq_linear. It minimizes subject to optional lower and upper bounds on the variables, offering both trust-region reflective and exact solvers. This example function is provided as-is without any representation of accuracy.
Usage
To fit a bounded linear system in Excel:
=LSQ_LINEAR(a_matrix, b_vector, [bounds_lower], [bounds_upper], [lsq_linear_method], [tol], [max_iter])a_matrix(2D list, required): Coefficient matrix with one row per equation.b_vector(2D list, required): Right-hand-side vector with one entry per row of .bounds_lower(2D list, optional): Single row providing lower bounds for each variable.bounds_upper(2D list, optional): Single row providing upper bounds for each variable.lsq_linear_method(string (enum), optional, default="trf"): Solver choice. Valid options:Trust Region Reflective,Bounded Variable Least Squares.tol(float, optional, default=1e-10): Termination tolerance (must be positive).max_iter(int, optional): Maximum number of iterations allowed.
The function returns a single-row 2D list containing the fitted variables followed by the cost value , or an error message string if validation fails.
Examples
Example 1: Identity System
Inputs:
| a_matrix | b_vector | |
|---|---|---|
| 1 | 0 | 1 |
| 0 | 1 | 2 |
Excel formula:
=LSQ_LINEAR({1,0;0,1}, {1;2})Expected output:
| x₁ | x₂ | Cost |
|---|---|---|
| 1.000 | 2.000 | 0.000 |
Example 2: Bounded Solution
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:
| x₁ | x₂ | Cost |
|---|---|---|
| 3.000 | -1.000 | 2.000 |
Example 3: Overdetermined Exact Solver
Inputs:
| a_matrix | b_vector | 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:
| x₁ | x₂ | Cost |
|---|---|---|
| 0.667 | 0.500 | 0.083 |
Example 4: Custom Tolerance and Iterations
Inputs:
| a_matrix | b_vector | bounds_lower | bounds_upper | tol | max_iter | |||
|---|---|---|---|---|---|---|---|---|
| 2 | -1 | 1 | -1 | -1 | 2 | 2 | 1E-08 | 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-08, 200)Expected output:
| x₁ | x₂ | Cost |
|---|---|---|
| 1.357 | 1.571 | 0.036 |
Python Code
import math
from typing import List, Optional
import numpy as np
from scipy.optimize import lsq_linear as scipy_lsq_linear
def lsq_linear(
a_matrix: List[List[float]],
b_vector: List[List[float]],
bounds_lower: Optional[List[List[float]]] = None,
bounds_upper: Optional[List[List[float]]] = None,
lsq_linear_method: str = 'trf',
tol: float = 1e-10,
max_iter: Optional[int] = None,
):
"""
Solve a bounded linear least-squares problem.
Wraps scipy.optimize.lsq_linear to solve linear least-squares problems with bounds.
See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.lsq_linear.html
Args:
a_matrix: 2D list representing the coefficient matrix A.
b_vector: 2D list representing the observation vector b.
bounds_lower: Optional 2D list providing lower bounds for each variable.
bounds_upper: Optional 2D list providing upper bounds for each variable.
lsq_linear_method: Solver to use ('trf' or 'bvls').
tol: Tolerance for termination.
max_iter: Maximum number of iterations for the solver.
Returns:
list[list[float]] or str: [[x1, x2, ..., cost]] on success, otherwise an error message string.
This example function is provided as-is without any representation of accuracy.
"""
# Validate coefficient matrix
try:
a_mat = np.array(a_matrix, dtype=float)
except Exception:
return "Invalid input: a_matrix must be a 2D list of numeric values."
if a_mat.ndim != 2:
return "Invalid input: a_matrix must be two-dimensional."
# Validate observation vector
try:
b_vec = np.array(b_vector, dtype=float).flatten()
except Exception:
return "Invalid input: b_vector must be a 2D list of numeric values."
if b_vec.size != a_mat.shape[0]:
return "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:
try:
lower_array = np.array(bounds_lower, dtype=float)
lower = lower_array.flatten() if lower_array.ndim > 1 else lower_array
except Exception:
return "Invalid input: bounds_lower must contain numeric values."
if lower.size != n_vars:
return "Invalid input: bounds_lower must have one value per variable."
if bounds_upper is not None:
try:
upper_array = np.array(bounds_upper, dtype=float)
upper = upper_array.flatten() if upper_array.ndim > 1 else upper_array
except Exception:
return "Invalid input: bounds_upper must contain numeric values."
if upper.size != n_vars:
return "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 "Invalid input: Each lower bound must be less than or equal to the corresponding upper bound."
if lower is None:
lower = -math.inf * np.ones(n_vars)
if upper is None:
upper = math.inf * np.ones(n_vars)
bounds = (lower, upper)
# Validate method
valid_methods = {'trf', 'bvls'}
if lsq_linear_method not in valid_methods:
return f"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 "Invalid input: tol must be a float."
if tol <= 0:
return "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 "Invalid input: max_iter must be an integer."
if max_iter_param <= 0:
return "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 during lsq_linear: {exc}"
except Exception as exc:
return f"Error during lsq_linear: {exc}"
if not result.success:
return f"lsq_linear failed: {result.message}"
try:
solution_vector = [float(val) for val in result.x]
except (TypeError, ValueError):
return "Error converting 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]]
Example Workbook
Last updated on