function sTrain = som_train_struct(varargin)

%SOM_TRAIN_STRUCT Default values for SOM training parameters.
%
% sT = som_train_struct([[argID,] value, ...])
%
%  sTrain = som_train_struct(sM,sD);
%  sTrain = som_train_struct('map',sM,'data',D);
%  sTrain = som_train_struct('phase','finetune','data',D); 
%  sTrain = som_train_struct('previous',sT0);
% 
%  Input and output arguments ([]'s are optional): 
%    [argID,  (string) Several default values depend on other SOM parameters
%     value]  (varies) or on the proporties of a data set. See below for a
%                      a list of required and optional arguments for
%                      different parameters, and well as the list of valid 
%                      argIDs and associated values. The values which are 
%                      unambiguous can be given without the preceeding argID.
%
%    sT       (struct) The training struct.
%
% Training struct contains values for training and initialization
% parameters. These parameters depend on the number of training samples,
% phase of training, the training algorithm.
% 
% Here are the valid argument IDs and corresponding values. The values which
% are unambiguous (marked with '*') can be given without the preceeding rgID.
%  'dim'          (scalar) input space dimension
%  'dlen'         (scalar) length of the training data
%  'data'         (matrix / struct) the training data
%  'munits'       (scalar) number of map units
%  'msize'        (vector) map size
%  'previous'     (struct) previous training struct can be given in 
%                  conjunction with 'finetune' phase (see below) 
%  'phase'       *(string) training phase: 'init', 'train', 'rough' or 'finetune'
%  'algorithm'   *(string) algorithm to use: 'lininit', 'randinit', 'batch' or 'seq'
%  'map'         *(struct) If a map struct is given, the last training struct
%                  in '.trainhist' field is used as the previous training
%                  struct. The map size and input space dimension are 
%                  extracted from the map struct.
%  'sTrain'      *(struct) a train struct, the empty fields of which are
%                  filled with sensible values
%
% For more help, try 'type som_train_struct' or check out the helpdesk.
% See also SOM_SET, SOM_TOPOL_STRUCT, SOM_MAKE.

%%%%%%%%%%%%% DETAILED DESCRIPTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% som_train_struct
%
% PURPOSE
%
% Default values for SOM training parameters.
%
% SYNTAX
%
%  sT = som_train_struct('argID',value,...);
%  sT = som_train_struct(value,...);
%
% DESCRIPTION
%
% This function is used to give sensible values for SOM training
% parameters and returns a training struct. Often, the parameters depend on
% the properties of the map and the training data. These are given as
% optional arguments to the function. The returned struct is selected with
% the first argument which can be either an identifier string, or a
% struct. If a struct is given, only its empty fields (field value is [] or
% '' or NaN) are supplimented with default values. The already set values
% are left as they are. The set values are in setting the default values.
%
% The training struct has a number of fields which depend on each other
% and the optional arguments in complex ways. The most important argument 
% is 'phase' which can be either 'init', 'train', 'rough' or 'finetune'.
%
%  'init'     Map initialization. 
%  'train'    Map training in a onepass operation, as opposed to the
%             rough-finetune combination.
%  'rough'    Rough organization of the map: large neighborhood, big
%             initial value for learning coefficient. Short training.
%  'finetune' Finetuning the map after rough organization phase. Small
%             neighborhood, learning coefficient is small already at 
%             the beginning. Long training.
%
% The fields of training struct set by this function are listed below.
%
%  '.mask'  Basically, a column vector of ones. But if a previous
%           training struct is given, it is copied from there.
%  '.neigh' Default value is 'gaussian' but if a previous training struct 
%           is given, it is copied from there.
%  '.alpha_type' Default value is 'inv' but if a previous training struct 
%           is given, it is copied from there.
%  '.alpha_ini' Initial alpha value is basically 0.5, but if the training 
%           phase is 'finetune', the value is 1/10th of the value used in 
%           the previous training.
%  '.radius_ini' Default value is max(msize)/8, but if the previous training
%           was random initialization, it is multiplied by 2. In 
%           'finetune' phase it is 2. Minimum value is 2.
%  '.radius_fin' Default value is 1, but if the training phase is
%           'rough', the value is 1/4th of the value of '.radius_ini'.
%  '.trainlen' Basic value is 20 times the ratio 'munits/dlen' in epochs, 
%           but if the phase is 'rough', it is only 4 times that, and if the
%           phase is 'finetune', it is 4 times the training length used
%           in the previous training.
%  '.algorithm' Default training algorithm is 'batch' and default
%           initialization algorithm is 'lininit'.
%
% OPTIONAL INPUT ARGUMENTS 
%
%  argID (string) Argument identifier string (see below).
%  value (varies) Value for the argument (see below).
%
%  The optional arguments can be given as 'argID',value -pairs. If an
%  argument is given value multiple times, the last one is used.  The
%  valid IDs and corresponding values are listed below. The values
%  which are unambiguous (marked with '*') can be given without the
%  preceeding argID.
%
%  'dim'          (scalar) input space dimension
%  'dlen'         (scalar) length of the training data
%  'data'         (matrix / struct) the training data
%  'munits'       (scalar) number of map units
%  'msize'        (vector) map size
%  'previous'     (struct) previous training struct can be given in 
%                  conjunction with 'finetune' phase. 
%  'phase'       *(string) training phase: 'init', 'train', 'rough' or 'finetune'
%  'algorithm'   *(string) algorithm to use: 'lininit', 'randinit', 
%                  'batch' or 'seq'
%  'map'         *(struct) If a map struc is given, the last training struct
%                  in '.trainhist' field is used as the previous training
%                  struct. The map size and input space dimension are 
%                  extracted from the map struct.
%  'sTrain'      *(struct) a train struct, the empty fields of which are
%                  filled with sensible values
%
% OUTPUT ARGUMENTS
% 
%  sT     (struct) The training struct.
%
% EXAMPLES
%
%  The most important optional argument for the training parameters is
%  'phase'. The second most important are 'previous' and/or 'map'. 
%
%  To get default initialization parameters, use: 
%
%    sTrain = som_train_struct('phase','init');
%     or
%    sTrain = som_train_struct('init');
%
%  To get default training parameters, use: 
%
%    sTrain = som_train_struct('phase','train','data',D,'map',sMap);
%     or  
%    sTrain = som_train_struct('train','data',D,sMap);
%     or
%    sTrain = som_train_struct('train','dlen',dlen, ...
%                              'msize',sMap.topol.msize,'dim',dim);
%  
%  If you want to first rough train and then finetune, do like this: 
%
%   sT1 = som_train_struct('rough','dlen',length(D),sMap); % rough training
%   sT2 = som_train_struct('finetune','previous',sT1);     % finetuning
%
% SEE ALSO
%
%  som_make         Initialize and train a map using default parameters.
%  som_topol_struct Default map topology.
%  som_randinint    Random initialization algorithm.
%  som_lininit      Linear initialization algorithm.
%  som_seqtrain     Sequential training algorithm.
%  som_batchtrain   Batch training algorithm.

% Copyright (c) 1999 by the SOM toolbox programming team.
% http://www.cis.hut.fi/projects/somtoolbox/

% Version 2.0beta juuso 101199

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% check arguments

% initial default structs
sTrain = som_set('som_train'); 

% initialize optional parameters
dlen = NaN;
msize = 0; 
munits = NaN;
sTprev = [];
dim = NaN; 
phase = '';

% varargin
i=1; 
while i<=length(varargin), 
  argok = 1; 
  if ischar(varargin{i}), 
    switch varargin{i}, 
     case 'dim',        i=i+1; dim = varargin{i}; 
     case 'dlen',       i=i+1; dlen = varargin{i}; 
     case 'msize',      i=i+1; msize = varargin{i};
     case 'munits',     i=i+1; munits = varargin{i}; msize = 0; 
     case 'phase',      i=i+1; phase = varargin{i}; 
     case 'algorithm',  i=i+1; sTrain.algorithm = varargin{i}; 
     case 'mask',       i=i+1; sTrain.mask = varargin{i}; 
     case {'previous','map'},   
      i=i+1; 
      if strcmp(varargin{i}.type,'som_map'), 
	if length(varargin{i}.trainhist), 
	  sTprev = varargin{i}.trainhist(end); 
	  msize = varargin{i}.topol.msize;
	end
      elseif strcmp(varargin{i}.type,'som_train'), 
	sTprev = varargin{i}; 
      end
     case 'data',       
      i=i+1; 
      if isstruct(varargin{i}), [dlen dim] = size(varargin{i}.data); 
      else [dlen dim] = size(varargin{i}); 
      end
     case {'init','train','rough','finetune'},  phase = varargin{i};       
     case {'lininit','randinit','seq','batch'}, sTrain.algorithm = varargin{i}; 
     otherwise argok=0; 
    end
  elseif isstruct(varargin{i}) & isfield(varargin{i},'type'), 
    switch varargin{i}.type, 
     case 'som_train', 
      sT = varargin{i}; 
      if ~isempty(sT.algorithm),  sTrain.algorithm = sT.algorithm; end
      if ~isempty(sT.neigh),      sTrain.neigh = sT.neigh; end
      if ~isempty(sT.mask),       sTrain.mask = sT.mask; end
      if ~isnan(sT.radius_ini),   sTrain.radius_ini = sT.radius_ini; end
      if ~isnan(sT.radius_fin),   sTrain.radius_fin = sT.radius_fin; end
      if ~isnan(sT.alpha_ini),    sTrain.alpha_ini = sT.alpha_ini; end
      if ~isempty(sT.alpha_type), sTrain.alpha_type = sT.alpha_type; end
      if ~isnan(sT.trainlen),     sTrain.trainlen = sT.trainlen; end
      if ~isempty(sT.data_name),  sTrain.data_name = sT.data_name; end
      if ~isnan(sT.qerror),       sTrain.qerror = sT.qerror; end
      if ~isempty(sT.time),       sTrain.time = sT.time; end
     case 'som_map', 
      if strcmp(varargin{i}.type,'som_map'), 
	if length(varargin{i}.trainhist), 
	  sTprev = varargin{i}.trainhist(end); 
	  msize = varargin{i}.topol.msize; 
	end
      elseif strcmp(varargin{i}.type,'som_train'), 
	sTprev = varargin{i}; 
      end
     case 'som_topol', msize = varargin{i}.msize; 
     case 'som_data', [dlen dim] = size(varargin{i}.data); 
     otherwise argok=0; 
    end
  else
    argok = 0; 
  end
  if ~argok, 
    disp(['(som_train_struct) Ignoring invalid argument #' num2str(i)]); 
  end
  i = i+1; 
end

% dim
if isnan(dim) & ~isempty(sTprev), dim = length(sTprev.mask); end

% mask
if isempty(sTrain.mask) & ~isnan(dim), sTrain.mask = ones(dim,1); end

% msize, munits
if ~msize | isempty(msize), 
  if isnan(munits), msize = [10 10]; 
  else s = round(sqrt(munits)); msize = [s round(munits/s)]; 
  end
end
munits = prod(msize);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% action

% previous training
prevalg = ''; 
if ~isempty(sTprev), 
  if any(findstr(sTprev.algorithm,'init')), prevalg = 'init';
  else prevalg = sTprev.algorithm; 
  end
end

% first determine phase  
if isempty(phase), 
  switch sTrain.algorithm,
   case {'lininit','randinit'},    phase = 'init'; 
   case {'batch','seq',''}, 
    if     isempty(sTprev),        phase = 'rough'; 
    elseif strcmp(prevalg,'init'), phase = 'rough';
    else                           phase = 'finetune'; 
    end
   otherwise,                      phase = 'train'; 
  end
end

% then determine algorithm  
if isempty(sTrain.algorithm),
  if     strcmp(phase,'init'),             sTrain.algorithm = 'lininit';
  elseif any(strcmp(prevalg,{'init',''})), sTrain.algorithm = 'batch';
  else sTrain.algorithm = sTprev.algorithm; 
  end
end

% mask
if isempty(sTrain.mask), 
  if ~isempty(sTprev), sTrain.mask = sTprev.mask; 
  elseif ~isnan(dim),  sTrain.mask = ones(dim,1); 
  end
end

% neighborhood function
if isempty(sTrain.neigh), 
  if ~isempty(sTprev) & ~isempty(sTprev.neigh), sTrain.neigh = sTprev.neigh; 
  else sTrain.neigh = 'gaussian';
  end
end

if strcmp(phase,'init'), 
  sTrain.alpha_ini = NaN;
  sTrain.alpha_type = '';
  sTrain.radius_ini = NaN;
  sTrain.radius_fin = NaN;
  sTrain.trainlen = NaN;
else
  mode = [phase, '-', sTrain.algorithm];
  
  % learning rate
  a_base = 0.5; % base value
  if isnan(sTrain.alpha_ini), 
    switch mode, 
     case 'train-seq',      sTrain.alpha_ini = a_base;
     case 'rough-seq',      sTrain.alpha_ini = a_base;
     case 'finetune-seq',   sTrain.alpha_ini = a_base/10;
     case 'train-batch',    sTrain.alpha_ini = NaN;
     case 'rough-batch',    sTrain.alpha_ini = NaN;
     case 'finetune-batch', sTrain.alpha_ini = NaN;
    end
  end
  if isempty(sTrain.alpha_type),     
    if ~isempty(sTprev) & ~isempty(sTprev.alpha_type) ... 
	  & ~strcmp(sTrain.algorithm,'batch'),
      sTrain.alpha_type = sTprev.alpha_type;
    elseif strcmp(sTrain.algorithm,'seq'),
      sTrain.alpha_type = 'inv';
    end
  end
  
  % radius
  r_base = max(msize);   
  if isnan(sTrain.radius_ini), 
    switch mode, 
     case 'train-seq',      sTrain.radius_ini = ceil(r_base/8);
     case 'rough-seq',      sTrain.radius_ini = ceil(r_base/8);
     case 'finetune-seq',   sTrain.radius_ini = 2;
     case 'train-batch',    sTrain.radius_ini = ceil(r_base/8);
     case 'rough-batch',    sTrain.radius_ini = ceil(r_base/8);
     case 'finetune-batch', sTrain.radius_ini = 2;
    end    
    if ~isempty(sTprev) & strcmp(sTprev.algorithm,'randinit'), 
      sTrain.radius_ini = sTrain.radius_ini*2; 
    end
    sTrain.radius_ini = max(2,sTrain.radius_ini);
  end
  if isnan(sTrain.radius_fin), 
    switch mode, 
     case 'train-seq',      sTrain.radius_fin = 1;
     case 'rough-seq',      sTrain.radius_fin = 2; %ceil(sTrain.radius_ini/2);
     case 'finetune-seq',   sTrain.radius_fin = 1;
     case 'train-batch',    sTrain.radius_fin = 1;
     case 'rough-batch',    sTrain.radius_fin = 2;
     case 'finetune-batch', sTrain.radius_fin = 1;
    end
    sTrain.radius_fin = max(1,sTrain.radius_fin);
  end
  
  % trainlen  
  if isnan(sTrain.trainlen), 
    switch mode, 
     case 'train-seq',      sTrain.trainlen = 20;
     case 'rough-seq',      sTrain.trainlen = 4; 
     case 'finetune-seq',   sTrain.trainlen = 16;
     case 'train-batch',    sTrain.trainlen = 20;
     case 'rough-batch',    sTrain.trainlen = 4;
     case 'finetune-batch', sTrain.trainlen = 16;
    end
    sTrain.trainlen = max(1,sTrain.trainlen);
  end

end

return;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
