|
Isis 3.0 Object Programmers' Reference |
Home |
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