function dens = productExact(npd_placeholder, npdensities , analyticFns, analyticParams)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% productExact(kde,{kdes} [,{analyticFns},{analyticParams}]);
%          generate the exact density for the product of the input densities
%          this produces an N1xN2xN3x... particle density
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

  for i=1:length(npdensities)
    if (npdensities{i}.type ~= 0) error('Sorry! Product only works for Gaussian densities.'); end;
  end;
  
  Ndens = length(npdensities); Ndim = getDim(npd_placeholder); Nfns = length(analyticFns);
  Np = getNpts(npd_placeholder);    % this is supposed to be how many particles to
                                    % approximate with
  Npts = zeros(Ndens,1); ind = ones(Ndens,1); totalInd = 1;
  for i=1:Ndens, Npts(i) = getNpts(npdensities{i}); end;

  REPEAT = 1;                       % do for exponentially many product particles
  while (REPEAT),                   %  (all combos of input indices)

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %    ind'  % do what we want with these indices
    for i=1:Ndens,  % Get locations & variance (brute force, lots of repetition)
      particles(:,i) = getPoints(npdensities{i},ind(i));
      variance(:,i)  = getBW(npdensities{i},ind(i)).^2;
    end;

    iC = sum(1./variance,2);                % calculate variance and mean of
    iM = sum(particles./variance,2);        % the product from this index set
    C = 1./iC;
    M = C .* iM;
    m(:,totalInd) = M;                        %  & save them
    c(:,totalInd) = C;

    p(totalInd) = 1;
    for i=1:Ndens,  % Get weight for this combo (again brute force, wasteful)
      p(totalInd) = p(totalInd) * getWeights(npdensities{i},ind(i));
      p(totalInd) = p(totalInd) / (2*pi)^(Ndim/2) / sqrt(prod(variance(:,i)));
      p(totalInd) = p(totalInd) * exp(-.5*sum((particles(:,i)-M).^2 ./ variance(:,i)));
    end;
    p(totalInd) = p(totalInd) * (2*pi)^(Ndim/2) * sqrt(prod(C));
    for k=1:Nfns    % Evaluate analytic functions here too
      pF = feval(analyticFns{k},M,analyticParams{k}{:});
      p(totalInd)  = pF .* p(totalInd);
    end;
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    if (sum(ind) == sum(Npts)), REPEAT =0; end; % check for end of loop condition

    ind(end) = ind(end)+1;                      % otherwise advance the two counters
    totalInd = totalInd + 1;
    for i=Ndens:-1:2                            % and check for wrapping in the
      if (ind(i)>Npts(i)),                      %   index counters
        ind(i)=1; ind(i-1)=ind(i-1)+1;
      else
        break;
    end; end;
  end;
  p = p ./ sum(p);                              % normalize the weights  
  dens = kde(m,sqrt(c),p);                     % and you've got a KDE