TPCCLIB
Loading...
Searching...
No Matches
nnlsq.c File Reference

NNLS (non-negative least squares) and required subroutines. More...

#include "tpcclibConfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "tpcextensions.h"
#include "tpclinopt.h"

Go to the source code of this file.

Functions

int nnlsq (NNLSQDATA *d, int verbose)
void nnlsqDataInit (NNLSQDATA *d)
void nnlsqDataFree (NNLSQDATA *d)
int nnlsqDataAllocate (NNLSQDATA *d, const int n, const int m)
int nnlsqDataCopy (NNLSQDATA *d1, NNLSQDATA *d2)
int nnlsqDataWght (NNLSQDATA *d, double *weight)
int nnlsqDataWghtSquared (NNLSQDATA *d, double *sweight)

Detailed Description

NNLS (non-negative least squares) and required subroutines.

Author
Vesa Oikonen

This routine is based on the text and Fortran code in C.L. Lawson and R.J. Hanson, Solving Least Squares Problems, Prentice-Hall, Englewood Cliffs, New Jersey, 1974.

Note
Contents of this file are currently under development and testing, and functions in nnls.c should be used instead.

Definition in file nnlsq.c.

Function Documentation

◆ nnlsq()

int nnlsq ( NNLSQDATA * d,
int verbose )

Algorithm NNLS (Non-negative least-squares).

The same algorithm as nnls(), but this one gets its data in a struct, and max iteration number and dependence factor (previously fixed to 0.01) can be set as function parameters in the struct.

Given an m by n matrix A, and an m-vector B, computes an n-vector X, that solves the least squares problem A * X = B , subject to X>=0

Instead of pointers for working space, NULL can be given to let this function to allocate and free the required memory.

See also
nnlsqDataInit, nnlsqDataAllocate, nnlsqDataFree, nnlsqWghtSquared, nnls, qrLSQ
Returns
Function returns 0 if successful, 1, if iteration count exceeded specified limit, 2 in case of invalid problem dimensions, and >2 in case of other errors.
Parameters
dPointer to structure containing the data and place for the results. If function is called repeatedly, remember to reset d.iternr before subsequent runs.
Precondition
Initiate the structure, allocate memory for the data, and possibly weight the data.
Postcondition
Free the allocated memory.
See also
nnlsqDataInit, nnlsqDataAllocate, nnlsqDataFree
Parameters
verboseVerbose level; if zero, then nothing is printed to stderr or stdout.

Definition at line 55 of file nnlsq.c.

