function DCM = spm_dcm_tfm(DCM)
% Estimate parameters of a DCM of (induced) cross-spectral density
% FORMAT DCM = spm_dcm_tfm(DCM)
%
% DCM
%    name: name string
%       xY: data   [1x1 struct]
%       xU: design [1x1 struct]
%
%   Sname: cell of source name strings
%       A: {[nr x nr double], [nr x nr double], ...}
%       B: {[nr x nr double], ...}   Connection constraints
%       C: [nr x 1 double]
%
%   options.Nmodes       - number of spatial modes
%   options.h            - order of (DCT) detrending
%   options.Tdcm         - [start end] time window in ms
%   options.Fdcm         - [start end] Frequency window in Hz
%   options.D            - time bin decimation       (usually 1 or 2)
%   options.spatial      - 'ECD', 'LFP' or 'IMG'     (see spm_erp_L)
%
% Returns:
%
% sensor space
%--------------------------------------------------------------------------
% DCM.csd;                  % conditional cross-spectral density
% DCM.tfm;                  % conditional induced responses
% DCM.dtf;                  % conditional directed transfer functions
% DCM.erp;                  % conditional evoked responses
% DCM.Qu;                   % conditional neuronal responses
% DCM.pst;                  % peristimulus times
% DCM.Hz;                   % frequencies
%
% store estimates in DCM
%--------------------------------------------------------------------------
% DCM.Ep;                   % conditional expectation - parameters
% DCM.Cp;                   % conditional covariance  - parameters
% DCM.Pp;                   % conditional probability - parameters
% DCM.Ce;                   % error covariance
% DCM.F;                    % Laplace log evidence
% DCM.ID;                   % data ID
%
% source space
%--------------------------------------------------------------------------
% DCM.CSD;                  % conditional cross-spectral density
% DCM.TFM;                  % conditional induced responses
% DCM.DTF;                  % conditional directed transfer functions
% DCM.ERP;                  % conditional evoked responses
%__________________________________________________________________________
 
% Karl Friston
% Copyright (C) 2005-2022 Wellcome Centre for Human Neuroimaging
 
 
% check options and preliminaries
%==========================================================================
drawnow
clear functions
try, DCM = rmfield(DCM,'M'); end
name     = sprintf('DCM_%s',date);
DCM.options.analysis  = 'TFM';
 
% Filename and options
%--------------------------------------------------------------------------
try, name  = DCM.name;            catch, DCM.name = name;  end
try, Nm    = DCM.options.Nmodes;  catch, Nm       = 8;     end
try, onset = DCM.options.onset;   catch, onset    = 60;    end
try, dur   = DCM.options.dur;     catch, dur      = 16;    end
try, h     = DCM.options.h;       catch, h        = 2;     end

 
% Design model and exogenous inputs
%==========================================================================
if isempty(DCM.xU.X), DCM.xU.X = sparse(1,0); end
 
 
% Spatial model
%==========================================================================
model              = 'TFM';
DCM.options.Nmodes = Nm;
DCM.options.model  = model;
DCM.options.Nmax   = 32;
DCM.options.h      = h;
DCM.M.dipfit.model = model;
DCM.M.N            = 2^8;
DCM.M.ds           = 8;

 
% Get posterior (and dipfit) from event-related responses
%==========================================================================
ERP            = DCM;
[pth,name]     = fileparts(DCM.name);
ERP.name       = fullfile(pth,[name '_erp']);
ERP.M.ds       = 0;
ERP.M.hE       = 6;
ERP.M.hC       = 1/128;
[ERP,dipfit]   = spm_dcm_erp(ERP);

%-Feature selection using principal components (U) of lead-field
%==========================================================================
try, DCM.M = rmfield(DCM.M,'TFM'); end
clear functions

DCM.xY         = ERP.xY;
DCM.M.U        = ERP.M.U;
DCM.M.dipfit   = dipfit;
DCM            = spm_dcm_tfm_data(DCM);
 
% Use posterior as the prior in a model of induced responses
%==========================================================================

% remove very precise modes from neuronal priors
%--------------------------------------------------------------------------
[u s]     = spm_svd(ERP.Cp);
i         = find(diag(s) >= 1/32);
Cp        = u(:,i)*s(i,i)*u(:,i)';
 
% prior moments on parameters (neuronal and spatial)
%--------------------------------------------------------------------------
pE        = spm_dcm_neural_priors(DCM.A,DCM.B,DCM.C,model);
pE        = spm_L_priors(DCM.M.dipfit,pE);
pE        = spm_unvec(spm_vec(ERP.Ep,ERP.Eg),pE);
 
% prior moments on parameters (spectral)
%--------------------------------------------------------------------------
[pE,pC]   = spm_ssr_priors(pE);
pC        = spm_cat(spm_diag({Cp,ERP.Cg,diag(spm_vec(pC))}));
 
% initial states and equations of motion
%--------------------------------------------------------------------------
[x,f,h]   = spm_dcm_x_neural(pE,model);
 
% orders and model
%==========================================================================
nx        = length(spm_vec(x));
nu        = size(pE.C,2);

% hyperpriors (slightly less precise than the ERP)
%--------------------------------------------------------------------------
hE        = 6;
hC        = 1/512;
 
% create DCM
%--------------------------------------------------------------------------
% DCM.M.FS  = 'spm_csd_sel';
DCM.M.IS  = 'spm_csd_int';
DCM.M.g   = 'spm_gx_erp';
DCM.M.f   = f;
DCM.M.h   = h;
DCM.M.x   = x;
DCM.M.n   = nx;
DCM.M.m   = nu;
DCM.M.pE  = pE;
DCM.M.pC  = pC;
DCM.M.hE  = hE;
DCM.M.hC  = hC;
DCM.M.ns  = length(DCM.xY.pst);
 
