function [GCM,gen] = spm_dcm_simulate(GCM, mode, noise, gen_idx)
% Populate the given group DCM array (GCM) with simulated data
% FORMAT [GCM,gen] = spm_dcm_simulate(GCM, mode, noise, gen_idx)
% 
% If each subject has M models, any one of these M can be chosen to be the
% generative model, and all models for the same subject will be assigned
% the same simulated data.
%
% GCM  - subjects x model cell array where the Ep structure contains
%        connection strengths
%
% mode - zero-mean Gaussian noise is added, defined by one of:
%        'SNR_var' - signal-to-noise ratio based on the variance
%        'SNR_std' - signal-to-noise ratio based on the standard deviation
%        'var'     - variance of the observation noise to be added
%        'Ce'      - picks up the log noise precision from GCM{x}.Ce
%                   [default]
%
% noise - real-valued added noise (interpretation depends on mode, above)
%         if mode is set to 'hE' then this can be empty
%
% gen_idx - index of the generative model
%
% Returns:
%
% GCM  - DCM array populated with simulated data
% gen  - vector of generative models for each subject
%
% Example:
% DCM = spm_dcm_simulate(GCM, 'SNR_std', 1);
%__________________________________________________________________________

% Peter Zeidman, Vladimir Litvak
% Copyright (C) 2016-2022 Wellcome Centre for Human Neuroimaging


% Check parameters and load specified DCM
%--------------------------------------------------------------------------
GCM = spm_dcm_load(GCM);

if nargin < 2 || isempty(mode),    mode = 'ce';    end
if nargin < 3 || isempty(noise),   noise = 1;        end
if nargin < 4 || isempty(gen_idx), gen_idx = 1;      end

model = spm_dcm_identify(GCM{1,1});

switch model
    case 'fMRI'
        [GCM, gen] = simulate_fmri(GCM, mode, noise, gen_idx);
    case 'ERP'
        [GCM, gen] = simulate_erp(GCM, mode, noise, gen_idx);
    otherwise
        error('spm_dcm_simulate not yet implemented for this type of DCM');
end
end
%--------------------------------------------------------------------------
function [GCM, gen] = simulate_fmri(GCM, mode, noise, gen_idx)
% Simulate deterministic DCM for fMRI (task-based)

[ns, nm] = size(GCM);

gen = cell(ns,1);

% Create spurious ROI information for compatibility
for i=1:GCM{1}.n
    str         = sprintf('R%d',i);
    xY(i).name  = str;
    xY(i).xyz   = [i i i]'*10;
    xY(i).XYZmm = [i i i]'*10;
    xY(i).s     = 1;
    xY(i).spec  = 1;
    xY(i).Sess  = 1;
    xY(i).u     = 1;
    xY(i).X0    = [];
end

graphics = false;

for s = 1:ns
    DCM = GCM{s,gen_idx};
    
    % Generate data
    switch lower(mode)
        case 'snr_std'
            SNR = noise;
            [Y,x,DCM_gen] = spm_dcm_generate(DCM,SNR,graphics);
        case 'snr_var'
            SNR = sqrt(noise);
            [Y,x,DCM_gen] = spm_dcm_generate(DCM,SNR,graphics);
        case 'var'
            [Y,x,DCM_gen] = spm_dcm_generate(DCM,Inf,graphics);
            
            e = sqrt(noise) .* randn(size(DCM_gen.Y.y));
            DCM_gen.Y.y = DCM_gen.y + e;
        case 'ce'
            [Y,x,DCM_gen] = spm_dcm_generate(DCM,Inf,graphics);
            
            e = randn(size(DCM_gen.Y.y)) * sqrtm(diag(DCM.Ce));
            DCM_gen.Y.y = DCM_gen.y + e;
        otherwise
            error('Unknown noise definition');
    end
        
    for i = 1:nm
        % Store simulated timeseries
        GCM{s,i}.Y = DCM_gen.Y;
        
        % Store ROI structure
        if ~isfield(GCM{s,i},'xY')
            GCM{s,i}.xY = xY;
        end
    end
    
    % Store generative model
    gen{s} = DCM_gen;
end
end

%--------------------------------------------------------------------------
function [GCM, gen] = simulate_erp(GCM, mode, noise, gen_idx)
% Simulate deterministic DCM for ERP

[ns, nm] = size(GCM);

gen = cell(ns,1);

for i = 1:ns
    DCM = GCM{i,gen_idx};     
     
    DCM.options.DATA = 0;
    
    if isfield(DCM, 'Eg')
        Eg = DCM.Eg;
        Ce = DCM.Ce;
    elseif isfield(GCM{i, 1}, 'Eg')
        Eg = GCM{i, 1}.Eg;
        Ce = GCM{i, 1}.Ce;
        
        M = DCM.M;
        DCM.M = GCM{i, 1}.M;
        
        fields = fieldnames(M);
        for j = 1:numel(fields)
            DCM.M.(fields{j}) = M.(fields{j});
        end       
    else
        error('Full inverted model expected in the first column of the input.');
    end       
    
    DCM   = spm_dcm_erp_dipfit(DCM,1);
    
    % report
    %----------------------------------------------------------------------
    fprintf('Creating subject %i\n',i)
    
    
    % generate data
    %----------------------------------------------------------------------
    G   = feval(DCM.M.G, Eg, DCM.M);
    U   = DCM.M.U;
    R   = DCM.xY.R;
    x   = feval(DCM.M.IS, DCM.Ep, DCM.M, DCM.xU);    
    for c = 1:length(x)
        y{c} = x{c}*G'*U;
                
        e    = randn(size(y{c}));
                
        switch lower(mode)
            case 'snr_std'
                e    = spm_conv(e,8,0);
                e    = e*noise*mean(std(y{c}))/mean(std(e));
            case 'snr_var'
                e    = spm_conv(e,8,0);
                e    = e*noise*mean(var(y{c}))/mean(var(e));                
            case 'var'
                e    = spm_conv(e,8,0);
                e    = e*sqrt(noise);
            case 'estimated'
                e = sqrtm(full(Ce))*e;
            otherwise
                error('Unknown noise definition');
        end
         
        y{c} = y{c} + e;
        y{c} = R*y{c}*spm_pinv(full(U));
    end
    
    % specify models
    %----------------------------------------------------------------------
    for j = 1:nm
        GCM{i,j}          = rmfield(DCM,'M');
        GCM{i,j}.M.dipfit = DCM.M.dipfit;
        GCM{i,j}.M.U      = U;
        GCM{i,j}.xY.y     = y;
    end    
    
    % Store generative model
    gen{i} = DCM;
end
end
