function [x]=als_rake_solve(a,y,tol,varargin)
%ALS-MINRES method for the solution of linear systems in QTT-Tucker format
%   [X]=ALS_RAKE_SOLVE(A,Y,TOL,VARARGIN) Attempts to solve the linear
%   system A*X = Y with accuracy EPS using the AMR iteration.
%   Matrix A has to be given in the QTT-Tucker, right-hand side Y should be
%   given in the QTT-Tucker format also. Options are provided in form
%   'PropertyName1',PropertyValue1,'PropertyName2',PropertyValue2 and so
%   on. The parameters are set to default (in brackets in the following)
%   The list of option names and default values are:
%       o x0 - initial approximation [tensor of all ones]
%       o nswp - maximal number of AMR sweeps [10]
%       o verb - verbosity level, 0-silent, 1-sweep info, 2-block info [1]
%       o kickrank - rank-increasing parameter [5]
%       o max_full_size - maximal size of the local matrix to full solver
%       [2500]
%       o local_prec: Local preconditioner, '' - none
%         'cjacobi' - block-Jacobi, diagonal over rank indices ['']
%       o local_iters - number of local gmres restarts [2]
%       o local_restart - dimension of local gmres [40]
%       o trunc_norm - truncation and stopping tolerance: 'resid' - the
%       residual norm is used, 'fro' - Frobenius (L2)  ['resid']
%       o resid_damp - gap between the local solver and truncation. Larger
%       values help to filter some noise, but require more local
%       iterations   [2]
%
%
% 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
%---------------------------

Au = [];

% Inner parameters
max_full_size=2500;

resid_damp = 2; % Truncation error to true residual treshold
als_tol_high = 10;
als_tol_low = 4;
als_iters=2;

nswp=10;
local_restart=40;
local_iters=2;

local_prec = '';
% local_prec_char = 0;
% local_prec = 'jacobi';

rmax=Inf;
trunc_norm = 'residual';
% trunc_norm_char = 1;
% trunc_norm = 'fro';

% local_solver = 'gmres';
% local_solver = 'pcg';

verb=1;
kickrank = 5;
x=[];

nswp_f = 1;
nswp_c = 1;

for i=1:2:length(varargin)-1
    switch lower(varargin{i})
        case 'nswp'
            nswp=varargin{i+1};
        case 'rmax'
            rmax=varargin{i+1};
        case 'x0'
            x=varargin{i+1};
        case 'verb'
            verb=varargin{i+1};
        case 'local_prec'
            local_prec=varargin{i+1};
        case 'local_restart'
            local_restart=varargin{i+1};
        case 'local_iters'
            local_iters=varargin{i+1};
%         case 'local_solver'
%             local_solver=varargin{i+1};
        case 'kickrank'
            kickrank=varargin{i+1};
        case 'als_tol_high'
            als_tol_high=varargin{i+1};
        case 'als_tol_low'
            als_tol_low=varargin{i+1};
        case 'als_iters'
            als_iters=varargin{i+1};
        case  'max_full_size'
            max_full_size=varargin{i+1};
%         case 'step_dpow'
%             step_dpow=varargin{i+1};
%         case 'min_dpow'
%             min_dpow=varargin{i+1};
        case 'resid_damp'
            resid_damp = varargin{i+1};
        case 'trunc_norm'
            trunc_norm = varargin{i+1};
%         case 'bot_conv'
%             bot_conv=varargin{i+1};
%         case 'top_conv'
%             top_conv=varargin{i+1};

        otherwise
            error('Unrecognized option: %s\n',varargin{i});
    end
end

tol2 = tol;

d = y.core.d;
yc = core2cell(y.core);
ryc = y.core.r;
n = cell(d,1);
L = zeros(d,1);
yf = cell(d,1);
ryf = cell(d,1);
for i=1:d
    n{i} = y.tuck{i}.n;
    L(i) = y.tuck{i}.d;
    ryf{i} = y.tuck{i}.r;
    ryf{i}(L(i)+2) = ryc(i+1);
    yf{i} = cell(L(i)+1,1);
    yf{i}(1:L(i)) = core2cell(y.tuck{i});
    yf{i}{L(i)+1} = yc{i};
end;

ac = core2cell(a.core);
rac = a.core.r;
af = cell(d,1);
raf = cell(d,1);
for i=1:d
    raf{i} = a.tuck{i}.r;
    raf{i}(L(i)+2) = rac(i+1);
    af{i} = cell(L(i)+1,1);
    af{i}(1:L(i)) = core2cell(a.tuck{i});
    af{i}{L(i)+1} = ac{i};
end;

xf = cell(d,1);
rxf = cell(d,1);
if (isempty(x))
    x = qtt_tucker;
    xc = tt_ones(1, d);
    x.core = xc;
    rxc = xc.r;
    xc = core2cell(xc);
    x.tuck = cell(d,1);
    for i=1:d
        rxf{i} = ones(L(i)+2,1);
        n{i}(L(i)+1) = 1;
        xf{i} = cell(L(i)+1,1);
        x.tuck{i} = tt_ones(n{i}(1:L(i)));
        xf{i}(1:L(i)) = core2cell(x.tuck{i});
        xf{i}{L(i)+1}=xc{i};
    end;
else
    xc = core2cell(x.core);
    rxc = x.core.r;
    for i=1:d
        rxf{i} = x.tuck{i}.r;
        rxf{i}(L(i)+2)=rxc(i+1);
        n{i}(L(i)+1) = rxc(i);
        xf{i} = cell(L(i)+1,1);
        xf{i}(1:L(i)) = core2cell(x.tuck{i});
        xf{i}{L(i)+1}=xc{i};
    end;
end;

max_dx = 0;
max_res = 0;

phiaf = cell(d,1);
phiyf = cell(d,1);
for i=1:d
    phiaf{i} = cell(L(i)+2,1);
    phiaf{i}{1}=1;
    phiyf{i} = cell(L(i)+2,1);
    phiyf{i}{1}=1;
end;
phiac = cell(d+1,1); phiac{1}=1; phiac{d+1}=1;
phiyc = cell(d+1,1); phiyc{1}=1; phiyc{d+1}=1;

acp = cell(d,1);
ycp = cell(d,1);

max_res_prev = Inf;
max_dx_prev = Inf;
max_frank = 0;
regurg_cnt = 0;
last_sweep = false;

for swp=1:nswp

    % Orthog + phi, Factors
    for i=1:d
        n{i}(L(i)+1)=rxc(i);
        rxf{i}(L(i)+2)=rxc(i+1);
        % Permute core blocks: rc1 is mode index now
        xf{i}{L(i)+1} = permute(xf{i}{L(i)+1}, [2,1,3]); % rxt,rxc1,rxc2
        for j=1:L(i)
            cr = xf{i}{j};
            cr = reshape(cr, rxf{i}(j)*n{i}(j), rxf{i}(j+1));
            [cr,rv]=qr(cr,0);
            cr2 = xf{i}{j+1};
            cr2 = reshape(cr2, rxf{i}(j+1), n{i}(j+1)*rxf{i}(j+2));
            cr2 = rv*cr2;
            rxf{i}(j+1) = size(cr, 2);
            cr = reshape(cr, rxf{i}(j), n{i}(j), rxf{i}(j+1));
            xf{i}{j} = cr;
            xf{i}{j+1} = reshape(cr2, rxf{i}(j+1), n{i}(j+1), rxf{i}(j+2));

            phiaf{i}{j+1} = compute_next_Phi(phiaf{i}{j}, cr, af{i}{j}, cr, 'lr');
            phiyf{i}{j+1} = compute_next_Phi(phiyf{i}{j}, cr, [], yf{i}{j}, 'lr');
        end;
        % core-projected matrix block
        acp{i} = permute(phiaf{i}{L(i)+1}, [2, 1, 3]);
        acp{i} = reshape(acp{i}, raf{i}(L(i)+1), rxf{i}(L(i)+1)*rxf{i}(L(i)+1));
        acp{i} = reshape(permute(af{i}{L(i)+1}, [1,3,2]), rac(i)*rac(i+1), raf{i}(L(i)+1)) * acp{i};
        acp{i} = reshape(acp{i}, rac(i), rac(i+1), rxf{i}(L(i)+1), rxf{i}(L(i)+1));
        acp{i} = permute(acp{i}, [1, 3, 4, 2]);
        % core-projected rhs block
        ycp{i} = phiyf{i}{L(i)+1}.';
        ycp{i} = reshape(permute(yf{i}{L(i)+1}, [1,3,2]), ryc(i)*ryc(i+1), ryf{i}(L(i)+1)) * ycp{i};
        ycp{i} = reshape(ycp{i}, ryc(i), ryc(i+1), rxf{i}(L(i)+1));
        ycp{i} = permute(ycp{i}, [1, 3, 2]);
        % return xf{i}{L{i}+1} to the core state
        xf{i}{L(i)+1} = permute(xf{i}{L(i)+1}, [2,1,3]); % rxc1,rxt,rxc2
    end;

    % ALS over core
    nc = zeros(d,1);
    for i=1:d
        nc(i)=rxf{i}(L(i)+1);
    end;
    for k=1:nswp_c
%     max_res_c = 0;
    % Orthog
    for i=1:d-1
        cr = xf{i}{L(i)+1};
        cr = reshape(cr, rxc(i)*nc(i), rxc(i+1));
        [cr,rv]=qr(cr,0);
        cr2 = xf{i+1}{L(i+1)+1};
        cr2 = reshape(cr2, rxc(i+1), nc(i+1)*rxc(i+2));
        cr2 = rv*cr2;
        rxc(i+1) = size(cr,2);
        % Update Factor-sizes too
%         n{i+1}(L(i+1)+1)=rxc(i+1);
%         rxf{i}(L(i)+2)=rxc(i+1);
        cr = reshape(cr, rxc(i), nc(i), rxc(i+1));
        xf{i}{L(i)+1} = cr;
        xf{i+1}{L(i+1)+1} = reshape(cr2, rxc(i+1), nc(i+1), rxc(i+2));

        phiac{i+1} = compute_next_Phi(phiac{i}, cr, acp{i}, cr, 'lr');
        phiyc{i+1} = compute_next_Phi(phiyc{i}, cr, [], ycp{i}, 'lr');
    end;
    % ALS
    for i=d:-1:1
        Phi1 = phiac{i};
        A1 = acp{i};
        Phi2 = phiac{i+1};

        sol_prev = reshape(xf{i}{L(i)+1}, rxc(i)*nc(i)*rxc(i+1), 1);

        dir = -1;
        if (i==1); dir=0; end;
        local_kickrank = kickrank;
        if (last_sweep); local_kickrank=0; end;
        if (verb>1); fprintf('\t core %d\n', i); end;
        [u,v,max_res,max_dx,flg]=local_solve(Phi1,A1,Phi2, phiyc{i},ycp{i},phiyc{i+1}, ...
            tol2/sqrt(sum(L+1))/resid_damp, resid_damp, trunc_norm, sol_prev, ...
            local_prec, local_restart, local_iters, max_full_size, max_res, max_dx, ...
            dir, rmax, local_kickrank, verb);

        if (flg>0); fprintf('-warn- local_solve did not converge at cb {%d}\n', i); end;

        if (i>1)
            cr2 = xf{i-1}{L(i-1)+1};
            cr2 = reshape(cr2, rxc(i-1)*nc(i-1), rxc(i));
            cr2 = cr2*u;
            rxc(i) = size(v,2);
            % Update Factor-sizes
