Isis 3.0 Programmer Reference
Back | 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
void Weight(int index, double weight)
Reset the weight for the ith known.
int Coefficients() const
Returns the number of coefficients for the equation.
Definition: BasisFunction.h:80
QR Decomposition.
Definition: LeastSquares.h:131
void SetCoefficients(const std::vector< double > &coefs)
Set the coefficients for the equation.
gmm::row_matrix< gmm::rsvector< double > > p_sparseA
design matrix &#39;A&#39;
Definition: LeastSquares.h:178
QString Name() const
Returns the name of the equation.
Definition: BasisFunction.h:96
void SolveSVD()
After all the data has been registered through AddKnown, invoke this method to solve the system of eq...
LeastSquares(Isis::BasisFunction &basis, bool sparse=false, int sparseRows=0, int sparseCols=0, bool jigsaw=false)
Creates a LeastSquares Object.
double Term(int c) const
Returns the cth term.
std::vector< double > p_epsilonsSparse
sparse vector of total parameter corrections
Definition: LeastSquares.h:175
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
Definition: IString.cpp:226
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:154
std::vector< double > p_expected
A vector of the expected values when solved.
Definition: LeastSquares.h:199
std::vector< double > Residuals() const
Returns a vector of residuals (errors).
~LeastSquares()
Destroys the LeastSquares object.
std::vector< std::vector< double > > p_input
A vector of the input variables to evaluate.
Definition: LeastSquares.h:197
void FillSparseA(const std::vector< double > &data)
Invoke this method for each set of knowns for sparse solutions.
bool SparseErrorPropagation()
Error propagation for sparse least-squares solution.
std::vector< double > p_xSparse
sparse solution vector
Definition: LeastSquares.h:174
std::vector< double > p_parameterWeights
vector of parameter weights
Definition: LeastSquares.h:176
Singular Value Decomposition.
Definition: LeastSquares.h:130
double Evaluate(const std::vector< double > &input)
Invokes the BasisFunction Evaluate method.
virtual void Expand(const std::vector< double > &vars)
This is the function you should replace depending on your needs.
int Variables() const
Returns the number of variables in the equation.
Definition: BasisFunction.h:88
std::vector< double > p_residuals
A vector of the residuals (or difference between expected and solved values).
Definition: LeastSquares.h:204
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:38
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:126
gmm::row_matrix< gmm::rsvector< double > > p_normals
normal equations matrix &#39;N&#39;
Definition: LeastSquares.h:180
gmm::dense_matrix< double > p_ATb
right-hand side vector
Definition: LeastSquares.h:182
gmm::SuperLU_factor< double > p_SLU_Factor
decomposed normal equations matrix
Definition: LeastSquares.h:183
int SolveSparse()
Solve using sparse class.
double p_sigma0
sigma nought - reference variance
Definition: LeastSquares.h:195
void AddKnown(const std::vector< double > &input, double expected, double weight=1.0)
Invoke this method for each set of knowns.
double Residual(int i) const
Returns the ith residual.
int p_degreesOfFreedom
degrees of freedom (redundancy)
Definition: LeastSquares.h:193
std::vector< double > GetInput(int row) const
This method returns the data at the given row.
double Evaluate(const std::vector< double > &vars)
Compute the equation using the input variables.
Generic linear equation class.
Definition: BasisFunction.h:64
double GetExpected(int row) const
This method returns the expected value at the given row.
Isis::BasisFunction * p_basis
Pointer to the BasisFunction object.
Definition: LeastSquares.h:208
Isis exception class.
Definition: IException.h:99
std::vector< double > p_sqrtWeight
A vector of the square roots of the weights for each known value.
Definition: LeastSquares.h:201
bool p_solved
Boolean value indicating solution is complete.
Definition: LeastSquares.h:187
void SolveQRD()
After all the data has been registered through AddKnown, invoke this method to solve the system of eq...
int Solve(Isis::LeastSquares::SolveMethod method=SVD)
After all the data has been registered through AddKnown, invoke this method to solve the system of eq...
int Rows() const
This methods returns the number of rows in the matrix.
int p_constrainedParameters
constrained parameters
Definition: LeastSquares.h:192

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the ISIS Support Center
File Modified: 07/12/2023 23:21:59