function [modeList,attr] = modes(dens,start)
%
% [modes,assoc] = modes(kde [,init]) -- Find modes of a KDE via fixed point iter. scheme
%   options:
%    init -- initial locations for search (default is kde's kernel centers)
%   returns:
%    modes -- list of estimated mode locations
%    assoc -- which mode each initial location was attracted to
%
% NOTE: this process is not guaranteed to find all modes; while it stands an
%   excellent chance for Gaussian mixtures, KDEs consisting of Ep. or Lap. kernels
%   have discontinous derivatives, leading to quite "jagged" distributions, and
%   may have many more modes than kernel centers. See M. Carreira-Perpinan's webpage,
%   http://www.cs.toronto.edu/~miguel/research/GMmodes.html, for an excellent
%   discussion.
%

% Copyright (C) 2003 Alexander Ihler; distributable under GPL -- see README.txt

if (nargin==1) start = getPoints(dens); end;

tol=1e-4; 					% set tolerance values etc. 
max_it=1000; 
minDistance = 1e-2;

pts = getPoints(dens); wts = getWeights(dens); start = start;
Ndim = size(pts,1); Npts = size(pts,2); Nloc = size(start,2);
BW = getBW(dens,1:Npts); bwMin = min(BW,[],2);
modeList = []; vals = [];

if (dens.type == 0)
  logBW = log(BW);                              % Cache log bandwidths for efficiency
end

for m=1:Nloc					% From each location given:
  x = start(:,m);				%   rename for convenience
  xTmp = x+inf;
  iter = 1;

  % FIXED POINT ITERATION TO FIND A MODE:
  while (tol < dist(x,xTmp,bwMin) && iter < max_it)	% Iterate until convergence:
    diff = pts - repmat(x,[1,Npts]);		%   get distance from kernel centers 
    xTmp = x;					%   and compute the update:

    if (dens.type == 0)				% GAUSSIAN
      K  = exp(sum(-.5*(diff./BW).^2-logBW,1)); %   compute kernel eval'n (missing 2pi)
      %K = prod(exp(-.5*(diff./BW).^2)./BW,1);	%   (slower kernel eval'n)
      px = wts * K';				%   compute kde eval  ( "" )
      x  = pts * (wts .* K)' ./ px;		%   compute recursion
    elseif (dens.type == 1)			% EPANETCHNIKOV (Mean-shift like update)
      K  = max(1-(diff./BW).^2,0)./BW;		%   compute kernel eval'n
      px = wts * prod(K,1)';			%   compute kde eval  ( "" )
      for d=1:Ndim,				%   compute update in each dimension
        Kd = wts .* prod(K([1:d-1,d+1:Ndim],:),1) .* (K~=0);
        Kd = Kd ./ sum(Kd);			%
        x(d) = pts(d,:) * Kd';			%
      end;
    else error('Sorry; KDE type not implemented');
    end;
    
    iter = iter + 1;				% increment and continue 
  end

  H = llHess(dens,x);				% Compute & 
  if max(eig(H)) < 0				% Check the Hessian: if it's
    modeList = [modeList,x]; vals = [vals,px];	%  neg. def. it's a mode; save it.
  end;

end

[tmp order] = sort(-vals);              % Sort by descending likelihd
modeList = modeList(:,order);			%
lookup=1:length(modeList); m = 1;       % Remove any redundant modes:
attr = 0*lookup; ok=[];
while (m < size(modeList,2))			%  start with "best" modes and
  ind = [m+1:size(modeList,2)];			%  work downwards:
  d = dist( modeList(:,ind), modeList(:,m+0*ind), bwMin(:,1+0*ind));
  ok  = find(d > minDistance);			%  remove any within minDistance
  modeList = modeList(:,[1:m,m+ok]); 	%   of a better mode   
  nok = find(d <= minDistance);
  attr(lookup([1,1+nok]))=m;
  lookup=lookup(1+ok);
  m = m+1;
end;
attr(lookup) = m;
attr(order) = attr;				% reverse sort operation for assoc.

function d=dist(x,y,bw)
  d = sqrt( sum( ((x-y)./bw).^2 ,1));