function varargout = process_test_parametric2( varargin )
% PROCESS_TEST_PARAMETRIC2: Parametric two-sample tests (independent).
%
% USAGE:  OutputFiles = process_test_parametric2('Run', sProcess, sInput)
%                   p = process_test_parametric2('ComputePvalues', t, df, TestType='t', TestTail='two')

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, Dimitrios Pantazis, 2008-2019

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Parametric test: Independent';
    sProcess.Category    = 'Stat2';
    sProcess.SubGroup    = 'Test';
    sProcess.Index       = 101;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Statistics';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'data',  'results',  'timefreq',  'matrix'};
    sProcess.OutputTypes = {'pdata', 'presults', 'ptimefreq', 'pmatrix'};
    sProcess.nInputs     = 2;
    sProcess.nMinFiles   = 2;

    % === GENERIC EXTRACT OPTIONS
    % Label
    sProcess.options.extract_title.Comment    = 'Select data to test:';
    sProcess.options.extract_title.Type       = 'label';
    % Options
    sProcess = process_extract_values('DefineExtractOptions', sProcess);
    % DISABLE ABSOLUTE VALUE
    sProcess.options.isabs.Value = 0;
    sProcess.options.isnorm.Value = 0;
    sProcess.options.isabs.Hidden = 1;
    sProcess.options.isnorm.Hidden = 1;

    % === EXCLUDE ZERO VALUES
    sProcess.options.iszerobad.Comment    = 'Exclude the zero values from the computation';
    sProcess.options.iszerobad.Type       = 'checkbox';
    sProcess.options.iszerobad.Value      = 1;
    sProcess.options.iszerobad.InputTypes = {'timefreq', 'matrix'};
    % === OUTPUT COMMENT
    sProcess.options.Comment.Comment = 'Comment (empty=default): ';
    sProcess.options.Comment.Type    = 'text';
    sProcess.options.Comment.Value   = '';

    % === TEST: title
    sProcess.options.test_title.Comment    = '
Test statistic:';
    sProcess.options.test_title.Type       = 'label';
    % === TEST: type
    sProcess.options.test_type.Comment = {['Student''s t-test   (equal variance)        A,B~N(m,v)
' ...
                                           't = (mean(A)-mean(B)) / (Sx * sqrt(1/nA + 1/nB))
' ...
                                           'Sx = sqrt(((nA-1)*var(A) + (nB-1)*var(B)) / (nA+nB-2))
' ...
                                           'df = nA + nB - 2'], ...
                                          ['Student''s t-test   (unequal variance)        A,B~N(m,v)
', ...
                                           't = (mean(A)-mean(B)) / sqrt(var(A)/nA + var(B)/nB)
' ...
                                           'df=(vA/nA+vB/nB)2 / ((vA/nA)2/(nA-1)+(vB/nB)2/(nB-1))'], ...
                                          ['Power F-test        A~N(0,vA), B~N(0,vB)
', ...
                                           'F = (sum(A^2)/nA) / (sum(B^2)/nB)      ~F(nA,nB)'], ...
                                          ['Power F-test (unconstrained sources)
', ...
                                           'F = (sum(Ax2+Ay2+Az2)/nA) / (sum(Bx2+By2+Bz2)/nB)
', ...
                                           'Ax,Ay,Az~N(0,vA), Bx,By,Bz~N(0,vB), F~F(3*nA,3*nB)']; ...
                                          'ttest_equal', 'ttest_unequal', 'power', 'power_unconstr'};
    sProcess.options.test_type.Type    = 'radio_label';
    sProcess.options.test_type.Value   = 'ttest_equal';
    % === TAIL FOR THE TEST STATISTIC
    sProcess.options.tail.Comment  = {'One-tailed (-)', 'Two-tailed', 'One-tailed (+)', ''; ...
                                      'one-', 'two', 'one+', ''};
    sProcess.options.tail.Type     = 'radio_linelabel';
    sProcess.options.tail.Value    = 'two';
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    % === DATA SELECTION COMMENT ===
    strData = process_extract_time('GetTimeString', sProcess);
    if isfield(sProcess.options, 'freqrange') && isfield(sProcess.options.freqrange, 'Value') && iscell(sProcess.options.freqrange.Value) && (length(sProcess.options.freqrange.Value) == 3) && (length(sProcess.options.freqrange.Value{1}) == 2)
        FreqRange = sProcess.options.freqrange.Value{1};
        if (FreqRange(1) == FreqRange(2))
            strData = [strData, ' ' num2str(FreqRange(1)) 'Hz'];
        else
            strData = [strData, ' ' num2str(FreqRange(1)) '-' num2str(FreqRange(2)) 'Hz'];
        end
    end
    if isfield(sProcess.options, 'sensortypes') && isfield(sProcess.options.sensortypes, 'Value') && ~isempty(sProcess.options.sensortypes.Value)
        strData = [strData, ' ', sProcess.options.sensortypes.Value];
    end
    if isfield(sProcess.options, 'rows') && isfield(sProcess.options.rows, 'Value') && ~isempty(sProcess.options.rows.Value)
        strData = [strData, ' ', sProcess.options.rows.Value];
    end
    if ~isempty(strData)
        strData = [' [' strData ']'];
    end

    % === ABSOLUTE VALUE ===
    % Get options
    if isfield(sProcess.options, 'isabs') && isfield(sProcess.options.isabs, 'Value') && sProcess.options.isabs.Value
        isAbsolute = 1;
        strAbs = ' abs';
    elseif isfield(sProcess.options, 'isnorm') && isfield(sProcess.options.isnorm, 'Value') && sProcess.options.isnorm.Value
        isAbsolute = 1;
        strAbs = ' norm';

    else
        isAbsolute = 0;
        strAbs = '';
    end

    % === TEST COMMENT ===
    % Get test info
    if isfield(sProcess.options, 'test_type') && isfield(sProcess.options.test_type, 'Value') && ~isempty(sProcess.options.test_type.Value)
        TestType = sProcess.options.test_type.Value;
    else
        TestType = 'ttest_unequal';
    end
    if isfield(sProcess.options, 'tail') && isfield(sProcess.options.tail, 'Value') && ~isempty(sProcess.options.tail.Value)
        TestTail = sProcess.options.tail.Value;
    else
        TestTail = [];
    end
    % Documenting test to perform
    switch (TestType)
        case {'ttest_equal', 'ttest_unequal', 'ttest_paired', 'wilcoxon_paired'}   % , 'wilcoxon'
            strHypo = '          H0:(A=B)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(A<B)'];
                case 'two',    strHypo = [strHypo, ', H1:(A<>B)'];
                case 'one+',   strHypo = [strHypo, ', H1:(A>B)'];
            end
        case 'signtest'
            strHypo = '         H0:(A=B), H1:(A<>B)';
        case 'ttest_onesample'
            strHypo = '          H0:(X=0)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(X<0)'];
                case 'two',    strHypo = [strHypo, ', H1:(X<>0)'];
                case 'one+',   strHypo = [strHypo, ', H1:(X>0)'];
            end
        case 'ttest_baseline'
            strHypo = '          H0:(X=Baseline)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(X<Baseline)'];
                case 'two',    strHypo = [strHypo, ', H1:(X<>Baseline)'];
                case 'one+',   strHypo = [strHypo, ', H1:(X>Baseline)'];
            end
        case {'power_baseline', 'power_baseline_unconstr'}
            strHypo = '          H0:(|X|=|Baseline|)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(|X|<|Baseline|)'];
                case 'two',    strHypo = [strHypo, ', H1:(|X|<>|Baseline|)'];
                case 'one+',   strHypo = [strHypo, ', H1:(|X|>|Baseline|)'];
            end
        case 'chi2_onesample'
            strHypo = '          H0:(|Zi| = 0)';
        case 'chi2_onesample_unconstr'
            strHypo = '          H0:(|Zi| = 0)';
        case {'power', 'power_unconstr'}
            strHypo = '          H0:(vA=vB)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(vA<vB)'];
                case 'two',    strHypo = [strHypo, ', H1:(vA<>vB)'];
                case 'one+',   strHypo = [strHypo, ', H1:(vA>vB)'];
            end
        case {'absmean', 'absmean_param'}
            strHypo = '          H0:(|mean(A)|=|mean(B)|)';
            switch(TestTail)
                case 'one-',   strHypo = [strHypo, ', H1:(|mean(A)|<|mean(B)|)'];
                case 'two',    strHypo = [strHypo, ', H1:(|mean(A)|<>|mean(B)|)'];
                case 'one+',   strHypo = [strHypo, ', H1:(|mean(A)|>|mean(B)|)'];
            end
    end

    % No comment when forcing a one-sided test
    if ismember(TestType, {'ttest_onesample'}) && isAbsolute
        strTail = '';
    elseif strcmpi(TestType, 'chi2_onesample') || strcmpi(TestType, 'chi2_onesample_unconstr')
        strTail = '';
        strAbs = '';
    elseif strcmpi(TestType, 'signtest')
        strTail = '';
    % Comment for one-tailed tests
    elseif ismember(TestTail, {'one-','one+'})
        strTail = [' ' TestTail];
    else
        strTail = '';
    end

    % === ASSEMBLING ===
    switch (TestType)
        case 'ttest_equal',      Comment = ['t-test equal'     strTail strAbs strData strHypo];
        case 'ttest_unequal',    Comment = ['t-test unequal'   strTail strAbs strData strHypo];
        case 'ttest_paired',     Comment = ['t-test paired'    strTail strAbs strData strHypo];
        case 'ttest_onesample',  Comment = ['t-test zero'      strTail strAbs strData strHypo];
        case 'ttest_baseline',   Comment = ['t-test baseline'  strTail strAbs strData strHypo];
        case 'power_baseline',   Comment = ['power test baseline'  strTail strData strHypo];
        case 'power_baseline_unconstr',   Comment = ['power test baseline unconstr' strTail strData strHypo];
        case 'signtest',         Comment = ['signtest'         strAbs strData strHypo];
        case 'wilcoxon_paired',  Comment = ['wilcoxon paired ' strTail strAbs strData strHypo];
        % case 'wilcoxon',         Comment = ['wilcoxon'       strTail strAbs strData strHypo];
        case 'power',            Comment = ['power test'       strTail strData strHypo];
        case 'power_unconstr',   Comment = ['power test unconstr' strTail strData strHypo];
        case 'chi2_onesample',   Comment = ['Chi2-test'        strTail strData strHypo];
        case 'chi2_onesample_unconstr',  Comment = ['Chi2-test unconstr' strTail strData strHypo];
        case 'absmean',          Comment = ['absmean test'     strTail strData strHypo];
        case 'absmean_param',    Comment = ['absmean test'     strTail strData strHypo];
    end
end

