function DCM = spm_dcm_post_hoc_old(P,fun,varargin)
% Post hoc optimisation of DCMs (under the Laplace approximation)
% FORMAT DCM = spm_dcm_post_hoc(P,fun,field,field,...)
%
% P         - character/cell array of DCM filenames
%           - or cell array of DCM structures; where
%  DCM.M.pE - prior expectation (with parameters in pE.A, pE.B and pE.C)
%  DCM.M.pC - prior covariance
%  DCM.Ep   - posterior expectations
%  DCM.Cp   - posterior covariance
%
% fun       - optional family definition function: k = fun(A,B,C)
%             k = 1,2,...,K for K families or proper subsets of a partition
%             of model space - a function of the adjacency matrices: e.g.,
%
%             fun = @(A,B,C) any(spm_vec(B(:,:,2))) + 1;
%
%             returns 1 if there are no bilinear parameters for the 2nd
%             bilinear effect and 2 if there are. fun should be an inline
%             function or script. NB: Model posteriors over families with
%             and without free parameters (in A,B,C and D) are evaluated
%             automatically and saved in DCM_BPA (DCM.Pp)
%
% field     - the field nsmes of the parameters in the structure pE and Ep 
%             that are to be inlcudied in Baysian model reduction.
%             The default is the cell array 'A','B','C'
%
%--------------------------------------------------------------------------
% This routine searches over all possible reduced models of a full model
% (DCM) and uses post hoc model selection to select the best. Reduced
% models mean all permutations of free parameters (parameters with a non-
% zero prior covariance), where models are defined in terms of their prior
% covariance. The full model should be inverted prior to post hoc
% optimization. If there are more than 16 free-parameters, this routine
% will implement a greedy search: This entails searching over all
% permutations of the 8 parameters whose removal (shrinking the prior
% variance to zero) produces the smallest reduction (greatest increase)
% in model evidence. This procedure is repeated until all 8 parameters
% are retained in the best model or there are no more parameters to
% consider. When several DCMs are optimized together (as in group studies),
% they are checked to ensure the same free parameters have been specified
% and the log-evidences are pooled in a fixed effects fashion.
%
% This application of post hoc optimization assumes the DCMs that are
% optimized are the same model of different data. Normally, this would be
% a full model, in the sense of having the maximum number of free
% parameters, such that the set of reduced models is as large as possible.
% In contrast spm_dcm_search operates on different DCMs of the same data
% to identify the best model, after inverting the full(est) model
%
% The outputs of this routine are graphics reporting the model reduction
% (optimisation) and a DCM_opt_??? for every specified DCM that contains
% reduced conditional parameters estimates (for simplicity, the original
% kernels and predicted states are retained). The structural and functional
% (spectral embedding) graphs are based on Bayesian parameter averages
% over multiple DCMs, which are stored in DCM_BPA.mat. This DCM also
% contains the posterior probability of models partitioned according to
% whether a particular parameter exists or not:
%
% DCM.Pp     -  Model posterior (with and without each parameter)
% DCM.Ep     -  Bayesian parameter average under selected model
% DCM.Cp     -  Bayesian parameter covariance under selected model
% DCM.Pf     -  Model posteriors over user specified families
% DCM.fun    -  User-specified family definition function
% DCM.files  -  List of DCM files used for Bayesian averaging
%
% See also: spm_dcm_search.m
%__________________________________________________________________________

% Karl Friston
% Copyright (C) 2010-2022 Wellcome Centre for Human Neuroimaging


% number of parameters to consider before invoking greedy search
%--------------------------------------------------------------------------
nmax = 8;

% Get filenames
%--------------------------------------------------------------------------
if nargin < 1
    [P, sts] = spm_select([1 Inf],'^DCM.*\.mat$','Select DCM*.mat files');
    if ~sts, return; end
end

if ischar(P),   P = cellstr(P); end
if isstruct(P), P = {P};        end
N   = numel(P);
TOL = exp(-16);

% Check family definition functions
%--------------------------------------------------------------------------
if nargin < 2; fun = {}; Pf = 0; end
if ~numel(fun); Pf = 0;          end

% Check fields of parameter structure
%--------------------------------------------------------------------------
if nargin < 3
    field = {'A','B','C'};
else
    field = varargin;
end

