% TESTICA - Test the RUNICA function's ability to separate synthetic sources. 
%             Use the input variables to estimate the (best) decomposition accuracy
%             for a given data set size.
% Usage:
%        >> testica(channels,frames);  % No return variable -> plot results
%        >> [testresult] = testica(channels,frames,sources,exppow,shape);
%                          % Return variable -> return results with no plots
% Inputs:
%   channels = number of simulated data channels {no default}
%   frames   = number of simulated time points {no default}
%   sources  = number of simulated quasi-independent sources {default: =channels}
%   exppow   = exponential power for scaling size of the sources (0->all equal)
%               {default: -0.05 -> Ex: 14 sources scaled between 1.0 and 0.24}
%   shape    = varies monotonically with kurtosis of the simulated sources 
%               {default: 1.2 -> source kurtosis near 1 (super-Gaussian>0)}
%
% Authors: Scott Makeig & Te-Won Lee, SCCN/INC/UCSD, La Jolla, 2-27-1997 
%
% See also: RUNICA

% Copyright (C) 2-27-97 Scott Makeig & Te-Won Lee, SCCN/INC/UCSD, scott@sccn.ucsd.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

% 2-28-97 added source, shape and exppow parameters, kurtosis -sm
% 4-03-97 shortened name to TESTICA -sm
% 4-14-97 changed call to RUNICA to use new variable order -sm
% 4-16-97 prints max and min abs corr -sm
% 7-10-97 changed to NEWRUNICA, added SURF plot -sm
% 7-30-97 altered RUNICA call to fit version 3.0 -sm
% 3-02-00 replaced IDIT call with call to Benjamin Blankertz' EYELIKE -sm
% 3-08-00 added kurt and exppow plots, changed defaults, added plot labels -sm
% 01-25-02 reformated help & license, added links -ad 

function [testresult] = testica(channels,frames,sources,exppow,shape)

icadefs; % read BACKCOLOR

% Default RUNICA parameter values:

block = 0;   % default block size 
lrate = 0;   % default starting lrate 
adeg  = 0;   % default annealing threshold 
maxsteps   = 0; % default 
sphereflag = 'on'; % default yes, perform sphering
stop  = 0.000001;  % default stopping wchange

% Defaults:

default_chans = 31;
default_frames = 10000;
% default sources = channels
default_exppow = -0.05;
default_shape = 1.2;
plotflag = 1;
try, plotflag = ismatlab; catch, end

if nargin<2
  help testica
  return
end
if nargin<5
  shape = default_shape;
end
if nargin<4
   exppow = default_exppow;
end
if nargin<3,
	sources = 0;
end
if nargin<2
 frames = 0;
end
if nargin < 1
 channels   = 0;
end
if frames == 0,
  frames = default_frames;
end
if channels == 0,
  channels = default_chans;
end
if sources == 0,
	sources = channels;
end

if sources < channels,
  fprintf('testica() - sources must be >= channels.\n');
  exit 1
end

% Generate artificial super-Gaussian sources:

fprintf('\n  Testing runica() using %d simulated sources.\n\n',sources);
fprintf('Computing %d simulated source activations of length %d ...\n', ...
                                      sources,frames);
fprintf('Simulated source strengths: %4.3f to %4.3f.\n', ...
                                      1.0, exp(exppow*(channels-1)));
exppowers = zeros(1,channels);
exppowers(1) = 1.0;
for s=1:sources
  exppowers(s) = exp(exppow*(s-1));
end

% Synthesize random source activations

