test sample unique

function test_suite = test_sample_unique
    % tests for cosmo_sample_unique
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions = localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_sample_unique_basics
    aet = @(varargin)assertExceptionThrown(@() ...
                                           cosmo_sample_unique(varargin{:}), '');

    for k = from_range_randomly(10, 5)
        for n = from_range_randomly(10, 5)
            for count = [0, from_range_randomly(100, 10)]
                if count == 0
                    args = {k, n};
                    ncol = 1;
                else
                    args = {k, n, count};
                    ncol = count;
                end

                if k > n
                    % n too large; should throw error
                    aet(args{:});
                    continue
                else
                    i = cosmo_sample_unique(args{:});
                    assertEqual(sort(i, 1), i);

                    % check size
                    assert(isequal(size(i), [k, ncol]));

                    % check contents
                    i_vec = i(:);
                    assert(all(i_vec >= 1 | i_vec <= n | round(i_vec) == i_vec));

                    % each element must be select about equally often,
                    % and each column must have unique elements
                    i_s = sort(i, 1);

                    % each element unique
                    assert(all(all(diff(i_s, 1, 1) > 0)));

                    % check counts
                    counts = zeros(n, 1);
                    for col = 1:ncol
                        counts(i(:, col)) = counts(i(:, col)) + 1;
                    end

                    % counts differ at most by one
                    assert(min(counts) + 1 >= max(counts));
                end
            end
        end
    end

function test_sample_unique_full_randperm()
    k = 2 + from_range_randomly(10);
    n = k;
    c = 5 + from_range_randomly(10);

    i = cosmo_sample_unique(k, n, c);
    assertEqual(sort(i, 1), repmat((1:k)', 1, c));

function test_sample_unique_difference_sequences()
    k = 2 + from_range_randomly(10);
    n = k + from_range_randomly(10);
    c = 5 + from_range_randomly(10);

    seed = 1 + from_range_randomly(1e5);
    last_args = {{'seed', seed}, {'seed', []'}, {}};
    for k = 1:numel(last_args)
        all_same = true;

        last_arg = last_args{k};
        use_seed = numel(last_arg) == 2 && ~isempty(last_arg{2});

        for tries = 1:5
            i = cosmo_sample_unique(k, n, c, last_arg{:});
            if tries == 1
                i_first = i;
            elseif ~isequal(i_first, i)
                all_same = false;
                break
            end
        end

        if xor(all_same, use_seed)
            assertFalse(true, 'randomness mismatch with respect to seed');
        end
    end

function test_sample_unique_exceptions
    aet = @(varargin)assertExceptionThrown(@() ...
                                           cosmo_sample_unique(varargin{:}), '');

    bad_args = {-1, 1.5, [1 1], struct(), false};
    for k = 1:numel(bad_args)
        bad_arg = bad_args{k};

        for j = 1:3
            all_args = {1, 1, 1};
            all_args{j} = bad_arg;
            aet(all_args{:});
        end
    end

function vs = from_range_randomly(n, count)
    if nargin <= 2
        count = 1;
    end

    assert(n >= count);
    vals = 1:n;

    rp = randperm(n);
    vs = vals(rp(1:count));