%             n{i}(L(i)+1)=rxc(i);
%             rxf{i-1}(L(i-1)+2)=rxc(i);
            v = reshape(v.', rxc(i), nc(i), rxc(i+1));
            xf{i}{L(i)+1} = v;
            xf{i-1}{L(i-1)+1} = reshape(cr2, rxc(i-1), nc(i-1), rxc(i));

            phiac{i} = compute_next_Phi(phiac{i+1}, v, acp{i}, v, 'rl');
            phiyc{i} = compute_next_Phi(phiyc{i+1}, v, [], ycp{i}, 'rl');
        else
            v = u*(v.');
            xf{i}{L(i)+1} = reshape(v, rxc(i), nc(i), rxc(i+1));
        end;
    end;
    end;
%     max_res = max(max_res, max_res_c);

    % Optimization over factors
    for i=1:d
        % ALS L->1, the orth. holds
        % Copy the extended factor data to the current array
        curx = xf{i};
        % Last mode size <- largest core rank
        if (rxc(i)>rxc(i+1))
            % Reshape the core block to be the factor one
            curx{L(i)+1} = permute(curx{L(i)+1}, [2, 1, 3]); % rtx, rxc1, rxc2

            cura = af{i};
            % last block has to be convolved with phiac-left
            cura{L(i)+1} = permute(phiac{i}, [1,3,2]);
            cura{L(i)+1} = reshape(cura{L(i)+1}, rxc(i)*rxc(i), rac(i));
            cura{L(i)+1} = cura{L(i)+1}*reshape(af{i}{L(i)+1}, rac(i), raf{i}(L(i)+1)*rac(i+1));
            cura{L(i)+1} = reshape(cura{L(i)+1}, rxc(i), rxc(i), raf{i}(L(i)+1), rac(i+1));
            cura{L(i)+1} = permute(cura{L(i)+1}, [3, 1,2, 4]); % tucker rank is now the left

            cury = yf{i};
            cury{L(i)+1} = phiyc{i}*reshape(yf{i}{L(i)+1}, ryc(i), ryf{i}(L(i)+1)*ryc(i+1));
            cury{L(i)+1} = reshape(cury{L(i)+1}, rxc(i), ryf{i}(L(i)+1), ryc(i+1));
            cury{L(i)+1} = permute(cury{L(i)+1}, [2, 1, 3]); % tucker rank is now the left

            % The L+2-th phi comes from the core
            phiaf{i}{L(i)+2} = phiac{i+1}; % sizes rxc2,ra2,rxc2. rxc2 - our last "rank" index
            phiyf{i}{L(i)+2} = phiyc{i+1};

            n{i}(L(i)+1) = rxc(i);
            rxf{i}(L(i)+2) = rxc(i+1);
        else
            % Reshape the core block to be the factor one
            curx{L(i)+1} = permute(curx{L(i)+1}, [2, 3, 1]); %  rtx, rxc2, rxc1

            cura = af{i};
            % last block has to be convolved with phiac-left
            cura{L(i)+1} = permute(phiac{i+1}, [2, 1,3]);
            cura{L(i)+1} = reshape(cura{L(i)+1}, rac(i+1), rxc(i+1)*rxc(i+1));
            cura{L(i)+1} = reshape(af{i}{L(i)+1}, rac(i)*raf{i}(L(i)+1), rac(i+1)) * cura{L(i)+1};
            cura{L(i)+1} = reshape(cura{L(i)+1}, rac(i), raf{i}(L(i)+1), rxc(i+1), rxc(i+1));
            cura{L(i)+1} = permute(cura{L(i)+1}, [2, 3,4, 1]); % tucker rank is now the left

            cury = yf{i};
            cury{L(i)+1} = reshape(yf{i}{L(i)+1}, ryc(i)*ryf{i}(L(i)+1), ryc(i+1)) * (phiyc{i+1}.');
            cury{L(i)+1} = reshape(cury{L(i)+1}, ryc(i), ryf{i}(L(i)+1), rxc(i+1));
            cury{L(i)+1} = permute(cury{L(i)+1}, [2, 3, 1]); % tucker rank is now the left

            % The L+2-th phi comes from the core
            phiaf{i}{L(i)+2} = phiac{i}; % sizes rxc2,ra2,rxc2. rxc2 - our last "rank" index
            phiyf{i}{L(i)+2} = phiyc{i};

            n{i}(L(i)+1) = rxc(i+1);
            rxf{i}(L(i)+2) = rxc(i);
        end;

        for k=1:nswp_f
%         max_res_f = 0;
        % First, orthogonality L->1
        for j=(L(i)+1):-1:2
            cr = curx{j};
            cr = reshape(cr, rxf{i}(j), n{i}(j)*rxf{i}(j+1));
            [cr,rv]=qr(cr.',0);
            cr2 = curx{j-1};
            cr2 = reshape(cr2, rxf{i}(j-1)*n{i}(j-1), rxf{i}(j));
            cr2 = cr2*(rv.');
            rxf{i}(j) = size(cr, 2);
            cr = reshape(cr.', rxf{i}(j), n{i}(j), rxf{i}(j+1));
            curx{j} = cr;
            curx{j-1} = reshape(cr2, rxf{i}(j-1), n{i}(j-1), rxf{i}(j));

            phiaf{i}{j} = compute_next_Phi(phiaf{i}{j+1}, cr, cura{j}, cr, 'rl');
            phiyf{i}{j} = compute_next_Phi(phiyf{i}{j+1}, cr, [], cury{j}, 'rl');
        end;

        % Now, finally, the optimization itself
%         for j=(L(i)+1):-1:1
        for j=1:(L(i)+1)
            Phi1 = phiaf{i}{j};
            A1 = cura{j};
            Phi2 = phiaf{i}{j+1};

            sol_prev = reshape(curx{j}, rxf{i}(j)*n{i}(j)*rxf{i}(j+1), 1);

            dir = 1;
            if (j==(L(i)+1)); dir=0; end;
            local_kickrank = kickrank;
            if (last_sweep); local_kickrank=0; end;
            if (verb>1); fprintf('\t factor {%d,%d}\n', i, j); end;
            [u,v,max_res,max_dx,flg]=local_solve(Phi1,A1,Phi2, phiyf{i}{j},cury{j},phiyf{i}{j+1}, ...
                tol2/sqrt(sum(L+1))/resid_damp, resid_damp, trunc_norm, sol_prev, ...
                local_prec, local_restart, local_iters, max_full_size, max_res, max_dx, ...
                dir, rmax, local_kickrank, verb);

            if (flg>0); fprintf('-warn- local_solve did not converge at fb {%d}{%d}\n', i, j); end;

            if (j<(L(i)+1))
                cr2 = curx{j+1};
                cr2 = reshape(cr2, rxf{i}(j+1), n{i}(j+1)*rxf{i}(j+2));
                cr2 = (v.')*cr2;
                rxf{i}(j+1) = size(u,2);
                max_frank = max(max_frank, rxf{i}(j+1));
                u = reshape(u, rxf{i}(j), n{i}(j), rxf{i}(j+1));
                curx{j} = u;
                curx{j+1} = reshape(cr2, rxf{i}(j+1), n{i}(j+1), rxf{i}(j+2));

                phiaf{i}{j+1} = compute_next_Phi(phiaf{i}{j}, u, cura{j}, u, 'lr');
                phiyf{i}{j+1} = compute_next_Phi(phiyf{i}{j}, u, [], cury{j}, 'lr');
            else
                u = u*(v.');
                curx{j} = reshape(u, rxf{i}(j), n{i}(j), rxf{i}(j+1));
            end;
        end;
        end;
%         max_res = max(max_res, max_res_f);

        if (i<d)
%             % The factor is ready. Now, orthogonalize it l-to-r
%             for j=1:L(i)
%                 cr = curx{j};
%                 cr = reshape(cr, rxf{i}(j)*n{i}(j), rxf{i}(j+1));
%                 [cr,rv]=qr(cr,0);
%                 cr2 = curx{j+1};
%                 cr2 = reshape(cr2, rxf{i}(j+1), n{i}(j+1)*rxf{i}(j+2));
%                 cr2 = rv*cr2;
%                 rxf{i}(j+1) = size(cr, 2);
%                 cr = reshape(cr, rxf{i}(j), n{i}(j), rxf{i}(j+1));
%                 curx{j} = cr;
%                 curx{j+1} = reshape(cr2, rxf{i}(j+1), n{i}(j+1), rxf{i}(j+2));
%
%                 phiaf{i}{j+1} = compute_next_Phi(phiaf{i}{j}, cr, cura{j}, cr, 'lr');
%                 phiyf{i}{j+1} = compute_next_Phi(phiyf{i}{j}, cr, [], cury{j}, 'lr');
%             end;
            % First L cells are all what we need for factor
            xf{i}(1:L(i)) = curx(1:L(i));
            % The last one should go to the core. Moreover, we need xc1->xc2 orth
            if (rxc(i)>rxc(i+1))
                cr = permute(curx{L(i)+1}, [2, 1, 3]); % now rc1,rt,rc2
            else
                cr = permute(curx{L(i)+1}, [3, 1, 2]); % now rc1,rt,rc2
            end;
            cr = reshape(cr, rxc(i)*rxf{i}(L(i)+1), rxc(i+1));
            [cr,rv]=qr(cr,0);
            cr2 = xf{i+1}{L(i+1)+1};
            cr2 = reshape(cr2, rxc(i+1), rxf{i+1}(L(i+1)+1)*rxc(i+2));
            cr2 = rv*cr2;
            rxc(i+1) = size(cr,2);
            % Update Factor-sizes too
%             n{i+1}(L(i+1)+1)=rxc(i+1);
%             rxf{i}(L(i)+2)=rxc(i+1);
            cr = reshape(cr, rxc(i), rxf{i}(L(i)+1), rxc(i+1));
            xf{i}{L(i)+1} = cr;
            xf{i+1}{L(i+1)+1} = reshape(cr2, rxc(i+1), rxf{i+1}(L(i+1)+1), rxc(i+2));

            % Update acp, ycp
            % core-projected matrix block
            acp{i} = permute(phiaf{i}{L(i)+1}, [2, 1, 3]);
            acp{i} = reshape(acp{i}, raf{i}(L(i)+1), rxf{i}(L(i)+1)*rxf{i}(L(i)+1));
            acp{i} = reshape(permute(af{i}{L(i)+1}, [1,3,2]), rac(i)*rac(i+1), raf{i}(L(i)+1)) * acp{i};
            acp{i} = reshape(acp{i}, rac(i), rac(i+1), rxf{i}(L(i)+1), rxf{i}(L(i)+1));
            acp{i} = permute(acp{i}, [1, 3, 4, 2]);
            % core-projected rhs block
            ycp{i} = phiyf{i}{L(i)+1}.';
            ycp{i} = reshape(permute(yf{i}{L(i)+1}, [1,3,2]), ryc(i)*ryc(i+1), ryf{i}(L(i)+1)) * ycp{i};
            ycp{i} = reshape(ycp{i}, ryc(i), ryc(i+1), rxf{i}(L(i)+1));
            ycp{i} = permute(ycp{i}, [1, 3, 2]);

            phiac{i+1} = compute_next_Phi(phiac{i}, cr, acp{i}, cr, 'lr');
            phiyc{i+1} = compute_next_Phi(phiyc{i}, cr, [], ycp{i}, 'lr');
        else
            xf{i}(1:L(i)) = curx(1:L(i));
            if (rxc(i)>rxc(i+1))
                xf{i}{L(i)+1} = permute(curx{L(i)+1}, [2, 1, 3]); % core block
            else
                xf{i}{L(i)+1} = permute(curx{L(i)+1}, [3, 1, 2]); % core block
            end;
        end;
    end;

    % Residual check, etc

%     x_old = x;
%     for i=1:d
%         x.tuck{i} = cell2core(x.tuck{i}, xf{i}(1:L(i)));
%         xc{i} = xf{i}{L(i)+1};
%     end;
%     x.core = cell2core(x.core, xc);

    if (verb>0)
        fprintf('=als_rake_solve= sweep %d, max_dx: %3.3e, max_res: %3.3e, mrank_c: %d, mrank_f: %d\n', swp, max_dx, max_res, max(rxc), max_frank);
    end;

    if (last_sweep)
        break;
    end;

    if (kickrank<0)
        kickrank=kickrank-1;
    end;

    if (strcmp(trunc_norm, 'fro'))
        if (max_dx<tol)&&(kickrank<=-als_iters)
            last_sweep=true;
%             tol2 = tol;
        end;
        if (max_dx_prev<=tol*als_tol_high)
            if (max_dx>max_dx_prev); regurg_cnt=regurg_cnt+1; fprintf('---- Regurgitation %d\n', regurg_cnt); end;
%             kickrank = -1;
%             tol2=tol/4;
        end;
        if ((regurg_cnt>0)||(max_dx<=tol*als_tol_low))&&(kickrank>0); kickrank=-1; end;
    else
%         for i=1:d
%             x.tuck{i} = cell2core(x.tuck{i}, xf{i}(1:L(i)));
%             xc{i} = xf{i}{L(i)+1};
%         end;
%         x.core = cell2core(x.core, xc);
%         x.dphys = d;
%         Au = mvrk2(a, x, tol, 'y0', Au, 'verb', 0);
%         real_res = norm(Au-y)/norm(y);
%         fprintf('=als_rake_solve= sweep %d, \t\t\t real_res: %3.3e\n', swp, real_res);
        if (max_res<tol)
            break;
%             kickrank = 0;
%             last_sweep = true;
        end;
%         if (max_res<tol)&&(kickrank<=-als_iters)
%             last_sweep=true;
%         end;
%         if (max_res<=tol*als_tol_high)
%             if (max_res>max_res_prev); regurg_cnt=regurg_cnt+1; fprintf('---- Regurgitation %d\n', regurg_cnt); end;
%             if ((regurg_cnt>als_iters)||(max_res<tol)); last_sweep=true; end;
% %             if ((regurg_cnt>0)||(max_res<=tol*als_tol_low))&&(kickrank>0); kickrank=-1; end;
%         end;
    end;

    if (swp==nswp-1)||(regurg_cnt>2)
        last_sweep=true;
%         tol2 = tol;
    end;

    max_res_prev = max_res;
    max_dx_prev = max_dx;

    max_res = 0;
    max_dx = 0;
    max_frank=0;
end;

% Stuff back
for i=1:d
    x.tuck{i} = cell2core(x.tuck{i}, xf{i}(1:L(i)));
    xc{i} = xf{i}{L(i)+1};
end;
x.core = cell2core(x.core, xc);
x.dphys = d;
% x.sz = n;
end

function [y]=bfun3(Phi1,B1,Phi2, x)
% Computes (Phi1 * B1 * Phi2)*x
% Phi1 is of sizes ry1, rB1, rx1
% B1 is of sizes rB1, k1, m1, rB2
% Phi2 is of sizes ry2, rB2, rx2
ry1 = size(Phi1,1); ry2 = size(Phi2,1);
rx1 = size(Phi1,3); rx2 = size(Phi2,3);
rb1=size(B1,1); rb2=size(B1,4);
m1 = size(B1,3);
k1 = size(B1,2);

y = reshape(x, rx1, m1*rx2);
Phi1 = reshape(Phi1, ry1*rb1, rx1);
y = Phi1*y; % size ry1*rb1,m1*rx2 % cplx rb*rx^3*m^2
y = reshape(y, ry1, rb1*m1, rx2);
y = permute(y, [2, 1, 3]);
y = reshape(y, rb1*m1, ry1*rx2);
B1 = permute(B1, [2, 4, 1, 3]);
B1 = reshape(B1, k1*rb2, rb1*m1);
y = B1*y; % size k1*rb2, ry1*rx2 % cplx rb^2*rx^2*n^3
y = reshape(y, k1, rb2, ry1, rx2);
y = permute(y, [2, 4, 3, 1]);
y = reshape(y, rb2*rx2, ry1*k1);
Phi2 = reshape(Phi2, ry2, rb2*rx2);
y = Phi2*y; % size ry2, ry1*k1 % cplx rb*rx^3*n^2
y = y.';
y = reshape(y, ry1*k1*ry2, 1);
end

function [u,v,max_res,max_dx,flg]=local_solve(Phi1,A1,Phi2, phiy1,y1,phiy2, tol, resid_damp, trunc_norm, sol_prev, local_prec, local_restart, local_iters, max_full_size, max_res, max_dx, dir, rmax, kickrank, verb)
rx1 = size(Phi1,1);
n = size(A1,2);
rx2 = size(Phi2,1);
ra1 = size(Phi1,2);
ra2 = size(Phi2,2);

ry1 = size(phiy1,2);
ry2 = size(phiy2,2);
if (dir>0)
    rhs = reshape(y1, ry1, n*ry2);
    rhs = phiy1*rhs;
    rhs = reshape(rhs, rx1*n, ry2);
    if (kickrank>0); y_save = rhs; end;
    rhs = rhs*(phiy2.');
else
    rhs = phiy2.';
    rhs = reshape(y1, ry1*n, ry2)*rhs;
    rhs = reshape(rhs, ry1, n*rx2);
    if (kickrank>0); y_save = rhs; end;
    rhs = phiy1*rhs;
end;
rhs = rhs(:);

norm_rhs = norm(rhs, 'fro');

if (rx1*n*rx2<max_full_size)
    %      |     |    |
    % B = Phi1 - A1 - Phi2
    %      |     |    |
    B = reshape(permute(Phi1, [1, 3, 2]), rx1*rx1, ra1);
    B = B*reshape(A1, ra1, n*n*ra2);
    B = reshape(B, rx1, rx1, n, n, ra2);
    B = permute(B, [1, 3, 2, 4, 5]);
    B = reshape(B, rx1*n*rx1*n, ra2);
    B = B*reshape(permute(Phi2, [2, 1, 3]), ra2, rx2*rx2);
    B = reshape(B, rx1*n, rx1*n, rx2, rx2);
    B = permute(B, [1, 3, 2, 4]);
    B = reshape(B, rx1*n*rx2, rx1*n*rx2);

    if (norm_rhs==0.0)
        % Ground state
        res_prev = norm(B*sol_prev);
        if (res_prev>tol)
            B2 = B+eye(rx1*n*rx2);
            sol_prev2 = sol_prev;
            for it=1:local_restart
                sol = B2 \ sol_prev2;
                sol = sol/norm(sol);
                res_new = norm(B*sol);
                if (strcmp(trunc_norm, 'fro'))
                    if (norm(sol-sol_prev2)<tol); break; end;
                else
                    if (res_new<tol); break; end;
                end;
                sol_prev2 = sol;
            end;
            flg = 0;
        else
            sol = sol_prev;
            res_new = res_prev;
            flg=0;
        end;
    else
        res_prev = norm(B*sol_prev-rhs)/norm_rhs;
        if (res_prev>tol)
            sol = B \ rhs;
            % If the system was ill-conditioned
            %         [sol,flg] = gmres(B, rhs, local_restart, real_tol, 2, [], [], sol);
            res_new = norm(B*sol-rhs)/norm_rhs;
            flg=0;
            if (res_new>tol); flg=1; end;
        else
            sol = sol_prev;
            res_new = res_prev;
            flg=0;
        end;
    end;
else
    if (norm_rhs==0.0)
        res_prev = norm(bfun3(Phi1, A1, Phi2, sol_prev));
        if (res_prev>tol)
            trunc_norm_char = 1;
            local_prec_char = 0;
            if ((strcmp(local_prec, 'cjacobi'))); local_prec_char = 1; end;
            if (strcmp(local_prec, 'ljacobi')); local_prec_char = 2;  end;
            if (strcmp(local_prec, 'rjacobi')); local_prec_char = 3;  end;

            Psi1 = zeros(rx1, rx1, ra1+1);
            Psi1(:,:,1:ra1) = permute(Phi1, [1,3,2]);
            Psi1(:,:,ra1+1) = eye(rx1);
            B1 = zeros(n, n, ra1+1, ra2+1);
            B1(:,:,1:ra1,1:ra2) = permute(A1, [2,3,1,4]);
            B1(:,:,ra1+1,ra2+1) = eye(n);
            B1 = permute(B1, [3,1,2,4]);
            Psi2 = zeros(rx2,rx2,ra2+1);
            Psi2(:,:,1:ra2) = permute(Phi2, [1,3,2]);
            Psi2(:,:,ra2+1) = eye(rx2);
            Psi2 = permute(Psi2, [2,3,1]);

            sol_prev2 = sol_prev;
            for it=1:local_restart
                sol = solve3d_2(Psi1, B1, Psi2, sol_prev2, tol, trunc_norm_char, sol_prev2, local_prec_char, local_restart, local_iters, 1);
                sol = sol/norm(sol);
                res_new = norm(bfun3(Phi1, A1, Phi2, sol));
                if (strcmp(trunc_norm, 'fro'))
                    if (norm(sol-sol_prev2)<tol); break; end;
                else
                    if (res_new<tol); break; end;
                end;
                sol_prev2 = sol;
            end;

            flg=0;
            if (res_new>tol); flg=1; end;
        else
            sol = sol_prev;
            res_new = res_prev;
            flg = 0;
        end;
    else
        res_prev = norm(bfun3(Phi1, A1, Phi2, sol_prev) - rhs)/norm_rhs;
        if (res_prev>tol)
            trunc_norm_char = 1;
%             if (strcmp(trunc_norm, 'fro')); trunc_norm_char = 0; end;
            local_prec_char = 0;
            if ((strcmp(local_prec, 'cjacobi'))); local_prec_char = 1; end;
            if (strcmp(local_prec, 'ljacobi')); local_prec_char = 2;  end;
            if (strcmp(local_prec, 'rjacobi')); local_prec_char = 3;  end;
            if (res_prev>1)
                sol_prev = zeros(rx1*n*rx2, 1);
                res_prev = 1;
            end;

%             sol = solve3d(permute(Phi1,[1,3,2]), A1, permute(Phi2, [1,3,2]), rhs, tol, trunc_norm_char, sol_prev, local_prec_char, local_restart, local_iters, 1);
            sol = solve3d_2(permute(Phi1,[1,3,2]), A1, permute(Phi2, [3,2,1]), rhs, tol, trunc_norm_char, sol_prev, local_prec_char, local_restart, local_iters, 0);

            res_new = norm(bfun3(Phi1, A1, Phi2, sol) - rhs)/norm_rhs;
            flg=0;
            if (res_new>tol); flg=1; end;
        else
            sol = sol_prev;
            res_new = res_prev;
            flg = 0;
        end;
    end;
end;

if (res_prev/res_new<resid_damp)&&(res_new>tol)
    fprintf('--warn-- the residual damp was smaller than in the truncation\n');
end;

% if (flg>0)&&(kickrank==0)
%     sol = sol_prev;
%     res_new = res_prev;
% end;

dx = norm(sol-sol_prev)/norm(sol);
max_dx = max(max_dx, dx);

max_res = max(max_res, res_prev);

if (norm_rhs==0.0)
    norm_rhs=1;
end;

% Truncation
if (dir>=0) % left-to-right
    sol = reshape(sol, rx1*n, rx2);
else
    sol = reshape(sol, rx1, n*rx2);
end;

if (kickrank>=0)
[u,s,v]=svd(sol, 'econ');
s = diag(s);

if (strcmp(trunc_norm, 'fro')) % We are happy with L2 truncation (when? but let it be)
    r = my_chop2(s, tol*resid_damp*norm(s));
else
    % Residual trunc; First, bin-search
    r1 = 1; r2 = numel(s); r = round((r1+r2)/2);
    while (r2-r1>1)
        cursol = u(:,1:r)*diag(s(1:r))*(v(:,1:r)');
        if (rx1*n*rx2<max_full_size)
            res = norm(B*cursol(:)-rhs)/norm_rhs;
        else
            res = norm(bfun3(Phi1, A1, Phi2, cursol)-rhs)/norm_rhs;
        end;
        if (res<max(tol, res_new)*resid_damp)
            r2 = r;
        else
            r1 = r;
        end;
        r = round((r1+r2)/2);
    end;
    r = max(r-1,1);
    % More accurate Linear search
    while (r<=numel(s))
        cursol = u(:,1:r)*diag(s(1:r))*(v(:,1:r)');
        if (rx1*n*rx2<max_full_size)
            res = norm(B*cursol(:)-rhs)/norm_rhs;
        else
            res = norm(bfun3(Phi1, A1, Phi2, cursol)-rhs)/norm_rhs;
        end;
        if (res<max(tol, res_new)*resid_damp)
            break;
        end;
        r = r+1;
    end;
end;

r = min(r, numel(s));
r = min(r, rmax);

else
    if (dir>=0)
        [u,v]=qr(sol, 0);
        v = v';
        r = size(u,2);
        s = ones(r,1);
    else
        [v,u]=qr(sol.', 0);
        u = u.';
        v = conj(v);
        r = size(v,2);
        s = ones(r,1);
    end;
end;

if (verb>1)
    fprintf('=als_rake_solve= dir %d, dx: %3.3e, res_prev: %3.3e, res_new: %3.3e r: %d\n', dir, dx, res_prev, res_new, r);
end;

    if (dir>0) % left-to-right, kickrank, etc
        u = u(:,1:r);
        v = conj(v(:,1:r))*diag(s(1:r));

        if (kickrank>0)
            % Smarter kick: low-rank PCA in residual
            % Matrix: Phi1-A{i}, rhs: Phi1-y{i}, sizes rx(i)*n - ra(i+1)
            leftresid = reshape(Phi1, rx1*ra1, rx1)*reshape(u*v.', rx1, n*rx2);
            leftresid = reshape(leftresid, rx1, ra1*n, rx2);
            leftresid = reshape(permute(leftresid, [2, 1, 3]), ra1*n, rx1*rx2);
            leftresid = reshape(permute(A1, [2,4,1,3]), n*ra2, ra1*n)*leftresid;
            leftresid = reshape(leftresid, n, ra2, rx1, rx2);
            leftresid = reshape(permute(leftresid, [3,1,2,4]), rx1*n, ra2*rx2);
%             leftA = permute(Phi1, [1, 3, 2]);
%             leftA = reshape(leftA, rx1*rx1, ra1);
%             leftA = leftA*reshape(A1, ra1, n*n*ra2);
%             leftA = reshape(leftA, rx1, rx1, n, n, ra2);
%             leftA = permute(leftA, [1, 3, 5, 2, 4]);
%             leftA = reshape(leftA, rx1*n*ra2, rx1*n);
%             leftresid = leftA*reshape(u*v.', rx1*n, rx2);
%             leftresid = reshape(leftresid, rx1*n, ra2*rx2);
            leftresid = [leftresid, -y_save];
%
%             uk = zeros(rx1*n, min(kickrank,rx1*n));
%             for i=1:min(kickrank, rx1*n)
%                 uk2 = uchol(leftresid.', 2);
%                 uk(:,i) = uk2(:,end);
%                 [uk(:,1:i),rv]=qr(uk(:,1:i), 0);
%                 leftresid = leftA*uk(:,i);
%                 leftresid = reshape(leftresid, rx1*n, ra2);
%             end;

% %             [uk,~,~]=svd(leftresid, 'econ');
% %             uk = uk(:,1:min(kickrank, size(uk,2)));
            uk = uchol(leftresid.', kickrank+1);
            uk = uk(:,size(uk,2):-1:max(size(uk,2)-kickrank+1,1));
%             leftresid = leftA*uk;
%             leftresid = reshape(leftresid, rx1*n, ra2*size(uk,2));
%             uk(:,size(uk,2)+1) = uchol(leftresid.', 1);
% %             uk = randn(rx1*n, kickrank);

            [u,rv]=qr([u,uk], 0);
            radd = size(uk,2);
            v = [v, zeros(rx2, radd)];
            v = v*(rv.');
        end;
    elseif (dir<0) % right-to-left
        u = u(:,1:r)*diag(s(1:r));
        v = conj(v(:,1:r));

        if (kickrank>0)
            % Smarter kick: low-rank PCA in residual
            % Matrix: Phi1-A{i}, rhs: Phi1-y{i}, sizes rx(i)*n - ra(i+1)
            rightresid = reshape(Phi2, rx2*ra2, rx2)*(reshape(u*v.', rx1*n, rx2).');
            rightresid = reshape(rightresid, rx2, ra2, rx1, n);
            rightresid = reshape(permute(rightresid, [4, 2, 3, 1]), n*ra2, rx1*rx2);
            rightresid = reshape(A1, ra1*n, n*ra2)*rightresid;
            rightresid = reshape(rightresid, ra1, n, rx1, rx2);
            rightresid = reshape(permute(rightresid, [2,4,1,3]), n*rx2, ra1*rx1);
%             rightA = permute(Phi2, [2, 1, 3]);
%             rightA = reshape(rightA, ra2, rx2*rx2);
%             rightA = reshape(A1, ra1*n*n, ra2)*rightA;
%             rightA = reshape(rightA, ra1, n, n, rx2, rx2);
%             rightA = permute(rightA, [2, 4, 1, 3, 5]);
%             rightA = reshape(rightA, n*rx2*ra1, n*rx2);
%             rightresid = rightA*(reshape(u*v.', rx1, n*rx2).');
%             rightresid = reshape(rightresid, n*rx2, ra1*rx1);
            rightresid = [rightresid, -(y_save.')];

%             uk = zeros(n*rx2, min(kickrank,n*rx2));
%             for i=1:min(kickrank, n*rx2)
%                 uk2 = uchol(rightresid.', 2);
%                 uk(:,i) = uk2(:,end);
%                 [uk(:,1:i),rv]=qr(uk(:,1:i), 0);
%                 rightresid = rightA*uk(:,i);
%                 rightresid = reshape(rightresid, n*rx2, ra1);
%             end;

% %             [uk,~,~]=svd(rightresid, 'econ');
% %             uk = uk(:,1:min(kickrank, size(uk,2)));

% %             uk = randn(n*rx2, kickrank);
            uk = uchol(rightresid.', kickrank+1);
            uk = uk(:,size(uk,2):-1:max(size(uk,2)-kickrank+1,1));

            [v,rv]=qr([v,uk], 0);
            radd = size(uk,2);
            u = [u, zeros(rx1, radd)];
            u = u*(rv.');
        end;
    else
        % Just stuff back the last core
        u = u(:,1:r);
        v = conj(v(:,1:r))*diag(s(1:r));
    end;

end

function [Phi] = compute_next_Phi(Phi_prev, x, A, y, direction)
% Performs the recurrent Phi (or Psi) matrix computation
% Phi = Phi_prev * (x'Ay).
% If direction is 'lr', computes Psi
% if direction is 'rl', computes Phi
% A can be empty, then only x'y is computed.

if (strcmp(direction, 'rl'))
  % Revert ranks to perform the right-to-left recursion
  x = permute(x, [3, 2, 1]);
  y = permute(y, [3, 2, 1]);
  if (~isempty(A))
    A = permute(A, [4, 2, 3, 1]);
  end
end

rx1 = size(x,1); n = size(x,2); rx2 = size(x,3);
ry1 = size(y,1); m = size(y,2); ry2 = size(y,3);
if (~isempty(A))
  ra1 = size(A,1); ra2 = size(A,4);
else
  ra1 = 1; ra2 = 1;
end

Phi = reshape(Phi_prev, [rx1*ra1, ry1]);
y = reshape(y, [ry1, m*ry2]);
Phi = Phi*y;	% complexity §\mcommentfont$\mathcal{O}(n  r_x r_A r_y^2)$§
Phi = reshape(Phi, [rx1, ra1, m, ry2]);
Phi = permute(Phi, [2, 3, 1, 4]);
if (~isempty(A))
  Phi = reshape(Phi, [ra1*m, rx1*ry2]);
  A = permute(A, [4, 2, 1, 3]);
  A = reshape(A, [ra2*n, ra1*m]);
  Phi = A*Phi;	% complexity §\mcommentfont$\mathcal{O}(n^2  r_x r_A^2 r_y)$§
  Phi = reshape(Phi, [ra2, n, rx1, ry2]);
end
Phi = permute(Phi, [3, 2, 1, 4]);
Phi = reshape(Phi, [rx1*n, ra2*ry2]);
x = reshape(x, [rx1*n, rx2]);
Phi = (x')*Phi;	% complexity §\mcommentfont$\mathcal{O}(n  r_x^2 r_A r_y)$§
if (~isempty(A))
  Phi = reshape(Phi, [rx2, ra2, ry2]);
end
end


function [prior,binSize] = part_location_prior(expidx)

p = rcnn_exp_params(expidx);
exp_dir = [p.expDir '/' p.shortName];

conf = rcnn_config('sub_dir', '/cachedir/test', 'exp_dir', exp_dir);
saveTo = [conf.cache_dir '/gtHist/'];
if (~exist(saveTo,'dir'))
    mkdir(saveTo);
end

% sc = p.refHeight/200;
sc = 2;
binSize = p.prior_bin_size*sc;
delta = 300*sc;
nBins = round(2*delta/binSize);

try
    assert(false);
%     load(fname, 'prior');
catch
%     load(p.testGT);
    load(p.trainGT);
%     if (exist('single_person_annolist','var'))
%         annolist = single_person_annolist;
%     end

%     fnameDist = [fileparts(p.evalTest) '/distAll'];
%     load(fnameDist, 'keypointsAll');
%     assert(length(keypointsAll) == length(annolist));

    [~,parts] = util_get_parts24();
    nParts = length(p.pidxs);
    prior = zeros(2*delta,2*delta,nParts);
    histLoc = zeros(nBins,nBins,nParts);
    nEx = zeros(nParts,1);
    X0 = size(prior,1)/2;
    Y0 = size(prior,2)/2;

    for imgidx = 1:length(annolist)
        fprintf('.');
        rect = annolist(imgidx).annorect;
        for ridx = 1:length(rect)
            objpos = rect(ridx).objpos;
            points = rect(ridx).annopoints.point;
            for pidx = 1:length(parts)
                pidxLin = find(p.pidxs == parts(pidx).id);
                if (~isempty(pidxLin))
                    assert(parts(pidx).pos(1) == parts(pidx).pos(2));
                    pp = util_get_annopoint_by_id(points,parts(pidx).pos(1));
%                     pp = keypointsAll(imgidx).det(parts(pidx).pos(1)+1,1:2);
                    if (~isempty(pp))
%                         iy = round(Y0 + sc*(pp(:,2) - objpos.y));
%                         ix = round(X0 + sc*(pp(:,1) - objpos.x));
                        iy = round(Y0 + sc*(pp.y - objpos.y));
                        ix = round(X0 + sc*(pp.x - objpos.x));
                        iy = max(iy,1);
                        iy = min(iy,2*delta);
                        ix = max(ix,1);
                        ix = min(ix,2*delta);
                        iy_hist = ceil(iy/binSize);
                        ix_hist = ceil(ix/binSize);
                        histLoc(iy_hist,ix_hist,pidxLin) = histLoc(iy_hist,ix_hist,pidxLin) + 1;
                        iy_prior = (iy_hist-1)*binSize+1:iy_hist*binSize;
                        ix_prior = (ix_hist-1)*binSize+1:ix_hist*binSize;
                        prior(iy_prior,ix_prior,pidxLin) = prior(iy_prior,ix_prior,pidxLin) + ones(binSize,binSize);
                        nEx(pidxLin) = nEx(pidxLin) + 1;
                    end
                end
            end
        end
        if (~mod(imgidx, 100))
            fprintf(' %d/%d\n',imgidx,length(annolist));
        end
    end
    fprintf(' done\n');
    if (isfield(p,'sigma_gauss'))
        sigma_gauss = p.sigma_gauss;
    else
        sigma_gauss = binSize;
    end
    for pidx = 1:nParts
%         prior(:,:,pidx) = prior(:,:,pidx)/nEx(pidx);
        prior(:,:,pidx) = prior(:,:,pidx)/max(max(prior(:,:,pidx)));
        pr = prior(:,:,pidx);
        pr(pr > 0) = 1;
        filterMask = gausswin(2*sigma_gauss+1)*gausswin(2*sigma_gauss+1)';
        prior(:,:,pidx) = filter2(filterMask, pr);%/nEx(pidx);
        prior(:,:,pidx) = prior(:,:,pidx)/max(max(prior(:,:,pidx)));
    end
%     save(fname, 'prior');
end

labels = {'rankle','rknee','rhip','lhip','lknee','lankle','rwrist','relbow','rshoulder','lshoulder','lelbow','lwrist','neck','tophead'};
tics = [1 size(prior,1)/6:size(prior,1)/6:size(prior,1)];
for pidx = 1:length(parts)
    figure(101);clf;%colormap gray;
    pidxLin = find(p.pidxs == parts(pidx).id);
    if (~isempty(pidxLin))
        pr = prior(:,:,pidxLin);
        imagesc(log(log(pr+1))); hold on; axis equal;
        plot([size(prior,2)/2; size(prior,2)/2], [1; size(prior,1)]', 'k-', 'lineWidth', 2);
        plot([1; size(prior,2)], [size(prior,1)/2; size(prior,1)/2]', 'k-', 'lineWidth', 2);
        title([labels{pidxLin}]);
        set(gca,'XTick',tics,'YTick',tics);
        fImgName = [saveTo '/partDetHist_' labels{pidxLin}];
        print(gcf, '-dpng', [fImgName '.png']);
    end
end
end


function [rmse_train, rmse_test] = aistats2012_mohsst5_rmse(F, testset, seed)

if nargin < 3
  [Ytrain, Ytest, Itrain, Itest, data, sea] = aistats2012_mohsst5_sets(testset);
else
  [Ytrain, Ytest, Itrain, Itest, data, sea] = aistats2012_mohsst5_sets(testset, ...
                                                    seed);
end

[M,N] = size(F);

% Error measures
[LON,LAT] = meshgrid(data.longitude, data.latitude);
weights = cosd(LAT(:));
weights = weights(sea);
weights = repmat(weights, [1, N]);
rmse_train = rmsew(Ytrain(Itrain)-F(Itrain),weights(Itrain));
rmse_test = rmsew(Ytest(Itest)-F(Itest),weights(Itest));
rmse_zero = rmsew(Ytest(Itest),weights(Itest));


classdef C45Classifier < Algorithm

    properties (Access = public)
        shouldTrain = false;
        inc_node;
        region;
    end

    properties (Access = private)
        classifier;
    end

    methods (Access = public)

        function obj = C45Classifier()
            obj.name = 'Ensemble';
            obj.inputPort = DataType.kTable;
            obj.outputPort = DataType.kTable;
        end

        function dataOut = compute(obj,data)
            if obj.shouldTrain
                obj.train(data);
                dataOut = [];
            else
                dataOut = obj.test(data);
            end
        end

        function D = train(obj,table)
            train_features = table.features;
            train_targets = table.labels;

            [Ni, M]		= size(train_features);
            obj.inc_node    = obj.inc_node*M/100;
            Nu          = 10;

            %For the decision obj.region
            N           = obj.region(5);
            mx          = ones(N,1) * linspace (obj.region(1),obj.region(2),N);
            my          = linspace (obj.region(3),obj.region(4),N)' * ones(1,N);
            flatxy      = [mx(:), my(:)]';

            %Find which of the input features are discrete, and discretisize the corresponding
            %dimension on the decision obj.region
            discrete_dim = zeros(1,Ni);
            for i = 1:Ni
                Nb = length(unique(train_features(i,:)));
                if (Nb <= Nu)
                    %This is a discrete feature
                    discrete_dim(i)	= Nb;
                    [~, flatxy(i,:)]	= high_histogram(flatxy(i,:), Nb);
                end
            end

            %Build the tree recursively
            disp('Building tree')
            obj.classifier        = make_tree(train_features, train_targets, obj.inc_node, discrete_dim, max(discrete_dim), 0);
            %{
            %Make the decision obj.region according to the tree
            disp('Building decision surface using the tree')
            targets		= use_tree(flatxy, 1:N^2, obj.classifier, discrete_dim, unique(train_targets));

            D   		= reshape(targets,N,N);
            %}
        end

        function labels = test(obj,table)
            labels = use_tree(table.features,1:N^2, obj.classifier, discrete_dim, unique(train_targets));
        end

        function metrics = computeMetrics(obj,table)
            flops = timeit(@()obj.test(table)) / Constants.kReferenceComputingTime;
            memory = Helper.ComputeObjectSize(obj.classifier);
            outputSize = table.height * Constants.kClassificationResultBytes;
            metrics = Metric(flops,memory,outputSize);
        end

        function str = toString(obj)
            str = sprintf('%s_%d',obj.name,obj.ensembleMethod);
        end

    end

    methods (Access = private)
        function targets = use_tree(features, indices, tree, discrete_dim, Uc)

            targets = zeros(1, size(features,2));

            if (tree.dim == 0)
                %Reached the end of the tree
                targets(indices) = tree.child;
                return;
            end

            %This is not the last level of the tree, so:
            %First, find the dimension we are to work on
            dim = tree.dim;
            dims= 1:size(features,1);

            %And classify according to it
            if (discrete_dim(dim) == 0)
                %Continuous feature
                in				= indices(find(features(dim, indices) <= tree.split_loc));
                targets		= targets + use_tree(features(dims, :), in, tree.child(1), discrete_dim(dims), Uc);
                in				= indices(find(features(dim, indices) >  tree.split_loc));
                targets		= targets + use_tree(features(dims, :), in, tree.child(2), discrete_dim(dims), Uc);
            else
                %Discrete feature
                Uf				= unique(features(dim,:));
                for i = 1:length(Uf)
                    in   	   = indices(find(features(dim, indices) == Uf(i)));
                    targets	= targets + use_tree(features(dims, :), in, tree.child(i), discrete_dim(dims), Uc);
                end
            end
        end

        function tree = make_tree(features, targets, discrete_dim, maxNbin, base)

            %Build a tree recursively

            [Ni, L]    					= size(features);
            Uc         					= unique(targets);
            tree.dim						= 0;
            %tree.child(1:maxNbin)	= zeros(1,maxNbin);
            tree.split_loc				= inf;

            if isempty(features)
                return;
            end

            %When to stop: If the dimension is one or the number of examples is small
            if ((obj.inc_node > L) | (L == 1) | (length(Uc) == 1))
                H					= hist(targets, length(Uc));
                [~, largest] 	= max(H);
                tree.child	 	= Uc(largest);
                return;
            end

            %Compute the node's I
            for i = 1:length(Uc)
                Pnode(i) = length(find(targets == Uc(i))) / L;
            end
            Inode = -sum(Pnode.*log(Pnode)/log(2));

            %For each dimension, compute the gain ratio impurity
            %This is done separately for discrete and continuous features
            delta_Ib    = zeros(1, Ni);
            split_loc	= ones(1, Ni)*inf;

            for i = 1:Ni
                data	= features(i,:);
                Nbins	= length(unique(data));
                if (discrete_dim(i))
                    %This is a discrete feature
                    P	= zeros(length(Uc), Nbins);
                    for j = 1:length(Uc)
                        for k = 1:Nbins
                            indices 	= find((targets == Uc(j)) & (features(i,:) == k));
                            P(j,k) 	= length(indices);
                        end
                    end
                    Pk          = sum(P);
                    P           = P/L;
                    Pk          = Pk/sum(Pk);
                    info        = sum(-P.*log(eps+P)/log(2));
                    delta_Ib(i) = (Inode-sum(Pk.*info))/-sum(Pk.*log(eps+Pk)/log(2));
                else
                    %This is a continuous feature
                    P	= zeros(length(Uc), 2);

                    %Sort the features
                    [sorted_data, indices] = sort(data);
                    sorted_targets = targets(indices);

                    %Calculate the information for each possible split
                    I	= zeros(1, L-1);
                    for j = 1:L-1
                        for k =1:length(Uc)
                            P(k,1) = length(find(sorted_targets(1:j) 		== Uc(k)));
                            P(k,2) = length(find(sorted_targets(j+1:end) == Uc(k)));
                        end
                        Ps		= sum(P)/L;
                        P		= P/L;
                        info	= sum(-P.*log(eps+P)/log(2));
                        I(j)	= Inode - sum(info.*Ps);
                    end
                    [delta_Ib(i), s] = max(I);
                    split_loc(i) = sorted_data(s);
                end
            end

            %Find the dimension minimizing delta_Ib
            [~, dim] = max(delta_Ib);
            dims		= 1:Ni;
            tree.dim = dim;

            %Split along the 'dim' dimension
            Nf		= unique(features(dim,:));
            Nbins	= length(Nf);
            if (discrete_dim(dim))
                %Discrete feature
                for i = 1:Nbins
                    indices    		= find(features(dim, :) == Nf(i));
                    tree.child(i)	= make_tree(features(dims, indices), targets(indices), discrete_dim(dims), maxNbin, base);
                end
            else
                %Continuous feature
                tree.split_loc		= split_loc(dim);
                indices1		   	= find(features(dim,:) <= split_loc(dim));
                indices2	   		= find(features(dim,:) > split_loc(dim));
                tree.child(1)		= make_tree(features(dims, indices1), targets(indices1), obj.inc_node, discrete_dim(dims), maxNbin);
                tree.child(2)		= make_tree(features(dims, indices2), targets(indices2), obj.inc_node, discrete_dim(dims), maxNbin);
            end

        end
    end
end


function [bFun, b0Fun, derFun] = conAffPoint2PlaneSimpleOLS(prm, cst, obs)

% ------------------------------------------------------------------------------

bFun = @(obs) bFunNested(obs);

    function bBlock = bFunNested(obs)

        bBlock = obs.dp;

    end

% ------------------------------------------------------------------------------

b0Fun = @(prm, cst) b0FunNested(prm, cst);

    function b0Block = b0FunNested(prm, cst)

        % Affine matrices
        A1 = [prm.a111 prm.a121 prm.a131
              prm.a211 prm.a221 prm.a231
              prm.a311 prm.a321 prm.a331];

        A2 = [prm.a112 prm.a122 prm.a132
              prm.a212 prm.a222 prm.a232
              prm.a312 prm.a322 prm.a332];

        % Homogeneous transformation matrix
        H1 = homotrafo(1, A1, [prm.tx1 prm.ty1 prm.tz1]);
        H2 = homotrafo(1, A2, [prm.tx2 prm.ty2 prm.tz2]);

        % Coordinates
        X1 = [cst.x1 cst.y1 cst.z1];
        X2 = [cst.x2 cst.y2 cst.z2];

        % Normal vector
        n1 = [cst.nx1 cst.ny1 cst.nz1];

        % Transformation from cartesian coord. to homogeneous coord.
        X1_h = homocoord(X1);
        X2_h = homocoord(X2);

        b0Block = dot((homocoord((H1 * X1_h')') - homocoord((H2 * X2_h')'))', n1')';

    end

% Derivatives ------------------------------------------------------------------

derFun.prm.a111 = @(prm, cst) derFunNested(prm, cst, 'prm_a111');
derFun.prm.a121 = @(prm, cst) derFunNested(prm, cst, 'prm_a121');
derFun.prm.a131 = @(prm, cst) derFunNested(prm, cst, 'prm_a131');
derFun.prm.a211 = @(prm, cst) derFunNested(prm, cst, 'prm_a211');
derFun.prm.a221 = @(prm, cst) derFunNested(prm, cst, 'prm_a221');
derFun.prm.a231 = @(prm, cst) derFunNested(prm, cst, 'prm_a231');
derFun.prm.a311 = @(prm, cst) derFunNested(prm, cst, 'prm_a311');
derFun.prm.a321 = @(prm, cst) derFunNested(prm, cst, 'prm_a321');
derFun.prm.a331 = @(prm, cst) derFunNested(prm, cst, 'prm_a331');
derFun.prm.tx1  = @(prm, cst) derFunNested(prm, cst, 'prm_tx1');
derFun.prm.ty1  = @(prm, cst) derFunNested(prm, cst, 'prm_ty1');
derFun.prm.tz1  = @(prm, cst) derFunNested(prm, cst, 'prm_tz1');

derFun.prm.a112 = @(prm, cst) derFunNested(prm, cst, 'prm_a112');
derFun.prm.a122 = @(prm, cst) derFunNested(prm, cst, 'prm_a122');
derFun.prm.a132 = @(prm, cst) derFunNested(prm, cst, 'prm_a132');
derFun.prm.a212 = @(prm, cst) derFunNested(prm, cst, 'prm_a212');
derFun.prm.a222 = @(prm, cst) derFunNested(prm, cst, 'prm_a222');
derFun.prm.a232 = @(prm, cst) derFunNested(prm, cst, 'prm_a232');
derFun.prm.a312 = @(prm, cst) derFunNested(prm, cst, 'prm_a312');
derFun.prm.a322 = @(prm, cst) derFunNested(prm, cst, 'prm_a322');
derFun.prm.a332 = @(prm, cst) derFunNested(prm, cst, 'prm_a332');
derFun.prm.tx2  = @(prm, cst) derFunNested(prm, cst, 'prm_tx2');
derFun.prm.ty2  = @(prm, cst) derFunNested(prm, cst, 'prm_ty2');
derFun.prm.tz2  = @(prm, cst) derFunNested(prm, cst, 'prm_tz2');

    function derFunBlock = derFunNested(prm, cst, var)

        switch var

            % ------------------------------------------------------------------

            case 'prm_a111'

                derFunBlock =   cst.x1 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a121'

                derFunBlock =   cst.y1 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a131'

                derFunBlock =   cst.z1 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a211'

                derFunBlock =   cst.x1 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a221'

                derFunBlock =   cst.y1 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a231'

                derFunBlock =   cst.z1 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a311'

                derFunBlock =   cst.x1 .* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_a321'

                derFunBlock =   cst.y1 .* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_a331'

                derFunBlock =   cst.z1.* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_a112'

                derFunBlock = - cst.x2 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a122'

                derFunBlock = - cst.y2 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a132'

                derFunBlock = - cst.z2 .* cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_a212'

                derFunBlock = - cst.x2 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a222'

                derFunBlock = - cst.y2 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a232'

                derFunBlock = - cst.z2 .* cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_a312'

                derFunBlock = - cst.x2 .* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_a322'

                derFunBlock = - cst.y2 .* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_a332'

                derFunBlock = - cst.z2.* cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_tx1'

                derFunBlock =   cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_tx2'

                derFunBlock = - cst.nx1;

            % ------------------------------------------------------------------

            case 'prm_ty1'

                derFunBlock =   cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_ty2'

                derFunBlock = - cst.ny1;

            % ------------------------------------------------------------------

            case 'prm_tz1'

                derFunBlock =   cst.nz1;

            % ------------------------------------------------------------------

            case 'prm_tz2'

                derFunBlock = - cst.nz1;

        end

    end

end


function [Words,Simbols] = huffcodes2bin(huffcodes)
%HUFFCODES2BIN   Convert huffcodes to binary representation
%   [W,S] = HUFFCODES2BIN(HC) returns the Huffman representation returned
%      by the function NORM2HUFF to the corresponding binary string of '0' and '1'
%   W is a cell array of binary strings,
%   S is a double array containing the corresponding simbols
%
%   REMARK: the first bit of each binary strings B is B(end)
%
%   See also HUFF2NORM, NORM2HUFF

%   $Author: Giuseppe Ridino' $
%   $Revision: 1.0 $  $Date: 25-May-2004 14:26:00 $

% get number of simbols
Nsimbols = nnz(huffcodes);

% initialize output
Words = cell(Nsimbols,1);
% Simbols = cell(Nsimbols,1);

% gte code words
CodeWords = find(not(huffcodes==0));

% convert words
for index=1:Nsimbols,
	Words{index} = Double2BinStr(CodeWords(index));
end

% return simbols
Simbols = full(huffcodes(CodeWords));

% #############################################################
function BinString = Double2BinStr(Word)
BinString = dec2bin(Word);
if not(BinString(1)=='1'),
	error('wrong codeword');        % most significat bit must always be "1"
else,
	BinString = BinString(2:end);   % remove stop bit
end


%IMPEXP_TRIANGLE Import/export grid data in Triangle format.
%
%   [ DATA ] = IMPEXP_TRIANGLE( FILE_NAME, MODE, DATA, HMAX, HMAX_E, FID_LOG ) Import
%   or export of Triangle grid data in .node/.ele/.edge/.poly format. FILE_NAME is a string
%   specifying the (root) file name to process. MODE can either be a string indicating
%   import or export. For export, DATA can be either a whole fea struct or just the geom
%   struct which is used to compute the poly information used by Triangle. HMAX and HMAX_E
%   specifies grid sizes for subdomains and edges when exporting. A DATA grid struct is
%   output when importng. FID_LOG is an optional log file for message output.

% Copyright 2013-2022 Precise Simulation, Ltd.


function [verticesList, facesList] = getVerticesAndFacesInSphere(mesh_outer, iV, radius)

% find all the vertices included in a sphere
verticesList = [];

for vertex = 1:length(mesh_outer.vertices)
    if isVertexInRadius(mesh_outer.vertices(vertex,:), mesh_outer.vertices(iV,:), radius)
        verticesList = [verticesList,vertex];
    end
end

% find faces to which those vertices belong
facesList = [];
for vert = verticesList
    facesList = [facesList, mesh_outer.facesOfVertex(vert).faceList];
end

facesList = unique(facesList);


function T = spm_type(x, arg)
% Translate data type specifiers between SPM & MATLAB representations
% FORMAT T = spm_type(x, arg)
% x    - specifier
% T    - type
% arg  - optional string argument, can be:
%         - 'maxval'  - return maximum allowed value.
%         - 'minval'  - return minimum allowed value.
%         - 'nanrep'  - return 1 if there is a NaN representation.
%         - 'bits'    - return the number of bits per voxel.
%         - 'intt'    - return 1 if values rounded to nearest integer.
%__________________________________________________________________________
%
% Format specifiers are based on NIFTI-1.
% If the input is a number then the corresponding MATLAB string is
% returned by default.
% If the input is a string then the appropriate TYPE is returned.
% However, if the optional arg argument is supplied then other
% information will be returned instead.
%
% With no arguments, a list of data types is returned.
%__________________________________________________________________________
% Copyright (C) 1996-2014 Wellcome Trust Centre for Neuroimaging

% John Ashburner & Andrew Holmes
% $Id: spm_type.m 5925 2014-03-20 16:47:44Z guillaume $

prec   = {'uint8','int16','int32','float32','float64','int8','uint16','uint32'};
conv   = {@uint8,@int16,@int32,@single,@double,@int8,@uint16,@uint32};
types  = [    2      4      8   16   64   256    512    768];
maxval = [2^8-1 2^15-1 2^31-1  Inf  Inf 2^7-1 2^16-1 2^32-1];
minval = [    0  -2^15  -2^31 -Inf -Inf  -2^7      0      0];
nanrep = [    0      0      0    1    1     0      0      0];
bits   = [    8     16     32   32   64     8     16     32];
intt   = [    1      1      1    0    0     1      1      1];

if ~nargin
    T = types;
    return;
end

if ischar(x)
    sel = find(strcmpi(prec,deblank(x)));
else
    sel = find(ismember(types,x));
end
if nargin == 1
    if ischar(x)
        if isempty(sel), T = NaN;
        else T = types(sel); end
    else
        if isempty(sel), T = 'unknown';
        else T = char(prec(sel)); end
    end
elseif isempty(sel)
    T = NaN;
else
    switch lower(arg)
        case 'maxval', T = maxval(sel);
        case 'minval', T = minval(sel);
        case 'nanrep', T = nanrep(sel);
        case 'bits',   T = bits(sel);
        case 'intt',   T = intt(sel);
        case 'conv',   T = conv(sel);
        otherwise,     T = NaN;
    end
end


function str = cvx_replicate_structure( str, sz )
%CVX_REPLICATE_STRUCTURE Apply matrix structure to N-D arrays.

szs  = size( str );
nmat = prod( sz );
omat = ones( 1, nmat );
[ r, c, v ] = find( str );
nelm = length( r );
oelm = ones( 1, nelm );
r = r( : );
nvec = ( 0 : nmat - 1 ) * szs( 1 );
r = r( :, omat ) + nvec( oelm, : );
c = c( : );
c = c( :, omat );
nvec = ( 0 : nmat - 1 ) * szs( 2 );
c = c( :, omat ) + nvec( oelm, : );
v = v( : );
v = v( :, omat );
str = sparse( r, c, v, nmat * szs( 1 ), nmat * szs( 2 ) );

% Copyright 2005-2016 CVX Research, Inc.
% See the file LICENSE.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


function test_suite = test_cg_ls
    clear all;
    initTestSuite;
end

function test_1
    A = [
    1 -1
    1 1
    2 1
    ];

    B = [
    2
    4
    8
    ];
    solver = SPX_CGLeastSquare(A, B);
    x = solver.solve();
    %solver.printResults();
    verifyTrue(testCase, solver.hasConverged());
end

function test_2
    m = 10;
    n = 4;
    s = 100;
    %rng('default');
    A = randn(m, n);
    X = randn(n, s);
    B = A * X;
    solver = SPX_CGLeastSquare(A, B);
    XX = solver.solve();
    % solver.printResults();
    % disp(X);
    % disp(XX);
    verifyTrue(testCase, solver.hasConverged());
end

function test_3
    m = 1000;
    n = 40;
    s = 10;
    %rng('default');
    A = randn(m, n);
    X = randn(n, s);
    B = A * X;
    solver = SPX_CGLeastSquare(A, B);
    XX = solver.solve();
    % solver.printResults();
    % disp(X);
    % disp(XX);
    verifyTrue(testCase, solver.hasConverged());
end


classdef prtClassRvm < prtClass
    % prtClassRvm  Relevance vector machine classifier
    %
    %   CLASSIFIER = prtClassRvm returns a relevance vector machine classifier
    %
    %   CLASSIFIER = prtClassRvm(PROPERTY1, VALUE1, ...) constructs a
    %   prtClassRvm object CLASSIFIER with properties as specified by
    %   PROPERTY/VALUE pairs.
    %
    %   A prtClassRvm object inherits all properties from the abstract class
    %   prtClass. In addition is has the following properties:
    %
    %   kernels                - A cell array of prtKernel objects specifying
    %                            the kernels to use
    %   verbosePlot            - Flag indicating whether or not to plot during
    %                            training
    %   verboseText            - Flag indicating whether or not to output
    %                            verbose updates during training
    %   learningMaxIterations  - The maximum number of iterations
    %
    %   A prtClassRvm also has the following read-only properties:
    %
    %   learningConverged  - Flag indicating if the training converged
    %   beta               - The regression weights, estimated during training
    %   sparseBeta         - The sparse regression weights, estimated during
    %                        training
    %   sparseKernels      - The sparse regression kernels, estimated during
    %                        training
    %
    %   For information on relevance vector machines, please
    %   refer to the following URL:
    %
    %   http://en.wikipedia.org/wiki/Relevance_vector_machine
    %
    %   By default, prtClassRvm uses the Laplacian approximation as found
    %   in the paper:
    %
    %   Michael E. Tipping. 2001. Sparse bayesian learning and the
    %   relevance vector machine. J. Mach. Learn. Res. 1 (September 2001),
    %
    %   The code is based on the algorithm in:
    %
    %   Herbrich, Learning Kernel Classifiers, The MIT Press, 2002
    %   http://www.learning-kernel-classifiers.org/
    %
    %   A prtClassRvm object inherits the TRAIN, RUN, CROSSVALIDATE and
    %   KFOLDS methods from prtAction. It also inherits the PLOT method
    %   from prtClass.
    %
    %   Example:
    %
    %   TestDataSet = prtDataGenUnimodal;      % Create some test and
    %   TrainingDataSet = prtDataGenUnimodal;  % training data
    %   classifier = prtClassRvm;              % Create a classifier
    %   classifier = classifier.train(TrainingDataSet);    % Train
    %   classified = run(classifier, TestDataSet);         % Test
    %   % Plot the results
    %   subplot(2,1,1);
    %   classifier.plot;
    %   subplot(2,1,2);
    %   [pf,pd] = prtScoreRoc(classified,TestDataSet);
    %   h = plot(pf,pd,'linewidth',3);
    %   title('ROC'); xlabel('Pf'); ylabel('Pd');
    %
    %   % Example 2, using a different kernel
    %
    %   TestDataSet = prtDataGenUnimodal;      % Create some test and
    %   TrainingDataSet = prtDataGenUnimodal;  % training data
    %   classifier = prtClassRvm;              % Create a classifier
    %
    %   % Create a prtKernelSet object with a different pair of
    %   % prtKernels and assign them to the classifier
    %   kernSet = prtKernelDirect & prtKernelRbf;
    %   classifier.kernels = kernSet;
    %
    %   classifier = classifier.train(TrainingDataSet);    % Train
    %   classified = run(classifier, TestDataSet);         % Test
    %   % Plot
    %   subplot(2,1,1);
    %   classifier.plot;
    %   subplot(2,1,2);
    %   [pf,pd] = prtScoreRoc(classified,TestDataSet);
    %   h = plot(pf,pd,'linewidth',3);
    %   title('ROC'); xlabel('Pf'); ylabel('Pd');
    %
    %   See also prtClass, prtClassLogisticDiscriminant, prtClassBagging,
    %   prtClassMap, prtClassCap, prtClassBinaryToMaryOneVsAll, prtClassDlrt,
    %   prtClassPlsda, prtClassFld, prtClassRvmFigueiredo, prtClassRvmSequential, prtClassGlrt,  prtClass

    properties (SetAccess=private)
        name = 'Relevance Vector Machine'  % Relevance Vector Machine
        nameAbbreviation = 'RVM'           % RVM
        isNativeMary = false;  % False
    end

    properties
        kernels = prtKernelDc & prtKernelRbfNdimensionScale;  % The kernels to be used

        verboseText = false;  % Whether or not to display text during training
        verbosePlot = false;  % Whether or not to plot during training

        learningMaxIterations = 1000;       % The maximum number of iterations
        learningConvergedTolerance = 1e-5;  % Learning tolerance;
        % at iteration i, if ||if \theta_{i}-\theta_{i-1}|| / length(theta)
        % < learningConvergedTolerance, learning has converged

        learningRelevantTolerance = 1e-5;   %Tolerance below which a kernel is marked as irrelevant and removed
        % Tolerance on \theta; if \theta is < learningConvergedTolerance, the kernel is irrelevant and can be ignored
    end

    % Estimated Parameters
    properties (GetAccess = public, SetAccess = protected)
        beta = [];    % Regression weights
        sparseBeta = [];  % Sparse Beta
        sparseKernels = {};  % Sparse Kernel array
        learningConverged = false;   % Flag indicating whether or not training convereged
    end

    properties
    end

    methods

        function Obj = prtClassRvm(varargin)
            Obj = prtUtilAssignStringValuePairs(Obj,varargin{:});
        end

        function Obj = set.learningMaxIterations(Obj,val)
            if ~prtUtilIsPositiveInteger(val)
                error('prt:prtClassRvm:learningMaxIterations','learningMaxIterations must be a positive integer');
            end
            Obj.learningMaxIterations = val;
        end

        function Obj = set.learningConvergedTolerance(Obj,val)
            if ~prtUtilIsPositiveScalar(val)
                error('prt:prtClassRvm:learningConvergedTolerance','learningConvergedTolerance must be a positive scalar');
            end
            Obj.learningConvergedTolerance = val;
        end

        function Obj = set.learningRelevantTolerance(Obj,val)
            if ~prtUtilIsPositiveScalar(val)
                error('prt:prtClassRvm:learningRelevantTolerance','learningRelevantTolerance must be a positive scalar');
            end
            Obj.learningRelevantTolerance = val;
        end

        function Obj = set.kernels(Obj,val)
            assert(numel(val)==1 &&  isa(val,'prtKernel'),'prt:prtClassRvm:kernels','kernels must be a prtKernel');

            Obj.kernels = val;
        end

        function Obj = set.verbosePlot(Obj,val)
            assert(isscalar(val) && (islogical(val) || prtUtilIsPositiveInteger(val)),'prt:prtClassRvm:verbosePlot','verbosePlot must be a logical value or a positive integer');
            Obj.verbosePlot = val;
        end

        function Obj = set.verboseText(Obj,val)
            assert(isscalar(val) && islogical(val),'prt:prtClassRvm:verboseText','verboseText must be a logical value, but value provided is a %s',class(val));
            Obj.verboseText = val;
        end

        function varargout = plot(Obj)
            % plot - Plot output confidence of the prtClassRvm object
            %
            %   CLASS.plot plots the output confidence of the prtClassRvm
            %   object. The dimensionality of the dataset must be 3 or
            %   less, and verboseStorage must be true.

            HandleStructure = plot@prtClass(Obj);

            holdState = get(gca,'nextPlot');
            hold on;
            Obj.sparseKernels.plot;
            set(gca, 'nextPlot', holdState);

            varargout = {};
            if nargout > 0
                varargout = {HandleStructure};
            end
        end

    end

    methods (Access=protected, Hidden = true)

        function Obj = trainAction(Obj,DataSet)
            %Rvm = trainAction(Rvm,DataSet) (Private; see prtClass\train)
            %   Implements Jefferey's prior based training of a relevance
            %   vector machine.  The Rvm output from this function contains
            %   fields "sparseBeta" and "sparseKernels"
            %

            assert(DataSet.isBinary,'prtClassRvm Requires a binary dataset');

            warningState = warning;
            warning off MATLAB:nearlySingularMatrix

            %Note: do not assume that getTargets returns a double array or
            %values "0" and "1", instead use this:
            y = Obj.getMinusOneOneTargets(DataSet);
            y(y==-1) = 0;

            localKernels = Obj.kernels.train(DataSet);
            gram = localKernels.run_OutputDoubleArray(DataSet);

            theta = ones(size(gram,2),1);
            Obj.beta = zeros(size(theta));
            deltaThetaNorm = ones(Obj.learningMaxIterations,1)*nan;

            if Obj.verboseText
                fprintf('RVM training with %d possible vectors.\n', size(gram,2));
            end

            for iteration = 1:Obj.learningMaxIterations

                %%%%
                %%See: Herbrich: Learning Kernel Classifiers, Algorithm 7, Page 328
                %%%%

                %check tolerance for basis removal
                cRelevant = theta > Obj.learningRelevantTolerance;

                Obj.beta(~cRelevant) = 0;

                cGram = gram(:,cRelevant);
                cTheta = theta(cRelevant);
                cThetaInv = diag(1./cTheta);

                if isempty(cGram)
                    error('prt:prtClassRvm:noRelevantVectors','No relevant vectors were retained; this indicates a kernel that is scaled improperly with regards to the classification problem.  Please try choosing a different kernel or modifying the kernel parameters');
                end
                [newBeta, SigmaInvChol] = prtUtilPenalizedIrls(y,cGram,Obj.beta(cRelevant),cThetaInv);

                Obj.beta(cRelevant) = newBeta;

                SigmaChol = inv(SigmaInvChol);
                sigma = SigmaChol*SigmaChol'; %#ok<MINV>

                zeta = ones(size(diag(cThetaInv))) - (1./cTheta).*diag(sigma);

                previousTheta= theta;
                theta(cRelevant) = Obj.beta(cRelevant).^2./zeta;

                deltaThetaNorm(iteration) = norm(previousTheta-theta)./length(theta);

                if ~mod(iteration,Obj.verbosePlot)
                    if DataSet.nFeatures == 2
                        Obj.verboseIterationPlot(DataSet,cRelevant);
                    elseif iteration == 1
                        warning('prt:prtClassRvm','Learning iteration plot can only be produced for training Datasets with 2 features');
                    end
                end

                if deltaThetaNorm(iteration) < Obj.learningConvergedTolerance && iteration > 1
                    % Converged

                    Obj.learningConverged = true;

                    if Obj.verboseText
                        fprintf('Convergence reached. Exiting...\n\n');
                    end

                    break;
                end

                if Obj.verboseText
                    fprintf('\t Iteration %d: %d RV''s, Convergence tolerance: %g \n',iteration, sum(cRelevant), deltaThetaNorm(iteration));
                end

            end

            if Obj.verboseText && iteration == Obj.learningMaxIterations
                fprintf('Exiting...Convergence not reached before the maximum allowed iterations was reached.\n\n');
            end

            % Make sparse represenation
            Obj.sparseBeta = Obj.beta(cRelevant,1);
            Obj.sparseKernels = localKernels.retainKernelDimensions(cRelevant);

            % Very bad training
            if isempty(find(cRelevant,1));
                warning('prt:prtClassRvm:NoRelevantFeatures','No relevant features were found during training.');
            end

            % Reset warning
            warning(warningState);

        end

        function DataSet = runAction(Obj,DataSet)

            if isempty(Obj.sparseBeta)
                DataSet = DataSet.setObservations(nan(DataSet.nObservations,DataSet.nFeatures));
                return
            end

            n = DataSet.nObservations;

            largestMatrixSize = prtOptionsGet('prtOptionsComputation','largestMatrixSize');

            memChunkSize = max(floor(largestMatrixSize/length(Obj.sparseBeta)),1);

            OutputMat = zeros(n,1);
            for i = 1:memChunkSize:n
                cI = i:min(i+memChunkSize,n);
                cDataSet = prtDataSetClass(DataSet.X(cI,:));

                gram = Obj.sparseKernels.run(cDataSet);

                OutputMat(cI) = prtRvUtilNormCdf(gram.getObservations*Obj.sparseBeta);
            end

            DataSet.X = OutputMat;
        end
    end

    methods (Access=protected, Hidden = true)

        function y = getMinusOneOneTargets(Obj, DataSet) %#ok<MANU>
            yMat = double(DataSet.getTargetsAsBinaryMatrix());
            y = nan(size(yMat,1),1);
            y(yMat(:,1) == 1) = -1;
            y(yMat(:,2) == 1) = 1;
        end

        function G = regularizeGramInnerProduct(Obj, gram)
            nBasis = size(gram,2);

            sigmaSquared = 1e-6;

            %Check to make sure the problem is well-posed.  This can be fixed either
            %with changes to kernels, or by regularization
            G = gram'*gram;
            while rcond(G) < 1e-6
                if sigmaSquared == eps && Obj.verboseText
                    %warning('prt:prtClassRvm:illConditionedG','RVM initial G matrix ill-conditioned; regularizing diagonal of G to resolve; this can be modified by changing kernel parameters\n');
                    fprintf('\n\tRegularizing Gram matrix...\n');
                end
                G = (sigmaSquared*eye(nBasis) + gram'*gram);
                sigmaSquared = sigmaSquared*2;
            end

        end

        function verboseIterationPlot(Obj,DataSet,relevantIndices)
            DsSummary = DataSet.summarize;

            [linGrid, gridSize,xx,yy] = prtPlotUtilGenerateGrid(DsSummary.lowerBounds, DsSummary.upperBounds, Obj.plotOptions); %#ok<ASGLU>

            localKernels = Obj.kernels.train(DataSet);
            cKernels = localKernels.retainKernelDimensions(relevantIndices);
            cPhiDataSet = cKernels.run(prtDataSetClass([xx(:),yy(:)]));
            cPhi = cPhiDataSet.getObservations;

            confMap = reshape(prtRvUtilNormCdf(cPhi*Obj.beta(relevantIndices)),gridSize);
            imagesc(xx(1,:),yy(:,1),confMap,[0,1])
            colormap(Obj.plotOptions.twoClassColorMapFunction());
            axis xy
            hold on
            plot(DataSet);
            cKernels.plot();
            hold off;

            set(gcf,'color',[1 1 1]);
            drawnow;
        end
    end
end


function [calibration_matrix,corr_vector,fraction_vector,dates] = aps_spectrometer_PWV_meris_modis
% aps_spectrometer_PWV_meris_modis(batchfile)
% Scipt to load meris and modis PWV data and compare each SAR data for the
% cloud free pixels.
%
% Bekaert David -- University of Leeds
%
% modifications:
% DB    08/2014     Initial codings
% DB    03/2015     Add bootstrapping to get the uncertancy of the linear
%                   mapping between PWV of MERIS and MODIS.

save_dir= 'aps_m_M';
n_boot_runs = 1200;

curdir = pwd;

fontsize = 15;

% loading the date information
meris_datapath = getparm_aps('meris_datapath',1);
modis_datapath = getparm_aps('modis_datapath',1);
stamps_processed = getparm_aps('stamps_processed',1);
if strcmp(stamps_processed,'y')
   ps = load(getparm_aps('ll_matfile',1));
   dates = ps.day;
   fprintf('Stamps processed structure \n')
else
    ifgday_matfile = getparm_aps('ifgday_matfile',1);
    dates = load(ifgday_matfile);
    dates = dates.ifgday;
    dates = reshape(dates,[],1);
    dates = unique(dates);
end

%start loop here to calculate atmos correction for each date
ndates = length(dates);
fprintf('Starting the computation for each SAR date \n')
calibration_matrix = NaN([ndates 2]);            % this is the linear correction matrix [a b] between MERIS = a*MODIS+b (PWV);
corr_vector = NaN([ndates 1]);
fraction_vector =NaN([ndates 1]);
std_vector=NaN([ndates 2]);
calibration_matrix_boot = NaN([n_boot_runs 2 ndates]);

if exist([curdir filesep 'figures'],'dir')~=7
    mkdir([curdir filesep 'figures']);
end

for n = 1:ndates
    date_str = datestr(dates(n,1),'yyyymmdd');

    meris_filename_PWV_nointerp = [meris_datapath filesep date_str filesep date_str '_ZPWV_nointerp.xyz'];
    modis_filename_PWV_nointerp = [modis_datapath filesep date_str filesep date_str '_ZPWV_nointerp.xyz'];

    % checking if there is actual meris data for this date, if not just
    % leave NaN's in the matrix.
    if exist(meris_filename_PWV_nointerp,'file') ==2 && exist(modis_filename_PWV_nointerp,'file') ==2

        [xyz_meris_PWV,temp] = load_meris_SAR(meris_filename_PWV_nointerp);
        [xyz_modis_PWV,temp] = load_meris_SAR(modis_filename_PWV_nointerp);
        clear temp

        % keep only those points in common
        lonlat_modis = xyz_modis_PWV(:,1:2);
        lonlat_meris = xyz_meris_PWV(:,1:2);

        % number of original grid points, this also includes water!
        n_original = size(lonlat_meris,1);

        [lonlat, ix_modis, ix_meris ]= intersect(lonlat_modis,lonlat_meris,'rows','stable');

        PWV_modis=xyz_modis_PWV(ix_modis,3);
        PWV_meris=xyz_meris_PWV(ix_meris,3);

        ix_nan = sum(isnan([PWV_modis PWV_meris]),2)>0;
        lonlat(ix_nan,:)=[];
        PWV_modis(ix_nan,:)=[];
        PWV_meris(ix_nan,:)=[];

        if length(PWV_meris)>100
            % number of gridpoitns left
            n_used = size(lonlat,1);
            fraction_vector(n,1) = n_used./n_original;

            % getting the starts
            A = [PWV_modis  ones(size(PWV_meris)) ];
            calibration_matrix(n,:) = lscov(A,PWV_meris);
            corr_vector(n,1) = corr(PWV_modis,PWV_meris);

            % getting the uncertaincy through bootstrapping - Can just use
            % the powerlaw bootstrap function as its not technique dependent
            [std_vector(n,:),calibration_matrix_boot(:,:,n)]= aps_powerlaw_bootstrap(A,PWV_meris,n_boot_runs);

            limits = [floor(min([PWV_modis;PWV_meris]))   ceil(max([PWV_modis;PWV_meris]))];
            h1 = figure('name',['MODIS/MERIS PWV comparison ' date_str]);
            plot(limits, limits,'k--')
            hold on
            plot(limits,calibration_matrix(n,1).*limits+calibration_matrix(n,2),'r-','linewidth',2)
            hold on
            plot(PWV_modis,PWV_meris,'k.')
            xlim(limits)
            ylim(limits)
            hold on
            plot(limits,calibration_matrix(n,1).*limits+calibration_matrix(n,2),'r-','linewidth',2)
            hold on
            plot(limits, limits,'k--')
            xlabel('MODIS PWV','fontsize',fontsize)
            ylabel('MERIS PWV','fontsize',fontsize)
            title({[date_str ':'],['PWV_{MERIS} = ' num2str(round(calibration_matrix(n,1)*100)/100) ' [+-' num2str(round(std_vector(n,1)*100)/100)  '] *PWV_{MODIS} + ' num2str(round(calibration_matrix(n,2)*100)/100) ' [+-' num2str(round(std_vector(n,2)*100)/100) ']' ]},'fontsize',fontsize)
            legend('1-to-1 relation','Linear fit',0)
            set(gca,'fontsize',fontsize)
            print(h1,'-depsc',[curdir filesep 'figures' filesep 'PWV_MERIS_MODIS_' date_str '.eps'])
            print(h1,'-dpng',[curdir filesep 'figures' filesep 'PWV_MERIS_MODIS_' date_str '.png'])
            close(h1)
        end
        fprintf([num2str(n) ' completed out of ' num2str(ndates) '\n'])
    else
        fprintf([date_str ': no comparison\n'])
    end

end

save([modis_datapath filesep 'MODIS_calibration.mat'],'calibration_matrix','corr_vector','fraction_vector','dates','std_vector','calibration_matrix_boot');


function [emp_cov, dist] = empCov2(x_obs, y_obs, data_obs, n_classes)
% SYNTAX:
%   [emp_cov, dist] = empCov2(x_obs, y_obs, data_obs, n_classes)
%
% DESCRIPTION:
%   return the array of the empirical covariance funtion
%
% EXAMPLE:
%   [emp_cov, dist] = empCov2(e_obs(id_td), n_obs(id_td), td_obs);
%   figure; plot(dist, emp_cov, 'b'); hold on;

%--- * --. --- --. .--. ... * ---------------------------------------------
%               ___ ___ ___
%     __ _ ___ / __| _ | __|
%    / _` / _ \ (_ |  _|__ \
%    \__, \___/\___|_| |___/
%    |___/                    v 1.0RC1
%
%--------------------------------------------------------------------------
%  Copyright (C) 2021 Geomatics Research & Development srl (GReD)
%  Written by:       Andrea Gatti
%  Contributors:     Andrea Gatti ...
%  A list of all the historical goGPS contributors is in CREDITS.nfo
%--------------------------------------------------------------------------
%
%   This program is free software: you can redistribute it and/or modify
%   it under the terms of the GNU General Public License as published by
%   the Free Software Foundation, either version 3 of the License, or
%   (at your option) any later version.
%
%   This program is distributed in the hope that it will be useful,
%   but WITHOUT ANY WARRANTY; without even the implied warranty of
%   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%   GNU General Public License for more details.
%
%   You should have received a copy of the GNU General Public License
%   along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
%--------------------------------------------------------------------------
% 01100111 01101111 01000111 01010000 01010011
%--------------------------------------------------------------------------

    if nargin < 4
        n_classes = 50;
    end

    [x_mesh, y_mesh] = meshgrid(x_obs, y_obs);
    d_obs = sqrt(abs(x_mesh - x_mesh').^2 + abs(y_mesh - y_mesh').^2);
    classes = ceil(d_obs / max(d_obs(:)) * (n_classes-1))+1;

    emp_cov = zeros(max(classes(:)), size(data_obs,2));
    for i = 1 : size(data_obs,2)
        corr = (data_obs(:,i)-mean(data_obs(:,i))) * (data_obs(:,i)-mean(data_obs(:,i)))';
        for c = 1 : max(classes)
            emp_cov(c, i) = mean(corr(serialize(triu(classes)==c)));
        end
    end
    emp_cov = mean(emp_cov, 2);
    dist = (0 : (n_classes - 1))' * max(d_obs(:))/n_classes;
end


function [ a, l, r ] = r8row_part_quick_a ( m, n, a )

%*****************************************************************************80
%
%% R8ROW_PART_QUICK_A reorders the columns of an R8ROW.
%
%  Discussion:
%
%    An R8ROW is an M by N array of R8's, regarded as an array of M rows,
%    each of length N.
%
%    The routine reorders the rows of A.  Using A(1,1:N) as a
%    key, all entries of A that are less than or equal to the key will
%    precede the key, which precedes all entries that are greater than the key.
%
%  Example:
%
%    Input:
%
%      M = 8, N = 2
%      A = ( 2 4
%            8 8
%            6 2
%            0 2
%           10 6
%           10 0
%            0 6
%            5 8 )
%
%    Output:
%
%      L = 2, R = 4
%
%      A = ( 0 2    LEFT
%            0 6
%            ----
%            2 4    KEY
%            ----
%            8 8    RIGHT
%            6 2
%           10 6
%           10 0
%            5 8 )
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    28 May 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer M, the row dimension of A.
%
%    Input, integer N, the column dimension of A.
%
%    Input, real A(M,N).  On input, the array to be checked.
%
%    Output, real A(M,N), the reordered array.
%
%    Output, integer L, R, the indices of A that define the three
%    segments.  Let KEY = the input value of A(1:M,1).  Then
%    I <= L                 A(I,1:N) < KEY;
%         L < I < R         A(I,1:N) = KEY;
%                 R <= I    KEY < A(I,1:N).
%
  if ( m < 1 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'R8ROWL_PART_QUICK_A - Fatal error!\n' );
    fprintf ( 1, '  M < 1.\n' );
    return
  end

  if ( m == 1 )
    l = 0;
    r = 2;
    return
  end

  key(1:n) = a(1,1:n);
  k = 1;
%
%  The elements of unknown size have indices between L+1 and R-1.
%
  l = 1;
  r = m + 1;

  for i = 2 : m

    if ( r8vec_gt ( n, a(l+1,1:n), key(1:n) ) )
      r = r - 1;
      vec = a(r,1:n);
      a(r,1:n) = a(l+1,1:n);
      a(l+1,1:n) = vec;
    elseif ( r8vec_eq ( n, a(l+1,1:n), key(1:n) ) )
      k = k + 1;
      vec = a(k,1:n);
      a(k,1:n) = a(l+1,1:n);
      a(l+1,1:n) = vec;
      l = l + 1;
    elseif ( r8vec_lt ( n, a(l+1,1:n), key(1:n) ) )
      l = l + 1;
    end

  end
%
%  Shift small elements to the left.
%
  for j = 1 : l - k
    a(j,1:n) = a(j+k,1:n);
  end
%
%  Shift KEY elements to center.
%
  for j = l - k + 1 : l
    a(j,1:n) = key(1:n);
  end
%
%  Update L.
%
  l = l - k;

  return
end


function [c, ceq] = lotkaConstraintFCN_models(u,x,N,p,select_model)
%% Constraint function of nonlinear MPC for Lotka-Volterra system
%
% Inputs:
%   u:      optimization variable, from time k to time k+N-1
%   x:      current state at time k
%   Ts:     controller sample time
%   N:      prediction horizon
%
% Output:
%   c:      inequality constraints applied across prediction horizon
%   ceq:    equality constraints (empty)
%

%% Nonlinear MPC design parameters
% Predator population size always positive: >0, min pop size
zMin = [10];

%% Integrate system
if strcmp(select_model,'DelayDMDc')
     [xk,~] = lsim(p.sys,[p.udelay(1:N);u'],[0:N-1].*p.dt,[p.xdelay(:,1); x]-[p.xmean; p.xmean]);
    xk = xk(:,3:4);
    xk = xk + repmat(p.xmean',[N 1]); xk = xk';
elseif strcmp(select_model,'DMDc')
    [xk,~] = lsim(p.sys,[u',0],[0:N].*p.dt,x-p.xmean);
    xk = xk(2:end,:) + repmat(p.xmean',[N 1]); xk = xk';
elseif strcmp(select_model,'SINDYc')
    Ns = size(x,1);
    xk = zeros(Ns,N+1); xk(:,1) = x;
    for ct=1:N
        % Obtain plant state at next prediction step.
        xk(:,ct+1) = rk4u(@sparseGalerkinControl_Discrete,xk(:,ct),u(ct),p.dt,1,[],p);
    end
    xk = xk(:,2:N+1);
elseif strcmp(select_model,'NARX')
    Hu = [u',0];
    Hx = zeros(2,length(Hu)); Hx(:,1) = x;
    [Us,Ui,Si] = preparets(p.net,con2seq(Hu),{},con2seq(Hx));
    xk = p.net(Us,Ui,Si);
    xk = cell2mat(xk);
end

%% Inequality constraints calculation
c = zeros(N,1);
% Apply N population size constraints across prediction horizon, from time
% k+1 to k+N
uk = u(1);
for ct=1:N
    % -z + zMin < 0 % lower bound
    c(ct) = -xk(2,ct)+zMin; %c(2*ct-1)
    % z - zMax < 0 % upper bound
    %c(2*ct) = xk1(1)-zMax;
    % update  input for next step
    if ct<N
        uk = u(ct+1);
    end
end
%% No equality constraints
ceq = [];


function model = weakTrain(X, Y, opts)
% weak random learner
% can currently train:
% 1. decision stump: look along random dimension of data, choose threshold
% that maximizes information gain in class labels
% 2. 2D linear decision learner: same as decision stump but in 2D. I know,
% in general this could all be folded into single linear stump, but I make
% distinction for historical, cultural, and efficiency reasons.
% 3. Conic section learner: second order learning in 2D. i.e. x*y is a
% feature in addition to x, y and offset (as in 2.)
% 4. Distance learner. Picks a data point in train set and a threshold. The
% label is computed based on distance to the data point

classifierID= 1; % by default use decision stumps only
numSplits= 30;
classifierCommitFirst= true;

if nargin < 3, opts = struct; end
if isfield(opts, 'classifierID'), classifierID = opts.classifierID; end
if isfield(opts, 'numSplits'), numSplits = opts.numSplits; end
if isfield(opts, 'classifierCommitFirst'), classifierCommitFirst = opts.classifierCommitFirst; end

if classifierCommitFirst
    % commit to a weak learner first, then optimize its parameters only. In
    % this variation, different weak learners don't compete for a node.
    if length(classifierID)>1
        classifierID= classifierID(randi(length(classifierID)));
    end
end

u= unique(Y);
[N, D]= size(X);

if N == 0
    % edge case. No data reached this leaf. Don't do anything...
    model.classifierID= 0;
    return;
end

bestgain= -100;
model = struct;
% Go over all applicable classifiers and generate candidate weak models
for classf = classifierID

    modelCandidate= struct;
    maxgain= -1;

    if classf == 1
        % Decision stump

        % proceed to pick optimal splitting value t, based on Information Gain
        for q= 1:numSplits

            if mod(q-1,5)==0
                r= randi(D);
                col= X(:, r);
                tmin= min(col);
                tmax= max(col);
            end

            t= rand(1)*(tmax-tmin)+tmin;
            dec = col < t;
            Igain = evalDecision(Y, dec, u);

            if Igain>maxgain
                maxgain = Igain;
                modelCandidate.r= r;
                modelCandidate.t= t;
            end
        end

    elseif classf == 2
        % Linear classifier using 2 dimensions

        % Repeat some number of times:
        % pick two dimensions, pick 3 random parameters, and see what happens
        for q= 1:numSplits

            r1= randi(D);
            r2= randi(D);
            w= randn(3, 1);

            dec = [X(:, [r1 r2]), ones(N, 1)]*w < 0;
            Igain = evalDecision(Y, dec, u);

            if Igain>maxgain
                maxgain = Igain;
                modelCandidate.r1= r1;
                modelCandidate.r2= r2;
                modelCandidate.w= w;
            end
        end

    elseif classf == 3
        % Conic section weak learner in 2D (not too good presently, what is the
        % best way to randomly suggest good parameters?

        % Pick random parameters and see what happens
        for q= 1:numSplits

            if mod(q-1,5)==0
                r1= randi(D);
                r2= randi(D);
                w= randn(6, 1);
                phi= [X(:, r1).*X(:, r2), X(:,r1).^2, X(:,r2).^2, X(:, r1), X(:, r2), ones(N, 1)];
                mv= phi*w;
            end

            t1= randn(1);
            t2= randn(1);
            if rand(1)<0.5, t1=-inf; end
            dec= mv<t2 & mv>t1;
            Igain = evalDecision(Y, dec, u);

            if Igain>maxgain
                maxgain = Igain;
                modelCandidate.r1= r1;
                modelCandidate.r2= r2;
                modelCandidate.w= w;
                modelCandidate.t1= t1;
                modelCandidate.t2= t2;
            end
        end

    elseif classf==4
        % RBF weak learner: Picks an example and bases decision on distance
        % threshold

        % Pick random parameters and see what happens
        for q= 1:numSplits

            % this is expensive, lets only recompute every once in a while...
            if mod(q-1,5)==0
                x= X(randi(size(X, 1)), :);
                dsts= pdist2(X, x);
                maxdsts= max(dsts);
                mindsts= min(dsts);
            end

            t= rand(1)*(maxdsts - mindsts)+ mindsts;
            dec= dsts < t;
            Igain = evalDecision(Y, dec, u);

            if Igain>maxgain
                maxgain = Igain;
                modelCandidate.x= x;
                modelCandidate.t= t;
            end
        end

    else
        fprintf('Error in weak train! Classifier with ID = %d does not exist.\n', classf);
    end

    % see if this particular classifier has the best information gain so
    % far, and if so, save it as the best choice for this node
    if maxgain >= bestgain
        bestgain = maxgain;
        model= modelCandidate;
        model.classifierID= classf;
    end

end

end

function Igain= evalDecision(Y, dec, u)
% gives Information Gain provided a boolean decision array for what goes
% left or right. u is unique vector of class labels at this node

    YL= Y(dec);
    YR= Y(~dec);
    H= classEntropy(Y, u);
    HL= classEntropy(YL, u);
    HR= classEntropy(YR, u);
    Igain= H - length(YL)/length(Y)*HL - length(YR)/length(Y)*HR;

end

% Helper function for class entropy used with Decision Stump
function H= classEntropy(y, u)

    cdist= histc(y, u) + 1;
    cdist= cdist/sum(cdist);
    cdist= cdist .* log(cdist);
    H= -sum(cdist);

end