## Copyright (C) 2013 Nir Krakauer <nkrakauer@ccny.cuny.edu>
##
## This file is part of the statistics package for GNU Octave.
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {statistics} {@var{y} =} wishpdf (@var{W}, @var{Sigma}, @var{df}, @var{log_y}=false)
##
## Compute the probability density function of the Wishart distribution
##
## Inputs: A @var{p} x @var{p} matrix @var{W} where to find the PDF. The @var{p}
## x @var{p} positive definite matrix @var{Sigma} and scalar degrees of freedom
## parameter @var{df} characterizing the Wishart distribution. (For the density
## to be finite, need @var{df} > (@var{p} - 1).)
##
## If the flag @var{log_y} is set, return the log probability density -- this
## helps avoid underflow when the numerical value of the density is very small
##
## Output: @var{y} is the probability density of Wishart(@var{Sigma}, @var{df})
## at @var{W}.
##
## Input arguments must be @qcode{double} or @qcode{single}; integer, logical,
## and character arrays are rejected.  MATLAB accepts a character array and
## evaluates it at the character codes, which Octave deliberately does not,
## since a character array is an integer type and integers are refused too.
##
## @seealso{wishrnd, iwishpdf, iwishrnd}
## @end deftypefn

function y = wishpdf (W, Sigma, df, log_y=false)

  if (nargin < 3)
    print_usage ();
  endif

  ## Check for W, SIGMA, and DF being double or single
  if (! (isfloat (W) && isfloat (Sigma) && isfloat (df)))
    error ("wishpdf: W, SIGMA, and DF must be double or single.");
  endif

  p = size (Sigma, 1);

  if (df <= (p - 1))
    error ("wishpdf: DF too small, no finite densities exist.");
  endif

  ## calculate the logarithm of G_d(df/2), the multivariate gamma function
  g = (p * (p-1) / 4) * log (pi);
  for i = 1:p
    g = g + log (gamma ((df + (1 - i))/2));
  endfor

  C = chol (Sigma);

  ## use formulas for determinant of positive definite matrix for better
  ## efficiency and numerical accuracy
  logdet_W = 2*sum (log (diag (chol (W))));
  logdet_Sigma = 2*sum (log (diag (C)));

  y = -(df*p)/2 * log (2) - (df/2)*logdet_Sigma - g + ...
      ((df - p - 1)/2)*logdet_W - trace (chol2inv (C)*W)/2;

  if ! log_y
    y = exp (y);
  endif
endfunction

##test results cross-checked against dwish function in R MCMCpack library
%!assert_equal (wishpdf (4, 3, 3.1), 0.07702496, 1E-7);
%!assert_equal (wishpdf ([2 -0.3;-0.3 4], [1 0.3;0.3 1], 4), 0.004529741, 1E-7);
%!assert_equal (wishpdf ([6 2 5; 2 10 -5; 5 -5 25], [9 5 5; 5 10 -8; 5 -8 22], 5.1), 4.474865e-10, 1E-15);

%% Test input validation
%!error<wishpdf: W, SIGMA, and DF must be double or single.> wishpdf (int32 (eye (2)), eye (2), 3)
%!error<wishpdf: W, SIGMA, and DF must be double or single.> wishpdf (true (2), eye (2), 3)
%!error<wishpdf: W, SIGMA, and DF must be double or single.> wishpdf (['ab'; 'cd'], eye (2), 3)
%!error wishpdf ()
%!error wishpdf (1, 2)
%!error wishpdf (1, 2, 0)

%!error wishpdf (1, 2)
