classdef MbcUserDefinedClass < handle
% Use class to define user-defined model for MBC
% Refer to template <matlabroot>\toolbox\mbc\mbcmodels\@xregusermod\functemplate.m
% 
% Super class for MBC user-defined models

% Copyright 2015-2018 The MathWorks, Inc.

%% Model properties
properties (Abstract)
    ParamNames      % Cell string of all parameter names
    NumFactors      % Number of factors for model
    DisplayOptIter  % Set as true to display optimization results each iteration
    OptOptions      % Structure of optimization options
    MdlName         % Display name of model
end

%% Model methods
methods
    %% Evaluate model (Required)
    y = Evaluate(obj, X)
%     function y = Evaluate(obj, X)
%         y = [];
%     end
    
    %% Init parameters (Required)
    InitParams(obj,X,Y)
%     function InitParams(obj,X,Y)
%         
%     end
%     
    %% Linear constraints (Optional)
%     [A, c] = LinearCon(obj, X, Y)
    function [A, c] = LinearCon(obj, X, Y)
        % Linear A, Linear b  (A*b<c)
        A = [];
        c = [];
    end
    %% Nonlinear constraints (Optional)
%     [c, ceq] = NonLinearCon(obj, X, Y)
    function [c, ceq] = NonLinearCon(obj, X, Y)
        c = [];
        ceq = [];
    end
    
    %% Set derived parameter values (Optional)
    % Ran when after setting param values SetParamsFromVec
%     SetDerivedParams(obj)
    function SetDerivedParams(obj)
        
    end

end

%% Public called methods
methods (Static)
%     %% Constructor
%     function obj = MbcUserDefinedClass()
%         obj.DisplayOptIter = false;
%         obj.OptOptions = [];
%     end
    
    %% Call user-defined function
    % user-defined model called by Model-Based Calibration Toolbox
    function varargout = CallUserDefinedFcn(m,x,varargin)
        % Create object
        FcnName = name(m);
        DotLoc = strfind(FcnName,'.');
        obj = eval(FcnName(1:(DotLoc-1)));
        
        % model parameters 
        b= double(m);
        if isnumeric(x)
           % evaluation of user-defined model is defined here

           % function definition
           % make sure the expression is vectorised
           obj.SetParamsFromVec(b);
           y = obj.Evaluate(x);

           % this line must not be changed
           varargout{1}= y;
        else
            %--------------------------------------------------------------------------
            % DO NOT CHANGE THIS CODE
            % x specifies which internal function to evaluate.
            % All internal functions are prefixed by 'i_'.
            % The model and model parameters are passed to all internal functions
            if ~isempty(varargin)
                [varargout{1:nargout}] = eval(sprintf('obj.i_%s(m,b,varargin{:})',lower(x))); %str2func(sprintf('obj.i_%s',lower(x)));
            else
                [varargout{1:nargout}] = eval(sprintf('obj.i_%s(m,b)',lower(x))); 
            end
%             [varargout{1:nargout}]= fInternal(m,b,varargin{:});
            %--------------------------------------------------------------------------
        end
    end
end

methods
    %% Check in method
    function CheckIn(obj,X)
        checkin(xregusermod, [class(obj), '.CallUserDefinedFcn'], X);      
    end
end

%% Protected methods
methods (Access = protected)
    
    %% Lower boundary
    function LB = LowerBound(obj)
        LB = zeros(length(obj.ParamNames),1);
        for i = 1:length(LB)
            LB(i) = obj.([obj.ParamNames{i}, '_lim'])(1);
        end
    end
    
    %% Upper boundary
    function UB = UpperBound(obj)
        UB = zeros(length(obj.ParamNames),1);
        for i = 1:length(UB)
            UB(i) = obj.([obj.ParamNames{i}, '_lim'])(2);
        end
    end
    
    %% GetNumParams
    function NumParams = GetNumParams(obj)
        NumParams = length(obj.ParamNames);
    end
    
    %% SetParamsFromVec
    function SetParamsFromVec(obj, b)
        for i = 1:length(b)
            obj.(obj.ParamNames{i}) = b(i);
        end
        obj.SetDerivedParams;
    end
    
    %% GetPramVec
    function b = GetParamVec(obj)
        b = zeros(length(obj.ParamNames),1);
        for i = 1:length(b)
            b(i) = obj.(obj.ParamNames{i});
        end
    end