% solve for steady state
%--------------------------------------------------------------------------
DCM.M.x   = spm_dcm_neural_x(pE,DCM.M);
 
% within-trial effects: adjust onset relative to PST
%--------------------------------------------------------------------------
DCM.M.ons = onset - DCM.xY.pst(1);
DCM.M.dur = dur;
DCM.xU.dt = DCM.xY.dt;
 
 
% complete model specification and invert
%==========================================================================
Ns        = length(DCM.A{1});                   % number of sources
Nm        = size(DCM.M.U,2);                    % number of spatial modes
Nf        = length(DCM.xY.Hz);                  % number of frequency bins
Nb        = length(DCM.xY.pst);                 % number of time bins
Nt        = length(DCM.xY.csd);                 % number of trial types
DCM.M.l   = Nm;
DCM.M.Hz  = DCM.xY.Hz;
DCM.M.Rft = DCM.xY.Rft;
DCM.M.pst = DCM.xY.pst;


% precision of noise
%--------------------------------------------------------------------------
Qt        = spm_Q(1/2,Nb,1);
Qf        = spm_Q(1/2,Nf,1);
DCM.xY.Q  = kron(Qf,Qt);
DCM.xY.X0 = sparse(Nt*Nm*Nm*Nf*Nb,0);

 
% Inspect stability
%++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
if 0
    [csd,erp] = spm_csd_int(pE,DCM.M,DCM.xU);
    xY.erp    = erp;
    xY.csd    = csd;
    spm_figure('GetWin','predicted (a priori)')
    spm_dcm_tfm_response(    xY,DCM.xY.pst,DCM.xY.Hz)
    spm_figure('GetWin','empirical time-frequency responses')
    spm_dcm_tfm_response(DCM.xY,DCM.xY.pst,DCM.xY.Hz)
    
    keyboard
end
%++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
% Variational Laplace: model inversion
%==========================================================================
DCM.M.Nmax   = 32;
[Qp,Cp,Eh,F] = spm_nlsi_GN(DCM.M,DCM.xU,DCM.xY);
DCM.M.ds     = 1;


% Re-estimate spatial (lead field) parameters for ERP
%==========================================================================
% clear functions
% ERP.M.Nmax   = 1;
% ERP.M.pE     = spm_unvec(spm_vec(DCM.Ep),ERP.M.pE);
% ERP.M.pC     = spm_unvec(spm_vec(ERP.M.pC)*0,ERP.M.pC);
% ERP          = spm_dcm_erp(ERP);
% clear functions
% 
% % and update posterior expectation
% %------------------------------------------------------------------------
% f     = fieldnames(DCM.Eg);
% for i = 1:numel(f)
%     Qp = setfield(Qp,f{i},getfield(ERP.Eg,f{i}));
% end

 
% Data ID
%--------------------------------------------------------------------------
ID  = spm_data_id(DCM.xY.y);
 
 
% Bayesian inference {threshold = prior} NB Prior on A,B and C = exp(0) = 1
%--------------------------------------------------------------------------
warning('off','SPM:negativeVariance');
dp  = spm_vec(Qp) - spm_vec(pE);
Pp  = spm_unvec(1 - spm_Ncdf(0,abs(dp),diag(Cp)),Qp);
warning('on', 'SPM:negativeVariance');
 
 
% predictions (csd and erp) - sensor space
%==========================================================================
[csd,erp,tfm,dtf,Hz,pst,x] = spm_csd_int(Qp,DCM.M,DCM.xU);
 
% sensor space
%--------------------------------------------------------------------------
DCM.csd = csd;                  % conditional cross-spectral density
DCM.tfm = tfm;                  % conditional induced responses
DCM.dtf = dtf;                  % conditional directed transfer functions
DCM.erp = erp;                  % conditional evoked responses
DCM.Qu  = x;                    % conditional neuronal responses
DCM.pst = pst;                  % peristimulus times (seconds)
DCM.Hz  = Hz;                   % frequencies
 
% store estimates in DCM
%--------------------------------------------------------------------------
DCM.Ep  = Qp;                   % conditional expectation - parameters
DCM.Cp  = Cp;                   % conditional covariance  - parameters
DCM.Pp  = Pp;                   % conditional probability - parameters
DCM.Ce  = exp(-Eh);             % error covariance
DCM.F   = F;                    % Laplace log evidence
DCM.ID  = ID;                   % data ID
 
% predictions (source space) - cf, a LFP from virtual electrode
%==========================================================================
M             = rmfield(DCM.M,'U');
M.dipfit.type = 'LFP';
clear functions
 
Qp.L    = ones(1,Ns);           % set virtual electrode gain to unity
Qp.b    = Qp.b - 32;            % and suppress non-specific and
Qp.c    = Qp.c - 32;            % specific channel noise
M.pE    = Qp;
M.l     = Ns;

[csd,erp,tfm,dtf] = spm_csd_int(Qp,M,DCM.xU);
 
% source space
%--------------------------------------------------------------------------
DCM.CSD = csd;                  % conditional cross-spectral density
DCM.TFM = tfm;                  % conditional induced responses
DCM.DTF = dtf;                  % conditional directed transfer functions
DCM.ERP = erp;                  % conditional evoked responses

save(DCM.name, 'DCM', spm_get_defaults('mat.format'));