%% ===== RUN =====
function sOutput = Run(sProcess, sInputsA, sInputsB) %#ok<DEFNU>
    % Initialize returned variables
    sOutput = [];

    % ===== GET OPTIONS =====
    % Get generic extract options
    OPTIONS = process_extract_values('GetExtractOptions', sProcess, sInputsA(1));
    % Exclude zero values
    if isfield(sProcess.options, 'iszerobad') && isfield(sProcess.options.iszerobad, 'Value') && ~isempty(sProcess.options.iszerobad.Value)
        OPTIONS.isZeroBad = sProcess.options.iszerobad.Value;
    else
        OPTIONS.isZeroBad = 1;
    end
    % Get test type
    OPTIONS.TestType = sProcess.options.test_type.Value;
    OPTIONS.TestTail = sProcess.options.tail.Value;
    % Invalid test/tail combinations
    if ismember(OPTIONS.TestType, {'ttest_onesample'}) && OPTIONS.isAbsolute && ismember(OPTIONS.TestTail, {'two', 'one-'})
        bst_report('Warning', sProcess, [], 'Testing |X|>0: Using a positive one-tailed test (one+) instead.');
        OPTIONS.TestTail = 'one+';
    elseif ismember(OPTIONS.TestType, {'chi2_onesample', 'chi2_onesample_unconstr'}) && ismember(OPTIONS.TestTail, {'two', 'one-'})
        bst_report('Warning', sProcess, [], 'Testing |X|>0: Using a positive one-tailed test (one+) instead.');
        OPTIONS.TestTail = 'one+';
    end
    % Time-frequency: Warning if processing power
    isTfPower = false;
    if strcmpi(sInputsA(1).FileType, 'timefreq')
        TfMat = in_bst_timefreq(sInputsA(1).FileName, 0, 'Measure');
        if isequal(TfMat.Measure, 'power')
            isTfPower = true;
        end
    end
    % Get average function
    switch (OPTIONS.TestType)
        case {'ttest_equal', 'ttest_unequal', 'ttest_onesample', 'ttest_paired', 'ttest_baseline', 'absmean', 'absmean_param'}
            if isTfPower
                bst_report('Warning', sProcess, [], ['You are testing power values, while a more standard analysis is to test the magnitude (ie. sqrt(power)).' 10 ...
                    'Option #1: Recompute the time-frequency maps using the option "Measure: Magnitude".' 10 ...
                    'Option #2: Run the process "Extract > Measure from complex values", with option "Magntiude".']);
                isTfPower = false;
            end
            if OPTIONS.isAbsolute
                AvgFunction = 'norm';
                isAvgVariance = 1;
            else
                AvgFunction = 'mean';
                isAvgVariance = 1;
            end
        case {'power', 'power_baseline', 'power_baseline_unconstr', 'power_unconstr', 'chi2_onesample', 'chi2_onesample_unconstr'}
            AvgFunction = 'rms';
            isAvgVariance = 0;
    end
    isAvgWeighted = 0;
    % Baseline: Only for test against baseline
    if isfield(sProcess.options, 'baseline') && isfield(sProcess.options.baseline, 'Value') && iscell(sProcess.options.baseline.Value) && ~isempty(sProcess.options.baseline.Value) && ~isempty(sProcess.options.baseline.Value{1})
        Baseline = sProcess.options.baseline.Value{1};
    else
        Baseline = [];
    end
    % Unconstrained chi2 test: force the computation of norm
    if ismember(OPTIONS.TestType, {'chi2_onesample_unconstr', 'power_baseline_unconstr'})
        OPTIONS.isAbsolute = 1;
    end

    % ===== CHECK INPUT FILES =====
    % Make sure that file type is indentical for both sets
    if ~isempty(sInputsA) && ~isempty(sInputsB) && ~strcmpi(sInputsA(1).FileType, sInputsB(1).FileType)
        bst_report('Error', sProcess, [], 'Cannot process inputs from different types.');
        return;
    end
    % Check the number of files in input
    if (length(sInputsA) < 2) && ~strcmpi(OPTIONS.TestType, 'ttest_baseline')
        bst_report('Error', sProcess, [], 'Not enough files in input.');
        return;
    end
    % Load time vector from the first file: if same as input, discard input
    TimeVector = in_bst(sInputsA(1).FileName, 'Time');
    if ~isempty(OPTIONS.TimeWindow) && (abs(TimeVector(1) - OPTIONS.TimeWindow(1)) < 1e-4) && (abs(TimeVector(end) - OPTIONS.TimeWindow(2)) < 1e-4)
        OPTIONS.TimeWindow = [];
    end
    % Load freq range from the first file: if same as input, discard input
    if ~isempty(OPTIONS.FreqRange)
        % Load Freqs field from the input file
        TfMat = in_bst_timefreq(sInputsA(1).FileName, 0, 'Freqs');
        if iscell(TfMat.Freqs)
            BandBounds = process_tf_bands('GetBounds', TfMat.Freqs);
            FreqList = unique(BandBounds(:));
        else
            FreqList = TfMat.Freqs;
        end
        if (abs(OPTIONS.FreqRange(1) - FreqList(1)) < 1e-4) && (abs(OPTIONS.FreqRange(2) - FreqList(end)) < 1e-4)
            OPTIONS.FreqRange = [];
        end
    end

    % ===== INPUT DATA =====
    % If there is nothing special done with the files: files can be handled directly by bst_avg_files
    if isempty(OPTIONS.TimeWindow) && isempty(OPTIONS.ScoutSel) && isempty(OPTIONS.SensorTypes) && isempty(OPTIONS.Rows) && isempty(OPTIONS.FreqRange) && ~OPTIONS.isAvgTime && ~OPTIONS.isAvgRow && ~OPTIONS.isAvgFreq && ~isTfPower
        InputSetA = {sInputsA.FileName};
        if ~isempty(sInputsB)
            InputSetB = {sInputsB.FileName};
        else
            InputSetB = [];
        end
        OutputType = sInputsA(1).FileType;
    % Else: Call process "Extract values" first
    else
        % Do not concatenate the output
        OPTIONS.Dim = 0;
        % Call extraction process: FilesA
        [InputSetA, OutputType] = process_extract_values('Extract', sProcess, sInputsA, OPTIONS);
        if isempty(InputSetA)
            return;
        end
        % Read FilesB
        if ~isempty(sInputsB)
            InputSetB = process_extract_values('Extract', sProcess, sInputsB, OPTIONS);
            if isempty(InputSetB)
                return;
            end
        else
            InputSetB = [];
        end
        % Adjust time-frequency already in 'power', for power stats.
        if isTfPower
            bst_report('Info', sProcess, [], 'Data is already power values, adapting power test (not squaring again).');
            for iIn = 1:numel(InputSetA)
                [InputSetA{iIn}.TF, isError] = process_tf_measure('Compute', InputSetA{iIn}.TF, InputSetA{iIn}.Measure, 'magnitude', true);
                if isError
                    bst_report('Error', sProcess, sInputsA(1), ['Error converting time-frequency measure ' InputSetA{iIn}.Measure 'to magnitude.']);
                end
            end
            for iIn = 1:numel(InputSetB)
                [InputSetB{iIn}.TF, isError] = process_tf_measure('Compute', InputSetB{iIn}.TF, InputSetB{iIn}.Measure, 'magnitude', true);
                if isError
                    bst_report('Error', sProcess, sInputsA(1), ['Error converting time-frequency measure ' InputSetB{iIn}.Measure 'to magnitude.']);
                end
            end
        end
    end

    % === COMPUTE TEST ===
    % Branch between dependent(=paired) and independent tests
    switch (OPTIONS.TestType)

        % ===== INDEPENDENT TESTS =====
        case {'ttest_equal', 'ttest_unequal', 'absmean', 'absmean_param', 'power', 'power_unconstr'}
            % Compute mean and var for both files sets
            [StatA, MessagesA] = bst_avg_files(InputSetA, [], AvgFunction, isAvgVariance, isAvgWeighted, OPTIONS.isMatchRows, OPTIONS.isZeroBad);
            [StatB, MessagesB] = bst_avg_files(InputSetB, [], AvgFunction, isAvgVariance, isAvgWeighted, OPTIONS.isMatchRows, OPTIONS.isZeroBad);
            % Add messages to report
            if ~isempty(MessagesA)
                if isempty(StatA)
                    bst_report('Error', sProcess, sInputsA, MessagesA);
                    return;
                else
                    bst_report('Warning', sProcess, sInputsA, MessagesA);
                end
            end
            if ~isempty(MessagesB)
                if isempty(StatB)
                    bst_report('Error', sProcess, sInputsB, MessagesB);
                    return;
                else
                    bst_report('Warning', sProcess, sInputsB, MessagesB);
                end
            end
            if ~isequal(size(StatA.mean), size(StatB.mean))
                bst_report('Error', sProcess, [], 'Files A and B do not have the same number of signals or time samples.');
                return;
            end
            % Detect if the source model is unconstrained
            isUnconstrained = panel_scout('isUnconstrained', StatA);
            % Do not allow unconstrained sources without a norm
            if isUnconstrained && ~OPTIONS.isAbsolute
                bst_report('Error', sProcess, [], ['Cannot run this test on unconstrained sources:' 10 'you must compute the norm of the three orientations first.']);
                return;
            end
            % Bad channels: For recordings, keep only the channels that are good in BOTH A and B sets
            if strcmpi(sInputsA(1).FileType, 'data') && ~isempty(StatA.ChannelFlag) && ~isempty(StatB.ChannelFlag)
                ChannelFlag = StatA.ChannelFlag;
                ChannelFlag(StatB.ChannelFlag == -1) = -1;
                isGood = (ChannelFlag == 1);
            else  % case {'results', 'timefreq', 'matrix'}
                ChannelFlag = [];
                isGood = true(size(StatA.mean, 1), 1);
                isGood((StatA.nGoodSamples < 2) | (StatB.nGoodSamples < 2)) = 0;
            end

            % === COMPUTE TEST ===
            % Display progress bar
            bst_progress('start', 'Processes', 'Computing test...');
            % Get average results
            mA = StatA.mean(isGood,:,:);
            mB = StatB.mean(isGood,:,:);
            nA = repmat(StatA.nGoodSamples(isGood,:,:), [1, size(mA,2), size(mA,3)]);
            nB = repmat(StatB.nGoodSamples(isGood,:,:), [1, size(mB,2), size(mB,3)]);
            % Get variance (if needed)
            if isAvgVariance
                vA = StatA.var(isGood,:,:);
                vB = StatB.var(isGood,:,:);
                % Remove null variances
                iNull = find((vA == 0) | (vB == 0));
                vA(iNull) = eps;
                vB(iNull) = eps;
            else
                iNull = [];
            end

            % Compute test statistic
            switch (OPTIONS.TestType)
                % === T-TEST: EQUAL VARIANCE ===
                case 'ttest_equal'
                    df   = nA + nB - 2 ;
                    pvar = ((nA-1).*vA + (nB-1).*vB) ./ df;
                    tmap = (mA-mB) ./ sqrt(pvar .* (1./nA + 1./nB));
                    % Calculate p-values from t-values
                    pmap = ComputePvalues(tmap, df, 't', OPTIONS.TestTail);
                    % Units: t
                    DisplayUnits = 't';

                % === T-TEST: UNEQUAL VARIANCE ===
                case 'ttest_unequal'
                    df = (vA./nA + vB./nB).^2 ./ ...
                         ((vA./nA).^2./(nA-1) + (vB./nB).^2./(nB-1));
                    tmap = (mA-mB) ./ sqrt(vA./nA + vB./nB);
                    % Calculate p-values from t-values
                    pmap = ComputePvalues(tmap, df, 't', OPTIONS.TestTail);
                    % Units: t
                    DisplayUnits = 't';

                % ===== POWER TEST (A/B) =====
                case {'power', 'power_unconstr'}
                    % If you have xi, n normal random variables with zero mean and unit variance N(0,1), then:
                    % X1 = sum_i(xi^2) is chi-square random variable with n degrees of freedom
                    % https://en.wikipedia.org/wiki/Chi-squared_distribution
                    %
                    % If X1 and X2 are chi-square random variables with nA and nB degrees of freedom, then
                    % F = (X1/nA) / (X2/nB) is F-distributed with nA numerator degrees of freedom and nB denominator degrees of freedom.
                    % https://en.wikipedia.org/wiki/F-distribution
                    %
                    % Use case here: If A~N(0,vA) and B~N(0,vB)
                    % Then F = (sum(A^2)/nA) / (sum(B^2)/nB)  ~ F(nA,nB)
                    %
                    % Can be used for two things:
                    % 1) Testing for variance difference:   H0:(A~N(0,vA), B~N(0,vB), vA=vB)
                    %    => Samples must be zero-mean (mA=0, mB=0) so probably normalized before testing
                    % 2) Testing for power difference:      H0:(A~N(0,1) and B~(0,1))
                    %    => Samples must be normalized before testing

                    % The output of bst_avg_files is the RMS of A and B:  mA = sqrt(sum(A^2)/nA)
                    % F statistic = (sum(A^2)/nA) / (sum(B^2)/nB)
                    %             = (mA^2) / (mB^2)
                    tmap = mA.^2 ./ mB.^2;
                    % Degrees of freedom
                    if strcmpi(OPTIONS.TestType, 'power_unconstr')
                        df = {3*nA,3*nB};
                    else
                        df = {nA,nB};
                    end
                    % Calculate p-values from t-values
                    pmap = ComputePvalues(tmap, df, 'F', OPTIONS.TestTail);
                    % Units: F
                    DisplayUnits = 'F';

                % === ABSOLUTE MEAN TEST ===
                case 'absmean_param'
                    % EXPLANATIONS:
                    %   Assume the individual samples xi follow a normal distribution with mean "m" and variance "s^2":  X ~ N(m,s^2)
                    %   Then mean(x) is also normal with mean m and variance sm^2 = s^2/N:    mean(X) ~ N(m,s^2/N)
                    %
                    %   When we apply abs(mean(x)), we are folding this normal distribution to make it positive.
                    %   Details are discussed here: https://en.wikipedia.org/wiki/Folded_normal_distribution
                    %   If y=|x|, with x~N(m,sm^2), then y has a new distribution with mean my and variance sy^2:
                    %      my = sm*sqrt(2/pi)*exp(-m^2/(2*sm^2)) - m*erf(-m/sqrt(2*sm^2))
                    %         = s/sqrt(N)*sqrt(2/pi)*exp(-m^2/(2*s^2/N)) - m*erf(-m/sqrt(2*s^2/N))
                    %      sy^2 = m^2 + sm^2 - my^2
                    %           = m^2 + s^2/N - my^2
                    %
                    % RESTRICTIONS
                    %   - A and B are normally distributed (same as t-test assumptions)
                    %   - Cannot be applied if an absolute has been applied already, we need the original values

                    % Test to check that there was no abs already applied, we need the relative values
                    if all(mA(:) > 0) && all(mB(:) > 0)
                        bst_report('Error', sProcess, [], ['This test is designed for values that are positive and negative.' 10 'It cannot be applied to values for which we have already discarded the sign.' 10 'If all your measures you are testing are always strictly positive, then use a Student t-test.']);
                        return;
                    end

                    % Mean of: abs(mean(A))-abs(mean(B))
                    % mAabs = sA/sqrt(N)*sqrt(2/pi)*exp(-mA^2/(2*sA^2/nA)) - mA*erf(-mA/sqrt(2*sA^2/nA))
                    %       = sqrt(vA./nA.*(2/pi)) .* exp(-mA.^2/(2.*vA./nA)) - mA*erf(-mA./sqrt(2.*vA./nA))
                    mAabs = sqrt(vA./nA.*(2/pi)) .* exp(-mA.^2./(2.*vA./nA)) - mA.*erf(-mA./sqrt(2.*vA./nA));
                    mBabs = sqrt(vB./nB.*(2/pi)) .* exp(-mB.^2./(2.*vB./nB)) - mB.*erf(-mB./sqrt(2.*vB./nB));
                    mAB = mAabs - mBabs;
                    % Variance of: abs(mean(A))-abs(mean(B))
                    vAabs = mA.^2 + vA./nA - mAabs.^2;
                    vBabs = mB.^2 + vB./nB - mBabs.^2;
                    sdAB = sqrt(vAabs + vBabs);
                    S = (abs(mA) - abs(mB) - mAB) ./ sdAB;  %S should be zero mean, unit variance under the null hypothesis

                    % [H,P] = ztest(S,0,1);   m = 0; sigma = 1;
                    % zval = (S - m) ./ (sigma ./ sqrt(length(S)));
                    tmap = S .* sqrt(length(S));
                    % Two-tailed test
                    pmap = 2 * (1/2 * erfc(-1 * -abs(tmap) / sqrt(2)));     % 2 * normcdf(-abs(zval),0,1);
                    % No need to recompute the values on the fly
                    df = [];
                    % Units: z
                    DisplayUnits = 'z';

                otherwise
                    error('Not supported yet');
            end
            % Remove values with null variances
            if ~isempty(iNull)
                tmap(iNull) = 0;
                pmap(iNull) = 1;
            end

        % ===== PAIRED/ONE-SAMPLE TESTS =====
        case {'ttest_paired', 'ttest_onesample', 'ttest_baseline', 'chi2_onesample', 'chi2_onesample_unconstr', 'power_baseline', 'power_baseline_unconstr'}
            % Number of samples must be equal
            if (length(sInputsA) ~= length(sInputsB)) && ismember(OPTIONS.TestType, {'ttest_paired'})
                bst_report('Error', sProcess, [], 'For a paired test, the number of files must be the same in the two groups.');
                return;
            end
            % Compute the mean and variance of (samples A - samples B)
            [StatA, MessagesA] = bst_avg_files(InputSetA, InputSetB, AvgFunction, isAvgVariance, isAvgWeighted, OPTIONS.isMatchRows, OPTIONS.isZeroBad);
            % Add messages to report
            if ~isempty(MessagesA)
                if isempty(StatA)
                    bst_report('Error', sProcess, [], MessagesA);
                    return;
                else
                    bst_report('Warning', sProcess, [], MessagesA);
                end
            end
            % Display progress bar
            bst_progress('start', 'Processes', 'Computing test...');
            % Bad channels and other properties
            switch lower(sInputsA(1).FileType)
                case {'data', 'pdata'}
                    ChannelFlag = StatA.ChannelFlag;
                    isGood = (ChannelFlag == 1);
                case {'results', 'timefreq', 'matrix', 'presults', 'ptimefreq', 'pmatrix'}
                    ChannelFlag = [];
                    isGood = true(size(StatA.mean, 1), 1);
                    isGood(StatA.nGoodSamples < 2) = -1;
            end

            % === COMPUTE TEST ===
            % Display progress bar
            bst_progress('start', 'Processes', 'Computing test...');
            % Get results
            mean_diff = StatA.mean(isGood,:,:);
            nA = repmat(StatA.nGoodSamples(isGood,:,:), [1, size(mean_diff,2), size(mean_diff,3)]);
            nB = [];
            % Get variance (if needed)
            if isAvgVariance
                std_diff = sqrt(StatA.var(isGood,:,:));
                % Remove null variances
                iNull = find(std_diff == 0);
                std_diff(iNull) = eps;
            else
                iNull = [];
            end

            % Get pre-stimulus baseline (for tests vs baseline)
            if ismember(OPTIONS.TestType, {'ttest_baseline', 'power_baseline', 'power_baseline_unconstr'})
                if ~isempty(Baseline)
                    % Get baseline bounds
                    iBaseline = bst_closest(Baseline, StatA.Time);
                    if (iBaseline(1) == iBaseline(2))
                        bst_report('Error', sProcess, [], 'The baseline must be included in the time window on which you run the test.');
                        return;
                    end
                    iBaseline = iBaseline(1):iBaseline(2);
                else
                    bst_report('Warning', sProcess, [], 'Baseline is not defined, using the entire time definition.');
                    iBaseline = 1:length(TimeVector);
                end
            end

            % Compute test statistic
            switch (OPTIONS.TestType)
                case {'ttest_paired', 'ttest_onesample'}
                    % Compute t-test
                    tmap = mean_diff ./ std_diff .* sqrt(nA);
                    df = nA - 1;
                    % Test if the statistics make sense
                    if all(tmap(:) == 0)
                        bst_report('Error', sProcess, [], 'The T-statistics is zero for all the tests.');
                        return;
                    end
                    % Calculate p-values from t-values
                    pmap = ComputePvalues(tmap, df, 't', OPTIONS.TestTail);
                    % Units: t
                    DisplayUnits = 't';

                case {'chi2_onesample', 'chi2_onesample_unconstr'}
                    % https://en.wikipedia.org/wiki/Chi-squared_distribution
                    % =>  If Zi~N(0,1) i=1..n  =>  Q=sum(Zi^2) ~ Chi2(n)
                    % Variable "mean_diff" contains RMS(data)=sqrt(sum(data.^2)/n)
                    % =>  If data is ~N(0,1)   =>  (mean_diff^2 * n) ~ Chi2(n)
                    tmap = mean_diff .^ 2 .* nA;
                    % Number of degrees of freedom
                    if strcmpi(OPTIONS.TestType, 'chi2_onesample_unconstr')
                        df = 3 * nA;
                    else
                        df = nA;
                    end
                    % Calculate p-values from F-values
                    pmap = ComputePvalues(tmap, df, 'chi2', OPTIONS.TestTail);
                    % Units: t
                    DisplayUnits = 'chi2';

                case 'ttest_baseline'
                    % TEST:  Y = mean_trials(X)
                    %        t = (Y - mean_time(Y(baseline)) / std_time(Y(baseline)))
                    % Compute variance over baseline (pre-stim interval)
                    meanBaseline = mean(mean_diff(:,iBaseline,:), 2);
                    stdBaseline = std(mean_diff(:,iBaseline,:), 0, 2);
                    % Remove null variances
                    iNull = find(stdBaseline == 0);
                    stdBaseline(iNull) = eps;
                    % Compute t-statistics (formula from wikipedia)
                    tmap = bst_bsxfun(@minus, mean_diff, meanBaseline);
                    tmap = bst_bsxfun(@rdivide, tmap, stdBaseline);
                    df = repmat(length(iBaseline) - 1, size(tmap));
                    % Calculate p-values from t-values
                    pmap = ComputePvalues(tmap, df, 't', OPTIONS.TestTail);
                    % Units: t
                    DisplayUnits = 't';

                case {'power_baseline', 'power_baseline_unconstr'}
                    % TEST:  Y = sum_trials(X^2)
                    %        F = Y / mean_time(Y(baseline))     F~F(Ntrials,Ntrials)
                    %
                    % The output of bst_avg_files is the RMS of X:  data = sqrt(sum_trials(X^2)/Ntrials)
                    % => Y = data^2 * Ntrials
                    % => F = data^2 / mean(data^2(baseline))

                    % Square the RMS
                    data = mean_diff .^ 2;
                    % Compute mean over baseline
                    meanBaseline = mean(data(:,iBaseline,:), 2);
                    % Remove null denominators
                    iNull = find(meanBaseline == 0);
                    meanBaseline(iNull) = eps;
                    % Compute F statistic
                    tmap = bst_bsxfun(@rdivide, data, meanBaseline);
                    % Degrees of freedom
                    if strcmpi(OPTIONS.TestType, 'power_baseline_unconstr')
                        df = {3*nA,3*nA};
                    else
                        df = {nA,nA};
                    end
                    % Calculate p-values from F-values
                    pmap = ComputePvalues(tmap, df, 'F', OPTIONS.TestTail);
                    % No need to recompute the values on the fly
                    df = [];
                    % Units: t
                    DisplayUnits = 'F';

                otherwise
                    error('Not supported yet');
            end
            % Remove values with null variances
            if ~isempty(iNull)
                tmap(iNull) = 0;
                pmap(iNull) = 1;
            end
    end

    % Return full matrices
    if all(isGood)
        tmap_full = tmap;
        pmap_full = pmap;
        df_full   = df;
        nA_full   = nA;
        nB_full   = nB;
    else
        tmap_full = zeros(size(StatA.mean));
        tmap_full(isGood,:,:) = tmap;
        if ~isempty(df)
            df_full = zeros(size(StatA.mean));
            df_full(isGood,:,:) = df;
        else
            df_full = [];
        end
        if ~isempty(pmap)
            pmap_full = ones(size(StatA.mean));
            pmap_full(isGood,:,:) = pmap;
        else
            pmap_full = [];
        end
        if ~isempty(nA)
            nA_full = zeros(1,size(StatA.mean,1));
            nA_full(isGood) = nA(1:size(nA,1));
        else
            nA_full = [];
        end
        if ~isempty(nB)
            nB_full = zeros(1,size(StatA.mean,1));
            nB_full(isGood) = nB(1:size(nB,1));
        else
            nB_full = [];
        end
    end

    % === CONVERT BACK MATRIX => DATA ===
    % If processing recordings with only some sensor types selected
    if strcmpi(sInputsA(1).FileType, 'data') && strcmpi(OutputType, 'matrix') && ~isempty(OPTIONS.SensorTypes) && ~OPTIONS.isAvgTime && ~OPTIONS.isAvgRow && ~OPTIONS.isAvgFreq
        % Get the list of selected sensors
        dataTypes = strtrim(str_split(OPTIONS.SensorTypes, ',;'));
        % If only major data types were selected: save results in "data" format
        if ~isempty(dataTypes) && all(ismember(dataTypes, {'MEG','EEG','MEG MAG''MEG GRAD','MEG GRAD2','MEG GRAD3','SEEG','ECOG','NIRS'}))
            % Load channel file
            ChannelMat = in_bst_channel(sInputsA(1).ChannelFile);
            % Find channel names in the output row names
            [tmp,iChan,iRow] = intersect({ChannelMat.Channel.Name}, StatA.RowNames);
            % Convert output data matrices
            tmap_tmp = zeros(length(ChannelMat.Channel), size(tmap_full,2), size(tmap_full,3));
            tmap_tmp(iChan,:,:) = tmap_full(iRow,:,:);
            tmap_full = tmap_tmp;
            if ~isempty(pmap_full)
                pmap_tmp = zeros(size(tmap_tmp));
                pmap_tmp(iChan,:,:) = pmap_full(iRow,:,:);
                pmap_full = pmap_tmp;
            end
            if ~isempty(df_full)
                df_tmp = zeros(size(tmap_tmp));
                df_tmp(iChan,:,:) = df_full(iRow,:,:);
                df_full = df_tmp;
            end
            % New channel flag
            tmpChannelFlag = -1 .* ones(length(ChannelMat.Channel), 1);
            if ~isempty(ChannelFlag) && (length(ChannelFlag) == length(iChan))
                tmpChannelFlag(iChan) = ChannelFlag(iRow);
            else
                tmpChannelFlag(iChan) = 1;
            end
            ChannelFlag = tmpChannelFlag;
            % Convert Stat structure
            OutputType = 'data';
            StatA.RowNames = [];
        end
    end

    % === OUTPUT STRUCTURE ===
    % Initialize output structure
    sOutput = db_template('statmat');
    sOutput.pmap         = pmap_full;
    sOutput.tmap         = tmap_full;
    sOutput.df           = df_full;
    sOutput.Correction   = 'no';
    sOutput.Type         = OutputType;
    sOutput.ChannelFlag  = ChannelFlag;
    sOutput.Time         = StatA.Time;
    sOutput.ColormapType = 'stat2';
    sOutput.DisplayUnits = DisplayUnits;
    sOutput.nComponents  = StatA.nComponents;
    sOutput.GridAtlas    = StatA.GridAtlas;
    sOutput.Freqs        = StatA.Freqs;
    sOutput.TFmask       = StatA.TFmask;
    % Row names
    if isfield(StatA, 'RowNames') && ~isempty(StatA.RowNames)
        if strcmpi(OutputType, 'matrix')
            sOutput.Description = StatA.RowNames;
        elseif strcmpi(OutputType, 'timefreq')
            sOutput.RowNames = StatA.RowNames;
        end
    end
    % Save options
    sOutput.Options = OPTIONS;
    % Save the number of good samples used for both sets
    sOutput.Options.nGoodSamplesA = nA_full;
    sOutput.Options.nGoodSamplesB = nB_full;