%-Check models are compatible in terms of their prior variances
%==========================================================================
for j = 1:N
    
    % Get prior covariances
    %----------------------------------------------------------------------
    try, load(P{j});          catch, DCM = P{j};              end
    try, pC = diag(DCM.M.pC); catch, pC  = spm_vec(DCM.M.pC); end
    
    % and compare it with the first model
    %----------------------------------------------------------------------
    if j == 1
        C = pC;
    else
        if any(xor(pC,C))
            fprintf('Please check model %i for compatibility.\n',j)
            return
        end
    end
end


%-Greedy search (GS) - eliminating parameters in a top down fashion
%==========================================================================

% Accumulated reduction vector (C)
%--------------------------------------------------------------------------
C  = logical(C);
GS = 1;
while GS
    
    % Find free coupling parameters
    %----------------------------------------------------------------------
    k = spm_fieldindices(DCM.Ep,field{:});
    k = k(C(k));
    nparam = length(k);
    
    
    % If there are too many find those with the least evidence
    %----------------------------------------------------------------------
    if nparam > nmax
        
        % Loop through DCMs and free parameters and get log-evidences
        %------------------------------------------------------------------
        Z     = [];
        for j = 1:N
            
            try, load(P{j}); catch, DCM = P{j}; end
            if isstruct(DCM.M.pC), DCM.M.pC = diag(spm_vec(DCM.M.pC)); end
            
            fprintf('\ninitial search (%i): 00%%',j)
            
            % Get priors and posteriors
            %--------------------------------------------------------------
            qE    = DCM.Ep;
            qC    = DCM.Cp;
            pE    = DCM.M.pE;
            pC    = DCM.M.pC;
            
            % Remove (a priori) null space
            %--------------------------------------------------------------
            U     = spm_svd(pC);
            qE    = U'*spm_vec(qE);
            pE    = U'*spm_vec(pE);
            qC    = U'*qC*U;
            pC    = U'*pC*U;
            
            % Model search over new prior without the i-th parameter
            %--------------------------------------------------------------
            for i = 1:length(k)
                r      = C; r(k(i)) = 0;
                R      = U(r,:)'*U(r,:);
                Z(i,j) = spm_log_evidence(qE,qC,pE,pC,pE,R*pC*R);
                
                fprintf('\b\b\b\b')
                fprintf('%-3.0f%%',i*100/length(k))
            end
        end
        
        % Find parameters with the least evidence
        %------------------------------------------------------------------
        Z          = sum(Z,2);
        [unused,i] = sort(-Z);
        k          = k(i(1:8));
        
        % Flag a greedy search
        %------------------------------------------------------------------
        GS = 1;
        
    elseif isempty(k)
        
        fprintf('\nThere are no free parameters in this model.\n')
        return
        
    else
        GS = 0;
    end
    
    % Create model space in terms of free parameter indices
    %----------------------------------------------------------------------
    K     = spm_perm_mtx(length(k));
    
    %-Loop through DCMs and models and get log-evidences
    %======================================================================
    G     = [];
    for j = 1:N
        
        try, load(P{j}); catch, DCM = P{j}; end
        if isstruct(DCM.M.pC), DCM.M.pC = diag(spm_vec(DCM.M.pC)); end
        fprintf('\nsearching (%i): 00%%',j)
        
        % Get priors and posteriors
        %------------------------------------------------------------------
        qE    = DCM.Ep;
        qC    = DCM.Cp;
        pE    = DCM.M.pE;
        pC    = DCM.M.pC;
        
        % Remove (a priori) null space
        %------------------------------------------------------------------
        U     = spm_svd(pC);
        qE    = U'*spm_vec(qE);
        pE    = U'*spm_vec(pE);
        qC    = U'*qC*U;
        pC    = U'*pC*U;
        
        % Model search over new prior (covariance)
        %------------------------------------------------------------------
        for i = 1:length(K)
            r      = C; r(k(K(i,:))) = 0;
            R      = U(r,:)'*U(r,:);
            G(i,j) = spm_log_evidence(qE,qC,pE,pC,pE,R*pC*R);
            
            fprintf('\b\b\b\b')
            fprintf('%-3.0f%%',i*100/length(K))
        end
    end
    fprintf('\n')
    
    % Pooled model evidence
    %----------------------------------------------------------------------
    S     = sum(G,2);
    S     = S - S(end);
    p     = exp(S - max(S));
    p     = p/sum(p);
    
    %-Get selected model and prune redundant parameters
    %======================================================================
    [unused, i]  = max(p);
    C(k(K(i,:))) = 0;
    
    % Continue greedy search if any parameters have been eliminated
    %----------------------------------------------------------------------
    nelim  = full(sum(K(i,:)));
    GS     = GS & nelim;
    
    % Show results
    % ---------------------------------------------------------------------
    spm_figure('Getwin','Graphics'); clf    
    fprintf('%i out of %i free parameters removed \n',nelim,nparam)
    
    subplot(3,2,1)
    if length(K) > 32, plot(S,'k'), else, bar(S,'c'), end
    title('log-posterior','FontSize',16)
    xlabel('model','FontSize',12)
    ylabel('log-probability','FontSize',12)
    axis square
    
    subplot(3,2,2)
    if length(K) > 32, plot(p,'k'), else, bar(p,'r'), end
    title('model posterior','FontSize',16)
    xlabel('model','FontSize',12)
    ylabel('probability','FontSize',12)
    axis square
    drawnow
    