end
%% Protected methods from template
% Refer to template <matlabroot>\toolbox\mbc\mbcmodels\@xregusermod\functemplate.m
methods (Access = protected)
    % You must specify the following functions. All internal functions in this
    % template file must exist in your user-defined model MATLAB file. See
    % xregusermod/weibul for an example. 

    %***************************************************************
    % User-defined internal functions
    %***************************************************************

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Mandatory internal functions
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    % [param,OK] = i_initial(m,b,X,Y);  % initial parameter values
    % n = i_nfactors(m,b,varargin);     % number of input factors

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Optional internal functions
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    % J = i_jacobian(m,b,X);           % analytic jacobian
    % fopts = i_foptions(m,b,fopts);   % optimization parameters
    % [LB,UB,A,c,nlcon,optparams] = i_constraints(m,b,X,Y);
    %                                  % define parameter constraints
    % g = i_nlconstraints(m,b,X,Y);    % evaluate nonlinear constraints
    % str = i_char(m,b);               % display string for function
    % str = i_str_func(m,b);           % one line summary of function

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Optional local regression internal functions
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    % names = i_rfnames(m,b)                 % nonlinear response feature names
    % [rf,dG] = i_rfvals(m,b)                % nonlinear response feature evaluation
    % p = i_reconstruct(m,b,Yrf,dG,rfuser)   % nonlinear local model reconstruction


    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Mandatory internal functions
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    function n = i_nfactors(obj,m,b,varargin)
        %I_NFACTORS number of input factors to model
        %
        % n = i_nfactors(m,b);
        n= obj.NumFactors;
    end
    function n = i_numparams(obj,m,b,varargin)  %#ok<INUSD,DEFNU>
        %I_NUMPARAMS number of parameters to be estimated
        %
        % n = i_numparams(m,b);

        n= length(obj.ParamNames);
    end

    function [b0,OK] = i_initial(obj,m,b,X,Y) %#ok<INUSD,DEFNU>
        %I_INITIAL Initial parameter values
        %
        % [b0,OK]= i_initial(m,b);  initial parameters independent of data
        % [b0,OK]= i_initial(m,b,X,Y); initial parameters dependent on data
        %   Outputs
        %     b0   Initial parameter values
        %     OK   model can be fitted
        if nargin > 4
            obj.InitParams(X,Y)
        else
            obj.InitParams()
        end
        OK = 1;
        b0 = obj.GetParamVec;
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Optional internal functions are below here
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    function [LB,UB,A,c,nlcon,optparams] = i_constraints(obj,m,b,X,Y, varargin) %#ok<INUSD,DEFNU>
        %I_CONSTRAINTS constraint definition for least squares fitting
        % 
        % [LB,UB,A,c,nlcon,optparams]= i_constraints(m,b);
        % [LB,UB,A,c,nlcon,optparams]= i_constraints(m,b,X,Y);
        %   Inputs
        %     m      user-defined model
        %     b      model parameter values
        %     X,Y    fitting data
        %   Outputs
        %     LB,UB  bounds on parameter values
        %     A,c    linear constraints on parameter values
        %     nlcon  number of nonlinear constraints
        %     optparams optional optimization parameter

        obj.SetParamsFromVec(b);
        
        % Lower Bounds, Upper Bounds  
        % make sure these are column vectors
        LB = obj.LowerBound; 
        UB = obj.UpperBound;

        % It is possible to make constraints that are dependent on the fitting
        % data. However, it is necessary to check that 4 inputs are provided to
        % this function before using X and Y

        % Linear A, Linear b  (A*b<c)
        if nargin < 5
            [A, c] = obj.LinearCon;
        else
            [A, c] = obj.LinearCon(X,Y);
        end
        

        % Number of NonLinear constraints
        if nargin < 5
            NonLinCon = obj.NonLinearCon;
        else
            NonLinCon = obj.NonLinearCon(X,Y);  
        end
        nlcon = length(NonLinCon);
        
        % You must define the nonlinear constraints in 'i_nlconstraints' function below if nlcon>0

        % Note that fmincon will be used if A and b are not empty, or nlcon>0
        % otherwise lsqnonlin is used

        % optional parameters for cost function
        optparams = [];
    end
    function g = i_nlconstraints(obj,m,b,X,Y,varargin) %#ok<INUSD,DEFNU>
        %I_NLCONSTRAINTS evaluate nonlinear constraints
        %
        % g = i_nlconstraints(m,b,X,Y);
        obj.SetParamsFromVec(b);
        g = obj.NonLinearCon(X,Y);
    end

    function fopts = i_foptions(obj,m,b,fopts) %#ok<INUSL,DEFNU>
        %I_FOPTIONS optimization options
        %
        % fopts = i_foptions(m,b,fopts) 
        %   fopts is the structure for optimization options. See optimset for more
        %   details. It is only possible to change the options 'Display',
        %   'MaxIter', 'TolFun', 'MaxFunEvals' or 'TolX'. 
        % 
        % Always base the output on input fopts. 
        if iscell(fopts)
            fopts = fopts{1};
        end
        if isstruct(obj.OptOptions)
            OptFields = fieldnames(obj.OptOptions);
            for i = 1:length(OptFields)
                fopts.(OptFields{i}) = obj.OptOptions.(OptFields{i});
            end
            fopts.Display = 'off';
        end
        if obj.DisplayOptIter
            fopts.Display = 'Iter';
        end
    end
    function J = i_jacobian(obj,m,b,X) %#ok<INUSD,DEFNU>
        %I_JACOBIAN analytic jacobian (if defined)
        %
        % J = i_jacobian(m,b,X);

        % return empty matrix if not defined. A numeric Jacobian will be used.
        J = [];
    end

    function c = i_labels(obj,m,b,varargin) %#ok<INUSD,DEFNU>
        %I_LABELS user parameter names 
        %
        % c = i_labels(m,b);
        %   By default the variable names defined in i_simvars are used to specify
        %   parameter names.
        c = obj.ParamNames;
    end
    function str = i_char(obj,m,b,varargin) %#ok<INUSD,DEFNU>
        %I_CHAR display equation string
        % 
        % str = i_char(m,b);
        %     The default name for a user-defined model is 
        %     'ModelName(ParametersNames, b = [ParameterValues])'

        %  define str as '' to use the default name
        str=obj.MdlName;
    end
    function str = i_str_func(obj,m,b) %#ok<INUSD,DEFNU>
        %I_STR_FUNC short name for model
        %
        % str = i_str_func(m,b);
        %   The default name for a user-defined model is the model name.

        %  define str as '' to use the default name
        str= obj.MdlName;
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Optional local regression functions
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    function [rfnames,InitialRFs] = i_rfnames(obj,m,b,varargin) %#ok<INUSD,DEFNU>
        %I_RFNAMES response feature names 
        % 
        % [rfnames,InitialRFs] = i_rfnames(m,b)
        %   Outputs 
        %     rfnames Cell array of nonlinear response feature names
        %     InitialRFs   Initial response features (indices) for user-defined model


        % cell array of response feature names 
        rfnames= {};
        % default is to select the model parameters for response features
        InitialRFs =  1:numParams(m);
    end
    function [rf,dG] = i_rfvals(obj,m,b,varargin) %#ok<INUSD,DEFNU>
        %I_RFVALS evaluate response features and Jacobian of response features 
        %
        % [rf,dG] = i_rfvals(m,b);
        %   Outputs
        %     rf     response feature values
        %     dG     Jacobian of response features with respect to parameters
        % Model parameters are automatically available as response features
        %
        % See xregusermod/richard (i_rfvals) for an example of how to use this
        % internal function

        rf= [];

        if nargout>1
           % Jacobian of response features with respect to model parameters
           dG= [];   
        end   
    end
    function p = i_reconstruct(obj,m,b,Yrf,dG,rfuser) %#ok<INUSD,DEFNU>
        %I_RECONSTRUCT nonlinear reconstruction
        %
        % p = i_reconstruct(m,b,Yrf,dG,rfuser)
        %  Inputs
        %     Yrf   response feature values
        %     dG    default Jacobian of response features
        %     rfuser index to the user-defined response features so you can find
        %     out which response features are which. rfuser(i) = 0 if the
        %     rf is a parameter.
        %
        % If all response features are linear in model parameters then you do not
        % need to define 'i_reconstruct'. 
        %
        % See xregusermod/weibul (i_reconstruct) for an example of how to use this
        % internal function

        p= [];
    end
end
end

