Skip to content

Instantly share code, notes, and snippets.

@jdelafon
Last active March 12, 2025 20:58
Show Gist options
  • Save jdelafon/b7fdc7a0bae42af56366fc7786cc5d54 to your computer and use it in GitHub Desktop.
Save jdelafon/b7fdc7a0bae42af56366fc7786cc5d54 to your computer and use it in GitHub Desktop.
A Python implementation of NNLS algorithm
"""
A Python implementation of NNLS algorithm
References:
[1] Lawson, C.L. and R.J. Hanson, Solving Least-Squares Problems, Prentice-Hall, Chapter 23, p. 161, 1974.
Based on MATLAB's lsqnonneg function
Ported to Python by Klaus Schuch ([email protected]) and contributed by myself (especially unit tests).
"""
import numpy
def lsqnonneg(C, d, x0=None, tol=None, itmax_factor=3):
'''Linear least squares with nonnegativity constraints.
(x, resnorm, residual) = lsqnonneg(C,d) returns the vector x that minimizes norm(d-C*x)
subject to x >= 0, C and d must be real
'''
eps = 2.22e-16 # from matlab
def norm1(x):
return abs(x).sum().max()
def msize(x, dim):
s = x.shape
if dim >= len(s):
return 1
else:
return s[dim]
if tol is None: tol = 10*eps*norm1(C)*(max(C.shape)+1)
C = numpy.asarray(C)
(m,n) = C.shape
P = numpy.zeros(n)
Z = numpy.arange(1, n+1)
if x0 is None: x=P
else:
if any(x0 < 0): x=P
else: x=x0
ZZ = Z
resid = d - numpy.dot(C, x)
w = numpy.dot(C.T, resid)
outeriter=0; it=0
itmax=itmax_factor*n
exitflag=1
# outer loop to put variables into set to hold positive coefficients
while numpy.any(Z) and numpy.any(w[ZZ-1] > tol):
outeriter += 1
t = w[ZZ-1].argmax()
t = ZZ[t]
P[t-1]=t
Z[t-1]=0
PP = numpy.where(P != 0)[0]+1
ZZ = numpy.where(Z != 0)[0]+1
CP = numpy.zeros(C.shape)
CP[:, PP-1] = C[:, PP-1]
CP[:, ZZ-1] = numpy.zeros((m, msize(ZZ, 1)))
z=numpy.dot(numpy.linalg.pinv(CP), d)
z[ZZ-1] = numpy.zeros((msize(ZZ,1), msize(ZZ,0)))
# inner loop to remove elements from the positve set which no longer belong
while numpy.any(z[PP-1] <= tol):
it += 1
if it > itmax:
max_error = z[PP-1].max()
raise Exception('Exiting: Iteration count (=%d) exceeded\n Try raising the \
tolerance tol. (max_error=%d)' % (it, max_error))
QQ = numpy.where((z <= tol) & (P != 0))[0]
alpha = min(x[QQ]/(x[QQ] - z[QQ]))
x = x + alpha*(z-x)
ij = numpy.where((abs(x) < tol) & (P != 0))[0]+1
Z[ij-1] = ij
P[ij-1] = numpy.zeros(max(ij.shape))
PP = numpy.where(P != 0)[0]+1
ZZ = numpy.where(Z != 0)[0]+1
CP[:, PP-1] = C[:, PP-1]
CP[:, ZZ-1] = numpy.zeros((m, msize(ZZ, 1)))
z=numpy.dot(numpy.linalg.pinv(CP), d)
z[ZZ-1] = numpy.zeros((msize(ZZ,1), msize(ZZ,0)))
x = z
resid = d - numpy.dot(C, x)
w = numpy.dot(C.T, resid)
return (x, sum(resid * resid), resid)
# Unittest
if __name__ == '__main__':
C = numpy.array([[0.0372, 0.2869],
[0.6861, 0.7071],
[0.6233, 0.6245],
[0.6344, 0.6170]])
C1 = numpy.array([[0.0372, 0.2869, 0.4],
[0.6861, 0.7071, 0.3],
[0.6233, 0.6245, 0.1],
[0.6344, 0.6170, 0.5]])
C2 = numpy.array([[0.0372, 0.2869, 0.4],
[0.6861, 0.7071,-0.3],
[0.6233, 0.6245,-0.1],
[0.6344, 0.6170, 0.5]])
d = numpy.array([0.8587, 0.1781, 0.0747, 0.8405])
[x, resnorm, residual] = lsqnonneg(C, d)
dres = abs(resnorm - 0.8315) # compare with matlab result
print('ok, diff:', dres)
if dres > 0.001:
raise Exception('Error')
[x, resnorm, residual] = lsqnonneg(C1, d)
dres = abs(resnorm - 0.1477) # compare with matlab result
print('ok, diff:', dres)
if dres > 0.01:
raise Exception('Error')
[x, resnorm, residual] = lsqnonneg(C2, d)
dres = abs(resnorm - 0.1027) # compare with matlab result
print('ok, diff:', dres)
if dres > 0.01:
raise Exception('Error')
k = numpy.array([[0.1210, 0.2319, 0.4398, 0.9342, 0.1370],
[0.4508, 0.2393, 0.3400, 0.2644, 0.8188],
[0.7159, 0.0498, 0.3142, 0.1603, 0.4302],
[0.8928, 0.0784, 0.3651, 0.8729, 0.8903],
[0.2731, 0.6408, 0.3932, 0.2379, 0.7349],
[0.2548, 0.1909, 0.5915, 0.6458, 0.6873],
[0.8656, 0.8439, 0.1197, 0.9669, 0.3461],
[0.2324, 0.1739, 0.0381, 0.6649, 0.1660],
[0.8049, 0.1708, 0.4586, 0.8704, 0.1556],
[0.9084, 0.9943, 0.8699, 0.0099, 0.1911]])
k1 = k-0.5
l = numpy.array([0.4225, 0.8560, 0.4902, 0.8159, 0.4608, 0.4574, 0.4507, 0.4122, 0.9016, 0.0056])
[x, resnorm, residual] = lsqnonneg(k, l)
dres = abs(resnorm - 0.3695) # compare with matlab result
print('ok, diff:', dres)
if dres > 0.01:
raise Exception('Error')
[x, resnorm, residual] = lsqnonneg(k1, l)
dres = abs(resnorm - 2.8639) # compare with matlab result
print('ok, diff:', dres)
if dres > 0.01:
raise Exception('Error')
C = numpy.array([[1.0, 1.0],
[2.0, 1.0],
[5.0, 1.0],
[6.0, 1.0],
[10.0, 1.0]])
d = numpy.array([3, 5, 11, 13, 21])
[x, resnorm, residual] = lsqnonneg(C, d)
print([x, resnorm, residual])
@jdelafon
Copy link
Author

Thank you for acknowledging the error. I wish one day robots will do something else than losing the developer's time with convincing but wrong results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment