Isis 3 Programmer Reference
LeastSquares.cpp
1 
6 /* SPDX-License-Identifier: CC0-1.0 */
7 #include "jama/jama_svd.h"
8 #include "jama/jama_qr.h"
9 
10 #include <armadillo>
11 
12 #include "LeastSquares.h"
13 #include "IException.h"
14 #include "IString.h"
15 
16 namespace Isis {
24  int sparseRows, int sparseCols, bool jigsaw) {
25  p_jigsaw = jigsaw;
26  p_basis = &basis;
27  p_solved = false;
28  p_sparse = sparse;
29  p_sigma0 = 0.;
30 
31  p_sparseRows = sparseRows;
32  p_sparseCols = sparseCols;
33 
34 
35  if (p_sparse) {
36 
37  // make sure sparse nrows/ncols have been set
38  if (sparseRows == 0 || sparseCols == 0) {
39  QString msg = "If solving using sparse matrices, you must enter the "
40  "number of rows/columns";
41  throw IException(IException::Programmer, msg, _FILEINFO_);
42  }
43 
44 
45  p_sparseA.set_size(sparseRows, sparseCols);
46  p_normals.set_size(sparseCols, sparseCols);
47  p_ATb.resize(sparseCols, 1);
48  p_xSparse.resize(sparseCols);
49 
50  if( p_jigsaw ) {
51  p_epsilonsSparse.resize(sparseCols);
52  std::fill_n(p_epsilonsSparse.begin(), sparseCols, 0.0);
53 
54  p_parameterWeights.resize(sparseCols);
55  }
56 
57  }
58  p_currentFillRow = -1;
59  }
60 
63  }
64 
96  void LeastSquares::AddKnown(const std::vector<double> &data, double result,
97  double weight) {
98  if((int) data.size() != p_basis->Variables()) {
99  QString msg = "Number of elements in data does not match basis [" +
100  p_basis->Name() + "] requirements";
101  throw IException(IException::Programmer, msg, _FILEINFO_);
102  }
103 
104  p_expected.push_back(result);
105 
106  if (weight == 1) {
107  p_sqrtWeight.push_back(weight);
108  }
109  else {
110  p_sqrtWeight.push_back(sqrt(weight));
111  }
112 
113  if(p_sparse) {
114  FillSparseA(data);
115  }
116  else {
117  p_input.push_back(data);
118  }
119  }
120 
121 
122 
137  void LeastSquares::FillSparseA(const std::vector<double> &data) {
138 
139  p_basis->Expand(data);
140 
141  p_currentFillRow++;
142 
143  int ncolumns = (int)data.size();
144 
145  for(int c = 0; c < ncolumns; c++) {
146  p_sparseA(p_currentFillRow, c) = p_basis->Term(c) * p_sqrtWeight[p_currentFillRow];
147  }
148  }
149 
150 
158  std::vector<double> LeastSquares::GetInput(int row) const {
159  if((row >= Rows()) || (row < 0)) {
160  QString msg = "Index out of bounds [Given = " + toString(row) + "]";
161  throw IException(IException::Programmer, msg, _FILEINFO_);
162  }
163  return p_input[row];
164  }
165 
173  double LeastSquares::GetExpected(int row) const {
174  if((row >= Rows()) || (row < 0)) {
175  QString msg = "Index out of bounds [Given = " + toString(row) + "]";
176  throw IException(IException::Programmer, msg, _FILEINFO_);
177  }
178  return p_expected[row];
179  }
180 
187  int LeastSquares::Rows() const {
188  return (int)p_input.size();
189  }
190 
206 
207  if((method == SPARSE && p_sparseRows == 0) ||
208  (method != SPARSE && Rows() == 0 )) {
209  p_solved = false;
210  QString msg = "No solution available because no input data was provided";
211  throw IException(IException::Unknown, msg, _FILEINFO_);
212  }
213 
214  if(method == SVD) {
215  SolveSVD();
216  }
217  else if(method == QRD) {
218  SolveQRD();
219  }
220  else if(method == SPARSE) {
221  int column = SolveSparse();
222  return column;
223  }
224  return 0;
225  }
226 
237 
238  // We are solving Ax=b ... start by creating A
239  TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
240  for(int r = 0; r < A.dim1(); r++) {
241  p_basis->Expand(p_input[r]);
242  for(int c = 0; c < A.dim2(); c++) {
243  A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
244  }
245  }
246 
247  // Ok use singular value decomposition to solve for the coefficients
248  // A = [U][S][V'] where [U] is MxN, [S] is NxN, [V'] is NxN transpose
249  // of [V]. We are solving for [A]x=b and need inverse of [A] such
250  // that x = [invA]b. Since inverse may not exist we use the
251  // pseudo-inverse [A+] from SVD which is [A+] = [V][invS][U']
252  // Our coefficents are then x = [A+]b where b is p_b.
253  JAMA::SVD<double> svd(A);
254 
255  TNT::Array2D<double> V;
256  svd.getV(V);
257 
258  // The inverse of S is the 1 over each diagonal element of S
259  TNT::Array2D<double> invS;
260  svd.getS(invS);
261 
262  for(int i = 0; i < invS.dim1(); i++) {
263  if(invS[i][i] != 0.0) invS[i][i] = 1.0 / invS[i][i];
264  }
265 
266  // Transpose U
267  TNT::Array2D<double> U;
268  svd.getU(U);
269  TNT::Array2D<double> transU(U.dim2(), U.dim1());
270 
271  for(int r = 0; r < U.dim1(); r++) {
272  for(int c = 0; c < U.dim2(); c++) {
273  transU[c][r] = U[r][c];
274  }
275  }
276 
277  // Now multiply everything together to get [A+]
278  TNT::Array2D<double> VinvS = TNT::matmult(V, invS);
279  TNT::Array2D<double> Aplus = TNT::matmult(VinvS, transU);
280 
281  // Using Aplus and our b we can solve for the coefficients
282  TNT::Array2D<double> b(p_expected.size(), 1);
283 
284  for(int r = 0; r < (int)p_expected.size(); r++) {
285  b[r][0] = p_expected[r] * p_sqrtWeight[r];
286  }
287 
288  TNT::Array2D<double> coefs = TNT::matmult(Aplus, b);
289 
290  // If the rank of the matrix is not large enough we don't
291  // have enough coefficients for the solution
292  if (coefs.dim1() < p_basis->Coefficients()) {
293  QString msg = "Unable to solve least-squares using SVD method. No "
294  "solution available. Not enough knowns or knowns are "
295  "co-linear ... [Unknowns = "
296  + toString(p_basis->Coefficients()) + "] [Knowns = "
297  + toString(coefs.dim1()) + "]";
298  throw IException(IException::Unknown, msg, _FILEINFO_);
299  }
300 
301  // Set the coefficients in our basis equation
302  std::vector<double> bcoefs;
303  for (int i = 0; i < coefs.dim1(); i++) bcoefs.push_back(coefs[i][0]);
304 
305  p_basis->SetCoefficients(bcoefs);
306 
307  // Compute the errors
308  for(int i = 0; i < (int)p_input.size(); i++) {
309  double value = p_basis->Evaluate(p_input[i]);
310  p_residuals.push_back(value - p_expected[i]);
312  }
313  // calculate degrees of freedom (or redundancy)
314  // DOF = # observations + # constrained parameters - # unknown parameters
315  p_degreesOfFreedom = p_basis->Coefficients() - coefs.dim1();
316 
317  if( p_degreesOfFreedom > 0.0 ) {
319  }
320 
321  // check for p_sigma0 < 0
322  p_sigma0 = sqrt(p_sigma0);
323 
324  // All done
325  p_solved = true;
326  }
327 
340 
341  // We are solving Ax=b ... start by creating an MxN matrix, A
342  TNT::Array2D<double> A(p_input.size(), p_basis->Coefficients());
343  for(int r = 0; r < A.dim1(); r++) {
344  p_basis->Expand(p_input[r]);
345  for(int c = 0; c < A.dim2(); c++) {
346  A[r][c] = p_basis->Term(c) * p_sqrtWeight[r];
347  }
348  }
349 
350  // Ok use to solve for the coefficients
351  // [A] = [Q][R] where [Q] is MxN and orthogonal and [R] is an NxN,
352  // upper triangular matrix. TNT provides the solve method that inverts
353  // [Q] and backsolves [R] to get the coefficients in the vector x.
354  // That is, we solve the system Rx = Q^T b
355  JAMA::QR<double> qr(A);
356 
357  // Using A and our b we can solve for the coefficients
358  TNT::Array1D<double> b(p_expected.size());
359  for(int r = 0; r < (int)p_expected.size(); r++) {
360  b[r] = p_expected[r] * p_sqrtWeight[r];
361  }// by construction, we know the size of b is equal to M, so b is conformant
362 
363  // Check to make sure the matrix is full rank before solving
364  // -- rectangular matrices must be full rank in order for the solve method
365  // to be successful
366  int full = qr.isFullRank();
367  if(full == 0) {
368  QString msg = "Unable to solve-least squares using QR Decomposition. "
369  "The upper triangular R matrix is not full rank";
370  throw IException(IException::Unknown, msg, _FILEINFO_);
371  }
372 
373  TNT::Array1D<double> coefs = qr.solve(b);
374 
375  // Set the coefficients in our basis equation
376  std::vector<double> bcoefs;
377  for(int i = 0; i < coefs.dim1(); i++) {
378  bcoefs.push_back(coefs[i]);
379  }
380  p_basis->SetCoefficients(bcoefs);
381 
382  // Compute the errors
383  for(int i = 0; i < (int)p_input.size(); i++) {
384  double value = p_basis->Evaluate(p_input[i]);
385  p_residuals.push_back(value - p_expected[i]);
386  }
387 
388  // All done
389  p_solved = true;
390  }
391 
392 
393 
394 
429 
430  // form "normal equations" matrix by multiplying ATA
432 
433  // Create the right-hand-side column vector 'b'
434  arma::mat b(p_sparseRows, 1);
435 
436  // multiply each element of 'b' by it's associated weight
437  for ( int r = 0; r < p_sparseRows; r++ )
438  b(r,0) = p_expected[r] * p_sqrtWeight[r];
439 
440  // form ATb
441  p_ATb = p_sparseA.t()*b;
442 
443  // apply parameter weighting if Jigsaw (bundle adjustment)
444  if ( p_jigsaw ) {
445  for( int i = 0; i < p_sparseCols; i++) {
446  double weight = p_parameterWeights[i];
447 
448  if( weight <= 0.0 )
449  continue;
450 
451  p_normals(i, i) += weight;
452  p_ATb(i, 0) -= p_epsilonsSparse[i]*weight;
453  }
454  }
455 
456  bool status = spsolve(p_xSparse, p_normals, p_ATb, "superlu");
457 
458  if (status == false) {
459  QString msg = "Could not solve sparse least squares problem.";
460  throw IException(IException::Unknown, msg, _FILEINFO_);
461  }
462 
463  // Set the coefficients in our basis equation
464  p_basis->SetCoefficients(arma::conv_to< std::vector<double> >::from(p_xSparse));
465 
466  // if Jigsaw (bundle adjustment)
467  // add corrections into epsilon vector (keeping track of total corrections)
468  if ( p_jigsaw ) {
469  for( int i = 0; i < p_sparseCols; i++ )
470  p_epsilonsSparse[i] += p_xSparse[i];
471  }
472 
473  // Compute the image coordinate residuals and sum into Sigma0
474  // (note this is exactly what was being done before, but with less overhead - I think)
475  // ultimately, we should not be using the A matrix but forming the normals
476  // directly. Then we'll have to compute the residuals by back projection
477 
478  p_residuals.resize(p_sparseRows);
479  p_residuals = arma::conv_to< std::vector<double> >::from(p_sparseA*p_xSparse);
480  p_sigma0 = 0.0;
481 
482  for ( int i = 0; i < p_sparseRows; i++ ) {
484  p_residuals[i] -= p_expected[i];
486  }
487 
488  // if Jigsaw (bundle adjustment)
489  // add contibution to Sigma0 from constrained parameters
490  if ( p_jigsaw ) {
491  double constrained_vTPv = 0.0;
492 
493  for ( int i = 0; i < p_sparseCols; i++ ) {
494  double weight = p_parameterWeights[i];
495 
496  if ( weight <= 0.0 )
497  continue;
498 
499  constrained_vTPv += p_epsilonsSparse[i]*p_epsilonsSparse[i]*weight;
500  }
501  p_sigma0 += constrained_vTPv;
502  }
503  // calculate degrees of freedom (or redundancy)
504  // DOF = # observations + # constrained parameters - # unknown parameters
505  p_degreesOfFreedom = p_sparseRows + p_constrainedParameters - p_sparseCols;
506 
507  if( p_degreesOfFreedom <= 0.0 ) {
508  p_sigma0 = 1.0;
509  }
510  else {
512  }
513 
514  // check for p_sigma0 < 0
515  p_sigma0 = sqrt(p_sigma0);
516 
517  // All done
518  p_solved = true;
519  return 0;
520  }
521 
522 
523  void LeastSquares::Reset ()
524  {
525  if ( p_sparse ) {
526  p_sparseA.zeros();
527  p_ATb.zeros();
528  p_normals.zeros();
529  p_currentFillRow = -1;
530  }
531  else {
532  p_input.clear();
533  }
534  p_sigma0 = 0.;
535  p_residuals.clear();
536  p_expected.clear();
537  p_sqrtWeight.clear();
538  p_solved = false;
539  }
540 
541 
542 
553  double LeastSquares::Evaluate(const std::vector<double> &data) {
554  if(!p_solved) {
555  QString msg = "Unable to evaluate until a solution has been computed";
556  throw IException(IException::Programmer, msg, _FILEINFO_);
557  }
558  return p_basis->Evaluate(data);
559  }
560 
570  std::vector<double> LeastSquares::Residuals() const {
571  if(!p_solved) {
572  QString msg = "Unable to return residuals until a solution has been computed";
573  throw IException(IException::Programmer, msg, _FILEINFO_);
574  }
575  return p_residuals;
576  }
577 
590  double LeastSquares::Residual(int i) const {
591  if(!p_solved) {
592  QString msg = "Unable to return residuals until a solution has been computed";
593  throw IException(IException::Programmer, msg, _FILEINFO_);
594  }
595  return p_residuals[i];
596  }
597 
614  void LeastSquares::Weight(int index, double weight) {
615  if(weight == 1) {
616  p_sqrtWeight[index] = weight;
617  }
618  else {
619  p_sqrtWeight[index] = sqrt(weight);
620  }
621  }
622 
623 } // end namespace isis
Isis::LeastSquares::LeastSquares
LeastSquares(Isis::BasisFunction &basis, bool sparse=false, int sparseRows=0, int sparseCols=0, bool jigsaw=false)
Creates a LeastSquares Object.
Definition: LeastSquares.cpp:23
Isis::LeastSquares::p_epsilonsSparse
std::vector< double > p_epsilonsSparse
sparse vector of total parameter corrections
Definition: LeastSquares.h:152
Isis::LeastSquares::Solve
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...
Definition: LeastSquares.cpp:205
Isis::LeastSquares::p_xSparse
arma::mat p_xSparse
sparse solution matrix
Definition: LeastSquares.h:151
Isis::LeastSquares::SolveSVD
void SolveSVD()
After all the data has been registered through AddKnown, invoke this method to solve the system of eq...
Definition: LeastSquares.cpp:236
Isis::LeastSquares::p_constrainedParameters
int p_constrainedParameters
constrained parameters
Definition: LeastSquares.h:167
Isis::LeastSquares::GetInput
std::vector< double > GetInput(int row) const
This method returns the data at the given row.
Definition: LeastSquares.cpp:158
Isis::BasisFunction::SetCoefficients
void SetCoefficients(const std::vector< double > &coefs)
Set the coefficients for the equation.
Definition: BasisFunction.cpp:42
Isis::BasisFunction
Generic linear equation class.
Definition: BasisFunction.h:48
Isis::IException::Unknown
@ Unknown
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:118
Isis::LeastSquares::FillSparseA
void FillSparseA(const std::vector< double > &data)
Invoke this method for each set of knowns for sparse solutions.
Definition: LeastSquares.cpp:137
Isis::LeastSquares::p_parameterWeights
std::vector< double > p_parameterWeights
vector of parameter weights
Definition: LeastSquares.h:153
Isis::LeastSquares::p_expected
std::vector< double > p_expected
A vector of the expected values when solved.
Definition: LeastSquares.h:174
Isis::LeastSquares::SPARSE
@ SPARSE
Sparse.
Definition: LeastSquares.h:114
Isis::LeastSquares::Evaluate
double Evaluate(const std::vector< double > &input)
Invokes the BasisFunction Evaluate method.
Definition: LeastSquares.cpp:553
Isis::BasisFunction::Term
double Term(int c) const
Returns the cth term.
Definition: BasisFunction.h:97
Isis::BasisFunction::Name
QString Name() const
Returns the name of the equation.
Definition: BasisFunction.h:80
Isis::LeastSquares::SolveMethod
SolveMethod
Definition: LeastSquares.h:112
Isis::LeastSquares::Rows
int Rows() const
This methods returns the number of rows in the matrix.
Definition: LeastSquares.cpp:187
Isis::LeastSquares::Residual
double Residual(int i) const
Returns the ith residual.
Definition: LeastSquares.cpp:590
Isis::toString
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
Definition: IString.cpp:211
Isis::LeastSquares::p_input
std::vector< std::vector< double > > p_input
A vector of the input variables to evaluate.
Definition: LeastSquares.h:172
Isis::LeastSquares::~LeastSquares
~LeastSquares()
Destroys the LeastSquares object.
Definition: LeastSquares.cpp:62
Isis::BasisFunction::Expand
virtual void Expand(const std::vector< double > &vars)
This is the function you should replace depending on your needs.
Definition: BasisFunction.cpp:123
Isis::LeastSquares::p_sparseA
arma::SpMat< double > p_sparseA
design matrix 'A'
Definition: LeastSquares.h:155
Isis::LeastSquares::p_normals
arma::SpMat< double > p_normals
normal equations matrix 'N'
Definition: LeastSquares.h:156
Isis::LeastSquares::p_basis
Isis::BasisFunction * p_basis
Pointer to the BasisFunction object.
Definition: LeastSquares.h:183
Isis::LeastSquares::GetExpected
double GetExpected(int row) const
This method returns the expected value at the given row.
Definition: LeastSquares.cpp:173
Isis::IException
Isis exception class.
Definition: IException.h:91
Isis::LeastSquares::p_residuals
std::vector< double > p_residuals
A vector of the residuals (or difference between expected and solved values).
Definition: LeastSquares.h:179
Isis::LeastSquares::p_sqrtWeight
std::vector< double > p_sqrtWeight
A vector of the square roots of the weights for each known value.
Definition: LeastSquares.h:176
Isis::LeastSquares::p_ATb
arma::mat p_ATb
right-hand side vector
Definition: LeastSquares.h:157
Isis::LeastSquares::QRD
@ QRD
QR Decomposition.
Definition: LeastSquares.h:113
Isis::BasisFunction::Variables
int Variables() const
Returns the number of variables in the equation.
Definition: BasisFunction.h:72
Isis::IException::Programmer
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:146
Isis::LeastSquares::p_sigma0
double p_sigma0
sigma nought - reference variance
Definition: LeastSquares.h:170
Isis::LeastSquares::p_solved
bool p_solved
Boolean value indicating solution is complete.
Definition: LeastSquares.h:162
Isis::LeastSquares::SolveSparse
int SolveSparse()
Solve using sparse class.
Definition: LeastSquares.cpp:428
Isis::LeastSquares::AddKnown
void AddKnown(const std::vector< double > &input, double expected, double weight=1.0)
Invoke this method for each set of knowns.
Definition: LeastSquares.cpp:96
Isis::LeastSquares::SVD
@ SVD
Singular Value Decomposition.
Definition: LeastSquares.h:112
Isis::BasisFunction::Evaluate
double Evaluate(const std::vector< double > &vars)
Compute the equation using the input variables.
Definition: BasisFunction.cpp:64
Isis::LeastSquares::p_degreesOfFreedom
int p_degreesOfFreedom
degrees of freedom (redundancy)
Definition: LeastSquares.h:168
Isis
This is free and unencumbered software released into the public domain.
Definition: Apollo.h:16
Isis::BasisFunction::Coefficients
int Coefficients() const
Returns the number of coefficients for the equation.
Definition: BasisFunction.h:64
Isis::LeastSquares::Weight
void Weight(int index, double weight)
Reset the weight for the ith known.
Definition: LeastSquares.cpp:614
Isis::LeastSquares::SolveQRD
void SolveQRD()
After all the data has been registered through AddKnown, invoke this method to solve the system of eq...
Definition: LeastSquares.cpp:339
Isis::LeastSquares::Residuals
std::vector< double > Residuals() const
Returns a vector of residuals (errors).
Definition: LeastSquares.cpp:570