function [CSD,ERP,csd,mtf,w,pst,x,dP] = spm_csd_int(P,M,U)
% Time frequency response of a neural mass model
% FORMAT [CSD,ERP,csd,mtf,w,pst,x,dP] = spm_csd_int(P,M,U)
%        ERP = spm_csd_int(P,M,U): M.ds = 0
%
% P    - parameters
% M    - neural mass model structure
% M.ds - down-sampling for comutational efficiency (default = 1)
%        if ~M.ds then the ERP is returned
% U    - time-dependent input
%
% ERP  - {E(t,nc}}      - event-related average (sensor space)
% CSD  - {Y(t,w,nc,nc}} - cross-spectral density for nc channels {trials}
%                       - for w frequencies over time t in M.Hz
% csd  - {G(t,w,nc,nc}} - cross spectrum density (before sampling)
% mtf  - {S(t,w,nc,nu}} - transfer functions
% w    - frequencies
% pst  - peristimulus time (sec)
% x    - expectation of hidden (neuronal) states (for last trial)
% dP   - {dP(t,np)}        - parameter fluctuations (plasticity)
%__________________________________________________________________________
%
% This integration routine evaluates the responses of a neural mass model
% to exogenous input - in terms of neuronal states. These are then used as
% expansion point to generate complex cross spectral responses due to
% random neuronal fluctuations. The ensuing spectral (induced) response is
% then convolved (in time) with a window that corresponds to the window of
% a standard wavelet transform. In other words, this routine generates
% predictions of data features based upon a wavelet transform
% characterisation of induced responses.
%
% If M.analysis = 'ERP' then only the ERP is evaluated
%__________________________________________________________________________
% Copyright (C) 2012-2013 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_csd_int.m 6254 2014-11-04 18:24:21Z karl $


% check input - default: one trial (no between-trial effects)
%--------------------------------------------------------------------------
if nargin < 3
    U.dt = 1/256;
    U.u  = sparse(1,M.m);
    U.X  = sparse(1,0);
end


% equations of motion (and OPT for computing CSD vs. ERP)
%--------------------------------------------------------------------------
if ~isfield(M,'g'),   M.g = @(x,u,P,M) x;      end
if ~isfield(M,'h'),   M.h = @(x,u,P,M) 0;      end

M.f = spm_funcheck(M.f);
M.g = spm_funcheck(M.g);
M.h = spm_funcheck(M.h);

% check input function  u = f(t,P,M)
%--------------------------------------------------------------------------
try, ds   = M.ds;   catch, ds    = 1;           end
try, fu   = M.fu;   catch, fu    = 'spm_erp_u'; end
try, ns   = M.ns;   catch, ns    = 128;         end
try, wnum = M.Rft;  catch, wnum  = 8;           end
try, dt   = U.dt;   catch, dt    = 1/256;       end


% within-trial (exogenous) inputs
%==========================================================================
if ~isfield(U,'u')
    u = feval(fu,(1:ns)*dt,P,M)';
else
    u = U.u';
end

% peristimulus time
%--------------------------------------------------------------------------
ns   = size(u,2);
pst  = (1:ns)*dt;
M.dt = dt;

% between-trial (experimental) inputs
%==========================================================================
try
    X = U.X;
    if ~size(X,1)
        X = sparse(1,0);
    end
catch
    X = sparse(1,0);
end

% number of endogenous inputs and hidden states
%==========================================================================
nu    = length(P.A{1});
nx    = M.n;
nc    = size(X,1);
dP    = cell(nc,1);
mtf   = cell(nc,1);
csd   = cell(nc,1);
CSD   = cell(nc,1);
ERP   = cell(nc,1);

% cycle over trials or conditions
%--------------------------------------------------------------------------
for c = 1:size(X,1)
    
    % condition-specific parameters
    %----------------------------------------------------------------------
    Q = spm_gen_Q(P,X(c,:));
    
    % initialise hidden states
    %----------------------------------------------------------------------
    x = spm_vec(spm_dcm_neural_x(Q,M));
        
    % remove state (X) and input (Y) dependent parameter from Q
    %----------------------------------------------------------------------
    if isfield(Q,'X'), Q = rmfield(Q,'X'); end
    if isfield(Q,'Y'), Q = rmfield(Q,'Y'); end
    
    
    % get local linear operator LL and delay operator D
    %======================================================================
    if nargout(M.f) >= 3
        [f0,dfdx,D] = M.f(x(:,1),u(:,1),Q,M);
        
    elseif nargout(M.f) == 2
        [f0,dfdx]   = M.f(x(:,1),u(:,1),Q,M);
        D           = 1;
        
    else
        dfdx        = spm_diff(M.f,x(:,1),u(:,1),Q,M,1);
        D           = 1;
    end
    
    % save Delay operator in M to speed computations
    %----------------------------------------------------------------------
    M.D   = D;
    
    % get local linear (Lie) operator L
    %----------------------------------------------------------------------
    p     = max(abs(real(eig(full(dfdx)))));
    N     = ceil(max(1,dt*p*2));
    L     = (spm_expm(dt*D*dfdx/N) - speye(nx,nx))*spm_inv(dfdx);
    
    % cycle over time - expanding around expected states and input
    %======================================================================
    R     = Q;
    Q     = spm_vec(Q);
    dR    = zeros(size(Q));
    dX    = dR;
    dU    = dR;
    
    % clear persistent variables and solve
    %------------------------------------------------------------------
    clear(char(M.h))
    for i = 1:length(pst)
        
        % hidden states
        %------------------------------------------------------------------
        if i > 1, x(:,i) = x(:,i - 1); end
        
        
        % state-dependent parameters (and plasticity)
        %==================================================================
        if isfield(P,'X'), dU  = P.X*u(:,i);             end
        if isfield(P,'Y'), dX  = P.Y*x(:,i);             end
        if isfield(M,'h'), dR  = M.h(x(:,i),u(:,i),P,M); end
        
        % update and save it necessary
        %------------------------------------------------------------------
        dQ = dR + dU + dX;
        R  = spm_unvec(Q + dQ,R);
        
        if nargout > 7
            dP{c}(:,i) = dQ;
        end
        
        % compute complex cross spectral density
        %==================================================================
        if ds
            if ~rem(i + ds - 1,ds)
            
                % add exogenous input and expand around current states
                %----------------------------------------------------------
                M.u     = sparse(nu,1);
                M.x     = spm_unvec(x(:,i),M.x);
                [g,w,s] = spm_csd_mtf(R,M);
                
                % reset exogenous input and expansion point
                %----------------------------------------------------------
                M.x     = spm_unvec(x(:,1),M.x);
                M       = rmfield(M,'u');
            
            end
            
            % place CSD and transfer functions in response
            %--------------------------------------------------------------
            csd{c}(i,:,:,:) = g{:};
            mtf{c}(i,:,:,:) = s{:};
                        
        end
        
        
        % update dx = (expm(dt*J) - I)*inv(J)*f(x,u) = L*f(x,u)
        %==================================================================
        
        % update expected hidden states
        %------------------------------------------------------------------
        for j = 1:N
            x(:,i) = x(:,i) + L*M.f(x(:,i),u(:,i),R,M);
        end
        
        % and get ERP
        %------------------------------------------------------------------
        erp(:,i)    = M.g(x(:,i),u(:,i),R,M);
        
    end
    
    % expected responses (under wavelet transforms)
    %----------------------------------------------------------------------
    ERP{c} = erp';
    if ds
        CSD{c} = spm_morlet_conv(csd{c},w,dt,wnum);
    else
        CSD{c} = ERP{c};
    end
    
end
