function [fusedBox] = fusePETCT(ROIbox_PET,ROIbox_CT,maskBox_PET,CTinv,CTweight,wavelet)
% -------------------------------------------------------------------------
% function [fusedBox] = fusePETCT(ROIbox_PET,ROIbox_CT,maskBox_PET,CTinv,CTweight,wavelet)
% -------------------------------------------------------------------------
% DESCRIPTION:
% This function fuses the region of interest (ROI) of two registered PET and
% CT volumes using a technique based on the wavelet transform. See Ref. [2]
% for more details.
% -------------------------------------------------------------------------
% REFERENCE:
% [1] Vallieres, M. et al. (2015). FDG-PET/CT radiomics models for the
%     early prediction of different tumour outcomes in head and neck cancer.
%     The Journal of Nuclear Medicine, aa(bb), xxx-yyy.
%     doi:
% -------------------------------------------------------------------------
% INPUTS:
% - ROIbox_PET: 3D array of the smallest box containing the ROI of the PET
%               volume.
% - ROIbox_CT: 3D array of the smallest box containing the ROI of the CT
%               volume.
% - maskBox_PET: 3D array of the mask of the smallest box specifying the
%                ROI of the PET volume. Voxels within the ROI are assigned
%                value of 1, voxels outside a value of 0.
% - CTinv: String specifying if the intensities of the CT volume are
%          inverted prior to fusion with PET. Either 'Inv' for inversion,
%          or 'NoInv' for no inversion.
% - CTweight: Numerical value specifying the weight of the CT scan in the
%              fusion with PET.
% - wavelet: String specifying the name of the MATLAB wavelet basis used
%            wavelet basis used in the fusion process.
% -------------------------------------------------------------------------
% OUTPUTS:
% - fusedBox: 3D array of the smallest box containing the ROI of the fused
%             PET/CT volume.
% -------------------------------------------------------------------------
% AUTHOR(S): Martin Vallieres <mart.vallieres@gmail.com>
% -------------------------------------------------------------------------
% HISTORY:
% - Creation: July 2015
% -------------------------------------------------------------------------
% STATEMENT:
% This file is part of <https://github.com/mvallieres/radiomics/>,
% a package providing MATLAB programming tools for radiomics analysis.
% --> Copyright (C) 2015  Martin Vallieres
%
%    This package 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 package 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 package.  If not, see <http://www.gnu.org/licenses/>.
% -------------------------------------------------------------------------

% PET PRE_PROCESSING
ROIbox_PET = sqrt(ROIbox_PET);

% RESAMPLING CT VOLUME TO PET IN-PLANE RESOLUTION
szPET = size(ROIbox_PET);
temp=zeros(szPET);
for k = 1:szPET(3)
    temp(:,:,k) = imresize(ROIbox_CT(:,:,k),[szPET(1),szPET(2)],'Method','cubic','Antialiasing',true);
end
ROIbox_CT = temp;

% NORMALIZATION (and inversion) OF VOLUMES
ROIOnly_CT = ROIbox_CT;
ROIOnly_PET = ROIbox_PET;
ROIOnly_CT(maskBox_PET==0) = NaN;
ROIOnly_PET(maskBox_PET==0) = NaN;
minCT = min(ROIOnly_CT(:));
ROIbox_CT = ROIbox_CT - minCT;
ROIOnly_CT = ROIOnly_CT - minCT;
ROIbox_CT = ROIbox_CT./max(ROIOnly_CT(:)).*255;
if strcmp(CTinv,'Inv')
    ROIbox_CT = 255 - ROIbox_CT;
end
minPET = min(ROIOnly_PET(:));
ROIbox_PET = ROIbox_PET - minPET;
ROIOnly_PET = ROIOnly_PET - minPET;
ROIbox_PET = ROIbox_PET./max(ROIOnly_PET(:)).*255;

% WAVELET DECOMPOSITION AND FUSION OF COEFFICIENTS
wdecCT = wavedec3(ROIbox_CT,1,wavelet);
wdecPET = wavedec3(ROIbox_PET,1,wavelet);
wdecFUSED = wdecPET;
szDEC = size(wdecFUSED.dec{1}); % All sub-bands are of the same size

% Fusion of the LLL sub-bands (dec{1})
b = 1;
wdecFUSED.dec{b} = (1-CTweight).*wdecPET.dec{b} + CTweight.*wdecCT.dec{b};

% Fusion of the HLL sub-bands (dec{2})
b = 2;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [~,Gy] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = sqrt(Gy.^2);
    [~,Gy] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = sqrt(Gy.^2);
end
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the LHL sub-bands (dec{3})
b = 3;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [Gx,~] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = sqrt(Gx.^2);
    [Gx,~] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = sqrt(Gx.^2);
end
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the HHL sub-bands (dec{4})
b = 4;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [Gx,Gy] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = sqrt(Gx.^2 + Gy.^2);
    [Gx,Gy] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = sqrt(Gx.^2 + Gy.^2);