end

%% ===== COMPUTE P-VALUES ====
function p = ComputePvalues(t, df, TestDistrib, TestTail)
    % Default: two-tailed tests
    if (nargin < 4) || isempty(TestTail)
        TestTail = 'two';
    end
    % Default: F-distribution
    if (nargin < 3) || isempty(TestDistrib)
        TestDistrib = 'f';
    end
    % Nothing to test
    if strcmpi(TestTail, 'no')
        p = zeros(size(t));
        return;
    end

    % Different distributions
    switch lower(TestDistrib)
        % === T-TEST ===
        case 't'
            % Calculate p-values from t-values
            switch (TestTail)
                case 'one-'
                    % Inferior one-tailed t-test:   p = tcdf(t, df);
                    % Equivalent without the statistics toolbox (FieldTrip formula)
                    p = 0.5 .* ( 1 + sign(t) .* betainc( t.^2 ./ (df + t.^2), 0.5, 0.5.*df ) );
                case 'two'
                    % Two-tailed t-test:     p = 2 * (1 - tcdf(abs(t),df));
                    % Equivalent without the statistics toolbox
                    p = betainc( df ./ (df + t .^ 2), df./2, 0.5);
                    % FieldTrip equivalent: p2 = 1 - betainc( t.^2 ./ (df + t.^2), 0.5, 0.5.*df );
                case 'one+'
                    % Superior one-tailed t-test:    p = 1 - tcdf(t, df);
                    % Equivalent without the statistics toolbox (FieldTrip formula)
                    p = 0.5 .* ( 1 - sign(t) .* betainc( t.^2 ./ (df + t.^2), 0.5, 0.5.*df ) );
            end

        % === F-TEST ===
        case 'f'
            v1 = df{1};
            v2 = df{2};
            % Evaluate for which values we can compute something
            k = ((t > 0) & ~isinf(t) & (v1 > 0) & (v2 > 0));
            % Initialize returned p-values
            p = ones(size(t));
            % Calculate p-values from F-values
            switch (TestTail)
                case 'one-'
                    % Inferior one-tailed F-test
                    % p = fcdf(t, v1, v2);
                    p(k) = 1 - betainc(v2(k)./(v2(k) + v1(k).*t(k)), v2(k)./2, v1(k)./2);
                case 'two'
                    % Two tailed F-test
                    % p = 2*min(fcdf(F,df1,df2),fpval(F,df1,df2))
                    p(k) = 2 * min(...
                            1 - betainc(v2(k)./(v2(k) + v1(k).*t(k)), v2(k)./2, v1(k)./2), ...
                            1 - betainc(v1(k)./(v1(k) + v2(k)./t(k)), v1(k)./2, v2(k)./2));
                case 'one+'
                    % Superior one-tailed F-test
                    % p = fpval(t, v1, v2);
                    %   = fcdf(1/t, v2, v1);
                    p(k) = 1 - betainc(v1(k)./(v1(k) + v2(k)./t(k)), v1(k)./2, v2(k)./2);
            end

        % === CHI2-TEST ===
        case 'chi2'
            % Calculate p-values from Chi2-values
            %   chi2cdf(x,n) = gammainc(t/2, n/2)
            switch (TestTail)
                case 'one-'
                    % Inferior one-tailed Chi2-test:    p = gammainc(t./2, df./2);
                    error('Not relevant.');
                case 'two'
                    % Two-tailed Chi2-test
                    error('Not relevant.');
                case 'one+'
                    % Superior one-tailed Chi2-test:    p = 1 - gammainc(t./2, df./2);
                    p = 1 - gammainc(t./2, df./2);
            end
    end
