cosmo squareform

function s=cosmo_squareform(x, varargin)
% converts pair-wise distances between matrix and vector form
%
% s=cosmo_squareform(x[, direction])
%
% Inputs:
%    x           One of:
%                - NxN distance matrix; x must be symmetric and have zeros
%                  on the diagonal
%                - 1xM distance vector
%    direction   Optional. If provided it must be 'tovector' (if x is a
%                matrix) or 'tomatrix' (if x is a vector). If not provided
%                it is set to 'tovector' if x is a matrix and to 'tomatrix'
%                if x is a vector
%
% Returns:
%    s           One of:
%                - NxN distance matrix, if direction=='tomatrix'
%                - 1xM distance vector, if direction=='tovector'
%                it must hold that N*(N-1)/2=M
%
% Notes:
%  - this function provides the same functionality as the built-in function
%    ''squareform'' in the matlab stats toolbox.
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    check_input(x);

    direction=get_direction(x,varargin{:});

    switch direction
        case 'tomatrix'
            s=to_matrix(x);

        case 'tovector'
            s=to_vector(x);

        otherwise
            error(['illegal direction argument, must be one of ', ...
                        '''tomatrix'',''tovector''']);
    end


function check_input(x)
    if numel(size(x))~=2
        error('first input must be matrix or vector');
    end

    if ~(islogical(x) || isnumeric(x))
        error(['Unsupported data type ''%s''; only numeric '...
                'and logical arrays are supported']', class(x));
    end


function s=to_vector(x)

    [n_rows,n_columns]=size(x);
    if n_rows~=n_columns
        error('direction ''to_vector'' requires a square matrix as input');
    end

    dg=diag(x);
    if any(dg)
        error('square matrix must be all zero on diagonal');
    end

    if ~cosmo_isequaln(x,x')
        error('square matrix must be symmetric');
    end

    msk=bsxfun(@gt,(1:n_rows)',1:n_rows);

    s=x(msk);
    s=s(:)';


function s=to_matrix(x)
    if isempty(x)
        s=[];
        return;
    end
    if ~isvector(x)
        error('direction ''to_matrix'' requires a vector as input');
    end
    n=numel(x);

    % side*(side+1)/2=n, solve for side>0
    side=(1+sqrt(1+8*n))/2;
    if ~isequal(side, round(side))
        error(['size %d of input vector is not correct for '...
                'the number of elements below diagonal of a '...
                'square matrix'], n);
    end
    msk=bsxfun(@gt,(1:side)',1:side);

    if islogical(x)
        s=false(side);
        s(msk)=x;
        s=s|s';
    elseif isnumeric(x)
        s=zeros(side);
        s(msk)=x;
        s=s+s';
    end

function direction=get_direction(x,varargin)
    if numel(varargin)<1
        sz=size(x);
        if sz(1)==sz(2) && sz(1)~=1
            direction='tovector';
        else
            direction='tomatrix';
        end
    elseif ischar(varargin{1})
        direction=varargin{1};
    else
        error('second argument must be a string');
    end