65 {
66 if(verbose>0) {printf("%s()\n", __func__); fflush(stdout);}
67
68 /* Check the data */
69 if(d==NULL || d->m<1 || d->n<1 || d->a==NULL || d->b==NULL ||
70 d->x==NULL || d->w==NULL || d->zz==NULL || d->index==NULL)
71 return(2);
72 if(!(d->depf>=1.0E-08) || !(d->depf<0.5)) return(3);
73
74 /* Initialize */
75 for(int ni=0; ni<d->n; ni++) d->x[ni]=0.0;
76 for(int ni=0; ni<d->n; ni++) d->index[ni]=ni;
77 d->rnorm=nan("");
78 int iz1=0;
79 int iz2=d->n-1;
80 int nsetp=0;
81 int npp1=0;
82
83
84 /* Main loop; quit if all coefficients are already in the solution or
85 if M cols of A have been triangulated */
86 double up=0.0;
87 int itermax, iter=0;
88 if(d->iternr>=3) itermax=d->iternr; else itermax=3*d->n;
89 int j=0, jj=0;
90 while(iz1<=iz2 && nsetp<d->m) {
91 /* Compute components of the dual (negative gradient) vector W[] */
92 for(int iz=iz1; iz<=iz2; iz++) {
93 int ni=d->index[iz];
94 double sm=0.;
95 for(int mi=npp1; mi<d->m; mi++) sm+=d->a[ni][mi]*d->b[mi];
96 d->w[ni]=sm;
97 }
98
99 double wmax;
100 int izmax=0;
101 while(1) {
102
103 /* Find largest positive W */
104 wmax=0.0;
105 for(int iz=iz1; iz<=iz2; iz++) {
106 int i=d->index[iz];
107 if(d->w[i]>wmax) {wmax=d->w[i]; izmax=iz;}
108 }
109
110 /* Terminate if wmax<=0.; it indicates satisfaction of the Kuhn-Tucker conditions */
111 if(wmax<=0.0) break;
112
113 /* The sign of W[j] is ok for j to be moved to set P.
114 Begin the transformation and check new diagonal element to avoid near linear dependence. */
115 j=d->index[izmax];
116 double asave=d->a[j][npp1];
117// up=0.0;
118 if(nnlsq_lss_h12(1, npp1, npp1+1, d->m, d->a[j], &up, NULL)) return(2);
119 double unorm=0.0;
120 if(nsetp!=0) for(int mi=0; mi<nsetp; mi++) unorm+=d->a[j][mi]*d->a[j][mi];
121 unorm=sqrt(unorm);
122 double e=unorm+fabs(d->a[j][npp1])*d->depf;
123 if((e-unorm)>0.0) {
124 /* Col j is sufficiently independent. Copy B into ZZ, update ZZ
125 and solve for ztest ( = proposed new value for X[j] ) */
126 for(int mi=0; mi<d->m; mi++) d->zz[mi]=d->b[mi];
127 nnlsq_lss_h12(2, npp1, npp1+1, d->m, d->a[j], &up, d->zz);
128 double ztest=d->zz[npp1]/d->a[j][npp1];
129 /* See if ztest is positive */
130 if(ztest>0.) break;
131 }
132
133 /* Reject j as a candidate to be moved from set Z to set P. Restore
134 A[npp1,j], set W[j]=0., and loop back to test dual coefficients again */
135 d->a[j][npp1]=asave; d->w[j]=0.;
136 } /* while(1) */
137 if(wmax<=0.0) break;
138
139 /* Index j=INDEX[izmax] has been selected to be moved from set Z to set P.
140 Update B and indices, apply householder transformations to cols in
141 new set Z, zero sub-diagonal elements in col j, set W[j]=0. */
142 for(int mi=0; mi<d->m; mi++) d->b[mi]=d->zz[mi];
143 d->index[izmax]=d->index[iz1]; d->index[iz1]=j; iz1++; nsetp=npp1+1; npp1++;
144 if(iz1<=iz2)
145 for(int jz=iz1; jz<=iz2; jz++) {
146 jj=d->index[jz];
147 nnlsq_lss_h12(2, nsetp-1, npp1, d->m, d->a[j], &up, d->a[jj]);
148 }
149 if(nsetp!=d->m) for(int mi=npp1; mi<d->m; mi++) d->a[j][mi]=0.;
150 d->w[j]=0.;
151
152 /* Solve the triangular system; store the solution temporarily in Z[] */
153 for(int mi=0; mi<nsetp; mi++) {
154 int ip=nsetp-(mi+1);
155 if(mi!=0) for(int ii=0; ii<=ip; ii++) d->zz[ii]-=d->a[jj][ii]*d->zz[ip+1];
156 jj=d->index[ip]; d->zz[ip]/=d->a[jj][ip];
157 }
158
159 /* Secondary loop begins here */
160 while(++iter<itermax) {
161 /* See if all new constrained coefficients are feasible; if not, compute alpha */
162 double alpha=2.0;
163 for(int ip=0; ip<nsetp; ip++) {
164 int ni=d->index[ip];
165 if(d->zz[ip]<=0.) {
166 double t=-d->x[ni]/(d->zz[ip]-d->x[ni]);
167 if(alpha>t) {alpha=t; jj=ip-1;}
168 }
169 }
170
171 /* If all new constrained coefficients are feasible then still alpha==2.
172 If so, then exit from the secondary loop to main loop */
173 if(alpha==2.0) break;
174
175 /* Use alpha (0.<alpha<1.) to interpolate between old X and new ZZ */
176 for(int ip=0; ip<nsetp; ip++) {
177 int ni=d->index[ip]; d->x[ni]+=alpha*(d->zz[ip]-d->x[ni]);
178 }
179
180 /* Modify A and B and the INDEX arrays to move coefficient i from set P to set Z. */
181 int pfeas=1;
182 int k=d->index[jj+1];
183 do {
184 d->x[k]=0.;
185 if(jj!=(nsetp-1)) {
186 jj++;
187 for(int ni=jj+1; ni<nsetp; ni++) {
188 int ii=d->index[ni]; d->index[ni-1]=ii;
189 double ss, cc;
190 nnlsq_lss_g1(d->a[ii][ni-1], d->a[ii][ni], &cc, &ss, &d->a[ii][ni-1]);
191 d->a[ii][ni]=0.0;
192 for(int nj=0; nj<d->n; nj++) if(nj!=ii) {
193 /* Apply procedure G2 (CC,SS,A(J-1,L),A(J,L)) */
194 double temp=d->a[nj][ni-1];
195 d->a[nj][ni-1]=cc*temp+ss*d->a[nj][ni];
196 d->a[nj][ni]=-ss*temp+cc*d->a[nj][ni];
197 }
198 /* Apply procedure G2 (CC,SS,B(J-1),B(J)) */
199 double temp=d->b[ni-1];
200 d->b[ni-1]=cc*temp+ss*d->b[ni];
201 d->b[ni]=-ss*temp+cc*d->b[ni];
202 }
203 }
204 npp1=nsetp-1; nsetp--; iz1--; d->index[iz1]=k;
205
206 /* See if the remaining coefficients in set P are feasible; they should be because of
207 the way alpha was determined. If any are infeasible it is due to round-off error.
208 Any that are non-positive will be set to zero and moved from set P to set Z. */
209 pfeas=1;
210 for(jj=0; jj<nsetp; jj++) {
211 k=d->index[jj]; if(d->x[k]<=0.) {pfeas=0; break;}
212 }
213 } while(pfeas==0);
214
215 /* Copy B[] into zz[], then solve again and loop back */
216 for(int mi=0; mi<d->m; mi++) d->zz[mi]=d->b[mi];
217 for(int mi=0; mi<nsetp; mi++) {
218 int ip=nsetp-(mi+1);
219 if(mi!=0) for(int ii=0; ii<=ip; ii++) d->zz[ii]-=d->a[jj][ii]*d->zz[ip+1];
220 jj=d->index[ip]; d->zz[ip]/=d->a[jj][ip];
221 }
222 } /* end of secondary loop */
223
224 if(iter>=itermax) break;
225 for(int ip=0; ip<nsetp; ip++) {int k=d->index[ip]; d->x[k]=d->zz[ip];}
226 } /* end of main loop */
227
228 if(npp1>=d->m) for(int ni=0; ni<d->n; ni++) d->w[ni]=0.;
229
230 /* Compute the norm of the final residual vector (sum-of-squares) */
231 d->rnorm=0.0;
232 for(int mi=npp1; mi<d->m; mi++) d->rnorm+=(d->b[mi]*d->b[mi]);
233
234 d->iternr=iter;
235 if(verbose>2) printf(" %d iterations.\n", iter);
236 if(iter>=itermax) {
237 if(verbose>1) printf(" max iterations reached.\n");
238 return(1);
239 }
240 return(0);
241} /* nnlsq */
double rnorm
Definition tpclinopt.h:136
double * x
Definition tpclinopt.h:123
int * index
Definition tpclinopt.h:131
double * b
Definition tpclinopt.h:121
double ** a
Definition tpclinopt.h:118
double * zz
Definition tpclinopt.h:129
double depf
Definition tpclinopt.h:141
double * w
Definition tpclinopt.h:127

