USGS

Isis 3.0 Object Programmers' Reference

Home

LeastSquares.cpp

Go to the documentation of this file.
00001 
00023 #include "jama/jama_svd.h"
00024 #include "jama/jama_qr.h"
00025 
00026 #ifndef __sun__
00027 #include "gmm/gmm_superlu_interface.h"
00028 #endif
00029 
00030 #include "LeastSquares.h"
00031 #include "IException.h"
00032 #include "IString.h"
00033 
00034 namespace Isis {
00041   LeastSquares::LeastSquares(Isis::BasisFunction &basis, bool sparse,
00042                              int sparseRows, int sparseCols, bool jigsaw) {
00043     p_jigsaw = jigsaw;
00044     p_basis = &basis;
00045     p_solved = false;
00046     p_sparse = sparse;
00047     p_sigma0 = 0.;
00048 
00049 #if defined(__sun__)
00050     p_sparse = false;
00051 #endif
00052 
00053     if (p_sparse) {
00054 
00055       //  make sure sparse nrows/ncols have been set
00056       if (sparseRows == 0  ||  sparseCols == 0) {
00057         QString msg = "If solving using sparse matrices, you must enter the "
00058                       "number of rows/columns";
00059         throw IException(IException::Programmer, msg, _FILEINFO_);
00060       }
00061 
00062 #ifndef __sun__
00063       gmm::resize(p_sparseA, sparseRows, sparseCols);
00064       gmm::resize(p_normals, sparseCols, sparseCols);
00065       gmm::resize(p_ATb, sparseCols, 1);
00066       p_xSparse.resize(sparseCols);
00067 
00068       if( p_jigsaw ) {
00069         p_epsilonsSparse.resize(sparseCols);
00070         std::fill_n(p_epsilonsSparse.begin(), sparseCols, 0.0);
00071 
00072         p_parameterWeights.resize(sparseCols);
00073       }
00074 
00075 #endif
00076       p_sparseRows = sparseRows;
00077       p_sparseCols = sparseCols;
00078     }
00079     p_currentFillRow = -1;
00080   }
00081 
00083   LeastSquares::~LeastSquares() {
00084   }
00085 
00117   void LeastSquares::AddKnown(const std::vector<double> &data, double result,
00118                               double weight) {
00119     if((int) data.size() != p_basis->Variables()) {
00120       QString msg = "Number of elements in data does not match basis [" +
00121                         p_basis->Name() + "] requirements";
00122       throw IException(IException::Programmer, msg, _FILEINFO_);
00123     }
00124 
00125     p_expected.push_back(result);
00126 
00127     if (weight == 1) {
00128       p_sqrtWeight.push_back(weight);
00129     }
00130     else {
00131       p_sqrtWeight.push_back(sqrt(weight));
00132     }
00133 
00134     if(p_sparse) {
00135 #ifndef __sun__
00136       FillSparseA(data);
00137 #endif
00138     }
00139     else {
00140       p_input.push_back(data);
00141     }
00142   }
00143 
00144 
00145 
00160 #ifndef __sun__
00161   void LeastSquares::FillSparseA(const std::vector<double> &data) {
00162 
00163     p_basis->Expand(data);
00164 
00165     p_currentFillRow++;
00166 
00167     //  ??? Speed this up using iterator instead of array indices???
00168     int ncolumns = (int)data.size();
00169 
00170     for(int c = 0;  c < ncolumns; c++) {
00171       p_sparseA(p_currentFillRow, c) = p_basis->Term(c) * p_sqrtWeight[p_currentFillRow];
00172     }
00173   }
00174 #endif
00175 
00176 
00184   std::vector<double> LeastSquares::GetInput(int row) const {
00185     if((row >= Rows()) || (row < 0)) {
00186       QString msg = "Index out of bounds [Given = " + toString(row) + "]";
00187       throw IException(IException::Programmer, msg, _FILEINFO_);
00188     }
00189     return p_input[row];
00190   }
00191 
00199   double LeastSquares::GetExpected(int row) const {
00200     if((row >= Rows()) || (row < 0)) {
00201       QString msg = "Index out of bounds [Given = " + toString(row) + "]";
00202       throw IException(IException::Programmer, msg, _FILEINFO_);
00203     }
00204     return p_expected[row];
00205   }
00206 
00213   int LeastSquares::Rows() const {
00214     return (int)p_input.size();
00215   }
00216 
00231   int LeastSquares::Solve(Isis::LeastSquares::SolveMethod method) {
00232 
00233 #if defined(__sun__)
00234     if(method == SPARSE) method = QRD;
00235 #endif
00236 
00237     if((method == SPARSE  &&  p_sparseRows == 0)  ||
00238        (method != SPARSE  &&  Rows() == 0 )) {
00239       p_solved = false;
00240       QString msg = "No solution available because no input data was provided";
00241       throw IException(IException::Unknown, msg, _FILEINFO_);
00242     }
00243 
00244     if(method == SVD) {
00245       SolveSVD();
00246     }
00247     else if(method == QRD) {
00248       SolveQRD();
00249     }
00250     else if(method == SPARSE) {
00251 #ifndef __sun__
00252       int column = SolveSparse();
00253       return column;
00254 #endif
00255     }
00256     return 0;
00257   }
00258 
00268   void LeastSquares::SolveSVD() {
00269 
00270     // We are solving Ax=b ... start by creating A
00271     TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
00272     for(int r = 0; r < A.dim1(); r++) {
00273       p_basis->Expand(p_input[r]);
00274       for(int c = 0; c < A.dim2(); c++) {
00275         A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
00276       }
00277     }
00278 
00279     // Ok use singular value decomposition to solve for the coefficients
00280     // A = [U][S][V']  where [U] is MxN, [S] is NxN, [V'] is NxN transpose
00281     // of [V].  We are solving for [A]x=b and need inverse of [A] such
00282     // that x = [invA]b. Since inverse may not exist we use the
00283     // pseudo-inverse [A+] from SVD which is [A+] = [V][invS][U']
00284     // Our coefficents are then x = [A+]b where b is p_b.
00285     JAMA::SVD<double> svd(A);
00286 
00287     TNT::Array2D<double> V;
00288     svd.getV(V);
00289 
00290     // The inverse of S is the 1 over each diagonal element of S
00291     TNT::Array2D<double> invS;
00292     svd.getS(invS);
00293 
00294     for(int i = 0; i < invS.dim1(); i++) {
00295       if(invS[i][i] != 0.0) invS[i][i] = 1.0 / invS[i][i];
00296     }
00297 
00298     // Transpose U
00299     TNT::Array2D<double> U;
00300     svd.getU(U);
00301     TNT::Array2D<double> transU(U.dim2(), U.dim1());
00302 
00303     for(int r = 0; r < U.dim1(); r++) {
00304       for(int c = 0; c < U.dim2(); c++) {
00305         transU[c][r] = U[r][c];
00306       }
00307     }
00308 
00309     // Now multiply everything together to get [A+]
00310     TNT::Array2D<double> VinvS = TNT::matmult(V, invS);
00311     TNT::Array2D<double> Aplus = TNT::matmult(VinvS, transU);
00312 
00313     // Using Aplus and our b we can solve for the coefficients
00314     TNT::Array2D<double> b(p_expected.size(), 1);
00315 
00316     for(int r = 0; r < (int)p_expected.size(); r++) {
00317       b[r][0] = p_expected[r] * p_sqrtWeight[r];
00318     }
00319 
00320     TNT::Array2D<double> coefs = TNT::matmult(Aplus, b);
00321 
00322     // If the rank of the matrix is not large enough we don't
00323     // have enough coefficients for the solution
00324     if (coefs.dim1() < p_basis->Coefficients()) {
00325       QString msg = "Unable to solve least-squares using SVD method. No "
00326                     "solution available. Not enough knowns or knowns are "
00327                     "co-linear ... [Unknowns = " 
00328                     + toString(p_basis->Coefficients()) + "] [Knowns = " 
00329                     + toString(coefs.dim1()) + "]";
00330       throw IException(IException::Unknown, msg, _FILEINFO_);
00331     }
00332 
00333     // Set the coefficients in our basis equation
00334     std::vector<double> bcoefs;
00335     for (int i = 0; i < coefs.dim1(); i++) bcoefs.push_back(coefs[i][0]);
00336 
00337     p_basis->SetCoefficients(bcoefs);
00338 
00339     // Compute the errors
00340     for(int i = 0; i < (int)p_input.size(); i++) {
00341       double value = p_basis->Evaluate(p_input[i]);
00342       p_residuals.push_back(value - p_expected[i]);
00343       p_sigma0 += p_residuals[i]*p_residuals[i]*p_sqrtWeight[i]*p_sqrtWeight[i];
00344     }
00345     // calculate degrees of freedom (or redundancy)
00346     // DOF = # observations + # constrained parameters - # unknown parameters
00347     p_degreesOfFreedom = p_basis->Coefficients() - coefs.dim1();
00348 
00349     if( p_degreesOfFreedom > 0.0 )  {
00350       p_sigma0 = p_sigma0/(double)p_degreesOfFreedom;
00351     }
00352 
00353     // check for p_sigma0 < 0
00354     p_sigma0 = sqrt(p_sigma0);
00355 
00356     // All done
00357     p_solved = true;
00358   }
00359 
00371   void LeastSquares::SolveQRD() {
00372 
00373     // We are solving Ax=b ... start by creating an MxN matrix, A
00374     TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
00375     for(int r = 0; r < A.dim1(); r++) {
00376       p_basis->Expand(p_input[r]);
00377       for(int c = 0; c < A.dim2(); c++) {
00378         A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
00379       }
00380     }
00381 
00382     // Ok use  to solve for the coefficients
00383     // [A] = [Q][R]  where [Q] is MxN and orthogonal and  [R] is an NxN,
00384     // upper triangular matrix.  TNT provides the solve method that inverts
00385     // [Q] and backsolves [R] to get the coefficients in the vector x.
00386     // That is, we solve the system Rx = Q^T b
00387     JAMA::QR<double> qr(A);
00388 
00389     // Using A and our b we can solve for the coefficients
00390     TNT::Array1D<double> b(p_expected.size());
00391     for(int r = 0; r < (int)p_expected.size(); r++) {
00392       b[r] = p_expected[r] * p_sqrtWeight[r];
00393     }// by construction, we know the size of b is equal to M, so b is conformant
00394 
00395     // Check to make sure the matrix is full rank before solving
00396     // -- rectangular matrices must be full rank in order for the solve method
00397     //    to be successful
00398     int full = qr.isFullRank();
00399     if(full == 0) {
00400       QString msg = "Unable to solve-least squares using QR Decomposition. "
00401                     "The upper triangular R matrix is not full rank";
00402       throw IException(IException::Unknown, msg, _FILEINFO_);
00403     }
00404 
00405     TNT::Array1D<double> coefs = qr.solve(b);
00406 
00407     // Set the coefficients in our basis equation
00408     std::vector<double> bcoefs;
00409     for(int i = 0; i < coefs.dim1(); i++) {
00410       bcoefs.push_back(coefs[i]);
00411     }
00412     p_basis->SetCoefficients(bcoefs);
00413 
00414     // Compute the errors
00415     for(int i = 0; i < (int)p_input.size(); i++) {
00416       double value = p_basis->Evaluate(p_input[i]);
00417       p_residuals.push_back(value - p_expected[i]);
00418     }
00419 
00420     // All done
00421     p_solved = true;
00422   }
00423 
00424 
00425 
00426 
00460 #ifndef __sun__
00461   int LeastSquares::SolveSparse() {
00462 
00463     // form "normal equations" matrix by multiplying ATA
00464     gmm::mult(gmm::transposed(p_sparseA), p_sparseA, p_normals);
00465 
00466     //  Test for any columns with all 0's
00467     //  Return column number so caller can determine the appropriate error.
00468     int numNonZeros;
00469     for(int c = 0; c < p_sparseCols; c++) {
00470       numNonZeros = gmm::nnz(gmm::sub_matrix(p_normals,
00471                              gmm::sub_interval(0,p_sparseCols),
00472                              gmm::sub_interval(c,1)));
00473 
00474       if(numNonZeros == 0) return c + 1;
00475     }
00476 
00477     // Create the right-hand-side column vector 'b'
00478     gmm::dense_matrix<double> b(p_sparseRows, 1);
00479 
00480     // multiply each element of 'b' by it's associated weight
00481     for ( int r = 0; r < p_sparseRows; r++ )
00482       b(r,0) = p_expected[r] * p_sqrtWeight[r];
00483 
00484     // form ATb
00485     gmm::mult(gmm::transposed(p_sparseA), b, p_ATb);
00486 
00487     // apply parameter weighting if Jigsaw (bundle adjustment)
00488     if ( p_jigsaw ) {
00489       for( int i = 0; i < p_sparseCols; i++) {
00490         double weight = p_parameterWeights[i];
00491 
00492         if( weight <= 0.0 )
00493           continue;
00494 
00495         p_normals[i][i] += weight;
00496         p_ATb[i] -= p_epsilonsSparse[i]*weight;
00497       }
00498     }
00499 
00500 //    printf("printing rhs\n");
00501 //    for( int i = 0; i < m_nSparseCols; i++ )
00502 //      printf("%20.10e\n",m_ATb[i]);
00503 
00504     // decompose normal equations matrix
00505     p_SLU_Factor.build_with(p_normals);
00506 
00507     // solve with decomposed normals and right hand side
00508     // int perm = 0;  //  use natural ordering
00509     int perm = 2;     //  confirm meaning and necessity of
00510 //  double recond;    //  variables perm and recond
00511     p_SLU_Factor.solve(p_xSparse,gmm::mat_const_col(p_ATb,0), perm);
00512     // Set the coefficients in our basis equation
00513     p_basis->SetCoefficients(p_xSparse);
00514 
00515     // if Jigsaw (bundle adjustment)
00516     // add corrections into epsilon vector (keeping track of total corrections)
00517     if ( p_jigsaw ) {
00518       for( int i = 0; i < p_sparseCols; i++ )
00519         p_epsilonsSparse[i] += p_xSparse[i];
00520     }
00521 
00522     // test print solution
00523 //    printf("printing solution vector and epsilons\n");
00524 //    for( int a = 0; a < p_sparseCols; a++ )
00525 //      printf("soln[%d]: %lf epsilon[%d]: %lf\n",a,p_xSparse[a],a,p_epsilonsSparse[a]);
00526 
00527 //    printf("printing design matrix A\n");
00528 //    for (int i = 0; i < p_sparseRows; i++ )
00529 //    {
00530 //      for (int j = 0; j < p_sparseCols; j++ )
00531 //      {
00532 //        if ( j == p_sparseCols-1 )
00533 //          printf("%20.20e \n",(double)(p_sparseA(i,j)));
00534 //        else
00535 //          printf("%20.20e ",(double)(p_sparseA(i,j)));
00536 //      }
00537 //    }
00538 
00539     // Compute the image coordinate residuals and sum into Sigma0
00540     // (note this is exactly what was being done before, but with less overhead - I think)
00541     // ultimately, we should not be using the A matrix but forming the normals
00542     // directly. Then we'll have to compute the residuals by back projection
00543 
00544     p_residuals.resize(p_sparseRows);
00545     gmm::mult(p_sparseA, p_xSparse, p_residuals);
00546     p_sigma0 = 0.0;
00547 
00548     for ( int i = 0; i < p_sparseRows; i++ ) {
00549         p_residuals[i] = p_residuals[i]/p_sqrtWeight[i];
00550         p_residuals[i] -= p_expected[i];
00551         p_sigma0 += p_residuals[i]*p_residuals[i]*p_sqrtWeight[i]*p_sqrtWeight[i];
00552     }
00553 
00554     // if Jigsaw (bundle adjustment)
00555     // add contibution to Sigma0 from constrained parameters
00556     if ( p_jigsaw ) {
00557       double constrained_vTPv = 0.0;
00558 
00559       for ( int i = 0; i < p_sparseCols; i++ ) {
00560         double weight = p_parameterWeights[i];
00561 
00562         if ( weight <= 0.0 )
00563           continue;
00564 
00565         constrained_vTPv += p_epsilonsSparse[i]*p_epsilonsSparse[i]*weight;
00566       }
00567       p_sigma0 += constrained_vTPv;
00568     }
00569     // calculate degrees of freedom (or redundancy)
00570     // DOF = # observations + # constrained parameters - # unknown parameters
00571     p_degreesOfFreedom = p_sparseRows + p_constrainedParameters - p_sparseCols;
00572 
00573     if( p_degreesOfFreedom <= 0.0 ) {
00574       printf("Observations: %d\nConstrained: %d\nParameters: %d\nDOF: %d\n",
00575              p_sparseRows,p_constrainedParameters,p_sparseCols,p_degreesOfFreedom);
00576       p_sigma0 = 1.0;
00577     }
00578     else
00579       p_sigma0 = p_sigma0/(double)p_degreesOfFreedom;
00580 
00581     // check for p_sigma0 < 0
00582     p_sigma0 = sqrt(p_sigma0);
00583 
00584     // kle testing - output residuals and some stats
00585     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);
00586 //    printf("printing residuals\n");
00587 //    for( int k = 0; k < p_sparseRows; k++ )
00588 //    {
00589 //      printf("%lf %lf\n",p_residuals[k],p_residuals[k+1]);
00590 //      k++;
00591 //    }
00592 
00593     // All done
00594     p_solved = true;
00595     return 0;
00596   }
00597 #endif
00598 
00648 #ifndef __sun__
00649   bool LeastSquares::SparseErrorPropagation ()
00650   {
00651     // clear memory
00652     gmm::clear(p_ATb);
00653     gmm::clear(p_xSparse);
00654 
00655     // for each column of the inverse, solve with a right-hand side consisting
00656     // of a column of the identity matrix, putting each resulting solution vectfor
00657     // into the corresponding column of the inverse matrix
00658     for ( int i = 0; i < p_sparseCols; i++ )
00659     {
00660       if( i > 0 )
00661         p_ATb(i-1,0) = 0.0;
00662 
00663       p_ATb(i,0) = 1.0;
00664 
00665       // solve with decomposed normals and right hand side
00666       p_SLU_Factor.solve(p_xSparse,gmm::mat_const_col(p_ATb,0));
00667 
00668       // put solution vector x into current column of inverse matrix
00669       gmm::copy(p_xSparse, gmm::mat_row(p_normals, i));
00670     }
00671 
00672     // scale inverse by Sigma0 squared to get variance-covariance matrix
00673     // if simulated data, we don't scale (effectively scaling by 1.0)
00674     //    printf("scaling by Sigma0\n");
00675     gmm::scale(p_normals,(p_sigma0*p_sigma0));
00676 
00677 //    printf("covariance matrix\n");
00678 //    for (int i = 0; i < p_sparseCols; i++ )
00679 //    {
00680 //      for (int j = 0; j < p_sparseCols; j++ )
00681 //      {
00682 //        if ( j == p_sparseCols-1 )
00683 //          printf("%20.20e \n",(double)(p_sparseInverse(i,j)));
00684 //        else
00685 //          printf("%20.20e ",(double)(p_sparseInverse(i,j)));
00686 //      }
00687 //    }
00688 
00689     // standard deviations from covariance matrix
00690 //    printf("parameter standard deviations\n");
00691 //  for (int i = 0; i < p_sparseCols; i++ )
00692 //    {
00693 //      printf("Sigma Parameter %d = %20.20e \n",i+1,sqrt((double)(p_sparseInverse(i,i))));
00694 //    }
00695 
00696     return true;
00697   }
00698 #endif
00699 
00700 
00701   void LeastSquares::Reset ()
00702   {
00703     if ( p_sparse ) {
00704       gmm::clear(p_sparseA);
00705       gmm::clear(p_ATb);
00706       gmm::clear(p_normals);
00707       p_currentFillRow = -1;
00708     }
00709     else {
00710       p_input.clear();
00711       //      p_sigma0 = 0.;
00712     }
00713       p_sigma0 = 0.;
00714     p_residuals.clear();
00715     p_expected.clear();
00716     p_sqrtWeight.clear();
00717     p_solved = false;
00718   }
00719 
00720 
00721 
00732   double LeastSquares::Evaluate(const std::vector<double> &data) {
00733     if(!p_solved) {
00734       QString msg = "Unable to evaluate until a solution has been computed";
00735       throw IException(IException::Programmer, msg, _FILEINFO_);
00736     }
00737     return p_basis->Evaluate(data);
00738   }
00739 
00749   std::vector<double> LeastSquares::Residuals() const {
00750     if(!p_solved) {
00751       QString msg = "Unable to return residuals until a solution has been computed";
00752       throw IException(IException::Programmer, msg, _FILEINFO_);
00753     }
00754     return p_residuals;
00755   }
00756 
00769   double LeastSquares::Residual(int i) const {
00770     if(!p_solved) {
00771       QString msg = "Unable to return residuals until a solution has been computed";
00772       throw IException(IException::Programmer, msg, _FILEINFO_);
00773     }
00774     return p_residuals[i];
00775   }
00776 
00793   void LeastSquares::Weight(int index, double weight) {
00794     if(weight == 1) {
00795       p_sqrtWeight[index] = weight;
00796     }
00797     else {
00798       p_sqrtWeight[index] = sqrt(weight);
00799     }
00800   }
00801 
00802 } // end namespace isis