function [M] = spm_DEM_M_set(M)
% sets indices and performs checks on hierarchical models
% FORMAT [M] = spm_DEM_M_set(M)
%
% for each level (i); required fields
%
%   M(i).g  = y(t)  = g(x,v,P)    {inline function, string or m-file}
%   M(i).f  = dx/dt = f(x,v,P)    {inline function, string or m-file}
%
% and
%
%   M(i).m  = number of inputs v(i + 1);
%   M(i).n  = number of states x(i);
%   M(i).l  = number of output v(i);
%
% or
%
%   M(i).x  = hidden states;
%   M(i).v  = causal states;
%
% for each level (i); optional fields
%
%   M(i).pE = prior expectation of p model-parameters
%   M(i).pC = prior covariances of p model-parameters
%   M(i).hE = prior expectation of h log-precision (cause noise)
%   M(i).hC = prior covariances of h log-precision (cause noise)
%   M(i).gE = prior expectation of g log-precision (state noise)
%   M(i).gC = prior covariances of g log-precision (state noise)
%   M(i).xC = prior covariances of states
%   M(i).Q  = precision components (input noise)
%   M(i).R  = precision components (state noise)
%   M(i).V  = fixed precision (input noise)
%   M(i).W  = fixed precision (state noise)
%
%
% sets fields, checks internal consistency of model specification and sets
% estimation parameters.  If a single hyperparameter is supplied i.i.d
% components are assumed (i.e., Q = I, R = I)
%--------------------------------------------------------------------------
%
%   M(1).E.s;     = smoothness (s.d. in time bins)
%   M(1).E.d;     = embedding order q(v)  (i.e., number of derivatives)
%   M(1).E.n;     = embedding order q(x)
%
% If the highest level involves any dynamic or static transformation
% of its inputs a further level is added with flat priors
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_DEM_M_set.m 7322 2018-05-31 09:47:15Z karl $

% order
%--------------------------------------------------------------------------
g      = length(M);
 
% set missing fields
%==========================================================================
 
% check for specification of hidden states
%--------------------------------------------------------------------------
if isfield(M,'f') && ~isfield(M,'n') && ~isfield(M,'x')
    msgbox('please specify hidden states or their number')
end
 
% check supra-ordinate level and add one (with flat priors) if necessary
%--------------------------------------------------------------------------
try
    fcnchk(M(g).g);
    g      = g + 1;
    M(g).l = M(g - 1).m;
end
M(g).m = 0;
M(g).n = 0;
 
% default fields for static models (hidden states)
%--------------------------------------------------------------------------
if ~isfield(M,'f')
    [M.f] = deal(inline('sparse(0,1)','x','v','P'));
    [M.x] = deal(sparse(0,1));
    [M.n] = deal(0);
end
for i  = 1:g
    try
        fcnchk(M(i).f);
    catch
        M(i).f = inline('sparse(0,1)','x','v','P');
        M(i).x = sparse(0,1);
        M(i).n = 0;
    end
end
 
% consistency and format check on states, parameters and functions
%==========================================================================

% prior expectation of parameters M.pE
%--------------------------------------------------------------------------
try
    M.pE;
catch
    % Assume fixed parameters
    %----------------------------------------------------------------------
    for i = 1:g
        M(i).pE = sparse(0,0);
    end
end


% and priors covariances - p
%--------------------------------------------------------------------------
try
    M.pC;
catch
    
    % Assume fixed (zero variance) parameters
    %----------------------------------------------------------------------
    for i = 1:g
        p       = length(spm_vec(M(i).pE));
        M(i).pC = sparse(p,p);
    end
end

% check pC, if user specified
%--------------------------------------------------------------------------
for i = 1:g
    
    % number of parameters
    %----------------------------------------------------------------------
    np  = length(spm_vec(M(i).pE));
 
    % Assume fixed parameters if not specified
    %----------------------------------------------------------------------
    if isempty(M(i).pC)
        M(i).pC = sparse(np,np);
    end
 
    % convert variances to covariances if necessary
    %----------------------------------------------------------------------
    if isvector(M(i).pC) || isstruct(M(i).pC)
        M(i).pC = sparse(diag(spm_vec(M(i).pC)));
    end
    
    % convert variance to covariances if necessary
    %----------------------------------------------------------------------
    if isscalar(M(i).pC) 
        M(i).pC = speye(np,np)*M(i).pC;
    end
 
    % check size
    %----------------------------------------------------------------------
    if length(M(i).pC) ~= np
        error('please check: M(%i).pC',i)
    end
 
end


% get inputs
%--------------------------------------------------------------------------
try
    v  = M(g).v;
catch
    v  = sparse(0,0);
end
if isempty(v)
    try
        v = sparse(M(g - 1).m,1);
    end
end
if isempty(v)
    try
        v = sparse(M(g).l,1);
    end
