function results = multilogit(y,x,beta0,maxit,tol);
% PURPOSE: implements multinomial logistic regression
% Pr(y_i=j) = exp(x_i'beta_j)/sum_l[exp(x_i'beta_l)]
%   where:
%   i    =   1,2,...,nobs
%   j,l  = 0,1,2,...,ncat
%-------------------------------------------------------------------------%
% USAGE: results = multilogit(y,x,beta)
% where: y = response variable vector (nobs x 1)
%            the response variable should be coded sequentially from 0 to
%            ncat, i.e., y in {0,1,2,...,ncat}
%        x = matrix of covariates (nobs x nvar)
%            NOTE: to include a constant term in each beta_j,
%            include a column of ones in x
%    beta0 = optional starting values for beta (nvar x ncat+1) (default=0)
%    maxit = optional maximum number of iterations (default=100)
%      tol = optional convergence tolerance (default=1e-6)
%-------------------------------------------------------------------------%
% RETURNS: a structure
%        results.meth = 'multilogit'
%    results.beta_mat = (nvar x ncat) matrix of beta coefficients:
%                       [beta_1 beta_2 ... beta_ncat] under the 
%                       normalization beta_0 = 0
%    results.beta_vec = (nvar*ncat x 1) vector of beta coefficients:
%                       [beta_1 ; beta_2 ; ... ; beta_ncat] under
%                       normalization beta_0 = 0
%        results.covb = (nvar*ncat x nvar*ncat) covariance matrix
%                       of results.beta_vec
%   results.tstat_mat = matrix of t-statistics conformable to 
%                       results.beta_mat
% results.tstat_vec   = vector of t-statistics conformable to
%                       results.beta_vec
%        results.yfit = (nobs x ncat+1) matrix of fitted 
%                       probabilities: [P_0 P_1 ... P_ncat]
%                       where P_j = [P_1j ; P_2j ; ... ; P_nobsj]
%         results.lik = unrestricted log likelihood
%        results.cnvg = convergence criterion
%        results.iter = number of iterations
%        results.nobs = number of observations
%        results.nvar = number of variables
%        results.ncat = number of categories of dependent variable
%                       (including the reference category j = 0)
%       results.count = vector of counts of each value taken by y, i.e., 
%                       count = [#y=0 #y=1 ... #y=ncat]
%           results.y = y vector
%      results.lratio = LR test statistic against intercept-only model (all 
%                       betas=0), distributed chi-squared with (nvar-1)*ncat
%                       degrees of freedom
%        results.rsqr = McFadden pseudo-R^2
%      
%-------------------------------------------------------------------------%
% A NOTE: Since users might prefer results (coefficients and tstats) in 
%   either a vector or matrix format, and since there is no single natural
%   representation for these in the multinomial logit model, the results
%   structure returns both.  Note that the input arguments require that 
%   (optional) starting values in matrix (nvar x ncat) format.
%
%-------------------------------------------------------------------------%
% SEE ALSO: prt_multilogit, multilogit_lik
%-------------------------------------------------------------------------%
% References: Greene (1997), p.914

% written by:
% Simon D. Woodcock
% CISER / Economics
% Cornell University
% Ithaca, NY 
% sdw9@cornell.edu

%---------------------------------------------------------%
%       ERROR CHECKING AND PRELIMINARY CALCULATIONS       %
%---------------------------------------------------------%

if nargin < 2, error('multilogit: wrong # of input arguments'); end;
y = round(y(:)); [nobs cy]=size(y); [rx nvar]=size(x);

if (rx~=nobs), error('multilogit: row dimensions of x and y must agree'); end;

% initial calculations
xstd = [1 std(x(:,2:nvar))];
x = x ./ ( ones(nobs,1)*xstd );                           % standardize x
ymin = min(y);
ymax = max(y);
ncat = ymax - ymin;
d0 = ( y*ones(1,ncat+1) ) == ( ones(nobs,1)*(ymin:ymax) );  % put y in dummy format
d = d0(:,2:ncat+1);                                         % normalize beta_0 = 0

% starting values

if nargin < 3
    beta0 = zeros(nvar,ncat+1);
else 
    [a b] = size(beta0);
    if a == 0
        beta0 = zeros(nvar,ncat+1);
    else for j = 1:ncat;
            beta0(:,j) = beta0(:,j) .* xstd';
         end;
    end;
end;

beta = beta0(:,2:ncat+1);

% default max iterations and tolerance
if nargin < 4 , maxit = 100; tol = 1e-6; end;
if nargin < 5 , tol = 1e-6; end;

if nargin > 6 , error('multilogit: wrong # of arguments'); end;

% check nvar and ncat are consistently defined;
[rbeta cbeta] = size(beta);
if nvar ~= rbeta
    error('multilogit: rows of beta and columns of x do not agree')
end;
if ncat ~= cbeta
    error(['multilogit: number of columns in beta and categories in y do not agree. ' ...
        'check that y is numbered continuously, i.e., y takes values in {0,1,2,3,4,5}' ...
        ' is ok, y takes values in {0,1,2,3,4,99} is not.'])
end;

%----------------------------------------------------%
% MAXIMUM LIKELIHOOD ESTIMATION OF MULTINOMIAL LOGIT %
%----------------------------------------------------%

% likelihood and derivatives at starting values
[P,lnL] = multilogit_lik(y,x,beta,d);
[g H] = multilogit_deriv(x,d,P,nvar,ncat,nobs);

iter=0;

for j = 1:ncat % vectorize beta and gradient for newton-raphson update
    f = (j-1)*nvar + 1;
    l = j*nvar;
    vb(f:l,1) = beta(:,j);
    vg(f:l,1) = g(:,j);
end;

% newton-raphson update
while (abs(vg'*(H\vg)/length(vg)) > tol) & (iter < maxit)
    iter = iter + 1;
    betaold = beta;
    vbold = vb;
    vb = vbold - H\vg;
    for j = 1:ncat                                   % de-vectorize updated beta for pass to multilogit_lik
        f = (j-1)*nvar + 1;
        l = j*nvar;
        beta(:,j) = vb(f:l,1);
    end;
    [P,lnL] = multilogit_lik(y,x,beta,d);            % update P, lnL
    [g H] = multilogit_deriv(x,d,P,nvar,ncat,nobs);  % update g,H
    for j = 1:ncat;                                  % vectorize updated g for next N-R update
        f = (j-1)*nvar + 1;
        l = j*nvar;
        vg(f:l,1) = g(:,j);
    end;
    disp(['iteration: ' num2str(iter)]);
    disp(['log-likelihood: ' num2str(lnL)]);
end;

%---------------------------------------------------------%
%               GENERATE RESULTS STRUCTURE                %
%---------------------------------------------------------%

results.meth = 'multilogit';
for j = 1:ncat
    results.beta_mat(:,j) = beta(:,j) ./ xstd';        % restore original scale
end;
for j = 1:ncat
    f = (j-1)*nvar + 1;
    l = j*nvar;
    results.beta_vec(f:l,1) = results.beta_mat(:,j);
end;
results.covb = -inv(H)./kron(ones(ncat),(xstd'*xstd)); % restore original scale
stdb = sqrt(diag(results.covb));
results.tstat_vec = results.beta_vec./stdb;
for j = 1:ncat                                  
    f = (j-1)*nvar + 1;
    l = j*nvar;
    results.tstat_mat(:,j) = results.tstat_vec(f:l,1);
end;
P_0 = ones(nobs,1) - sum(P')';
results.yfit = [P_0 P];
results.lik = lnL;
results.cnvg = tol;
results.iter = iter;
results.nobs = nobs;
results.nvar = nvar;
results.ncat = ncat;
results.count = [nobs-sum(sum(d)') sum(d)];
results.y = y;

% basic specification testing;
p = results.count / nobs;
lnLr = nobs*sum((p.*log(p))'); % restricted log-likelihood: intercepts only
results.lratio = -2*(lnLr - results.lik);
results.rsqr = 1 - (results.lik / lnLr); % McFadden pseudo-R^2




%---------------------------------------------------------%
%     SUPPLEMENTARY FUNCTION FOR COMPUTING DERIVATIVES    %
%---------------------------------------------------------%

function [g,H] = multilogit_deriv(x,d,P,nvar,ncat,nobs);
% PURPOSE: Computes gradient and Hessian of multinomial logit 
% model
% ---------------------------------------------------------
% References: Greene (1997), p.914

% written by:
% Simon D. Woodcock
% CISER / Economics
% 201 Caldwell Hall
% Cornell University
% Ithaca, NY 14850
% sdw9@cornell.edu

% compute gradient matrix (nvar x ncat)
tmp = d - P;
g = x'*tmp;

% compute Hessian, which has (ncat)^2 blocks of size (nvar x nvar)
% this algorithm builds each block individually, m&n are block indices
H = zeros(nvar*ncat);
for m = 1:ncat; 
    for n = 1:ncat;
        fr = (m-1)*nvar + 1;
        lr = m*nvar;
        fc = (n-1)*nvar + 1;
        lc = n*nvar;       
        index = (n==m);
        index = repmat(index,nobs,1);
        H(fr:lr,fc:lc) = -( ( x.*( P(:,m)*ones(1,nvar) ) )' * ( x.*( (index-P(:,n))*ones(1,nvar) ) ) ) ;
    end;
end;
