cosmo oddeven partitioner

function partitions = cosmo_oddeven_partitioner(ds, type)
% generates an odd-even partition scheme
%
% partitions=cosmo_oddeven_partitioner(chunks,[type])
%
% Input
%    ds              dataset struct with field .ds.chunks.
%    type            One of:
%                    - 'full': two partitions are returned, training on odd
%                       unique chunks and testing on even unique chunks,
%                       and vice versa (default). A typical use case is
%                       classification analysis (using
%                       cosmo_crossvalidation_measure)
%                    - 'half': a single partition is returned, training on
%                       odd unique chunks and testing on even unique chunks
%                       only. A typical use case is split-half
%                       correlation analysis (using
%                       cosmo_correlation_measure) because correlations are
%                       symmetric (i.e. corr(a,b)==corr(b,a) for column
%                       vectors a and b)
%
% Output:
%    partitions      A struct with fields .train_indices and .test_indices.
%                    Each of these is an Nx1 cell (N=1 when type='half',
%                    N=2 when type='full'.).
%                    .train_indices{k} and .test_indices{k} contain the
%                    sample indices for the sets of unique chunks
%                    alternatingly
%
% Example:
%     ds=struct();
%     ds.samples=NaN(8,99); % will be ignored by this function
%     ds.sa.targets=[8 9 8 9 8 9 8 9]';
%     ds.sa.chunks=[1 1 2 2 6 7 7 6]';
%     p=cosmo_oddeven_partitioner(ds);
%     % note that chunks=6 ends up in the odd chunks and chunks=7 in the
%     % even chunks, as 6 [7] is the third [fourth] unique value of chunks.
%     cosmo_disp(p);
%     %|| .train_indices
%     %||   { [ 1    [ 3
%     %||       2      4
%     %||       5      6
%     %||       8 ]    7 ] }
%     %|| .test_indices
%     %||   { [ 3    [ 1
%     %||       4      2
%     %||       6      5
%     %||       7 ]    8 ] }
%     %||
%     %
%     % only half-partition (for correlation-based analysis)
%     p=cosmo_oddeven_partitioner(ds,'half');
%     cosmo_disp(p);
%     %|| .train_indices
%     %||   { [ 1
%     %||       2
%     %||       5
%     %||       8 ] }
%     %|| .test_indices
%     %||   { [ 3
%     %||       4
%     %||       6
%     %||       7 ] }
%
% Notes:
%   - typically, the 'half' option can be used with
%     cosmo_correlation_measure, because correlations are symmetric; the
%     'full' option can be used with cosmo_crossvalidation_measure.
%   - this function returns partitions based on the sorted unique values
%     in chunks, not the chunk values themselves. For example, if the
%     sorted unique values of .sa.chunks are [2,4,5,8], then the values
%     2 and 5 are at the 'odd' position (1 and 3), and 4 and 8 are at the
%     'even' position.
%
% See also: cosmo_nchoosek_partitioner
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    chunks=get_chunks(ds);

    if nargin<2 || isempty(type)
        type='full';
    end

    switch type
        case 'full'
            do_half_partition=false;
        case 'half'
            do_half_partition=true;
        otherwise
            error('illegal type: must be ''full'' or ''half''');
    end


    indices=cosmo_index_unique(chunks);
    nparts=numel(indices);
    if nparts<2
        error('Need >=2 chunks, found %d', numel(indices));
    end

    % there are two partitions, unless do_half_partition in which case
    % there is one

    if do_half_partition
        npartitions=1;
    else
        npartitions=2;
    end

    % allocate space for output
    train_indices=cell(1,npartitions);
    test_indices=cell(1,npartitions);

    % Make partitions using even and odd chunks

    % find the indices of even and odd chunks
    odd_indices=cat(1,indices{1:2:end});
    even_indices=cat(1,indices{2:2:end});

    % set the train and test indices
    train_indices{1}=odd_indices;
    test_indices{1}=even_indices;

    if npartitions==2
        train_indices{2}=even_indices;
        test_indices{2}=odd_indices;
    end


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

    % final check to make sure all is kosher (including balanced-ness)
    cosmo_check_partitions(partitions,ds);

function ds=get_chunks(ds)
    if isnumeric(ds) && isvector(ds)
        % direct numeric
        return
    elseif isstruct(ds)
        if cosmo_isfield(ds,'sa.chunks')
            ds=ds.sa.chunks;
            return
        end
    end

    error(['illegal input: expected dataset struct with field '...
                '.sa.chunks, or numeric vector']);