end
M(g).l    = length(spm_vec(v));
M(g).v    = v;

 
% check functions
%--------------------------------------------------------------------------
for i = (g - 1):-1:1
    try
        x = M(i).x;
    catch
        x = sparse(M(i).n,1);
    end
    if isempty(x) && M(i).n
        x = sparse(M(i).n,1);
    end
 
    % check f(x,v,P)
    %----------------------------------------------------------------------
    try
        M(i).f  = fcnchk(M(i).f,'x','v','P');
    end
    try
        f       = feval(M(i).f,x,v,M(i).pE);
        if length(spm_vec(x)) ~= length(spm_vec(f))
            error('please check: M(%i).f(x,v,P)',i)
        end
 
    catch
        sprintf('??? evaluation failure: M(%i).f(x,v,P)',i)
        error(lasterror)
    end
    try M(i).fx = fcnchk(M(i).fx,'x','v','P'); end
    try M(i).fv = fcnchk(M(i).fv,'x','v','P'); end
    try M(i).fp = fcnchk(M(i).fp,'x','v','P'); end

 
    % check g(x,v,P)
    %----------------------------------------------------------------------
    try
        M(i).g = fcnchk(M(i).g,'x','v','P');
    end
    try
        M(i).m = length(spm_vec(v));
        v      = feval(M(i).g,x,v,M(i).pE);
        M(i).l = length(spm_vec(v));
        M(i).n = length(spm_vec(x));
 
        M(i).v = v;
        M(i).x = x;
 
    catch
        sprintf('??? evaluation failure: M(%i).g(x,v,P)',i)
        error(lasterror)
    end
    try M(i).gx = fcnchk(M(i).gx,'x','v','P'); end
    try M(i).gv = fcnchk(M(i).gv,'x','v','P'); end
    try M(i).gp = fcnchk(M(i).gp,'x','v','P'); end
    
end
    
% full priors on states
%--------------------------------------------------------------------------
try, M.xP; catch, M(1).xP = []; end
try, M.vP; catch, M(1).vP = []; end
for i = 1:g
    
    % hidden states
    %----------------------------------------------------------------------
    if isvector(M(i).xP), M(i).xP = diag(M(i).xP); end
    if length(M(i).xP) ~= M(i).n
        try
            M(i).xP = speye(M(i).n,M(i).n)*M(i).xP(1);
        catch
            M(i).xP = sparse(M(i).n,M(i).n);
        end
    end
    
    % hidden states
    %----------------------------------------------------------------------
    if isvector(M(i).vP), M(i).vP = diag(M(i).vP); end
    if length(M(i).vP) ~= M(i).l
        try
            M(i).vP = speye(M(i).l,M(i).l)*M(i).vP(1);
        catch
            M(i).vP = sparse(M(i).l,M(i).l);
        end
    end
end

% number of x (hidden states)
%--------------------------------------------------------------------------
nx     = sum(spm_vec(M.n));


% Hyperparameters and components (causes: Q V and hidden states R, W)
%==========================================================================
try, M.Q;  catch, M(1).Q  = []; end
try, M.R;  catch, M(1).R  = []; end
try, M.V;  catch, M(1).V  = []; end
try, M.W;  catch, M(1).W  = []; end
try, M.hE; catch, M(1).hE = []; end
try, M.gE; catch, M(1).gE = []; end
try, M.ph; catch, M(1).ph = []; end
try, M.pg; catch, M(1).pg = []; end

