scijit.optimize.nnls

scijit.optimize.nnls(A, b, *, maxiter=None, validate=True)

Non-negative least squares.

min ||A x - b||_2 subject to x >= 0, by the classical Lawson-Hanson active-set method. Callable from Python and from inside @njit; both entries run the same compiled core.

Parameters:
Aarray_like, shape (m, n)

Coefficient matrix. Cast to float64 and copied to a contiguous buffer, so strided views are safe. Must be 2-D.

barray_like, shape (m,)

Right-hand side, with b.size == A.shape[0]. A 2-D b of one column is ravelled.

maxiterint or None, optional

Cap on the inner feasibility iterations. None and 0 both mean 3 * n; a negative value is a real budget and is exhausted at once. Keyword-only. See Notes for what the cap counts.

validatebool, optional

True (default) raises RuntimeError when maxiter is reached. False returns the current iterate instead.

Returns:
xndarray, shape (n,)

Non-negative solution vector.

rnormfloat

Residual 2-norm ||A x - b||_2, not squared and not halved.

Raises:
ValueError

If A or b holds a non-finite entry; if A is not 2-D; if b has rank above 2, or rank 2 with more than one column; or if b.size != A.shape[0]. The finiteness of both arrays is read before either shape is.

RuntimeError

If maxiter is reached, unless validate=False.

See also

scipy.optimize.nnls

The scipy routine this mirrors.

scijit.optimize.lsq_linear

General lower and upper bounds, not just x >= 0.

scijit.optimize.leastsq

Nonlinear least squares.

Notes

maxiter caps the INNER feasibility iterations of the Lawson-Hanson loop, where scipy’s caps the outer ones, so a small explicit maxiter can stop the two at different points, converging here while scipy raises RuntimeError. The default and any cap large enough to converge agree.

Inside @njit, maxiter and validate may also be passed positionally: numba’s dispatcher does not enforce keyword-only. The Python entry enforces it.

validate has no counterpart in scipy’s signature. scipy raises on non-convergence unconditionally, so the default reproduces scipy. validate=False returns the current iterate instead, for a run over many points where an exception would abort the whole loop.

An A with zero rows returns (zeros(n), 0.0), the same values on every call. With no equations the residual vector has length zero, so ||A x - b|| is 0 for any x; every non-negative x is a minimiser and the zero vector is the minimum-norm one among them. scipy 1.18 reads uninitialised memory on that input: consecutive calls to scipy.optimize.nnls(np.zeros((0, 3)), np.zeros(0)) return a different x each time.

Pure @njit, no callback, no module state and no library handle, so it is safe to call from a numba.prange loop.

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

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.optimize import nnls
>>> A = np.array([[1.0, 0.0], [1.0, 0.0], [0.0, 1.0]])
>>> b = np.array([2.0, 1.0, -1.0])
>>> @njit
... def run():
...     return nnls(A, b)
>>> x, rnorm = run()
>>> x
array([1.5, 0. ])
>>> round(rnorm, 10)
1.2247448714