function [Eft, Varft, lpyt, Eyt, Varyt] = gpep_pred(gp, x, y, varargin)
%GPEP_PRED  Predictions with Gaussian Process EP approximation
%
%  Description
%    [EFT, VARFT] = GPEP_PRED(GP, X, Y, XT, OPTIONS)
%    takes a GP structure together with matrix X of training
%    inputs and vector Y of training targets, and evaluates the
%    predictive distribution at test inputs XT. Returns a posterior
%    mean EFT and variance VARFT of latent variables.
%
%    [EFT, VARFT, LPYT] = GPEP_PRED(GP, X, Y, XT, 'yt', YT, OPTIONS)
%    returns also logarithm of the predictive density LPYT of the
%    observations YT at test input locations XT. This can be used
%    for example in the cross-validation. Here Y has to be a vector.
%
%    [EFT, VARFT, LPYT, EYT, VARYT] = GPEP_PRED(GP, X, Y, XT, OPTIONS)
%    returns also the posterior predictive mean EYT and variance VARYT.
%
%    [EF, VARF, LPY, EY, VARY] = GPEP_PRED(GP, X, Y, OPTIONS)
%    evaluates the predictive distribution at training inputs X
%    and logarithm of the predictive density LPY of the training
%    observations Y.
%
%    OPTIONS is optional parameter-value pair
%      predcf - an index vector telling which covariance functions are
%               used for prediction. Default is all (1:gpcfn).
%               See additional information below.
%      tstind - a vector/cell array defining, which rows of X belong
%               to which training block in *IC type sparse models.
%               Default is []. In case of PIC, a cell array
%               containing index vectors specifying the blocking
%               structure for test data. IN FIC and CS+FIC a
%               vector of length n that points out the test inputs
%               that are also in the training set (if none, set
%               TSTIND = [])
%      yt     - optional observed yt in test points (see below)
%      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.
%      zt     - optional observed quantity in triplet (xt_i,yt_i,zt_i)
%               Some likelihoods may use this. For example, in case of
%               Poisson likelihood we have z_i=E_i, that is, the expected
%               value for the ith case.
%      fcorr  - Method used for latent marginal posterior corrections.
%               Default is 'off'. For EP possible method is 'fact'.
%               If method is 'on', 'fact' is used for EP.
%
%    NOTE! In case of FIC and PIC sparse approximation the
%    prediction for only some PREDCF covariance functions is just
%    an approximation since the covariance functions are coupled in
%    the approximation and are not strictly speaking additive
%    anymore.
%
%    For example, if you use covariance such as K = K1 + K2 your
%    predictions Eft1 = gpep_pred(GP, X, Y, X, 'predcf', 1) and
%    Eft2 = gpep_pred(gp, x, y, x, 'predcf', 2) should sum up to
%    Eft = gpep_pred(gp, x, y, x). That is Eft = Eft1 + Eft2. With
%    FULL model this is true but with FIC and PIC this is true only
%    approximately. That is Eft \approx Eft1 + Eft2.
%
%    With CS+FIC the predictions are exact if the PREDCF covariance
%    functions are all in the FIC part or if they are CS
%    covariances.
%
%    NOTE! When making predictions with a subset of covariance
%    functions with FIC approximation the predictive variance can
%    in some cases be ill-behaved i.e. negative or unrealistically
%    small. This may happen because of the approximative nature of
%    the prediction.
%
%  See also
%    GPEP_E, GPEP_G, GP_PRED, DEMO_SPATIAL, DEMO_CLASSIFIC

% Copyright (c) 2007-2010 Jarno Vanhatalo
% Copyright (c) 2010      Heikki Peura
% Copyright (c) 2011      Pasi Jylänki
% Copyright (c) 2012 Aki Vehtari