end


%-Inference over families (one family per coupling parameter)
%==========================================================================
pE    = DCM.M.pE;
pC    = DCM.M.pC;
for i = 1:length(k)
    Pk(1,i) = mean(p(~K(:,i)));
    Pk(2,i) = mean(p( K(:,i)));
end
Pk    = Pk(1,:)./sum(Pk);
Pn    = full(C);
Pn(k) = Pk;
Pk    = spm_unvec(Pn,pE);


%-Inference over families (specified by fun)
%==========================================================================
if ~isempty(fun)
    for i = 1:length(K)
        Pn     = full(C);
        Pn(K(i,:)) = 0;
        Pn     = spm_unvec(Pn,pE);
        try
            Kf(i)     = fun(Pn.A,Pn.B,Pn.C);
        catch
            try
                Kf(i) = fun(Pn.A,Pn.B);
            catch
                Kf(i) = fun(Pn.A);
            end
        end
            
    end
    for i = 1:max(Kf)
        Pf(i) = mean(p(Kf == i));
    end
    Pf(isnan(Pf)) = 0;
    Pf    = Pf/sum(Pf);
end


%-Conditional estimates of selected model for each data set
%==========================================================================

% Selected model (reduced prior covariance);
%--------------------------------------------------------------------------
rC      = diag(C)*pC*diag(C);

% Record pruned parameters
%--------------------------------------------------------------------------
R       = spm_unvec(full(C),pE);
try
    R.a = DCM.a & R.A;
    R.b = DCM.b & R.B;
    R.c = DCM.c & R.C;
    R.d = DCM.d & R.D;
end

EQ      = 0;
PQ      = 0;
Eq      = 0;
Pq      = 0;
for j = 1:N
    
    % Get priors and posteriors
    %----------------------------------------------------------------------
    try, load(P{j}); catch, DCM = P{j}; end
    
    qE  = DCM.Ep;
    qC  = DCM.Cp;
    pE  = DCM.M.pE;
    pC  = DCM.M.pC;
    
    % Get posterior of selected model - rC
    %----------------------------------------------------------------------
    [F,Ep,Cp] = spm_log_evidence_reduce(qE,qC,pE,pC,pE,rC);
    
    % Bayesian parameter average (for full and reduced selected model)
    %----------------------------------------------------------------------
    PP  = spm_inv(qC,TOL);
    Pp  = spm_inv(Cp,TOL);
    EQ  = EQ + PP*spm_vec(qE);
    Eq  = Eq + Pp*spm_vec(Ep);
    PQ  = PQ + PP;
    Pq  = Pq + Pp;
    
    
    %-Put reduced conditional estimates in DCM
    %======================================================================
    
    % Bayesian inference and variance
    %----------------------------------------------------------------------
    try
        T = full(spm_vec(pE)) + DCM.T;
    catch
        T = full(spm_vec(pE));
    end
    sw   = warning('off','SPM:negativeVariance');
    Pp   = spm_unvec(1 - spm_Ncdf(T,abs(spm_vec(Ep)),diag(Cp)),Ep);
    Vp   = spm_unvec(diag(Cp),Ep);
    warning(sw);
    
    % Store parameter estimates
    %----------------------------------------------------------------------
    DCM.M.pC    = rC;
    DCM.Ep      = Ep;
    DCM.Cp      = Cp;
    DCM.Pp      = Pp;
    DCM.Vp      = Vp;
    
    % and in DEM format
    %----------------------------------------------------------------------
    DCM.qP.P{1} = Ep;
    DCM.qP.C    = Cp;
    DCM.qP.V{1} = spm_unvec(diag(Cp),Ep);
    
    % and in prior constraints fields
    %----------------------------------------------------------------------
    try
        DCM.a   = R.a;
        DCM.b   = R.b;
        DCM.c   = R.c;
        DCM.d   = R.d;
    end  
    
    % approximations to model evidence: negative free energy, AIC, BIC
    %------------------------------------------------------------------
    DCM.F    = F;
    try
        evidence = spm_dcm_evidence(DCM);
        DCM.AIC  = evidence.aic_overall;
        DCM.BIC  = evidence.bic_overall;
    end
    
    %-Save optimised DCM
    %======================================================================
    try
        [pth, name] = fileparts(P{j});
        if ~strncmp(name,'DCM_opt_',8)
            name = ['DCM_opt_' name(5:end) '.mat'];
        end
        P{j} = fullfile(pth,name);
    catch
        P{j} = fullfile(pwd,['DCM_opt_' date '.mat']);
    end
    save(P{j},'DCM','F','Ep','Cp', spm_get_defaults('mat.format'));
    
