%PARZENDDC Parzen density based classifier for dissimilarity data
% 
%  [W,H] = PARZENDDC(D)
%  W     = PARZENDDC(D,H)
% 
% INPUT
%   D   Dissimilarity dataset
%   H   Smoothing parameters (optional; default: estimated from D)
%
% OUTPUT
%  W    Trained Parzen classifier
%  H    Smoothing parameters, estimated from the data
%
% DESCRIPTION
%
% SEE ALSO
% DATASETS, MAPPINGS, PARZEND_MAP
 
% Copyright: R.P.W. Duin, r.p.w.duin@prtools.org
% Faculty EWI, Delft University of Technology
% P.O. Box 5031, 2600 GA Delft, The Netherlands

function [W,h] = parzenddc(d,h)
  
  prtrace(mfilename);
  
  if nargin < 2, h = []; end
  
  % No input arguments: return an untrained mapping.
  if nargin == 0 | isempty(d)
    W = mapping(mfilename,h); 
    W = setname(W,'Parzen DisRep Classifier');
    return; 
  end
  
  if nargin < 2 | isempty(h);
    %prwarning(5,'Smoothing parameters not specified, estimated from the data.');
    %error('Smoothing parameter estimation not yet implemented')
    h = [];
  end

  islabtype(d,'crisp','soft');
  isvaldfile(d,2,2); % at least 2 objects per class, 2 classes
  d = testdatasize(d);
  d = testdatasize(d,'objects');
  
  [m,k,c] = getsize(d);
  nlab = getnlab(d);

  if ~isempty(h)       % Take user settings for smoothing parameters.
    
    nlab = getnlab(d);
    if length(h) == 1
      hh = repmat(h,m,1);
    elseif length(h) == c
      hh = zeros(m,1);
      for j=1:c
        J = find(nlab==j);
        hh(J) = h(j);
      end
    else
      error('Smoothing parameter has wrong size')
    end

  else   % Estimate smoothing parameters (copied and adapted from parzenc)
  
    %error('Not yet implemented')

    % compute all object distances
    D = +d;
    dim = intrdim(D);
    hinit = max(D(:)); % for sure a too high value
    D = D.^2 + diag(inf*ones(1,m));
    % find object weights q
    q = classsizes(d);
  
    % find for each object its class freqency
    of = q(nlab);
  
    % find object weights q
    p = getprior(d);
    %a = setprior(a,p);
    q = p(nlab)./q(nlab);
  
    % initialise
    h = hinit;
    %h = 2.5
    L = -inf;
    Ln = 0;
    z = 0.01^(1/dim); % initial step size

    % iterate
  
    len1 = prprogress([],'parzenddc: error optimization smoothing parameter: ');
    len2 = prprogress([],' %6.4f  %6.4f ',0,0);
	  iter = 0;
    prwaitbar(100,'parzenc: Optimizing smoothing parameter',m > 100);
    while abs(Ln-L) > 0.0001 & z < 1
      % In L we store the best performance estimate found so far.
      % Ln is the actual performance (for the actual h)
      % If Ln > L we improve the bound L, and so we rest it.
		  iter = iter+1;
		  prwaitbar(100,100-100*exp(-iter/10));
      if Ln > L, L = Ln; end

      r = -0.5/(h^2);
      F = q(ones(1,m),:)'.*exp(D*r);           % density contributions
      FS = sum(F)*((m-1)/m); IFS = find(FS>0); % joint density distribution
      if islabtype(d,'crisp');
        G = sum(F .* (nlab(:,ones(1,m)) == nlab(:,ones(1,m))'));
      else
        G = zeros(1,m);
        for j=1:c
          G = G + sum(F .* (d.targets(:,j) * d.targets(:,j)'));
        end
      end
      G = G.*(of-1)./of;                       % true-class densities
      % performance estimate, neglect zeros
    
      en = max(p)*ones(1,m);
      en(IFS) = (G(IFS))./FS(IFS);
      Ln = exp(sum(log(en+realmin))/m);
  
      closemess([],len2);
      len2 = prprogress([],' %6.4f  %6.4f ',h,Ln);

      if Ln < L            % compute next estimate
        z = sqrt(z);       % adjust stepsize up (recall: 0 < z < 1)
        h = h / z;         % if we don't improve, increase h (approach h_opt from below)
      else
        z = z*z;
        h = h * z;         % if we improve, decrease h (approach h_opt from above)
      end
      %disp([Ln,L,z,h])
    end
    closemess([],len1+len2);
    hh = h*ones(m,1);
    prwaitbar(0);

  end

  par.objects = [1:m]';
  par.prior   = getprior(d);
  par.nlab    = getnlab(d);
  par.smoothpar = hh;
  par.weights = ones(m,1);
    
  W = mapping('parzend_map','trained',par,getlablist(d),k,c);
  W = setname(W,'Parzen DisRep Classifier');
  W = setcost(W,d);

return;