Referenced by spectralDMSurge().

◆ nnlsqDataAllocate()

int nnlsqDataAllocate ( NNLSQDATA * d,
const int n,
const int m )

Allocate memory for NNLSQDATA (and set data pointers inside the structure). Any previous contents are deleted. Contents are set to NaN or zero.

See also
nnlsDataInit, nnlsqDataFree, nnlsqDataCopy, nnlsq, nnlsqWght
Returns
Returns TPCERROR status (0 when successful).
Parameters
dPointer to initiated structure; any old contents are deleted.
nNumber of parameters (columns of matrix A).
mNumber of samples (rows of matrix A).

Definition at line 412 of file nnlsq.c.

419 {
420 if(d==NULL) return(TPCERROR_FAIL);
421 /* Delete any previous contents */
422 nnlsqDataFree(d);
423 /* If no memory is requested, then return fail */
424 if(n<1 || m<1) return(TPCERROR_FAIL);
425
426 /* Allocate memory for all double arrays and matrix */
427 int s = n*m + m + n + n + m;
428#ifdef TEST_BUFOVERFLOW
429 s+=5;
430#endif
431 d->_data=(double*)malloc(sizeof(double)*s);
432 if(d->_data==NULL) return(TPCERROR_OUT_OF_MEMORY);
433 for(int i=0; i<s; i++) d->_data[i]=nan("");
434 /* Allocate memory for matrix pointers */
435 d->a=(double**)malloc(sizeof(double*)*n);
436 if(d->a==NULL) {free(d->_data); return(TPCERROR_OUT_OF_MEMORY);}
437 /* Set up matrix a and double vectors */
438 for(int i=0; i<n; i++) d->a[i] = d->_data + i*m;
439 d->b = d->_data + n*m;
440 d->x = d->_data + n*m + m;
441 d->w = d->_data + n*m + m + n;
442 d->zz = d->_data + n*m + m + n + n;
443#ifdef TEST_BUFOVERFLOW
444 d->b++; d->x+=2; d->w+=3; d->zz+=4;
445#endif
446
447 /* Allocate memory for integer array */
448#ifdef TEST_BUFOVERFLOW
449 d->index=(int*)malloc(sizeof(int)*(1+n));
450 d->index[n] = 999;
451#else
452 d->index=(int*)malloc(sizeof(int)*n);
453#endif
454 if(d->index==NULL) {free(d->_data); free(d->a); return(TPCERROR_OUT_OF_MEMORY);}
455
456 /* Set data sizes */
457 d->n=n;
458 d->m=m;
459
460 return(TPCERROR_OK);
461}
void nnlsqDataFree(NNLSQDATA *d)
Definition nnlsq.c:379
double * _data
Definition tpclinopt.h:134
@ TPCERROR_FAIL
General error.
@ TPCERROR_OUT_OF_MEMORY
Cannot allocate memory.
@ TPCERROR_OK
No error.