% 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.

  ip=inputParser;
  ip.FunctionName = 'GPEP_PRED';
  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.addOptional('xt', [], @(x) isempty(x) || (isreal(x) && all(isfinite(x(:)))))
  ip.addParamValue('yt', [], @(x) isreal(x) && all(isfinite(x(:))))
  ip.addParamValue('z', [], @(x) isreal(x) && all(isfinite(x(:))))
  ip.addParamValue('zt', [], @(x) isreal(x) && all(isfinite(x(:))))
  ip.addParamValue('predcf', [], @(x) isempty(x) || ...
                   isvector(x) && isreal(x) && all(isfinite(x)&x>0))
  ip.addParamValue('tstind', [], @(x) isempty(x) || iscell(x) ||...
                   (isvector(x) && isreal(x) && all(isfinite(x)&x>0)))
  ip.addParamValue('fcorr', 'off', @(x) ismember(x, {'off', ...
                   'cm2', 'fact', 'on','lr'}))
  if numel(varargin)==0 || isnumeric(varargin{1})
    % inputParser should handle this, but it doesn't
    ip.parse(gp, x, y, varargin{:});
  else
    ip.parse(gp, x, y, [], varargin{:});
  end
  xt=ip.Results.xt;
  yt=ip.Results.yt;
  z=ip.Results.z;
  zt=ip.Results.zt;
  predcf=ip.Results.predcf;
  tstind=ip.Results.tstind;
  fcorr=ip.Results.fcorr;
  if isempty(xt)
    xt=x;
    if isempty(tstind)
      if iscell(gp)
        gptype=gp{1}.type;
      else
        gptype=gp.type;
      end
      switch gptype
        case {'FULL' 'VAR' 'DTC' 'SOR'}
          tstind = [];
        case {'FIC' 'CS+FIC'}
          tstind = 1:size(x,1);
        case 'PIC'
          if iscell(gp)
            tstind = gp{1}.tr_index;
          else
            tstind = gp.tr_index;
          end
      end
    end
    if isempty(yt)
      yt=y;
    end
    if isempty(zt)
      zt=z;
    end
  end

  if isfield(gp, 'monotonic') && gp.monotonic
      [gp,x,y,z,xt,zt] = gp.fh.setUpDataForMonotonic(gp,x,y,z,xt,zt);
  end

  [tn, tnin] = size(x);
  [n, nout] = size(y);

  if isfield(gp.lik, 'nondiagW')
    switch gp.type
      % ============================================================
      % FULL
      % ============================================================
      case 'FULL'
        %[e, edata, eprior, tautilde, nutilde, BKnu, B, cholP, invPBKnu]= gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        [e, edata, eprior, p]= gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isnan(e)
            Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
            return
        end
        [nutilde, BKnu, B, cholP, invPBKnu]=deal(p.nutilde, p.BKnu, p.B, p.cholP, p.invPBKnu);

        if isfield(gp, 'comp_cf')  % own covariance for each ouput component
          multicf = true;
          if length(gp.comp_cf) ~= nout
            error('GPEP_PRED: the number of component vectors in gp.comp_cf must be the same as number of outputs.')
          end
          if ~isempty(predcf)
            if ~iscell(predcf) || length(predcf)~=nout
              error(['GPEP_PRED: if own covariance for each output component is used,'...
                     'predcf has to be cell array and contain nout (vector) elements.   '])
            end
          else
            predcf = gp.comp_cf;
          end
        else
          multicf = false;
          for i1=1:nout
            predcf2{i1} = predcf;
          end
          predcf=predcf2;
        end

        ntest=size(xt,1);
        % covariances between the training and test latents
        Kt = zeros(ntest,n,nout);
        if multicf
          for i1=1:nout
            Kt(:,:,i1) = gp_cov(gp,xt,x,predcf{i1});
          end
        else
          for i1=1:nout
            Kt(:,:,i1) = gp_cov(gp,xt,x,predcf{i1});
          end
        end

        % full ep with non-diagonal site covariances
        zz=zeros(n*nout,1);
        for k1=1:nout
          zz((1:n)+(k1-1)*n)=BKnu(:,k1)-B(:,:,k1)*invPBKnu;
        end

        %- posterior predictive mean
        Eft=zeros(ntest*nout,1);
        for z1=1:nout
          Eft((1:ntest)+(z1-1)*ntest)=Kt(:,:,z1)*(nutilde(:,z1)-zz((1:n)+(z1-1)*n));
        end

        if nargout > 1
          % posterior predictive covariance
          Covf=zeros(nout, nout, ntest);

          invcholPBKt=zeros(n,ntest,nout);
          for k1=1:nout
            invcholPBKt(:,:,k1)=cholP\(B(:,:,k1)*Kt(:,:,k1)');
          end

          %- update posterior covariance
          for k1=1:nout
            % covariances for the test latents
            kstarstar = gp_trvar(gp,xt,predcf{i1});

            Covf(k1,k1,:)=kstarstar-sum(Kt(:,:,k1)'.*(B(:,:,k1)*Kt(:,:,k1)'))'+sum(invcholPBKt(:,:,k1).*invcholPBKt(:,:,k1))';
            for j1=(k1+1):nout
              Covf(k1,j1,:)=sum(invcholPBKt(:,:,k1).*invcholPBKt(:,:,j1));
              Covf(j1,k1,:)=Covf(k1,j1,:);
            end
          end
          Varft=Covf;

        end

        % ============================================================
        % FIC
        % ============================================================
      case 'FIC'        % Predictions with FIC sparse approximation for GP
                        % ============================================================
                        % PIC
                        % ============================================================
      case {'PIC' 'PIC_BLOCK'}        % Predictions with PIC sparse approximation for GP
                                      % ============================================================
                                      % CS+FIC
                                      % ============================================================
      case 'CS+FIC'        % Predictions with CS+FIC sparse approximation for GP
    end

  else % isfield(gp.lik, 'nondiagW')
    switch gp.type
      % ============================================================
      % FULL
      % ============================================================
      case 'FULL'        % Predictions with FULL GP model
        %[e, edata, eprior, tautilde, nutilde, L] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isfield(gp.lik, 'int_likparam')

          [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
          if isnan(e)
              Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
              return
          end
          [tautildee, nutildee, L, L2] = deal(p.tautilde, p.nutilde, p.L, p.La2);

          tautilde=tautildee(:,1);
          nutilde=nutildee(:,1);
          if isfield(gp.lik,'int_likparam') && gp.lik.int_likparam && ~gp.lik.inputparam
             % Give q(theta) to likelihood function to integrate ovet
            zt=[p.mf2 L2'*L2];
          end
          if isfield(gp.lik, 'int_magnitude') && gp.lik.int_magnitude && ~gp.lik.inputmagnitude
            zt=[zt p.mf3 p.La3'*p.La3];
          end
          if (isfield(gp.lik, 'int_likparam') && gp.lik.inputparam) || ...
             (isfield(gp.lik, 'int_magnitude') && gp.lik.inputmagnitude) ...
              || (isfield(gp.lik, 'int_likparam') && isfield(gp, 'comp_cf'))
            [K,C]=gp_trcov(gp,x,gp.comp_cf{1});
            kstarstar = gp_trvar(gp, xt, gp.comp_cf{1});
            K_nf=gp_cov(gp,xt,x,gp.comp_cf{1});
          else
            [K, C]=gp_trcov(gp,x);
            kstarstar = gp_trvar(gp, xt, predcf);
            K_nf=gp_cov(gp,xt,x,predcf);
          end
          ntest=size(xt,1);
          [n,nin] = size(x);

          if size(tautildee,2)==1 && all(tautilde > 0) && ~isequal(gp.latent_opt.optim_method, 'robust-EP')
            % This is the usual case where likelihood is log concave
            % for example, Poisson and probit
            sqrttautilde = sqrt(tautilde(:,1));
            Stildesqroot = sparse(1:n, 1:n, sqrttautilde, n, n);

            if ~isfield(gp,'meanf')
              if issparse(L) % If compact support covariance functions are used
                             % the covariance matrix will be sparse
                zz=Stildesqroot*ldlsolve(L,Stildesqroot*(C*nutilde));
              else
                zz=Stildesqroot*(L'\(L\(Stildesqroot*(C*nutilde))));
              end
              Eft=K_nf*(nutilde-zz);    % The mean, zero mean GP
            else
              zz = Stildesqroot*(L'\(L\(Stildesqroot*(C))));

              Eft_zm=K_nf*(nutilde-zz*nutilde); % The mean, zero mean GP
              Ks = eye(size(zz)) - zz;           % inv(K + S^-1)*S^-1
              Ksy = Ks*nutilde;
              [RB RAR] = mean_predf(gp,x,xt,K_nf',Ks,Ksy,'EP',Stildesqroot.^2);

              Eft = Eft_zm + RB;            % The mean
            end

            % Compute variance
            if nargout > 1
              if issparse(L)
                V = ldlsolve(L, Stildesqroot*K_nf');
                Varft = kstarstar - sum(K_nf.*(Stildesqroot*V)',2);
              else
                V = (L\Stildesqroot)*K_nf';
                Varft = kstarstar - sum(V.^2)';
              end
              if isfield(gp,'meanf')
                Varft = Varft + RAR;
              end
            end
          else
            % We might end up here if the likelihood is not log concave
            % For example Student-t likelihood.

            %{
            zz=tautilde.*(L'*(L*nutilde));
            Eft=K_nf*(nutilde-zz);

            if nargout > 1
              S = diag(tautilde);
              V = K_nf*S*L';
              Varft = kstarstar - sum((K_nf*S).*K_nf,2) + sum(V.^2,2);
            end
            %}

            % An alternative implementation for avoiding negative variances
            [Eft,V]=pred_var(tautilde,K,K_nf,nutilde);
            Varft=kstarstar-V;

          end
          if isfield(gp.lik, 'int_likparam') && gp.lik.int_likparam && gp.lik.inputparam
            tautilde=tautildee(:,2);
            nutilde=nutildee(:,2);
            [K, C]=gp_trcov(gp,x, gp.comp_cf{2});
            kstarstar = gp_trvar(gp, xt, gp.comp_cf{2});
            K_nf=gp_cov(gp,xt,x,gp.comp_cf{2});

            [Eft(:,2),V]=pred_var(tautilde,K,K_nf,nutilde);
            Varft(:,2)=kstarstar-V;
          end
          if isfield(gp.lik, 'int_magnitude') && gp.lik.int_magnitude && gp.lik.inputmagnitude
            tautilde=tautildee(:,end);
            nutilde=nutildee(:,end);
            [K, C]=gp_trcov(gp,x, gp.comp_cf{end});
            kstarstar = gp_trvar(gp, xt, gp.comp_cf{end});
            K_nf=gp_cov(gp,xt,x,gp.comp_cf{end});

            [Eft(:,end+1),V]=pred_var(tautilde,K,K_nf,nutilde);
            Varft(:,end+1)=kstarstar-V;
          end

        else % isfield(gp.lik, 'int_likparam')

          [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
          if isnan(e)
              Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
              return
          end
          [tautilde, nutilde, L] = deal(p.tautilde, p.nutilde, p.L);

          [K, C]=gp_trcov(gp,x);
          kstarstar = gp_trvar(gp, xt, predcf);
          ntest=size(xt,1);
          K_nf=gp_cov(gp,xt,x,predcf);
          [n,nin] = size(x);

          if all(tautilde > 0) ...
                  && ~(isequal(gp.latent_opt.optim_method, 'robust-EP') )
            % This is the usual case where likelihood is log concave
            % for example, Poisson and probit
            sqrttautilde = sqrt(tautilde);
            nstt=length(sqrttautilde);
            Stildesqroot = sparse(1:nstt, 1:nstt, sqrttautilde, nstt,  nstt);

            if ~isfield(gp,'meanf')
              if issparse(L)          % If compact support covariance functions a  re used
                                      % the covariance matrix will be sparse
                zz=Stildesqroot*ldlsolve(L,Stildesqroot*(C*nutilde));
              else
                zz=Stildesqroot*(L'\(L\(Stildesqroot*(C*nutilde))));
              end

              Eft=K_nf*(nutilde-zz);    % The mean, zero mean GP
            else
              zz = Stildesqroot*(L'\(L\(Stildesqroot*(C))));

              Eft_zm=K_nf*(nutilde-zz*nutilde); % The mean, zero mean GP
              Ks = eye(size(zz)) - zz;           % inv(K + S^-1)*S^-1
              Ksy = Ks*nutilde;
              [RB RAR] = mean_predf(gp,x,xt,K_nf',Ks,Ksy,'EP',Stildesqroot.^2);

              Eft = Eft_zm + RB;            % The mean
            end

            % Compute variance
            if nargout > 1
              if issparse(L)
                V = ldlsolve(L, Stildesqroot*K_nf');
                Varft = kstarstar - sum(K_nf.*(Stildesqroot*V)',2);
              else
                V = (L\Stildesqroot)*K_nf';
                Varft = kstarstar - sum(V.^2)';
              end
              if isfield(gp,'meanf')
                Varft = Varft + RAR;
              end
            end
          else
            % We might end up here if the likelihood is not log concave
            % For example Student-t likelihood.

            %{
            zz=tautilde.*(L'*(L*nutilde));
            Eft=K_nf*(nutilde-zz);

            if nargout > 1
              S = diag(tautilde);
              V = K_nf*S*L';
              Varft = kstarstar - sum((K_nf*S).*K_nf,2) + sum(V.^2,2);
            end
            %}

            % An alternative implementation for avoiding negative variances
            [Eft,V]=pred_var(tautilde,K,K_nf,nutilde);
            Varft=kstarstar-V;

          end

        end % isfield(gp.lik, 'int_likparam')
        % ============================================================
        % FIC
        % ============================================================
      case 'FIC'        % Predictions with FIC sparse approximation for GP
                        %[e, edata, eprior, tautilde, nutilde, L, La, b] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isnan(e)
            Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
            return
        end
        [tautilde, nutilde, L, La, b] = deal(p.tautilde, p.nutilde, p.L, p.La2, p.b);

        % Here tstind = 1 if the prediction is made for the training set
        if nargin > 6
          if ~isempty(tstind) && length(tstind) ~= size(x,1)
            error('tstind (if provided) has to be of same length as x.')
          end
        else
          tstind = [];
        end

        u = gp.X_u;
        m = size(u,1);

        K_fu = gp_cov(gp,x,u,predcf);          % f x u
        K_nu=gp_cov(gp,xt,u,predcf);
        K_uu = gp_trcov(gp,u,predcf);          % u x u, noiseless covariance K_uu
        K_uu = (K_uu+K_uu')./2;                % ensure the symmetry of K_uu

        kstarstar=gp_trvar(gp,xt,predcf);

        if all(tautilde > 0) && ~isequal(gp.latent_opt.optim_method, 'robust-EP')

          % From this on evaluate the prediction
          % See Snelson and Ghahramani (2007) for details
          %        p=iLaKfu*(A\(iLaKfu'*mutilde));
          p = b';

          ntest=size(xt,1);

          Eft = K_nu*(K_uu\(K_fu'*p));

          % if the prediction is made for training set, evaluate Lav also for prediction points
          if ~isempty(tstind)
            [Kv_ff, Cv_ff] = gp_trvar(gp, xt(tstind,:), predcf);
            Luu = chol(K_uu)';
            B=Luu\(K_fu');
            Qv_ff=sum(B.^2)';
            Lav = Kv_ff-Qv_ff;
            Eft(tstind) = Eft(tstind) + Lav.*p;
          end

          % Compute variance
          if nargout > 1
            %Varft(i1,1)=kstarstar(i1) - (sum(Knf(i1,:).^2./La') - sum((Knf(i1,:)*L).^2));
            Luu = chol(K_uu)';
            B=Luu\(K_fu');
            B2=Luu\(K_nu');
            Varft = kstarstar - sum(B2'.*(B*(repmat(La,1,m).\B')*B2)',2)  + sum((K_nu*(K_uu\(K_fu'*L))).^2, 2);

            % if the prediction is made for training set, evaluate Lav also for prediction points
            if ~isempty(tstind)
              Varft(tstind) = Varft(tstind) - 2.*sum( B2(:,tstind)'.*(repmat((La.\Lav),1,m).*B'),2) ...
                  + 2.*sum( B2(:,tstind)'*(B*L).*(repmat(Lav,1,m).*L), 2)  ...
                  - Lav./La.*Lav + sum((repmat(Lav,1,m).*L).^2,2);
            end
          end

        else
          % Robust-EP
          [Eft,V]=pred_var2(tautilde,nutilde,L,K_uu,K_fu,b,K_nu);
          Varft=kstarstar-V;

        end

        % ============================================================
        % PIC
        % ============================================================
      case {'PIC' 'PIC_BLOCK'}        % Predictions with PIC sparse approximation for GP
                                      % Calculate some help matrices
        u = gp.X_u;
        ind = gp.tr_index;
        %[e, edata, eprior, tautilde, nutilde, L, La, b] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isnan(e)
            Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
            return
        end
        [L, La, b] = deal(p.L, p.La2, p.b);

        K_fu = gp_cov(gp, x, u, predcf);         % f x u
        K_nu = gp_cov(gp, xt, u, predcf);         % n x u
        K_uu = gp_trcov(gp, u, predcf);    % u x u, noiseles covariance K_uu

        % From this on evaluate the prediction
        % See Snelson and Ghahramani (2007) for details
        %        p=iLaKfu*(A\(iLaKfu'*mutilde));
        p = b';

        iKuuKuf = K_uu\K_fu';

        w_bu=zeros(length(xt),length(u));
        w_n=zeros(length(xt),1);
        for i=1:length(ind)
          w_bu(tstind{i},:) = repmat((iKuuKuf(:,ind{i})*p(ind{i},:))', length(tstind{i}),1);
          K_nf = gp_cov(gp, xt(tstind{i},:), x(ind{i},:), predcf);              % n x u
          w_n(tstind{i},:) = K_nf*p(ind{i},:);
        end

        Eft = K_nu*(iKuuKuf*p) - sum(K_nu.*w_bu,2) + w_n;

        % Compute variance
        if nargout > 1
          kstarstar = gp_trvar(gp, xt, predcf);
          KnfL = K_nu*(iKuuKuf*L);
          Varft = zeros(length(xt),1);
          for i=1:length(ind)
            v_n = gp_cov(gp, xt(tstind{i},:), x(ind{i},:), predcf);              % n x u
            v_bu = K_nu(tstind{i},:)*iKuuKuf(:,ind{i});
            KnfLa = K_nu*(iKuuKuf(:,ind{i})/chol(La{i}));
            KnfLa(tstind{i},:) = KnfLa(tstind{i},:) - (v_bu + v_n)/chol(La{i});
            Varft = Varft + sum((KnfLa).^2,2);
            KnfL(tstind{i},:) = KnfL(tstind{i},:) - v_bu*L(ind{i},:) + v_n*L(ind{i},:);
          end
          Varft = kstarstar - (Varft - sum((KnfL).^2,2));

        end
        % ============================================================
        % CS+FIC
        % ============================================================
      case 'CS+FIC'        % Predictions with CS+FIC sparse approximation for GP
                           % Here tstind = 1 if the prediction is made for the training set
        if nargin > 6
          if ~isempty(tstind) && length(tstind) ~= size(x,1)
            error('tstind (if provided) has to be of same length as x.')
          end
        else
          tstind = [];
        end

        u = gp.X_u;
        m = length(u);
        n = size(x,1);
        n2 = size(xt,1);

        %[e, edata, eprior, tautilde, nutilde, L, La, b] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isnan(e)
            Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
            return
        end
        [L, La, b] = deal(p.L, p.La2, p.b);

        % Indexes to all non-compact support and compact support covariances.
        cf1 = [];
        cf2 = [];
        % Indexes to non-CS and CS covariances, which are used for predictions
        predcf1 = [];
        predcf2 = [];

        ncf = length(gp.cf);
        % Loop through all covariance functions
        for i = 1:ncf
          % Non-CS covariances
          if ~isfield(gp.cf{i},'cs')
            cf1 = [cf1 i];
            % If used for prediction
            if ~isempty(find(predcf==i))
              predcf1 = [predcf1 i];
            end
            % CS-covariances
          else
            cf2 = [cf2 i];
            % If used for prediction
            if ~isempty(find(predcf==i))
              predcf2 = [predcf2 i];
            end
          end
        end
        if isempty(predcf1) && isempty(predcf2)
          predcf1 = cf1;
          predcf2 = cf2;
        end

        % Determine the types of the covariance functions used
        % in making the prediction.
        if ~isempty(predcf1) && isempty(predcf2)       % Only non-CS covariances
          ptype = 1;
          predcf2 = cf2;
        elseif isempty(predcf1) && ~isempty(predcf2)   % Only CS covariances
          ptype = 2;
          predcf1 = cf1;
        else                                           % Both non-CS and CS covariances
          ptype = 3;
        end

        K_fu = gp_cov(gp,x,u,predcf1);   % f x u
        K_uu = gp_trcov(gp,u,predcf1);     % u x u, noiseles covariance K_uu
        K_uu = (K_uu+K_uu')./2;     % ensure the symmetry of K_uu
        K_nu=gp_cov(gp,xt,u,predcf1);

        Kcs_nf = gp_cov(gp, xt, x, predcf2);

        p = b';
        ntest=size(xt,1);

        % Calculate the predictive mean according to the type of
        % covariance functions used for making the prediction
        if ptype == 1
          Eft = K_nu*(K_uu\(K_fu'*p));
        elseif ptype == 2
          Eft = Kcs_nf*p;
        else
          Eft = K_nu*(K_uu\(K_fu'*p)) + Kcs_nf*p;
        end

        % evaluate also Lav if the prediction is made for training set
        if ~isempty(tstind)
          [Kv_ff, Cv_ff] = gp_trvar(gp, xt(tstind,:), predcf1);
          Luu = chol(K_uu)';
          B=Luu\(K_fu');
          Qv_ff=sum(B.^2)';
          Lav = Kv_ff-Qv_ff;
        end

        % Add also Lav if the prediction is made for training set
        % and non-CS covariance function is used for prediction
        if ~isempty(tstind) && (ptype == 1 || ptype == 3)
          Eft(tstind) = Eft(tstind) + Lav.*p;
        end

        % Evaluate the variance
        if nargout > 1
          Knn_v = gp_trvar(gp,xt,predcf);
          Luu = chol(K_uu)';
          B=Luu\(K_fu');
          B2=Luu\(K_nu');
          p = amd(La);
          iLaKfu = La\K_fu;
          % Calculate the predictive variance according to the type
          % covariance functions used for making the prediction
          if ptype == 1 || ptype == 3
            % FIC part of the covariance
            Varft = Knn_v - sum(B2'.*(B*(La\B')*B2)',2) + sum((K_nu*(K_uu\(K_fu'*L))).^2, 2);
            % Add Lav2 if the prediction is made for the training set
            if  ~isempty(tstind)
              % Non-CS covariance
              if ptype == 1
                Kcs_nf = sparse(tstind,1:n,Lav,n2,n);
                % Non-CS and CS covariances
              else
                Kcs_nf = Kcs_nf + sparse(tstind,1:n,Lav,n2,n);
              end
              % Add Lav2 inside Kcs_nf
              Varft = Varft - sum((Kcs_nf(:,p)/chol(La(p,p))).^2,2) + sum((Kcs_nf*L).^2, 2) ...
                      - 2.*sum((Kcs_nf*iLaKfu).*(K_uu\K_nu')',2) + 2.*sum((Kcs_nf*L).*(L'*K_fu*(K_uu\K_nu'))' ,2);
              % In case of both non-CS and CS prediction covariances add
              % only Kcs_nf if the prediction is not done for the training set
            elseif ptype == 3
              Varft = Varft - sum((Kcs_nf(:,p)/chol(La(p,p))).^2,2) + sum((Kcs_nf*L).^2, 2) ...
                      - 2.*sum((Kcs_nf*iLaKfu).*(K_uu\K_nu')',2) + 2.*sum((Kcs_nf*L).*(L'*K_fu*(K_uu\K_nu'))' ,2);
            end
            % Prediction with only CS covariance
          elseif ptype == 2
            Varft = Knn_v - sum((Kcs_nf(:,p)/chol(La(p,p))).^2,2) + sum((Kcs_nf*L).^2, 2) ;
          end
        end
        % ============================================================
        % DTC/(VAR)
        % ============================================================
      case {'DTC' 'VAR' 'SOR'}        % Predictions with DTC or variational sparse approximation for GP
                                      %[e, edata, eprior, tautilde, nutilde, L, La, b] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        [e, edata, eprior, p] = gpep_e(gp_pak(gp), gp, x, y, 'z', z);
        if isnan(e)
            Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
            return
        end
        [L, La, b] = deal(p.L, p.La2, p.b);

        % Here tstind = 1 if the prediction is made for the training set
        if nargin > 6
          if ~isempty(tstind) && length(tstind) ~= size(x,1)
            error('tstind (if provided) has to be of same length as x.')
          end
        else
          tstind = [];
        end

        u = gp.X_u;
        m = size(u,1);

        K_fu = gp_cov(gp,x,u,predcf);         % f x u
        K_nu=gp_cov(gp,xt,u,predcf);
        K_uu = gp_trcov(gp,u,predcf);          % u x u, noiseles covariance K_uu
        K_uu = (K_uu+K_uu')./2;          % ensure the symmetry of K_uu

        kstarstar=gp_trvar(gp,xt,predcf);

        % From this on evaluate the prediction
        p = b';

        ntest=size(xt,1);

        Eft = K_nu*(K_uu\(K_fu'*p));

        % if the prediction is made for training set, evaluate Lav also for prediction points
        if ~isempty(tstind)
          [Kv_ff, Cv_ff] = gp_trvar(gp, xt(tstind,:), predcf);
          Luu = chol(K_uu)';
          B=Luu\(K_fu');
          Qv_ff=sum(B.^2)';
          Lav = Kv_ff-Cv_ff;
          Eft(tstind) = Eft(tstind);% + Lav.*p;
        end

        if nargout > 1
          % Compute variances of predictions
          %Varft(i1,1)=kstarstar(i1) - (sum(Knf(i1,:).^2./La') - sum((Knf(i1,:)*L).^2));
          Luu = chol(K_uu)';
          B=Luu\(K_fu');
          B2=Luu\(K_nu');

          Varft = sum(B2'.*(B*(repmat(La,1,m).\B')*B2)',2)  + sum((K_nu*(K_uu\(K_fu'*L))).^2, 2);
          switch gp.type
            case {'VAR' 'DTC'}
              Varft = kstarstar - Varft;
            case 'SOR'
              Varft = sum(B2.^2,1)' - Varft;
          end
        end
    end
  end
  if ~isequal(fcorr, 'off')
    % Do marginal corrections for samples
    [pc_predm, fvecm] = gp_predcm(gp, x, y, xt, 'z', z, 'ind', 1:size(xt,1), 'fcorr', fcorr);
    for i=1:size(xt,1)
      % Remove NaNs and zeros
      pc_pred=pc_predm(:,i);
      dii=isnan(pc_pred)|pc_pred==0;
      pc_pred(dii)=[];
      fvec=fvecm(:,i);
      fvec(dii)=[];
      % Compute mean correction
      Eft(i) = trapz(fvec.*(pc_pred./sum(pc_pred)));
    end
   end

  % ============================================================
  % Evaluate also the predictive mean and variance of new observation(s)
  % ============================================================
  if ~isequal(fcorr, 'off')
    if nargout == 3
      if isempty(yt)
        lpyt=[];
      else
        lpyt = gp.lik.fh.predy(gp.lik, fvecm', pc_predm', yt, zt);
      end
    elseif nargout > 3
      [lpyt, Eyt, Varyt] = gp.lik.fh.predy(gp.lik, fvecm', pc_predm', yt, zt);
    end
  else
    if nargout == 3
      if isempty(yt)
        lpyt=[];
      else
        lpyt = gp.lik.fh.predy(gp.lik, Eft, Varft, yt, zt);
      end
    elseif nargout > 3
      [lpyt, Eyt, Varyt] = gp.lik.fh.predy(gp.lik, Eft, Varft, yt, zt);
    end
  end
end

function [m,S]=pred_var(tau_q,K,A,b)

% helper function for determining
%
% m = A * inv( K+ inv(diag(tau_q)) ) * inv(diag(tau_q)) *b
% S = diag( A * inv( K+ inv(diag(tau_q)) ) * A)
%
% when the site variances tau_q may be negative
%

  ii1=find(tau_q>0); n1=length(ii1); W1=sqrt(tau_q(ii1));
  ii2=find(tau_q<0); n2=length(ii2); W2=sqrt(abs(tau_q(ii2)));

  m=A*b;
  b=K*b;
  S=zeros(size(A,1),1);
  u=0;
  U=0;
  L1=[];
  if ~isempty(ii1)
    % Cholesky decomposition for the positive sites
    L1=(W1*W1').*K(ii1,ii1);
    L1(1:n1+1:end)=L1(1:n1+1:end)+1;
    L1=chol(L1);

    U = bsxfun(@times,A(:,ii1),W1')/L1;
    u = L1'\(W1.*b(ii1));

    m = m-U*u;
    S = S+sum(U.^2,2);
  end

  if ~isempty(ii2)
    % Cholesky decomposition for the negative sites
    V=bsxfun(@times,K(ii2,ii1),W1')/L1;
    if isempty(V)
        V=0;
    end
    L2=(W2*W2').*(V*V'-K(ii2,ii2));
    L2(1:n2+1:end)=L2(1:n2+1:end)+1;

    [L2,pd]=chol(L2);
    if pd==0
      U = bsxfun(@minus, bsxfun(@times,A(:,ii2),W2')/L2,U*(bsxfun(@times,V,W2)'/L2));
      u = L2'\(W2.*b(ii2)) -L2'\(bsxfun(@times,V,W2)*u);

      m = m+U*u;
      S = S-sum(U.^2,2);
    else
      fprintf('Posterior covariance is negative definite.\n')
    end
  end

end

function [m_q,S_q]=pred_var2(tautilde,nutilde,L,K_uu,K_fu,D,K_nu)

% function for determining the parameters of the q-distribution
% when site variances tau_q may be negative
%
% q(f) = N(f|0,K)*exp( -0.5*f'*diag(tau_q)*f + nu_q'*f )/Z_q = N(f|m_q,S_q)
%
% S_q = inv(inv(K)+diag(tau_q)) where K is sparse approximation for prior
%       covariance
% m_q = S_q*nu_q;
%
% det(eye(n)+K*diag(tau_q))) = det(L1)^2 * det(L2)^2
% where L1 and L2 are upper triangular
%
% see Expectation consistent approximate inference (Opper & Winther, 2005)

  n=length(nutilde);

  U = K_fu;
  S = 1+tautilde.*D;
  B = tautilde./S;
  BUiL = bsxfun(@times, B, U)/L';
  % iKS = diag(B) - BUiL*BUiL';

  Ktnu = D.*nutilde + U*(K_uu\(U'*nutilde));
  m_q = nutilde - B.*Ktnu + BUiL*(BUiL'*Ktnu);
  kstar = K_nu*(K_uu\K_fu');
  m_q = kstar*m_q;

  S_q = sum(bsxfun(@times,B',kstar.^2),2) - sum((kstar*BUiL).^2,2);
  % S_q = kstar*iKS*kstar';

end


classdef prtRvMvn < prtRv & prtRvMemebershipModel
    % prtRvMvn  Multivariate normal random variable
    %
    %   RV = prtRvMvn creates a prtRvMvn object with empty mean and
    %   covariance matrices. The mean and covariance matrices must be set
    %   either directly, or by calling the MLE method.
    %
    %   RV = prtRvMvn('covarianceStructure', VALUE) enforces a covariance
    %   structure, which may be either 'full', 'spherical', or 'diagonal'.
    %   Setting this property to 'spherical' or 'diagonal' will enforce
    %   this structure onto the existing covariance matrix, or one
    %   estimated by calling the MLE method.
    %
    %   RV = prtRvMvn(PROPERTY1, VALUE1,...) creates a prtRvMv object RV
    %   with properties as specified by PROPERTY/VALUE pairs.
    %
    %   A prtRvMvn object inherits all properties from the prtRv class. In
    %   addition, it has the following properties:
    %
    %   covarianceStructure - A string specifying the structure of the
    %                         covariance matrix to estimate or enforce.
    %                         Valid values are 'full','spherical', or
    %                         'diagonal'
    %   mu                  - The mean of the distribution, which is
    %                         a 1 x nDimensions vector.
    %   sigma               - The covariance matrix of the distribution,
    %                         which is a nDimensions x nDimensions
    %                         matrix.
    %
    %  A prtRvMvn object inherits all methods from the prtRv class. The MLE
    %  method can be used to estimate the distribution parameters from
    %  data.
    %
    %  Example:
    %
    %  dataSet    = prtDataGenUnimodal;   % Load a dataset consisting of 2
    %                                     % classes
    %  % Extract one of the classes from the dataSet
    %  dataSetOneClass = prtDataSetClass(dataSet.getObservationsByClass(1));
    %
    %  RV = prtRvMvn;                       % Create a prtRvMvn object
    %  RV = RV.mle(dataSetOneClass.getX);   % Compute the maximum
    %                                       % likelihood estimate from the
    %                                       % data
    %  RV.plotPdf                           % Plot the pdf
    %
    %  RVspec = prtRvMvn;                   % Create another prtRvMvn
    %                                       % object
    %  RVspec.mu = [1 2];                   % Specify the mean
    %  RVspec.sigma = [2 -1; -1 2]          % Specify the covariance
    %  figure;
    %  RVspec.plotPdf                       % Plot the pdf
    %  sample = RVspec.draw(1)              % Draw 1 random sample from the
    %                                       % Distribution
    %
    %   See also: prtRv, prtRvGmm, prtRvMultinomial, prtRvUniform,
    %   prtRvUniformImproper, prtRvVq, prtRvDiscrete

    properties (SetAccess = 'private')
        name = 'Multi-Variate Normal';
        nameAbbreviation = 'RVMVN';
    end

    properties (SetAccess = 'protected')
        isSupervised = false;
        isCrossValidateValid = true;
    end

    properties (Dependent)
        covarianceStructure  % The covariance structure
        mu                   % The mean vector
        sigma                % The covariance matrix
    end
    properties (SetAccess = 'private', GetAccess = 'private', Hidden = true)
        muDepHelper
        sigmaDepHelper
        covarianceStructureDepHelper = 'full';
    end

    properties (Hidden = true)
        covarianceBias = [];
    end

    properties (Hidden = true, Dependent = true)
        nDimensions
    end

    properties (SetAccess = 'private', Hidden = true)
        trueCovariance
        badCovariance = false;
    end

    methods
        function R = prtRvMvn(varargin)
            R = constructorInputParse(R,varargin{:});
        end

        function R = mle(R,X)
           % MLE Compute the maximum likelihood estimate
            %
            % RV = RV.mle(X) computes the maximum likelihood estimate based
            % the data X. X should be nObservations x nDimensions.

            X = R.dataInputParse(X); % Basic error checking etc

            R.mu = mean(X,1);
            if size(X,1) == 1
                % A single observation
                % This is bad..
                % You can't call cov for this case
                % Since we always want to output sum( (x_i - u)'*(x_i - u))
                % We will output a matrix of zeros of the correct size.
                % Error checking for a propert covariance will happen later
                % after the bias has been applied.
                R.sigma = zeros(size(X,2));
            else
                R.sigma = cov(X);
            end

        end

        function vals = pdf(R,X)
            % PDF Output the pdf of the random variable evaluated at the points specified
            %
            % pdf = RV.pdf(X) returns  the pdf of the prtRv
            % object evaluated at X. X must be an N x nDims matrix, where
            % N is the number of locations to evaluate the pdf, and nDims
            % is the same as the number of dimensions, nDimensions, of the
            % prtRv object RV.

            [isValid, reasonStr] = R.isValid;
            assert(isValid,'PDF cannot yet be evaluated. This RV is not yet valid %s.',reasonStr);

            assert(~R.badCovariance,'Covariance matrix is not positive definite. Consider modifying "covarianceStructure".');
            X = R.dataInputParse(X); % Basic error checking etc

            assert(size(X,2) == R.nDimensions,'Data, RV dimensionality missmatch. Input data, X, has dimensionality %d and this RV has dimensionality %d.', size(X,2), R.nDimensions)
            assert(isnumeric(X) && ndims(X)==2,'X must be a 2D numeric array.');

            vals = exp(prtRvUtilMvnLogPdf(X,R.mu,R.sigma));
        end

        function vals = logPdf(R,X)
            % LOGPDF Output the log pdf of the random variable evaluated at the points specified
            %
            % logpdf = RV.logpdf(X) returns the logarithm of value of the
            % pdf of the prtRv object evaluated at X. X must be an N x
            % nDims matrix, where N is the number of locations to evaluate
            % the pdf, and nDims is the same as the number of dimensions,
            % nDimensions, of the prtRv object RV.

            X = R.dataInputParse(X); % Basic error checking etc
            try
               vals = prtRvUtilMvnLogPdf(X,R.mu,R.sigma);
            catch ME
                [isValid, reasonStr] = R.isValid;
                assert(isValid,'LOGPDF cannot yet be evaluated. This RV is not yet valid %s.',reasonStr);
                assert(size(X,2) == R.nDimensions,'Data, RV dimensionality missmatch. Input data, X, has dimensionality %d and this RV has dimensionality %d.', size(X,2), R.nDimensions)
                throw(ME);
            end
        end

        function varargout = plotCdf(R,varargin)
            % PLOTCDF Plots the CDF of the prtRv
            assert(R.nDimensions == 1,'prtRvMvn.plotCdf can only be used for 1D RV objects.');

            varargout = cell(nargout,1);

            [varargout{:}] = plotCdf@prtRv(R,varargin{:});

        end

        function vals = cdf(R,X)
            % CDF Output the cdf of the random variable evaluated at the points specified
            %
            % cdf = RV.cdf(X) returns the value of the cdf of the prtRv
            % object evaluated at X. X must be an N x nDims matrix, where
            % N is the number of locations to evaluate the pdf, and nDims
            % is the same as the number of dimensions, nDimensions, of the
            % prtRv object RV.

            [isValid, reasonStr] = R.isValid;
            assert(isValid,'CDF cannot yet be evaluated. This RV is not yet valid %s.',reasonStr);

            assert(~R.badCovariance,'Covariance matrix is not positive definite. Consider modifying "covarianceStructure"');
            X = R.dataInputParse(X); % Basic error checking etc

            assert(size(X,2) == R.nDimensions,'Data, RV dimensionality missmatch. Input data, X, has dimensionality %d and this RV has dimensionality %d.', size(X,2), R.nDimensions)
            vals = prtRvUtilMvnCdf(X,R.mu,R.sigma);
        end

        function vals = draw(R,N)
            % DRAW  Draw random samples from the distribution described by the prtRv object
            %
            % VAL = RV.draw(N) generates N random samples drawn from the
            % distribution described by the prtRv object RV. VAL will be a
            % N x nDimensions vector, where nDimensions is the number of
            % dimensions of RV.

            assert(numel(N)==1 && N==floor(N) && N > 0,'N must be a positive integer scalar.')
            [isValid, reasonStr] = R.isValid;
            assert(isValid,'DRAW cannot yet be evaluated. This RV is not yet valid %s.',reasonStr);

            assert(~R.badCovariance,'Covariance matrix is not positive definite. Consider modifying "covarianceStructure"');
            vals = prtRvUtilMvnDraw(R.mu,R.sigma,N);
        end
    end

    methods (Hidden=true)
        function [val, reasonStr] = isValid(R)

            if numel(R) > 1
                val = false(size(R));
                for iR = 1:numel(R)
                    [val(iR), reasonStr] = isValid(R(iR));
                end
                return
            end

            val = ~isempty(R.sigma) && ~isempty(R.mu);

            if val
                reasonStr = '';
            else
                badCov = isempty(R.sigma);
                badMean = isempty(R.mu);

                if badCov && ~badMean
                    reasonStr = 'because sigma has not been set';
                elseif ~badCov && badMean
                    reasonStr = 'because mu has not been set';
                elseif badCov && badMean
                    reasonStr = 'because mu and sigma have not been set';
                else
                    reasonStr = 'because of an unknown reason';
                end
            end
        end

        function val = plotLimits(R)
            [isValid, reasonStr] = R.isValid;
            if isValid
                minX = min(R.mu, [], 1)' - 2*sqrt(diag(R.sigma));
                maxX = max(R.mu, [], 1)' + 2*sqrt(diag(R.sigma));

                val = zeros(1,2*R.nDimensions);
                val(1:2:R.nDimensions*2-1) = minX;
                val(2:2:R.nDimensions*2) = maxX;
            else
                error('prtRvMvn:plotLimits','Plotting limits can not be determined for this RV. It is not yet valid %s',reasonStr)
            end
        end

        function R = weightedMle(R,X,weights)
            assert(numel(weights)==size(X,1),'The number of weights must mach the number of observations.');

            weights = weights(:);

            Nbar = sum(weights);
            R.mu = 1/Nbar*sum(bsxfun(@times,X,weights),1);
            X = bsxfun(@times,bsxfun(@minus,X,R.mu),sqrt(weights));
            R.sigma = 1/Nbar*(X'*X);
        end
    end

    % Get methods
    methods
        function val = get.nDimensions(R)
            val = getNumDimensions(R);
        end

        function val = get.mu(R)
            val = R.muDepHelper;
        end
        function val = get.sigma(R)
            val = R.sigmaDepHelper;
        end
        function val = get.covarianceStructure(R)
            val = R.covarianceStructureDepHelper;
        end
    end
    methods (Access = 'protected')
        function val = getNumDimensions(R)
            if ~isempty(R.mu)
                val = length(R.mu);
            elseif ~isempty(R.sigma)
                val = size(R.sigma,2);
            else
                val = [];
            end
        end
    end
    % Set Methods
    methods
        function R = set.covarianceStructure(R,covarianceStructure)
            % Find and fix known abbreviations

            assert(ischar(covarianceStructure),'covarianceStructure must be a string that is either, full, diagonal, or spherical');

            covarianceStructure = lower(covarianceStructure);

            if strcmpi(covarianceStructure,'diag')
                covarianceStructure = 'diagonal';
            end

            % Limit the options for the covariance structure
            if ~(strcmpi(covarianceStructure,'full') || ...
                    strcmpi(covarianceStructure,'diagonal') || ...
                    strcmpi(covarianceStructure,'spherical'))
                error('%s is not a valid covariance structure. Possible types are, full, diagonal, and spherical',covarianceStructure);
            end
            R.covarianceStructureDepHelper = covarianceStructure;

            % Redo the covariance to reflect the updated covarianceStructure
            if ~isempty(R.sigma)
                R.sigma = R.trueCovariance; % This will call set.sigma()
            end
        end

        function R = set.mu(R,meanVal)
            if ~isempty(R.sigma) && size(meanVal,2) ~= size(R.sigma,2)
                error('prtRvMvn:dimensions','Dimensions mismatch between supplied mu and prtRvMvn dimensionality');
            end
            R.muDepHelper = meanVal;
        end

        function R = set.sigma(R,sigma)
            if size(sigma,1) ~= size(sigma,2)
                error('Covariance matrix must be square.')
            end

            if ~isempty(R.mu) && size(sigma,1) ~= R.nDimensions
                error('prtRvMvn:dimensions','Dimensions mismatch between sigma and prtRvMvn dimensionality')
            end

            % Save this input as a true hidden sigma
            R.trueCovariance = sigma;

            % Enforce the covariance structure
            switch lower(R.covarianceStructure)
                case 'full'
                    R.sigmaDepHelper = sigma;
                case 'diagonal'
                    R.sigmaDepHelper = eye(size(sigma)).*sigma;
                case 'spherical'
                    R.sigmaDepHelper = eye(size(sigma))*mean(diag(sigma));
            end

            if ~isempty(R.covarianceBias)
                R.sigmaDepHelper = R.sigmaDepHelper + R.covarianceBias.*eye(size(sigma));
            end

            [dontNeed, cholErr] = chol(R.sigmaDepHelper); %#ok<ASGLU>
            if cholErr ~=0
                R.badCovariance = true;
                warning('prt:prtRvMvn','Covariance matrix is not positive definite. This may cause errors. Consider modifying "covarianceStructure".');
            end
        end
    end
end


function [MDP] = spm_MDP_check(MDP)
% MDP structure checking
% FORMAT [MDP] = spm_MDP_check(MDP)
%
% MDP.V(T - 1,P,F)      - P allowable policies of T moves over F factors
% or
% MDP.U(1,P,F)          - P allowable actions at each move
% MDP.T                 - number of outcomes
%
% MDP.A{G}(O,N1,...,NF) - likelihood of O outcomes given hidden states
% MDP.B{F}(NF,NF,MF)    - transitions among hidden under MF control states
% MDP.C{G}(O,T)         - prior preferences over O outcomes in modality G
% MDP.D{F}(NF,1)        - prior probabilities over initial states
%
% MDP.a{G}              - concentration parameters for A
% MDP.b{F}              - concentration parameters for B
% MDP.d{F}              - concentration parameters for D
%
% optional:
% MDP.s(F,T)            - vector of true states - for each hidden factor
% MDP.o(G,T)            - vector of outcome     - for each outcome modality
% MDP.u(F,T - 1)        - vector of action      - for each hidden factor
% MDP.w(1,T)            - vector of precisions
%
% if C or D are not specified, they will be set to default values (of no
% preferences and uniform priors over initial steps).  If there are no
% policies, it will be assumed that I = 1 and all policies (for each
% marginal hidden state) are allowed.
%__________________________________________________________________________
% Copyright (C) 2005 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_MDP_check.m 7766 2020-01-05 21:37:39Z karl $

% deal with a sequence of trials
%==========================================================================

% if there are multiple structures check each separately
%--------------------------------------------------------------------------
if numel(MDP) > 1
    for m = 1:size(MDP,1)
        for i = 1:size(MDP,2)
            mdp(m,i) = spm_MDP_check(MDP(m,i));
        end
    end
    MDP   = mdp;
    return
end

% fill in (posterior or  process) likelihood and priors
%--------------------------------------------------------------------------
if ~isfield(MDP,'A'), MDP.A = MDP.a; end
if ~isfield(MDP,'B'), MDP.B = MDP.b; end

% check format of likelihood and priors
%--------------------------------------------------------------------------
if ~iscell(MDP.A), MDP.A = {full(MDP.A)}; end
if ~iscell(MDP.B), MDP.B = {full(MDP.B)}; end

if isfield(MDP,'a'), if ~iscell(MDP.a), MDP.a = {full(MDP.a)}; end; end
if isfield(MDP,'b'), if ~iscell(MDP.b), MDP.b = {full(MDP.b)}; end; end

% check dimensions and orders
%==========================================================================

% numbers of transitions, policies and states
%--------------------------------------------------------------------------
Nf  = numel(MDP.B);                 % number of hidden state factors
Ng  = numel(MDP.A);                 % number of outcome factors
for f = 1:Nf
    Nu(f)    = size(MDP.B{f},3);    % number of hidden controls
    Ns(f)    = size(MDP.B{f},1);    % number of hidden states
    MDP.B{f} = double(MDP.B{f});
end
for g = 1:Ng
    No(g)    = size(MDP.A{g},1);    % number of outcomes
    MDP.A{g} = double(MDP.A{g});
end

% check policy specification (create default moving policy U, if necessary)
% V = V(Nt,Np,Nf)
% U = U(Np,Nf)
%--------------------------------------------------------------------------
if isfield(MDP,'U')
    if size(MDP.U,1) == 1 && size(MDP.U,3) == Nf
        MDP.U = shiftdim(MDP.U,1);
    end
end
try
    V(1,:,:) = MDP.U;               % allowable actions (1,Np)
catch
    try
        V = MDP.V;                  % allowable policies (T - 1,Np)
    catch

        % allowable (moving) policies using all allowable actions
        %------------------------------------------------------------------
        for f = 1:Nf
            u = 1;
            for i = 1:Nf
                if i == f
                    u = kron(1:Nu(i),u);
                else
                    u = kron(ones(1,Nu(i)),u);
                end
            end
            MDP.U(:,f)  = u;
        end
        V(1,:,:) = MDP.U;
    end
end
MDP.V = V;

% check policy specification
%--------------------------------------------------------------------------
if Nf ~= size(V,3) && size(V,3) > 1
    error('please ensure V(:,:,1:Nf) is consistent with MDP.B{1:Nf}')
end

% check preferences
%--------------------------------------------------------------------------
if ~isfield(MDP,'C')
    for g = 1:Ng
        MDP.C{g} = zeros(No(g),1);
    end
end
for g = 1:Ng
    if iscell(MDP.C)
        if isvector(MDP.C{g})
            MDP.C{g} = spm_vec(MDP.C{g});
        end
        if No(g) ~= size(MDP.C{g},1)
            error(['please ensure A{' num2str(g) '} and C{' num2str(g) '} are consistent'])
        end
    end
end

% check iinitial states
%--------------------------------------------------------------------------
if ~isfield(MDP,'D')
    for f = 1:Nf
        MDP.D{f} = ones(Ns(f),1);
    end
end
if Nf  ~= numel(MDP.D)
    error('please ensure V(:,:,1:Nf) is consistent with MDP.D{1:Nf}')
end

% check iinitial states and internal consistency
%--------------------------------------------------------------------------
if Nf  ~= numel(MDP.D)
    error('please ensure V(:,:,1:Nf) is consistent with MDP.D{1:Nf}')
end
for f = 1:Nf
    if Ns(f) ~= size(MDP.D{f},1)
        error(['please ensure B{' num2str(f) '} and D{' num2str(f) '} are consistent'])
    end
    if size(V,3) > 1
        if Nu(f) < max(spm_vec(V(:,:,f)))
            error(['please check V(:,:,' num2str(f) ') or U(:,:,' num2str(f) ')'])
        end
    end
    for g = 1:Ng
        Na  = size(MDP.A{g});
        if ~all(Na(2:end) == Ns)
            error(['please ensure A{' num2str(g) '} and D{' num2str(f) '} are consistent'])
        end
    end
end

% check probability matrices are properly specified
%--------------------------------------------------------------------------
for f = 1:Nf
    if ~all(spm_vec(any(MDP.B{f},1)))
        error(['please check B{' num2str(f) '} for missing entries'])
    end
end
for g = 1:Ng
    if ~all(spm_vec(any(MDP.A{g},1)))
        error(['please check A{' num2str(g) '} for missing entries'])
    end
end

% check initial states
%--------------------------------------------------------------------------
if isfield(MDP,'s')
    if size(MDP.s,1) > Nf
        error('please specify an initial state MDP.s for %i factors',Nf)
    end
    f  = max(MDP.s,[],2)';
    if any(f > Ns(1:numel(f)))
        error('please ensure initial states MDP.s are consistent with MDP.B')
    end
end

% check outcomes if specified
%--------------------------------------------------------------------------
if isfield(MDP,'o')
    if numel(MDP.o)
        if size(MDP.o,1) ~= Ng
            error('please specify an outcomes MDP.o for %i modalities',Ng)
        end
        if any(max(MDP.o,[],2) > No(:))
            error('please ensure outcomes MDP.o are consistent with MDP.A')
        end
    end
end

% check (primary link array if necessary)
%--------------------------------------------------------------------------
if isfield(MDP,'link')

    % cardinality of subordinate level
    %----------------------------------------------------------------------
    nf    = numel(MDP.MDP(1).B);               % number of hidden factors
    for f = 1:nf
        ns(f)    = size(MDP.MDP(1).B{f},1);    % number of hidden states
    end

    % check the size of link
    %----------------------------------------------------------------------
    if ~all(size(MDP.link) == [nf,Ng]);
        error('please check the size of link {%i,%i}',nf,Ng)
    end

    % convert matrix to cell array if necessary
    %----------------------------------------------------------------------
    if isnumeric(MDP.link)
        link  = cell(nf,Ng);
        for f = 1:size(MDP.link,1)
            for g = 1:size(MDP.link,2)
                if MDP.link(f,g)
                    link{f,g} = spm_speye(ns(f),No(g),0);
                end
            end
        end
        MDP.link = link;
    end

    % check sizes of cell array
    %----------------------------------------------------------------------
    for f = 1:size(MDP.link,1)
        for g = 1:size(MDP.link,2)
            if ~isempty(MDP.link{f,g})
                if ~all(size(MDP.link{f,g}) == [ns(f),No(g)]);
                    error('please check link{%i,%i}',f,g)
                end
            end
        end
    end

end

% Empirical prior preferences
%--------------------------------------------------------------------------
if isfield(MDP,'linkC')
    if isnumeric(MDP.linkC)
        linkC  = cell(numel(MDP.MDP.C),Ng);
        for f = 1:size(MDP.linkC,1)
            for g = 1:size(MDP.linkC,2)
                if MDP.linkC(f,g)
                    linkC{f,g} = spm_speye(size(MDP.MDP.C{f},1),No(g),0);
                end
            end
        end
        MDP.linkC = linkC;
    end
end

% Empirical priors over policies
%--------------------------------------------------------------------------
if isfield(MDP,'linkE')
    if isnumeric(MDP.linkE)
        linkE  = cell(1,Ng);
        for g = 1:size(MDP.linkE,2)
            if MDP.linkE(g)
                linkE{g} = spm_speye(size(MDP.MDP.E,1),No(g),0);
            end
        end
        MDP.linkE = linkE;
    end
end

% check factors and outcome modalities have proper labels
%--------------------------------------------------------------------------
for i = 1:Nf

    % name of factors
    %----------------------------------------------------------------------
    try
        MDP.label.factor(i);
    catch
        try
            MDP.label.factor{i} = MDP.Bname{i};
        catch
            MDP.label.factor{i} = sprintf('factor %i',i);
        end
    end

    % name of levels of each factor
    %----------------------------------------------------------------------
    for j = 1:Ns(i)
        try
            MDP.label.name{i}(j);
        catch
            try
                MDP.label.name{i}{j} = MDP.Sname{i}{j};
            catch
                MDP.label.name{i}{j} = sprintf('state %i(%i)',j,i);
            end
        end
    end

    % name of actions under each factor
    %----------------------------------------------------------------------
    for j = 1:Nu(i)
        try
            MDP.label.action{i}(j);
        catch
            MDP.label.action{i}{j} = sprintf('act %i(%i)',j,i);
        end
    end
end

% name of outcomes under each modality
%--------------------------------------------------------------------------
for i = 1:Ng
    try
        MDP.label.modality(i);
    catch
        try
            MDP.label.modality{i} = MDP.Bname{i};
        catch
            MDP.label.modality{i} = sprintf('modality %i',i);
        end
    end
    for j = 1:No(i)
        try
            MDP.label.outcome{i}(j);
        catch
            try
                MDP.label.outcome{i}{j} = MDP.Oname{i}{j};
            catch
                MDP.label.outcome{i}{j} = sprintf('outcome %i(%i)',j,i);
            end
        end
    end
end

% check names are specified properly
%--------------------------------------------------------------------------
if isfield(MDP,'Aname')
    if numel(MDP.Aname) ~= Ng
        error('please specify an MDP.Aname for each modality')
    end
else
    MDP.Aname = MDP.label.modality;
end
if isfield(MDP,'Bname')
    if numel(MDP.Bname) ~= Nf
        error('please specify an MDP.Bname for each factor')
    end
else
    MDP.Bname = MDP.label.factor;
end


% op_B0Correction.m
% Jamie Near, McGill University 2021.
%E dits from Brenden Kadota, 2021.
%
% USAGE:
% [out, phaseMap, freqMap]=op_B0Correction(in, (optional) phaseMap);
%
% DESCRIPTION:
% Corrects for slight B0 shifts by aligning all voxels to the voxel with
% the highest water peak. (Possibly not correct output as highest peak may
% no have the correct ppm shifts)
%
% INPUTS:
% in   = MRSI struct
% phaseMap = phaseMap matrix used to apply corrections
%
% OUTPUTS:
% B0Map        = 2D array of shift intensity at each specific voxel
% phaseMap     = 2D array of phase shift intensity at each specific voxel
% freqMap      = 2D array of frequency shift intensity at each specific voxel

function [MRSIStruct, phaseMap, freqMap] = op_CSIB0Correction(MRSIStruct, phaseMap, ...
        freqMap, plottingArguments)
    arguments
        MRSIStruct (1, 1) struct
        phaseMap double = []
        freqMap double = []
        plottingArguments.isPlot = false;
    end
    %only correct if coils have be combined
    if(getFlags(MRSIStruct, 'addedrcvrs') == 0)
        error("MRSIStruct Error. Add coils first in MRSIStruct");
    end
    if(getFlags(MRSIStruct, 'spatialft') == 0)
        error("Please fourier transform along the spatial dimension");
    end
    if(getFlags(MRSIStruct, 'spectralft') == 0)
        error("Please fourier transform along the spectral dimension");
    end

    %find the coordinate of the highest intensity
    [x, y, a] = findMaxCoord(MRSIStruct);

    %create twix object from coordinate with highest intensity
    referenceMRS = op_CSItoMRS(MRSIStruct, x, y, "averageIndex", a);
    referenceMRS = setFlags(referenceMRS, 'averaged', true);
    referenceMRS.flags.isISIS = 0;

    if(~isempty(phaseMap) && ~isempty(freqMap))
        [MRSIStruct, prevPermute, prevSize] = reshapeDimensions(MRSIStruct, {'t', 'y', 'x'});
        MRSIStruct = applyFreqMap(MRSIStruct, freqMap);
        MRSIStruct = applyPhaseMap(MRSIStruct, phaseMap);
        MRSIStruct = reshapeBack(MRSIStruct, prevPermute, prevSize);

    else

        [MRSIStruct, prevPermute, prevSize] = reshapeDimensions(MRSIStruct, {'t', 'y', 'x'});

        phaseMap = zeros(getSizeFromDimensions(MRSIStruct, {'y', 'x', 'extras'}));
        freqMap = zeros(getSizeFromDimensions(MRSIStruct, {'y', 'x', 'extras'}));

        data = getData(MRSIStruct);
        for e = 1:getSizeFromDimensions(MRSIStruct, {'extras'})
            for y = 1:getSizeFromDimensions(MRSIStruct, {'y'})
                for x = 1:getSizeFromDimensions(MRSIStruct, {'x'})
                    %get twix object at x and y coordinate
                    alignMRS = op_CSItoMRS(MRSIStruct, x, y, 'Extra', e);
                    alignMRS = setFlags(alignMRS, 'averaged', true);
                    alignMRS.flags.isISIS = 0;
                    %aligns twix object to the maximum intensity twix object
                    [alignMRS, phase, freq] = op_alignScans(alignMRS, referenceMRS, referenceMRS.t(end));

                    timeDimension = getDimension(alignMRS, 't');
                    %adds the aligned twix to the specs of MRSI object.
                    data(:, y, x, e) = flip(alignMRS.specs, timeDimension);
                    %add phase and frequency to maps
                    phaseMap(y, x, e) = phase;
                    freqMap(y, x, e) = freq;
                end
            end
        end
        MRSIStruct = setData(MRSIStruct, data);
        MRSIStruct = reshapeBack(MRSIStruct, prevPermute, prevSize);

        %plot maps
        if(plottingArguments.isPlot)
            plotFreqAndPhaseMap(phaseMap, MRSIStruct, freqMap);
        end
        MRSIStruct = setFlags(MRSIStruct, 'phasecorrected', 1);
        MRSIStruct = setFlags(MRSIStruct, 'freqcorrected', 1);
    end
end

%finds the (x,y) coordinate with the highest intensity spectrum.
function [x,y,a] = findMaxCoord(in)
    [~, i] = max(getData(in),[], 'all', 'linear');
    out = cell(size(in.sz));
    [out{:}] = ind2sub(in.sz, i);
    x = out{in.dims.x};
    y = out{in.dims.y};
    if(in.dims.averages)
        a = out{in.dims.averages};
    else
        a = 1;
    end

end

function plotFreqAndPhaseMap(phaseMap, MRSIStruct, freqMap)
    for i = 1:size(phaseMap,3)
        figure;
        subplot(2,1,1);
        imagesc(MRSIStruct.coordinates.x, MRSIStruct.coordinates.y, phaseMap(:,:,i)')
        title("phaseMap")
        subplot(2,1,2);
        imagesc(MRSIStruct.coordinates.x, MRSIStruct.coordinates.y, freqMap(:,:,i)')
        title("freqMap")
    end
end

function MRSIStruct = applyFreqMap(MRSIStruct, freqMap)
    %applying the freqMap map if added as an argument
    for e = 1:getSizeFromDimensions(MRSIStruct, {'extras'})
        for x = 1:getSizeFromDimensions(MRSIStruct, {'x'})
            for y = 1:getSizeFromDimensions(MRSIStruct, {'y'})
                %create twix object from the coordinates
                alignMRS = op_CSItoMRS(MRSIStruct, x, y, 'extraIndex', e);
                %align twix object with op_freqshift to the equivalent
                %freqmap coordinate.
                alignMRS = op_freqshift(alignMRS, freqMap(x, y));
                %add the specs to MRSI object
                MRSIStruct.data(:, x, y, e) = alignMRS.specs;
            end
        end
    end
end

function MRSIStruct = applyPhaseMap(MRSIStruct, phaseMap)
    %applying the freqMap map if added as an argument
    for e = 1:getSizeFromDimensions(MRSIStruct, {'extras'})
        for x = 1:getSizeFromDimensions(MRSIStruct, {'x'})
            for y = 1:getSizeFromDimensions(MRSIStruct, {'y'})
                %create twix object from the coordinates
                alignMRS = op_CSItoMRS(MRSIStruct, x, y, 'extraIndex', e);
                %align twix object with op_freqshift to the equivalent
                %freqmap coordinate.
                alignMRS = op_addphase(alignMRS, phaseMap(x, y));
                %add the specs to MRSI object
                MRSIStruct.data(:, x, y, e) = alignMRS.specs;
            end
        end
    end
end


classdef math_model_opf_acci < mp.math_model_opf_acc
%MP.MATH_MODEL_OPF_ACCI  MATPOWER mathematical model for AC optimal power flow (OPF) problem.
%   ?
%
%   MP.MATH_MODEL_OPF_ACCI ... power flow ...
%
%   Properties
%       ? - ?
%
%   Methods
%       ?

%   MATPOWER
%   Copyright (c) 2021-2022, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

%     properties
%     end

    methods
        function tag = form_tag(obj)
            tag = 'acci';
        end

        function name = form_name(obj)
            name = 'AC-cartesian-current';
        end

        function add_node_balance_constraints(obj, nm, dm, mpopt)
            %% power balance constraints
            nn = nm.node.N;             %% number of nodes
            fcn_mis = @(x)obj.nodal_current_balance_fcn(x, nm);
            hess_mis = @(x, lam)obj.nodal_current_balance_hess(x, lam, nm);
            obj.add_nln_constraint({'rImis', 'iImis'}, [nn;nn], 1, fcn_mis, hess_mis);
        end

        function [lam_p, lam_q] = node_power_balance_prices(obj, nm)
            %% shadow prices on node power balance
            nne = obj.get_idx('nle');

            %% convert current balance shadow prices to equivalent lam_p and lam_q
            %% P + jQ = (Vr + jVi) * (M - jN)
            %% M = (Vr P + Vi Q) / (Vr^2 + Vi^2)
            %% N = (Vi P - Vr Q) / (Vr^2 + Vi^2)
            %% lam_p = df/dP = df/dM * dM/dP + df/dN + dN/dP
            %% lam_q = df/dQ = df/dM * dM/dQ + df/dN + dN/dQ
            V = nm.soln.v;
            lambda = obj.soln.lambda;
            VV = V ./ (V .* conj(V));   %% V / vm^2
            VVr = real(VV);
            VVi = imag(VV);
            lamM = lambda.eqnonlin(nne.i1.rImis:nne.iN.rImis);
            lamN = lambda.eqnonlin(nne.i1.iImis:nne.iN.iImis);
            lam_p = (VVr.*lamM + VVi.*lamN);
            lam_q = (VVi.*lamM - VVr.*lamN);
        end
    end     %% methods
end         %% classdef


function [dat, ref] = ft_preproc_rereference(dat, refchan, method, handlenan)

% FT_PREPROC_REREFERENCE computes the average reference over all EEG channels
% or rereferences the data to the selected channels
%
% Use as
%   [dat] = ft_preproc_rereference(dat, refchan, method, handlenan)
% where
%   dat        data matrix (Nchans X Ntime)
%   refchan    vector with indices of the new reference channels, or 'all'
%   method     string, can be 'avg' or 'median'
%   handlenan  boolean, can be true or false
%
% If the new reference channel is not specified, the data will be
% rereferenced to the average of all channels.
%
% See also PREPROC

% Copyright (C) 1998-2017, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% determine the size of the data
[Nchans, Nsamples] = size(dat);

% determine the new reference channels
if nargin<2 || isempty(refchan) || (ischar(refchan) && strcmp(refchan, 'all'))
  refchan = 1:Nchans;
end

if nargin<3 || isempty(method)
  method = 'avg';
end

if nargin<4 || isempty(handlenan)
  handlenan = false;
end

hasnan = any(any(isnan(dat(refchan,:))));

if hasnan && handlenan
  % preprocessing works differently if channels contain NaN
  switch method
    case 'avg'
      ref = nanmean(dat(refchan,:), 1);
    case 'median'
      ref = nanmedian(dat(refchan,:), 1);
    otherwise
      ft_error('unsupported method')
  end % switch
else
  % preprocessing fails on channels that contain NaN
  if any(isnan(dat(:)))
    ft_warning('FieldTrip:dataContainsNaN', 'data contains NaN values');
  end
  % compute the average value over the reference channels
  switch method
    case 'avg'
      ref = mean(dat(refchan,:), 1);
    case 'median'
      ref = median(dat(refchan,:), 1);
    otherwise
      ft_error('unsupported method')
  end % switch
end

% subtract the new reference from the data
for chan=1:Nchans
  dat(chan,:) = dat(chan,:) - ref;
end


## Copyright (C) 2013 CarnĂŤ Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {@var{cmap} =} ycbcr2rgb (@var{YCbCrmap})
## @deftypefnx {Function File} {@var{RGB} =} ycbcr2rgb (@var{YCbCr})
## @deftypefnx {Function File} {@dots{} =} ycbcr2rgb (@dots{}, [@var{Kb} @var{Kr}])
## @deftypefnx {Function File} {@dots{} =} ycbcr2rgb (@dots{}, @var{standard})
## Convert YCbCr color space to RGB.
##
## The convertion changes the image @var{YCbCr} or colormap @var{YCbCrmap},
## from the YCbCr (luminance, chrominance blue, and chrominance red)
## color space to RGB values.  @var{YCbCr} must be of class double, single,
## uint8, or uint16.
##
## The formula used for the conversion is dependent on two constants, @var{Kb}
## and @var{Kr} which can be specified individually, or according to existing
## standards:
##
## @table @asis
## @item "601" (default)
## According to the ITU-R BT.601 (formerly CCIR 601) standard.  Its values
## of @var{Kb} and @var{Kr} are 0.114 and 0.299 respectively.
## @item "709" (default)
## According to the ITU-R BT.709 standard.  Its values of @var{Kb} and
## @var{Kr} are 0.0722 and 0.2116 respectively.
## @end table
##
## @seealso{hsv2rgb, ntsc2rgb, rgb2hsv, rgb2ntsc, rgb2ycbcr}
## @end deftypefn

function rgb = ycbcr2rgb (ycbcr, standard = "601")
  if (nargin < 1 || nargin > 2)
    print_usage ();
  endif
  rgb = ycbcrfunc ("ycbcr2rgb", ycbcr, standard);
endfunction

%!assert (ycbcr2rgb (rgb2ycbcr (jet (10))), jet (10), 0.00001);

## Copyright (C) 2013 CarnĂŤ Draug <carandraug+dev@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <http://www.gnu.org/licenses/>.

## Private function for ycbcr2rgb and rgb2ycbcr functions which are
## very similar

function out = ycbcrfunc (func, in, standard)

  img = false; # was input an image?
  if (iscolormap (in))
    ## do nothing, it's a colormap
  elseif (isrgb (in))
    img = true;
    ## we shape it as a colormap (2D matrix) so we can use matrix multiplcation
    nRows = rows (in);
    nCols = columns (in);
    in    = reshape (in, [nRows*nCols 3]);
  else
    error ("%s: input must be a colormap (Nx3) or RGB image (NxMx3)", func);
  endif

  if (ischar (standard))
    if (strcmpi (standard, "601")) # for ITU-R BT.601
      Kb = 0.114;
      Kr = 0.299;
    elseif (strcmpi (standard, "709")) # for ITU-R BT.709
      Kb = 0.0722;
      Kr = 0.2126;
    else
      error ("%s: unknown standard `%s'", func, standard);
    endif
  elseif (isnumeric (standard) && numel (standard) == 2)
    Kb = standard(1);
    Kr = standard(2);
  else
    error ("%s: must specify a standard (string), or Kb and Kr values", func);
  endif

  ## the color matrix for the conversion. Derived from:
  ##    Y  = Kr*R + (1-Kr-Kb)*G + kb*B
  ##    Cb = (1/2) * ((B-Y)/(1-Kb))
  ##    Cr = (1/2) * ((R-Y)/(1-Kr))
  ## It expects RGB values in the range [0 1], and returns Y in the
  ## range [0 1], and Cb and Cr in the range [-0.5 0.5]
  cmat = [  Kr            (1-Kr-Kb)            Kb
          -(Kr/(2-2*Kb)) -(1-Kr-Kb)/(2-2*Kb)   0.5
            0.5          -(1-Kr-Kb)/(2-2*Kr) -(Kb/(2-2*Kr)) ];

  cls = class (in);
  in  = im2double (in);

  ## note that these blocks are the inverse of one another. Changes
  ## in one will most likely require a change on the other
  if (strcmp (func, "rgb2ycbcr"))
    ## convert to YCbCr colorspace
    out = (cmat * in')'; # transpose at the end to get back colormap shape
    ## rescale Cb and Cr to range [0 1]
    out(:, [2 3]) += 0.5;
    ## footroom and headroom will take from the range 16/255 each for Cb and Cr,
    ## and 16/255 and 20/255 for Y. So we have to compress the values of the
    ## space, and then shift forward
    out(:,1)     = (out(:,1) * 219/255) + 16/255;
    out(:,[2 3]) = (out(:,[2 3]) * 223/255) + 16/255;

  elseif (strcmp (func, "ycbcr2rgb"))
    ## just the inverse of the rgb2ycbcr conversion
    in(:,[2 3])  = (in(:,[2 3]) - 16/255) / (223/255);
    in(:,1)      = (in(:,1) - 16/255) / (219/255);
    in(:,[2 3]) -= 0.5;
    out          = (inv (cmat) * in')';
  else
    error ("internal error for YCbCr conversion. Unknown function %s", func);
  endif

  switch (cls)
    case {"single", "double"}
      ## do nothing. All is good
    case "uint8"
      out = im2uint8 (out);
    case "uint16"
      out = im2uint16 (out);
    otherwise
      error ("%s: unsupported image class %s", func, cls);
  endswitch

  if (img)
    ## put the image back together
    out = reshape (out, [nRows nCols 3]);
  endif

endfunction


function [b, iCv, iC] = gp_looprep(gp, x, y)
%GP_LOOPREP  Calculates help terms needed in Gaussian GP LOO
%
%  Description
%    [b, iCv] = gp_looprep(gp, x, y, varargin) takes GP structure,
%    training input and output and returns following terms
%      b = C\y
%      iCv = diag(inv(C))
%    [b, iCv, iC] = gp_looprep(gp, x, y, varargin) returns additionally
%      iC = inv(C)
%    In case of CS, FIC, PIC and CS+FIC, these terms are computed
%    using appropriate efficient sparse matrix and inverse lemma
%    computations.
%

% Copyright (c) 2012 Aki Vehtari

% 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.

  switch gp.type
    case 'FULL'
      % FULL GP (and compact support GP)
      [K, C] = gp_trcov(gp, x);

      if issparse(C)
        if nargout==3
          iC = full(spinv(C));        % sparse inverse
          iCv = full(diag(iC));       % the diagonal of sparse inverse
        else
          iCv = full(diag(spinv(C))); % the diagonal of sparse inverse
        end
        [LD, notpositivedefinite] = ldlchol(C);
        if notpositivedefinite
          b = NaN;
          iCv = NaN;
          iC = NaN;
          return
        end
        b = ldlsolve(LD,y);
      else
        if nargout==3
          iC = inv(C);                % full inverse
          iCv = diag(inv(C));         % the diagonal of full inverse
        else
          iCv = diag(inv(C));         % the diagonal of full inverse
        end
        b = C\y;
      end

    case 'FIC'
      % FIC
      % Use inverse lemma for FIC low rank covariance matrix approximation
      % Code adapated from gp_pred

      u = gp.X_u;
      m = size(u,1);
      % Turn the inducing vector on right direction
      if size(u,2) ~= size(x,2)
        u=u';
      end
      [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
      [Luu, notpositivedefinite] = chol(K_uu,'lower');
      if notpositivedefinite
        b = NaN;
        iCv = NaN;
        iC = NaN;
        return
      end
      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,
      n=size(x,1);
      for i=1:n
        iLaKfu(i,:) = K_fu(i,:)./Lav(i);  % f x u
      end
      A = K_uu+K_fu'*iLaKfu;
      A = (A+A')./2;
      L = iLaKfu/chol(A);

      if nargout==3
        iC = diag(1./Lav) - L*L'; % Cv = inv(C);
        iCv = diag(iC); %           iCv = diag(inv(C));
      else
        iCv = 1./Lav - sum(L.^2,2); % iCv = diag(inv(C));
      end
      b = y./Lav - L*(L'*y);      % b = C\y;

    case {'PIC' 'PIC_BLOCK'}
      % PIC
      % Use inverse lemma for PIC low rank covariance matrix approximation
      % Code adapated from gp_pred

      u = gp.X_u;
      ind = gp.tr_index;
      if size(u,2) ~= size(x,2)
        % Turn the inducing vector on right direction
        u=u';
      end

      % Calculate some help matrices
      [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
      Luu = chol(K_uu)';

      % Evaluate the Lambda (La) for specific model
      % 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';
      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{i} = Cbl_ff - Qbl_ff;
        iLaKfu(ind{i},:) = La{i}\K_fu(ind{i},:);
      end
      A = K_uu+K_fu'*iLaKfu;
      A = (A+A')./2;            % Ensure symmetry
      L = iLaKfu/chol(A);

      % From this on evaluate the prediction
      % See Snelson and Ghahramani (2007) for details
      n=size(y,1);
      iCv=zeros(n,1);
      if nargout==3
        iC=zeros(n,n);
      end
      for i=1:length(ind)
        if nargout==3
          iC(ind{i},ind{i}) = inv(La{i});
        else
          iCv(ind{i},:) = diag(inv(La{i}));
        end
        b(ind{i},:) = La{i}\y(ind{i},:);
      end
      if nargout==3
        iC = iC - L*L';          % iC  = inv(C);
        iCv = diag(iC);          % iCv = diag(inv(C));
      else
        iCv = iCv - sum(L.^2,2); % iCv = diag(inv(C));
      end
      b = b - L*(L'*y);        % b = C\y;

    case 'CS+FIC'
      % CS+FIC
      % Use inverse lemma for CS+FIC
      % Code adapated from gp_pred

      u = gp.X_u;
      if size(u,2) ~= size(x,2)
        % Turn the inducing vector on right direction
        u=u';
      end

      n = size(x,1);
      m = size(u,1);
      ncf = length(gp.cf);

      % Indexes to all non-compact support and compact support covariances.
      cf1 = [];
      cf2 = [];

      % Loop through all covariance functions
      for i1 = 1:ncf
        if ~isfield(gp.cf{i1},'cs')
          % Non-CS covariances
          cf1 = [cf1 i1];
        else
          % CS-covariances
          cf2 = [cf2 i1];
        end
      end

      % First evaluate needed covariance matrices
      % v defines that parameter is a vector
      [Kv_ff, Cv_ff] = gp_trvar(gp, x, cf1); % f x 1  vector
      K_fu = gp_cov(gp, x, u, cf1);          % f x u
      K_uu = gp_trcov(gp, u, cf1);    % u x u, noiseles covariance K_uu
      K_uu = (K_uu+K_uu')./2;         % ensure the symmetry of K_uu

      Luu  = chol(K_uu)';
      % Evaluate the Lambda (La)
      % Q_ff = K_fu*inv(K_uu)*K_fu'
      B=Luu\(K_fu');       % u x f
      Qv_ff=sum(B.^2)';
      Lav = Cv_ff-Qv_ff;   % f x 1, Vector of diagonal elements

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

      iLaKfu = La\K_fu;
      A = K_uu+K_fu'*iLaKfu;
      A = (A+A')./2;     % Ensure symmetry
      L = iLaKfu/chol(A);

      if nargout==3
        iC = inv(La) - L*L'; % iCv = diag(inv(C));
        iCv = diag(iC)     ; % iCv = diag(inv(C));
      else
        iCv = diag(inv(La)) - sum(L.^2,2); % iCv = diag(inv(C));
      end
      b = La\y - L*(L'*y);         % b = C\y;

    case 'SSGP' % SSGP
      error('GP_LOOPRED is not yet implemented for SSGP and Gaussian likelihood')

    otherwise
      error('Unknown type of Gaussian process')
  end

end