classdef poster
% poster  Posterior objects and functions.
%
% Posterior objects, `poster`, are used to evaluate the behaviour of the
% posterior dsitribution, and to draw model parameters from the posterior
% distibution.
%
% Posterior objects are set up within the
% [`model/estimate`](model/estimate) function and returned as the second
% output argument - the set up and initialisation of the posterior object
% is fully automated in this case. Alternatively, you can set up a
% posterior object manually, by setting all its properties appropriately.
%
% Poster methods:
%
% Constructor
% ============
%
% * [`poster`](poster/poster) - Posterior objects and functions.
%
% Evaluating posterior density
% =============================
%
% * [`arwm`](poster/arwm) - Adaptive random-walk Metropolis posterior simulator.
% * [`eval`](poster/eval) - Evaluate posterior density at specified points.
% * [`testpar`](poster/testpar) - Test performance of for versus parfor loops in evaluating posterior simulator.
%
% Chain statistics
% =================
%
% * [`stats`](poster/stats) - Evaluate selected statistics of an MCMC chain.
%
% Getting on-line help on model functions
% ========================================
%
%     help poster
%     help poster/function_name
%

% -IRIS Toolbox.
% -Copyright (c) 2007-2012 Jaromir Benes.

    properties
        paramList = {};
        minusLogPostFunc = [];
        minusLogPostFuncArgs = {};
        minusLogLikFunc = [];
        minusLogLikFuncArgs = {};
        logPriorFunc = {};
        initLogPost = NaN;
        initParam = [];
        initProposalCov = [];
        lowerBounds = [];
        upperBounds = [];
    end
    
    methods
        
        function this = poster(varargin)
            if isempty(varargin)
                return
            elseif length(varargin) == 1 && isa(varargin{1},'poster')
                this = varargin{1};
            end
        end
        
        varargout = arwm(varargin)
        varargout = eval(varargin)
        varargout = testpar(varargin)
        varargout = stats(varargin)
        
        function this = set.paramList(this,list)
            if ischar(list) || iscellstr(list)
                if ischar(list)
                    list = regexp(list,'\w+','match');
                end
                this.paramList = list(:).';
                if length(this.paramList) ~= length(unique(this.paramList))
                    utils.error('poster', ...
                        'Parameter names must be unique.');
                end
                n = length(this.paramList);
                this.logPriorFunc = cell([1,n]); %#ok<MCSUP>
                this.lowerBounds = -inf([1,n]); %#ok<MCSUP>
                this.upperBounds = inf([1,n]); %#ok<MCSUP>
            elseif isnumericscalar(list)
                n = list;
                this.paramList = cell([1,n]);
                for i = 1 : n
                    this.paramList{i} = sprintf('p%g',i);
                end
            else
                utils.error('poster', ...
                    'Invalid assignment to poster.paramList.');
            end
        end
        
        function this = set.initParam(this,init)
            n = length(this.paramList); %#ok<MCSUP>
            if isnumeric(init)
                init = init(:).';
                if length(init) == n
                    this.initParam = init;
                    chkbounds(this);
                else
                    utils.error('poster', ...
                        ['Length of the initial parameter vector ', ...
                        'must match the number of parameters.']);
                end
            else
                utils.error('poster', ...
                    'Invalid assignment to poster.initParam.');
            end
        end
        
        function this = set.lowerBounds(this,x)
            n = length(this.paramList); %#ok<MCSUP>
            if numel(x) == n
                this.lowerBounds = -inf([1,n]);
                this.lowerBounds(:) = x(:);
                chkbounds(this);
            else
                utils.error('poster', ...
                    ['Length of lower bounds vector must match ', ...
                    'the number of parameters.']);
            end
        end
        
        function this = set.upperBounds(this,x)
            n = length(this.paramList); %#ok<MCSUP>
            if numel(x) == n
                this.upperBounds = -inf([1,n]);
                this.upperBounds(:) = x(:);
                chkbounds(this);
            else
                utils.error('poster', ...
                    ['Length of upper bounds vector must match ', ...
                    'the number of parameters.']);
            end
        end
        
        function this = set.initProposalCov(this,C)
            if ~isnumeric(C)
                utils.error('poster', ...
                    'Invalid assignment to poster.initProposalCov.');
            end
            n = length(this.paramList); %#ok<MCSUP>
            C = C(:,:);
            if any(size(C) ~= n)
                utils.error('poster', ...
                    ['Size of the initial proposal covariance matrix ', ...
                    'must match the number of parameters.']);
            end
            C = (C+C.')/2;
            Cdiag = diag(C);
            if ~all(Cdiag > 0)
                utils.error('poster', ...
                    ['Diagonal elements of the initial proposal ', ...
                    'cov matrix must be positive.']);
            end
            ok = false;
            adjusted = false;
            offDiagIndex = eye(size(C)) == 0;
            count = 0;
            while ~ok && count < 100
                try
                    chol(C);
                    ok = true;
                catch %#ok<CTCH>
                    C(offDiagIndex) = 0.9*C(offDiagIndex);
                    C = (C+C.')/2;
                    adjusted = true;
                    ok = false;
                    count = count + 1;
                end
            end
            if ~ok
                utils.error('poster', ...
                    ['Cannot make the initial proposal cov matrix ', ...
                    'positive definite.']);
            elseif adjusted
                utils.warning('poster', ...
                    ['The initial proposal cov matrix ', ...
                    'adjusted to be numerically positive definite.']);
            end
            this.initProposalCov = C;
        end
        
    end
    
    methods (Hidden) 
        
        function this = setlowerbounds(this,varargin)
            this = setbounds(this,'lower',varargin{:});
        end
        
        function this = setupperbounds(this,varargin)
            this = setbounds(this,'upper',varargin{:});
        end
        
        function this = setbounds(this,lowerupper,varargin)
            if length(varargin) == 1 && isnumeric(varargin{1})
                if lowerupper(1) == 'l'
                    this.lowerBounds = varargin{1};
                else
                    this.upperBounds = varargin{1};
                end
            elseif length(varargin) == 2 ...
                    && (ischar(varargin{1}) || iscellstr(varargin{1})) ...
                    && isnumeric(varargin{2})
                userlist = varargin{1};
                if ischar(userlist)
                    userlist = regexp(userlist,'\w+','match');
                end
                userlist = userlist(:).';
                pos = nan(size(userlist));
                for i = 1 : length(userlist)
                    temp = find(strcmp(this.paramList,userlist{i}));
                    if ~isempty(temp)
                        pos(i) = temp;
                    end
                end
                if any(isnan(pos))
                    utils.error('poster', ...
                        'This is not a valid parameter name: ''%s''.', ...
                        userlist{isnan(pos)});
                end
                if lowerupper(1) == 'l'
                    this.lowerBounds(pos) = varargin{2}(:).';
                else
                    this.upperBounds(pos) = varargin{2}(:).';
                end
            end
            chkbounds(this);
        end
        
        function this = setprior(this,name,func)
            if ischar(name) && isa(func,'function_handle')
                pos = strcmp(this.paramList,name);
                if any(pos)
                    this.logPriorFunc{pos} = func;
                else
                    utils.error('poster', ...
                        'This is not a valid parameter name: ''%s''.', ...
                        name);
                end
            end
        end
        
        function chkbounds(this)
            n = length(this.paramList);
            if isempty(this.initParam)
                return
            end
            if isempty(this.lowerBounds)
                this.lowerBounds = -inf([1,n]);
            end
            if isempty(this.upperBounds)
                this.upperBounds = inf([1,n]);
            end
            index = this.initParam < this.lowerBounds ...
                | this.initParam > this.upperBounds;
            if any(index)
                utils.error('poster', ...
                    ['The initial value for this parameter is ', ...
                    'out of bounds: ''%s''.'], ...
                    this.paramList{index});
            end
        end
        
    end
    
    methods (Access=protected, Hidden)
        
        varargout = simulate(varargin)
        
    end
    
end