function [estParams, LL] = fastfa(X, zDim, varargin)
typ = 'fa';
tol = 1e-8;
cyc = 1e8;
minVarFrac = 0.01;
verbose = false;
assignopts(who, varargin);
randn('state', 0);
[xDim, N] = size(X);
cX = cov(X', 1);
if rank(cX) == xDim
scale = exp(2*sum(log(diag(chol(cX))))/xDim);
else
fprintf('WARNING in fastfa.m: Data matrix is not full rank.\n');
r = rank(cX);
e = sort(eig(cX), 'descend');
scale = geomean(e(1:r));
end
L = randn(xDim,zDim)*sqrt(scale/zDim);
Ph = diag(cX);
d = mean(X, 2);
varFloor = minVarFrac * diag(cX);
I = eye(zDim);
const = -xDim/2*log(2*pi);
LLi = 0;
LL = [];
for i = 1:cyc
iPh = diag(1./Ph);
iPhL = iPh * L;
MM = iPh - iPhL / (I + L' * iPhL) * iPhL';
beta = L' * MM;
cX_beta = cX * beta';
EZZ = I - beta * L + beta * cX_beta;
LLold = LLi;
ldM = sum(log(diag(chol(MM))));
LLi = N*const + N*ldM - 0.5*N*sum(sum(MM .* cX));
if verbose
fprintf('EM iteration %5i lik %8.1f \r', i, LLi);
end
LL = [LL LLi];
L = cX_beta / EZZ;
Ph = diag(cX) - sum(cX_beta .* L, 2);
if isequal(typ, 'ppca')
Ph = mean(Ph) * ones(xDim, 1);
end
if isequal(typ, 'fa')
Ph = max(varFloor, Ph);
end
if i<=2
LLbase = LLi;
elseif (LLi < LLold)
disp('VIOLATION');
elseif ((LLi-LLbase) < (1+tol)*(LLold-LLbase))
break;
end
end
if verbose
fprintf('\n');
end
if any(Ph == varFloor)
fprintf('Warning: Private variance floor used for one or more observed dimensions in FA.\n');
end
estParams.L = L;
estParams.Ph = Ph;
estParams.d = d;