super=randn(sources,frames).*(exppowers'*ones(1,frames));  
super=sign(super).*abs(super.^shape); % make super-Gaussian if shape > 1
% fprintf('Size of super = %d,%d\n',size(super,1),size(super,2));

if frames > 40 && plotflag
  figure
  pos = get(gcf,'position');
  off = [40 -40 0 0]; % succeeding figure screen position offsets

  hist(super(1,:),round(frames/20));
  tt=title('Amplitude distribution of source 1');
  set(tt,'fontsize',14);
  xlm=get(gca,'xlim');
  ylm=get(gca,'ylim');
  kurttext = ['Kurtosis = ' num2str(kurt(super(1,:)),3)];
  tp=[xlm;ylm]*[0.25;0.75];
  kt=text(tp(1),tp(2),kurttext);
  set(kt,'fontsize',13);
  set(kt,'horizontalalignment','center');
else
  fprintf('Not plotting source amplitude histogram: data length too small.\n')
end 

if nargout == 0 && plotflag
  input('Hit enter to view source strengths: ');
  fprintf('\n')
  if frames <= 40
    figure
    pos = get(gcf,'position');
  else
    figure('position',pos+off);
  end
  plot(1:sources,exppowers);
  hold on;plot(1:sources,exppowers,'r^');
  set(gca,'xlim',[0 sources+1]);
  set(gca,'ylim',[0 1]);
  xt=title(['Relative source amplitudes (exppow = ' num2str(exppow,3) ')']);
  set(xt,'fontsize',14);
  axl=xlabel('Source Number');
  ayl=ylabel('Relative Amplitude');
  set(axl,'fontsize',14);
  set(ayl,'fontsize',14);
end

k = kurt(super'); % find kurtosis of rows of super
maxkurt = max(k); minkurt=min(k);
fprintf('Simulated source kurtosis: %4.3f to %4.3f.\n',minkurt,maxkurt);

tmp = corrcoef(super');
i = find(tmp<1);
minoff = min(abs(tmp(i)));
maxoff = max(abs(tmp(i)));
fprintf('Absolute correlations between sources range from %5.4f to %5.4f\n', ...
                                                           minoff,maxoff);

fprintf('Mixing the simulated sources into %d channels ...\n',channels);
forward = randn(channels,sources);   % random forward mixing matrix
data = forward*super; % these are the simulated observed data
if nargout == 0
    input('Hit enter to start ICA decomposition: ')
    fprintf('\n')
end

fprintf('Decomposing the resulting simulated data using runica() ...\n');
 [weights,sphere,compvars,bias,signs,lrates,activations] = runica(data, ...
        'block',block, ...
           'lrate',lrate, ...
              'nochange',stop, ...
                 'annealdeg',adeg, ...
                    'maxsteps',maxsteps, ...
                       'sphering',sphereflag, 'weights',eye(channels)); 
 
fprintf('ICA decomposition complete.\n');

% Alternatively, activations = icaact(data,weights,sphere,datamean);

fprintf('\nScaling and row-permuting the resulting performance matrix ...\n')
testid = weights*sphere*forward(:,1:channels); 
                              % if separation were complete, this
                              % would be a scaled and row-permuted 
                              % identity matrix
testresult = eyelike(testid); % permute output matrix rows to rememble eye()
                              % using Benjamin Blankertz eyeLike() - 3/2/00
% testresult = idit(testid);  % permute output matrix rows to rememble EYE
                              % scale to make max column elements all = 1

tmp = corrcoef(activations');
i = find(tmp<1);
maxoff = max(abs(tmp(i)));
minoff = min(abs(tmp(i)));
fprintf('Absolute activation correlations between %5.4f and %5.4f\n',minoff,maxoff);

i = find(testresult<1);       % find maximum abs off-diagonal value
maxoff = max(abs(testresult(i)));
meanoff = mean(abs(testresult(i)));
if sources > channels,
 fprintf('The returned matrix measures the separation');
 fprintf('of the largest %d simulated sources,\n',channels);
end

fprintf('Perfect separation would return an identity matrix.\n');
fprintf('Max absolute off-diagonal value in the returned matrix: %f\n',maxoff);
fprintf('Mean absolute off-diagonal value in the returned matrix: %f\n',meanoff);

 [corr,indx,indy,corrs] = matcorr(activations,super);

fprintf('Absolute corrs between best-matching source and activation\n');
fprintf('         component pairs range from %5.4f to %5.4f\n', ...
                                 abs(corr(1)),abs(corr(length(corr))));

if nargout == 0
   fprintf('\nView the results:\n');
   fprintf('Use mouse to rotate the image.\n');
end
if ~plotflag, return; end
figure('Position',pos+2*off);
set(gcf,'Color',BACKCOLOR);
surf(testresult);  % plot the resulting ~identity matrix
st=title('Results: Test of ICA Separation');
set(st,'fontsize',14)
sxl=xlabel('Source Out');
set(sxl,'fontsize',14);
syl=ylabel('Source In');
set(syl,'fontsize',14);
szl=zlabel('Relative Recovery');
set(szl,'fontsize',14);
view(-52,50);
axis('auto');
if max(max(abs(testresult)))>1
  fprintf('NOTE: Some sources not recovered well.\n');
  fprintf('Restricting plot z-limits to: [-1,1]\n');
  set(gca,'zlim',[-1 1]);
end
rotate3d

if nargout == 0
   testresult = [];
end