end


%-Bayesian parameter average
%==========================================================================
if isstruct(pC), pC = diag(spm_vec(pC)); end

CQ  = spm_inv(PQ,TOL);
Cq  = spm_inv(Pq,TOL);
EQ  = CQ*EQ;
Eq  = Cq*Eq;
EQ  = spm_unvec(EQ,pE);
Eq  = spm_unvec(Eq,pE);

% Show full and reduced conditional estimates (for Bayesian average)
%--------------------------------------------------------------------------
spm_figure('Getwin','Graphics');

i   = spm_fieldindices(DCM.Ep,'A','B','C');
pE  = spm_vec(pE);
EP  = spm_vec(EQ);
Ep  = spm_vec(Eq);

subplot(3,2,3)
spm_plot_ci(EP(i),CQ(i,i))
title('MAP connections (full)','FontSize',16)
axis square
a   = axis;

subplot(3,2,4)
spm_plot_ci(Ep(i),abs(Cq(i,i)))
title('MAP connections (reduced)','FontSize',16)
axis square
axis(a)

subplot(3,2,5)
bar(EP(i) - pE(i))
xlabel('parameter')
title('MAP minus prior','FontSize',16)
spm_axis tight
axis square

subplot(3,2,6)
bar(Ep(i) - EP(i))
xlabel('parameter')
title('differences in MAP','FontSize',16)
spm_axis tight
axis square
drawnow


% Show structural and functional graphs
%--------------------------------------------------------------------------
if ~nargout
    spm_figure('Getwin','Graph analysis'); clf
    try
        spm_dcm_graph(DCM.xY,Eq.A);
    catch
        try
            spm_dcm_graph(DCM,Eq.A);
        end
    end
end


% Show coupling matrices
%--------------------------------------------------------------------------
if ~nargout && numel(field) == 3;
    
    spm_figure('Getwin','Bayesian parameter average (selected model)'); clf
    spm_dcm_fmri_image(Eq)
    
    spm_figure('Getwin','Model posterior (over parameters)'); clf
    spm_dcm_fmri_image(Pk)

end

% illustrate family-wise inference
%--------------------------------------------------------------------------
if ~isempty(fun) && ~nargout
    
    spm_figure('Getwin','Model posterior (over families)'); clf
    subplot(2,1,1)
    bar(Pf)
    xlabel('familiy')
    title('Model posterior (over families)','FontSize',16)
    axis square
end

%-Save Bayesian parameter average and family-wise model inference
%==========================================================================

% Get original (first) DCM
% -------------------------------------------------------------------------
try, load(P{1}); catch, DCM = P{1}; end

DCM.Pp    = Pk;       % Model posterior over parameters (with and without)
DCM.Ep    = Eq;       % Bayesian parameter average under selected model
DCM.Cp    = Cq;       % Bayesian parameter covariance under selected model
DCM.fun   = fun;      % user-specified family definition function
DCM.files = P;        % list of DCM files used for Bayesian averaging
DCM.Pf    = Pf;       % Model posteriors over user specified families

% and save as DCM_BPA
% -------------------------------------------------------------------------
try
    pth  = fileparts(P{1});
    name = 'DCM_BPA.mat';
    name = fullfile(pth,name);
catch
    name = fullfile(pwd,'DCM_BPA.mat');
end

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