% Linear fit with singular value decomposition
% 
% INPUT:
%   - exps: is a N dimensional struct whose fields are fitdata
%   (experimental data), fitbasis (basis for the fit) and params (cell
%   array with the names of the parameters used in the experiment).
%   - groundexps: a M dim struct containing results from on ground experiments.
%   Fuields are: pos (parameter position number), value (on ground measurement
%   result) and err (experimental error on the on ground measurement).
%   - sThreshold it's a threshold for singular values. It is a
%     number, typically 1. It will remove singular values larger
%     than sThreshold which corresponds to removing svd parameters estimated
%     with an error larger than sThreshold.
% 
% OUTPUT:
%   a:      params values
%   Ca:     fit covariance matrix for A
%   Corra:  fit correlation matrix for A
%   Vu:     is the complete conversion matrix
%   Cbu:    is the new variables covariance matrix
%   Fbu:    is the information matrix for the new variable
%   mse:    is the fit Mean Square Error
%   dof:    degrees of freedom for the global estimation
%   ppm:    number of svd parameters per measurements, provides also the
%   number of independent combinations of parameters per each singular
%   measurement. The coefficients of the combinations are then stored in Vu
% 
% 21-07-2009 L Ferraioli
%       CREATION
% 
% 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% $Id: linfitsvd.m,v 1.8 2010/10/29 12:40:47 ingo Exp $
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [a,Ca,Corra,Vu,bu,Cbu,Fbu,mse,dof,ppm] = linfitsvd(varargin)
  
  % get inputs
  if nargin == 1 % no ground experiment
    exps = varargin{1};
    groundexps = [];
    remerr = false;
  elseif nargin == 2
    exps = varargin{1};
    if isnumeric(varargin{2})
      sThreshold = varargin{2};
      remerr = true;
      groundexps = [];
    else
      groundexps = varargin{2};
      remerr = false;
    end
  elseif nargin == 3
    exps = varargin{1};
    groundexps = varargin{2};
    sThreshold = varargin{3}; % admitted threshold for singular values
    remerr = true;
    if ~isnumeric(sThreshold)
      sThreshold = inf;
    end
  end
  
  % init collect results struct
  
  % init union matrices
  Vu = [];
  Fbu = [];
  wFbu = [];
  bu = [];
%   eCbu = [];
  Cbu = [];
  ppm = []; % number of svd parameters per measurements
  
  N = numel(exps);
  % run over input experiments
  for ii=1:N
    % get data of the experiments out of the input struct
    y = exps(ii).fitdata;
    X = exps(ii).fitbasis;
    
    % willing to work with columns
    if size(y,1)<size(y,2)
      y = y.';
    end
    if size(X,1)<size(X,2)
      X = X.';
    end
    
    % get svd of X
    [U,W,V] = svd(X,0);
    
    % removing zero singular values
    svals = diag(W);    
    idx = svals==0;    
    svals(idx) = [];
    
    % removing columns of U and V corresponding to zero svalues
    U(:,idx) = [];
    V(:,idx) = [];
    
    if remerr
      % Find params combinations with error larger than 1
      idx = svals<sThreshold;
      svals(idx) = [];

      % removing columns of U and V corresponding to params combinations with
      % error larger than 1
      U(:,idx) = [];
      V(:,idx) = [];
    end
    
    % Sanity check
    % if svals is empty you are going to gain no information from the
    % current data series
    if ~isempty(svals) % go with the fit
      % rebuild W
      W = diag(svals);
      
      % update ppm with the number of parameters for the given experiment
      ppm = [ppm; numel(svals)];

      % get new basis for the fit
      K = U*W;

      % get expected covariance matrix for b, assuming white noise
      Fb = W*W; % information matrix for parameters b

      % get b params with errors, mse is the mean square error, Cb is the
      % covariance matrix of fitted b. It should be equal to eCb.*mse
      [b,stdxb,mseb,Cb] = lscov(K,y);

      % information matrix weighted for fit results Cb = inv(wFb)
      wFb = Fb./mseb;

      % add params from present experiment
      % get union matrices
      Vu = [Vu;V.'];
      Fbu = blkdiag(Fbu,Fb);
      wFbu = blkdiag(wFbu,wFb);
      Cbu = blkdiag(Cbu,Cb);
      bu = [bu;b];
    
    else % no information, no fit
      % update ppm with the number of parameters for the given experiment
      ppm = [ppm; 0];
    end
      
    
    
  end
  
  % insert values from on-ground measured parameters if applicable
  if nargin == 2
    
    for jj = 1:numel(groundexps)

      % get values
      pos = groundexps(jj).pos;
      val = groundexps(jj).value;
      err = groundexps(jj).err;

      tV = zeros(1,size(Vu,2));
      tV(1,pos) = 1;
      Vu = [Vu;tV];
      bu = [bu;val];
      Cbu = blkdiag(Cbu,err^2);
      Fbu = blkdiag(Fbu,1/(err^2));
      wFbu = blkdiag(wFbu,1/(err^2));
      ppm = [ppm; 1];

    end
    
  end
    
  % get results on physical parameters, solve the system with proper weights
  a = lscov(Vu,bu,1./diag(Cbu));
    
  % get params covariance
  Ca = inv(Vu'*wFbu*Vu);
  
  % get params correlation matrix
  Corra = Ca;
  for tt=1:size(Corra,1)
    for hh=1:size(Corra,2)
      Corra(tt,hh) = Ca(tt,hh)/(sqrt(Ca(tt,tt))*sqrt(Ca(hh,hh)));
    end
  end
  
  % get chi square assuming unitary variance white noise
  N = numel(exps);
  mse = 0;
  tL = 0;
  % run over input experiments
  for ii=1:N
    % get data of the experiments out of the input struct
    y = exps(ii).fitdata;
    X = exps(ii).fitbasis;
    
    mse = mse + sum((y - X*a).^2);
    tL = tL + numel(y);
  end
  dof = tL-numel(a);
  mse = mse./dof;

end









