/*
   File:        gaussian.c
   Author:      Andrew W. Moore
   Created:     Sun Apr  5 11:49:13 EDT 1998
   Description: Representing multivariate gaussians

   Copyright 1998, Carnegie Mellon University
*/

#ifndef GAUSSIAN_H
#define GAUSSIAN_H

#include "amut.h"
#include "hrect.h"
#include "qopt.h"
#include "mapshape.h"

#define ROOT_TWO_PI 2.506628274631000242

double root_two_pi_to_num_dims(int num_dims);

typedef struct gaussian
{
  bool diag;
  dyv *mean;
  dym *cov;   /* NULL if diagonal */
  dym *cov_inv; /* NULL means lazy not yet computed */
                /* NULL if diag */
  dyv *cov_diag; /* NULL if not diag */
  bool cov_det_defined; 
  double cov_det;
} gaussian;

bool gaussian_is_diag(gaussian *gs);

bool gaussian_is_ill_defined(gaussian *gs);

bool gaussian_loglik_is_ill_defined(gaussian *gs);

gaussian *mk_empty_gaussian(bool diag,int dim);

void free_gaussian(gaussian *gs);

/* Updates the gaussian's mean and covariance. Arguments are COPIED in */
void update_full_gaussian(gaussian *gs,dyv *mean,dym *cov);

/* Updates the gaussian's mean and covariance. Arguments are COPIED in */
void update_diag_gaussian(gaussian *gs,dyv *mean,dyv *cov);

gaussian *mk_full_gaussian(dyv *mean,dym *cov);

gaussian *mk_diag_gaussian(dyv *mean,dyv *cov);

void free_gaussian(gaussian *gs);
void copy_gaussian(gaussian *src,gaussian *dest);
gaussian *mk_copy_gaussian(gaussian *gs);
int gaussian_num_dims(gaussian *gs);
/* Assume we are evaluating the pdf of a Gaussian at point x.
   Assume mean Mu and covariance Cov.

   Then, provided mahalanobis_sqd = (x - MU)^T Cov^-1 (x - MU)
         and provided               cov_det = Determinant of COV

   Then this function returns that pdf value.
*/
double eval_full_gauss_from_mah_and_covdet(double mahalanobis_sqd,
                                            double cov_det,
                                            int num_dims);

/* Assume we are evaluating the pdf of a Gaussian at point x.
   Assume gs has mean Mu and covariance Cov.

   Then, provided mahalanobis_sqd = (x - MU)^T Cov^-1 (x - MU)

   Then this function returns that pdf value.
*/
double eval_full_gauss_from_mah(gaussian *gs,double mahalanobis_sqd);

/* Returns x^T a x */
double eval_mahalanobis_sqd(const dyv *x, const dym *a);

/* Evaluates the pdf of the gaussian at point x. If
 r_mahal_dsqd is non-NULL, returns the mahalanbois distance of x from
 the gaussian mean in *r_mahal_dsqd */
double gauss_eval(gaussian *gs,dyv *x,double *r_mahal_dsqd);

/* What is the maximum value that the gaussian can take?
   I.E. argmax_x P(x|wj,gaussian)

   and this obviously is maximized by putting x at the center of
   the gaussian. */
double gauss_eval_maximum(gaussian *gs);

/* Find x constrained to lie inside or on the surface of hr
   such that x^T a x is maximized. Returns the resulting
   value: x^T a x.

   argmax_x may be NULL in which case it's ignored. Else it
   gets filled up with the x that causes this maximum. */
double constrained_mahalanobis_sqd_max(hrect *hr,dym *a,dyv *argmax_x);

/* Find x constrained to lie inside or on the surface of hr
   such that x^T a x is minimized. Returns the resulting
   value: x^T a x

   argmin_x may be NULL in which case it's ignored. Else it
   gets filled up with the x that causes this minimum. */
double constrained_mahalanobis_sqd_min(hrect *hr,dym *a,dyv *argmin_x);

/* Sets *aloj = argmin_x P(x|wj,gaussian)
        *ahij = argmax_x P(x|wj,gaussian)
   where x is allowed to vary anywhere inside or on the surface of
   the given hyper-rectangle.

   You may pass in *aloj or *ahij as NULL if you don't want both the
   upper and lower limits. That'll save some computation.

   If x_minlik is non-NULL, and aloj is non-NULL, then the point
   inside hr that minimizes the likelihood (i.e. the point furthest in
   mahalanobis distance from mean) will be stored in x_minlik.
   Ditto for x_maxlik.
   */
