function [hits] = som_hits(sMap, sData, mode)

%SOM_HITS Calculate the response of the given data on the map.
%
% hits = som_hits(sMap, sData, [mode])
%
%   h = som_hits(sMap,sData);
%   h = som_hits(sMap,sData,'fuzzy');
%
%  Input and output arguments ([]'s are optional): 
%   sMap     (struct) map struct
%            (matrix) codebook matrix, size munits x dim
%   sData    (struct) data struct
%            (matrix) data matrix, size dlen x dim
%   [mode]   (string) 'crisp' (default), 'kernel' or 'fuzzy'
%
%   hits     (vector) the number of hits in each map unit, length = munits
%
% The response of the data on the map can be calculated e.g. in
% three ways, selected with the mode argument: 
%  'crisp'    traditional hit histogram
%  'kernel'   a sum of dlen neighborhood kernels, where kernel
%             is positioned on the BMU of each data sample. The 
%             neighborhood function is sMap.neigh and the
%             neighborhood width is sMap.trainhist(end).radius_fin
%             or 1 if this is empty or NaN
%  'fuzzy'    fuzzy response calculated by summing 1./(1+(q/a)^2)
%             for each data sample, where q is a vector containing
%             distance from the data sample to each map unit and 
%             a is average quantization error
% 
% For more help, try 'type som_hits' or check out the helpdesk.
% See also SOM_AUTOLABEL, SOM_BMUS.    

%%%%%%%%%%%%% DETAILED DESCRIPTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% som_hits
%
% PURPOSE
%
% Calculate the response of the given data on the map.
%
% SYNTAX
%
%  hits = som_hits(sMap, sData)
%  hits = som_hits(M, D)
%  hits = som_hits(..., mode)
%
% DESCRIPTION
%
% Returns a vector indicating the response of the map to the data.
% The response of the data on the map can be calculated e.g. in
% three ways, selected with the mode argument: 
%  'crisp'    traditional hit histogram: how many times each map unit 
%             was the BMU for the data set
%  'kernel'   a sum of neighborhood kernels, where a kernel
%             is positioned on the BMU of each data sample. The 
%             neighborhood function is sMap.neigh and the
%             neighborhood width is sMap.trainhist(end).radius_fin
%             or 1 if this is not available 
%  'fuzzy'    fuzzy response calculated by summing 
%
%                            1
%                       ------------
%                       1 +  (q/a)^2
%
%             for each data sample, where q is a vector containing
%             distance from the data sample to each map unit and 
%             a is average quantization error
%
% REQUIRED INPUT ARGUMENTS
%
%   sMap              The vectors from among which the BMUs are searched
%                     for. These must not have any unknown components (NaNs).
%            (struct) map struct
%            (matrix) codebook matrix, size munits x dim
%                     
%   sData             The data vector(s) for which the BMUs are searched.
%            (struct) data struct
%            (matrix) data matrix, size dlen x dim
%
% OPTIONAL INPUT ARGUMENTS
%
%   mode     (string) The respond mode: 'crisp' (default), 'kernel'
%                     or 'fuzzy'. 'kernel' can only be used if 
%                     the first argument (sMap) is a map struct.                     
% 
% OUTPUT ARGUMENTS
% 
%   hits     (vector) The number of hits in each map unit.
%
% EXAMPLES
%
%  hits = som_hits(sM,D);
%  hits = som_hits(sM,D,'kernel');
%  hits = som_hits(sM,D,'fuzzy');
%
% SEE ALSO
% 
%  som_bmus      Find BMUs and quantization errors for a given data set.

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

% Version 1.0beta juuso 220997
% Version 2.0beta juuso 161199

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

error(nargchk(2, 3, nargin));  % check no. of input args is correct

if isstruct(sMap), 
  switch sMap.type, 
   case 'som_map', munits = prod(sMap.topol.msize);
   case 'som_data', munits = size(sMap.data,1);
   otherwise, 
    error('Illegal struct for 1st argument.')
  end
else 
  munits = size(sMap,1); 
end
hits = zeros(munits,1);

if nargin<3, mode = 'crisp'; end

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

% calculate BMUs
[bmus,qerrs] = som_bmus(sMap,sData,1);

switch mode, 
case 'crisp',    

 % for each unit, check how many hits it got
 for i=1:munits, hits(i) = sum(bmus == i); end
   
case 'kernel',

 % check that sMap really is a map 
 if ~isstruct(sMap) & ~strcmp(sMap.type,'som_map'), 
   error('Kernel mode can only be used for maps.');
 end	   

 % calculate neighborhood kernel  
 Ud = som_unit_dists(sMap.topol).^2;
 sTrain = sMap.trainhist(end); 
 if ~isempty(sTrain), 
   rad = sTrain.radius_fin; 
   if isempty(rad) | isnan(rad), rad = 1; end 
 else 
   rad = 1; 
 end    
 rad = rad^2;
 switch sTrain.neigh, 
  case 'bubble',   H = (Ud<=rad); 
  case 'gaussian', H = exp(-Ud/(2*rad)); 
  case 'cutgauss', H = exp(-Ud/(2*rad)) .* (Ud<=rad);
  case 'ep',       H = (1-Ud/rad) .* (Ud<=rad);
 end
 
 % weight hits with neighborhood kernel
 hits = sum(H(bmus,:),1)';
   
case 'fuzzy',	

 % extract the two matrices (M, D) and the mask
 mask = [];
 if isstruct(sMap), 
   if strcmp(sMap.type,'som_data'), M = sMap.data; 
   else M = sMap.codebook; mask = sMap.mask;
   end
 else M = sMap;
 end
 if any(isnan(M(:))), 
   error('Data in first argument must not have any NaNs.'); 
 end

 if isstruct(sData), 
   switch sData.type, 
    case 'som_map', 
     D = sData.codebook; 
     if isempty(mask), mask = sData.mask; end
    case 'som_data', D = sData.data;
    otherwise, error('Illegal 2nd argument.');
   end	
 else D = sData;
 end
 [dlen dim] = size(D);   
 if isempty(mask), mask = ones(dim,1); end

 % scaling factor   
 a = mean(qerrs).^2;
 
 % calculate distances & bmus
 % (this is better explained in som_batchtrain and som_bmus)
 Known = ~isnan(D); D(find(~Known)) = 0; % unknown components  
 blen = min(munits,dlen); % block size 
 W1 = mask*ones(1,blen); W2 = ones(munits,1)*mask'; D = D'; Known = Known';
 i0 = 0; 
 while i0+1<=dlen,    
   inds = [(i0+1):min(dlen,i0+blen)]; i0 = i0+blen; % indeces   
   Dist = (M.^2)*(W1(:,1:length(inds)).*Known(:,inds)) ...
	  + W2*(D(:,inds).^2) ...
	  - 2*M*diag(mask)*D(:,inds); % squared distances
   hits = hits + sum(1./(1+Dist/a),2);   
 end  
 
 otherwise, 
  error(['Unknown mode: ' mode]);
  
end

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

