% PEST constructor for parameter estimates (pest) class.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: PEST constructor for parameter estimates (pest) class.
%
% CONSTRUCTOR:
%
%       pe = pest()
%       pe = pest(filename)
%       pe = pest(plist-object)
%       pe = pest(y, paramNames)
%       pe = pest(y, paramNames, dy)
%       pe = pest(y, paramNames, dy, cov)
%       pe = pest(y, paramNames, dy, cov, chi2)
%       pe = pest(y, paramNames, dy, cov, chi2, dof)
%
% INPUTS:
%
%       y          - best fit parameters
%       paramNames - names of the parameters
%       dy         - standard errors of the parameters
%       cov        - covariance matrix of the parameters
%       chi2       - reduced chi^2 of the final fit
%       dof        - degrees of freedom
%
% <a href="matlab:utils.helper.displayMethodInfo('pest', 'pest')">Parameters Description</a>
%
% VERSION:     $Id: pest.m,v 1.33 2011/08/22 05:22:23 hewitson Exp $
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

classdef pest < ltpda_uoh
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                            Property definition                            %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  %---------- Protected read-only Properties ----------
  properties (SetAccess = protected, Dependent = true)
  end
  
  %---------- Protected read-only Properties ----------
  properties (GetAccess = public, SetAccess = protected)
    dy                % standard errors of the parameters.
    y      = [];      % best fit parameters
    names  = {};      % names of the parameters, if any
    yunits = unit.initObjectWithSize(1,0);  % the units of each parameter
    pdf    = [];      % posterior probability distribution of the parameters
    cov    = [];      % covariance matrix of the parameters
    corr   = [];      % correlation matrix of the parameters
    chi2   = [];      % reduced chi^2 of the final fit
    dof    = [];      % degrees of freedom
    chain  = [];      % monte carlo markov chain
    models = [];      % models that were fit
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                          Check property setting                           %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods
    % y
    function set.y(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || (~isvector(val) && ~isempty(val))
        error('### The value for the property ''y'' must be a numeric vector');
      end
      if ~isempty(val)
        obj.y = reshape(val, [], 1);
      else
        obj.y = val;
      end
    end
    % dy
    function set.dy(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || (~isvector(val) && ~isempty(val))
        error('### The value for the property ''dy'' must be a numeric vector');
      end
      if ~isempty(val)
        obj.dy = reshape(val, [], 1);
      else
        obj.dy = val;
      end
    end
    % names
    function set.names(obj, val)
      if numel(val) == 1 && iscell(val{1})
        val = val{1};
      end
      if isempty(val)
        val = {};
      end
      if ~(iscell(val) || ischar(val))
        error('### The value for the property ''names'' must be a cell of parameter names.');
      end
      if iscell(val)
        obj.names = val;
      else
        obj.names = cellstr(val);
      end
    end
    % yunits
    function set.yunits(obj, val)
      if numel(val) == 1 && iscell(val) && iscell(val{1})
        val = val{1};
      end
      if isempty(val)
        obj.yunits = unit.initObjectWithSize(size(val,1), size(val,2));
      elseif ischar(val)
        obj.yunits = unit(val);
      elseif isa(val, 'unit')
        obj.yunits = copy(val,1);
      elseif iscell(val)
        obj.yunits = unit(val{:});
      else
        error('### The yunits value must be a vector of unit-objects or a string');
      end
    end
    % pdf
    function set.pdf(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || ndims(val) ~= 2
        error('### The value for the property ''pdf'' must be a numeric matrix');
      end
      obj.pdf = val;
    end
    % cov
    function set.cov(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || ndims(val) ~= 2
        error('### The value for the property ''Cov'' must be a numeric matrix');
      end
      obj.cov = val;
    end
    % corr
    function set.corr(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || ndims(val) ~= 2
        error('### The value for the property ''Corr'' must be a numeric matrix');
      end
      obj.corr = val;
    end
    % chi2
    function set.chi2(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || (any(size(val) ~= [1 1]) && ~isempty(val))
        error('### The value for the property ''chi2'' must be a single double');
      end
      obj.chi2 = val;
    end
    % dof
    function set.dof(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val) || (any(size(val) ~= [1 1]) && ~isempty(val))
        error('### The value for the property ''dof'' must be a single double');
      end
      obj.dof = val;
    end
    % dof
    function set.chain(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~isnumeric(val)
        error('### The value for the property ''chain'' must be a double');
      end
      obj.chain = val;
    end
    
    % models
    function set.models(obj, val)
      try
        % The genericSet method sets the value with a cell-array
        val = [val{:}];
      end
      if ~(isa(val, 'ltpda_uoh') || isempty(val))
        error('### The value for the property ''models'' must be ltpda_uoh object');
      end
      if ~isempty(val)
        obj.models = val;
      else
        obj.models = [];
      end
    end
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                     Compute the Dependent properties                      %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods
    %     function val = get.dy(obj)
    %       val = sprintf('Please define a method which gets the dy from the object: %s', obj.name);
    %     end
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                Constructor                                %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods
    function obj = pest(varargin)
      
      import utils.const.*
      utils.helper.msg(msg.OMNAME, 'running %s/%s', mfilename('class'), mfilename);
      
      % Collect all pest objects
      [objs, dummy, rest] = utils.helper.collect_objects(varargin(:), 'pest');
      
      if isempty(rest) && ~isempty(objs)
        % Do copy constructor and return
        utils.helper.msg(msg.OPROC1, 'copy constructor');
        obj = copy(objs, 1);
        for kk=1:numel(obj)
          obj(kk).addHistory(pest.getInfo('pest', 'None'), [], [], obj(kk).hist);
        end
        return
      end
      
      switch nargin
        case 0
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%   no input   %%%%%%%%%%%%%%%%%%%%%%%%%%%*
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          utils.helper.msg(msg.OPROC1, 'empty constructor');
          obj.addHistory(pest.getInfo('pest', 'None'), plist(), [], []);
          
        case 1
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          %%%%%%%%%%%%%%%%%%%%%%%%%%%   One input   %%%%%%%%%%%%%%%%%%%%%%%%%%%
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          
          if ischar(varargin{1})
            %%%%%%%%%%   obj = pest('foo.mat')   %%%%%%%%%%
            %%%%%%%%%%   obj = pest('foo.xml')   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from file %s', varargin{1});
            obj = fromFile(obj, varargin{1});
            
          elseif isstruct(varargin{1})
            %%%%%%%%%%   obj = pest(struct)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from struct');
            obj = obj.fromStruct(varargin{1});
            
          elseif isa(varargin{1}, 'plist')
            %%%%%%%%%%  obj = pest(plist-object)   %%%%%%%%%%
            
            pl = varargin{1};
            
            if pl.isparam('filename')
              utils.helper.msg(msg.OPROC1, 'constructing from file %s', varargin{1});
              obj = fromFile(obj, pl);
              
            elseif pl.isparam('hostname') || pl.isparam('conn')
              utils.helper.msg(msg.OPROC1, 'constructing from repository %s', pl.find('hostname'));
              obj = obj.fromRepository(pl);
              
            elseif pl.isparam('built-in')
              utils.helper.msg(msg.OPROC1, 'constructing from built-in model');
              obj = fromModel(obj, pl);
              
            elseif pl.isparam('y')
              utils.helper.msg(msg.OPROC1, 'constructing from values');
              obj = obj.fromValues(pl);
              
            else
              obj.setProperties(pl);
              obj.addHistory(pest.getInfo('pest', 'None'), pl, [], []);
            end
            
          elseif isnumeric(varargin{1})
            %%%%%%%%%%   obj = pest(y)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y');
            obj.y = varargin{1};
            pl = plist('y', varargin{1});
            obj.fromValues(pl);
            
          else
            error('### Unknown single argument constructor.');
          end
          
        case 2
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          %%%%%%%%%%%%%%%%%%%%%%%%%%%   two input   %%%%%%%%%%%%%%%%%%%%%%%%%%%
          %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          if (isa(varargin{1}, 'database') || isa(varargin{1}, 'java.sql.Connection')) && isnumeric(varargin{2})
            %%%%%%%%%%  obj = pest(<database-object>, [IDs])   %%%%%%%%%%
            obj = obj.fromRepository(plist('conn', varargin{1}, 'id', varargin{2}));
            
          elseif isnumeric(varargin{1}) && (iscell(varargin{2}) || ischar(varargin{2}))
            %%%%%%%%%%   obj = pest(y, paramNames)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y and param names');
            pl = plist('y', varargin{1}, 'paramNames', varargin{2});
            obj.fromValues(pl);
            
          elseif isa(varargin{1}, 'pest') && isa(varargin{2}, 'plist') && isempty(varargin{2}.params)
            %%%%%%%%%%  obj = pest(pest, <empty-plist>)   %%%%%%%%%%
            obj = pest(varargin{1});
            
          elseif isa(varargin{1}, 'org.apache.xerces.dom.DeferredElementImpl') && ...
                 isa(varargin{2}, 'history')
            %%%%%%%%%%   obj = pest(DOM node, history-objects)   %%%%%%%%%%
            obj = fromDom(obj, varargin{1}, varargin{2});
            
          elseif isa(varargin{1}, 'ltpda_uoh') && isa(varargin{2}, 'plist')
            %%%%%%%%%%%   obj = pest(<ltpda_uoh>-object, plist-object)   %%%%%%%%%%
            % always recreate from plist
            
            % If we are trying to load from file, and the file exists, do
            % that. Otherwise, copy the input object.
            if varargin{2}.isparam('filename')
              if exist(fullfile('.', find(varargin{2}, 'filename')), 'file')==2
                obj = pest(varargin{2});
              else
                obj = pest(varargin{1});
              end
            else
              obj = pest(varargin{2});
            end
            
          else
            error('### Unknown 2 argument constructor.');
          end
          
        case 3
          if  isnumeric(varargin{1})                       && ...
              (iscell(varargin{2}) || ischar(varargin{2})) && ...
              isnumeric(varargin{3})
            %%%%%%%%%%   obj = pest(y, paramNames, dy)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y, param names and dy');
            pl = plist(...
              'y',          varargin{1}, ...
              'paramNames', varargin{2}, ...
              'dy',         varargin{3});
            obj.fromValues(pl);
          else
            error('### Unknown 3 argument constructor.');
          end
          
        case 4
          if  isnumeric(varargin{1})                       && ...
              (iscell(varargin{2}) || ischar(varargin{2})) && ...
              isnumeric(varargin{3})                       && ...
              isnumeric(varargin{4})
            %%%%%%%%%%   obj = pest(y, paramNames, dy)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y, param names, dy and cov');
            pl = plist(...
              'y',          varargin{1}, ...
              'paramNames', varargin{2}, ...
              'dy',         varargin{3}, ...
              'cov',        varargin{4});
            obj.fromValues(pl);
          else
            error('### Unknown 4 argument constructor.');
          end
          
        case 5
          if  isnumeric(varargin{1})                       && ...
              (iscell(varargin{2}) || ischar(varargin{2})) && ...
              isnumeric(varargin{3})                       && ...
              isnumeric(varargin{4})                       && ...
              isnumeric(varargin{5})
            %%%%%%%%%%   obj = pest(y, paramNames, dy)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y, param names, dy, cov and chi2');
            pl = plist(...
              'y',          varargin{1}, ...
              'paramNames', varargin{2}, ...
              'dy',         varargin{3}, ...
              'cov',        varargin{4}, ...
              'chi2',       varargin{5});
            obj.fromValues(pl);
          else
            error('### Unknown 5 argument constructor.');
          end
          
        case 6
          if  isnumeric(varargin{1})                       && ...
              (iscell(varargin{2}) || ischar(varargin{2})) && ...
              isnumeric(varargin{3})                       && ...
              isnumeric(varargin{4})                       && ...
              isnumeric(varargin{5})                       && ...
              isnumeric(varargin{6})
            %%%%%%%%%%   obj = pest(y, paramNames, dy)   %%%%%%%%%%
            utils.helper.msg(msg.OPROC1, 'constructing from y, param names, dy, cov, chi2 and dof');
            pl = plist(...
              'y',          varargin{1}, ...
              'paramNames', varargin{2}, ...
              'dy',         varargin{3}, ...
              'cov',        varargin{4}, ...
              'chi2',       varargin{5}, ...
              'dof',        varargin{6});
            obj.fromValues(pl);
          else
            error('### Unknown 6 argument constructor.');
          end
          
        otherwise
          error('### Unknown number of arguments.');
      end
      
    end % End constructor
  end % End public methods
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                              Methods  (Public, hidden)                    %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods (Hidden = true)
    varargout = attachToDom(varargin)
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                              Methods (protected)                          %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods (Access = protected)
    varargout = fromStruct(varargin)
    varargout = fromDom(varargin)
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                               Methods (private)                           %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods (Access = private)
    % Constructors
    varargout = fromValues(varargin)
    varargout = genericSet(varargin)
  end
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                            Methods (static)                               %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  methods (Static)
    
    function mdls = getBuiltInModels(varargin)
      mdls = ltpda_uo.getBuiltInModels('pest');
    end
    
    function out = VEROUT()
      out = '$Id: pest.m,v 1.33 2011/08/22 05:22:23 hewitson Exp $';
    end
    
    function ii = getInfo(varargin)
      ii = utils.helper.generic_getInfo(varargin{:}, 'pest');
    end
    
    function out = SETS()
      out = [SETS@ltpda_uoh, {'From Values'}];
    end
    
    
    function plout = getDefaultPlist(set)
      persistent pl;
      persistent lastset;
      if exist('pl', 'var')==0 || isempty(pl) || ~strcmp(lastset, set)
        pl = pest.buildplist(set);
        lastset = set;
      end
      plout = pl;
    end
    
    function out = buildplist(set)
      
      if ~utils.helper.ismember(lower(pest.SETS), lower(set))
        error('### Unknown set [%s]', set);
      end
      
      out = plist();
      out = pest.addGlobalKeys(out);
      out = buildplist@ltpda_uoh(out, set);
      
      switch lower(set)
        case 'from values'
          % y
          p = param({'y','Best fit parameters'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % parameter names
          p = param({'paramNames','Names of the parameters'}, paramValue.EMPTY_CELL);
          out.append(p);
          % dy
          p = param({'dy','Standard errors of the parameters'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % cov
          p = param({'cov','Covariance matrix of the parameters'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % chi2
          p = param({'chi2','Reduced chi^2 of the final fit'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % dof
          p = param({'dof','Degrees of freedom'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % models
          p = param({'models',['Models used for the fit in which the coefficients were calculated<br>' ...
            'Please notice that the models need to be stored in a <tt>smodel</tt> object!']}, smodel);
          out.append(p);
          % pdf
          p = param({'pdf','Probability density function, as output by MCMC methods'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          % yunits
          p = param({'yunits','A cell-array of units for the parameters.'}, paramValue.EMPTY_CELL);
          out.append(p);
          
      end
    end % function out = getDefaultPlist(varargin)
    
    function obj = initObjectWithSize(n,m)
      obj = pest.newarray([n m]);
    end
    
  end % End static methods
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                         Methods (static, private)                         %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods (Static, Access = private)
  end % End static, private methods
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                         Methods (static, hidden)                          %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
  methods (Static = true, Hidden = true)
    varargout = loadobj(varargin)
    varargout = update_struct(varargin);
  end
  
end
