swh:1:snp:af87cd67498ef4fe47c76ed3e7caffe5b61facaf
Raw File
Tip revision: a4a30f9d00f1400ef5e8ff51a2d06d1644b72759 authored by Axel Naumann on 14 June 2020, 15:51:48 UTC
"Update ROOT version files to v6.22/00."
Tip revision: a4a30f9
solveLinear.C
/// \file
/// \ingroup tutorial_matrix
/// \notebook -nodraw
/// This macro shows several ways to perform a linear least-squares
/// analysis . To keep things simple we fit a straight line to 4
/// data points
/// The first 4 methods use the linear algebra package to find
///  x  such that min \f$ (A x - b)^T (A x - b) \f$ where A and b
///  are calculated with the data points  and the functional expression :
///
///  1. Normal equations:
///   Expanding the expression \f$ (A x - b)^T (A x - b) \f$ and taking the
///   derivative wrt x leads to the "Normal Equations":
///   \f$ A^T A x = A^T b \f$ where \f$ A^T A \f$ is a positive definite matrix. Therefore,
///   a Cholesky decomposition scheme can be used to calculate its inverse .
///   This leads to the solution \f$ x = (A^T A)^-1 A^T b \f$ . All this is done in
///   routine NormalEqn . We made it a bit more complicated by giving the
///   data weights .
///   Numerically this is not the best way to proceed because effectively the
///   condition number of \f$ A^T A \f$ is twice as large as that of A, making inversion
///   more difficult
///
///  2. SVD
///   One can show that solving \f$ A x = b \f$ for x with A of size \f$ (m x n) \f$
///   and \f$ m > n \f$  through a Singular Value Decomposition is equivalent to minimizing
///   \f$ (A x - b)^T (A x - b) \f$ Numerically , this is the most stable method of all 5
///
///  3. Pseudo Inverse
///   Here we calculate the generalized matrix inverse ("pseudo inverse") by
///   solving \f$ A X = Unit \f$ for matrix \f$ X \f$ through an SVD . The formal expression for
///   is \f$ X = (A^T A)^-1 A^T \f$ . Then we multiply it by \f$ b \f$ .
///   Numerically, not as good as 2 and not as fast . In general it is not a
///   good idea to solve a set of linear equations with a matrix inversion .
///
///  4. Pseudo Inverse , brute force
///   The pseudo inverse is calculated brute force through a series of matrix
///   manipulations . It shows nicely some operations in the matrix package,
///   but is otherwise a big "no no" .
///
///  5. Least-squares analysis with Minuit
///   An objective function L is minimized by Minuit, where
///    \f$ L = sum_i { (y - c_0 -c_1 * x / e)^2 } \f$
///   Minuit will calculate numerically the derivative of L wrt c_0 and c_1 .
///   It has not been told that these derivatives are linear in the parameters
///   c_0 and c_1 .
///   For ill-conditioned linear problems it is better to use the fact it is
///   a linear fit as in 2 .
///
/// Another interesting thing is the way we assign data to the vectors and
/// matrices through adoption .
/// This allows data assignment without physically moving bytes around .
///
///  #### USAGE
///
/// This macro can be executed via CINT or via ACLIC
/// - via the interpretor, do
/// ~~~{.cpp}
///    root > .x solveLinear.C
/// ~~~
/// - via ACLIC
/// ~~~{.cpp}
///    root > gSystem->Load("libMatrix");
///    root > gSystem->Load("libGpad");
///    root > .x solveLinear.C+
/// ~~~
///
/// \macro_output
/// \macro_code
///
/// \author Eddy Offermann

#include "Riostream.h"
#include "TMatrixD.h"
#include "TVectorD.h"
#include "TGraphErrors.h"
#include "TDecompChol.h"
#include "TDecompSVD.h"
#include "TF1.h"


void solveLinear(Double_t eps = 1.e-12)
{
   cout << "Perform the fit  y = c0 + c1 * x in four different ways" << endl;

   const Int_t nrVar  = 2;
   const Int_t nrPnts = 4;

   Double_t ax[] = {0.0,1.0,2.0,3.0};
   Double_t ay[] = {1.4,1.5,3.7,4.1};
   Double_t ae[] = {0.5,0.2,1.0,0.5};

   // Make the vectors 'Use" the data : they are not copied, the vector data
   // pointer is just set appropriately

   TVectorD x; x.Use(nrPnts,ax);
   TVectorD y; y.Use(nrPnts,ay);
   TVectorD e; e.Use(nrPnts,ae);

   TMatrixD A(nrPnts,nrVar);
   TMatrixDColumn(A,0) = 1.0;
   TMatrixDColumn(A,1) = x;

   cout << " - 1. solve through Normal Equations" << endl;

   const TVectorD c_norm = NormalEqn(A,y,e);

   cout << " - 2. solve through SVD" << endl;
   // numerically  preferred method

   // first bring the weights in place
   TMatrixD Aw = A;
   TVectorD yw = y;
   for (Int_t irow = 0; irow < A.GetNrows(); irow++) {
      TMatrixDRow(Aw,irow) *= 1/e(irow);
      yw(irow) /= e(irow);
   }

   TDecompSVD svd(Aw);
   Bool_t ok;
   const TVectorD c_svd = svd.Solve(yw,ok);

   cout << " - 3. solve with pseudo inverse" << endl;

   const TMatrixD pseudo1  = svd.Invert();
   TVectorD c_pseudo1 = yw;
   c_pseudo1 *= pseudo1;

   cout << " - 4. solve with pseudo inverse, calculated brute force" << endl;

   TMatrixDSym AtA(TMatrixDSym::kAtA,Aw);
   const TMatrixD pseudo2 = AtA.Invert() * Aw.T();
   TVectorD c_pseudo2 = yw;
   c_pseudo2 *= pseudo2;

   cout << " - 5. Minuit through TGraph" << endl;

   TGraphErrors *gr = new TGraphErrors(nrPnts,ax,ay,0,ae);
   TF1 *f1 = new TF1("f1","pol1",0,5);
   gr->Fit("f1","Q");
   TVectorD c_graph(nrVar);
   c_graph(0) = f1->GetParameter(0);
   c_graph(1) = f1->GetParameter(1);

   // Check that all 4 answers are identical within a certain
   // tolerance . The 1e-12 is somewhat arbitrary . It turns out that
   // the TGraph fit is different by a few times 1e-13.

   Bool_t same = kTRUE;
   same &= VerifyVectorIdentity(c_norm,c_svd,0,eps);
   same &= VerifyVectorIdentity(c_norm,c_pseudo1,0,eps);
   same &= VerifyVectorIdentity(c_norm,c_pseudo2,0,eps);
   same &= VerifyVectorIdentity(c_norm,c_graph,0,eps);
   if (same)
      cout << " All solutions are the same within tolerance of " << eps << endl;
   else
      cout << " Some solutions differ more than the allowed tolerance of " << eps << endl;
}
back to top