end
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the LLH sub-bands (dec{5})
b = 5;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for i = 1:szDEC(1)
    [~,Gy_PET] = imgradientxy(squeeze(wdecPET.dec{b}(i,:,:))');
    [~,Gy_CT] = imgradientxy(squeeze(wdecCT.dec{b}(i,:,:))');
    for k = 1:szDEC(3)
        tempPET(i,:,k) = sqrt((Gy_PET(k,:)).^2);
        tempCT(i,:,k) = sqrt((Gy_CT(k,:)).^2);
    end
end
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the HLH sub-bands (dec{6})
b = 6;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [~,Gy] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = Gy.^2;
    [~,Gy] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = Gy.^2;
end
for i = 1:szDEC(1)
    [~,Gy_PET] = imgradientxy(squeeze(wdecPET.dec{b}(i,:,:))');
    [~,Gy_CT] = imgradientxy(squeeze(wdecCT.dec{b}(i,:,:))');
    for k = 1:szDEC(3)
        tempPET(i,:,k) = tempPET(i,:,k) + (Gy_PET(k,:)).^2;
        tempCT(i,:,k) = tempCT(i,:,k) + (Gy_CT(k,:)).^2;
    end
end
tempPET = sqrt(tempPET); tempCT = sqrt(tempCT);
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the LHH sub-bands (dec{7})
b = 7;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [Gx,~] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = Gx.^2;
    [Gx,~] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = Gx.^2;
end
for i = 1:szDEC(1)
    [~,Gy_PET] = imgradientxy(squeeze(wdecPET.dec{b}(i,:,:))');
    [~,Gy_CT] = imgradientxy(squeeze(wdecCT.dec{b}(i,:,:))');
    for k = 1:szDEC(3)
        tempPET(i,:,k) = tempPET(i,:,k) + (Gy_PET(k,:)).^2;
        tempCT(i,:,k) = tempCT(i,:,k) + (Gy_CT(k,:)).^2;
    end
end
tempPET = sqrt(tempPET); tempCT = sqrt(tempCT);
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% Fusion of the HHH sub-bands (dec{8})
b = 8;
tempPET = zeros(szDEC);
tempCT = zeros(szDEC);
for k = 1:szDEC(3)
    [Gx,Gy] = imgradientxy(wdecPET.dec{b}(:,:,k)); tempPET(:,:,k) = Gx.^2 + Gy.^2;
    [Gx,Gy] = imgradientxy(wdecCT.dec{b}(:,:,k)); tempCT(:,:,k) = Gx.^2 + Gy.^2;
end
for i = 1:szDEC(1)
    [~,Gy_PET] = imgradientxy(squeeze(wdecPET.dec{b}(i,:,:))');
    [~,Gy_CT] = imgradientxy(squeeze(wdecCT.dec{b}(i,:,:))');
    for k = 1:szDEC(3)
        tempPET(i,:,k) = tempPET(i,:,k) + (Gy_PET(k,:)).^2;
        tempCT(i,:,k) = tempCT(i,:,k) + (Gy_CT(k,:)).^2;
    end
end
tempPET = sqrt(tempPET); tempCT = sqrt(tempCT);
wdecFUSED.dec{b}(tempCT > tempPET) = wdecCT.dec{b}(tempCT > tempPET);

% WAVELET RECONSTRUCTION
fusedBox = waverec3(wdecFUSED);
fusedOnly = fusedBox;
fusedOnly(maskBox_PET==0) = NaN;
minFus = min(fusedOnly(:));
fusedBox = fusedBox - minFus;
fusedOnly = fusedOnly - minFus;
fusedBox = fusedBox./max(fusedOnly(:)).*255;

end


function [fMc, mcCalculator] = calc_Mc(mCatalog, calcMethod, binInterval, mcCorrectionFactor)
    % CALC_MC Calculates the magnitude of completeness for a given catalog
    %
    % fMc = CALC_MC(mCatalog, nMethod, binInterval, mcCorrectionFactor)
    % [fMc, mc_calculator] = CALC_MC(...) returns a function handle to the calculation
    % method, so it can be reused in heavy loops.  MC_CALCULATOR has the form:
    %    fMc =  MY_CALCULATOR(catalog, bins, correction);
    %
    % --------------------------------------------------------------------
    %
    %
    % Input parameters:
    %   mCatalog       Earthquake catalog for determing the magnitude of completeness
    %   calcMethod        Method to determine the magnitude of completeness
    %                     see: McMethods for a list of valid values
    %   binInterval       Binning of catalog's magnitudes (default 0.1)
    %   mcCorrectionFactor  Correction term to be added to fMc (default 0)
    %
    % Output parameters:
    %   fMc            Magnitude of completeness
    %   mc_calculator
    %
    %
    % Copyright (C) 2004 by Danijel Schorlemmer, Jochen Woessner

    % 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 2 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, write to the
    % Free Software Foundation, Inc.,
    % 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

    % Magnitude binning
    if ~exist('binInterval', 'var') || isempty(binInterval)
        binInterval = 0.1;
    end
    if ~isa(calcMethod,'McMethods')
        error('Expected an actual method (McMethods) but received something else.\n See McMethods');
    end
    % Correction
    if ~exist('mcCorrectionFactor', 'var') || isempty(mcCorrectionFactor)
        mcCorrectionFactor = 0;
    end
    useNumeric = isnumeric(mCatalog) || (ischarlike(mCatalog) && mCatalog == "AsMagnitudes");
    if useNumeric
        switch calcMethod
            case McMethods.MaxCurvature
                methodFun = @calc_McMaxCurvature;

            case  McMethods.FixedMc
                methodFun = @min;

            case  McMethods.Mc90
                methodFun = @(C)method_mc90(C,binInterval);

            case  McMethods.Mc95
                methodFun = @(C)method_mc95(C,binInterval);

            case McMethods.McBestCombo
                methodFun = @(C)method_bestcombo(C,binInterval);

            case  McMethods.McEMR
                error('this only works with full catalogs')
                %methodFun = @(C)calc_McEMR(C,binInterval); %requires catalog Magnitude AND Date

            case  McMethods.McDueB_ShiBolt
                methodFun = @(C)calc_Mcdueb(C, binInterval);

            case McMethods.McDueB_Bootstrap
                nSample     = 500;
                nWindowSize = 5;
                nMinEvents  = 50;
                methodFun   = @(C) calc_McduebBst(C, binInterval, nWindowSize, nMinEvents,nSample);

            case McMethods.McDueB_Cao
                methodFun = @(C, ~)calc_McduebCao(C);

            otherwise
                error('unknown Mc method');
        end
    else % catalog object
        switch calcMethod
            case McMethods.MaxCurvature
                methodFun = @(C)calc_McMaxCurvature(C.Magnitude);

            case  McMethods.FixedMc
                methodFun = @(C) min(C.Magnitude);

            case  McMethods.Mc90
                methodFun = @(C)method_mc90(C.Magnitude,binInterval);

            case  McMethods.Mc95
                methodFun = @(C)method_mc95(C.Magnitude,binInterval);

            case McMethods.McBestCombo
                methodFun = @(C)method_bestcombo(C.Magnitude,binInterval);

            case  McMethods.McEMR
                methodFun = @(C)calc_McEMR(C,binInterval); %requires catalog Magnitude AND Date

            case  McMethods.McDueB_ShiBolt
                methodFun = @(C)calc_Mcdueb(C.Magnitude, binInterval);

            case McMethods.McDueB_Bootstrap
                nSample     = 500;
                nWindowSize = 5;
                nMinEvents  = 50;
                methodFun   = @(C) calc_McduebBst(C.Magnitude, binInterval, nWindowSize, nMinEvents,nSample);

            case McMethods.McDueB_Cao
                methodFun = @(C, ~)calc_McduebCao(C.Magnitude);

            otherwise
                error('unknown Mc method');
        end
    end

    % lock the method into this calculation
    mcCalculator = @(C) do_calculation(methodFun, C, mcCorrectionFactor);

    if ~isempty(mCatalog)
        % do the calculation
        fMc = mcCalculator(mCatalog);
    else
        fMc = [];
    end

end

function fMc = do_calculation(methodFun, mCatalog, mcCorrectionFactor)
    if isempty(mCatalog) || ischarlike(mCatalog)
        fMc = nan;
        return
    end

    fMc = methodFun(mCatalog);

    % Check fMc
    if isempty(fMc)
        fMc = nan;
    end

    % Apply correction
    fMc = fMc + mcCorrectionFactor;
end

% functions to return that all have same signature
function fMc = method_mc90(mags, binInterval)
    [~, ~, fMc] = calc_McBest(mags, binInterval);
end

function fMc = method_mc95(mags, binInterval)
    [~, fMc] = calc_McBest(mags, binInterval);
end

function fMc = method_bestcombo(mags, binInterval)
        [~, Mc95, Mc90] = calc_McBest(mags, binInterval);
        if ~isnan(Mc95)
            fMc = Mc95;
        elseif ~isnan(Mc90)
            fMc = Mc90;
        else
            fMc = calc_McMaxCurvature(mags);
        end
end


function [dpsi,deps]=nut_iau1980(t,f)

AS2R=pi/180/3600;
nut=[   0,   0,   0,   0,   1, -6798.4, -171996, -174.2, 92025,   8.9;
        0,   0,   2,  -2,   2,   182.6,  -13187,   -1.6,  5736,  -3.1;
        0,   0,   2,   0,   2,    13.7,   -2274,   -0.2,   977,  -0.5;
        0,   0,   0,   0,   2, -3399.2,    2062,    0.2,  -895,   0.5;
        0,  -1,   0,   0,   0,  -365.3,   -1426,    3.4,    54,  -0.1;
        1,   0,   0,   0,   0,    27.6,     712,    0.1,    -7,   0.0;
        0,   1,   2,  -2,   2,   121.7,    -517,    1.2,   224,  -0.6;
        0,   0,   2,   0,   1,    13.6,    -386,   -0.4,   200,   0.0;
        1,   0,   2,   0,   2,     9.1,    -301,    0.0,   129,  -0.1;
        0,  -1,   2,  -2,   2,   365.2,     217,   -0.5,   -95,   0.3;
        -1,   0,   0,   2,   0,    31.8,     158,    0.0,    -1,   0.0;
        0,   0,   2,  -2,   1,   177.8,     129,    0.1,   -70,   0.0;
        -1,   0,   2,   0,   2,    27.1,     123,    0.0,   -53,   0.0;
        1,   0,   0,   0,   1,    27.7,      63,    0.1,   -33,   0.0;
        0,   0,   0,   2,   0,    14.8,      63,    0.0,    -2,   0.0;
        -1,   0,   2,   2,   2,     9.6,     -59,    0.0,    26,   0.0;
        -1,   0,   0,   0,   1,   -27.4,     -58,   -0.1,    32,   0.0;
        1,   0,   2,   0,   1,     9.1,     -51,    0.0,    27,   0.0;
        -2,   0,   0,   2,   0,  -205.9,     -48,    0.0,     1,   0.0;
        -2,   0,   2,   0,   1,  1305.5,      46,    0.0,   -24,   0.0;
        0,   0,   2,   2,   2,     7.1,     -38,    0.0,    16,   0.0;
        2,   0,   2,   0,   2,     6.9,     -31,    0.0,    13,   0.0;
    2,   0,   0,   0,   0,    13.8,      29,    0.0,    -1,   0.0;
    1,   0,   2,  -2,   2,    23.9,      29,    0.0,   -12,   0.0;
    0,   0,   2,   0,   0,    13.6,      26,    0.0,    -1,   0.0;
    0,   0,   2,  -2,   0,   173.3,     -22,    0.0,     0,   0.0;
    -1,   0,   2,   0,   1,    27.0,      21,    0.0,   -10,   0.0;
    0,   2,   0,   0,   0,   182.6,      17,   -0.1,     0,   0.0;
    0,   2,   2,  -2,   2,    91.3,     -16,    0.1,     7,   0.0;
    -1,   0,   0,   2,   1,    32.0,      16,    0.0,    -8,   0.0;
    0,   1,   0,   0,   1,   386.0,     -15,    0.0,     9,   0.0;
    1,   0,   0,  -2,   1,   -31.7,     -13,    0.0,     7,   0.0;
    0,  -1,   0,   0,   1,  -346.6,     -12,    0.0,     6,   0.0;
    2,   0,  -2,   0,   0, -1095.2,      11,    0.0,     0,   0.0;
    -1,   0,   2,   2,   1,     9.5,     -10,    0.0,     5,   0.0;
    1,   0,   2,   2,   2,     5.6,      -8,    0.0,     3,   0.0;
    0,  -1,   2,   0,   2,    14.2,      -7,    0.0,     3,   0.0;
    0,   0,   2,   2,   1,     7.1,      -7,    0.0,     3,   0.0;
    1,   1,   0,  -2,   0,   -34.8,      -7,    0.0,     0,   0.0;
    0,   1,   2,   0,   2,    13.2,       7,    0.0,    -3,   0.0;
    -2,   0,   0,   2,   1,  -199.8,      -6,    0.0,     3,   0.0;
    0,   0,   0,   2,   1,    14.8,      -6,    0.0,     3,   0.0;
    2,   0,   2,  -2,   2,    12.8,       6,    0.0,    -3,   0.0;
    1,   0,   0,   2,   0,     9.6,       6,    0.0,     0,   0.0;
    1,   0,   2,  -2,   1,    23.9,       6,    0.0,    -3,   0.0;
    0,   0,   0,  -2,   1,   -14.7,      -5,    0.0,     3,   0.0;
    0,  -1,   2,  -2,   1,   346.6,      -5,    0.0,     3,   0.0;
    2,   0,   2,   0,   1,     6.9,      -5,    0.0,     3,   0.0;
    1,  -1,   0,   0,   0,    29.8,       5,    0.0,     0,   0.0;
    1,   0,   0,  -1,   0,   411.8,      -4,    0.0,     0,   0.0;
    0,   0,   0,   1,   0,    29.5,      -4,    0.0,     0,   0.0;
    0,   1,   0,  -2,   0,   -15.4,      -4,    0.0,     0,   0.0;
    1,   0,  -2,   0,   0,   -26.9,       4,    0.0,     0,   0.0;
    2,   0,   0,  -2,   1,   212.3,       4,    0.0,    -2,   0.0;
    0,   1,   2,  -2,   1,   119.6,       4,    0.0,    -2,   0.0;
    1,   1,   0,   0,   0,    25.6,      -3,    0.0,     0,   0.0;
    1,  -1,   0,  -1,   0, -3232.9,      -3,    0.0,     0,   0.0;
    -1,  -1,   2,   2,   2,     9.8,      -3,    0.0,     1,   0.0;
    0,  -1,   2,   2,   2,     7.2,      -3,    0.0,     1,   0.0;
    1,  -1,   2,   0,   2,     9.4,      -3,    0.0,     1,   0.0;
    3,   0,   2,   0,   2,     5.5,      -3,    0.0,     1,   0.0;
    -2,   0,   2,   0,   2,  1615.7,      -3,    0.0,     1,   0.0;
    1,   0,   2,   0,   0,     9.1,       3,    0.0,     0,   0.0;
    -1,   0,   2,   4,   2,     5.8,      -2,    0.0,     1,   0.0;
    1,   0,   0,   0,   2,    27.8,      -2,    0.0,     1,   0.0;
    -1,   0,   2,  -2,   1,   -32.6,      -2,    0.0,     1,   0.0;
    0,  -2,   2,  -2,   1,  6786.3,      -2,    0.0,     1,   0.0;
    -2,   0,   0,   0,   1,   -13.7,      -2,    0.0,     1,   0.0;
    2,   0,   0,   0,   1,    13.8,       2,    0.0,    -1,   0.0;
    3,   0,   0,   0,   0,     9.2,       2,    0.0,     0,   0.0;
    1,   1,   2,   0,   2,     8.9,       2,    0.0,    -1,   0.0;
    0,   0,   2,   1,   2,     9.3,       2,    0.0,    -1,   0.0;
    1,   0,   0,   2,   1,     9.6,      -1,    0.0,     0,   0.0;
    1,   0,   2,   2,   1,     5.6,      -1,    0.0,     1,   0.0;
    1,   1,   0,  -2,   1,   -34.7,      -1,    0.0,     0,   0.0;
    0,   1,   0,   2,   0,    14.2,      -1,    0.0,     0,   0.0;
    0,   1,   2,  -2,   0,   117.5,      -1,    0.0,     0,   0.0;
    0,   1,  -2,   2,   0,  -329.8,      -1,    0.0,     0,   0.0;
    1,   0,  -2,   2,   0,    23.8,      -1,    0.0,     0,   0.0;
    1,   0,  -2,  -2,   0,    -9.5,      -1,    0.0,     0,   0.0;
    1,   0,   2,  -2,   0,    32.8,      -1,    0.0,     0,   0.0;
    1,   0,   0,  -4,   0,   -10.1,      -1,    0.0,     0,   0.0;
    2,   0,   0,  -4,   0,   -15.9,      -1,    0.0,     0,   0.0;
    0,   0,   2,   4,   2,     4.8,      -1,    0.0,     0,   0.0;
    0,   0,   2,  -1,   2,    25.4,      -1,    0.0,     0,   0.0;
    -2,   0,   2,   4,   2,     7.3,      -1,    0.0,     1,   0.0;
    2,   0,   2,   2,   2,     4.7,      -1,    0.0,     0,   0.0;
    0,  -1,   2,   0,   1,    14.2,      -1,    0.0,     0,   0.0;
    0,   0,  -2,   0,   1,   -13.6,      -1,    0.0,     0,   0.0;
    0,   0,   4,  -2,   2,    12.7,       1,    0.0,     0,   0.0;
    0,   1,   0,   0,   2,   409.2,       1,    0.0,     0,   0.0;
    1,   1,   2,  -2,   2,    22.5,       1,    0.0,    -1,   0.0;
    3,   0,   2,  -2,   2,     8.7,       1,    0.0,     0,   0.0;
    -2,   0,   2,   2,   2,    14.6,       1,    0.0,    -1,   0.0;
    -1,   0,   0,   0,   2,   -27.3,       1,    0.0,    -1,   0.0;
    0,   0,  -2,   2,   1,  -169.0,       1,    0.0,     0,   0.0;
    0,   1,   2,   0,   1,    13.1,       1,    0.0,     0,   0.0;
    -1,   0,   4,   0,   2,     9.1,       1,    0.0,     0,   0.0;
    2,   1,   0,  -2,   0,   131.7,       1,    0.0,     0,   0.0;
    2,   0,   0,   2,   0,     7.1,       1,    0.0,     0,   0.0;
    2,   0,   2,  -2,   1,    12.8,       1,    0.0,    -1,   0.0;
    2,   0,  -2,   0,   1,  -943.2,       1,    0.0,     0,   0.0;
    1,  -1,   0,  -2,   0,   -29.3,       1,    0.0,     0,   0.0;
    -1,   0,   0,   1,   1,  -388.3,       1,    0.0,     0,   0.0;
    -1,  -1,   0,   2,   1,    35.0,       1,    0.0,     0,   0.0;
    0,   1,   0,   1,   0,    27.3,       1,    0.0,     0,   0.0];

dpsi=0;
deps=0;
for i=1:106
    ang=0;
    for j=1:5
        ang=ang+nut(i,j)*f(j);
    end
    dpsi=dpsi+(nut(i,7)+nut(i,8)*t)*sin(ang);
    deps=deps+(nut(i,9)+nut(i,10)*t)*cos(ang);
end
dpsi=dpsi*1e-4*AS2R;
deps=deps*1e-4*AS2R;

return


function [Q,R,S,U,P] = spm_MDP(MDP)
% solves the active inference problem for Markov decision processes
% FROMAT [Q,R,S,U,P] = spm_MDP(MDP)
%
% MDP.T           - process depth (the horizon)
% MDP.S(N,1)      - initial state
% MDP.B{M}(N,N)   - transition probabilities among hidden states (priors)
% MDP.C(N,1)      - terminal cost probabilities (prior N over hidden states)
% MDP.D(M,1)      - control probabilities (prior over M control states)
%
% optional:
%
% MDP.W           - log-precision of beliefs about transitions (default: 1)
% MDP.G{M}(N,N)   - transition probabilities used to generate outcomes
%                   (default: the prior transition probabilities)
% MDP.A(N,N)      - Likelihood of outcomes given hidden states
%                   (default: an identity mapping from states to outcomes)
% MDP.B{T,M}(N,N) - transition probabilities for each time point
% MDP.G{T,M}(N,N) - transition probabilities for each time point
%                   (default: MDP.B{T,M} = MDP.B{M})
%
% MDP.plot        -  swtich to suppress graphics
%
% produces:
%
% Q(N,K,T) - an array of conditional (posterior) expectations over N hidden
%            states and time 1,...,T at time 1,...,K
% R(M,K,T) - an array of conditional expectations over M control
%            states and time 1,...,T at time 1,...,K
% S(N,T)   - a sparse matrix of ones, encoding the state at time 1,...,T
% U(M,T)   - a sparse matrix of ones, encoding the action at time 1,...,T
% P(M,T)   - probabaility of emitting action 1,...,M at time 1,...,T
%
% This routine provides solutions of active inference (minimisation of
% variational free energy)using a generative model based upon a Markov
% decision process. This model and inference scheme is formulated
% in discrete space and time. This means that the generative model (and
% process) are  finite state  machines or hidden Markov models whose
% dynamics are given by transition probabilities among states. For
% simplicity, we assume an isomorphism between hidden states and outcomes,
% where the likelihood corresponds to a particular outcome conditioned upon
% hidden states. Similarly, for simplicity, this routine assumes that action
% and hidden controls are isomorphic. If the dynamics of transition
% probabilities of the true process are not provided, this routine will use
% the equivalent probabilities from the generative model.
%
% The transition probabilities are a cell array of probability transition
% matrices corresponding to each (discrete) the level of the control state.
%
% Mote that the conditional expectations are functions of time but also
% contain expectations about fictive states over time at each time point.
% To create time dependent transition probabilities, one can specify a
% function in place of the transition probabilities under different levels
% of control.
%
% partially observed Markov decision processes can be modelled by
% specifying a likelihood (as part of a generative model) and absorbing any
% probabilistic mapping between (isomorphic) hidden states and outcomes
% into the transition probabilities G.
%
% See also spm_MDP_game
%__________________________________________________________________________
% Copyright (C) 2005 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_MDP.m 6061 2014-06-21 09:02:42Z karl $

% set up and preliminaries
%==========================================================================

% plotting and precision defaults
%--------------------------------------------------------------------------
try PLOT   = MDP.plot;   catch, PLOT   = 1; end
try lambda = MDP.lambda; catch, lambda = 1; end
try W      = MDP.W;      catch, W      = 1; end

% get figure
%--------------------------------------------------------------------------
if PLOT, spm_figure('GetWin','MDP'); clf,   end

% generative model and initial states
%--------------------------------------------------------------------------
P0    = exp(-32);             % smallest probability
T     = MDP.T;                % process depth (the horizon)
Ns    = size(MDP.B{1},1);     % number of hidden states
Nb    = size(MDP.B,1);        % number of time-dependent probabilities
Nu    = size(MDP.B,2);        % number of hidden controls

% likelihood model (for a partially observed MDP implicit in G)
%--------------------------------------------------------------------------
try
    A = MDP.A + P0;
catch
    A = speye(Ns,Ns) + P0;
end
A     = A*diag(1./sum(A));

% transition probabilities (priors)
%--------------------------------------------------------------------------
for i = 1:T
    for j = 1:Nu
        if i == 1 || Nb == T
            B{i,j}   = MDP.B{i,j} + P0;
            H{i,j}   = B{i,j}';
            B{i,j}   = B{i,j}*diag(1./sum(B{i,j}));
            H{i,j}   = H{i,j}*diag(1./sum(H{i,j}));
            lnB{i,j} = log(B{i,j})*W;
            lnH{i,j} = log(H{i,j})*W;
        else
            B{i,j}   = B{1,j};
            lnB{i,j} = lnB{1,j};
            lnH{i,j} = lnH{1,j};
        end
    end
end

% terminal cost probabilities (priors)
%--------------------------------------------------------------------------
try
    C = spm_vec(MDP.C) + P0;
catch
    C = ones(Ns,1);
end

% control probabilities (priors)
%--------------------------------------------------------------------------
try
    D = spm_vec(MDP.D) + P0;
catch
    D = ones(Nu,1);
end

% state probabilities (priors)
%--------------------------------------------------------------------------
try
    E = spm_vec(MDP.E) + P0;
catch
    E = ones(Ns,1);
end

C     = C/sum(C);
D     = D/sum(D);
E     = E/sum(E);
lnA   = log(A);
lnD   = log(D)*W;
lnE   = log(E)*W;

% generative process (assume the true process is the same as the model)
%--------------------------------------------------------------------------
try
    G = MDP.G;
catch
    G = MDP.B;
end
Ng    = size(G,1);
for i = 1:T
    for j = 1:Nu
        if i == 1 || Ng == T
            G{i,j}   = G{i,j}*diag(1./sum(G{i,j}));
        else
            G{i,j}   = G{1,j};
        end
    end
end

% effector action (e) and sufficient statistics of proposal density
%--------------------------------------------------------------------------
a        = zeros(Ns,T);                          % state probability
b        = ones(Nu,T);                           % control probability
a(:,T)   = C;                                    % final state
b        = b*diag(1./sum(b));

% posterior expectations (states Q, control R)
%--------------------------------------------------------------------------
Q(:,1,:) = a;
R(:,1,:) = b;

%  initial state and posterior (states Q, control R) and action (E)
%--------------------------------------------------------------------------
s     = find(spm_vec(MDP.S));
S     = sparse(s,1,1,Ns,T);
U     = sparse(Nu,T);

% solve
%==========================================================================
for k = 1:(T - 1)

    % forward and backward passes at this time point
    %----------------------------------------------------------------------
    for i = 1:8
        for t = (T - 1):-1:k

            % get data likelihood if available at this time
            %--------------------------------------------------------------
            if t > k
                at = lnE;
            else
                at = lnA'*S(:,t) + lnE;
            end

            % and accumulate empirical priors
            %--------------------------------------------------------------
            for j = 1:Nu
                if t > 1
                    at  = at     + b(j,t - 1) *lnH{t,j}'*a(:,t - 1);
                end
                at      = at     + b(j,t    ) *lnB{t,j}'*a(:,t + 1);
                bt(j,1) = lnD(j) + a(:,t    )'*lnB{t,j}'*a(:,t + 1);
            end

            % update sufficient statistics of hidden states and control
            %--------------------------------------------------------------
            a(:,t) = spm_softmax(at);
            b(:,t) = spm_softmax(bt);

            % graphics to inspect update scheme
            %==============================================================
            if PLOT > 2

                % posterior beliefs about hidden states
                %----------------------------------------------------------
                spm_plot_states(a,b)

                % pause if requested
                %----------------------------------------------------------
                if PLOT > 3, pause, end

            end

        end

        % graphics to inspect update scheme
        %==================================================================
        if PLOT > 1

            % posterior beliefs about hidden states
            %--------------------------------------------------------------
            spm_plot_states(a,b)

        end
    end

    % sampling of next state (outcome)
    %======================================================================
    for i = 1:Nu
        F(i,1) = B{k,i}(:,s)'*lnA*a(:,k + 1);
    end

    % next action (the action and minimises expected free energy)
    %----------------------------------------------------------------------
    Pu       = spm_softmax(F,lambda);
    i        = find(rand < cumsum(Pu),1);

    % next state (assuming G mediates uncertainty modelled the likelihood)
    %----------------------------------------------------------------------
    Ps       = spm_softmax(G{k,i}(:,s),lambda);
    s        = find(rand < cumsum(Ps),1);

    % save action, state and posterior expectations (states Q, control R)
    %----------------------------------------------------------------------
    P(:,k)     = Pu;
    Q(:,k,:)   = a;
    R(:,k,:)   = b;
    S(s,k + 1) = 1;
    U(i,k)     = 1;

    % plot
    %======================================================================
    if PLOT > 0

        % posterior beliefs about hidden states
        %------------------------------------------------------------------
        spm_plot_states(a,b)

        % states sampled (outcome)
        %------------------------------------------------------------------
        subplot(2,2,3)
        if size(S,1) > 512
            spy(S,16)
        else
            imagesc(1 - S)
        end
        axis square
        title('Sampled state','FontSize',16)
        xlabel('time','FontSize',12)
        ylabel('action','FontSize',12)

        % action sampled (selected)
        %------------------------------------------------------------------
        subplot(2,2,4)
        if size(U,1) > 512
            spy(U,16)
        else
            imagesc(1 - U)
        end
        axis square
        title('Selected action','FontSize',16)
        xlabel('time','FontSize',12)
        ylabel('action','FontSize',12)
        drawnow

    end

end

function spm_plot_states(a,b)

% posterior beliefs about hidden states
%--------------------------------------------------------------------------
subplot(2,2,1)
if size(a,1) > 512
    spy(a > 1/32,16)
else
    imagesc(1 - a)
end
axis square
title('Expected hidden states','FontSize',16)
xlabel('time','FontSize',12)
ylabel('hidden state','FontSize',12)

% posterior beliefs about control states
%--------------------------------------------------------------------------
subplot(2,2,2)
if size(b,1) > 512
    spy(b > 1/8,16)
else
    imagesc(1 - b)
end
axis square
title('Expected control states','FontSize',16)
xlabel('time','FontSize',12)
ylabel('control state','FontSize',12)
drawnow


% Using A Binary Sensor Mask Example
%
% This example demonstrates how to use a binary sensor mask for the
% detection of the pressure field generated by an initial pressure
% distribution within a two-dimensional homogeneous propagation medium. It
% builds on the Homogeneous Propagation Medium Example.
%
% author: Bradley Treeby
% date: 29th June 2009
% last update: 22nd September 2012
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave 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 Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

clear all;

% =========================================================================
% SIMULATION
% =========================================================================

% create the computational grid
Nx = 128;           % number of grid points in the x (row) direction
Ny = 128;           % number of grid points in the y (column) direction
dx = 0.1e-3;        % grid point spacing in the x direction [m]
dy = 0.1e-3;        % grid point spacing in the y direction [m]
kgrid = makeGrid(Nx, dx, Ny, dy);

% define the properties of the propagation medium
medium.sound_speed = 1500; 	% [m/s]
medium.alpha_coeff = 0.75;  % [dB/(MHz^y cm)]
medium.alpha_power = 1.5;

% create initial pressure distribution using makeDisc
disc_magnitude = 5; % [Pa]
disc_x_pos = 50;    % [grid points]
disc_y_pos = 50;    % [grid points]
disc_radius = 8;    % [grid points]
disc_1 = disc_magnitude*makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius);

disc_magnitude = 3; % [Pa]
disc_x_pos = 80;    % [grid points]
disc_y_pos = 60;    % [grid points]
disc_radius = 5;    % [grid points]
disc_2 = disc_magnitude*makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius);

source.p0 = disc_1 + disc_2;

% define a binary sensor mask
sensor_x_pos = Nx/2;        % [grid points]
sensor_y_pos = Ny/2;        % [grid points]
sensor_radius = Nx/2 - 22;  % [grid points]
sensor_arc_angle = 3*pi/2;  % [radians]
sensor.mask = makeCircle(Nx, Ny, sensor_x_pos, sensor_y_pos, sensor_radius, sensor_arc_angle);

% run the simulation
sensor_data = kspaceFirstOrder2D(kgrid, medium, source, sensor);

% reorder the simulation data
sensor_data_reordered = reorderSensorData(kgrid, sensor, sensor_data);

% =========================================================================
% VISUALISATION
% =========================================================================

% plot the initial pressure and sensor distribution
figure;
imagesc(kgrid.y_vec*1e3, kgrid.x_vec*1e3, source.p0 + disc_magnitude*sensor.mask, [-1 1]);
colormap(getColorMap);
ylabel('x-position [mm]');
xlabel('y-position [mm]');
axis image;

% plot the simulated sensor data
figure;
imagesc(sensor_data, [-1, 1]);
colormap(getColorMap);
ylabel('Sensor Position');
xlabel('Time Step');
colorbar;

% plot the re-ordered sensor data
figure;
imagesc(sensor_data_reordered, [-1, 1]);
colormap(getColorMap);
ylabel('Sensor Position');
xlabel('Time Step');
colorbar;


% sensors.m
%   Compute the output of rate gyros, accelerometers, and pressure sensors
%
%  Revised:
%   3/5/2010  - RB
%   5/14/2010 - RB

function y = sensors(uu, P)

    % relabel the inputs
%    pn      = uu(1);
%    pe      = uu(2);
    pd      = uu(3);
%    u       = uu(4);
%    v       = uu(5);
%    w       = uu(6);
    phi     = uu(7);
    theta   = uu(8);
%    psi     = uu(9);
    p       = uu(10);
    q       = uu(11);
    r       = uu(12);
    F_x     = uu(13);
    F_y     = uu(14);
    F_z     = uu(15);
%    M_l     = uu(16);
%    M_m     = uu(17);
%    M_n     = uu(18);
    Va      = uu(19);
%    alpha   = uu(20);
%    beta    = uu(21);
%    wn      = uu(22);
%    we      = uu(23);
%    wd      = uu(24);

    % simulate rate gyros (units are rad/sec)
    y_gyro_x = p + P.sigma_gyro * randn;
    y_gyro_y = q + P.sigma_gyro * randn;
    y_gyro_z = r + P.sigma_gyro * randn;

    % simulate accelerometers (units of g)
    y_accel_x = F_x / P.mass / P.gravity + sin(theta) + P.sigma_accel * randn;
    y_accel_y = F_y / P.mass / P.gravity - cos(theta) * sin(phi) + P.sigma_accel * randn;
    y_accel_z = F_z / P.mass / P.gravity - cos(theta) * cos(phi) + P.sigma_accel * randn;

    % simulate pressure sensors
    y_static_pres = P.rho * P.gravity * (-pd) + P.beta_abs_pres + P.sigma_abs_pres * randn;
    y_diff_pres = P.rho * Va^2 / 2 + P.beta_diff_pres + P.sigma_diff_pres * randn;

    % construct total output
    y = [...
        y_gyro_x;...
        y_gyro_y;...
        y_gyro_z;...
        y_accel_x;...
        y_accel_y;...
        y_accel_z;...
        y_static_pres;...
        y_diff_pres;...
    ];

end


function lik = lik_t(varargin)
%LIK_T  Create a Student-t likelihood structure
%
%  Description
%    LIK = LIK_T('PARAM1',VALUE1,'PARAM2,VALUE2,...)
%    creates Student-t likelihood structure in which the named
%    parameters have the specified values. Any unspecified
%    parameters are set to default values.
%
%    LIK = LIK_T(LIK,'PARAM1',VALUE1,'PARAM2,VALUE2,...)
%    modify a likelihood structure with the named parameters
%    altered with the specified values.
%
%    Parameters for Student-t likelihood [default]
%      sigma2       - scale squared [1]
%      nu           - degrees of freedom [4]
%      sigma2_prior - prior for sigma2 [prior_logunif]
%      nu_prior     - prior for nu [prior_fixed]
%
%    Note! If the prior is 'prior_fixed' then the parameter in
%    question is considered fixed and it is not handled in
%    optimization, grid integration, MCMC etc.
%
%    The likelihood is defined as follows:
%                  __ n
%      p(y|f, z) = || i=1 C(nu,s2) * (1 + 1/nu * (y_i - f_i)^2/s2 )^(-(nu+1)/2)
%
%      where nu is the degrees of freedom, s2 the scale and f_i the
%      latent variable defining the mean. C(nu,s2) is constant
%      depending on nu and s2.
%
%  See also
%    GP_SET, LIK_*, PRIOR_*
%

% Copyright (c) 2009-2010 Jarno Vanhatalo
% Copyright (c) 2010 Aki Vehtari
% Copyright (c) 2011 Pasi Jyl�nki

% 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 = 'LIK_T';
  ip.addOptional('lik', [], @isstruct);
  ip.addParamValue('sigma2',0.1, @(x) isscalar(x) && x>0);
  ip.addParamValue('sigma2_prior',prior_logunif(), @(x) isstruct(x) || isempty(x));
  ip.addParamValue('nu',4, @(x) isscalar(x) && x>0);
  ip.addParamValue('nu_prior',prior_fixed, @(x) isstruct(x) || isempty(x));
  ip.parse(varargin{:});
  lik=ip.Results.lik;

  if isempty(lik)
    init=true;
    lik.type = 'Student-t';
  else
    if ~isfield(lik,'type') || ~isequal(lik.type,'Student-t')
      error('First argument does not seem to be a valid likelihood function structure')
    end
    init=false;
  end

  % Initialize parameters
  if init || ~ismember('sigma2',ip.UsingDefaults)
    lik.sigma2 = ip.Results.sigma2;
  end
  if init || ~ismember('nu',ip.UsingDefaults)
    lik.nu = ip.Results.nu;
  end
  % Initialize prior structure
  if init
    lik.p=[];
  end
  if init || ~ismember('sigma2_prior',ip.UsingDefaults)
    lik.p.sigma2=ip.Results.sigma2_prior;
  end
  if init || ~ismember('nu_prior',ip.UsingDefaults)
    lik.p.nu=ip.Results.nu_prior;
  end

  if init
    % Set the function handles to the subfunctions
    lik.fh.pak = @lik_t_pak;
    lik.fh.unpak = @lik_t_unpak;
    lik.fh.lp = @lik_t_lp;
    lik.fh.lpg = @lik_t_lpg;
    lik.fh.ll = @lik_t_ll;
    lik.fh.llg = @lik_t_llg;
    lik.fh.llg2 = @lik_t_llg2;
    lik.fh.llg3 = @lik_t_llg3;
    lik.fh.tiltedMoments = @lik_t_tiltedMoments;
    lik.fh.tiltedMoments2 = @lik_t_tiltedMoments2;
    lik.fh.siteDeriv = @lik_t_siteDeriv;
    lik.fh.siteDeriv2 = @lik_t_siteDeriv2;
    lik.fh.optimizef = @lik_t_optimizef;
    lik.fh.upfact = @lik_t_upfact;
    lik.fh.invlink = @lik_t_invlink;
    lik.fh.predy = @lik_t_predy;
    lik.fh.predprcty = @lik_t_predprcty;
    lik.fh.recappend = @lik_t_recappend;
  end

end

function [w, s] = lik_t_pak(lik)
%LIK_T_PAK  Combine likelihood parameters into one vector.
%
%  Description
%    W = LIK_T_PAK(LIK) takes a likelihood structure LIK and
%    combines the parameters into a single row vector W. This
%    is a mandatory subfunction used for example in energy and
%    gradient computations.
%
%       w = [ log(lik.sigma2)
%             (hyperparameters of lik.sigma2)
%             log(log(lik.nu))
%             (hyperparameters of lik.nu)]'
%
%  See also
%    LIK_T_UNPAK, GP_PAK

  w = []; s = {};
  if ~isempty(lik.p.sigma2)
    w = [w log(lik.sigma2)];
    s = [s; 'log(lik.sigma2)'];
    [wh sh] = lik.p.sigma2.fh.pak(lik.p.sigma2);
    w = [w wh];
    s = [s; sh];
  end
  if ~isempty(lik.p.nu)
    w = [w log(log(lik.nu))];
    s = [s; 'loglog(lik.nu)'];
    [wh sh] = lik.p.nu.fh.pak(lik.p.nu);
    w = [w wh];
    s = [s; sh];
  end
end

function [lik, w] = lik_t_unpak(lik, w)
%LIK_T_UNPAK  Extract likelihood parameters from the vector.
%
%  Description
%    W = LIK_T_UNPAK(W, LIK) takes a likelihood structure LIK and
%    extracts the parameters from the vector W to the LIK
%    structure. This is a mandatory subfunction used for example
%    in energy and gradient computations.
%
%    Assignment is inverse of
%       w = [ log(lik.sigma2)
%             (hyperparameters of lik.sigma2)
%             log(log(lik.nu))
%             (hyperparameters of lik.nu)]'
%
%   See also
%   LIK_T_PAK, GP_UNPAK

  if ~isempty(lik.p.sigma2)
    lik.sigma2 = exp(w(1));
    w = w(2:end);
    [p, w] = lik.p.sigma2.fh.unpak(lik.p.sigma2, w);
    lik.p.sigma2 = p;
  end
  if ~isempty(lik.p.nu)
    lik.nu = exp(exp(w(1)));
    w = w(2:end);
    [p, w] = lik.p.nu.fh.unpak(lik.p.nu, w);
    lik.p.nu = p;
  end
end

function lp = lik_t_lp(lik)
%LIK_T_LP  log(prior) of the likelihood parameters
%
%  Description
%    LP = LIK_T_LP(LIK) takes a likelihood structure LIK and
%    returns log(p(th)), where th collects the parameters.
%    This subfunction is needed when there are likelihood parameters.
%
%  See also
%    LIK_T_LLG, LIK_T_LLG3, LIK_T_LLG2, GPLA_E

  v = lik.nu;
  sigma2 = lik.sigma2;
  lp = 0;

  if ~isempty(lik.p.sigma2)
    lp = lp + lik.p.sigma2.fh.lp(sigma2, lik.p.sigma2) +log(sigma2);
  end
  if ~isempty(lik.p.nu)
    lp = lp + lik.p.nu.fh.lp(lik.nu, lik.p.nu)  +log(v) +log(log(v));
  end
end

function lpg = lik_t_lpg(lik)
%LIK_T_LPG  d log(prior)/dth of the likelihood parameters th
%
%  Description
%    LPG = LIK_T_LPG(LIK) takes a likelihood structure LIK
%    and returns d log(p(th))/dth, where th collects the
%    parameters. This subfunction is needed when there are
%    likelihood parameters.
%
%  See also
%    LIK_T_LLG, LIK_T_LLG3, LIK_T_LLG2, GPLA_G

% Evaluate the gradients of log(prior)

  v = lik.nu;
  sigma2 = lik.sigma2;
  lpg = [];
  i1 = 0;

  if ~isempty(lik.p.sigma2)
    i1 = i1+1;
    lpg(i1) = lik.p.sigma2.fh.lpg(lik.sigma2, lik.p.sigma2).*sigma2 + 1;
  end
  if ~isempty(lik.p.nu)
    i1 = i1+1;
    lpg(i1) = lik.p.nu.fh.lpg(lik.nu, lik.p.nu).*v.*log(v) +log(v) + 1;
  end
end

function ll = lik_t_ll(lik, y, f, z)
%LIK_T_LL  Log likelihood
%
%  Description
%    LL = LIK_T_LL(LIK, Y, F) takes a likelihood structure LIK,
%    observations Y, and latent values F. Returns the log
%    likelihood, log p(y|f,z). This subfunction is needed when
%    using Laplace approximation or MCMC for inference with
%    non-Gaussian likelihoods. This subfunction is also used in
%    information criteria (DIC, WAIC) computations.
%
%  See also
%    LIK_T_LLG, LIK_T_LLG3, LIK_T_LLG2, GPLA_E

  r = y-f;
  v = lik.nu;
  sigma2 = lik.sigma2;

  term = gammaln((v + 1) / 2) - gammaln(v/2) -log(v.*pi.*sigma2)/2;
  ll = term + log(1 + (r.^2)./v./sigma2) .* (-(v+1)/2);
  ll = sum(ll);
end

function llg = lik_t_llg(lik, y, f, param, z)
%LIK_T_LLG  Gradient of the log likelihood
%
%  Description
%    LOKLIKG = LIK_T_LLG(LIK, Y, F, PARAM) takes a likelihood
%    structure LIK, observations Y, and latent values F. Returns
%    the gradient of log likelihood with respect to PARAM. At the
%    moment PARAM can be 'param' or 'latent'. This subfunction is
%    needed when using Laplace approximation or MCMC for inference
%    with non-Gaussian likelihoods.
%
%  See also
%    LIK_T_LL, LIK_T_LLG2, LIK_T_LLG3, GPLA_E

  r = y-f;
  v = lik.nu;
  sigma2 = lik.sigma2;

  switch param
    case 'param'
      n = length(y);

      i1=0;
      if ~isempty(lik.p.sigma2)
        i1=i1+1;
        % Derivative with respect to sigma2
        llg(i1) = -n./sigma2/2 + (v+1)./2.*sum(r.^2./(v.*sigma2.^2+r.^2*sigma2));
        % correction for the log transformation
        llg(i1) = llg(i1).*sigma2;
      end
      if ~isempty(lik.p.nu)
        i1=i1+1;
        % Derivative with respect to nu
        llg(i1) = 0.5.* sum(psi((v+1)./2) - psi(v./2) - 1./v - log(1+r.^2./(v.*sigma2)) + (v+1).*r.^2./(v.^2.*sigma2 + v.*r.^2));

        % correction for the log transformation
        llg(i1) = llg(i1).*v.*log(v);
      end
    case 'latent'
      llg  = (v+1).*r ./ (v.*sigma2 + r.^2);
  end

end

function llg2 = lik_t_llg2(lik, y, f, param, z)
%LIK_T_LLG2  Second gradients of log likelihood
%
%  Description
%    LLG2 = LIK_T_LLG2(LIK, Y, F, PARAM) takes a likelihood
%    structure LIK, observations Y, and latent values F. Returns
%    the Hessian of log likelihood with respect to PARAM. At the
%    moment PARAM can be only 'latent'. LLG2 is a vector with
%    diagonal elements of the Hessian matrix (off diagonals are
%    zero). This subfunction is needed when using Laplace
%    approximation or EP for inference with non-Gaussian likelihoods.
%
%  See also
%    LIK_T_LL, LIK_T_LLG, LIK_T_LLG3, GPLA_E

  r = y-f;
  v = lik.nu;
  sigma2 = lik.sigma2;

  switch param
    case 'param'

    case 'latent'
      % The Hessian d^2 /(dfdf)
      llg2 =  (v+1).*(r.^2 - v.*sigma2) ./ (v.*sigma2 + r.^2).^2;
    case 'latent+param'
      % gradient d^2 / (dfds2)
      llg2 = -v.*(v+1).*r ./ (v.*sigma2 + r.^2).^2;

      % Correction for the log transformation
      llg2 = llg2.*sigma2;
      if ~isempty(lik.p.nu)
        % gradient d^2 / (dfdnu)
        llg2(:,2) = r./(v.*sigma2 + r.^2) - sigma2.*(v+1).*r./(v.*sigma2 + r.^2).^2;

        % Correction for the log transformation
        llg2(:,2) = llg2(:,2).*v.*log(v);
      end
  end
end

function llg3 = lik_t_llg3(lik, y, f, param, z)
%LIK_T_LLG3  Third gradients of log likelihood (energy)
%
%  Description
%    LLG3 = LIK_T_LLG3(LIK, Y, F, PARAM) takes a likelihood
%    structure LIK, observations Y and latent values F and
%    returns the third gradients of log likelihood with respect
%    to PARAM. At the moment PARAM can be only 'latent'. G3 is a
%    vector with third gradients. This subfunction is needed when
%    using Laplace approximation for inference with non-Gaussian
%    likelihoods.
%
%  See also
%    LIK_T_LL, LIK_T_LLG, LIK_T_LLG2, GPLA_E, GPLA_G

  r = y-f;
  v = lik.nu;
  sigma2 = lik.sigma2;

  switch param
    case 'param'

    case 'latent'
      % Return the diagonal of W differentiated with respect to latent values / dfdfdf
      llg3 = (v+1).*(2.*r.^3 - 6.*v.*sigma2.*r) ./ (v.*sigma2 + r.^2).^3;
    case 'latent2+param'
      % Return the diagonal of W differentiated with respect to
      % likelihood parameters / dfdfds2
      llg3 = (v+1).*v.*( v.*sigma2 - 3.*r.^2) ./ (v.*sigma2 + r.^2).^3;
      llg3 = llg3.*sigma2;
      if ~isempty(lik.p.nu)
        % dfdfdnu
        llg3(:,2) = (r.^2-2.*v.*sigma2-sigma2)./(v.*sigma2 + r.^2).^2 - 2.*sigma2.*(r.^2-v.*sigma2).*(v+1)./(v.*sigma2 + r.^2).^3;
        llg3(:,2) = llg3(:,2).*v.*log(v);
      end
  end
end

function [logM_0, m_1, sigm2hati1] = lik_t_tiltedMoments(lik, y, i1, sigm2_i, myy_i, z)
%LIK_T_TILTEDMOMENTS  Returns the marginal moments for EP algorithm
%
%  Description
%    [M_0, M_1, M2] = LIK_T_TILTEDMOMENTS(LIK, Y, I, S2, MYY, Z)
%    takes a likelihood structure LIK, incedence counts Y,
%    expected counts Z, index I and cavity variance S2 and mean
%    MYY. Returns the zeroth moment M_0, mean M_1 and variance
%    M_2 of the posterior marginal (see Rasmussen and Williams
%    (2006): Gaussian processes for Machine Learning, page 55).
%    This subfunction is needed when using EP for inference with
%    non-Gaussian likelihoods.
%
%  See also
%    GPEP_E

  zm = @zeroth_moment;

  tol = 1e-8;
  yy = y(i1);
  nu = lik.nu;
  sigma2 = lik.sigma2;

  % Set the limits for integration and integrate with quad
  % -----------------------------------------------------
  mean_app = myy_i;
  sigm_app = sqrt(sigm2_i);

  lambdaconf(1) = mean_app - 8.*sigm_app; lambdaconf(2) = mean_app + 8.*sigm_app;
  test1 = zm((lambdaconf(2)+lambdaconf(1))/2) > zm(lambdaconf(1));
  test2 = zm((lambdaconf(2)+lambdaconf(1))/2) > zm(lambdaconf(2));
  testiter = 1;
  if test1 == 0
    lambdaconf(1) = lambdaconf(1) - 3*sigm_app;
    test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
    if test1 == 0
      go=true;
      while testiter<10 & go
        lambdaconf(1) = lambdaconf(1) - 2*sigm_app;
        lambdaconf(2) = lambdaconf(2) - 2*sigm_app;
        test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
        test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
        if test1==1&test2==1
          go=false;
        end
        testiter=testiter+1;
      end
    end
    mean_app = (lambdaconf(2)+lambdaconf(1))/2;
  elseif test2 == 0
    lambdaconf(2) = lambdaconf(2) + 3*sigm_app;
    test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
    if test2 == 0
      go=true;
      while testiter<10 & go
        lambdaconf(1) = lambdaconf(1) + 2*sigm_app;
        lambdaconf(2) = lambdaconf(2) + 2*sigm_app;
        test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
        test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
        if test1==1&test2==1
          go=false;
        end
        testiter=testiter+1;
      end
    end
    mean_app = (lambdaconf(2)+lambdaconf(1))/2;
  end
  RTOL = 1.e-6;
  ATOL = 1.e-10;

  % Integrate with quadrature
  [m_0, m_1, m_2] = quad_moments(zm,lambdaconf(1), lambdaconf(2), RTOL, ATOL);

  sigm2hati1 = m_2 - m_1.^2;
  logM_0 = log(m_0);
  function integrand = zeroth_moment(f)
    r = yy-f;
    term = gammaln((nu + 1) / 2) - gammaln(nu/2) -log(nu.*pi.*sigma2)/2;
    integrand = exp(term + log(1 + r.^2./nu./sigma2) .* (-(nu+1)/2));
    integrand = integrand.*exp(- 0.5 * (f-myy_i).^2./sigm2_i - log(sigm2_i)/2 - log(2*pi)/2); %
  end
end

function [g_i] = lik_t_siteDeriv(lik, y, i1, sigm2_i, myy_i, z)
%LIK_T_SITEDERIV  Evaluate the expectation of the gradient
%                 of the log likelihood term with respect
%                 to the likelihood parameters for EP
%
%  Description
%    [M_0, M_1, M2] = LIK_T_TILTEDMOMENTS(LIK, Y, I, S2, MYY)
%    takes a likelihood structure LIK, observations Y, index I
%    and cavity variance S2 and mean MYY. Returns E_f [d log
%    p(y_i|f_i) /d a], where a is the likelihood parameter and
%    the expectation is over the marginal posterior. This term is
%    needed when evaluating the gradients of the marginal
%    likelihood estimate Z_EP with respect to the likelihood
%    parameters (see Seeger (2008): Expectation propagation for
%    exponential families).  This subfunction is needed when using
%    EP for inference with non-Gaussian likelihoods and there are
%    likelihood parameters.
%
%  See also
%    GPEP_G

  zm = @zeroth_moment;
  znu = @deriv_nu;
  zsigma2 = @deriv_sigma2;

  tol = 1e-8;
  yy = y(i1);
  nu = lik.nu;
  sigma2 = lik.sigma2;

  % Set the limits for integration and integrate with quad
  mean_app = myy_i;
  sigm_app = sqrt(sigm2_i);

  lambdaconf(1) = mean_app - 6.*sigm_app; lambdaconf(2) = mean_app + 6.*sigm_app;
  test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
  test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
  testiter = 1;
  if test1 == 0
    lambdaconf(1) = lambdaconf(1) - 3*sigm_app;
    test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
    if test1 == 0
      go=true;
      while testiter<10 & go
        lambdaconf(1) = lambdaconf(1) - 2*sigm_app;
        lambdaconf(2) = lambdaconf(2) - 2*sigm_app;
        test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
        test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
        if test1==1&test2==1
          go=false;
        end
        testiter=testiter+1;
      end
    end
    mean_app = (lambdaconf(2)+lambdaconf(1))/2;
  elseif test2 == 0
    lambdaconf(2) = lambdaconf(2) + 3*sigm_app;
    test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
    if test2 == 0
      go=true;
      while testiter<10 & go
        lambdaconf(1) = lambdaconf(1) + 2*sigm_app;
        lambdaconf(2) = lambdaconf(2) + 2*sigm_app;
        test1 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(1));
        test2 = zm((lambdaconf(2)+lambdaconf(1))/2)>zm(lambdaconf(2));
        if test1==1&test2==1
          go=false;
        end
        testiter=testiter+1;
      end
    end
    mean_app = (lambdaconf(2)+lambdaconf(1))/2;
  end

  % Integrate with quad
  [m_0, fhncnt] = quadgk(zm, lambdaconf(1), lambdaconf(2));

  %         t=linspace(lambdaconf(1),lambdaconf(2),100);
  %         plot(t,zm(t))
  %         keyboard

  [g_i(1), fhncnt] = quadgk( @(f) zsigma2(f).*zm(f) , lambdaconf(1), lambdaconf(2));
  g_i(1) = g_i(1)/m_0*sigma2;

  if ~isempty(lik.p.nu)
    [g_i(2), fhncnt] = quadgk(@(f) znu(f).*zm(f) , lambdaconf(1), lambdaconf(2));
    g_i(2) = g_i(2)/m_0.*nu.*log(nu);
  end

  function integrand = zeroth_moment(f)
    r = yy-f;
    term = gammaln((nu + 1) / 2) - gammaln(nu/2) -log(nu.*pi.*sigma2)/2;
    integrand = exp(term + log(1 + r.^2./nu./sigma2) .* (-(nu+1)/2));
    integrand = integrand.*exp(- 0.5 * (f-myy_i).^2./sigm2_i - log(sigm2_i)/2 - log(2*pi)/2);
  end

  function g = deriv_nu(f)
    r = yy-f;
    temp = 1 + r.^2./nu./sigma2;
    g = psi((nu+1)/2)./2 - psi(nu/2)./2 - 1./(2.*nu) - log(temp)./2 + (nu+1)./(2.*temp).*(r./nu).^2./sigma2;
  end

  function g = deriv_sigma2(f)
    r = yy-f;
    g  = -1/sigma2/2 + (nu+1)./2.*r.^2./(nu.*sigma2.^2 + r.^2.*sigma2);
  end

end

function [lnZhat, muhat, sigm2hat] = lik_t_tiltedMoments2(likelih, y, yi, sigm2_i, myy_i, z, eta)
%LIKELIH_T_TILTEDMOMENTS    Returns the marginal moments for EP algorithm
%
%   Description
%   [M_0, M_1, M2] = LIKELIH_T_TILTEDMOMENTS(LIKELIH, Y, I, S2, MYY, Z)
%   takes a likelihood data structure LIKELIH, incedence counts Y,
%   expected counts Z, index I and cavity variance S2 and mean
%   MYY. Returns the zeroth moment M_0, mean M_1 and variance M_2
%   of the posterior marginal (see Rasmussen and Williams (2006):
%   Gaussian processes for Machine Learning, page 55). This subfunction
%   is needed when using robust-EP for inference with non-Gaussian
%   likelihoods.
%
%   See also
%   GPEP_E

  if nargin<7
    eta=1;
  end

  yy = y(yi);
  nu = likelih.nu;
  sigma2 = likelih.sigma2;
  sigma = sqrt(sigma2);

  nuprime = eta*nu+eta-1;
  a=nuprime/2; %a=nu/2;

  u=linspace(log(1e-8),5,200);
  du=u(2)-u(1);
  lnpu=(a-1)*u -a*exp(u)+u;

  %   sigma2 t-likelihood parameter, scale squared
  %   sigm2_i cavity variance
  %   myy_i cavity mean

  sigma2prime = sigma2*nu/nuprime;
  Vu = sigm2_i + (sigma2prime)./exp(u);
  lnZu = 0.5*(-log(2*pi*Vu)) -0.5 * (yy-myy_i)^2 ./Vu;
  lnZt = eta*gammaln((nu+1)/2) - eta/2*log(nu*pi*sigma2) - eta*gammaln(nu/2) - gammaln((nuprime+1)/2) + 0.5*log(nuprime*pi*sigma2prime) + gammaln(nuprime/2);

  ptu=exp(lnpu+lnZu+lnZt);

  Z_0=sum(ptu)*du;
  lnZhat=log(Z_0) + a*log(a)-gammaln(a);

  Vtu=1./(1/sigm2_i +(1/sigma2prime)*exp(u));
  mtu=Vtu.*(myy_i/sigm2_i + (yy/sigma2prime)*exp(u));

  muhat=sum(mtu.*ptu)*du/Z_0;
  sigm2hat=sum((Vtu+mtu.^2).*ptu)*du/Z_0-muhat^2;

  % limiting distribution (nu -> infinity)
%   Vg=1/(1/sigm2_i +eta/sigma2);
%   mg=Vg*(myy_i/sigm2_i +yy*eta/sigma2);
%   sigm_i=sqrt(sigm2_i);
%   sg=sqrt(Vg);
%
%   % set integration limits and scaling
%   nu_lim=1e10;
%   if nu<nu_lim
%
%     if sqrt(sigma2/sigm2_i)<0.05
%       % set the integration limits when the likelihood is very narrow
%
%       % grid resolution
%       dd=10;
%       df = [12*sigm_i/100 2*dd*sigma/100];
%
%       if yy>=myy_i
%         % grid break points
%         bp=[min(myy_i-6*sigm_i,yy-dd*sigma) myy_i-6*sigm_i, ...
%           min(myy_i+6*sigm_i,yy-dd*sigma), yy-dd*sigma, yy+dd*sigma,...
%           max(myy_i+6*sigm_i,yy+dd*sigma)];
%
%         % grid values
%         a=1e-6;
%         fvec =[ bp(1):df(2):bp(2)-a, bp(2):df(1):bp(3)-a, bp(3):max(df):bp(4)-a, ...
%           bp(4):df(2):bp(5)-a, bp(5):df(1):bp(6)];
%       else
%         % grid break points
%         bp=[min(myy_i-6*sigm_i,yy-dd*sigma), yy-dd*sigma, yy+dd*sigma,...
%           max(myy_i-6*sigm_i,yy+dd*sigma), myy_i+6*sigm_i, ...
%           max(myy_i+6*sigm_i,yy+dd*sigma)];
%
%         % grid values
%         a=1e-6;
%         fvec =[ bp(1):df(1):bp(2)-a, bp(2):df(2):bp(3)-a, bp(3):max(df):bp(4)-a, ...
%           bp(4):df(1):bp(5)-a, bp(5):df(2):bp(6)];
%       end
%
%       np=numel(fvec);
%       logpt = lpt(fvec,0);
%       lpt_max = max([logpt lpt([myy_i mg],0)]);
%       lambdaconf=[fvec(1), fvec(end)];
%       for i1=2:np-1
%         if logpt(i1) < lpt_max+log(1e-7) %(exp(logpt(i1))/exp(lpt_max) < 1e-7)
%           lambdaconf(1) = fvec(i1);
%         else
%           break;
%         end
%       end
%       for i1=1:np-2
%         if logpt(end-i1) < lpt_max+log(1e-7) %(exp(logpt(end-i1))/exp(lpt_max) < 1e-7)
%           lambdaconf(2) = fvec(end-i1);
%         else
%           break;
%         end
%       end
%     else
%       % set the integration limits in easier cases
%       np=20;
%       if mg>myy_i
%         lambdaconf=[myy_i-6*sigm_i,max(mg+6*sg,myy_i+6*sigm_i)];
%         fvec=linspace(myy_i,mg,np);
%       else
%         lambdaconf=[min(mg-6*sg,myy_i-6*sigm_i),myy_i+6*sigm_i];
%         fvec=linspace(mg,myy_i,np);
%       end
%       lpt_max=max(lpt(fvec,0));
%     end
%     C=log(1)-lpt_max; % scale the log-density for the quadrature tolerance
%   else
%     lambdaconf=[mg-6*sg,mg+6*sg];
%     C=log(1)-lpt(mg,0);
%   end
%
%   if nu>nu_lim
%     % the limiting Gaussian case
%     Vz=sigm2_i+sigma2/eta;
%     lnZhat = 0.5*(-log(eta) +(1-eta)*log(2*pi*sigma2) -log(2*pi*Vz)) -(0.5/Vz)*(yy-myy_i)^2;
%     muhat = mg;
%     sigm2hat = Vg;
%   else
%     % Integrate with quadrature
%     RTOL = 1.e-6;
%     ATOL = 1.e-7;
%     tic
%     [m_0, m_1, m_2] = quad_moments(@(f) exp(lpt(f,C)),lambdaconf(1), lambdaconf(2), RTOL, ATOL);toc
%     muhat = m_1;
%     sigm2hat = m_2 - m_1.^2;
%     lnZhat = log(m_0) -C;
%   end

  function lpdf = lpt(f,C)
    % logarithm of the tilted distribution
    r = yy-f;
    lpdf = gammaln((nu + 1) / 2) - gammaln(nu/2) -log(nu.*pi.*sigma2)/2;
    lpdf = lpdf + log(1 + r.^2./nu./sigma2) .* (-(nu+1)/2);
    lpdf = lpdf*eta - (0.5/sigm2_i) * (f-myy_i).^2 + (C-log(2*pi*sigm2_i)/2);
  end
end

function [g_i] = lik_t_siteDeriv2(likelih, y, yi, sigm2_i, myy_i, z, eta, lnZhat)
%LIKELIH_T_SITEDERIV   Evaluate the expectation of the gradient
%                           of the log likelihood term with respect
%                           to the likelihood parameters for EP
%
%   Description
%   [M_0, M_1, M2] = LIKELIH_T_TILTEDMOMENTS(LIKELIH, Y, I, S2, MYY)
%   takes a likelihood data structure LIKELIH, observations Y, index I
%   and cavity variance S2 and mean MYY. Returns E_f [d log
%   p(y_i|f_i) /d a], where a is the likelihood parameter and the
%   expectation is over the marginal posterior. This term is
%   needed when evaluating the gradients of the marginal
%   likelihood estimate Z_EP with respect to the likelihood
%   parameters (see Seeger (2008): Expectation propagation for
%   exponential families).  This subfunction is needed when using
%   robust-EP for inference with non-Gaussian likelihoods and there
%   are likelihood parameters.
%
%   See also
%   GPEP_G

  if nargin<7
    eta=1;
  end

  yy = y(yi);
  nu = likelih.nu;
  sigma2 = likelih.sigma2;
  sigma = sqrt(sigma2);

  % limiting distribution (nu -> infinity)
  Vg=1/(1/sigm2_i +eta/sigma2);
  mg=Vg*(myy_i/sigm2_i +yy*eta/sigma2);
  sigm_i=sqrt(sigm2_i);
  sg=sqrt(Vg);

  % set integration limits and scaling
  nu_lim=1e10;
  if nu<nu_lim

    if sqrt(sigma2/sigm2_i)<0.05
      % set the integration limits when the likelihood is very narrow

      % grid resolution
      dd=10;
      df = [12*sigm_i/100 2*dd*sigma/100];

      if yy>=myy_i
        % grid break points
        bp=[min(myy_i-6*sigm_i,yy-dd*sigma) myy_i-6*sigm_i, ...
          min(myy_i+6*sigm_i,yy-dd*sigma), yy-dd*sigma, yy+dd*sigma,...
          max(myy_i+6*sigm_i,yy+dd*sigma)];

        % grid values
        a=1e-6;
        fvec =[ bp(1):df(2):bp(2)-a, bp(2):df(1):bp(3)-a, bp(3):max(df):bp(4)-a, ...
          bp(4):df(2):bp(5)-a, bp(5):df(1):bp(6)];
      else
        % grid break points
        bp=[min(myy_i-6*sigm_i,yy-dd*sigma), yy-dd*sigma, yy+dd*sigma,...
          max(myy_i-6*sigm_i,yy+dd*sigma), myy_i+6*sigm_i, ...
          max(myy_i+6*sigm_i,yy+dd*sigma)];

        % grid values
        a=1e-6;
        fvec =[ bp(1):df(1):bp(2)-a, bp(2):df(2):bp(3)-a, bp(3):max(df):bp(4)-a, ...
          bp(4):df(1):bp(5)-a, bp(5):df(2):bp(6)];
      end

      np=numel(fvec);
      logpt = lpt(fvec,0);
      lpt_max = max([logpt lpt([myy_i mg],0)]);
      lambdaconf=[fvec(1), fvec(end)];
      for i1=2:np-1
        if logpt(i1) < lpt_max+log(1e-7) %(exp(logpt(i1))/exp(lpt_max) < 1e-7)
          lambdaconf(1) = fvec(i1);
        else
          break;
        end
      end
      for i1=1:np-2
        if logpt(end-i1) < lpt_max+log(1e-7) %(exp(logpt(end-i1))/exp(lpt_max) < 1e-7)
          lambdaconf(2) = fvec(end-i1);
        else
          break;
        end
      end
    else
      % set the integration limits in easier cases
      np=20;
      if mg>myy_i
        lambdaconf=[myy_i-6*sigm_i,max(mg+6*sg,myy_i+6*sigm_i)];
        fvec=linspace(myy_i,mg,np);
      else
        lambdaconf=[min(mg-6*sg,myy_i-6*sigm_i),myy_i+6*sigm_i];
        fvec=linspace(mg,myy_i,np);
      end
      lpt_max=max(lpt(fvec,0));
    end
    C=log(1)-lpt_max; % scale the log-density for the quadrature tolerance
  else
    lambdaconf=[mg-6*sg,mg+6*sg];
    C=log(1)-lpt(mg,0);
  end

  if nu>nu_lim
    % the limiting normal observation model
    Vz=sigm2_i+sigma2/eta;
    g_i(1) = 0.5*( (1-eta)/sigma2 -1/Vz/eta  + (yy-myy_i)^2 /Vz^2 /eta ) *sigma2/eta;

    if (isfield(likelih,'p') && ~isempty(likelih.p.nu))
      g_i(2) = 0;
    end
  else

    % Integrate with quadrature
    RTOL = 1.e-6;
    ATOL = 1e-7;

    % Integrate with quad
    %zm=@(f) exp(lpt(f,C));
    %[m_0, fhncnt] = quadgk(zm, lambdaconf(1), lambdaconf(2),'AbsTol',ATOL,'RelTol',RTOL)

    % Use the normalization determined in the lik_t_tiltedMoments2
    m_0=exp(lnZhat+C);

    zm=@(f) deriv_sigma2(f).*exp(lpt(f,C))*sigma2;
    [g_i(1), fhncnt] = quadgk( zm, lambdaconf(1), lambdaconf(2),'AbsTol',ATOL,'RelTol',RTOL);
    g_i(1) = g_i(1)/m_0;

    if (isfield(likelih,'p') && ~isempty(likelih.p.nu))
      zm=@(f) deriv_nu(f).*exp(lpt(f,C));
      [g_i(2), fhncnt] = quadgk( zm, lambdaconf(1), lambdaconf(2),'AbsTol',ATOL,'RelTol',RTOL);
      g_i(2) = g_i(2)/m_0.*nu.*log(nu);
    end

  end

  function lpdf = lpt(f,C)
    % logarithm of the tilted distribution
    r = yy-f;
    lpdf = gammaln((nu + 1) / 2) - gammaln(nu/2) -log(nu.*pi.*sigma2)/2;
    lpdf = lpdf + log(1 + r.^2./nu./sigma2) .* (-(nu+1)/2);
    lpdf = lpdf*eta - (0.5/sigm2_i) * (f-myy_i).^2 + (C-log(2*pi*sigm2_i)/2);
  end

  function g = deriv_nu(f)
    % derivative of the log-likelihood wrt nu
    r = yy-f;
    temp = r.^2 ./(nu*sigma2);
    g = psi((nu+1)/2) - psi(nu/2) - 1/nu;
    g = g + (1+1/nu).*temp./(1+temp);

    % for small values use a more accurate method for log(1+x)
    ii = temp<1e3;
    g(ii) = g(ii)  - log1p(temp(ii));
    g(~ii) = g(~ii) - log(1+temp(~ii));
    g = g*0.5;

  end

  function g = deriv_sigma2(f)
    % derivative of the log-likelihood wrt sigma2
    r = yy-f;
    temp = r.^2 /sigma2;
    g  = -1/sigma2/2 + ((1+1/nu)/2) * temp ./ (1 + temp/nu) /sigma2;
  end

end

function [f, a] = lik_t_optimizef(gp, y, K, Lav, K_fu)
%LIK_T_OPTIMIZEF  function to optimize the latent variables
%                 with EM algorithm
%
%  Description:
%    [F, A] = LIK_T_OPTIMIZEF(GP, Y, K, Lav, K_fu) Takes Gaussian
%    process structure GP, observations Y and the covariance
%    matrix K. Solves the posterior mode of F using EM algorithm
%    and evaluates A = (K + W)\Y as a sideproduct. Lav and K_fu
%    are needed for sparse approximations. For details, see
%    Vanhatalo, Jyl�nki and Vehtari (2009): Gaussian process
%    regression with Student-t likelihood. This subfunction is
%    needed when using lik_specific optimization method for mode
%    finding in Laplace algorithm.
%

  iter = 1;
  sigma2 = gp.lik.sigma2;
%  if sigma2==0
%    f=NaN;a=NaN;
%    return
%  end
  nu = gp.lik.nu;
  n = length(y);

  switch gp.type
    case 'FULL'
      iV = ones(n,1)./sigma2;
      siV = sqrt(iV);
      B = eye(n) + siV*siV'.*K;
      [L,notpositivedefinite] = chol(B);
      if notpositivedefinite
        f=NaN;a=NaN;
        return
      end
      B=B';
      b = iV.*y;
      a = b - siV.*(L'\(L\(siV.*(K*b))));
      f = K*a;
      while iter < 200
        fold = f;
        iV = (nu+1) ./ (nu.*sigma2 + (y-f).^2);
        siV = sqrt(iV);
        B = eye(n) + siV*siV'.*K;
        L = chol(B)';
        b = iV.*y;
        ws=warning('off','MATLAB:nearlySingularMatrix');
        a = b - siV.*(L'\(L\(siV.*(K*b))));
        warning(ws);
        f = K*a;

        if max(abs(f-fold)) < 1e-8
          break
        end
        iter = iter + 1;
      end
    case 'FIC'
      K_uu = K;

      Luu = chol(K_uu)';
      B=Luu\(K_fu');       % u x f

      K = diag(Lav) + B'*B;

      iV = ones(n,1)./sigma2;
      siV = sqrt(iV);
      B = eye(n) + siV*siV'.*K;
      L = chol(B)';
      b = iV.*y;
      a = b - siV.*(L'\(L\(siV.*(K*b))));
      f = K*a;
      while iter < 200
        fold = f;
        iV = (nu+1) ./ (nu.*sigma2 + (y-f).^2);
        siV = sqrt(iV);
        B = eye(n) + siV*siV'.*K;
        L = chol(B)';
        b = iV.*y;
        a = b - siV.*(L'\(L\(siV.*(K*b))));
        f = K*a;

        if max(abs(f-fold)) < 1e-8
          break
        end
        iter = iter + 1;
      end
  end

end

function upfact = lik_t_upfact(gp, y, mu, ll, z)
  nu = gp.lik.nu;
  sigma = sqrt(gp.lik.sigma2);
  sll = sqrt(ll);

  fh_e = @(f) t_pdf(f, nu, y, sigma).*norm_pdf(f, mu, sll);
  EE = quadgk(fh_e, -40, 40);

  fm = @(f) f.*t_pdf(f, nu, y, sigma).*norm_pdf(f, mu, sll)./EE;
  mm  = quadgk(fm, -40, 40);

  fV = @(f) (f - mm).^2.*t_pdf(f, nu, y, sigma).*norm_pdf(f, mu, sll)./EE;
  Varp = quadgk(fV, -40, 40);

  upfact = -(Varp - ll)./ll^2;
end

function [lpy, Ey, Vary] = lik_t_predy(lik, Ef, Varf, y, z)
%LIK_T_PREDY    Returns the predictive mean, variance and density of y
%
%  Description
%    LPY = LIK_T_PREDY(LIK, EF, VARF YT)
%    Returns logarithm of the predictive density PY of YT, that is
%       p(yt | zt) = \int p(yt | f, zt) p(f|y) df.
%    This requires also the observations YT. This subfunction is
%    needed when computing posterior preditive distributions for
%    future observations.
%
%    [LPY, EY, VARY] = LIK_T_PREDY(LIK, EF, VARF) takes a likelihood
%    structure LIK, posterior mean EF and posterior Variance
%    VARF of the latent variable and returns the posterior
%    predictive mean EY and variance VARY of the observations
%    related to the latent variables. This subfunction is needed when
%    computing posterior preditive distributions for future observations.
%

%
%  See also
%    GPLA_PRED, GPEP_PRED, GPMC_PRED

  nu = lik.nu;
  sigma2 = lik.sigma2;
  sigma = sqrt(sigma2);

  Ey = zeros(size(Ef));
  EVary = zeros(size(Ef));
  VarEy = zeros(size(Ef));
  lpy = zeros(size(Ef));
  if nargout > 1
%       for i1=1:length(Ef)
%         %%% With quadrature
%         ci = sqrt(Varf(i1));
%
%         F = @(x) x.*norm_pdf(x,Ef(i1),sqrt(Varf(i1)));
%         Ey(i1) = quadgk(F,Ef(i1)-6*ci,Ef(i1)+6*ci);
%
%         F2 = @(x) (nu./(nu-2).*sigma2).*norm_pdf(x,Ef(i1),sqrt(Varf(i1)));
%         EVary(i1) = quadgk(F2,Ef(i1)-6*ci,Ef(i1)+6*ci);
%
%         F3 = @(x) x.^2.*norm_pdf(x,Ef(i1),sqrt(Varf(i1)));
%         VarEy(i1) = quadgk(F3,Ef(i1)-6*ci,Ef(i1)+6*ci) - Ey(i1).^2;
%       end
%       Vary = EVary + VarEy;

      Ey = Ef;
      if nu>2
        Vary=nu./(nu-2).*sigma2 +Varf;
      else
        warning('Variance of Student''s t-distribution is not defined for nu<=2')
        Vary=NaN+Varf;
      end
  end

  lpy = zeros(length(y),1);
  for i2 = 1:length(y)
    mean_app = Ef(i2);
    sigm_app = sqrt(Varf(i2));

    pd = @(f) t_pdf(y(i2), nu, f, sigma).*norm_pdf(f,Ef(i2),sqrt(Varf(i2)));
    lpy(i2) = log(quadgk(pd, mean_app - 12*sigm_app, mean_app + 12*sigm_app));
  end

end

function prctys = lik_t_predprcty(lik, Ef, Varf, zt, prcty)
%LIK_T_PREDPRCTY  Returns the percentiles of predictive density of y
%
%  Description
%    PRCTY = LIK_T_PREDPRCTY(LIK, EF, VARF YT, ZT)
%    Returns percentiles of the predictive density PY of YT. This
%    subfunction is needed when using function gp_predprcty.
%
%  See also
%    GP_PREDPCTY

  opt=optimset('TolX',1e-5,'Display','off');
  nt=size(Ef,1);
  prctys = zeros(nt,numel(prcty));
  prcty=prcty/100;
  nu = lik.nu;
  nu_p=max(2.5,nu);
  sigma2 = lik.sigma2;
  Vary=nu_p./(nu_p-2).*sigma2 +Varf;
  for i1=1:nt
    ci = sqrt(Varf(i1));
    for i2=1:numel(prcty)
      minf=sqrt(Vary(i1))*tinv(prcty(i2),nu)+(Ef(i1)-2.5*sqrt(Vary(i1)));
      maxf=sqrt(Vary(i1))*tinv(prcty(i2),nu)+(Ef(i1)+2.5*sqrt(Vary(i1)));
      a=(fminbnd(@(a) (quadgk(@(f) tcdf((a-f)/sqrt(Vary(i1)),nu).*norm_pdf(f,Ef(i1),ci),Ef(i1)-6*ci,Ef(i1)+6*ci,'AbsTol',1e-4)-prcty(i2)).^2,minf,maxf,opt));
%       a=(fminbnd(@(a) (quadgk(@(f) quadgk(@(y) t_pdf(y,nu,Ef(i1),sqrt(Vary(i1))),Ef(i1)-12*sqrt(Vary(i1)),a).*norm_pdf(f,Ef(i1),ci),Ef(i1)-6*ci,Ef(i1)+6*ci,'AbsTol',1e-4)-prcty(i2)).^2,minf,maxf,opt));
      prctys(i1,i2)=a;
      close all;
    end
  end
end

function mu = lik_t_invlink(lik, f, z)
%LIK_T_INVLINK  Returns values of inverse link function
%
%  Description
%    P = LIK_T_INVLINK(LIK, F) takes a likelihood structure LIK and
%    latent values F and returns the values MU of inverse link function.
%    This subfunction is needed when using gp_predprctmu.
%
%     See also
%     LIK_T_LL, LIK_T_PREDY

  mu = f;
end

function reclik = lik_t_recappend(reclik, ri, lik)
%RECAPPEND  Record append
%  Description
%    RECCF = GPCF_SEXP_RECAPPEND(RECCF, RI, GPCF) takes old
%    covariance function record RECCF, record index RI, RECAPPEND
%    returns a structure RECCF. This subfunction is needed when
%    using MCMC sampling (gp_mc).

  if nargin == 2
    % Initialize the record
    reclik.type = 'Student-t';

    % Initialize parameters
    reclik.nu = [];
    reclik.sigma2 = [];

    % Set the function handles
    reclik.fh.pak = @lik_t_pak;
    reclik.fh.unpak = @lik_t_unpak;
    reclik.fh.lp = @lik_t_lp;
    reclik.fh.lpg = @lik_t_lpg;
    reclik.fh.ll = @lik_t_ll;
    reclik.fh.llg = @lik_t_llg;
    reclik.fh.llg2 = @lik_t_llg2;
    reclik.fh.llg3 = @lik_t_llg3;
    reclik.fh.tiltedMoments = @lik_t_tiltedMoments;
    reclik.fh.tiltedMoments2 = @lik_t_tiltedMoments2;
    reclik.fh.siteDeriv = @lik_t_siteDeriv;
    reclik.fh.siteDeriv2 = @lik_t_siteDeriv2;
    reclik.fh.optimizef = @lik_t_optimizef;
    reclik.fh.upfact = @lik_t_upfact;
    reclik.fh.invlink = @lik_t_invlink;
    reclik.fh.predy = @lik_t_predy;
    reclik.fh.predprcty = @lik_t_predprcty;
    reclik.fh.recappend = @lik_t_recappend;
    reclik.p.nu=[];
    if ~isempty(ri.p.nu)
      reclik.p.nu = ri.p.nu;
    end
    reclik.p.sigma2=[];
    if ~isempty(ri.p.sigma2)
      reclik.p.sigma2 = ri.p.sigma2;
    end
  else
    % Append to the record
    likp = lik.p;

    % record sigma2
    reclik.sigma2(ri,:) = lik.sigma2;
    if isfield(likp,'sigma2') && ~isempty(likp.sigma2)
      reclik.p.sigma2 = likp.sigma2.fh.recappend(reclik.p.sigma2, ri, likp.sigma2);
    end
    % record nu
    reclik.nu(ri,:) = lik.nu;
    if isfield(likp,'nu') && ~isempty(likp.nu)
      reclik.p.nu = likp.nu.fh.recappend(reclik.p.nu, ri, likp.nu);
    end
  end

end


function [res]=tt_smv(sttm, vec)
%TT-matrix with sparse factors by TT-vector multiplication
%   [res]=TT_SMV(STTM,VEC) Multiplies the TT-matrix stored in the TT1.0
%   format but with "sparse" cores by a vector VEC
%
%
% TT-Toolbox 2.2, 2009-2012
%
%This is TT Toolbox, written by Ivan Oseledets et al.
%Institute of Numerical Mathematics, Moscow, Russia
%webpage: http://spring.inm.ras.ru/osel
%
%For all questions, bugs and suggestions please mail
%ivan.oseledets@gmail.com
%---------------------------

d=size(vec,1);
res=cell(d,1);

n=sttm{d+2}(1);
% m=size(vec{1},1);
rm1=sttm{d+1}(1);
rv1=size(vec{1},2);
%mat{1} is (n x m x rm1 , vec{1} is mxrv1)
%            n x rm1 x rv1
res{1}=sttm{1}*vec{1};
res{1}=reshape(permute(reshape(res{1},[n,rm1,rv1]),[1,3,2]),[n,rv1*rm1]);

if (d>1)
    n=sttm{d+2}(d);
    % m=size(mat{d},2);
    rm1=sttm{d+1}(d-1);
    rv1=size(vec{d},2);
    %mat{1} is (n x rm1 x rv1 , vec{1} is mxrv1)
    res{d}=sttm{d}*vec{d};
    res{d}=reshape(permute(reshape(res{d},[n,rm1,rv1]),[1,3,2]),[n,rv1*rm1]);
end;

for i=2:d-1
    n=sttm{d+2}(i);
    m=size(vec{i},1);
    rm1=sttm{d+1}(i-1);
    rm2=sttm{d+1}(i);
    rv1=size(vec{i},2);
    rv2=size(vec{i},3);
    %mat{i} is n x m x rm1 x rm2, vec{i} is m x rv1 x rv2
    %n x (rv1*rv2)*rm1*rm2
    %n x rm2 x rm1 x m, n x rm2 x rm1 x (rv1*rv2)
    %n x rm2 x rmx1 x rv1 x rv2
    %want: n x rv1*rm1 x rv2*rm2
    res{i}=sttm{i}*reshape(vec{i},[m,rv1*rv2]);
    res{i}=reshape(res{i},[n,rm2,rm1,rv1,rv2]);
    res{i}=permute(res{i},[1,4,3,5,2]);
    res{i}=reshape(res{i},[n,rv1*rm1,rv2*rm2]);
    %res{i}=permute(reshape(permute(mat{i},[1,3,4,2])*reshape(vec{i},[m,rv1*rv2]),[n,rv1,rv2,rm1,rm2]),[1,2,4,3,5]);
end

end


function h = conv(f, g)
%CONV   Convolution of DELTAFUN objects.
%   H = CONV(F, G) produces the convolution of DELTAFUN objects F and G:
%                     -
%                    /
%           H(x) =   |    F(t) G(x-t) dt,  x in [a + c, b + d]
%                    /
%   The output H is a cell array of DELTAFUNs or CLASSICFUNS. If the result of
%   the convolution of F and G is zero, an empty cell is returned. The cell
%   array returned is used by higher level convolutions.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

% Return empty for an empty input:
if ( isempty(f) || isempty(g) )
    h = deltafun();
    return
end

if ( ~isa(f, 'deltafun') )
    deltaMagF = [];
    deltaLocF = [];
    funF = f;
else
    f = simplifyDeltas(f);
    if ( isa(f, 'deltafun') )
        deltaMagF = f.deltaMag;
        deltaLocF = f.deltaLoc;
        funF = f.funPart;
    else
        deltaMagF = [];
        deltaLocF = [];
        funF = f;
    end
end

if ( ~isa(g, 'deltafun') )
    deltaMagG = [];
    deltaLocG = [];
    funG = g;
else
    g = simplifyDeltas(g);
    if ( isa(g, 'deltafun') )
        deltaMagG = g.deltaMag;
        deltaLocG = g.deltaLoc;
        funG = g.funPart;
    else
        deltaMagG = [];
        deltaLocG = [];
        funG = g;
    end
end

% Extract the domains of f and g:
domF = funF.domain;
domG = funG.domain;
a = domF(1);
b = domF(end);
c = domG(1);
d = domG(end);

% Get the threshold for deltafunctions:
pref = chebfunpref();
deltaTol = pref.deltaPrefs.deltaTol;

% Compute the convolution of funParts and append it to the output cell:
h = conv(funF, funG);

% Remove zero funs for simplicity:
zeroIndices = cellfun( @(hk) iszero(hk), h);
h(zeroIndices) = [];

%% Get all the deltafunction contributions
% (f + df) * (g + dg) = f*g + dg * (f + df) + df * (g + dg) - df * dg
%                     = f*g + dg * (f + df/2) + df * (g + dg/2)

% Contributions due to deltafunctions in F:
% df * (g + dg/2):
h = convDeltas(deltaMagF, deltaLocF, g, c, d, deltaTol, h);

% Contributions due to delta functions in G:
% dg * (f + df/2);
h = convDeltas(deltaMagG, deltaLocG, f, a, b, deltaTol, h);

% Check for emptiness:
if ( isempty(h) )
    h = {fun.constructor(0, struct('domain', [a, b]))};
    return
end

%% Make sure that h is a cell array with non-overlapping funs.

% First extract the breakpints from funs forming h:
doms = cellfun(@(hk) hk.domain, h, 'uniformoutput', 0);
breakPoints = sort(unique(horzcat(doms{:})));

% Coalesce breakpoints that are extremely close together.
if ( any(isinf(breakPoints)) )
    tol = 2*eps;  % hscale of functions on unbounded domains is 1.
else
    tol = 2*eps*norm(breakPoints, Inf);
end

% TODO:  Take the average of points which are close together instead of just
% picking one more or less arbitrarily?
closeBreaks = abs(diff(breakPoints)) < tol;
breakPoints(closeBreaks) = [];

% Restrict each FUN to lie within the new breakpoints.
nFuns = length(h);
H = {};
deltaTol = pref.deltaPrefs.proximityTol;
for k = 1:nFuns
    hk = h{k};
    dom = hk.domain;

    % The domain endpoints of each computed FUN must lie _exactly_ within the
    % set of breakpoints or RESTRICT will fail.
    idx1 = find(abs(breakPoints - dom(1)) < tol);
    idx2 = find(abs(breakPoints - dom(2)) < tol);
    hk = changeMap(hk, breakPoints([idx1, idx2]));

    % If we just changed the map of a DELTAFUN, the locations of deltas at the
    % domain endpoints also need to be updated.
    if ( isa(hk, 'deltafun') )
        deltaLoc = hk.deltaLoc;
        domk = hk.domain;
        if( ~isempty(deltaLoc) )
            if ( abs(deltaLoc(1) - domk(1) ) < deltaTol )
                deltaLoc(1) = domk(1);
            end
            if ( abs(deltaLoc(end) - domk(2)) < deltaTol )
                deltaLoc(end) = domk(2);
            end
            hk.deltaLoc = deltaLoc;
        end
    end

    % Do the restriction.
    hk = restrict(hk, breakPoints(idx1:idx2));
    if ( ~isa(hk, 'cell') )
        hk = {hk};
    end
    H = [H, hk]; %#ok<AGROW>
end

% If H has the same number of funs, nothing further needs to be done:
if ( length(H) == length(breakPoints) - 1 )
    return
end

% H has more funs as a result of restrict. Re-initialize h with 0 BNDFUNS:
nFuns = length(breakPoints) - 1;
h = {};
for k = 1:nFuns
    data.domain = breakPoints(k:k+1);
    h = [h, {bndfun(0, data)}]; %#ok<AGROW>
end

% Loop through H and add each fun to the relevant domain piece in h:
for k = 1:length(H)
    hk = H{k};
    dom = hk.domain;
    if ( abs(dom(2) - dom(1)) > tol )
        idx = find(breakPoints == dom(1));
        h{idx} = h{idx} + hk; %#ok<AGROW>
    end
end

end

function h = convDeltas(deltaMagF, deltaLocF, g, c, d, deltaTol, h)
%   H = CONVDELTAS() convolves the function G with deltafunctions described by
%   deltaMagF and deltaLocF. G originally lives on [c, d] and the output is
%   returned in H. This function is called twice so deltafunctions in g are
%   scaled by half to get the correct delta-delta contributions.

%%
% Contributions due to deltafunctions in F:
% df * (g + dg/2):
[m, n] = size(deltaMagF);
% Loop through the delta function matrix:
for i = 1:m
    for j = 1:n
        if ( abs(deltaMagF(i, j)) > deltaTol )
            % Take appropriate derivative, scale and shift the function:
            hij = deltaMagF(i, j) * changeMap(diff(g,i-1), deltaLocF(j) + [c d]);
            % The half below is to make sure that delta-delta interaction
            % is not counted twice:
            if ( isa(hij, 'deltafun') )
                hij.deltaMag = hij.deltaMag/2;
            end

            % If the result is non-zero, append it:
            if ( ~iszero(hij) )
                h = [h, {hij}]; %#ok<AGROW>
            end
        end
    end
end
end


function [pred, accuracy] = cosmo_crossvalidate(ds, classifier, partitions, opt)
% performs cross-validation using a classifier
%
% [pred, accuracy] = cosmo_crossvalidate(dataset, classifier, partitions, opt)
%
% Inputs
%   ds                  struct with fields .samples (PxQ for P samples and
%                       Q features) and .sa.targets (Px1 labels of samples)
%   classifier          function handle to classifier, e.g.
%                       @classify_naive_baysian
%   partitions          For example the output from nfold_partition
%   opt                 optional struct with options for classifier
%     .normalization    optional, one of 'zscore','demean','scale_unit'
%                       to normalize the data prior to classification using
%                       zscoring, demeaning or scaling to [-1,1] along the
%                       first dimension of ds. Normalization
%                       parameters are estimated using the training data
%                       and applied to the testing data.
%     .pca_explained_count   optional, transform the data with PCA prior to
%                            classification, and retain this number of
%                            components
%     .pca_explained_ratio   optional, transform the data with PCA prior to
%                            classification, and retain the components that
%                            explain this percentage of the variance
%                            (value between 0-1)
%    .check_partitions  optional (default: true). If set to false then
%                       partitions are not checked for being set properly.
%    .average_train_X   average the samples in the train set using
%                       cosmo_average_samples. For X, use any parameter
%                       supported by cosmo_average_samples, i.e. either
%                       'count' or 'ratio', and optionally, 'resamplings'
%                       or 'repeats'.
%
% Output
%   pred                Qx1 array with predicted class labels.
%                       elements with no predictions have the value NaN.
%   accuracy            scalar classification accuracy
%   test_chunks         Qx1 array with chunks of input dataset, if each
%                       prediction was based using a single classification
%                       step. Predictions with no or more than one
%                       classification step are set to NaN
%
% Examples:
%     % generate dataset with 3 targets and 4 chunks, first target is 3
%     ds=cosmo_synthetic_dataset('ntargets',3,'nchunks',4,'target1',3);
%     % use take-1-chunk for testing crossvalidation
%     partitions=cosmo_nfold_partitioner(ds);
%     classifier=@cosmo_classify_naive_bayes;
%     % run crossvalidation
%     [pred,accuracy]=cosmo_crossvalidate(ds, classifier, ...
%                                                       partitions);
%     % show targets, chunks, and predictions labels for each of the
%     % four folds
%     cosmo_disp({ds.sa.targets,ds.sa.chunks,pred},'threshold',inf)
%     %|| { [ 3    [ 1    [   3   NaN   NaN   NaN
%     %||     4      1        4   NaN   NaN   NaN
%     %||     5      1        5   NaN   NaN   NaN
%     %||     3      2      NaN     3   NaN   NaN
%     %||     4      2      NaN     5   NaN   NaN
%     %||     5      2      NaN     5   NaN   NaN
%     %||     3      3      NaN   NaN     3   NaN
%     %||     4      3      NaN   NaN     4   NaN
%     %||     5      3      NaN   NaN     5   NaN
%     %||     3      4      NaN   NaN   NaN     3
%     %||     4      4      NaN   NaN   NaN     4
%     %||     5 ]    4 ]    NaN   NaN   NaN     5 ] }
%     cosmo_disp(accuracy)
%     %||  0.917
%     %
%     % use take-2-chunks out for testing crossvalidation, LDA classifier
%     partitions=cosmo_nchoosek_partitioner(ds,2);
%     classifier=@cosmo_classify_lda;
%     % run crossvalidation
%     [pred,accuracy]=cosmo_crossvalidate(ds, classifier, ...
%                                                           partitions);
%     % show targets, chunks, and predictions labels for each of the
%     % four folds
%     cosmo_disp({ds.sa.targets,ds.sa.chunks,pred},'threshold',inf)
%     %|| { [ 3    [ 1    [   5     5     3   NaN   NaN   NaN
%     %||     4      1        4     4     4   NaN   NaN   NaN
%     %||     5      1        5     5     4   NaN   NaN   NaN
%     %||     3      2        3   NaN   NaN     3     3   NaN
%     %||     4      2        4   NaN   NaN     4     4   NaN
%     %||     5      2        5   NaN   NaN     4     5   NaN
%     %||     3      3      NaN     5   NaN     3   NaN     3
%     %||     4      3      NaN     4   NaN     4   NaN     4
%     %||     5      3      NaN     5   NaN     5   NaN     5
%     %||     3      4      NaN   NaN     3   NaN     3     3
%     %||     4      4      NaN   NaN     4   NaN     4     5
%     %||     5 ]    4 ]    NaN   NaN     5   NaN     3     3 ] }
%     cosmo_disp(accuracy)
%     %||   0.778
%     %
%     % as the example above, but (1) use z-scoring on each training set
%     % and apply the estimated mean and std to the test set, and (2)
%     % use odd-even partitioner
%     opt=struct();
%     opt.normalization='zscore';
%     partitions=cosmo_oddeven_partitioner(ds);
%     % run crossvalidation
%     [pred,accuracy]=cosmo_crossvalidate(ds, classifier, ...
%                                                        partitions, opt);
%     % show targets, predicted labels, and accuracy
%     cosmo_disp({ds.sa.targets,ds.sa.chunks,pred},'threshold',inf)
%     %|| { [ 3    [ 1    [ NaN     5
%     %||     4      1      NaN     4
%     %||     5      1      NaN     5
%     %||     3      2        3   NaN
%     %||     4      2        4   NaN
%     %||     5      2        5   NaN
%     %||     3      3      NaN     5
%     %||     4      3      NaN     4
%     %||     5      3      NaN     5
%     %||     3      4        3   NaN
%     %||     4      4        4   NaN
%     %||     5 ]    4 ]      3   NaN ] }
%     cosmo_disp(accuracy)
%     %||   0.75
%
% Notes:
%   - to apply this to a dataset struct as a measure (for searchlights),
%     consider using cosmo_crossvalidation_measure
%   - to average samples in the training set prior to training, use the
%     options provided by cosmo_average_samples prefixed by
%     'average_train_'. For example, to take averages of 5 samples, and use
%     each sample in the input approximately 4 times, use:
%           opt.average_train_count=5;
%           opt.average_train_resamplings=4;
%
% See also: cosmo_crossvalidation_measure, cosmo_average_samples
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    if nargin<4,opt=struct(); end
    if ~isfield(opt, 'normalization'),
        opt.normalization=[];
    end
    if ~isfield(opt, 'check_partitions'),
        opt.check_partitions=true;
    end

    if ~isempty(opt.normalization);
        normalization=opt.normalization;
        opt.autoscale=false; % disable for {matlab,lib}svm classifiers
    else
        normalization=[];
    end

    if all(isfield(opt, {'pca_explained_count','pca_explained_ratio'}))
            error(['pca_explained_count and pca_explained_ratio are ' ...
                'mutually exclusive'])
    elseif isfield(opt, 'pca_explained_count');
        arg_pca='pca_explained_count';
        arg_pca_value=opt.pca_explained_count;
    elseif isfield(opt, 'pca_explained_ratio');
        arg_pca='pca_explained_ratio';
        arg_pca_value=opt.pca_explained_ratio;
    else
        arg_pca=[];
    end

    if opt.check_partitions
        cosmo_check_partitions(partitions, ds);
    end

    % see if samples in training set are to be averaged
    [do_average_train, average_train_opt]=get_average_train_opt(opt);

    train_indices = partitions.train_indices;
    test_indices = partitions.test_indices;

    npartitions=numel(train_indices);

    nsamples=size(ds.samples,1);

    % space for output (one column per partition)
    % the k-th column contains predictions for the k-th fold
    % (with values of NaNs if there was no prediction)
    pred=NaN(nsamples,npartitions);

    targets=ds.sa.targets;

    % process each fold
    for fold=1:npartitions
        train_idxs=train_indices{fold};
        test_idxs=test_indices{fold};
        % for each partition get the training and test data, store in
        % train_data and test_data
        train_data = ds.samples(train_idxs,:);
        train_targets = targets(train_idxs);

        if do_average_train
            % make a minimal dataset, then use cosmo_average_samples to
            % compute averages
            n_train=numel(train_idxs);
            ds_train=struct();
            ds_train.samples=train_data;
            ds_train.sa.chunks=ones(n_train,1);
            ds_train.sa.targets=train_targets;

            ds_train_avg=cosmo_average_samples(ds_train,average_train_opt);
            train_data=ds_train_avg.samples;
            train_targets=ds_train_avg.sa.targets;
        end

        test_data = ds.samples(test_idxs,:);

        % apply pca
        if ~isempty(arg_pca)
            [train_data,pca_params]=cosmo_map_pca(train_data,...
                    arg_pca,arg_pca_value);
            test_data=cosmo_map_pca(test_data,'pca_params',pca_params);
        end

        % apply normalization
        if ~isempty(normalization)
            [train_data,params]=cosmo_normalize(train_data,normalization);
            test_data=cosmo_normalize(test_data,params);
        end

        % then get predictions for the training samples using
        % the classifier, and store these in the k-th column of all_pred.
        p = classifier(train_data, train_targets, test_data, opt);

        pred(test_idxs,fold) = p;
    end

    % compute accuracies
    has_prediction_mask=~isnan(pred);
    correct_mask=bsxfun(@eq,targets,pred) & has_prediction_mask;

    accuracy=sum(correct_mask)/sum(has_prediction_mask);

function [do_average_train, average_train_opt]=get_average_train_opt(opt)
    persistent cached_opt;
    persistent cached_do_average_train;
    persistent cached_average_train_opt;

    if ~isequal(cached_opt,opt)
        cached_average_train_opt=struct();
        cached_do_average_train=false;
        prefix='average_train_';
        keys=fieldnames(opt);
        for k=1:numel(keys)
            key=keys{k};
            sp=cosmo_strsplit(key,prefix);
            if isempty(sp{1})
                % starts with average_train, so take the rest of the key
                % and flag cached_do_average_train
                cached_average_train_opt.(sp{2})=opt.(key);
                cached_do_average_train=true;
            end
        end

        cached_opt=opt;
    end

    do_average_train=cached_do_average_train;
    average_train_opt=cached_average_train_opt;


%IM_BOX Fixed mapping determining a rectangular enclosing a blob (0/1 image)
%
%   B = IM_BOX(A)
%   B = A*IM_BOX
%
% If A is a 0/1 image then B is the same image with all empty (0) border
% columns and rows removed.
%
%   B = IM_BOX(A,N)
%
% If A is a 0/1 image then B is the same image, but having in each direction
% N empty (0) columns and rows around the object (1).
%
%   B = IM_BOX(A,[NX1 NX2 NY1 NY2])
%
% If A is a 0/1 image then B is the same image, but having NX1, NX2 empty
% columns (0) left, respectively right of the object (1) and NY1, NY2 empty
% rows (0) above, respectively below the object(1).
%
%   B = IM_BOX(A,N,ALF)
%
% Adds as many empty (0) columns or rows such that the aspect ratio of
% images (height/width) equals ALF. For ALF == 1, square images are returned.
% For ALF == 0, images are taken as they are and N rows and columns are
% added.
%
% EXAMPLE
% prdatafiles;            % make sure prdatafiles is in the path
% x = kimia_images;       % load kimia images
% x = x*im_box(0);        % remove all empty rows and columns
% x = x*im_box(0,1);      % add rows/columns to make images square
% x = x*im_resize([32 32]); % resample them
% x = x*im_box(1,0);      % add rows/columns and keep image square
% % now all images are 34x34 and no object touches the border.
% show(gendat(x,4))       % show a few
%
% SEE ALSO (PRTools Guide)
% DATASETS, DATAFILES

% Copyright: R.P.W. Duin, r.p.w.duin@37steps.com
% Faculty EWI, Delft University of Technology
% P.O. Box 5031, 2600 GA Delft, The Netherlands

function [b,J] = im_box(varargin)

	argin = shiftargin(varargin,'vector');
  argin = setdefaults(argin,[],[],[]);
  if mapping_task(argin,'definition')
    b = define_mapping(argin,'fixed');
    b = setname(b,'Image bounding box');
  else
    [a,n,alf] = deal(argin{:});
    if isdataset(a)
      error('Command cannot be used for datasets as it may change image size')
    elseif isdatafile(a)
      isobjim(a);
      b = filtim(a,mfilename,{n,alf});
      %b = setfeatsize(b,getfeatsize(a));
    elseif isa(a,'double') || isa(a,'dip_image') % here we have a single image
      if isa(a,'dip_image'), a = double(a); end
      if isempty(n)
        jx = find(sum(a,1) ~= 0);
        jy = find(sum(a,2) ~= 0);
        J = [min(jy),max(jy),min(jx),max(jx)];
        b = a(min(jy):max(jy),min(jx):max(jx));
      else
        if (~isempty(alf) && alf == 0)
          c = a;
        else
          c = feval(mfilename,a);
        end
        [my,mx] = size(c);
        if length(n) == 1
        	n = [n n n n];
        elseif length(n) ~= 4
        	error('Second parameter should be scalar or vector of length 4')
        end
        b = zeros(my+n(3)+n(4),mx+n(1)+n(2));
        b(n(3)+1:n(3)+my,n(1)+1:n(1)+mx) = c;
      end
      if ~isempty(alf) && alf ~= 0
        [m,k] = size(b);
        r = round(m*alf) - k;
        if r == 0
          ;
        elseif r >= 1 % add r columns
          c = zeros(m,k+r);
          c(:,ceil(r/2):ceil(r/2)+k-1) = b;
          b = c;
        else % add rows
          r = round(k/alf) - m;
          c = zeros(m+r,k);
          c(ceil(r/2):ceil(r/2)+m-1,:) = b;
          b = c;
        end
      end
		end
	end

return