%SUBSC Subspace Classifier
%
%   W = SUBSC(A,F)
%   W = A*SUBSC([],F)
%   W = A*SUBSC(F)
%
% INPUT
%   A    Dataset
%   F    Desired model dimensionality or fraction of retained 
%              variance per class
%
% OUTPUT
%   W    Subspace classifier  
%
% DESCRIPTION
% Each class in the trainingset A is described by linear subspace of
% dimensionality F (F>=1), or such that at least a fraction F (F<1) of its 
% varianceis retained. This is realised by calling PCAM(AI,F) or for
% each subset AI of A (objects of class I). For each class a model is
% built that assumes that the distances of the objects to the class
% subspaces follow a one-dimensional distribution. 
%
% New objects are assigned to the class of the nearest subspace.
% Classification by D = B*W, in which W is a trained subspace classifier
% and B is a testset, returns a dataset D with one-dimensional densities
% for each of the classes in its columns. The result may be improved in
% case of multi-class problems by a trained combiner, e.g.
% W = A*(SUBSC*QDC([],[],1e-6))
%
% If F is NaN it is optimised by REGOPTC.
%
% This routine will only use the classes with more than two training
% objects.
%
% REFERENCE
% E. Oja, The Subspace Methods of Pattern Recognition, Wiley, New York, 1984.
%
% SEE ALSO (<a href="http://37steps.com/prtools">PRTools Guide</a>)
% DATASETS, MAPPINGS, PCAM, FISHERC, FISHERM, GAUSSM, REGOPTC

% Copyright: R.P.W. Duin, r.p.w.duin@37steps.com
% Faculty EWI, Delft University of Technology
% P.O. Box 5031, 2600 GA Delft, The Netherlands

function W = subsc(varargin)
  
	mapname = 'SubspaceC';
  argin = shiftargin(varargin,'scalar');
  argin = setdefaults(argin,[],1);
  
  if mapping_task(argin,'definition')       % Define mapping
    W = define_mapping(argin,'untrained',mapname);
    
  elseif mapping_task(argin,'training')			% Train a mapping.
  
    [A,N] = deal(argin{:});
	
    if isnan(N)    % optimise regularisation parameter
      defs = {1};
      parmin_max = [1,size(A,2)];
      W = regoptc(A,mfilename,{N},defs,[1],parmin_max,[],0);

    else

    % handle training like A*subsc, A*subsc([],3), subsc(A)
    % PRTools takes care that they are all converted to subsc(A,N)

      islabtype(A,'crisp');     % allow crisp labels only   
      
      % remove too small classes, escape in case no two classes are left
      [A,m,k,c,lablist,L,W] = cleandset(A,3,knnc([],1));
      if ~isempty(W), return; end
      
      A = testdatasize(A,'features');
      A = setprior(A,getprior(A));
      for j = 1:c                 % run over all classes
        B = seldat(A,j)*remclass;% get the objects of a single class only
        u = mean(B);            % compute its mean
        B = B - repmat(u,size(B,1),1); % subtract mean
        v = pcam(B,N);          % compute PCA for this class
        v = v*v';               % trick: affine mappings in the original space
        B = B - B*v;            % differences of objects and their mappings
        s = mean(sum(B.*B,2));  % mean square error w.r.t. the subspace
        data(j).u = u;          % store mean
        data(j).w = v;          % store mapping
        data(j).s = s;          % store mean square distance
      end
                                % define trained mapping,
                                % store class labels and size
      W = prmapping(mfilename,'trained',data,getlablist(A),k,c);
      W = setname(W,mapname);
      W = allclass(W,lablist,L);% complete classifier with missing classes
      
    end

  elseif mapping_task(argin,'trained execution') % Evaluation

    % handle evaluation of a trained subspace classifier W for a dataset A.
    % The command D = A*W is by PRTools translated into D = subsc(A,W)
    
    [A,W] = deal(argin{1:2}); % get the variables
     m = size(A,1);           % number of test objects
    [k,c] = size(W);          % mapping size: from K features to C classes
     d = zeros(m,c);          % output: C class densities for M objects

     L = true(1,c);
    for j=1:numel(W.data)     % run over all classes
      if ~isempty(W.data(j).u)
        u = W.data(j).u;        % class mean in training set
        v = W.data(j).w;        % mapping to subspace in original space
        s = W.data(j).s;        % mean square distance
        B = A - repmat(u,m,1);  % substract mean from test set
        B = B - B*v;            % differences objects and their mappings
        d(:,j) = sum(B.*B,2)/s; % convert to distance and normalise 
        L(j) = false;
      end
    end
    d = exp(-d/2)/sqrt(2*pi); % convert to normal density
    d(:,L) = zeros(m,sum(L));

    A = prdataset(A);           % make sure A is a dataset 
    d = setdata(A,d,getlabels(W)); % take data from D and use 
                              % class labels as given in W
                              % other information in A is preserved   
    W = d;                    % return result in output variable W

  else     

    error('Illegal call')     % this should not happen

  end
      
return
