function Asup = mg_ns_cd_setup(x,y,viscosity,flowsol, domain,lncoarse,lnfine,outbnd)
%mg_ns_cd_setup GMG convection-diffusion problem (Navier-Stokes)
%   Asup = mg_ns_cd_setup(x,y,viscosity,flowsol, domain,lncoarse,lnfine,outbnd)
%   input
%          x            x grid coordinates
%          y            y grid coordinates
%          viscosity    viscosity in NS equations
%          flowsol      current discrete velocity field
%          domain       domain parameter, 1 for cavity, 3 for step
%          lncoarse     coarse grid index, log2(nc) for nc x nc square grid
%          lnfine       finest grid index, log2(nf) for nf x nf square grid
%          outbnd       location of outflow boundary
%   output
%          Asup         discrete convection-diffusion operator for grid
%                       of level lncoarse
%
%   IFISS function: HCE; 18 March 2005.
% Copyright (c) 2005 D.J. Silvester, H.C. Elman, A. Ramage

% adaptation of AR mg_cd_setup allowing convection coefficients to come
% from flowsol rather than function call

if domain==1,
   n=length(x)-1; np=n/2; nq=n/4;
   nvtx=(n+1)*(n+1);
   [X,Y]=meshgrid(x,y);
   xx=reshape(X',nvtx,1);
   yy=reshape(Y',nvtx,1);
   xy=[xx(:),yy(:)];
% assembly process
   kx = 1;
   ky = 1;
   mel=0;
   for j=1:np
      for i=1:np
         mref=(n+1)*(ky-1)+kx;
         pref=(np+1)*(j-1)+i;
         mel=mel+1;
         nvv(1) = mref;
         nvv(2) = mref+2;
         nvv(3) = mref+2*n+4;
         nvv(4) = mref+2*n+2;
         nvv(5) = mref+1;
         nvv(6) = mref+n+3;
         nvv(7) = mref+2*n+3;
         nvv(8)=  mref+n+1;
         nvv(9)=  mref+n+2;
         npp(1) = pref;
         npp(2) = pref+1;
         npp(3) = pref+np+2;
         npp(4) = pref+np+1;
         mv(mel,1:9)=nvv(1:9);
         mp(mel,1:4)=npp(1:4);
         kx = kx + 2;
      end
      ky = ky + 2;
      kx = 1;
   end
%% compute boundary vertices and edges (four boundary edges)
   k1=find( xy(:,2)==-1 );
   e1=[]; for k=1:mel, if any(mv(k,5)==k1), e1=[e1,k]; end, end
   ef1=ones(size(e1));
%
   k2=find( xy(:,1)==1  & xy(:,2)<1   & xy(:,2) >-1);
   e2=[]; for k=1:mel, if any(mv(k,6)==k2), e2=[e2,k]; end, end
   ef2=2*ones(size(e2));
%
   k3=find( xy(:,2)==1 );
   e3=[]; for k=1:mel, if any(mv(k,7)==k3), e3=[e3,k]; end, end
   ef3=3*ones(size(e3));
%
   k4=find( xy(:,1)==-1 & xy(:,2)<1   & xy(:,2) >-1 );
   e4=[]; for k=1:mel, if any(mv(k,8)==k4), e4=[e4,k]; end, end
   ef4=4*ones(size(e4));
%
   bound=sort([k1;k2;k3;k4]);
   mbound=[e1',ef1';e2',ef2';e3',ef3';e4',ef4'];

elseif domain==3,
   n=length(y)-1; np=n/2; nq=n/4;
   nvtx= np*(np*1) + (outbnd*np+1)*(n+1);
% negative x-values
   xneg=x(1:n/2);yneg=y(1:n/2);
   xpos=x(n/2+1:end);ypos=y(n/2+1:end);
   [Xneg,Ypos]=meshgrid(xneg,ypos);
   xx=reshape(Xneg',np*(np+1),1);
   yy=reshape(Ypos',np*(np+1),1);
   xyleft=[xx(:),yy(:)];

% assembly process
   kx = 1;
   ky = 1;
   mel=0;
% loop over 2x2 macroelements
   for j=1:nq
      for i=1:nq
         mref=np*(ky-1)+kx;
         pref=nq*(j-1)+i;
         mel=mel+1;
         nvv(1) = mref;
         nvv(2) = mref+2;
         nvv(3) = mref+2*np+2;
         nvv(4) = mref+2*np;
         nvv(5) = mref+1;
         nvv(6) = mref+np+2;
         nvv(7) = mref+2*np+1;
         nvv(8)=  mref+np;
         nvv(9)=  mref+np+1;
         npp(1) = pref;
         npp(2) = pref+1;
         npp(3) = pref+nq+1;
         npp(4) = pref+nq;
         mv(mel,1:9)=nvv(1:9);
         mp(mel,1:4)=npp(1:4);
         kx = kx + 2;
      end
      ky = ky + 2;
      kx = 1;
   end

% correction along the internal boundary
   mref=2*np*(((outbnd+1)/2)*np+1)+1;
   pref=2*nq*(((outbnd+1)/2)*nq+1)+1;
   for mel=nq:nq:nq*nq;
      nvv=mv(mel,:);
      npp=mp(mel,:);
      nvv(2) = mref;
      nvv(3) = mref+(2*outbnd)*np+2;
      nvv(6) = mref+outbnd*np+1;
      npp(2) = pref;
      npp(3) = pref+outbnd*nq+1;
      mv(mel,1:9)=nvv(1:9);
      mp(mel,1:4)=npp(1:4);
      mref=mref+(2*outbnd)*np+2;
      pref=pref+outbnd*nq+1;
   end
% positive x_values
   [Xpos,Y]=meshgrid(xpos,y);
   xx=reshape(Xpos',(outbnd*np+1)*(n+1),1);
   yy=reshape(Y',(outbnd*np+1)*(n+1),1);
   xyright=[xx(:),yy(:)];
   xy=[xyleft;xyright];
%
   kx = 1;
   ky = 1;
   mel=nq*nq;
   for j=1:np
      for i=1:outbnd*nq
         mref = (outbnd*np+1)*(ky-1)+kx + np*(np+1);
         pref = (outbnd*nq+1)*(j-1)+i +  nq*(nq+1);
         mel=mel+1;
         nvv(1) = mref;
         nvv(2) = mref+2;
         nvv(3) = mref+(2*outbnd)*np+4;
         nvv(4) = mref+(2*outbnd)*np+2;
         nvv(5) = mref+1;
         nvv(6) = mref+outbnd*np+3;
         nvv(7) = mref+(2*outbnd)*np+3;
         nvv(8)=  mref+outbnd*np+1;
         nvv(9)=  mref+outbnd*np+2;
         npp(1) = pref;
         npp(2) = pref+1;
         npp(3) = pref+outbnd*nq+2;
         npp(4) = pref+outbnd*nq+1;
         mv(mel,1:9)=nvv(1:9);
         mp(mel,1:4)=npp(1:4);
         kx = kx + 2;
      end
      ky = ky + 2;
      kx = 1;
   end

%% compute boundary vertices and edges (five boundary edges)
   k1=find( xy(:,1) <0  & xy(:,2)==0 );
   e1=[]; for k=1:mel, if any(mv(k,5)==k1), e1=[e1,k]; end, end
   ef1=ones(size(e1));
%
   k2=find( xy(:,1)==0  & xy(:,2)<=0 );
   e2=[]; for k=1:mel, if any(mv(k,8)==k2), e2=[e2,k]; end, end
   ef2=4*ones(size(e2));
%
   k3=find( xy(:,1) >0  & xy(:,2)==-1);
   e3=[]; for k=1:mel, if any(mv(k,5)==k3), e3=[e3,k]; end, end
   ef3=ones(size(e3));
%
   k5=find( xy(:,2)==1 );
   e5=[]; for k=1:mel, if any(mv(k,7)==k5), e5=[e5,k]; end, end
   ef5=3*ones(size(e5));
%
   k6=find( xy(:,1)==-1 & xy(:,2)<1   & xy(:,2) >0 );
   e6=[]; for k=1:mel, if any(mv(k,8)==k6), e6=[e6,k]; end, end
   ef6=4*ones(size(e6));
%
   bound=sort([k1;k2;k3;k5;k6]);
   mbound=[e1',ef1';e2',ef2';e3',ef3';;e5',ef5';e6',ef6'];
end

%
%% set up matrices for Q1 approximation
[ev,ebound]=mg_q1grid(x,y,xy,mv,bound,mbound);
[A,N,M,epe,eph,epw,usolc,vsolc] = mg_ns_q1cd(xy,ev,flowsol,domain,lncoarse,lnfine,outbnd);
% SUPG
supg=0;
A = viscosity*A + N;
%% compute element peclet numbers
epe = epe/viscosity;
%% include streamline diffusion matrix (if necessary)
esupg=find(epe<=1); expe=epe;
if any(expe),
   expe=0.5*(1-1./expe);
   expe(esupg)=inf;
   epp=expe; epp(esupg)=0; epp=epp.*eph./epw;
   S=mg_ns_q1cd_supg(xy,ev,expe,eph,epw,usolc,vsolc);A=A+S;
end
%
%% impose zero boundary conditions
Asup = mg_zerobc(A,xy,bound);


function rez = set_cutoff(rez)
% after everything else is done, this function takes spike trains and cuts off
% any noise they might have picked up at low amplitude values
% We look for bimodality in the amplitude plot, thus setting an individual threshold
% for each neuron.
% Also, this function calls "good" and "bad" clusters based on the auto-correlogram

ops = rez.ops;
dt = 1/1000; % step size for CCG binning

Nk = numel(rez.mu); % number of templates

% sort by firing rate first
rez.good = zeros(Nk, 1);
rez.est_contam_rate = ones(Nk, 1);

for j = 1:Nk
    ix = find(rez.st3(:,2)==j); % find all spikes from this neuron
    ss = rez.st3(ix,1)/ops.fs; % convert to seconds
    if numel(ss)==0
        continue; % break if there are no spikes
    end

    vexp = rez.st3(ix,4); % vexp is the relative residual variance of the spikes

    Th = ops.Th(1); % start with a high threshold

    fcontamination = 0.1; % acceptable contamination rate

    rez.est_contam_rate(j) = 1;
    while Th>=ops.Th(2)
      % continually lower the threshold, while the estimated unit contamination is low
        st = ss(vexp>Th); % take spikes above the current threshold
        if isempty(st)
            Th = Th - .5; % if there are no spikes, we need to keep lowering the threshold
            continue;
        end
        [K, Qi, Q00, Q01, rir] = ccg(st, st, 500, dt); % % compute the auto-correlogram with 500 bins at 1ms bins
        Q = min(Qi/(max(Q00, Q01))); % this is a measure of refractoriness
        R = min(rir); % this is a second measure of refractoriness (kicks in for very low firing rates)
        if Q>fcontamination || R>.05 % if the unit is already contaminated, we break, and use the next higher threshold
           break;
        else
            if Th==ops.Th(1) && Q<.05
              % only on the first iteration, we consider if the unit starts well isolated
              % if it does, then we put much stricter criteria for isolation
              % to make sure we don't settle for a relatively high contamination unit
                fcontamination = min(.05, max(.01, Q*2));
%                 fcontamination = .05;

                % if the unit starts out contaminated, we will settle with the higher contamination rate
            end
            rez.good(j) = 1; % this unit is good, because we will stop lowering the threshold when it becomes bad
            Th = Th - .5; % try the next lower threshold
        end
    end
    Th = Th + .5; % we exited the loop because the contamination was too high. We revert to the higher threshold
    st = ss(vexp>Th); % take spikes above the current threshold
    if isempty(st)
        rez.est_contam_rate(j) = 1;
    else
        [K, Qi, Q00, Q01, rir] = ccg(st, st, 500, dt); % % compute the auto-correlogram with 500 bins at 1ms bins
        Q = min(Qi/(max(Q00, Q01))); % this is a measure of refractoriness
        rez.est_contam_rate(j) = Q; % this score will be displayed in Phy
    end

    rez.Ths(j) = Th; % store the threshold for potential debugging
    rez.st3(ix(vexp<=Th), 2) = 0; % any spikes below the threshold get discarded into a 0-th cluster.
end

% we sometimes get NaNs, why? replace with full contamination
rez.est_contam_rate(isnan(rez.est_contam_rate)) = 1;

% remove spikes from the 0th cluster
rez = remove_spikes(rez,rez.st3(:,2)==0,'below_cutoff');


function [g, gdata, gprior] = gp_g(w, gp, x, y, varargin)
%GP_G  Evaluate the gradient of energy (GP_E) for Gaussian Process
%
%  Description
%    G = GP_G(W, GP, X, Y, OPTIONS) takes a full GP parameter
%    vector W, GP structure GP, a matrix X of input vectors and a
%    matrix Y of target vectors, and evaluates the gradient G of
%    the energy function (gp_e). Each row of X corresponds to one
%    input vector and each row of Y corresponds to one target
%    vector.
%
%    [G, GDATA, GPRIOR] = GP_G(W, GP, X, Y, OPTIONS) also returns
%    separately the data and prior contributions to the gradient.
%
%    OPTIONS is optional parameter-value pair
%      z - optional observed quantity in triplet (x_i,y_i,z_i)
%          Some likelihoods may use this. For example, in case of
%          Poisson likelihood we have z_i=E_i, that is, expected
%          value for ith case.
%
%  See also
%    GP_E, GP_PAK, GP_UNPAK, GPCF_*
%

% Copyright (c) 2007-2011 Jarno Vanhatalo
% Copyright (c) 2010 Aki Vehtari
% Copyright (c) 2010 Heikki Peura

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

if isfield(gp,'latent_method') && ~strcmp(gp.latent_method,'MCMC')
  % use an inference specific method
  fh_g = gp.fh.g;
  switch nargout
    case {0 1}
      [g] = fh_g(w, gp, x, y, varargin{:});
    case 2
      [g, gdata] = fh_g(w, gp, x, y, varargin{:});
    case 3
      [g, gdata, gprior] = fh_g(w, gp, x, y, varargin{:});
  end
  return
end

ip=inputParser;
ip.FunctionName = 'GP_G';
ip.addRequired('w', @(x) isvector(x) && isreal(x));
ip.addRequired('gp',@isstruct);
ip.addRequired('x', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addRequired('y', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addParamValue('z', [], @(x) isreal(x) && all(isfinite(x(:))))
ip.parse(w, gp, x, y, varargin{:});
z=ip.Results.z;
if ~all(isfinite(w(:)));
  % instead of stopping to error, return NaN
  g=NaN;
  gdata = NaN;
  gprior = NaN;
  return;
end

% unpak the parameters
gp=gp_unpak(gp, w);
ncf = length(gp.cf);
if isfield(gp.lik, 'nondiagW')
  % Likelihoods with non-diagonal Hessian
  switch gp.lik.type
    case {'LGP', 'LGPC'}
      % Do nothing
    case {'Softmax', 'Multinom'}
      n=size(x,1);
      nout=size(y(:),1)./n;
%       [n,nout]=size(y);
      nl=cumsum([0 repmat(n,1,nout)]);
    otherwise
      n=size(x,1);
      nout=length(gp.comp_cf);

      % Help indices for latent processes
      if ~isfield(gp.lik, 'xtime')
        nl=[0 repmat(n,1,nout)];
      else
        xtime=gp.lik.xtime;
        ntime=size(xtime,1);
        nl=[0 ntime n];
      end
      nl=cumsum(nl);
  end
  if isfield(gp, 'comp_cf')  % own covariance for each ouput component
    multicf = true;
    if length(gp.comp_cf) ~= nout
      error('GP2_G: the number of component vectors in gp.comp_cf must be the same as number of outputs.')
    end
  else
    multicf = false;
  end
else
  n=size(x,1);
end

g = [];
gdata = [];
gprior = [];

if isfield(gp,'savememory') && gp.savememory
  savememory=1;
else
  savememory=0;
end

switch gp.type
  case 'FULL'
    % ============================================================
    % FULL
    % ============================================================

    if ~isfield(gp.lik, 'nondiagW') || ismember(gp.lik.type, {'LGP' 'LGPC'})
      % Evaluate covariance
      [K, C] = gp_trcov(gp,x);

      if issparse(C)
        % evaluate the sparse inverse
        [LD, notpositivedefinite] = ldlchol(C);
        invC = spinv(LD,1);
        if notpositivedefinite
          % instead of stopping to chol error, return NaN
          g=NaN;
          gdata = NaN;
          gprior = NaN;
          return;
        end
        if  ~isfield(gp,'meanf')
          b = ldlsolve(LD,y);
        else
          [invNM invAt HinvC]=mean_gf(gp,x,C,LD,[],[],y,'gaussian');
        end
      else
        % evaluate the full inverse
        ws1=warning('off','MATLAB:nearlySingularMatrix');
        ws2=warning('off','MATLAB:SingularMatrix');
        invC = inv(C);
        if  ~isfield(gp,'meanf')
          b = C\y;
        else
          [invNM invAt HinvC]=mean_gf(gp,x,C,invC,[],[],y,'gaussian');
        end
        warning(ws1);
        warning(ws2);
      end
    else
      b = zeros(nl(end),1);
      y=y(:);

      switch gp.lik.type
        case 'Coxph'
          % In Cox-Ph, latent processes have different inputs so stacking in invC
          % is not possible.
          invC = zeros(nl(end),nl(end));
          if multicf
            [tmp, C] = gp_trcov(gp, xtime, gp.comp_cf{1});
            invC(1+nl(1):nl(2),1+nl(1):nl(2)) = inv(C);
            b(nl(1)+1:nl(2)) = C\y(nl(1)+1:nl(2));
            [tmp, C] = gp_trcov(gp, x, gp.comp_cf{2});
            invC(1+nl(2):nl(3),1+nl(2):nl(3)) = inv(C);
            b(1+nl(2):nl(3)) = C\y(1+nl(2):nl(3));
          else
            error('Specify covariance function for time process and input process, when using Cox-Ph likelihood');
          end
        otherwise
          invC = zeros(n,n,nout);
          if multicf
            for i1=1:nout
              [tmp, C] = gp_trcov(gp, x, gp.comp_cf{i1});
              invC(:,:,i1) = inv(C);
              b(1+nl(i1):nl(i1+1)) = C\y(1+nl(i1):nl(i1+1));
              %             b(:,i1) = C\y(:,i1);
            end
          else
            [tmp, C] = gp_trcov(gp, x);
            invCtmp = inv(C);
            for i1=1:nout
              invC(:,:,i1) = invCtmp;
              b(1+nl(i1):nl(i1+1)) = C\y(1+nl(i1):nl(i1+1));
              %             b(:,i1) = C\y(:,i1);
            end
          end
      end
    end

    % =================================================================
    % Gradient with respect to covariance function parameters
    i1=0;
    if ~isempty(strfind(gp.infer_params, 'covariance'))
      for i=1:ncf
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        gpcf = gp.cf{i};

        if isfield(gp.lik, 'nondiagW') && ~ismember(gp.lik.type, {'LGP' 'LGPC'})
          % check in which components the covariance function is present
          % for likelihoods with non-diagonal Hessian
          do = false(nout,1);
          if multicf
            for z1=1:nout
              if any(gp.comp_cf{z1}==i)
                do(z1) = true;
              end
            end
          else
            do = true(nout,1);
          end
        end

        if ~(isfield(gp,'derivobs') && gp.derivobs)
          % No derivative observations
          if ~savememory
            if isfield(gp.lik, 'nondiagW') && isfield(gp,'comp_cf') && ~isempty(intersect(gp.comp_cf{1},i)) && isfield(gp.lik, 'xtime')
              DKffc = gpcf.fh.cfg(gpcf, xtime);
            else
              DKffc = gpcf.fh.cfg(gpcf, x);
            end
            np=length(DKffc);
          else
            % If savememory option is used, just get the number of
            % hyperparameters and calculate gradients later
            np=gpcf.fh.cfg(gpcf,[],[],[],0);
          end
          gprior_cf = -gpcf.fh.lpg(gpcf);
        else
          [n m]=size(x);
          %Case: input dimension is 1
          if m==1

            DKffa = gpcf.fh.cfg(gpcf, x);
            DKdf = gpcf.fh.cfdg(gpcf, x);
            DKdd = gpcf.fh.cfdg2(gpcf, x);
            gprior_cf = -gpcf.fh.lpg(gpcf);

            % DKff{1} -- d K / d magnSigma2
            % DKff{2} -- d K / d lengthScale
            DKffc{1} = [DKffa{1}, DKdf{1}'; DKdf{1}, DKdd{1}];
            DKffc{2} = [DKffa{2}, DKdf{2}'; DKdf{2}, DKdd{2}];
            np=2;

            %Case: input dimension is >1
          else
            DKffa = gpcf.fh.cfg(gpcf, x);
            DKdf = gpcf.fh.cfdg(gpcf, x);
            DKdd = gpcf.fh.cfdg2(gpcf, x);
            gprior_cf = -gpcf.fh.lpg(gpcf);

            %Check whether ARD method is in use (with gpcf_sexp)
            Ard=length(gpcf.lengthScale);

            % DKff{1} - d K / d magnSigma2
            % DKff{2:end} - d K / d lengthScale(1:end)
            for i=1:2
              DKffc{i}=[DKffa{i} DKdf{i}';DKdf{i} DKdd{i}];
            end

            %If ARD is in use
            if Ard>1
              for i=2+1:2+Ard-1
                DKffc{i}=[DKffa{i} DKdf{i}';DKdf{i} DKdd{i}];
              end
            end
            np=length(DKffc);
          end
        end

        % Are there specified mean functions
        if  ~isfield(gp,'meanf')
          % Evaluate the gradient with respect to covariance function
          % parameters
          for i2 = 1:np
            if savememory
              if isfield(gp.lik, 'nondiagW') && isfield(gp,'comp_cf') && ~isempty(intersect(gp.comp_cf{1},i)) && isfield(gp.lik, 'xtime')
                DKff=gpcf.fh.cfg(gpcf,xtime,[],[],i2);
              else
                DKff=gpcf.fh.cfg(gpcf,x,[],[],i2);
              end
            else
              DKff=DKffc{i2};
            end
            i1 = i1+1;
            if ~isfield(gp.lik, 'nondiagW')
              Bdl = b'*(DKff*b);
              Cdl = sum(sum(invC.*DKff)); % help arguments
            else
              % Non-diagonalizable likelihoods
              Bdl=0; Cdl=0;
              if isfield(gp.lik,'xtime');
                if do(1)
                  Bdl = Bdl + b(1:ntime)'*(DKff*b(1:ntime));
                  Cdl = Cdl + sum(sum(invC(1:ntime,1:ntime).*DKff)); % help arguments
                end
                if do(2)
                  Bdl = Bdl + b(ntime+1:end)'*(DKff*b(ntime+1:end));
                  Cdl = Cdl + sum(sum(invC(ntime+1:end,ntime+1:end).*DKff)); % help arguments
                end
              else
                for z1=1:nout
                  if do(z1)
                    Bdl = Bdl + b(1+nl(z1):nl(z1+1))'*(DKff*b(1+nl(z1):nl(z1+1)));
                    Cdl = Cdl + sum(sum(invC(:,:,z1).*DKff)); % help arguments
                  end
                end
              end
            end
            gdata(i1)=0.5.*(Cdl - Bdl);
            gprior(i1) = gprior_cf(i2);
          end
        else
          for i2 = 1:np
            if savememory
              DKff=gpcf.fh.cfg(gpcf,x,[],[],i2);
            else
              DKff=DKffc{i2};
            end
            i1=i1+1;
            dA = -1*HinvC*DKff*HinvC';                  % d A / d th
            trA = sum(invAt(:).*dA(:));                 % d log(|A|) / dth
            dMNM = invNM'*(DKff*invNM);           % d M'*N*M / d th
            trK = sum(sum(invC.*DKff));       % d log(Ky�?�) / d th
            gdata(i1)=0.5*(-1*dMNM + trK + trA);
            gprior(i1) = gprior_cf(i2);
          end
        end

        % Set the gradients of hyperparameter
        if length(gprior_cf) > np
          for i2=np+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        if ~isfield(gp,'meanf')
          if size(DCff{i2}) > 1
            yKy = b'*(DCff{i2}*b);
            trK = sum(sum(invC.*DCff{i2})); % help arguments
            gdata_zeromean(i1)=0.5.*(trK - yKy);
          else
            yKy=DCff{i2}.*(b'*b);
            trK = DCff{i2}.*(trace(invC));
            gdata_zeromean(i1)=0.5.*(trK - yKy);
          end
          gdata(i1)=gdata_zeromean(i1);
        else
          if size(DCff{i2}) > 1
            trK = sum(sum(invC.*DCff{i2})); % help arguments
          else
            trK = DCff{i2}.*(trace(invC));
          end
          dA = -1*HinvC*DCff{i2}*HinvC';            % d A / d th
          trA = sum(invAt(:).*dA(:));               % d log(|A|) / dth
          dMNM = invNM'*(DCff{i2}*invNM);           % d M'*N*M / d th
          gdata(i1)=0.5*(-1*dMNM + trA + trK);
        end
        gprior(i1) = gprior_lik(i2);
      end

      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    if ~isempty(strfind(gp.infer_params, 'mean')) && isfield(gp,'meanf')
        notpositivedefinite2 = 0; notpositivedefinite3 = 0;

        nmf=numel(gp.meanf);
        [H,b,B]=mean_prep(gp,x,[]);
        M = H'*b-y;

        if issparse(C)
            [LD, notpositivedefinite] = ldlchol(C);
            if ~notpositivedefinite
                KH = ldlsolve(LD, H');
            end
            [LB, notpositivedefinite2] = chol(B);
            if ~notpositivedefinite2
                A = LB\(LB'\eye(size(B))) + H*KH;
                LA = chol(A);
                a = ldlsolve(LD, M) - KH*(LA\(LA'\(KH'*M)));
                iNH = ldlsolve(LD, H') - KH*(LA\(LA'\(KH'*H')));
            end
        else
            N = C + H'*B*H;
            [LN, notpositivedefinite3] = chol(N);
            if ~notpositivedefinite3
                a = LN\(LN'\M);
                iNH = LN\(LN'\H');
            end
        end
        if (~notpositivedefinite2 && ~notpositivedefinite3)
            Ha=H*a;
            g_bb = (-H*a)';     % b and B parameters are log transformed in packing
            indB = find(B>0);
            for i=1:length(indB)
                Bt = zeros(size(B)); Bt(indB(i))=1;
                BH = Bt*H;
                g_B(i) = 0.5* ( Ha'*Bt*Ha - sum(sum(iNH.*(BH'))) );
            end
            g_BB = g_B.*B(indB)';
            for i=1:nmf
                gpmf = gp.meanf{i};
                [lpg_b, lpg_B] = gpmf.fh.lpg(gpmf);
                ll=length(lpg_b);
                gdata = [gdata -g_bb((i-1)*ll+1:i*ll)];
                gprior = [gprior -lpg_b];
                ll=length(lpg_B);
                gdata = [gdata -g_B((i-1)*ll+1:i*ll)];
                gprior = [gprior -lpg_B];
            end
        else
          g=NaN;
          gdata = NaN;
          gprior = NaN;
          return
        end
    end

    g = gdata + gprior;

  case 'FIC'
    % ============================================================
    % FIC
    % ============================================================
    g_ind = zeros(1,numel(gp.X_u));
    gdata_ind = zeros(1,numel(gp.X_u));
    gprior_ind = zeros(1,numel(gp.X_u));

    u = gp.X_u;
    DKuu_u = 0;
    DKuf_u = 0;

    % First evaluate the needed covariance matrices
    % v defines that parameter is a vector
    [Kv_ff, Cv_ff] = gp_trvar(gp, x);  % 1 x f  vector
    K_fu = gp_cov(gp, x, u);         % f x u
    K_uu = gp_trcov(gp, u);          % u x u, noiseles covariance K_uu
    K_uu = (K_uu+K_uu')./2;          % ensure the symmetry of K_uu
    [Luu, notpositivedefinite] = chol(K_uu,'lower');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    % Evaluate the Lambda (La)
    % Q_ff = K_fu*inv(K_uu)*K_fu'
    % Here we need only the diag(Q_ff), which is evaluated below
    B=Luu\(K_fu');
    Qv_ff=sum(B.^2)';
    Lav = Cv_ff-Qv_ff;   % 1 x f, Vector of diagonal elements
                         % iLaKfu = diag(inv(Lav))*K_fu = inv(La)*K_fu
    iLaKfu = zeros(size(K_fu));  % f x u,
    for i=1:n
      iLaKfu(i,:) = K_fu(i,:)./Lav(i);  % f x u
    end
    % ... then evaluate some help matrices.
    % A = K_uu+K_uf*inv(La)*K_fu
    A = K_uu+K_fu'*iLaKfu;
    A = (A+A')./2;               % Ensure symmetry
    [A, notpositivedefinite] = chol(A,'upper');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    L = iLaKfu/A;
    b = y'./Lav' - (y'*L)*L';
    iKuuKuf = Luu'\(Luu\K_fu');
    La = Lav;
    LL = sum(L.*L,2);

    % =================================================================
    % Gradient with respect to covariance function parameters
    if ~isempty(strfind(gp.infer_params, 'covariance'))
      % Loop over the covariance functions
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        % Get the gradients of the covariance matrices
        % and gprior from gpcf_* structures
        gpcf = gp.cf{i};
        if savememory
          % If savememory option is used, just get the number of
          % hyperparameters and calculate gradients later
          np=gpcf.fh.cfg(gpcf,[],[],[],0);
        else
          DKffc = gpcf.fh.cfg(gpcf, x, [], 1);
          DKuuc = gpcf.fh.cfg(gpcf, u);
          DKufc = gpcf.fh.cfg(gpcf, u, x);
          np=length(DKffc);
        end
        gprior_cf = -gpcf.fh.lpg(gpcf);

        for i2 = 1:np
          i1 = i1+1;
          if savememory
            DKff=gpcf.fh.cfg(gpcf,x,[],1,i2);
            DKuu=gpcf.fh.cfg(gpcf,u,[],[],i2);
            DKuf=gpcf.fh.cfg(gpcf,u,x,[],i2);
          else
            DKff=DKffc{i2};
            DKuu=DKuuc{i2};
            DKuf=DKufc{i2};
          end
          KfuiKuuKuu = iKuuKuf'*DKuu;
          gdata(i1) = -0.5.*((2*b*DKuf'-(b*KfuiKuuKuu))*(iKuuKuf*b') + 2.*sum(sum(L'.*(L'*DKuf'*iKuuKuf))) - ...
                             sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));

          gdata(i1) = gdata(i1) - 0.5.*(b.*DKff')*b';
          gdata(i1) = gdata(i1) + 0.5.*(2.*b.*sum(DKuf'.*iKuuKuf',2)'*b'- b.*sum(KfuiKuuKuu.*iKuuKuf',2)'*b');
          gdata(i1) = gdata(i1) + 0.5.*(sum(DKff./La) - sum(LL.*DKff));
          gdata(i1) = gdata(i1) + 0.5.*(2.*sum(LL.*sum(DKuf'.*iKuuKuf',2)) - sum(LL.*sum(KfuiKuuKuu.*iKuuKuf',2)));
          gprior(i1) = gprior_cf(i2);
        end

        % Set the gradients of hyperparameter
        if length(gprior_cf) > np
          for i2=length(DKff)+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        gdata(i1)= -0.5*DCff{i2}.*b*b';
        gdata(i1)= gdata(i1) + 0.5*sum(DCff{i2}./La-sum(L.*L,2).*DCff{i2});
        gprior(i1) = gprior_lik(i2);
      end
      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    % =================================================================
    % Gradient with respect to inducing inputs
    if ~isempty(strfind(gp.infer_params, 'inducing'))
      if isfield(gp.p, 'X_u') && ~isempty(gp.p.X_u)
        m = size(gp.X_u,2);
        st=0;
        if ~isempty(gprior)
          st = length(gprior);
        end

        gdata(st+1:st+length(gp.X_u(:))) = 0;
        i1 = st+1;
        for i = 1:size(gp.X_u,1)
          if iscell(gp.p.X_u) % Own prior for each inducing input
            pr = gp.p.X_u{i};
            gprior(i1:i1+m) = -pr.fh.lpg(gp.X_u(i,:), pr);
          else % One prior for all inducing inputs
            gprior(i1:i1+m-1) = -gp.p.X_u.fh.lpg(gp.X_u(i,:), gp.p.X_u);
          end
          i1 = i1 + m;
        end

        % Loop over the covariance functions
        for i=1:ncf
          i1 = st;
          gpcf = gp.cf{i};

          if savememory
            % If savememory option is used, just get the number of
            % covariates in X and calculate gradients later
            np=gpcf.fh.ginput(gpcf,u,[],0);
          else
            np=1;
            DKuu = gpcf.fh.ginput(gpcf, u);
            DKuf = gpcf.fh.ginput(gpcf, u, x);
          end
          for i3=1:np
            if savememory
              DKuu=gpcf.fh.ginput(gpcf,u,[],i3);
              DKuf=gpcf.fh.ginput(gpcf,u,x,i3);
            end
            for i2 = 1:length(DKuu)
              i1=i1+1;
              KfuiKuuKuu = iKuuKuf'*DKuu{i2};

              gdata(i1) = gdata(i1) - 0.5.*((2*b*DKuf{i2}'-(b*KfuiKuuKuu))*(iKuuKuf*b') + ...
                                          2.*sum(sum(L'.*(L'*DKuf{i2}'*iKuuKuf))) - sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));
              gdata(i1) = gdata(i1) + 0.5.*(2.*b.*sum(DKuf{i2}'.*iKuuKuf',2)'*b'- b.*sum(KfuiKuuKuu.*iKuuKuf',2)'*b');
              gdata(i1) = gdata(i1) + 0.5.*(2.*sum(LL.*sum(DKuf{i2}'.*iKuuKuf',2)) - ...
                                          sum(LL.*sum(KfuiKuuKuu.*iKuuKuf',2)));
            end
          end
        end
      end
    end

    g = gdata + gprior;

  case {'PIC' 'PIC_BLOCK'}
    % ============================================================
    % PIC
    % ============================================================
    g_ind = zeros(1,numel(gp.X_u));
    gdata_ind = zeros(1,numel(gp.X_u));
    gprior_ind = zeros(1,numel(gp.X_u));

    u = gp.X_u;
    ind = gp.tr_index;
    DKuu_u = 0;
    DKuf_u = 0;

    % First evaluate the needed covariance matrices
    % if they are not in the memory
    % v defines that parameter is a vector
    K_fu = gp_cov(gp, x, u);         % f x u
    K_uu = gp_trcov(gp, u);          % u x u, noiseles covariance K_uu
    K_uu = (K_uu+K_uu')./2;          % ensure the symmetry of K_uu
    [Luu, notpositivedefinite] = chol(K_uu,'lower');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    % Evaluate the Lambda (La)
    % Q_ff = K_fu*inv(K_uu)*K_fu'
    % Here we need only the diag(Q_ff), which is evaluated below
    %B=K_fu/Luu;
    B=Luu\K_fu';
    iLaKfu = zeros(size(K_fu));  % f x u
    for i=1:length(ind)
      Qbl_ff = B(:,ind{i})'*B(:,ind{i});
      [Kbl_ff, Cbl_ff] = gp_trcov(gp, x(ind{i},:));
      la = Cbl_ff - Qbl_ff;
      La{i} = (la + la')./2;
      iLaKfu(ind{i},:) = La{i}\K_fu(ind{i},:);
    end
    % ... then evaluate some help matrices.
    % A = chol(K_uu+K_uf*inv(La)*K_fu))
    A = K_uu+K_fu'*iLaKfu;
    A = (A+A')./2;            % Ensure symmetry

    [LA,notpositivedefinite]=chol(A,'upper');
    if notpositivedefinite
      % instead of stopping to chol error, return NaN
      g=NaN; gdata = NaN; gprior = NaN;
      return;
    end
    L = iLaKfu/LA;
    b = zeros(1,n);
    b_apu=(y'*L)*L';
    for i=1:length(ind)
      b(ind{i}) = y(ind{i})'/La{i} - b_apu(ind{i});
    end
    iKuuKuf = Luu'\(Luu\K_fu');

    % =================================================================
    % Gradient with respect to covariance function parameters

    if ~isempty(strfind(gp.infer_params, 'covariance'))
      % Loop over the  covariance functions
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        % Get the gradients of the covariance matrices
        % and gprior from gpcf_* structures
        gpcf = gp.cf{i};
        if savememory
          % If savememory option is used, just get the number of
          % hyperparameters and calculate gradients later
          np=gpcf.fh.cfg(gpcf,[],[],[],0);
        else
          DKuuc = gpcf.fh.cfg(gpcf, u);
          DKufc = gpcf.fh.cfg(gpcf, u, x);
          for kk = 1:length(ind)
            DKffc{kk} = gpcf.fh.cfg(gpcf, x(ind{kk},:));
          end
          np=length(DKuuc);
        end
        gprior_cf = -gpcf.fh.lpg(gpcf);

        for i2 = 1:np
          i1 = i1+1;
          if savememory
            DKuu=gpcf.fh.cfg(gpcf,u,[],[],i2);
            DKuf=gpcf.fh.cfg(gpcf,u,x,[],i2);
          else
            DKuu=DKuuc{i2};
            DKuf=DKufc{i2};
          end
          KfuiKuuKuu = iKuuKuf'*DKuu;
          %            H = (2*K_uf'- KfuiKuuKuu)*iKuuKuf;
          % Here we evaluate  gdata = -0.5.* (b*H*b' + trace(L*L'H)
          gdata(i1) = -0.5.*((2*b*DKuf'-(b*KfuiKuuKuu))*(iKuuKuf*b') + 2.*sum(sum(L'.*(L'*DKuf'*iKuuKuf))) - ...
                             sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));
          for kk=1:length(ind)
            if savememory
              DKff=gpcf.fh.cfg(gpcf,x(ind{kk},:),[],[],i2);
            else
              DKff=DKffc{kk}{i2};
            end
            gdata(i1) = gdata(i1) ...
                + 0.5.*(-b(ind{kk})*DKff*b(ind{kk})' ...
                        + 2.*b(ind{kk})*DKuf(:,ind{kk})'*iKuuKuf(:,ind{kk})*b(ind{kk})'- ...
                        b(ind{kk})*KfuiKuuKuu(ind{kk},:)*iKuuKuf(:,ind{kk})*b(ind{kk})' ...
                        +trace(La{kk}\DKff)...
                        - trace(L(ind{kk},:)*(L(ind{kk},:)'*DKff)) ...
                        + 2.*sum(sum(L(ind{kk},:)'.*(L(ind{kk},:)'*DKuf(:,ind{kk})'*iKuuKuf(:,ind{kk})))) - ...
                        sum(sum(L(ind{kk},:)'.*((L(ind{kk},:)'*KfuiKuuKuu(ind{kk},:))*iKuuKuf(:,ind{kk})))));
          end
          gprior(i1) = gprior_cf(i2);
        end

        % Set the gradients of hyperparameter
        if length(gprior_cf) > np
          for i2=np+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        gdata(i1)= -0.5*DCff{i2}.*b*b';
        for kk=1:length(ind)
          gdata(i1)= gdata(i1) + 0.5*trace((inv(La{kk})-L(ind{kk},:)*L(ind{kk},:)')).*DCff{i2};
        end
        gprior(i1) = gprior_lik(i2);
      end
      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    % =================================================================
    % Gradient with respect to inducing inputs
    if ~isempty(strfind(gp.infer_params, 'inducing'))
      if isfield(gp.p, 'X_u') && ~isempty(gp.p.X_u)
        m = size(gp.X_u,2);

        st=0;
        if ~isempty(gprior)
          st = length(gprior);
        end
        gdata(st+1:st+length(gp.X_u(:))) = 0;

        i1 = st+1;
        for i = 1:size(gp.X_u,1)
          if iscell(gp.p.X_u) % Own prior for each inducing input
            pr = gp.p.X_u{i};
            gprior(i1:i1+m) = -pr.fh.lpg(gp.X_u(i,:), pr);
          else % One prior for all inducing inputs
            gprior(i1:i1+m-1) = -gp.p.X_u.fh.lpg(gp.X_u(i,:), gp.p.X_u);
          end
          i1 = i1 + m;
        end

        % Loop over the  covariance functions
        for i=1:ncf
          i1=st;
          gpcf = gp.cf{i};
          if savememory
            % If savememory option is used, just get the number of
            % covariates in X and calculate gradients later
            np=gpcf.fh.ginput(gpcf,u,[],0);
          else
            np=1;
            DKuu = gpcf.fh.ginput(gpcf, u);
            DKuf = gpcf.fh.ginput(gpcf, u, x);
          end

          for i3 = 1:np
            if savememory
              DKuu=gpcf.fh.ginput(gpcf, u, [], i3);
              DKuf=gpcf.fh.ginput(gpcf, u, x, i3);
            end
            for i2 = 1:length(DKuu)
              i1 = i1+1;
              KfuiKuuDKuu_u = iKuuKuf'*DKuu{i2};
              gdata(i1) = gdata(i1) -0.5.*((2*b*DKuf{i2}'-(b*KfuiKuuDKuu_u))*(iKuuKuf*b') + 2.*sum(sum(L'.*((L'*DKuf{i2}')*iKuuKuf))) - ...
                                         sum(sum(L'.*((L'*KfuiKuuDKuu_u)*iKuuKuf))));

              for kk=1:length(ind)
                gdata(i1) = gdata(i1) + 0.5.*(2.*b(ind{kk})*DKuf{i2}(:,ind{kk})'*iKuuKuf(:,ind{kk})*b(ind{kk})'- ...
                                            b(ind{kk})*KfuiKuuDKuu_u(ind{kk},:)*iKuuKuf(:,ind{kk})*b(ind{kk})' ...
                                            + 2.*sum(sum(L(ind{kk},:)'.*(L(ind{kk},:)'*DKuf{i2}(:,ind{kk})'*iKuuKuf(:,ind{kk})))) - ...
                                            sum(sum(L(ind{kk},:)'.*((L(ind{kk},:)'*KfuiKuuDKuu_u(ind{kk},:))*iKuuKuf(:,ind{kk})))));
              end
            end
          end
        end
      end
    end
    g = gdata + gprior;

  case 'CS+FIC'
    % ============================================================
    % CS+FIC
    % ============================================================
    g_ind = zeros(1,numel(gp.X_u));
    gdata_ind = zeros(1,numel(gp.X_u));
    gprior_ind = zeros(1,numel(gp.X_u));

    u = gp.X_u;
    DKuu_u = 0;
    DKuf_u = 0;

    cf_orig = gp.cf;

    cf1 = {};
    cf2 = {};
    j = 1;
    k = 1;
    for i = 1:ncf
      if ~isfield(gp.cf{i},'cs')
        cf1{j} = gp.cf{i};
        j = j + 1;
      else
        cf2{k} = gp.cf{i};
        k = k + 1;
      end
    end
    gp.cf = cf1;

    % First evaluate the needed covariance matrices
    % if they are not in the memory
    % v defines that parameter is a vector
    [Kv_ff, Cv_ff] = gp_trvar(gp, x);  % 1 x f  vector
    K_fu = gp_cov(gp, x, u);         % f x u
    K_uu = gp_trcov(gp, u);          % u x u, noiseles covariance K_uu
    K_uu = (K_uu+K_uu')./2;          % ensure the symmetry of K_uu
    [Luu, notpositivedefinite] = chol(K_uu,'lower');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    % Evaluate the Lambda (La)
    % Q_ff = K_fu*inv(K_uu)*K_fu'
    % Here we need only the diag(Q_ff), which is evaluated below
    B=Luu\(K_fu');
    Qv_ff=sum(B.^2)';
    Lav = Cv_ff-Qv_ff;   % 1 x f, Vector of diagonal elements

    gp.cf = cf2;
    K_cs = gp_trcov(gp,x);
    La = sparse(1:n,1:n,Lav,n,n) + K_cs;
    gp.cf = cf_orig;

    LD = ldlchol(La);
    %        iLaKfu = La\K_fu;
    iLaKfu = ldlsolve(LD, K_fu);

    % ... then evaluate some help matrices.
    % A = chol(K_uu+K_uf*inv(La)*K_fu))
    A = K_uu+K_fu'*iLaKfu;
    A = (A+A')./2;            % Ensure symmetry
    [LA, notpositivedefinite] = chol(A,'upper');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    L = iLaKfu/LA;
    %b = y'/La - (y'*L)*L';
    b = ldlsolve(LD,y)' - (y'*L)*L';

    siLa = spinv(La);
    idiagLa = diag(siLa);
    iKuuKuf = K_uu\K_fu';
    LL = sum(L.*L,2);

    % =================================================================
    % Gradient with respect to covariance function parameters
    if ~isempty(strfind(gp.infer_params, 'covariance'))
      % Loop over covariance functions
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        gpcf = gp.cf{i};

        % Evaluate the gradient for FIC covariance functions
        if ~isfield(gpcf,'cs')
          % Get the gradients of the covariance matrices
          % and gprior from gpcf_* structures
          if savememory
            % If savememory option is used, just get the number of
            % hyperparameters and calculate gradients later
            np=gpcf.fh.cfg(gpcf,[],[],[],0);
          else
            DKffc = gpcf.fh.cfg(gpcf, x, [], 1);
            DKuuc = gpcf.fh.cfg(gpcf, u);
            DKufc = gpcf.fh.cfg(gpcf, u, x);
            np=length(DKuuc);
          end
          gprior_cf = -gpcf.fh.lpg(gpcf);

          for i2 = 1:np
            i1 = i1+1;
            if savememory
              DKff=gpcf.fh.cfg(gpcf,x,[],1,i2);
              DKuu=gpcf.fh.cfg(gpcf,u,[],[],i2);
              DKuf=gpcf.fh.cfg(gpcf,u,x,[],i2);
            else
              DKff=DKffc{i2};
              DKuu=DKuuc{i2};
              DKuf=DKufc{i2};
            end
            KfuiKuuKuu = iKuuKuf'*DKuu;
            gdata(i1) = -0.5.*((2*b*DKuf'-(b*KfuiKuuKuu))*(iKuuKuf*b') + 2.*sum(sum(L'.*(L'*DKuf'*iKuuKuf))) - ...
                               sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));

            temp1 = sum(KfuiKuuKuu.*iKuuKuf',2);
            temp2 = sum(DKuf'.*iKuuKuf',2);
            temp3 = 2.*DKuf' - KfuiKuuKuu;
            gdata(i1) = gdata(i1) - 0.5.*(b.*DKff')*b';
            gdata(i1) = gdata(i1) + 0.5.*(2.*b.*temp2'*b'- b.*temp1'*b');
            gdata(i1) = gdata(i1) + 0.5.*(sum(idiagLa.*DKff - LL.*DKff));   % corrected
            gdata(i1) = gdata(i1) + 0.5.*(2.*sum(LL.*temp2) - sum(LL.*temp1));

            %gdata(i1) = gdata(i1) + 0.5.*sum(sum(La\((2.*K_uf') - KfuiKuuKuu).*iKuuKuf',2));
            gdata(i1) = gdata(i1) + 0.5.*sum(sum(ldlsolve(LD,temp3).*iKuuKuf',2));
            gdata(i1) = gdata(i1) - 0.5.*( idiagLa'*(sum(temp3.*iKuuKuf',2)) ); % corrected
            gprior(i1) = gprior_cf(i2);
          end

          % Evaluate the gradient for compact support covariance functions
        else
          % Get the gradients of the covariance matrices
          % and gprior from gpcf_* structures
            if savememory
              % If savememory option is used, just get the number of
              % hyperparameters and calculate gradients later
              np=gpcf.fh.cfg(gpcf,[],[],[],0);
            else
              DKffc = gpcf.fh.cfg(gpcf, x);
              np=length(DKffc);
            end
          gprior_cf = -gpcf.fh.lpg(gpcf);

          for i2 = 1:np
            i1 = i1+1;
            if savememory
              DKff=gpcf.fh.cfg(gpcf,x,[],[],i2);
            else
              DKff=DKffc{i2};
            end
            gdata(i1) = 0.5*(sum(sum(siLa.*DKff',2)) - sum(sum(L.*(L'*DKff')')) - b*DKff*b');
            gprior(i1) = gprior_cf(i2);
          end
        end
        % Set the gradients of hyperparameter
        if length(gprior_cf) > np
          for i2=np+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        gdata(i1)= -0.5*DCff{i2}.*b*b';
        gdata(i1)= gdata(i1) + 0.5*sum(idiagLa-LL).*DCff{i2};
        gprior(i1) = gprior_lik(i2);
      end

      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    % =================================================================
    % Gradient with respect to inducing inputs
    if ~isempty(strfind(gp.infer_params, 'inducing'))
      if isfield(gp.p, 'X_u') && ~isempty(gp.p.X_u)
        m = size(gp.X_u,2);
        st=0;
        if ~isempty(gprior)
          st = length(gprior);
        end

        gdata(st+1:st+length(gp.X_u(:))) = 0;
        i1 = st+1;
        for i = 1:size(gp.X_u,1)
          if iscell(gp.p.X_u) % Own prior for each inducing input
            pr = gp.p.X_u{i};
            gprior(i1:i1+m) = -pr.fh.lpg(gp.X_u(i,:), pr);
          else % One prior for all inducing inputs
            gprior(i1:i1+m-1) = -gp.p.X_u.fh.lpg(gp.X_u(i,:), gp.p.X_u);
          end
          i1 = i1 + m;
        end

        for i=1:ncf
          i1=st;
          gpcf = gp.cf{i};
          if ~isfield(gpcf,'cs')
            if savememory
              % If savememory option is used, just get the number of
              % covariates in X and calculate gradients later
              np=gpcf.fh.ginput(gpcf,u,[],0);
            else
              np=1;
              DKuu = gpcf.fh.ginput(gpcf, u);
              DKuf = gpcf.fh.ginput(gpcf, u, x);
            end

            for i3 = 1:np
              if savememory
                DKuu = gpcf.fh.ginput(gpcf,u,[],i3);
                DKuf = gpcf.fh.ginput(gpcf,u,x,i3);
              end
              for i2 = 1:length(DKuu)
                i1 = i1+1;
                KfuiKuuKuu = iKuuKuf'*DKuu{i2};

                gdata(i1) = gdata(i1) - 0.5.*((2*b*DKuf{i2}'-(b*KfuiKuuKuu))*(iKuuKuf*b') + ...
                                            2.*sum(sum(L'.*(L'*DKuf{i2}'*iKuuKuf))) - sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));
                gdata(i1) = gdata(i1) + 0.5.*(2.*b.*sum(DKuf{i2}'.*iKuuKuf',2)'*b'- b.*sum(KfuiKuuKuu.*iKuuKuf',2)'*b');
                gdata(i1) = gdata(i1) + 0.5.*(2.*sum(sum(L.*L,2).*sum(DKuf{i2}'.*iKuuKuf',2)) - ...
                                            sum(sum(L.*L,2).*sum(KfuiKuuKuu.*iKuuKuf',2)));

                gdata(i1) = gdata(i1) + 0.5.*sum(sum(ldlsolve(LD,(2.*DKuf{i2}') - KfuiKuuKuu).*iKuuKuf',2));
                gdata(i1) = gdata(i1) - 0.5.*( idiagLa'*(sum((2.*DKuf{i2}' - KfuiKuuKuu).*iKuuKuf',2)) ); % corrected
              end
            end
          end
        end
      end
    end

    g = gdata + gprior;

  case {'DTC' 'VAR' 'SOR'}
    % ============================================================
    % DTC/VAR/SOR
    % ============================================================
    g_ind = zeros(1,numel(gp.X_u));
    gdata_ind = zeros(1,numel(gp.X_u));
    gprior_ind = zeros(1,numel(gp.X_u));

    u = gp.X_u;
    DKuu_u = 0;
    DKuf_u = 0;

    % First evaluate the needed covariance matrices
    % v defines that parameter is a vector
    [Kv_ff, Cv_ff] = gp_trvar(gp, x);  % 1 x f  vector
    K_fu = gp_cov(gp, x, u);         % f x u
    K_uu = gp_trcov(gp, u);          % u x u, noiseles covariance K_uu
    K_uu = (K_uu+K_uu')./2;          % ensure the symmetry of K_uu
    [Luu, notpositivedefinite] = chol(K_uu,'lower');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    % Evaluate the Lambda (La)
    % Q_ff = K_fu*inv(K_uu)*K_fu'
    % Here we need only the diag(Q_ff), which is evaluated below
    B=Luu\(K_fu');
    Qv_ff=sum(B.^2)';
    Lav = Cv_ff-Kv_ff;   % 1 x f, Vector of diagonal elements
                         % iLaKfu = diag(inv(Lav))*K_fu = inv(La)*K_fu
    iLaKfu = zeros(size(K_fu));  % f x u,
    for i=1:n
      iLaKfu(i,:) = K_fu(i,:)./Lav(i);  % f x u
    end
    % ... then evaluate some help matrices.
    % A = K_uu+K_uf*inv(La)*K_fu
    A = K_uu+K_fu'*iLaKfu;
    A = (A+A')./2;               % Ensure symmetry
    [LA, notpositivedefinite] = chol(A);
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    L = iLaKfu/LA;
    b = y'./Lav' - (y'*L)*L';
    iKuuKuf = Luu'\(Luu\K_fu');
    La = Lav;
    LL = sum(L.*L,2);
    iLav=1./Lav;

    LL1=iLav-LL;

    % =================================================================

    if ~isempty(strfind(gp.infer_params, 'covariance'))
      % Loop over the covariance functions
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        % Get the gradients of the covariance matrices
        % and gprior from gpcf_* structures
        gpcf = gp.cf{i};
        if savememory
          np=gpcf.fh.cfg(gpcf,[],[],[],0);
        else
          DKffc = gpcf.fh.cfg(gpcf, x, [], 1);
          DKuuc = gpcf.fh.cfg(gpcf, u);
          DKufc = gpcf.fh.cfg(gpcf, u, x);
          np=length(DKuuc);
        end
        gprior_cf = -gpcf.fh.lpg(gpcf);

        for i2 = 1:np
          i1 = i1+1;
          if savememory
            % If savememory option is used, just get the number of
            % hyperparameters and calculate gradients later
            DKff=gpcf.fh.cfg(gpcf,x,[],1,i2);
            DKuu=gpcf.fh.cfg(gpcf,u,[],[],i2);
            DKuf=gpcf.fh.cfg(gpcf,u,x,[],i2);
          else
            DKff=DKffc{i2};
            DKuu=DKuuc{i2};
            DKuf=DKufc{i2};
          end

          KfuiKuuKuu = iKuuKuf'*DKuu;
          gdata(i1) = -0.5.*((2*b*DKuf'-(b*KfuiKuuKuu))*(iKuuKuf*b'));
          gdata(i1) = gdata(i1) + 0.5.*(2.*(sum(iLav'*sum(DKuf'.*iKuuKuf',2))-sum(sum(L'.*(L'*DKuf'*iKuuKuf))))...
                                        - sum(iLav'*sum(KfuiKuuKuu.*iKuuKuf',2))+ sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));

          if strcmp(gp.type, 'VAR')
            gdata(i1) = gdata(i1) + 0.5.*(sum(iLav.*DKff)-2.*sum(iLav'*sum(DKuf'.*iKuuKuf',2)) + ...
                                          sum(iLav'*sum(KfuiKuuKuu.*iKuuKuf',2))); % trace-term derivative
          end
          gprior(i1) = gprior_cf(i2);
        end

        % Set the gradients of hyperparameter
        if length(gprior_cf) > np
          for i2=np+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        gdata(i1)= -0.5*DCff{i2}.*b*b';
        gdata(i1)= gdata(i1) + 0.5*sum(DCff{i2}./La-sum(L.*L,2).*DCff{i2});
        if strcmp(gp.type, 'VAR')
          gdata(i1)= gdata(i1) + 0.5*(sum((Kv_ff-Qv_ff)./La));
        end

        gprior(i1) = gprior_lik(i2);
      end
      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    % =================================================================
    % Gradient with respect to inducing inputs
    if ~isempty(strfind(gp.infer_params, 'inducing'))
      if isfield(gp.p, 'X_u') && ~isempty(gp.p.X_u)
        m = size(gp.X_u,2);
        st=0;
        if ~isempty(gprior)
          st = length(gprior);
        end

        gdata(st+1:st+length(gp.X_u(:))) = 0;
        i1 = st+1;
        for i = 1:size(gp.X_u,1)
          if iscell(gp.p.X_u) % Own prior for each inducing input
            pr = gp.p.X_u{i};
            gprior(i1:i1+m) = -pr.fh.lpg(gp.X_u(i,:), pr);
          else % One prior for all inducing inputs
            gprior(i1:i1+m-1) = -gp.p.X_u.fh.lpg(gp.X_u(i,:), gp.p.X_u);
          end
          i1 = i1 + m;
        end

        % Loop over the covariance functions
        for i=1:ncf
          i1 = st;
          gpcf = gp.cf{i};
          if savememory
            % If savememory option is used, just get the number of
            % covariates in X and calculate gradients later
            np=gpcf.fh.ginput(gpcf,u,[],0);
          else
            np=1;
            DKuu = gpcf.fh.ginput(gpcf, u);
            DKuf = gpcf.fh.ginput(gpcf, u, x);
          end

          for i3 = 1:np
            if savememory
              DKuu=gpcf.fh.ginput(gpcf,u,[],i3);
              DKuf=gpcf.fh.ginput(gpcf,u,x,i3);
            end
            for i2 = 1:length(DKuu)
              i1=i1+1;
              KfuiKuuKuu = iKuuKuf'*DKuu{i2};
              gdata(i1) = gdata(i1) - 0.5.*((2*b*DKuf{i2}'-(b*KfuiKuuKuu))*(iKuuKuf*b'));
              gdata(i1) = gdata(i1) + 0.5.*(2.*(sum(iLav'*sum(DKuf{i2}'.*iKuuKuf',2))-sum(sum(L'.*(L'*DKuf{i2}'*iKuuKuf))))...
                                          - sum(iLav'*sum(KfuiKuuKuu.*iKuuKuf',2))+ sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));

              if strcmp(gp.type, 'VAR')
                gdata(i1) = gdata(i1) + 0.5.*(0-2.*sum(iLav'*sum(DKuf{i2}'.*iKuuKuf',2)) + ...
                                            sum(iLav'*sum(KfuiKuuKuu.*iKuuKuf',2)));
              end
            end
          end
        end
      end
    end

    g = gdata + gprior;

  case 'SSGP'
    % ============================================================
    % SSGP
    % ============================================================
    % Predictions with sparse spectral sampling approximation for GP
    % The approximation is proposed by M. Lazaro-Gredilla, J.
    % Quinonero-Candela and A. Figueiras-Vidal in Microsoft
    % Research technical report MSR-TR-2007-152 (November 2007)
    % NOTE! This does not work at the moment.

    % First evaluate the needed covariance matrices
    % v defines that parameter is a vector
    [Phi, S] = gp_trcov(gp, x);        % n x m and nxn sparse matrices
    Sv = diag(S);

    m = size(Phi,2);

    A = eye(m,m) + Phi'*(S\Phi);
    [LA, notpositivedefinite] = chol(A,'lower');
    if notpositivedefinite
      % If not positive definite, return NaN
      g=NaN; gdata=NaN; gprior=NaN;
      return;
    end
    L = (S\Phi)/A';

    b = y'./Sv' - (y'*L)*L';
    iSPhi = S\Phi;

    % =================================================================
    if ~isempty(strfind(gp.infer_params,'covariance'))
      % Loop over the covariance functions
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        gpcf = gp.cf{i};

        % Get the gradients of the covariance matrices
        % and gprior from gpcf_* structures
        DKff = gpcf.fh.cfg(gpcf, x);
        gprior_cf = -gpcf.fh.lpg(gpcf);

        % Evaluate the gradient with respect to lengthScale
        for i2 = 1:length(DKff)
          i1 = i1+1;
          iSDPhi = S\DKff{i2};

          gdata(i1) = 0.5*( sum(sum(iSDPhi.*Phi,2)) + sum(sum(iSPhi.*DKff{i2},2)) );
          gdata(i1) = gdata(i1) - 0.5*( sum(sum(L'.*(L'*DKff{i2}*Phi' + L'*Phi*DKff{i2}'),1)) );
          gdata(i1) = gdata(i1) - 0.5*(b*DKff{i2}*Phi' + b*Phi*DKff{i2}')*b';
          gprior(i1) = gprior_cf(i2);
        end

        % Set the gradients of hyperparameter
        if length(gprior_cf) > length(DKff)
          for i2=length(DKff)+1:length(gprior_cf)
            i1 = i1+1;
            gdata(i1) = 0;
            gprior(i1) = gprior_cf(i2);
          end
        end
      end
    end

    % =================================================================
    % Gradient with respect to Gaussian likelihood function parameters
    if ~isempty(strfind(gp.infer_params, 'likelihood')) && isfield(gp.lik.fh,'trcov')
      % Evaluate the gradient from Gaussian likelihood
      DCff = gp.lik.fh.cfg(gp.lik, x);
      gprior_lik = -gp.lik.fh.lpg(gp.lik);
      for i2 = 1:length(DCff)
        i1 = i1+1;
        gdata(i1)= -0.5*DCff{i2}.*b*b';
        gdata(i1)= gdata(i1) + 0.5*sum(1./Sv-sum(L.*L,2)).*DCff{i2};
        gprior(i1) = gprior_lik(i2);
      end

      % Set the gradients of hyperparameter
      if length(gprior_lik) > length(DCff)
        for i2=length(DCff)+1:length(gprior_lik)
          i1 = i1+1;
          gdata(i1) = 0;
          gprior(i1) = gprior_lik(i2);
        end
      end
    end

    % =================================================================
    % Gradient with respect to inducing inputs
    if ~isempty(strfind(gp.infer_params, 'inducing'))
      for i=1:ncf
        i1=0;
        if ~isempty(gprior)
          i1 = length(gprior);
        end

        gpcf = gp.cf{i};

        gpcf.GPtype = gp.type;
        [gprior_ind, DKuu, DKuf] = gpcf.fh.gind(gpcf, x, y, g_ind, gdata_ind, gprior_ind);

        for i2 = 1:length(DKuu)
          KfuiKuuKuu = iKuuKuf'*DKuu{i2};

          gdata_ind(i2) = gdata_ind(i2) - 0.5.*((2*b*DKuf{i2}'-(b*KfuiKuuKuu))*(iKuuKuf*b') + ...
                                                2.*sum(sum(L'.*(L'*DKuf{i2}'*iKuuKuf))) - sum(sum(L'.*((L'*KfuiKuuKuu)*iKuuKuf))));
          gdata_ind(i2) = gdata_ind(i2) + 0.5.*(2.*b.*sum(DKuf{i2}'.*iKuuKuf',2)'*b'- b.*sum(KfuiKuuKuu.*iKuuKuf',2)'*b');
          gdata_ind(i2) = gdata_ind(i2) + 0.5.*(2.*sum(sum(L.*L,2).*sum(DKuf{i2}'.*iKuuKuf',2)) - ...
                                                sum(sum(L.*L,2).*sum(KfuiKuuKuu.*iKuuKuf',2)));
        end
      end
    end

    g = gdata + gprior;

end


function [a,count] = nanplus(a,b,count,alpha)

if nargin < 4
  alpha = 1;
elseif isnan(alpha)
  return;
end
ind = ~isnan(b);

a(ind) = a(ind) + alpha * b(ind);

if nargin > 2
  count = count + alpha * ind;
end


function rcnn_model = rcnn_create_model(cnn_definition_file, cnn_binary_file, cache_name)
% AUTORIGHTS
% ---------------------------------------------------------
% Copyright (c) 2014, Ross Girshick
%
% This file is part of the R-CNN code and is available
% under the terms of the Simplified BSD License provided in
% LICENSE. Please retain this notice and LICENSE if you use
% this file (or any portion of it) in your project.
% ---------------------------------------------------------

if ~exist('cache_name', 'var') || isempty(cache_name)
  cache_name = 'none';
end

%  model =
%    cnn: [1x1 struct]
%        binary_file: 'path/to/cnn/model/binary'
%        definition_file: 'path/to/cnn/model/definition'
%        batch_size: 256
%        image_mean: [227x227x3 single]
%        init_key: -1
%    detectors.W: [N x <numclasses> single]  % matrix of SVM weights
%    detectors.B: [1 x <numclasses> single]  % (row) vector of SVM biases
%    detectors.crop_mode: 'warp' or 'square'
%    detectors.crop_padding: 16
%    detectors.nms_thresholds: [1x20 single]
%    training_opts: [1x1 struct]
%        bias_mult: 10
%        fine_tuned: 1
%        layer: 'fc7'
%        pos_loss_weight: 2
%        svm_C: 1.0000e-03
%        trainset: 'trainval'
%        use_flipped: 0
%        year: '2007'
%        feat_norm_mean: 20.1401
%    classes: {cell array of class names}
%    class_to_index: map from class name to column index in W

% init empty convnet
assert(exist(cnn_binary_file, 'file') ~= 0);
assert(exist(cnn_definition_file, 'file') ~= 0);
cnn.binary_file = cnn_binary_file;
cnn.definition_file = cnn_definition_file;
cnn.batch_size = 32;
cnn.init_key = -1;
cnn.input_size = 224;
% load the ilsvrc image mean
data_mean_file = './external/caffe/matlab/caffe/ilsvrc_2014_mean.mat';
%data_mean_file = './external/caffe/matlab/caffe/ilsvrc_2014_mean.mat';

assert(exist(data_mean_file, 'file') ~= 0);
ld = load(data_mean_file);
image_mean = ld.image_mean; clear ld;
off = floor((size(image_mean,1) - cnn.input_size)/2)+1;
image_mean = image_mean(off:off+cnn.input_size-1, off:off+cnn.input_size-1, :);
cnn.image_mean = image_mean;

% init empty detectors
detectors.W = [];
detectors.B = [];
detectors.crop_mode = 'warp';
detectors.crop_padding = 16;
detectors.nms_thresholds = [];

% rcnn model wraps the convnet and detectors
rcnn_model.cnn = cnn;
rcnn_model.cache_name = cache_name;
rcnn_model.detectors = detectors;


function [coregisteredImage, affineCoregistrationGeometry, outputOtherImages] =...
    coregister_to(this, stationaryImage, varargin)
% Coregister this MrImage to another MrImage using SPM's coregister.
%
%   Y = MrImage()
%   cY = Y.coregister_to(stationaryImage)
%
% This is a method of class MrImage.
%
% IN
%   stationaryImage     cell {nImages,n} of MrImages or single MrImage that
%                       serves as stationary" or reference image to which
%                       this image is coregistered to
%
%  optional parameter name/value pairs:
%
%  transformation estimation and application
%       applyTransformation
%                   'geometry'      MrImageGeometry is updated,
%                                   MrImage.data remains untouched
%                   'data'          MrImage.data is resliced to new
%                                   geometry
%                                   NOTE: An existing
%                                   transformation in MrImageGeometry will
%                                   also be applied to MrImage, combined
%                                   with the calculated one for
%                                   coregistration
%                   'none'          transformation matrix is
%                                   computed, but not applied to geometry
%                                   of data of this image
%       trafoParameters             'translation', 'rigid', 'affine',
%                                   'rigidscaled' or
%                                   [1,1-12] vector of starting parameters
%                                   for transformation estimation number of
%                                   elements decides whether
%                                   translation only (1-3)
%                                   rigid (4-6)
%                                   rigid and scaling (7-9)
%                                   affine (10-12)
%                                   transformation is performed
%                                   default: 'rigid' (as in SPM)
% SPM input parameters:
%          separation           optimisation sampling steps (mm)
%                               default: [4 2]
%          objectiveFunction    cost function string:
%                               'mi'  - Mutual Information
%                               'nmi' - Normalised Mutual Information
%                               'ecc' - Entropy Correlation Coefficient
%                               'ncc' - Normalised Cross Correlation
%                               default: 'nmi'
%          tolerances           tolerances for accuracy of each param
%                               default: [0.02 0.02 0.02 0.001 0.001 0.001]
%          histSmoothingFwhm    smoothing to apply to 256x256 joint histogram
%                               default: [7 7]
%          otherImages          cell(nImages,1) of other images (either
%                               file names or MrImages) that should undergo
%                               the same trafo as this images due to coreg
%                               default: {}
%           doPlot              set to true for graphical output and PS file creation
%                               in SPM graphics window
%                               default: false
%
%   Parameters for high-dim application:
%       representationIndexArray:   either an MrImageObject or a selection
%                                   (e.g. {'echo', 1} which is then applied to
%                                   obtain one 4D image
%                                   default representationIndexArray: first
%                                   index of all extra (non-4D) dimensions
%       applicationIndexArray:      a selection which defines one or multiple
%                                   4D images on which the estimated parameters
%                                   are applied
%                                   default applicationIndexArray: all non-4D
%                                   dimensions
%       splitComplex                'ri' or 'mp'
%                                   If the data are complex numbers, real and imaginary or
%                                   magnitude and phase are realigned separately.
%                                   default: ri (real and imaginary)
%
% OUT
%
% EXAMPLE
%   coregister_to
%
%   See also MrImageSpm4D.coregister_to

% Author:   Saskia Bollmann & Lars Kasper
% Created:  2019-11-28
% Copyright (C) 2019 Institute for Biomedical Engineering
%                    University of Zurich and ETH Zurich
%
% This file is part of the TAPAS UniQC Toolbox, which is released
% under the terms of the GNU General Public License (GPL), version 3.
% You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version).
% For further details, see the file COPYING or
%  <http://www.gnu.org/licenses/>.

% SPM parameters
defaults.doPlot = false; % set to true for graphical output and PS file creation
defaults.otherImages = {};
defaults.objectiveFunction = 'nmi';
defaults.separation = [4 2 1];
defaults.tolerances = [0.02 0.02 0.02 0.001 0.001 0.001 0.01 0.01 0.01 0.001 0.001 0.001];
defaults.histSmoothingFwhm = [7 7];
defaults.trafoParameters = 'rigid';
% other coreg_to parameters of MrImageSpm4D
defaults.applyTransformation = 'data';
defaults.otherImages = {};
args = tapas_uniqc_propval(varargin, defaults);
% tapas_uniqc_strip_fields(args);

methodParameters = {args};

%% Distinguish cases

%% 1) this: 3D; stationary: 3D; other: nD
%  -> trivial case, directly mimicking SPM's coregister and passing to
%  MrImageSpm4D

thisNonSDims = this.dimInfo.get_non_singleton_dimensions;
thisIs3D = numel(thisNonSDims) == 3;
thisIsReal = isreal(this);
thisIsReal3D = thisIs3D && thisIsReal;

stationaryNonSDims = stationaryImage.dimInfo.get_non_singleton_dimensions;
stationaryIs3D = numel(stationaryNonSDims) == 3;
stationaryIsReal = isreal(stationaryImage);
stationaryIsReal3D = stationaryIs3D && stationaryIsReal;

if thisIsReal3D &&  stationaryIsReal3D % just do as SPM does!

    % for now, the stationary is 3D
    stationaryImage = stationaryImage.remove_dims();

    xyzDimLabels = {'x','y','z'};
    splitDimIndexArray = setdiff(1:this.dimInfo.nDims, ...
        this.dimInfo.get_dim_index(xyzDimLabels));
    splitDimLabels = this.dimInfo.dimLabels(splitDimIndexArray);

    [coregisteredImage, affineCoregistrationGeometry, outputOtherImages] = ...
        this.apply_spm_method_per_4d_split(...
        @(x, y) coregister_to(x, stationaryImage, y), ...
        'methodParameters', methodParameters, 'splitDimLabels', splitDimLabels);

end

end
%% 2) this: nD; stationary: 3D; other: cell(nSplits,nOtherImages) of 3D images
%% 2a) representation: 3D; application: nD
%  -> one 3D part represents this for the coregistration to stationary, and
%  the estimated coreg parameters are then applied to all images in the
%  application selections.
%% 2b) representation: nD; application: nD
%  -> each 3D part of "this" will be individually coregistered to stationary;
%     and kth coreg is applied to coresponding k-th cell of other images
%     Note: cell of nSplits of 3D images can be created by
%     other.split('splitDimLabels', {'t','echo'}) or similar
%% 3) this: 3D; stationary: cell(nStationary,1) of 3D images; other: cell (nStationary, nOtherImages)
%  -> "this" is coregistered to each of the stationaries (therefore 3+x D
%  output), and parameters of iStationary's coeregistration are applied to corresponding other images in cell {iStationary,:}
%% 4) this: nD; stationary: cell(nStationary,1) of 3D images; other cell(nStationary, nOtherImages)
%     Note: nStationary == nSplits of "this" here
%  -> Each 3D part of "this" is coregistered to the corresponding iSplit
%  (iStationary) entry of cell stationary, and the coreg parameters are
%  applied to all entries in cell {iStationary,:} of other images.


function kern = simwhiteKernParamInit(kern)

% SIMWHITEKERNPARAMINIT SIM-WHITE kernel parameter initialisation.
% FORMAT
% DESC initialises the SIM-White (Single Input Motif - White)
%  kernel structure with some default parameters.
% ARG kern : the kernel structure which requires initialisation.
% RETURN kern : the kernel structure with the default parameters placed in.
%
% SEEALSO : kernCreate, kernParamInit
%
% COPYRIGHT : David Luengo, 2009

% KERN

if kern.inputDimension > 1
  error('SIM-WHITE kernel only valid for one-D input.')
end

kern.nParams = 3;
kern.decay = 1;
kern.variance = 1;
kern.sensitivity = 1;

kern.delay = 0;
kern.initVal = 1;

kern.transforms.index = [1 2];
kern.transforms.type = optimiDefaultConstraint('positive');

kern.isStationary = false;
kern.positiveTime = true;


function test_old_ft_multiplotER

% MEM 2gb
% WALLTIME 00:10:00
% DEPENDENCY

% this script tests the functionality of ft_multiplotER with respect to the
% different input datatypes. no other functionality is tested.
% the script has been written in order to test a clean up of the code

filename = dccnpath('/home/common/matlab/fieldtrip/data/test/latest/raw/eeg/preproc_neuroscan16');
load(filename);

%there's an unresolved issue with duplicate labels 'FREE'
%FIXME
data.label{1} = 'FREE1';
data.label{2} = 'FREE2';
data.label{3} = 'FREE3';
data.label{4} = 'FREE4';

cfg = [];
cfg.channel = data.label(5:end);
cfg.preproc.demean = 'yes';
cfg.trials = 1:5;
tlck1 = ft_timelockanalysis(cfg, data);
cfg.trials = 6:10;
tlck2 = ft_timelockanalysis(cfg, data);

cfg = [];
cfg.layout = 'biosemi64.lay';
lay = ft_prepare_layout(cfg);

cfg        = [];
cfg.layout = lay;

%plot timelocked-data
ft_multiplotER(cfg, tlck1, tlck2);

%create frequency-data
cfgf = [];
cfgf.method = 'mtmfft';
cfgf.foilim = [0 100];
cfgf.taper  = 'hanning';
cfgf.output = 'pow';
cfgf.trials = 1:5;
cfgf.channel = data.label(5:end); % the first 4 are empty
freq1 = ft_freqanalysis(cfgf, data);
cfgf.trials = 6:10;
freq2 = ft_freqanalysis(cfgf, data);

%plot frequency-data
ft_multiplotER(cfg, freq1, freq2);

%create connectivity-data
cfgf.output = 'fourier';
cfgf.trials = 1:10;
freqx = ft_freqanalysis(cfgf, data);

cfgc2 = [];
cfgc2.method = 'coh';
coh   = ft_connectivityanalysis(cfgc2, freqx);

%plot connectivity-data
cfg.refchannel = 'gui';
cfg.parameter = 'cohspctrm';
ft_multiplotER(cfg, coh);

% FIXME this causes a crash when a new reference is selected and the old one is not unselected
% FIXME it also crashes when more than one cohref is selected

%create connectivity-data with sparse linear indexing
cfgc2.channelcmb = [repmat(freqx.label(5),[numel(freqx.label)-1 1]) freqx.label([1:4 6:end])];
coh2  = ft_connectivityanalysis(cfgc2, freqx);

%plot
cfg.refchannel = 'Cz';
coh2 = ft_checkdata(coh2, 'cmbstyle', 'full');
ft_multiplotER(cfg, coh2);
coh2 = ft_checkdata(coh2, 'cmbstyle', 'sparse');
ft_multiplotER(cfg, coh2);

%create connectivity-data with very sparse linear indexing
cfgc2.channelcmb = cfgc2.channelcmb(1:25,:);
coh3   = ft_connectivityanalysis(cfgc2, freqx);

%plot
ft_multiplotER(cfg, coh3);

%create connectivity-data with even sparser linear indexing
% cfgc2.channelcmb = [repmat(freq2.label(5),[10 1]) freq2.label(21:30)';repmat(freq2.label(10),[10 1]) freq2.label(21:30)'];
% coh4 = ft_connectivityanalysis(cfgc2, freq2);
%
% %plot: this breaks
% cfg.cohrefchannel = 'gui';
% ft_topoplotER(cfg, coh4);
%
% %plot: this works
% cfg.cohrefchannel = coh4.labelcmb(1,1);
% ft_topoplotER(cfg, coh4);
%
% %create connectivity-data with asymmetry
% %the data are probably not full-rank creating a problem for the sf
% %freq3 = ft_selectdata(freq2, 'channel', freq2.label(1:30));
% %%subselection of channels does not help
% freq4 = freq2transfer([], freq2);
%
% cfgc2 = [];
% cfgc2.method = 'granger';
% granger = ft_connectivityanalysis(cfgc2, freq4);
%
% cfg.cohrefchannel = 'gui';
% cfg.zparam = 'grangerspctrm';
% ft_topoplotER(cfg, granger);


function [costs, grad, charInfo] = lstmCostGrad(model, trainData, params, isTest)
%%%
%
% Compute cost/grad for LSTM.
% When params.predictPos>0, returns costs.pos and costs.word
% If isTest==1, this method only computes cost (for testing purposes).
%
% Thang Luong @ 2014, 2015, <lmthang@stanford.edu>
%
%%%

  %%%%%%%%%%%%
  %%% INIT %%%
  %%%%%%%%%%%%
  curBatchSize = size(trainData.tgtInput, 1);
  if params.isBi
    srcMaxLen = trainData.srcMaxLen;
  else
    srcMaxLen = 1;
  end

  params.curBatchSize = curBatchSize;
  params.srcMaxLen = srcMaxLen;

  [params] = setAttnParams(params);
  [grad, params] = initGrad(model, params);
  zeroBatch = zeroMatrix([params.lstmSize, params.curBatchSize], params.isGPU, params.dataType);

  % init states
  [zeroState] = createZeroState(params);

  % init costs
  costs = initCosts(params);

  tgtOutput = trainData.tgtOutput;
  tgtInput = trainData.tgtInput;
  % char
  if params.charOpt
    % tgt
    if params.charTgtGen
      origTgtOutput = trainData.tgtOutput;
    end

    tgtOutput(tgtOutput > params.tgtCharShortList) = params.tgtUnk;
    tgtInput(tgtInput > params.tgtCharShortList) = params.tgtUnk;
  end

  %%%%%%%%%%%%%%%%%%%%
  %%% FORWARD PASS %%%
  %%%%%%%%%%%%%%%%%%%%
  %% encoder
  lastEncState = zeroState;
  if params.isBi
    [encStates, lastEncState, encRnnFlags, trainData, srcCharData] = encoderLayerForward(model, zeroState, trainData, params, isTest);

    charInfo.trainNumSrcUnkTypes = 0;
    charInfo.trainNumSrcUnkTokens = 0;
    if params.charSrcRep
      charInfo.trainNumSrcUnkTypes = srcCharData.numRareWords;
      charInfo.trainNumSrcUnkTokens = sum(srcCharData.rareFlags(:));
    end
  end

  %% decoder
  decRnnFlags = struct('decode', 1, 'test', isTest, 'attn', params.attnFunc, 'feedInput', params.feedInput, 'charSrcRep', params.charSrcRep, ...
      'charTgtGen', params.charTgtGen, 'initEmb', []);
  [decStates, trainData, attnInfos] = rnnLayerForward(model.W_tgt, model.W_emb_tgt, lastEncState, tgtInput, trainData.tgtMask, ...
    params, decRnnFlags, trainData, model, []);

  %% softmax
  [costs.word, grad.W_soft, dec_top_grads, costs.indLosses] = softmaxCostGrad(decStates, model.W_soft, tgtOutput, trainData.tgtMask, ...
    params, isTest);
  costs.total = costs.word;

  %% tgt char foward / backprop %%
  charInfo.numChars = 0;
  charInfo.trainNumTgtUnkTokens = 0;
  if params.charTgtGen
    [costs.char, tgtCharGrad, charInfo.numChars] = tgtCharCostGrad(decStates, attnInfos, model, origTgtOutput, trainData.tgtMask, params.tgtCharMap, params, isTest);
    costs.total = costs.total + costs.char;
    charInfo.trainNumTgtUnkTokens = tgtCharGrad.numRareWords;
    if isTest==0
      if tgtCharGrad.numRareWords>0
        grad.W_soft_char = tgtCharGrad.W_soft;
        grad.W_tgt_char = tgtCharGrad.W_tgt;
        grad.W_emb_tgt_char = tgtCharGrad.W_emb_tgt_char;
        grad.indices_tgt_char = tgtCharGrad.indices_tgt_char;

        if params.charFeedOpt
          assert(params.attnFunc>0);
          grad.W_h_char = tgtCharGrad.W_h_char;
        else
          % add top grads from tgt char
          rareCount = 0;
          decLen = length(dec_top_grads);
          for tt=1:decLen
            rareIndices = find(tgtCharGrad.rareFlags(:, tt));
            dec_top_grads{tt}(:, rareIndices) = dec_top_grads{tt}(:, rareIndices) + tgtCharGrad.initEmb(:, rareCount+1:rareCount+length(rareIndices));
            rareCount = rareCount + length(rareIndices);
          end
          assert(rareCount == tgtCharGrad.numRareWords);
        end
      else
        grad.indices_tgt_char = [];
      end

      % clear tgtCharGrad;
      if params.debug
        fprintf(2, '# after clearing tgtCharGrad, %s\n', gpuInfo(params.gpu));
      end
    end
  else
    tgtCharGrad = [];
  end

  if isTest==1 % don't compute grad
    return;
  end

  %%%%%%%%%%%%%%%%%%%%%
  %%% BACKWARD PASS %%%
  %%%%%%%%%%%%%%%%%%%%%
  % h_t and c_t gradients accumulate over time per layer
  zeroGrad = cell(params.numLayers, 1);
  for ll=1:params.numLayers
    zeroGrad{ll} = zeroBatch;
  end

  %% decoder
  [dc, dh, grad.W_tgt, grad.W_emb_tgt, grad.indices_tgt, attnGrad, grad.srcHidVecs] = rnnLayerBackprop(model.W_tgt, ...
    decStates, lastEncState, dec_top_grads, zeroGrad, zeroGrad, tgtInput, trainData.tgtMask, params, decRnnFlags, ...
    attnInfos, trainData, model, tgtCharGrad);
  if params.attnFunc % copy attention grads
    [grad] = copyStruct(attnGrad, grad);
  end

  %% encoder
  if params.isBi
    enc_top_grads = cell(srcMaxLen - 1, 1);
    for tt=1:params.numSrcHidVecs % attention
      enc_top_grads{tt} = grad.srcHidVecs(:,:,tt);
    end

    [~, ~, grad.W_src, grad.W_emb_src, grad.indices_src, ~, ~] = rnnLayerBackprop(model.W_src, encStates, zeroState, ...
    enc_top_grads, dc, dh, trainData.srcInput, trainData.srcMask, params, encRnnFlags, attnInfos, trainData, model, []);

    % char backprop
    if params.charSrcRep
      if srcCharData.numRareWords > 0
        if isTest==0 && params.charSrcSample > 0 % also include frequent words
          rareFlags = ismember(grad.indices_src, srcCharData.rareWords);
        else
          rareFlags = grad.indices_src > params.srcCharShortList;
        end

        % rare
        srcCharGrad.embs = grad.W_emb_src(:, rareFlags);
        srcCharGrad.indices = grad.indices_src(rareFlags);

        % frequent
        grad.W_emb_src = grad.W_emb_src(:, ~rareFlags);
        grad.indices_src = grad.indices_src(~rareFlags);

        [grad.W_src_char, grad.W_emb_src_char, grad.indices_src_char] = srcCharLayerBackprop(model.W_src_char, srcCharData, srcCharGrad);
      else
        grad.indices_src_char = [];
      end
    end
  end

  % remove unused variables
  if params.attnFunc>0
    grad = rmfield(grad, 'srcHidVecs');
  end
end

function [grad, params] = initGrad(model, params)
  %% grad
  for ii=1:length(params.varsDenseUpdate)
    field = params.varsDenseUpdate{ii};
    if iscell(model.(field))
      for jj=1:length(model.(field)) % cell, like W_src, W_tgt
        grad.(field){jj} = zeroMatrix(size(model.(field){jj}), params.isGPU, params.dataType);
      end
    else
      grad.(field) = zeroMatrix(size(model.(field)), params.isGPU, params.dataType);
    end
  end

  %% backprop to src hidden states for attention and positional models
  if params.attnFunc>0
    % we extract trainData.srcHidVecs later, which contains all src hidden states, lstmSize * curBatchSize * numSrcHidVecs
    grad.srcHidVecs = zeroMatrix([params.lstmSize, params.curBatchSize, params.numSrcHidVecs], params.isGPU, params.dataType);
  end
end