% check hyperpriors hE - [log]hyper-parameters and components
%--------------------------------------------------------------------------
pP    = 1;               % prior precision on log-precisions
for i = 1:g
    
    
    % make sure components are cell arrays
    %----------------------------------------------------------------------
    if ~isempty(M(i).Q) && ~iscell(M(i).Q), M(i).Q = {M(i).Q}; end
    if ~isempty(M(i).R) && ~iscell(M(i).R), M(i).R = {M(i).R}; end 
    
    % check hyperpriors
    %======================================================================
    
    % vectorise
    %----------------------------------------------------------------------
    M(i).hE = spm_vec(M(i).hE);
    M(i).gE = spm_vec(M(i).gE);
    
    % check hyperpriors (expectations)
    %----------------------------------------------------------------------
    if isempty(M(i).hE), M(i).hE = sparse(length(M(i).Q),1); end
    if isempty(M(i).gE), M(i).gE = sparse(length(M(i).R),1); end
    
    % check hyperpriors (covariances)
    %----------------------------------------------------------------------
    try, M(i).hC*M(i).hE; catch, M(i).hC = speye(length(M(i).hE))/pP; end
    try, M(i).gC*M(i).gE; catch, M(i).gC = speye(length(M(i).gE))/pP; end
    
    if isempty(M(i).hC), M(i).hC = speye(length(M(i).hE))/pP; end
    if isempty(M(i).gC), M(i).gC = speye(length(M(i).gE))/pP; end
    
    % check Q and R (precision components)
    %======================================================================

    
    % check components and assume i.i.d if not specified
    %----------------------------------------------------------------------
    if length(M(i).Q) > length(M(i).hE)
        M(i).hE = sparse(length(M(i).Q),1) + M(i).hE(1);
    end
    if length(M(i).Q) < length(M(i).hE)
        M(i).Q  = {speye(M(i).l,M(i).l)};
        M(i).hE = M(i).hE(1);
    end
    if length(M(i).hE) > length(M(i).hC)
        M(i).hC = speye(length(M(i).Q))*M(i).hC(1);
    end
    if length(M(i).R) > length(M(i).gE)
        M(i).gE = sparse(length(M(i).R),1) + M(i).gE(1);
    end
    if length(M(i).R) < length(M(i).gE)
        M(i).R  = {speye(M(i).n,M(i).n)};
        M(i).gE = M(i).gE(1);
    end
    if length(M(i).gE) > length(M(i).gC)
        M(i).gC = speye(length(M(i).R))*M(i).gC(1);
    end
    
    % check consistency and sizes (Q)
    %----------------------------------------------------------------------
    for j = 1:length(M(i).Q)
        if length(M(i).Q{j}) ~= M(i).l
            error('wrong size; M(%d).Q{%d}',i,j)
        end
    end
    
    % check consistency and sizes (R)
    %----------------------------------------------------------------------
    for j = 1:length(M(i).R)
        if length(M(i).R{j}) ~= M(i).n
            error('wrong size; M(%d).R{%d}',i,j)
        end
    end
    
    % check V and W (lower bound on precisions)
    %======================================================================

    % check V and assume unit precision if improperly specified
    %----------------------------------------------------------------------
    if isvector(M(i).V), M(i).V = diag(M(i).V); end
    if length(M(i).V) ~= M(i).l
        try
            M(i).V = speye(M(i).l,M(i).l)*M(i).V(1);
        catch
            if isempty(M(i).hE) && isempty(M(i).ph)
                M(i).V = speye(M(i).l,M(i).l);
            else
                M(i).V = sparse(M(i).l,M(i).l);
            end
        end
    end
    
                
    % check W and assume unit precision if improperly specified
    %----------------------------------------------------------------------
    if isvector(M(i).W), M(i).W = diag(M(i).W); end
    if length(M(i).W) ~= M(i).n
        try
            M(i).W = speye(M(i).n,M(i).n)*M(i).W(1);
        catch
            if isempty(M(i).gE) && isempty(M(i).pg)
                M(i).W = speye(M(i).n,M(i).n);
            else
                M(i).W = sparse(M(i).n,M(i).n);
            end
        end
    end  
end

 
% estimation parameters M(1).E.s, n,...
%==========================================================================
% E.s;                               % smoothness (seconds)
% E.dt;                              % time step
% E.d;                               % approximation order of q(x,v)
% E.n;                               % order of embedding (n >= d)
 
% temporal smoothness - s.d. of kernel
%--------------------------------------------------------------------------
try M(1).E.s;  catch, if nx, M(1).E.s = 1/2; else M(1).E.s = 0; end, end
 
% time step
%--------------------------------------------------------------------------
try M(1).E.dt; catch, M(1).E.dt = 1; end
 
% embedding orders
%--------------------------------------------------------------------------
try M(1).E.d;  catch, if nx, M(1).E.d = 2; else M(1).E.d = 0;  end, end
try M(1).E.n;  catch, if nx, M(1).E.n = 6; else M(1).E.n = 0;  end, end

M(1).E.d = min(M(1).E.d,M(1).E.n);
 
% number of iterations
%--------------------------------------------------------------------------
try M(1).E.nD; catch, if nx, M(1).E.nD = 1; else M(1).E.nD = 8; end, end
try M(1).E.nE; catch,        M(1).E.nE = 8; end
try M(1).E.nM; catch,        M(1).E.nM = 8; end
try M(1).E.nN; catch,        M(1).E.nN = 8; end
 
% checks on smoothness hyperparameter
%==========================================================================
try, M = rmfield(M,'sv'); end
try, M = rmfield(M,'sw'); end
for i = 1:g
    
    try, M(i).sv; catch, M(i).sv = M(1).E.s;   end
    try, M(i).sw; catch, M(i).sw = M(1).E.s;   end
        
    if ~isscalar(M(i).sv), M(i).sv = M(1).E.s; end
    if ~isscalar(M(i).sw), M(i).sw = M(1).E.s; end
end

% check on linear approximation scheme
%==========================================================================
try
    M(1).E.linear;
catch
    M(1).E.linear = 0;
end

% checks on estimability
%==========================================================================
 
% check that there are informative priors on the states or the causes
%--------------------------------------------------------------------------
Q     = ~norm(M(end).V,1);
for i = 1:(g - 1)
    P = norm(M(i).pC,1) > exp(8);
    if P && Q
        warndlg('please use informative priors on causes or parameters')
    end
end

