function y = poisspdfln(x,lambda)
% Possion log probability function

    if ~isfloat(x)
       x = double(x);
    end

    y(lambda < 0) = NaN;
    y(isnan(x) | isnan(lambda)) = NaN;
    y(x==0 & lambda==0) = 0;

    k = find(x >= 0 & x == round(x) & lambda > 0);

    if ~isempty(k)
        x = x(k);
        lambda = lambda(k);

        smallx = x <= lambda * realmin;
        y(k(smallx)) = -lambda(smallx);

        largex = lambda < x * realmin;
        y(k(largex)) = -lambda(largex) + x(largex).*log(lambda(largex)) - gammaln(x(largex)+1);

        other = ~smallx & ~largex;
        lnsr2pi = 0.9189385332046727; % log(sqrt(2*pi))
        y(k(other)) = -lnsr2pi -0.5*log(x(other)) - stirlerr(x(other)) - binodeviance(x(other),lambda(other));
    end
end

function bd0 = binodeviance(x,np)
%BINODEVIANCE Deviance term for binomial and Poisson probability calculation.
%    BD0=BINODEVIANCE(X,NP) calculates the deviance as defined in equation
%    5.2 in C. Loader, "Fast and Accurate Calculations of Binomial
%    Probabilities", July 9, 2000. X and NP must be of the same size.
%
%    For "x/np" not close to 1:
%        bd0(x,np) = np*f(x/np) where f(e)=e*log(e)+1-e
%    For "x/np" close to 1: 
%         The function is calculated using the formula in Equation 5.2. 

%   Copyright 2010 The MathWorks, Inc.
%   $Revision: 1.1.6.2 $

    bd0=zeros(size(x));

    % If "x/np" is close to 1:
    k = abs(x-np)<0.1*(x+np);
    if any(k(:))
        s = (x(k)-np(k)).*(x(k)-np(k))./(x(k)+np(k));
        v = (x(k)-np(k))./(x(k)+np(k));
        ej = 2.*x(k).*v;
        s1 = zeros(size(s));
        ok = true(size(s));
        j = 0;
        while any(ok(:))
            ej(ok) = ej(ok).*v(ok).*v(ok);
            j = j+1;
            s1(ok) = s(ok) + ej(ok)./(2*j+1);
            ok = ok & s1~=s;
            s(ok) = s1(ok);
        end
        bd0(k) = s;
    end

    % If "x/np" is not close to 1:
    k = ~k;
    if any(k(:))
        bd0(k)= x(k).*log(x(k)./np(k))+np(k)-x(k);
    end
end


function delta = stirlerr(n)
%STIRLERR Error of Stirling-De Moivre approximation to n factorial.
%    DELTA=STIRLERR(N) computes
%        gammaln(n+1) - (0.5*log(2*pi*n)+n*log(n)-n)
%    This function is used in C. Loader, "Fast and Accurate Calculations of
%    Binomial Probabilities", July 9, 2000, to compute binomial and Poisson
%    probabilities.
%
%    DELTA is approximated as
%        delta(n) = 1/(12*n) - 1/(360*n^3) + 1/(1260*n^5) + O(1/n^7)
%    For small values of "n", the pre-calculated values for delta(n) are
%    used.

%   Copyright 2010 The MathWorks, Inc.
%   $Revision: 1.1.6.2 $

    delta = zeros(size(n));
    nn = n.*n;

    % Define S0=1/12 S1=1/360 S2=1/1260 S3=1/1680 S4=1/1188
    S0 = 8.333333333333333e-02;
    S1 = 2.777777777777778e-03;
    S2 = 7.936507936507937e-04;
    S3 = 5.952380952380952e-04;
    S4 = 8.417508417508418e-04;

    % Define delta(n) for n<0:0.5:15
    sfe=[                    0;...
         1.534264097200273e-01;...
         8.106146679532726e-02;...
         5.481412105191765e-02;...
         4.134069595540929e-02;... 
         3.316287351993629e-02;...
         2.767792568499834e-02;...
         2.374616365629750e-02;...
         2.079067210376509e-02;...
         1.848845053267319e-02;...
         1.664469118982119e-02;...
         1.513497322191738e-02;...
         1.387612882307075e-02;...
         1.281046524292023e-02;...
         1.189670994589177e-02;...
         1.110455975820868e-02;...
         1.041126526197210e-02;...
         9.799416126158803e-03;...
         9.255462182712733e-03;...
         8.768700134139385e-03;...
         8.330563433362871e-03;...
         7.934114564314021e-03;...
         7.573675487951841e-03;...
         7.244554301320383e-03;...
         6.942840107209530e-03;...
         6.665247032707682e-03;...
         6.408994188004207e-03;...
         6.171712263039458e-03;...
         5.951370112758848e-03;...
         5.746216513010116e-03;...
         5.554733551962801e-03];

    k = find(n<=15);
    if any(k)
        n1 = n(k);
        n2 = 2*n1;
        if n2==round(n2)
            delta(k) = sfe(n2+1);
        else
            lnsr2pi = 0.9189385332046728;
            delta(k) = gammaln(n1+1)-(n1+0.5).*log(n1)+n1-lnsr2pi;
        end
    end
    k = find(n>15 & n<=35);
    if any(k)
        delta(k) = (S0-(S1-(S2-(S3-S4./nn(k))./nn(k))./nn(k))./nn(k))./n(k);
    end
    k = find(n>35 & n<=80);
    if any(k)
        delta(k) = (S0-(S1-(S2-S3./nn(k))./nn(k))./nn(k))./n(k);
    end
    k = find(n>80 & n<=500);
    if any(k)
        delta(k) = (S0-(S1-S2./nn(k))./nn(k))./n(k);
    end
    k = find(n>500);
    if any(k)
        delta(k) = (S0-S1./nn(k))./n(k);
    end

end