function [grid D] = mvn_som_skl(cm, n, global_training, local_training)
%MVN_SOM_SKL  Computes a NxN SOM using the MVN models and the
%   Symmetric Kullback-Leibler divergence. 
%
%The algorithm was published in 2010:
%   ``Islands of Gaussians: The Self Organizing Map and Gaussian Music Similarity Features'', D. Schnitzer, A. Flexer, G. Widmer and M. Gasser, Proceedings of the 11th International Society for Music Information Retrieval Conference, 2010.
%
%   [grid D] = MVN_SOM_SKL(models, n) Select the models to compute
%   the NxN SOM.
%
%   [grid D] = MVN_SOM_SKL(models, n, global_training, local_training)
%   Select the models to compute the NxN SOM; global/local_training sets the global/local training iterations of the SOM

%   MVN is (c) 2010-2011, Dominik Schnitzer, <dominik.schnitzer@ofai.at>
%   <http://www.ofai.at/~dominik.schnitzer/mvn>
%
%   This file is part of the MVN Octave/Matlab Toolbox
%
%   MVN 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.
%
%   MVN 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 MVN.  If not, see <http://www.gnu.org/licenses/>.

    cmsel = 1:length(cm);

    % initialization
    grid = [];
    for i = 0:n-1
        for j = 0:n-1
            grid(i*n + (j+1)).x = i;
            grid(i*n + (j+1)).y = j;
            grid(i*n + (j+1)).n = [];
        end
    end

    cmlen = length(cmsel);
    
    mlen = n*n;
    
    % RANDOM INIT
    mperm = randperm(cmlen);
    m = cm(mperm(1:mlen));
    
    if (nargin <= 2)
        global_training = 100;
        local_training = 200;
    end

    for learniter = 1:global_training+local_training

        if (learniter < global_training)

            % select appropriate alpha
            alpha = 0.9 * (1 - learniter/global_training);

            % relatively large radius, decay to 1 during global learning
            sigma = 1 + 2/3*n*(1 - learniter/global_training);
            fprintf(1, 'global training; som iteration=%d, alpha=%f, sigma=%f\n', learniter,...
                alpha, sigma);
        else
            local_learniter = learniter - global_training;

            % attain alpha values in the range of 0.06 - 0.01
            alpha = 0.01 + 0.05*(1 - local_learniter/local_training);

            % only modify neighbours
            sigma = 1.5;
            fprintf(1, 'local training; som iteration=%d, alpha=%f, sigma=%f\n',...
                local_learniter, alpha, sigma);
        end

        % select a random input
        cm_now = randi(cmlen);

        % compute the winner unit (c)
        dc = realmax();
        c = 0;
        for i = 1:mlen
            d = mvn_div_skl(cm(cmsel(cm_now)), m(i));
            if (d < dc)
                c = i;
                dc = d;
            end
        end

        for i = 1:mlen

            e2dist = (grid(i).x - grid(c).x)^2 + (grid(i).y - grid(c).y)^2;
            nkernel = alpha * exp(-e2dist/(2*sigma*sigma));
            

            try
                r = bregman_combine_r(m(i), cm(cmsel(cm_now)), nkernel);
                l = bregman_combine_l(m(i), cm(cmsel(cm_now)), nkernel);
                
                lrm = [mvn_new(l.cov, l.m), mvn_new(r.cov, r.m)];
                sym = mvn_bregmancentroid_skl(lrm);
                m(i).cov = sym.cov;
                m(i).icov = sym.icov;
                m(i).m = sym.m;

            catch ex
                fprintf(1, 'Skip this, this is dangerous (mainloop, i=%d)\n', i);
                fprintf(1, 'Exception: %s\n', ex.message);
                break;
            end
        end

    end

    % build the final grid
    D = zeros(cmlen, mlen);
    for i = 1:cmlen
        fprintf(1, 'assignment iteration = %d\n', i);

        dnn = realmax();
        nn = 0;
        for j = 1:mlen
            D(i, j) = mvn_div_skl(cm(cmsel(i)), m(j));
            if (D(i, j) < dnn)
                dnn = D(i, j);
                nn = j;
            end
        end
        grid(nn).n = [grid(nn).n cmsel(i)];
    end
end


function r = bregman_combine_r(x, y, ytheta)
% compute right centroid mixture

    r.m = (1-ytheta) * (x.icov * x.m) + (ytheta)*(y.icov * y.m);
    r.cov = (1-ytheta) * (0.5 * x.icov) + (ytheta)*(0.5 * y.icov);
    r.icov = 2 * r.cov;
    r.cov = inv(r.icov);
    r.m = r.cov * r.m;
end

function l = bregman_combine_l(x, y, ytheta)
% compute left centroid mixture

    l.m = (1-ytheta)*(x.m) + (ytheta)*(y.m);
    l.cov = (1-ytheta)* (-(x.cov + x.m*x.m')) +...
       (ytheta)*(-(y.cov + y.m*y.m'));
    l.cov = -(l.cov + l.m*l.m');
    l.icov = inv(l.cov);
end