void gauss_eval_limits(hrect *hr,gaussian *gs,
                         double *aloj,double *ahij,
                         dyv *x_minlik,dyv *x_maxlik);

      
/* Returns a pointer to the mean vector of the gaussian. Do not
   ever free or update this vector. */
dyv *gaussian_mean(gaussian *gs);

/* Makes a copy of the covariance matrix of the gaussian. You CANNOT and
   SHOULD NOT TRY to access the internal covariance pointer directly
   since we do not promise that it exists. */
dym *mk_covariance_of_full_gaussian(gaussian *gs);

/* Makes a copy of the covariance matrix of the gaussian. You CANNOT and
   SHOULD NOT TRY to access the internal covariance pointer directly
   since we do not promise that it exists. */
dyv *mk_covariance_of_diag_gaussian(gaussian *gs);


dyv *mk_sample_from_gaussian(gaussian *gs);

double gaussian_volume(gaussian *gs);

/* Test if the covariance is kind of bad in the sense that it may have only
   been some random bits at the bottom of our double representation that
   allows us to invert it.  We test this by considering the smallest
   eigenvalue (which is 0 in a singular matrix) as a ratio to the largest.
*/
bool gaussian_has_not_so_good_covariance(gaussian *gs);
/* its bad if we can't invert it */
bool gaussian_has_bad_covariance(gaussian *gs);

/* Returns a dyv of size 2 corresponding to the location on the screen
   that the user clicked. Waits for this click. Prints the message to stdout
   before waiting. By default uses the ag coordinate system with 0,0 in
   bottom left and 512,512 in top right. But if ag_set_frame has been called
   uses those coordinates. ag_on(..) must have been called. See draw/amgr.h
   for more details. */
/* Returns the button pressed in *button. 1 is left, 2 middle, 3 right */
dyv *mk_dyv_from_mouse(mapshape *ms,char *message,int *button);

/* Makes a 2-d gaussian with mean at c, and who's 2-std-dev contour
   (which is an ellipse) has its major axis extreme at a, and who's
   minor axis extreme is at b assuming that the user has inputted b so that
   (b-c) is perpendicular to (a-c). 

   If b doesn't satisfy the above, does something sensible anyway (it
   puts the length of the minor axis to be the perpendicular distance from
   b to the line thru a and c).
*/
gaussian *mk_gaussian_from_cab(mapshape *ms,dyv *c,dyv *a,dyv *b);

/* Instructs the user to do three mouse clicks to enter the
   covariance of a 2-d gaussian */
gaussian *mk_gaussian_from_mouse_with_given_center(mapshape *ms,dyv *c);

/* Instructs the user to do three mouse clicks to enter the mean and
   covariance of a 2-d gaussian */
gaussian *mk_gaussian_from_mouse(mapshape *ms);

void fprintf_gaussian(FILE *s,char *m1,gaussian *gs,char *m2);

/* Returns the minimum lambda such that for all unit vectors n,
   [Diag(metric) n]^T cov^-1 [Diag(metric) n]] <= (num_sigmas/lambda)^2,

   i.e. such that sqrt(q^T cov^-1 q) <= num_sigmas where

           q = lambda Diag(metric) n 

*/ 
double gaussian_metric_radius(gaussian *gs,dyv *metric,double num_sigmas);

/* Loglikeuates the pdf of the gaussian at point x. If
 r_mahal_dsqd is non-NULL, returns the mahalanbois distance of x from
 the gaussian mean in *r_mahal_dsqd */
double gauss_loglike(gaussian *gs,dyv *x,double *r_mahal_dsqd);

/* NULL rows means "use all rows" */
gaussian *mk_gaussian_from_dym_rows(bool diag,dym *x,ivec *rows);

dym *mk_invert_spd_robustly(const dym *x);

/* The following four functions all return a scalar property of
   the Gaussian, specific to one of the dimensions. (Actually, the
   covariance function depends on two of the dimensions) */

double gaussian_dim_to_mean(gaussian *gs,int i);

double gaussian_dim_to_variance(gaussian *gs,int i);

double gaussian_dim_to_sdev(gaussian *gs,int i);

/* PRE: gaussian must be full. Do not call if
   gaussian_is_diag(gs) is TRUE. */
double gaussian_dims_to_cov(gaussian *gs,int i,int j);


#endif /* #ifdef GAUSSIAN_H */
