USGS

Isis 3.0 Object Programmers' Reference

Home

LeastSquares.cpp
Go to the documentation of this file.
1 
23 #include "jama/jama_svd.h"
24 #include "jama/jama_qr.h"
25 
26 #ifndef __sun__
27 #include "gmm/gmm_superlu_interface.h"
28 #endif
29 
30 #include "LeastSquares.h"
31 #include "IException.h"
32 #include "IString.h"
33 
34 namespace Isis {
42  int sparseRows, int sparseCols, bool jigsaw) {
43  p_jigsaw = jigsaw;
44  p_basis = &basis;
45  p_solved = false;
46  p_sparse = sparse;
47  p_sigma0 = 0.;
48 
49 #if defined(__sun__)
50  p_sparse = false;
51 #endif
52 
53  if (p_sparse) {
54 
55  // make sure sparse nrows/ncols have been set
56  if (sparseRows == 0 || sparseCols == 0) {
57  QString msg = "If solving using sparse matrices, you must enter the "
58  "number of rows/columns";
60  }
61 
62 #ifndef __sun__
63  gmm::resize(p_sparseA, sparseRows, sparseCols);
64  gmm::resize(p_normals, sparseCols, sparseCols);
65  gmm::resize(p_ATb, sparseCols, 1);
66  p_xSparse.resize(sparseCols);
67 
68  if( p_jigsaw ) {
69  p_epsilonsSparse.resize(sparseCols);
70  std::fill_n(p_epsilonsSparse.begin(), sparseCols, 0.0);
71 
72  p_parameterWeights.resize(sparseCols);
73  }
74 
75 #endif
76  p_sparseRows = sparseRows;
77  p_sparseCols = sparseCols;
78  }
79  p_currentFillRow = -1;
80  }
81 
84  }
85 
117  void LeastSquares::AddKnown(const std::vector<double> &data, double result,
118  double weight) {
119  if((int) data.size() != p_basis->Variables()) {
120  QString msg = "Number of elements in data does not match basis [" +
121  p_basis->Name() + "] requirements";
123  }
124 
125  p_expected.push_back(result);
126 
127  if (weight == 1) {
128  p_sqrtWeight.push_back(weight);
129  }
130  else {
131  p_sqrtWeight.push_back(sqrt(weight));
132  }
133 
134  if(p_sparse) {
135 #ifndef __sun__
136  FillSparseA(data);
137 #endif
138  }
139  else {
140  p_input.push_back(data);
141  }
142  }
143 
144 
145 
160 #ifndef __sun__
161  void LeastSquares::FillSparseA(const std::vector<double> &data) {
162 
163  p_basis->Expand(data);
164 
165  p_currentFillRow++;
166 
167  // ??? Speed this up using iterator instead of array indices???
168  int ncolumns = (int)data.size();
169 
170  for(int c = 0; c < ncolumns; c++) {
171  p_sparseA(p_currentFillRow, c) = p_basis->Term(c) * p_sqrtWeight[p_currentFillRow];
172  }
173  }
174 #endif
175 
176 
184  std::vector<double> LeastSquares::GetInput(int row) const {
185  if((row >= Rows()) || (row < 0)) {
186  QString msg = "Index out of bounds [Given = " + toString(row) + "]";
188  }
189  return p_input[row];
190  }
191 
199  double LeastSquares::GetExpected(int row) const {
200  if((row >= Rows()) || (row < 0)) {
201  QString msg = "Index out of bounds [Given = " + toString(row) + "]";
203  }
204  return p_expected[row];
205  }
206 
213  int LeastSquares::Rows() const {
214  return (int)p_input.size();
215  }
216 
232 
233 #if defined(__sun__)
234  if(method == SPARSE) method = QRD;
235 #endif
236 
237  if((method == SPARSE && p_sparseRows == 0) ||
238  (method != SPARSE && Rows() == 0 )) {
239  p_solved = false;
240  QString msg = "No solution available because no input data was provided";
242  }
243 
244  if(method == SVD) {
245  SolveSVD();
246  }
247  else if(method == QRD) {
248  SolveQRD();
249  }
250  else if(method == SPARSE) {
251 #ifndef __sun__
252  int column = SolveSparse();
253  return column;
254 #endif
255  }
256  return 0;
257  }
258 
269 
270  // We are solving Ax=b ... start by creating A
271  TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
272  for(int r = 0; r < A.dim1(); r++) {
273  p_basis->Expand(p_input[r]);
274  for(int c = 0; c < A.dim2(); c++) {
275  A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
276  }
277  }
278 
279  // Ok use singular value decomposition to solve for the coefficients
280  // A = [U][S][V'] where [U] is MxN, [S] is NxN, [V'] is NxN transpose
281  // of [V]. We are solving for [A]x=b and need inverse of [A] such
282  // that x = [invA]b. Since inverse may not exist we use the
283  // pseudo-inverse [A+] from SVD which is [A+] = [V][invS][U']
284  // Our coefficents are then x = [A+]b where b is p_b.
285  JAMA::SVD<double> svd(A);
286 
287  TNT::Array2D<double> V;
288  svd.getV(V);
289 
290  // The inverse of S is the 1 over each diagonal element of S
291  TNT::Array2D<double> invS;
292  svd.getS(invS);
293 
294  for(int i = 0; i < invS.dim1(); i++) {
295  if(invS[i][i] != 0.0) invS[i][i] = 1.0 / invS[i][i];
296  }
297 
298  // Transpose U
299  TNT::Array2D<double> U;
300  svd.getU(U);
301  TNT::Array2D<double> transU(U.dim2(), U.dim1());
302 
303  for(int r = 0; r < U.dim1(); r++) {
304  for(int c = 0; c < U.dim2(); c++) {
305  transU[c][r] = U[r][c];
306  }
307  }
308 
309  // Now multiply everything together to get [A+]
310  TNT::Array2D<double> VinvS = TNT::matmult(V, invS);
311  TNT::Array2D<double> Aplus = TNT::matmult(VinvS, transU);
312 
313  // Using Aplus and our b we can solve for the coefficients
314  TNT::Array2D<double> b(p_expected.size(), 1);
315 
316  for(int r = 0; r < (int)p_expected.size(); r++) {
317  b[r][0] = p_expected[r] * p_sqrtWeight[r];
318  }
319 
320  TNT::Array2D<double> coefs = TNT::matmult(Aplus, b);
321 
322  // If the rank of the matrix is not large enough we don't
323  // have enough coefficients for the solution
324  if (coefs.dim1() < p_basis->Coefficients()) {
325  QString msg = "Unable to solve least-squares using SVD method. No "
326  "solution available. Not enough knowns or knowns are "
327  "co-linear ... [Unknowns = "
328  + toString(p_basis->Coefficients()) + "] [Knowns = "
329  + toString(coefs.dim1()) + "]";
331  }
332 
333  // Set the coefficients in our basis equation
334  std::vector<double> bcoefs;
335  for (int i = 0; i < coefs.dim1(); i++) bcoefs.push_back(coefs[i][0]);
336 
337  p_basis->SetCoefficients(bcoefs);
338 
339  // Compute the errors
340  for(int i = 0; i < (int)p_input.size(); i++) {
341  double value = p_basis->Evaluate(p_input[i]);
342  p_residuals.push_back(value - p_expected[i]);
344  }
345  // calculate degrees of freedom (or redundancy)
346  // DOF = # observations + # constrained parameters - # unknown parameters
347  p_degreesOfFreedom = p_basis->Coefficients() - coefs.dim1();
348 
349  if( p_degreesOfFreedom > 0.0 ) {
351  }
352 
353  // check for p_sigma0 < 0
354  p_sigma0 = sqrt(p_sigma0);
355 
356  // All done
357  p_solved = true;
358  }
359 
372 
373  // We are solving Ax=b ... start by creating an MxN matrix, A
374  TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
375  for(int r = 0; r < A.dim1(); r++) {
376  p_basis->Expand(p_input[r]);
377  for(int c = 0; c < A.dim2(); c++) {
378  A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
379  }
380  }
381 
382  // Ok use to solve for the coefficients
383  // [A] = [Q][R] where [Q] is MxN and orthogonal and [R] is an NxN,
384  // upper triangular matrix. TNT provides the solve method that inverts
385  // [Q] and backsolves [R] to get the coefficients in the vector x.
386  // That is, we solve the system Rx = Q^T b
387  JAMA::QR<double> qr(A);
388 
389  // Using A and our b we can solve for the coefficients
390  TNT::Array1D<double> b(p_expected.size());
391  for(int r = 0; r < (int)p_expected.size(); r++) {
392  b[r] = p_expected[r] * p_sqrtWeight[r];
393  }// by construction, we know the size of b is equal to M, so b is conformant
394 
395  // Check to make sure the matrix is full rank before solving
396  // -- rectangular matrices must be full rank in order for the solve method
397  // to be successful
398  int full = qr.isFullRank();
399  if(full == 0) {
400  QString msg = "Unable to solve-least squares using QR Decomposition. "
401  "The upper triangular R matrix is not full rank";
403  }
404 
405  TNT::Array1D<double> coefs = qr.solve(b);
406 
407  // Set the coefficients in our basis equation
408  std::vector<double> bcoefs;
409  for(int i = 0; i < coefs.dim1(); i++) {
410  bcoefs.push_back(coefs[i]);
411  }
412  p_basis->SetCoefficients(bcoefs);
413 
414  // Compute the errors
415  for(int i = 0; i < (int)p_input.size(); i++) {
416  double value = p_basis->Evaluate(p_input[i]);
417  p_residuals.push_back(value - p_expected[i]);
418  }
419 
420  // All done
421  p_solved = true;
422  }
423 
424 
425 
426 
460 #ifndef __sun__
462 
463  // form "normal equations" matrix by multiplying ATA
464  gmm::mult(gmm::transposed(p_sparseA), p_sparseA, p_normals);
465 
466  // Test for any columns with all 0's
467  // Return column number so caller can determine the appropriate error.
468  int numNonZeros;
469  for(int c = 0; c < p_sparseCols; c++) {
470  numNonZeros = gmm::nnz(gmm::sub_matrix(p_normals,
471  gmm::sub_interval(0,p_sparseCols),
472  gmm::sub_interval(c,1)));
473 
474  if(numNonZeros == 0) return c + 1;
475  }
476 
477  // Create the right-hand-side column vector 'b'
478  gmm::dense_matrix<double> b(p_sparseRows, 1);
479 
480  // multiply each element of 'b' by it's associated weight
481  for ( int r = 0; r < p_sparseRows; r++ )
482  b(r,0) = p_expected[r] * p_sqrtWeight[r];
483 
484  // form ATb
485  gmm::mult(gmm::transposed(p_sparseA), b, p_ATb);
486 
487  // apply parameter weighting if Jigsaw (bundle adjustment)
488  if ( p_jigsaw ) {
489  for( int i = 0; i < p_sparseCols; i++) {
490  double weight = p_parameterWeights[i];
491 
492  if( weight <= 0.0 )
493  continue;
494 
495  p_normals[i][i] += weight;
496  p_ATb[i] -= p_epsilonsSparse[i]*weight;
497  }
498  }
499 
500 // printf("printing rhs\n");
501 // for( int i = 0; i < m_nSparseCols; i++ )
502 // printf("%20.10e\n",m_ATb[i]);
503 
504  // decompose normal equations matrix
505  p_SLU_Factor.build_with(p_normals);
506 
507  // solve with decomposed normals and right hand side
508  // int perm = 0; // use natural ordering
509  int perm = 2; // confirm meaning and necessity of
510 // double recond; // variables perm and recond
511  p_SLU_Factor.solve(p_xSparse,gmm::mat_const_col(p_ATb,0), perm);
512  // Set the coefficients in our basis equation
514 
515  // if Jigsaw (bundle adjustment)
516  // add corrections into epsilon vector (keeping track of total corrections)
517  if ( p_jigsaw ) {
518  for( int i = 0; i < p_sparseCols; i++ )
519  p_epsilonsSparse[i] += p_xSparse[i];
520  }
521 
522  // test print solution
523 // printf("printing solution vector and epsilons\n");
524 // for( int a = 0; a < p_sparseCols; a++ )
525 // printf("soln[%d]: %lf epsilon[%d]: %lf\n",a,p_xSparse[a],a,p_epsilonsSparse[a]);
526 
527 // printf("printing design matrix A\n");
528 // for (int i = 0; i < p_sparseRows; i++ )
529 // {
530 // for (int j = 0; j < p_sparseCols; j++ )
531 // {
532 // if ( j == p_sparseCols-1 )
533 // printf("%20.20e \n",(double)(p_sparseA(i,j)));
534 // else
535 // printf("%20.20e ",(double)(p_sparseA(i,j)));
536 // }
537 // }
538 
539  // Compute the image coordinate residuals and sum into Sigma0
540  // (note this is exactly what was being done before, but with less overhead - I think)
541  // ultimately, we should not be using the A matrix but forming the normals
542  // directly. Then we'll have to compute the residuals by back projection
543 
544  p_residuals.resize(p_sparseRows);
545  gmm::mult(p_sparseA, p_xSparse, p_residuals);
546  p_sigma0 = 0.0;
547 
548  for ( int i = 0; i < p_sparseRows; i++ ) {
549  p_residuals[i] = p_residuals[i]/p_sqrtWeight[i];
550  p_residuals[i] -= p_expected[i];
551  p_sigma0 += p_residuals[i]*p_residuals[i]*p_sqrtWeight[i]*p_sqrtWeight[i];
552  }
553 
554  // if Jigsaw (bundle adjustment)
555  // add contibution to Sigma0 from constrained parameters
556  if ( p_jigsaw ) {
557  double constrained_vTPv = 0.0;
558 
559  for ( int i = 0; i < p_sparseCols; i++ ) {
560  double weight = p_parameterWeights[i];
561 
562  if ( weight <= 0.0 )
563  continue;
564 
565  constrained_vTPv += p_epsilonsSparse[i]*p_epsilonsSparse[i]*weight;
566  }
567  p_sigma0 += constrained_vTPv;
568  }
569  // calculate degrees of freedom (or redundancy)
570  // DOF = # observations + # constrained parameters - # unknown parameters
571  p_degreesOfFreedom = p_sparseRows + p_constrainedParameters - p_sparseCols;
572 
573  if( p_degreesOfFreedom <= 0.0 ) {
574  printf("Observations: %d\nConstrained: %d\nParameters: %d\nDOF: %d\n",
575  p_sparseRows,p_constrainedParameters,p_sparseCols,p_degreesOfFreedom);
576  p_sigma0 = 1.0;
577  }
578  else
580 
581  // check for p_sigma0 < 0
582  p_sigma0 = sqrt(p_sigma0);
583 
584  // kle testing - output residuals and some stats
585  printf("Sigma0 = %20.10lf\nNumber of Observations = %d\nNumber of Parameters = %d\nNumber of Constrained Parameters = %d\nDOF = %d\n",p_sigma0,p_sparseRows,p_sparseCols,p_constrainedParameters,p_degreesOfFreedom);
586 // printf("printing residuals\n");
587 // for( int k = 0; k < p_sparseRows; k++ )
588 // {
589 // printf("%lf %lf\n",p_residuals[k],p_residuals[k+1]);
590 // k++;
591 // }
592 
593  // All done
594  p_solved = true;
595  return 0;
596  }
597 #endif
598 
648 #ifndef __sun__
650  {
651  // clear memory
652  gmm::clear(p_ATb);
653  gmm::clear(p_xSparse);
654 
655  // for each column of the inverse, solve with a right-hand side consisting
656  // of a column of the identity matrix, putting each resulting solution vectfor
657  // into the corresponding column of the inverse matrix
658  for ( int i = 0; i < p_sparseCols; i++ )
659  {
660  if( i > 0 )
661  p_ATb(i-1,0) = 0.0;
662 
663  p_ATb(i,0) = 1.0;
664 
665  // solve with decomposed normals and right hand side
666  p_SLU_Factor.solve(p_xSparse,gmm::mat_const_col(p_ATb,0));
667 
668  // put solution vector x into current column of inverse matrix
669  gmm::copy(p_xSparse, gmm::mat_row(p_normals, i));
670  }
671 
672  // scale inverse by Sigma0 squared to get variance-covariance matrix
673  // if simulated data, we don't scale (effectively scaling by 1.0)
674  // printf("scaling by Sigma0\n");
675  gmm::scale(p_normals,(p_sigma0*p_sigma0));
676 
677 // printf("covariance matrix\n");
678 // for (int i = 0; i < p_sparseCols; i++ )
679 // {
680 // for (int j = 0; j < p_sparseCols; j++ )
681 // {
682 // if ( j == p_sparseCols-1 )
683 // printf("%20.20e \n",(double)(p_sparseInverse(i,j)));
684 // else
685 // printf("%20.20e ",(double)(p_sparseInverse(i,j)));
686 // }
687 // }
688 
689  // standard deviations from covariance matrix
690 // printf("parameter standard deviations\n");
691 // for (int i = 0; i < p_sparseCols; i++ )
692 // {
693 // printf("Sigma Parameter %d = %20.20e \n",i+1,sqrt((double)(p_sparseInverse(i,i))));
694 // }
695 
696  return true;
697  }
698 #endif
699 
700 
701  void LeastSquares::Reset ()
702  {
703  if ( p_sparse ) {
704  gmm::clear(p_sparseA);
705  gmm::clear(p_ATb);
706  gmm::clear(p_normals);
707  p_currentFillRow = -1;
708  }
709  else {
710  p_input.clear();
711  // p_sigma0 = 0.;
712  }
713  p_sigma0 = 0.;
714  p_residuals.clear();
715  p_expected.clear();
716  p_sqrtWeight.clear();
717  p_solved = false;
718  }
719 
720 
721 
732  double LeastSquares::Evaluate(const std::vector<double> &data) {
733  if(!p_solved) {
734  QString msg = "Unable to evaluate until a solution has been computed";
736  }
737  return p_basis->Evaluate(data);
738  }
739 
749  std::vector<double> LeastSquares::Residuals() const {
750  if(!p_solved) {
751  QString msg = "Unable to return residuals until a solution has been computed";
753  }
754  return p_residuals;
755  }
756 
769  double LeastSquares::Residual(int i) const {
770  if(!p_solved) {
771  QString msg = "Unable to return residuals until a solution has been computed";
773  }
774  return p_residuals[i];
775  }
776 
793  void LeastSquares::Weight(int index, double weight) {
794  if(weight == 1) {
795  p_sqrtWeight[index] = weight;
796  }
797  else {
798  p_sqrtWeight[index] = sqrt(weight);
799  }
800  }
801 
802 } // end namespace isis