contrib/mul/vpdfl/vpdfl_gaussian.cxx
Go to the documentation of this file.
00001 // This is mul/vpdfl/vpdfl_gaussian.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 // \file
00007 // \brief Multi-variate Gaussian PDF with arbitrary axes.
00008 // \author Tim Cootes
00009 // \date 16-Oct-1998
00010 //
00011 // \verbatim
00012 //  Modifications
00013 //    IMS   Converted to VXL 18 April 2000
00014 // \endverbatim
00015 
00016 #include "vpdfl_gaussian.h"
00017 
00018 #include <vcl_cassert.h>
00019 #include <vcl_cstdlib.h>
00020 #include <vcl_string.h>
00021 #include <vcl_cmath.h>
00022 
00023 #include <vsl/vsl_indent.h>
00024 #include <vnl/vnl_math.h>
00025 #include <mbl/mbl_matxvec.h>
00026 #include <mbl/mbl_matrix_products.h>
00027 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00028 #include <vnl/io/vnl_io_matrix.h>
00029 #include <vpdfl/vpdfl_gaussian_sampler.h>
00030 #include <vpdfl/vpdfl_sampler_base.h>
00031 #include <vpdfl/vpdfl_prob_chi2.h>
00032 
00033 //=======================================================================
00034 static inline bool almostEqualsOne(double value)
00035 {
00036   const double upper = 1 + 1e-06;
00037   const double lower = 1 - 1e-06;
00038   return value > lower && value < upper;
00039 }
00040 //=======================================================================
00041 
00042 #ifndef NDEBUG
00043 static inline bool columnsAreUnitNorm(const vnl_matrix<double>& vecs)
00044 {
00045   const int m = vecs.rows();
00046   const int n = vecs.cols();
00047   for (int j=0; j<n; j++)
00048   {
00049     double sumsq = 0.0;
00050     for (int i=0; i<m; i++)
00051       sumsq += vnl_math_sqr(vecs(i,j));
00052     if (!almostEqualsOne(sumsq)) return false;
00053   }
00054   return true;
00055 }
00056 #endif // NDEBUG
00057 //=======================================================================
00058 #if 0
00059 static bool vectorHasDescendingOrder(const vnl_vector<double>& v)
00060 {
00061   int n = v.size();
00062   for (int i = 1; i < n; i++)
00063     if (v(i-1) < v(i)) return false;
00064   return true;
00065 }
00066 #endif
00067 
00068 //=======================================================================
00069 
00070 vpdfl_gaussian::vpdfl_gaussian()
00071 {
00072 }
00073 
00074 //=======================================================================
00075 
00076 vpdfl_gaussian::~vpdfl_gaussian()
00077 {
00078 }
00079 
00080 //=======================================================================
00081 
00082 void vpdfl_gaussian::calcLogK()
00083 {
00084   const double *v_data = evals_.data_block();
00085   int n = n_dims();
00086   double log_v_sum = 0.0;
00087   for (int i=0;i<n;i++) log_v_sum+=vcl_log(v_data[i]);
00088 
00089   log_k_ = -0.5 * (n*vcl_log(2 * vnl_math::pi) + log_v_sum);
00090 }
00091 
00092 //: Initialise safely
00093 // Calculates the variance, and checks that
00094 // the Eigenvalues are ordered and the Eigenvectors are unit normal
00095 // Turn off assertions to remove error checking.
00096 void vpdfl_gaussian::set(const vnl_vector<double>& mean,
00097                          const vnl_matrix<double>& evecs,
00098                          const vnl_vector<double>& evals)
00099 {
00100   const unsigned int m = evecs.rows();
00101   const unsigned int n = evecs.cols();
00102   assert(evals.size() == m);
00103   assert(mean.size() == m);
00104 
00105 // Ensure that every Eigenvector has a unit norm;
00106   assert(columnsAreUnitNorm(evecs));
00107 // Ensure that every Eigenvalues are properly ordered is a unit norm;
00108 //assert(vectorHasDescendingOrder(evals));
00109 
00110   // calculate the variance
00111   // Notionally - apply the inverse diagonalisation to get
00112   // back to the Covariance matrix, and select the diagonal
00113   // Efficiently - Var(i) = Sum Wij * Li * Wij
00114   vnl_vector<double> v(m);
00115   for (unsigned int i=0; i<m; i++)
00116   {
00117     double &vi = v(i);
00118     vi = 0.0;
00119     for (unsigned int j=0; j<n; j++)
00120       vi += vnl_math_sqr(evecs(i,j)) * evals(i);
00121   }
00122 
00123   set(mean, v, evecs, evals);
00124 }
00125 
00126 //=======================================================================
00127 
00128 void vpdfl_gaussian::set(const vnl_vector<double>& m,
00129                          const vnl_vector<double>& v,
00130                          const vnl_matrix<double>& evecs,
00131                          const vnl_vector<double>& evals)
00132 {
00133   set_mean(m);
00134   set_variance(v);
00135 
00136   evecs_ = evecs;
00137   evals_ = evals;
00138 
00139   calcLogK();
00140 }
00141 
00142 //: Modify just the mean of the distribution
00143 // This functions should only be used by builders.
00144 void vpdfl_gaussian::set_mean(const vnl_vector<double>& mean)
00145 {
00146   vpdfl_pdf_base::set_mean(mean);
00147 }
00148 
00149 
00150 //=======================================================================
00151 //: Initialise from mean and covariance matrix
00152 //  Note, eigenvectors computed from covar, and those corresponding
00153 //  to evals smaller than min_eval are truncated
00154 void vpdfl_gaussian::set(const vnl_vector<double>& mean,
00155                          const vnl_matrix<double>& covar,
00156                          double min_eval)
00157 {
00158   unsigned int n = mean.size();
00159   assert(covar.rows()==n && covar.cols()==n);
00160 
00161   vnl_matrix<double> evecs;
00162   vnl_vector<double> evals;
00163 
00164   vnl_symmetric_eigensystem_compute(covar, evecs, evals);
00165   // eigenvalues are lowest first here
00166   evals.flip();
00167   evecs.fliplr();
00168   // eigenvalues are highest first now
00169 
00170   // Apply threshold to variance
00171   for (unsigned int i=0;i<n;++i)
00172     if (evals(i)<min_eval) evals(i)=min_eval;
00173 
00174   set(mean, evecs, evals);
00175 }
00176 
00177 
00178 //=======================================================================
00179 
00180 vnl_matrix<double> vpdfl_gaussian::covariance() const
00181 {
00182   vnl_matrix<double> Cov;
00183   mbl_matrix_product_adb(Cov, evecs_, evals_, evecs_.transpose());
00184   return Cov;
00185 }
00186 
00187 //=======================================================================
00188 
00189 vpdfl_sampler_base* vpdfl_gaussian::new_sampler() const
00190 {
00191   vpdfl_gaussian_sampler *i = new vpdfl_gaussian_sampler;
00192   i->set_model(*this);
00193   return i;
00194 }
00195 
00196 //=======================================================================
00197 
00198 //: Calculate (x-mu)' * Sigma^-1 * (x-mu)
00199 // This is the Mahalanobis distance squared from the mean.
00200 double vpdfl_gaussian::dx_sigma_dx(const vnl_vector<double>& x) const
00201 {
00202   unsigned int n = n_dims();
00203  #ifndef NDEBUG
00204   if (n!=x.size())
00205   {
00206     vcl_cerr<<"ERROR: vpdfl_gaussian::dx_sigma_dx: Target vector has "
00207             <<n<<" dimensions, not the required "<<n_dims()<<vcl_endl;
00208     vcl_abort();
00209   }
00210 #endif
00211 
00212   b_.set_size(n);
00213 
00214   dx_=x;
00215   dx_-=mean();
00216   // Rotate dx_ into co-ordinate frame of axes of Gaussian
00217   // b_ = P'dx_
00218   mbl_matxvec_prod_vm(dx_, eigenvecs(),b_);
00219 
00220   const double* b_data = b_.data_block() ;
00221   const double* v_data = eigenvals().data_block() ;
00222 
00223   double sum=0.0;
00224 
00225   unsigned int i=n;
00226   while (i-- != 0)
00227   {
00228     double db=b_data[i];
00229     sum+=(db*db)/v_data[i];
00230   }
00231   return sum;
00232 }
00233 
00234 // Probability densities:
00235 double vpdfl_gaussian::log_p(const vnl_vector<double>& x) const
00236 {
00237   return log_k() - 0.5*dx_sigma_dx(x);
00238 }
00239 
00240 //=======================================================================
00241 
00242 void vpdfl_gaussian::gradient(vnl_vector<double>& g,
00243                               const vnl_vector<double>& x,
00244                               double& p) const
00245 {
00246   unsigned int n = n_dims();
00247   dx_ = x;
00248   dx_ -= mean();
00249 
00250   if (b_.size()!=n) b_.set_size(n);
00251 
00252   // Rotate dx_ into co-ordinate frame of axes of Gaussian
00253   // b_ = P'dx_
00254   mbl_matxvec_prod_vm(dx_, eigenvecs(),b_);
00255 
00256   if (g.size()!=n) g.set_size(n);
00257 
00258   double* b_data = b_.data_block();
00259   const double* v_data = eigenvals().data_block();
00260 
00261   double sum=0.0;
00262 
00263   for (unsigned int i=0;i<n;++i)
00264   {
00265     double db=b_data[i];
00266     sum+=(db*db)/v_data[i];
00267     // Record gradient in dx_
00268     b_data[i]/=v_data[i];
00269   }
00270 
00271   p = vcl_exp(log_k() - 0.5*sum);
00272 
00273   b_*=(-1.0*p);
00274 
00275   // Rotate back into x frame
00276   mbl_matxvec_prod_mv(eigenvecs(),b_,g);
00277 }
00278 
00279 // ====================================================================
00280 
00281 double vpdfl_gaussian::log_prob_thresh(double pass_proportion) const
00282 {
00283   // The Mahalanobis distance of n-D Gaussian is distributed as Chi^2(n),
00284   // by definition, Chi^2 is the sum of independent Normal RVs.
00285   return log_k() - 0.5 * vpdfl_chi2_for_cum_prob (pass_proportion, n_dims());
00286 }
00287 
00288 //=======================================================================
00289 
00290 void vpdfl_gaussian::nearest_plausible(vnl_vector<double>& x, double log_p_min) const
00291 {
00292   // calculate radius of plausible region in standard deviations.
00293   log_p_min -= log_k();
00294   assert(log_p_min <0); // Check sd_limit is positive and real.
00295   const double sd_limit_sqr = -2.0*log_p_min;
00296   const double x_dist_sqr = dx_sigma_dx(x);
00297 
00298   unsigned int n = n_dims();
00299 
00300   if (sd_limit_sqr >= x_dist_sqr) return;
00301 
00302   const double corrective_factor = vcl_sqrt(sd_limit_sqr / x_dist_sqr);
00303 
00304   for (unsigned i=0;i<n;++i)
00305     x(i) = ((x(i)-mean()(i)) * corrective_factor) + mean()(i);
00306 }
00307 
00308 //=======================================================================
00309 // Method: is_a
00310 //=======================================================================
00311 
00312 vcl_string vpdfl_gaussian::is_a() const
00313 {
00314   static vcl_string class_name_ = "vpdfl_gaussian";
00315   return class_name_;
00316 }
00317 
00318 //=======================================================================
00319 // Method: is_class
00320 //=======================================================================
00321 
00322 bool vpdfl_gaussian::is_class(vcl_string const& s) const
00323 {
00324   return vpdfl_pdf_base::is_class(s) || s==vpdfl_gaussian::is_a();
00325 }
00326 
00327 //=======================================================================
00328 // Method: version_no
00329 //=======================================================================
00330 
00331 short vpdfl_gaussian::version_no() const
00332 {
00333   return 1;
00334 }
00335 
00336 //=======================================================================
00337 // Method: clone
00338 //=======================================================================
00339 
00340 vpdfl_pdf_base* vpdfl_gaussian::clone() const
00341 {
00342   return new vpdfl_gaussian(*this);
00343 }
00344 
00345 //=======================================================================
00346 // Method: print
00347 //=======================================================================
00348 
00349 #if 0 // commented out
00350 static void ShowStartVec(vcl_ostream& os, const vnl_vector<double>& v)
00351 {
00352   int n = 3;
00353   if (n>v.size()) n=v.size();
00354   os<<'(';
00355   for (int i=0;i<n;++i) os<<v(i)<<' ';
00356   if (v.size()>n) os<<"...";
00357   os<<")\n";
00358 }
00359 
00360 static void ShowStartMat(vcl_ostream& os, const vnl_matrix<double>& A)
00361 {
00362   os << A.rows() << " by " << A.cols() << " Matrix\n";
00363 
00364   int m = 3, n= 3;
00365   if (m>A.rows()) m=A.rows();
00366   if (n>A.cols()) n=A.cols();
00367   vsl_indent_inc(os);
00368 
00369   for (int i=0;i<m;++i)
00370   {
00371     os<<vsl_indent()<<'(';
00372 
00373     for ( int j=0; j<n; ++j)
00374       os<<A(i,j)<<' ';
00375     if (A.cols()>n) os<<"...";
00376     os<<")\n";
00377   }
00378   if (A.rows()>m) os <<vsl_indent()<<"(...\n";
00379 
00380   vsl_indent_dec(os);
00381 }
00382 #endif // commented out
00383 
00384 void vpdfl_gaussian::print_summary(vcl_ostream& os) const
00385 {
00386   vpdfl_pdf_base::print_summary(os);
00387   os << '\n';
00388   if (n_dims()!=1)
00389   {
00390     os<<vsl_indent()<<"Eigenvectors: "; vsl_print_summary(os, eigenvecs() );
00391     os<<vsl_indent()<<"log_k: "<< log_k_ << '\n';
00392 //  os<<vsl_indent()<<"Covariance: "; ShowStartMat(os, covariance() );
00393   }
00394 }
00395 
00396 //=======================================================================
00397 // Method: save
00398 //=======================================================================
00399 
00400 void vpdfl_gaussian::b_write(vsl_b_ostream& bfs) const
00401 {
00402   vsl_b_write(bfs,is_a());
00403   vsl_b_write(bfs,version_no());
00404   vpdfl_pdf_base::b_write(bfs);
00405   vsl_b_write(bfs,evecs_);
00406   vsl_b_write(bfs,evals_);
00407   vsl_b_write(bfs,log_k_);
00408 }
00409 
00410 //=======================================================================
00411 // Method: load
00412 //=======================================================================
00413 
00414 void vpdfl_gaussian::b_read(vsl_b_istream& bfs)
00415 {
00416   if (!bfs) return;
00417 
00418   vcl_string name;
00419   vsl_b_read(bfs,name);
00420   if (name != is_a())
00421   {
00422     vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&, vpdfl_gaussian &)\n"
00423              << "           Attempted to load object of type "
00424              << name <<" into object of type " << is_a() << vcl_endl;
00425     bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00426     return;
00427   }
00428 
00429   short version;
00430   vsl_b_read(bfs,version);
00431   switch (version)
00432   {
00433     case (1):
00434       vpdfl_pdf_base::b_read(bfs);
00435       vsl_b_read(bfs,evecs_);
00436       vsl_b_read(bfs,evals_);
00437       vsl_b_read(bfs,log_k_);
00438       break;
00439     default:
00440       vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&, vpdfl_gaussian &)\n"
00441                << "           Unknown version number "<< version << vcl_endl;
00442       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00443       return;
00444   }
00445 }
00446 
00447 //==================< end of vpdfl_gaussian.cxx >====================