end


function [SDR,ISR,SIR,SAR,perm]=bss_eval_images(ie,i)

% BSS_EVAL_IMAGES Ordering and measurement of the separation quality for
% estimated source spatial image signals in terms of true source, spatial
% (or filtering) distortion, interference and artifacts.
%
% [SDR,ISR,SIR,SAR,perm]=bss_eval_images(ie,i)
%
% Inputs:
% ie: nsrc x nsampl x nchan matrix containing estimated source images
% i: nsrc x nsampl x nchan matrix containing true source images
%
% Outputs:
% SDR: nsrc x 1 vector of Signal to Distortion Ratios
% ISR: nsrc x 1 vector of source Image to Spatial distortion Ratios
% SIR: nsrc x 1 vector of Source to Interference Ratios
% SAR: nsrc x 1 vector of Sources to Artifacts Ratios
% perm: nsrc x 1 vector containing the best ordering of estimated source
% images in the mean SIR sense (estimated source image number perm(j)
% corresponds to true source image number j)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copyright 2007-2008 Emmanuel Vincent
% This software is distributed under the terms of the GNU Public License
% version 3 (http://www.gnu.org/licenses/gpl.txt)
% If you find it useful, please cite the following reference:
% Emmanuel Vincent, Hiroshi Sawada, Pau Bofill, Shoji Makino and Justinian
% P. Rosca, "First stereo audio source separation evaluation campaign:
% data, algorithms and results," In Proc. Int. Conf. on Independent
% Component Analysis and Blind Source Separation (ICA), pp. 552-559, 2007.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%% Errors %%%
if nargin<2, error('Not enough input arguments.'); end
[nsrc,nsampl,nchan]=size(ie);
[nsrc2,nsampl2,nchan2]=size(i);
if nsrc2~=nsrc, error('The number of estimated source images and reference source images must be equal.'); end
if nsampl2~=nsampl, error('The estimated source images and reference source images must have the same duration.'); end
if nchan2~=nchan, error('The estimated source images and reference source images must have the same number of channels.'); end

%%% Performance criteria %%%
% Computation of the criteria for all possible pair matches
SDR=zeros(nsrc,nsrc);
ISR=zeros(nsrc,nsrc);
SIR=zeros(nsrc,nsrc);
SAR=zeros(nsrc,nsrc);
for jest=1:nsrc,
    for jtrue=1:nsrc,
        [s_true,e_spat,e_interf,e_artif]=bss_decomp_mtifilt(reshape(ie(jest,:,:),nsampl,nchan).',i,jtrue,512);
        [SDR(jest,jtrue),ISR(jest,jtrue),SIR(jest,jtrue),SAR(jest,jtrue)]=bss_image_crit(s_true,e_spat,e_interf,e_artif);
    end
end
% Selection of the best ordering
perm=perms(1:nsrc);
nperm=size(perm,1);
meanSIR=zeros(nperm,1);
for p=1:nperm,
    meanSIR(p)=mean(SIR((0:nsrc-1)*nsrc+perm(p,:)));
end
[meanSIR,popt]=max(meanSIR);
perm=perm(popt,:).';
SDR=SDR((0:nsrc-1).'*nsrc+perm);
ISR=ISR((0:nsrc-1).'*nsrc+perm);
SIR=SIR((0:nsrc-1).'*nsrc+perm);
SAR=SAR((0:nsrc-1).'*nsrc+perm);

return;

function [s_true,e_spat,e_interf,e_artif]=bss_decomp_mtifilt(se,s,j,flen)

% BSS_DECOMP_MTIFILT Decomposition of an estimated source image into four
% components representing respectively the true source image, spatial (or
% filtering) distortion, interference and artifacts, derived from the true
% source images using multichannel time-invariant filters.
%
% [s_true,e_spat,e_interf,e_artif]=bss_decomp_mtifilt(se,s,j,flen)
%
% Inputs:
% se: nchan x nsampl matrix containing the estimated source image (one row per channel)
% s: nsrc x nsampl x nchan matrix containing the true source images
% j: source index corresponding to the estimated source image in s
% flen: length of the multichannel time-invariant filters in samples
%
% Outputs:
% s_true: nchan x nsampl matrix containing the true source image (one row per channel)
% e_spat: nchan x nsampl matrix containing the spatial (or filtering) distortion component
% e_interf: nchan x nsampl matrix containing the interference component
% e_artif: nchan x nsampl matrix containing the artifacts component

%%% Errors %%%
if nargin<4, error('Not enough input arguments.'); end
[nchan2,nsampl2]=size(se);
[nsrc,nsampl,nchan]=size(s);
if nchan2~=nchan, error('The number of channels of the true source images and the estimated source image must be equal.'); end
if nsampl2~=nsampl, error('The duration of the true source images and the estimated source image must be equal.'); end

%%% Decomposition %%%
% True source image
s_true=[reshape(s(j,:,:),nsampl,nchan).',zeros(nchan,flen-1)];
% Spatial (or filtering) distortion
e_spat=project(se,s(j,:,:),flen)-s_true;
% Interference
e_interf=project(se,s,flen)-s_true-e_spat;
% Artifacts
e_artif=[se,zeros(nchan,flen-1)]-s_true-e_spat-e_interf;

return;

function sproj=project(se,s,flen)

% SPROJ Least-squares projection of each channel of se on the subspace
% spanned by delayed versions of the channels of s, with delays between 0
% and flen-1

[nsrc,nsampl,nchan]=size(s);
s=reshape(permute(s,[3 1 2]),nchan*nsrc,nsampl);

%%% Computing coefficients of least squares problem via FFT %%%
% Zero padding and FFT of input data
s=[s,zeros(nchan*nsrc,flen-1)];
se=[se,zeros(nchan,flen-1)];
fftlen=2^nextpow2(nsampl+flen-1);
sf=fft(s,fftlen,2);
sef=fft(se,fftlen,2);
% Inner products between delayed versions of s
G=zeros(nchan*nsrc*flen);
for k1=0:nchan*nsrc-1,
    for k2=0:k1,
        ssf=sf(k1+1,:).*conj(sf(k2+1,:));
        ssf=real(ifft(ssf));
        ss=toeplitz(ssf([1 fftlen:-1:fftlen-flen+2]),ssf(1:flen));
        G(k1*flen+1:k1*flen+flen,k2*flen+1:k2*flen+flen)=ss;
        G(k2*flen+1:k2*flen+flen,k1*flen+1:k1*flen+flen)=ss.';
    end
end
% Inner products between se and delayed versions of s
D=zeros(nchan*nsrc*flen,nchan);
for k=0:nchan*nsrc-1,
    for i=1:nchan,
        ssef=sf(k+1,:).*conj(sef(i,:));
        ssef=real(ifft(ssef,[],2));
        D(k*flen+1:k*flen+flen,i)=ssef(:,[1 fftlen:-1:fftlen-flen+2]).';
    end
end

%%% Computing projection %%%
% Distortion filters
C=G\D;
C=reshape(C,flen,nchan*nsrc,nchan);
% Filtering
sproj=zeros(nchan,nsampl+flen-1);
for k=1:nchan*nsrc,
    for i=1:nchan,
        sproj(i,:)=sproj(i,:)+fftfilt(C(:,k,i).',s(k,:));
    end
end

return;

function [SDR,ISR,SIR,SAR]=bss_image_crit(s_true,e_spat,e_interf,e_artif)

% BSS_IMAGE_CRIT Measurement of the separation quality for a given source
% image in terms of true source, spatial (or filtering) distortion,
% interference and artifacts.
%
% [SDR,ISR,SIR,SAR]=bss_image_crit(s_true,e_spat,e_interf,e_artif)
%
% Inputs:
% s_true: nchan x nsampl matrix containing the true source image (one row per channel)
% e_spat: nchan x nsampl matrix containing the spatial (or filtering) distortion component
% e_interf: nchan x nsampl matrix containing the interference component
% e_artif: nchan x nsampl matrix containing the artifacts component
%
% Outputs:
% SDR: Signal to Distortion Ratio
% ISR: source Image to Spatial distortion Ratio
% SIR: Source to Interference Ratio
% SAR: Sources to Artifacts Ratio

%%% Errors %%%
if nargin<4, error('Not enough input arguments.'); end
[nchant,nsamplt]=size(s_true);
[nchans,nsampls]=size(e_spat);
[nchani,nsampli]=size(e_interf);
[nchana,nsampla]=size(e_artif);
if ~((nchant==nchans)&&(nchant==nchani)&&(nchant==nchana)), error('All the components must have the same number of channels.'); end
if ~((nsamplt==nsampls)&&(nsamplt==nsampli)&&(nsamplt==nsampla)), error('All the components must have the same duration.'); end

%%% Energy ratios %%%
% SDR
SDR=10*log10(sum(sum(s_true.^2))/sum(sum((e_spat+e_interf+e_artif).^2)));
% ISR
ISR=10*log10(sum(sum(s_true.^2))/sum(sum(e_spat.^2)));
% SIR
SIR=10*log10(sum(sum((s_true+e_spat).^2))/sum(sum(e_interf.^2)));
% SAR
SAR=10*log10(sum(sum((s_true+e_spat+e_interf).^2))/sum(sum(e_artif.^2)));

return;


function X_cache=fmridesign(frametimes,slicetimes, ...
                            events,S,exclude,hrf_parameters)

%FMRIDESIGN
%
% Produces a set of design matrices, one for each slice, for fmristat.
% With just the frametimes, it gives the hemodynamic response function.
%
% X_CACHE = FMRIDESIGN( FRAME_TIMES [, SLICE_TIMES [, EVENTS , [S ,
%                       [, EXCLUDE [, HRF_PARAMETERS ]]]]] )
%
% FRAME_TIMES is a row vector of frame acquisition times in seconds.
%
% SLICE_TIMES is a row vector of relative slice acquisition times,
% i.e. absolute acquisition time of a slice is FRAME_TIMES + SLICE_TIMES.
% Default is 0.
%
% EVENTS is a matrix whose rows are events and whose columns are:
% 1. id - an integer from 1:(number of events) to identify event type;
% 2. times - start of event, synchronised with frame and slice times;
% 3. durations (optional - default is 0) - duration of event;
% 4. heights (optional - default is 1) - height of response for event.
% For each event type, the response is a box function starting at the event
% times, with the specified durations and heights, convolved with the
% hemodynamic response function (see below). If the duration is zero, the
% response is the hemodynamic response function whose integral is
% the specified height - useful for `instantaneous' stimuli such as visual
% stimuli. The response is then subsampled at the appropriate frame and slice
% times to create a design matrix for each slice, whose columns correspond
% to the event id number. EVENT_TIMES=[] will ignore event times and just
% use the stimulus design matrix S (see next). Default is [1 0].
%
% S: Events can also be supplied by a stimulus design matrix,
% whose rows are the frames, and column are the event types. Events
% are created for each column, beginning at the frame time for each row
% of S, with a duration equal to the time to the next frame, and a height
% equal to the value of S for that row and column. Note that a
% constant term is not usually required, since it is removed by the
% polynomial trend terms provided N_POLY>=0. Note that all values for
% all frames must be supplied, because smoothing and lagging by the
% hemodynamic resonse is done BEFORE excluding time points by EXCLUDE.
% Default is [].
%
% EXCLUDE is a list of frames that should be excluded from the
% analysis. This must be used with Siemens EPI scans to remove the
% first few frames, which do not represent steady-state images.
% Default is [].
%
% HRF_PARAMETERS is a matrix whose rows are 6 parameters for the
% hemodynamic response function, one row for each event type and column
% of S (if there is just one row, this is repeated as necessary).
% The hrf is modeled as the difference of two
% gamma density functions (Glover, NeuroImage, 9:416-429).
% The components of HRF_PARAMETERS are:
% 1. PEAK1: time to the peak of the first gamma density;
% 2. FWHM1: approximate FWHM of the first gamma density;
% 3. PEAK2: time to the peak of the second gamma density;
% 4. FWHM2: approximate FWHM of the second gamma density;
% 5. DIP: coefficient of the second gamma density;
%    Final hrf is:   gamma1/max(gamma1)-DIP*gamma2/max(gamma2)
%    scaled so that its total integral is 1.
% 6. FIT_SCALE: 1 - fit the time scale of the hrf by convolving its
%    derivative with the specified column of the design matrix, to create an
%    additional column for the design matrix. Dividing the effect
%    of this column by the effect of the hrf itself estimates the
%    scale shift. 0 ignores this option.
% If PEAK1=0 then there is no smoothing of that event type with the hrf.
% Default is: [5.4 5.2 10.8 7.35 0.35 0] chosen by Glover (1999) for
% an auditory stimulus.
%
% X_CACHE: A cache of the design matrices; rows are the non-excluded frames,
% columns are all the regressor variables, with slices running slowest.

%############################################################################
% COPYRIGHT:   Copyright 2000 K.J. Worsley and C. Liao,
%              Department of Mathematics and Statistics,
%              McConnell Brain Imaging Center,
%              Montreal Neurological Institute,
%              McGill University, Montreal, Quebec, Canada.
%              worsley@math.mcgill.ca, liao@math.mcgill.ca
%
%              Permission to use, copy, modify, and distribute this
%              software and its documentation for any purpose and without
%              fee is hereby granted, provided that the above copyright
%              notice appear in all copies.  The author and McGill University
%              make no representations about the suitability of this
%              software for any purpose.  It is provided "as is" without
%              express or implied warranty.
%############################################################################

% Defaults:

if nargin < 2
   slicetimes=0
end
if nargin < 3
   events=[1 0]
end
if nargin < 4
   S=[]
end
if nargin < 5
   exclude=[]
end
if nargin < 6
   hrf_parameters=[5.4 5.2 10.8 7.35 0.35 0]
end

numframes=length(frametimes);
numslices=length(slicetimes);

% Keep time points that are not excluded:

allpts = 1:numframes;
allpts(exclude) = zeros(1,length(exclude));
keep = allpts( find( allpts ) );
n=length(keep);
scantimes=frametimes(keep);

if ~isempty(events)
   numevents=size(events,1);
   eventid=events(:,1);
   numeventypes=max(eventid);
   eventime=events(:,2);
   if size(events,2)>=3
      duration=events(:,3);
   else
      duration=zeros(numevents,1);
   end
   if size(events,2)>=4
      height=events(:,4);
   else
      height=ones(numevents,1);
   end
   mineventime=min(eventime);
   maxeventime=max(eventime+duration);
else
   numeventypes=0;
   mineventime=Inf;
   maxeventime=-Inf;
end

if ~isempty(S)
   numcolS=size(S,2);
else
   numcolS=0;
end

% Set up response matrix:

dt=0.02;
  startime=min(mineventime,min(frametimes)+min([slicetimes 0]));
finishtime=max(maxeventime,max(frametimes)+max([slicetimes 0]));
numtimes=ceil((finishtime-startime)/dt)+1;
numresponses=numeventypes+numcolS;
response=zeros(numtimes,numresponses);

if ~isempty(events)
   height=height./(1+(duration==0)*(dt-1));
   for k=1:numevents
      type=eventid(k);
      n1=ceil((eventime(k)-startime)/dt)+1;
      n2=ceil((eventime(k)+duration(k)-startime)/dt)+(duration(k)==0);
      if n2>=n1
         response(n1:n2,type)=response(n1:n2,type)+height(k)*ones(n2-n1+1,1);
      end
   end
end

if ~isempty(S)
   for j=1:numcolS
      for i=find(S(:,j)')
         n1=ceil((frametimes(i)-startime)/dt)+1;
         if i<numframes
            n2=ceil((frametimes(i+1)-startime)/dt);
         else
            n2=numtimes;
         end
         if n2>=n1
            response(n1:n2,numeventypes+j)= ...
               response(n1:n2,numeventypes+j)+S(i,j)*ones(n2-n1+1,1);
         end
      end
   end
end

% Set hrf parameters:

numscale=0;
for k=1:numresponses
   if k<=size(hrf_parameters,1)
      if hrf_parameters(k,1)>0
         peak1=hrf_parameters(k,1);
         fwhm1=hrf_parameters(k,2);
         peak2=hrf_parameters(k,3);
         fwhm2=hrf_parameters(k,4);
         dip=hrf_parameters(k,5);
         alpha1=peak1^2/fwhm1^2*8*log(2);
         alpha2=peak2^2/fwhm2^2*8*log(2);
         beta1=fwhm1^2/peak1/8/log(2);
         beta2=fwhm2^2/peak2/8/log(2);

         numlags=ceil(max(peak1+2*fwhm1,peak2+2*fwhm2)/dt);
         time=(0:(numlags-1))'*dt;
         gamma1=(time/peak1).^alpha1.*exp(-(time-peak1)./beta1);
         gamma2=(time/peak2).^alpha2.*exp(-(time-peak2)./beta2);
         hrf=gamma1-dip*gamma2;
         sumhrf=sum(hrf);
         hrf=hrf/sumhrf;
         if hrf_parameters(k,6)==1
            fit_scale=1;
            d_hrf=((time/beta1-alpha1-1).*gamma1- ...
               dip*(time/beta2-alpha2-1).*gamma2);
            d_hrf=d_hrf/sumhrf;
         else
            fit_scale=0;
         end
      else
         fit_scale=0;
         hrf=1;
      end
   end
   eventmatrix(:,k)=conv2(response(:,k),hrf);
   if fit_scale==1
      numscale=numscale+1;
      eventmatrix(:,numresponses+numscale)=conv2(response(:,k),d_hrf);
   end
end

% Make all the design matrices for each slice:

numcolX=numresponses+numscale;
X_cache=zeros(n,numcolX*numslices);
for slice = 1:numslices
   subtime=floor((scantimes+slicetimes(slice)-startime)/dt)+1;
   X_cache(:,(1:numcolX)+(slice-1)*numcolX)=eventmatrix(subtime,:);
end

% End.


%%********************************************************************
%% infeaspt: generate an initial point for sdp.m
%%
%%  [X0,y0,Z0] = infeaspt(blk,At,C,b,options,scalefac);
%%
%%  options = 1  if want X0,Z0 to be scaled identity matrices
%%          = 2  if want X0,Z0 to be scalefac*(identity matrices).
%%
%% SDPT3: version 3.1
%% Copyright (c) 1997 by
%% K.C. Toh, M.J. Todd, R.H. Tutuncu
%% Last Modified: 16 Sep 2004
%%********************************************************************

   function [X0,y0,Z0] = infeaspt(blk,At,C,b,options,scalefac);
%%
   if (nargin < 5); options = 1; end;
   if (options == 1); scalefac = []; end;
   if (options == 2) & (nargin < 6); scalefac = 1000; end;
   if (scalefac <= 0); error('scalefac must a positive number'); end;
   state = rand('state');
   rand('state',0);
%%
   if ~iscell(At); At = {At}; end;
   if ~iscell(C);  C = {C}; end;
   m = length(b);
   if all(size(At) == [size(blk,1) m]);
      convertyes = zeros(size(blk,1),1);
      for p = 1:size(blk,1)
         if strcmp(blk{p,1},'s') & all(size(At{p,1}) == sum(blk{p,2}))
            convertyes(p) = 1;
         end
      end
      if any(convertyes)
         At = svec(blk,At,ones(size(blk,1),1));
      end
   end;
%%
   %%[blk,At,C,b] = validate(blk,At,C,b);
%%
   X0 = cell(size(C)); Z0 = cell(size(C));
   m = length(b);
   for p = 1:size(blk,1);
      pblk = blk(p,:);
      blktmp = pblk{2};
      n = length(C{p});
      y0 = zeros(m,1);
      b2 = 1 + abs(b');
      if (options == 1);
         if strcmp(pblk{1},'s');
            normAni = [];
            X0{p} = sparse(n,n); Z0{p} = sparse(n,n);
            ss = [0, cumsum(blktmp)];
            tt = [0, cumsum(blktmp.*(blktmp+1)/2)];
            for i = 1:length(pblk{2})
               if ~isempty(At{p,1})
                  pos = [tt(i)+1 : tt(i+1)];
                  Ai = At{p,1}(pos,:);
                  normAni = 1+sqrt(sum(Ai.*Ai));
               end
               if (length(At(p,:)) >= 2) %% for low rank constraints
                  dd = At{p,3};
                  qq = [0, cumsum(pblk{3})]; normtmp = ones(1,length(pblk{3}));
                  idxD = [0; find(diff(dd(:,1))); size(dd,1)];
                  for k = 1:length(pblk{3})
                      idx = [qq(k)+1 : qq(k+1)];
                      idx2 = [idxD(k)+1: idxD(k+1)];
                      Ak = At{p,2}(:,idx);
                      ii = dd(idx2,2)-qq(k); %% undo cumulative indexing
                      jj = dd(idx2,3)-qq(k);
                      len = pblk{3}(k);
                      Dk = spconvert([ii,jj,dd(idx2,4); len,len,0]);
                      tmp = Ak'*Ak*Dk;
                      normtmp(1,k) = 1+sqrt(sum(sum(tmp.*tmp')));
                  end
                  normAni = [normAni, normtmp];
               end
               pos = [ss(i)+1 : ss(i+1)];  ni = length(pos);
               tmp = C{p}(pos,pos);
               normCni = 1+sqrt(sum(sum(tmp.*tmp)));
               const  = 10; %%--- old: const = 1;
               constX = max([const,sqrt(ni),ni*(b2./normAni)]);
               constZ = max([const,sqrt(ni),normAni,normCni]);
               X0{p}(pos,pos) = constX*spdiags(1+1e-10*rand(ni,1),0,ni,ni);
               Z0{p}(pos,pos) = constZ*spdiags(1+1e-10*rand(ni,1),0,ni,ni);
            end
         elseif strcmp(pblk{1},'q');
            s = 1+[0, cumsum(blktmp)];
            len = length(blktmp);
            normC = 1+norm(C{p});
            normA = 1+sqrt(sum(At{p,1}.*At{p,1}));
            idenqX = zeros(sum(blktmp),1);
            idenqZ = zeros(sum(blktmp),1);
            idenqX(s(1:len)) = max([1,b2./normA])*sqrt(blktmp') ;
            idenqZ(s(1:len)) = max([sqrt(blktmp); max([normA,normC])*ones(1,len)])';
            idenqX(s(1:len)) = idenqX(s(1:len)).*(1+1e-10*rand(len,1));
            idenqZ(s(1:len)) = idenqZ(s(1:len)).*(1+1e-10*rand(len,1));
            X0{p} = idenqX;
            Z0{p} = idenqZ;
         elseif strcmp(pblk{1},'l');
            normC = 1+norm(C{p});
            normA = 1+sqrt(sum(At{p,1}.*At{p,1}));
            const = 10; %%--- old: const =1;
            constX = max([const,sqrt(n),sqrt(n)*b2./normA]);
            constZ = max([const,sqrt(n),normA,normC]);
            X0{p} = constX*(1+1e-10*rand(n,1));
            Z0{p} = constZ*(1+1e-10*rand(n,1));
         elseif strcmp(pblk{1},'u');
            X0{p} = sparse(n,1);
            Z0{p} = sparse(n,1);
         else
            error(' blk: some fields not specified correctly');
         end;
      elseif (options == 2);
         if strcmp(pblk{1},'s');
            n = sum(blktmp);
            X0{p} = scalefac*spdiags(1+1e-10*rand(n,1),0,n,n);
            Z0{p} = scalefac*spdiags(1+1e-10*rand(n,1),0,n,n);
         elseif strcmp(pblk{1},'q');
            s = 1+[0, cumsum(blktmp)];
            len = length(blktmp);
            idenq = zeros(sum(blktmp),1);
            idenq(s(1:len)) = 1+1e-10*rand(len,1);
            X0{p} = scalefac*idenq;
            Z0{p} = scalefac*idenq;
         elseif strcmp(pblk{1},'l');
            X0{p} = scalefac*(1+1e-10*rand(n,1));
            Z0{p} = scalefac*(1+1e-10*rand(n,1));
         elseif strcmp(pblk{1},'u');
            X0{p} = sparse(n,1);
            Z0{p} = sparse(n,1);
         else
            error(' blk: some fields not specified correctly');
         end
      end
   end
   rand('state',state);
%%********************************************************************


function c = tapas_kf_config
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Contains the configuration for the Kalman filter
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% The Kalman filter configuration consists of the priors of parameters and initial values. All
% priors are Gaussian in the space where the quantity they refer to is estimated. They are specified
% by their sufficient statistics: mean and variance (NOT standard deviation).
%
% Quantities are estimated in their native space if they are unbounded (e.g., mu_0). They are
% estimated in log-space if they have a natural lower bound at zero (e.g., the pi_u).
%
% Parameters can be fixed (i.e., set to a fixed value) by setting the variance of their prior to
% zero.
%
% Fitted trajectories can be plotted by using the command
%
% >> tapas_kf_plotTraj(est)
%
% where est is the stucture returned by tapas_fitModel. This structure contains the estimated
% perceptual parameters in est.p_prc and the estimated trajectories of the filter's
% representations. Their meanings are:
%
%         est.p_prc.g_0        initial value of gain
%         est.p_prc.mu_0       initial values of hidden state mean
%         est.p_prc.om         process variance
%         est.p_prc.pi_u       observation precision
%
%         est.traj.da          prediction error
%         est.traj.g           gain
%         est.traj.mu          hidden state mean
%
% Tips:
% - When analyzing a new dataset, take your inputs u and use
%
%   >> est = tapas_fitModel([], u, 'tapas_kf_config', 'tapas_bayes_optimal_config');
%
%   to determine the Bayes optimal perceptual parameters (given your current priors as defined in
%   this file here, so choose them wide and loose to let the inputs influence the result). You can
%   then use the optimal parameters as your new prior means for the perceptual parameters.
%
% - If the log-model evidence cannot be calculated because the Hessian poses problems, look at
%   est.optim.H and fix the parameters that lead to NaNs.
%
% - Your guide to all these adjustments is the log-model evidence (LME). Whenever the LME increases
%   by at least 3 across datasets, the adjustment was a good idea and can be justified by just this:
%   the LME increased, so you had a better model.
%
% --------------------------------------------------------------------------------------------------
% Copyright (C) 2016 Christoph Mathys, TNU, UZH & ETHZ
%
% This file is part of the HGF toolbox, which is released under the terms of the GNU General Public
% Licence (GPL), version 3. You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version). For further details, see the file
% COPYING or <http://www.gnu.org/licenses/>.

% Config structure
c = struct;

% Model name
c.model = 'Kalman filter';

% Sufficient statistics of Gaussian parameter priors

% PLACEHOLDER VALUES
% It is often convenient to set some priors to values
% derived from the inputs. This can be achieved by
% using placeholder values. The available placeholders
% are:
%
% 99991   Value of the first input
%         Usually a good choice for mu_0mu(1)
% 99992   Variance of the first 20 inputs
%         Usually a good choice for mu_0sa(1)
% 99993   Log-variance of the first 20 inputs
%         Usually a good choice for logsa_0mu(1), and
%         its negative, ie the log-precision of the
%         first 20 inputs, for logpiumu
% 99994   Log-variance of the first 20 inputs minus two
%         Usually a good choice for ommu(1)

% Initial gain
c.logg_0mu = 0.1;
c.logg_0sa = 1;

% Initial hidden state mean
c.mu_0mu = 99991;
c.mu_0sa = 99992;

% Process variance
c.ommu = 99993;
c.omsa = 1;

% Pi_u
% Fix this to zero (no percpeptual uncertainty) by setting
% logpiumu = -Inf; logpiusa = 0;
c.logpiumu = -99993;
c.logpiusa = 1;

% Gather prior settings in vectors
c.priormus = [
    c.logg_0mu,...
    c.mu_0mu,...
    c.ommu,...
    c.logpiumu,...
         ];

c.priorsas = [
    c.logg_0sa,...
    c.mu_0sa,...
    c.omsa,...
    c.logpiusa,...
         ];

% Model function handle
c.prc_fun = @tapas_kf;

% Handle to function that transforms perceptual parameters to their native space
% from the space they are estimated in
c.transp_prc_fun = @tapas_kf_transp;

return;


classdef CAEAD < ALGORITHM
% <multi> <real/integer/label/binary/permutation> <constrained>
% Dual-population evolutionary algorithm based on alternative evolution and degeneration
% type --- 1 --- Type of operator (1. DE 2. GA)

%------------------------------- Reference --------------------------------
% J. Zou, R. Sun, S. Yang, and J. Zheng, A dual-population algorithm based
% on alternative evolution and degeneration for solving constrained multi-
% objective optimization problems, Informaction Scinece, 2021, 239: 89-102.
%------------------------------- Copyright --------------------------------
% Copyright (c) 2023 BIMK Group. You are free to use the PlatEMO for
% research purposes. All publications which use this platform or any code
% in the platform should acknowledge the use of "PlatEMO" and reference "Ye
% Tian, Ran Cheng, Xingyi Zhang, and Yaochu Jin, PlatEMO: A MATLAB Platform
% for Evolutionary Multi-Objective Optimization [Educational Forum], IEEE
% Computational Intelligence Magazine, 2017, 12(4): 73-87".
%--------------------------------------------------------------------------

   methods
       function main(Algorithm,Problem)
           type = Algorithm.ParameterSet(1);

           %% Generate random population
           Population1 = Problem.Initialization();
           Population2 = Problem.Initialization();
           Fitness1    = CalFitness(Population1.objs,Population1.cons,0);
           Fitness2    = CalFitness(Population2.objs,Population2.cons,1e6);

           min_epsilon      = 1e-4;
           change_threshold = 1e-2;
           max_change       = 1;
           epsilon_k        = 1e8;
           tao              = 0.05;
           max_ep           = 0;
           gen              = 1;
           stage            = false;

           %% Optimization
           while Algorithm.NotTerminated(Population1)
               pop_cons2      = Population2.cons;
               cv2            = overall_cv(pop_cons2);
               population     = [Population2.decs,Population2.objs,cv2];
               Objvalues(gen) = sum(sum(Population2.objs,1));
               ep(gen)        =  epsilon_k;
               if type == 1
                   MatingPool1 = TournamentSelection(2,2*Problem.N,Fitness1);
                   MatingPool2 = TournamentSelection(2,2*Problem.N,Fitness2);
                   Offspring1  = OperatorDE(Problem,Population1,Population1(MatingPool1(1:end/2)),Population1(MatingPool1(end/2+1:end)));
                   Offspring2  = OperatorDE(Problem,Population2,Population2(MatingPool2(1:end/2)),Population2(MatingPool2(end/2+1:end)));
               elseif type == 2
                   MatingPool1 = TournamentSelection(2,Problem.N,Fitness1);
                   MatingPool2 = TournamentSelection(2,Problem.N,Fitness2);
                   Offspring1  = OperatorGAhalf(Problem,Population1(MatingPool1));
                   Offspring2  = OperatorGAhalf(Problem,Population2(MatingPool2));
               end
               [FrontNo2,~] = NDSort(Population2.objs,size(Population2.objs,1));
               NC2 = size(find(FrontNo2==1),2);
               if gen ~= 1
                   max_change = abs(Objvalues(gen)-Objvalues(gen-1));
               end
               if max_change <= change_threshold &&NC2 == Problem.N && stage == false
                   epsilon_k = max(population(:,end),[],1);
                   stage = true;
               end
               Offspring3 = [];
               if stage == true
                   if type == 1
                       Offspring3 = OperatorDE(Problem,Population1,Population2(MatingPool2(1:end/2)),Population2(MatingPool2(end/2+1:end)));
                   elseif type == 2
                       for i=1:Problem.N/2
                           Offtemp = OperatorGAhalf(Problem,[Population1(MatingPool1(i)),Population2(MatingPool2(i))]);
                           Offspring3 = [Offspring3,Offtemp];
                       end
                   end
               end
               if stage == true
                   [stage,epsilon_k] =  update_epsilon(stage,tao,epsilon_k,max_ep,min_epsilon);
               end
               if epsilon_k < 9e5
                   max_ep = max(max_ep,epsilon_k);
               end
               [Population1,Fitness1] = EnvironmentalSelection([Population1,Offspring1,Offspring2,Offspring3],Problem.N,true,0);
               [Population2,Fitness2] = EnvironmentalSelection([Population2,Offspring2],Problem.N,false,epsilon_k);
               gen = gen+1;
           end
       end
   end
end

function result = overall_cv(cv)
    cv(cv <= 0) = 0;cv = abs(cv);
    result = sum(cv,2);
end

function [stage,result] = update_epsilon(stage,tao,epsilon_k,epsilon_0,min_epsilon)
    if epsilon_k > min_epsilon
        result = (1 - tao) * epsilon_k;
        stage = true;
    else
        result = epsilon_0;
        stage = false;
    end
end


function []=panel6disp(n,N,m,p,k,T,d1,d2,d3,d4,d5,d,Ymat,Xtilde,Units,endo,exo,const,Xi,theta_median,theta_std,theta_lbound,theta_ubound,sigma_median,D_estimates,gamma_estimates,alpha0,delta0,gama,a0,b0,rho,psi,acceptrate,startdate,enddate,forecast_record,forecast_estimates,Fcperiods,stringdates3,Fstartdate,Fcenddate,Feval,Fcomp,data_endo_c,IRF,IRFt,pref,names)

% recover a point estimate (the median) of the VAR coefficients
betatilde=Xi*theta_median(:,:,end);
Btilde=reshape(betatilde,k,N*n);

% check whether the model is stationary
[stationary,eigmodulus]=bear.checkstable(betatilde,N*n,p,k);

% estimate the in-sample evaluation criteria

% obtain a point estimate thetatilde of the structural factors, which is the median
Thetatilde=reshape(theta_median,T*d,1);
% compute fitted values
Ytilde=full(reshape(Xtilde*Thetatilde,N*n,T)');
% reshape for convenience
Ytilde=reshape(Ytilde,T,n,N);
Ymat=reshape(Ymat,T,n,N);

% loop over units
for ii=1:N
% estimate the residuals for this unit
EPS(:,:,ii)=Ymat(:,:,ii)-Ytilde(:,:,ii);

% Compute then the sum of squared residuals
% compute first the RSS matrix, defined in (1.9.5)
RSS(:,:,ii)=EPS(:,:,ii)'*EPS(:,:,ii);
% retain only the diagonal elements to get the vector of RSSi values
rss(:,:,ii)=diag(RSS(:,:,ii));

% Go on calculating R2
% generate Mbar
Mbar=eye(T)-ones(T,T)/T;
% then compute the TSS matrix, defined in (1.9.8)
TSS(:,:,ii)=Ymat(:,:,ii)'*Mbar*Ymat(:,:,ii);
% generate the R2 matrix in (1.9.9)
R2(:,:,ii)=eye(n)-RSS(:,:,ii)./TSS(:,:,ii);
% retain only the diagonal elements to get the vector of R2 values
r2(:,:,ii)=diag(R2(:,:,ii));

% then calculate the adjusted R2, using (1.9.11)
R2bar(:,:,ii)=eye(n)-((T-1)/(T-k))*(eye(n)-R2(:,:,ii));
% retain only the diagonal elements to get the vector of R2bar values
r2bar(:,:,ii)=diag(R2bar(:,:,ii));

end

% estimate the forecast evaluation criteria
% first note that forecast evaluation can only be conducted if it is activated, and if there is some observable data after the beginning of the forecast
if Feval==1 && Fcomp==1

% generate the elements required for the evaluation
forecast_record=reshape(forecast_record,N*n,1);
forecast_estimates=reshape(forecast_estimates,N*n,1);
data_endo_c=reshape(data_endo_c,Fcperiods,N*n);

% compute forecast evaluation
[RMSE,MAE,MAPE,Ustat,CRPS_estimates]=bear.panel6feval(N,n,forecast_record,forecast_estimates,Fcperiods,data_endo_c);

end

% start displaying and saving the general results

% preliminary task: create and open the txt file used to save the results

filelocation=fullfile(pref.results_path, [pref.results_sub '.txt']);
fid=fopen(filelocation,'wt');

% print toolbox header

fprintf('%s\n','');
fprintf(fid,'%s\n','');

% print the list of contributors
bear.printcontributors(fid);

% print then estimation results

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

toolboxinfo='BEAR toolbox estimates';
fprintf('%s\n',toolboxinfo);
fprintf(fid,'%s\n',toolboxinfo);

time=clock;
datestring=datestr(time);
dateinfo=['Date: ' datestring(1,1:11) '   Time: ' datestring(1,13:17)];
fprintf('%s\n',dateinfo);
fprintf(fid,'%s\n',dateinfo);

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

VARtypeinfo='Panel VAR: structural factor (dynamic)';
fprintf('%s\n',VARtypeinfo);
fprintf(fid,'%s\n',VARtypeinfo);

if IRFt==1
SVARinfo='structural decomposition: none';
fprintf('%s\n',SVARinfo);
fprintf(fid,'%s\n',SVARinfo);
elseif IRFt==2
SVARinfo='structural decomposition: choleski factorisation';
fprintf('%s\n',SVARinfo);
fprintf(fid,'%s\n',SVARinfo);
elseif IRFt==3
SVARinfo='structural decomposition: triangular factorisation';
fprintf('%s\n',SVARinfo);
fprintf(fid,'%s\n',SVARinfo);
end

temp='units: ';
for ii=1:N
temp=[temp ' ' Units{ii,1} ' '];
end
unitinfo=temp;
fprintf('%s\n',unitinfo);
fprintf(fid,'%s\n',unitinfo);

temp='endogenous variables: ';
for ii=1:n
temp=[temp ' ' endo{ii,1} ' '];
end
endoinfo=temp;
fprintf('%s\n',endoinfo);
fprintf(fid,'%s\n',endoinfo);

temp='exogenous variables: ';
if const==0 && m==0
temp=[temp ' none'];
elseif const==1 && m==1
temp=[temp ' constant '];
elseif const==0 && m>0
   for ii=1:m-1
   temp=[temp ' ' exo{ii,1} ' '];
   end
elseif const==1 && m>1
temp=[temp ' constant '];
   for ii=1:m-1
   temp=[temp ' ' exo{ii,1} ' '];
   end
end
exoinfo=temp;
fprintf('%s\n',exoinfo);
fprintf(fid,'%s\n',exoinfo);

sampledateinfo=['estimation sample: ' startdate '-' enddate];
fprintf('%s\n',sampledateinfo);
fprintf(fid,'%s\n',sampledateinfo);

samplelengthinfo=['sample size (omitting initial conditions): ' num2str(T)];
fprintf('%s\n',samplelengthinfo);
fprintf(fid,'%s\n',samplelengthinfo);

laginfo=['number of lags included in regression: ' num2str(p)];
fprintf('%s\n',laginfo);
fprintf(fid,'%s\n',laginfo);

hyperparam1='hyperparameters:';
fprintf('%s\n',hyperparam1);
fprintf(fid,'%s\n',hyperparam1);

hyperparam2=['IG shape on residual variance (alpha0):         ' num2str(alpha0)];
fprintf('%s\n',hyperparam2);
fprintf(fid,'%s\n',hyperparam2);

hyperparam3=['IG scale on residual variance (delta0):         ' num2str(delta0)];
fprintf('%s\n',hyperparam3);
fprintf(fid,'%s\n',hyperparam3);

hyperparam4=['AR coefficient on residual variance (gamma):    ' num2str(gama)];
fprintf('%s\n',hyperparam4);
fprintf(fid,'%s\n',hyperparam4);

hyperparam5=['IG shape on factor variance (a0):               ' num2str(a0)];
fprintf('%s\n',hyperparam5);
fprintf(fid,'%s\n',hyperparam5);

hyperparam6=['IG scale on factor variance (b0):               ' num2str(b0)];
fprintf('%s\n',hyperparam6);
fprintf(fid,'%s\n',hyperparam6);

hyperparam7=['AR coefficient on factors (rho):                ' num2str(rho)];
fprintf('%s\n',hyperparam7);
fprintf(fid,'%s\n',hyperparam7);

hyperparam8=['variance of Metropolis draw (psi):              ' num2str(psi)];
fprintf('%s\n',hyperparam8);
fprintf(fid,'%s\n',hyperparam8);

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% Metropolis Hastings summary

MHinfo1=['The acceptance rate of the Metropolis-Hastings step during the Gibbs sampler was ' num2str(acceptrate,'%.2f') '%.'];
fprintf('%s\n',MHinfo1);
fprintf(fid,'%s\n',MHinfo1);

MHinfo2='A good acceptance rate is typically comprised between 20% and 30%.';
fprintf('%s\n',MHinfo2);
fprintf(fid,'%s\n',MHinfo2);

if acceptrate<=20

MHinfo3='Your acceptance rate is therefore low, which implies that some values may be repeated';
fprintf('%s\n',MHinfo3);
fprintf(fid,'%s\n',MHinfo3);
MHinfo4='in the estimation of the posterior distribution for Z.';
fprintf('%s\n',MHinfo4);
fprintf(fid,'%s\n',MHinfo4);
MHinfo5='Your may consider decreasing the value of psi in order to remedy to this situation.';
fprintf('%s\n',MHinfo5);
fprintf(fid,'%s\n',MHinfo5);

elseif acceptrate>=30

MHinfo3='Your acceptance rate is therefore high, which implies that only a small portion of the';
fprintf('%s\n',MHinfo3);
fprintf(fid,'%s\n',MHinfo3);
MHinfo4='support of the posterior distribution of Z may have been covered by the algorithm.';
fprintf('%s\n',MHinfo4);
fprintf(fid,'%s\n',MHinfo4);
MHinfo5='Your may consider increasing the value of psi in order to remedy to this situation.';
fprintf('%s\n',MHinfo5);
fprintf(fid,'%s\n',MHinfo5);

elseif (acceptrate>20 && acceptrate<30)

MHinfo3='This is satisfied in this case. Hence, the value of psi that has been selected seems';
fprintf('%s\n',MHinfo3);
fprintf(fid,'%s\n',MHinfo3);
MHinfo4='suitable for an efficient estimation of the posterior distribution of Z.';
fprintf('%s\n',MHinfo4);
fprintf(fid,'%s\n',MHinfo4);

end

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% display factor estimates

factorinfo=['Structural factors (final sample period):'];
fprintf('%s\n',factorinfo);
fprintf(fid,'%s\n',factorinfo);

fprintf('%s\n','');
fprintf(fid,'%s\n','');

factorheader=fprintf('%35s %15s %15s %15s %15s\n','','Median','St.dev','Low.bound','Upp.bound');
factorheader=fprintf(fid,'%35s %15s %15s %15s %15s\n','','Median','St.dev','Low.bound','Upp.bound');

% common component

fprintf('%s\n','theta1 (common component)');
fprintf(fid,'%s\n','theta1 (common component)');
values=[theta_median(1,1,end) theta_std(1,1,end) theta_lbound(1,1,end) theta_ubound(1,1,end)];
fprintf('%-35s %15.3f %15.3f %15.3f %15.3f\n','common component',values);
fprintf(fid,'%-35s %15.3f %15.3f %15.3f %15.3f\n','common component',values);
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% unit component

fprintf('%s\n','theta2 (unit-specific component)');
fprintf(fid,'%s\n','theta2 (unit component)');
for ii=1:d2
values=[theta_median(d1+ii,1,end) theta_std(d1+ii,1,end) theta_lbound(d1+ii,1,end) theta_ubound(d1+ii,1,end)];
fprintf('%-35s %15.3f %15.3f %15.3f %15.3f\n',['unit ' int2str(ii) ' component'],values);
fprintf(fid,'%-35s %15.3f %15.3f %15.3f %15.3f\n',['unit ' int2str(ii) ' component'],values);
end
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% variable component

fprintf('%s\n','theta3 (variable-specific component)');
fprintf(fid,'%s\n','theta3 (endogenous variable component)');
for ii=1:d3
values=[theta_median(d1+d2+ii,1,end) theta_std(d1+d2+ii,1,end) theta_lbound(d1+d2+ii,1,end) theta_ubound(d1+d2+ii,1,end)];
fprintf('%-35s %15.3f %15.3f %15.3f %15.3f\n',['variable ' int2str(ii) ' component'],values);
fprintf(fid,'%-35s %15.3f %15.3f %15.3f %15.3f\n',['variable ' int2str(ii) ' component'],values);
end
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% lag component (if applicable)

if d4~=0
fprintf('%s\n','theta4 (lag-specific component)');
fprintf(fid,'%s\n','theta4 (lag component)');
for ii=1:d4
values=[theta_median(d1+d2+d3+ii,1,end) theta_std(d1+d2+d3+ii,1,end) theta_lbound(d1+d2+d3+ii,1,end) theta_ubound(d1+d2+d3+ii,1,end)];
fprintf('%-35s %15.3f %15.3f %15.3f %15.3f\n',['lag ' int2str(ii) ' component'],values);
fprintf(fid,'%-35s %15.3f %15.3f %15.3f %15.3f\n',['lag ' int2str(ii) ' component'],values);
end
fprintf('%s\n','');
fprintf(fid,'%s\n','');
end

% exogenous component (if applicable)

if d5~=0
fprintf('%s\n','theta5 (exogenous variable component)');
fprintf(fid,'%s\n','theta5 (exogenous component)');
% initiate equation count
eqcount=0;
% initiate exogenous count
exocount=0;
for ii=1:d5
   if exocount==m
   exocount=0;
   end
   if exocount==0
   eqcount=eqcount+1;
   end
exocount=exocount+1;
values=[theta_median(d1+d2+d3+d4+ii,1,end) theta_std(d1+d2+d3+d4+ii,1,end) theta_lbound(d1+d2+d3+d4+ii,1,end) theta_ubound(d1+d2+d3+d4+ii,1,end)];
fprintf('%-35s %15.3f %15.3f %15.3f %15.3f\n',['equation ' int2str(eqcount) ', exogenous ' int2str(exocount) ' component'],values);
fprintf(fid,'%-35s %15.3f %15.3f %15.3f %15.3f\n',['equation ' int2str(eqcount) ', exogenous ' int2str(exocount) ' component'],values);
end
fprintf('%s\n','');
fprintf(fid,'%s\n','');
end

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% display VAR stability results
eigmodulus=reshape(eigmodulus,p,N*n);
stabilityinfo1=['Roots (modulus) of the characteristic polynomial for the final sample period:'];
fprintf('%s\n',stabilityinfo1);
fprintf(fid,'%s\n',stabilityinfo1);
for jj=1:p
temp=num2str(eigmodulus(jj,1),'%.3f');
   for kk=2:N*n
   temp=[temp,'  ',num2str(eigmodulus(jj,kk),'%.3f')];
   end
fprintf('%s\n',temp);
fprintf(fid,'%s\n',temp);
end
if stationary==1;
stabilityinfo2=['No root lies outside the unit circle.'];
stabilityinfo3=['The estimated VAR model satisfies the stability condition'];
fprintf('%s\n',stabilityinfo2);
fprintf(fid,'%s\n',stabilityinfo2);
fprintf('%s\n',stabilityinfo3);
fprintf(fid,'%s\n',stabilityinfo3);
else
stabilityinfo2=['Warning: at leat one root lies on or outside the unit circle.'];
stabilityinfo3=['The estimated VAR model will not be stable'];
end

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% display posterior for sigma
% reshape sigma
sigma_median=reshape(sigma_median(:,:,end),N*n,N*n);
% start displaying
sigmainfo=['sigma (residual covariance matrix): posterior estimates for the final sample period'];
fprintf('%s\n',sigmainfo);
fprintf(fid,'%s\n',sigmainfo);
% calculate the (integer) length of the largest number in sigma, for formatting purpose
width=length(sprintf('%d',floor(max(abs(bear.vec(sigma_median))))));
% add a separator, a potential minus sign, and three digits (total=5) to obtain the total space for each entry in the matrix
width=width+5;
for ii=1:N*n
temp=[];
   for jj=1:N*n
   % convert matrix entry into string
   number=num2str(sigma_median(ii,jj),'% .3f');
      % pad potential missing blanks
      while numel(number)<width
      number=[' ' number];
      end
   number=[number '  '];
   temp=[temp number];
   end
fprintf('%s\n',temp);
fprintf(fid,'%s\n',temp);
end

fprintf('%s\n','');
fprintf(fid,'%s\n','');

% then display the results for D and gamma, if a structural decomposition was selected

if IRF==1 && IRFt~=1
svarinfo1=['D (structural decomposition matrix): posterior estimates'];
fprintf('%s\n',svarinfo1);
fprintf(fid,'%s\n',svarinfo1);

% recover D
D=reshape(D_estimates(:,:,end),N*n,N*n);
% calculate the (integer) length of the largest number in D, for formatting purpose
width=length(sprintf('%d',floor(max(abs(bear.vec(D))))));
% add a separator, a potential minus sign and three digits (total=5) to obtain the total space for each entry in the matrix
width=width+5;
for ii=1:N*n
temp=[];
   for jj=1:N*n
   % convert matrix entry into string
   number=num2str(D(ii,jj),'% .3f');
      % pad potential missing blanks
      while numel(number)<width
      number=[' ' number];
      end
   number=[number '  '];
   temp=[temp number];
   end
fprintf('%s\n',temp);
fprintf(fid,'%s\n',temp);
end

fprintf('%s\n','');
fprintf(fid,'%s\n','');

svarinfo2=['gamma (structural disturbances covariance matrix): posterior estimates'];
fprintf('%s\n',svarinfo2);
fprintf(fid,'%s\n',svarinfo2);

% recover gamma
gamma=reshape(gamma_estimates(:,:,end),N*n,N*n);
% calculate the (integer) length of the largest number in D, for formatting purpose
width=length(sprintf('%d',floor(max(abs(bear.vec(D))))));
% add a separator, a potential minus sign and three digits (total=5) to obtain the total space for each entry in the matrix
width=width+5;
for ii=1:N*n
temp=[];
   for jj=1:N*n
   % convert matrix entry into string
   number=num2str(gamma(ii,jj),'% .3f');
      % pad potential missing blanks
      while numel(number)<width
      number=[' ' number];
      end
   number=[number '  '];
   temp=[temp number];
   end
fprintf('%s\n',temp);
fprintf(fid,'%s\n',temp);
end

end

fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');
fprintf('%s\n','');
fprintf(fid,'%s\n','');

% finally display in-sample and forecast evaluation measures
% if forecast evaluation is activated and possible, display the results
if Feval==1 && Fcomp==1
bear.panel6fprint(Units,N,n,endo,rss,r2,r2bar,RMSE,MAE,MAPE,Ustat,CRPS_estimates,stringdates3,Fstartdate,Fcenddate,Fcperiods,fid);

% if forecast evaluation is activated but not possible, return a message to signal it
elseif Feval==1 && Fcomp==0

finfo1=['Forecast evaluation cannot be conducted.'];
fprintf('%s\n',finfo1);
fprintf(fid,'%s\n',finfo1);
finfo2=['Forecasts start in ' Fstartdate ', while observable data is available only until ' names{end,1} '.'];
fprintf('%s\n',finfo2);
fprintf(fid,'%s\n',finfo2);
finfo3=['To obtain forecast evaluation, the forecast start date must be anterior to the end of the data set.'];
fprintf('%s\n',finfo3);
fprintf(fid,'%s\n',finfo3);

% if forecast evaluation is not activated altogether, do not do anything

end