%% Implementation of the Devroye (2014) algorithm for sampling from 
% the generalized inverse Gaussian (GIG) distribution
%
% function X = gigrnd(p, a, b, sampleSize)
%
% The generalized inverse Gaussian (GIG) distribution is a continuous
% probability distribution with probability density function:
%
% p(x | p,a,b) = (a/b)^(p/2)/2/besselk(p,sqrt(a*b))*x^(p-1)*exp(-(a*x + b/x)/2)
%
% Parameters:
%   p \in Real, a > 0, b > 0
%
% See Wikipedia page for properties:
% https://en.wikipedia.org/wiki/Generalized_inverse_Gaussian_distribution 
%
% This is an implementation of the Devroye (2014) algorithm for GIG sampling.
% 
% Returns:
%   X      = random variates [sampleSize x 1] from the GIG(p, a, b)
%  
% References:
% L. Devroye
% Random variate generation for the generalized inverse Gaussian distribution 
% Statistics and Computing, Vol. 24, pp. 239-246, 2014.
%
% (c) Copyright Enes Makalic and Daniel F. Schmidt, 2015
function X = gigrnd(P, a, b, sampleSize)

%% Setup -- we sample from the two parameter version of the GIG(alpha,omega)
lambda = P;
omega = sqrt(a*b);
alpha = sqrt(omega^2 + lambda^2) - lambda;

%% Find t
x = -psi(1, alpha, lambda);
if((x >= 0.5) && (x <= 2))
    t = 1;
elseif(x > 2)
    t = sqrt(2 / (alpha + lambda));
elseif(x < 0.5)
    t = log(4/(alpha + 2*lambda));     
end

%% Find s
x = -psi(-1, alpha, lambda);
if((x >= 0.5) && (x <= 2))
    s = 1;
elseif(x > 2)
    s = sqrt(4/(alpha*cosh(1) + lambda));
elseif(x < 0.5)
    s = min(1/lambda, log(1 + 1/alpha + sqrt(1/alpha^2+2/alpha)));
end

%% Generation
eta = -psi(t, alpha, lambda);
zeta = -dpsi(t, alpha, lambda);
theta = -psi(-s, alpha, lambda);
xi = dpsi(-s, alpha, lambda);
p = 1/xi;
r = 1/zeta;
td = t - r*eta;
sd = s - p*theta;
q = td + sd;

X = zeros(sampleSize, 1);
for sample = 1:sampleSize
    done = false;
    while(~done)
        U = rand(1); 
        V = rand(1); 
        W = rand(1);
        if(U < (q / (p + q + r)))
            X(sample) = -sd + q*V;
        elseif(U < ((q + r) / (p + q + r)))
            X(sample) = td - r*log(V);
        else
            X(sample) = -sd + p*log(V);
        end

        %% Are we done?
        f1 = exp(-eta - zeta*(X(sample)-t));
        f2 = exp(-theta + xi*(X(sample)+s));
        if((W*g(X(sample), sd, td, f1, f2)) <= exp(psi(X(sample), alpha, lambda)))
            done = true;
        end
    end
end

%% Transform X back to the three parameter GIG(p,a,b)
X = exp(X) * (lambda / omega + sqrt(1 + (lambda/omega)^2));
X = X ./ sqrt(a/b);

end

function f = psi(x, alpha, lambda)
    f = -alpha*(cosh(x) - 1) - lambda*(exp(x) - x - 1);
end

function f = dpsi(x, alpha, lambda)
    f = -alpha*sinh(x) - lambda*(exp(x) - 1);
end

function f = g(x, sd, td, f1, f2)

a = 0;
b = 0;
c = 0;
if((x >= -sd) && (x <= td))
    a = 1;
elseif(x > td)
    b = f1;
elseif(x < -sd)
    c = f2;   
end

f = a + b + c;

end