Referenced by nnlsqDataCopy(), and spectralDMSurge().

◆ nnlsqDataCopy()

int nnlsqDataCopy ( NNLSQDATA * d1,
NNLSQDATA * d2 )

Copy the contents of NNLSQDATA structure.

Returns
Returns TPCERROR status.
See also
nnlsqDataInit, nnlsqDataFree, nnlsqDataAllocate, nnlsq
Author
Vesa Oikonen
Parameters
d1Pointer to the source structure.
d2Pointer to the target structure. Must be initiated.

Definition at line 470 of file nnlsq.c.

475 {
476 if(d1==NULL || d2==NULL || d1==d2) return(TPCERROR_FAIL);
477 if(d1->n<1 || d1->m<1) return(2);
478 /* If data sizes are different then allocate memory for target */
479 if(d1->n!=d2->n || d1->m!=d2->m) {
480 nnlsqDataFree(d2);
481 int ret=nnlsqDataAllocate(d2, d1->n, d1->m);
482 if(ret!=TPCERROR_OK) return(ret);
483 }
484 /* Copy contents */
485 for(int ni=0; ni<d1->n; ni++)
486 for(int mi=0; mi<d1->m; mi++)
487 d2->a[ni][mi]=d1->a[ni][mi];
488 for(int mi=0; mi<d1->m; mi++)
489 d2->b[mi]=d1->b[mi];
490 for(int ni=0; ni<d1->n; ni++)
491 d2->x[ni]=d1->x[ni];
492 for(int ni=0; ni<d1->n; ni++)
493 d2->w[ni]=d1->w[ni];
494 for(int mi=0; mi<d1->m; mi++)
495 d2->zz[mi]=d1->zz[mi];
496 for(int ni=0; ni<d1->n; ni++)
497 d2->index[ni]=d1->index[ni];
498 d2->rnorm=d1->rnorm;
499 d2->iternr=d1->iternr;
500 d2->depf=d1->depf;
501
502 return(TPCERROR_OK);
503}
int nnlsqDataAllocate(NNLSQDATA *d, const int n, const int m)
Definition nnlsq.c:412

◆ nnlsqDataFree()

void nnlsqDataFree ( NNLSQDATA * d)

Free memory allocated for NNLSQDATA data. All contents are destroyed.

Precondition
Before first use initialize the structure with nnlsqDataInit().
See also
nnlsqDataInit, nnlsqDataAllocate
Author
Vesa Oikonen
Parameters
dPointer to structure.

Definition at line 379 of file nnlsq.c.

382 {
383 if(d==NULL) return;
384 if(d->n<1) return;
385 if(d->m<1) return;
386
387#ifdef TEST_BUFOVERFLOW
388 fprintf(stderr, "testing if buffer overflow happened\n"); fflush(stderr);
389 if(d->index[d->n]!=999) fprintf(stderr, "BUFFER OVERFLOW 1\n");
390 if(!isnan(d->_data[d->m*d->n])) fprintf(stderr, "BUFFER OVERFLOW 2\n");
391 if(!isnan(d->b[d->m])) fprintf(stderr, "BUFFER OVERFLOW 3\n");
392 if(!isnan(d->x[d->n])) fprintf(stderr, "BUFFER OVERFLOW 4\n");
393 if(!isnan(d->w[d->n])) fprintf(stderr, "BUFFER OVERFLOW 5\n");
394 if(!isnan(d->zz[d->m])) fprintf(stderr, "BUFFER OVERFLOW 6\n");
395 fprintf(stderr, "tested buffer overflow\n"); fflush(stderr);
396#endif
397
398 free(d->a);
399 free(d->index);
400 free(d->_data);
401 // then set everything to zero or NULL again
402 nnlsqDataInit(d);
403}
void nnlsqDataInit(NNLSQDATA *d)
Definition nnlsq.c:358

Referenced by nnlsqDataAllocate(), nnlsqDataCopy(), and spectralDMSurge().

◆ nnlsqDataInit()

void nnlsqDataInit ( NNLSQDATA * d)

Initiate the NNLSQDATA structure before any use.

See also
nnlsqDataFree, nnlsqDataAllocate, nnlsq, nnlsqWght
Author
Vesa Oikonen
Parameters
dPointer to structure to initiate before use.

Definition at line 358 of file nnlsq.c.

361 {
362 if(d==NULL) return;
363 d->n = d->m = 0;
364 d->a = NULL;
365 d->b = d->x = d->w = d->zz = d->_data = NULL;
366 d->index = NULL;
367 d->rnorm = 0.0;
368 d->iternr = 0;
369 d->depf = 0.01; // default
370}

Referenced by nnlsqDataFree(), and spectralDMSurge().

◆ nnlsqDataWght()

int nnlsqDataWght ( NNLSQDATA * d,
double * weight )

Algorithm for weighting the problem that is given to NNLS-algorithm.

Square roots of weights are used because in NNLS the difference w*A-w*b is squared.

Returns
Algorithm returns zero if successful, 1 if arguments are inappropriate.
See also
nnlsqWghtSquared, nnlsq, llsqWght
Parameters
dPointer to filled data structure.
weightWeights for each sample (array of length M).

Definition at line 513 of file nnlsq.c.

518 {
519 if(d==NULL || d->n<1 || d->m<1 || d->a==NULL || d->b==NULL || weight==NULL) return(1);
520
521 /* Check that weights are not zero and get the square roots of them to w[] */
522 double w[d->m];
523 for(int mi=0; mi<d->m; mi++) {
524 if(weight[mi]<=1.0e-20) w[mi]=0.0;
525 else w[mi]=sqrt(weight[mi]);
526 }
527
528 /* Multiply rows of matrix A and elements of vector b with weights*/
529 for(int mi=0; mi<d->m; mi++) {
530 for(int ni=0; ni<d->n; ni++) {
531 d->a[ni][mi]*=w[mi];
532 }
533 d->b[mi]*=w[mi];
534 }
535
536 return(0);
537}

Referenced by spectralDMSurge().

◆ nnlsqDataWghtSquared()

int nnlsqDataWghtSquared ( NNLSQDATA * d,
double * sweight )

Algorithm for weighting the problem that is given to NNLS-algorithm.

Square roots of weights are used because in NNLS the difference w*A-w*b is squared. Here user must give squared weights; this makes calculation faster, when this function needs to be called many times.

Returns
Algorithm returns zero if successful, 1 if arguments are inappropriate.
See also
nnlsqWght, nnlsq
Parameters
dPointer to filled data structure.
sweightSquared weights for each sample (array of length d->m).

Definition at line 549 of file nnlsq.c.

554 {
555 if(d==NULL || d->n<1 || d->m<1 || d->a==NULL || d->b==NULL || sweight==NULL) return(1);
556
557 /* Multiply rows of matrix A and elements of vector b with weights */
558 for(int mi=0; mi<d->m; mi++) {
559 for(int ni=0; ni<d->n; ni++) {
560 d->a[ni][mi]*=sweight[mi];
561 }
562 d->b[mi]*=sweight[mi];
563 }
564
565 return(0);
566}