classdef incrementalClassificationECOC < incremental.classif.ClassificationModel
    %incrementalClassificationECOC Multiclass classification model
    %   for incremental learning. This model can train with and predict responses 
    %   for new data.
    %
    %   incrementalClassificationECOC properties:
    %       BinaryLearners              - Trained binary learners, specified as a cell vector of model objects. 
    %                                     The number of binary learners depends on the number of classes in Y 
    %                                     and the coding design.
    %       BinaryLoss                  - Binary learner loss function.
    %       CodingMatrix                - Class assignment codes for the binary learners.
    %       CodingName                  - Coding design name, specified as a character vector.
    %       Decoding                    - String specifying the decoding scheme.
    %       ClassNames                  - Names of classes in Y.
    %       Prior                       - Prior class probabilities.
    %       ScoreTransform              - Transformation applied to predicted classification scores.
    %       NumPredictors               - Number of predictors.
    %       NumTrainingObservations     - Number of observations used for training the incremental model.
    %       MetricsWarmupPeriod         - Number of observations that are used for training
    %                                     prior to metrics tracking.
    %       MetricsWindowSize           - Window size for window metrics tracking.
    %       IsWarm                      - Flag to indicate if incremental model is warm.
    %       Metrics                     - Metrics information.
    %
    %   incrementalClassificationECOC methods:
    %       incrementalClassificationECOC     - Construct incremental
    %                                           classification ECOC model.
    %       fit                               - Train model with new data.     
    %       loss                              - Classification loss.
    %       perObservationLoss                - Per observation classification loss.
    %       predict                           - Predict responses of the model.
    %       reset                             - Reset model.
    %       updateMetrics                     - Update metrics with new data.
    %       updateMetricsAndFit               - Update metrics and train model with new data.
    %
    %
    %   Example 1: Initialize and train an incremental ecoc model without providing any
    %   information.
    %
    %       mdl  = incrementalClassificationECOC('MaxNumClasses',5);
    %       mdl  = updateMetricsAndFit(mdl,X,Y);
    %
    %   Example 2: Initialize an incremental ecoc model using a traditionally trained model
    %   and continue incremental training.
    %
    %       ttModel = fitcecoc(Xoffline,Yoffline);
    %       mdl = incrementalLearner(ttModel);
    %       mdl = updateMetrics(mdl,Xnew,Ynew,'Weights',Wnew,'ObservationsIn','columns');
    %       mdl = fit(mdl,Xnew,Ynew,'Weights',Wnew,'ObservationsIn','columns');
    %
    %   See also classreg.learning.classif.CompactClassificationECOC.incrementalLearner.

    %   Copyright 2021-2023 The MathWorks, Inc.

    properties (Dependent, SetAccess = protected)

        % BinaryLearners A cell array of incremental binary learners
        BinaryLearners
        
        % Binary learner loss function
        BinaryLoss

        % CodingName Coding design name, specified as a character vector.
        CodingName

        % CodingMatrix Class assignment codes for the binary learners
        CodingMatrix

        % Decoding
        Decoding
    end
    
    properties (Constant,Access = protected)
        IsBinary = false
        IsAdaptive = false
        LabelPredictor = @incrementalClassificationECOC.maxScore
        HasCost = false
        CanSupportCategorical = false
        HasAdditionalPredictionInputs = true
    end
    
    properties (Access = private)
        pModelSpecificMetrics = 'classiferror'
    end
    
    properties(Constant, Access = protected)
        DefaultLoss = 'classiferror'
    end
    
    properties (SetAccess = protected,Hidden)
        Impl = [];
    end

    methods (Access = protected)
        function [modelArgs,remainingArgs] = parseMakeParameters(~,params)
           [modelArgs,remainingArgs] = incremental.impl.ECOCImpl.parseMakeParameters(params);
        end

        function this = makeModel(this,parametersAreFromOffline,implParams)
           this.Impl = incremental.impl.ECOCImpl.make(parametersAreFromOffline,implParams);
        end

        function [trainingArgs,remainingArgs] = parseTrainingParameters(this,params)            
           [trainingArgs,remainingArgs] = parseTrainingParameters(this.Impl,params);
        end 
        
        function this = fillTrainingParameters(this,parametersAreFromOffline,trainingParams)            
           this.Impl = fillTrainingParameters(this.Impl,parametersAreFromOffline,trainingParams);
        end
 
        function [classArgs,remainingArgs] = parseDataParameters(this,params)
            [classArgs,remainingArgs] = parseDataParameters@incremental.classif.ClassificationModel(this,params);
        end    
        
        function this = fillStateParameters(this,parametersAreFromOffline,numPredictors,stateParams)
            this.Impl = fillStateParameters(this.Impl,parametersAreFromOffline,this.MaxNumClasses,numPredictors,stateParams,this.Prior,this.NumClasses);
        end

        function [stateArgs,remainingArgs] = parseStateParameters(this,params)            
           [stateArgs,remainingArgs] = parseStateParameters(this.Impl,params);
        end  

        function this = updateStateParameters(this,numPredictors,~)
            this.Impl = updateStateParameters(this.Impl,this.MaxNumClasses,numPredictors,this.Prior,this.NumClasses);
        end

        function [this,X,Y,W,WisRaw,C] = prepareDataAndUpdateState(this,Xraw,Yraw,Wraw,obsInRows,numObservations)
            % update Prior, ClassNames and Weights if needed
            [this,X,Y,W,WisRaw,C] = prepareDataAndUpdateState@incremental.classif.ClassificationModel(this,Xraw,Yraw,Wraw,obsInRows,numObservations);
            this = updateStateParameters(this,this.NumPredictors,this.PredictorClass);
        end

        function [numPredictors,predictorClass] = predictorAttributes(this)
                [numPredictors,predictorClass] = predictorAttributes(this.Impl);
        end
        
        function this = fitModelWithoutValidation(this,X,Y,W,WisRaw,obsInRows,numObservations)
            this.Impl = fit(this.Impl,X,Y,W,WisRaw,obsInRows,numObservations);
        end
    end
    
    methods (Access = protected)
        function [predArgs,remainingArgs] = parsePredictionArgs(this,varargin)
            narginchk(1,7);
            [predArgs,remainingArgs] = parsePredictionArgs@incremental.classif.ClassificationModel(this,varargin{:});
            p = inputParser;
            p.StructExpand = true;
            p.KeepUnmatched = true;
            addParameter(p,'Decoding',this.Decoding);
            addParameter(p,'BinaryLoss',this.BinaryLoss);
            parse(p,remainingArgs);
            predArgs.Decoding = p.Results.Decoding;
            predArgs.BinaryLoss = p.Results.BinaryLoss;
            remainingArgs = p.Unmatched;
            
            % Cost and prior are needed for validating predict arguments in
            % ClassificaitonModel
            predArgs.Cost = [];
            predArgs.Prior = [];

            % No parameters should remain
            f = fieldnames(remainingArgs);
            if ~isempty(f)
                error(message('stats:incremental:Learner:UnrecognizedNV',f{1}));
            end
        end
    end
    
    methods       
        function this = incrementalClassificationECOC(varargin) 
            %INCREMENTALCLASSIFICATIONECOC Construct an ECOC classification model for 
            %   incremental learning.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONECOC('MAXNUMCLASSES',MaxNumClasses) 
            %   returns an ecoc classification model for incremental learning. 
            %   MaxNumClasses is the maximum number of classes expected in the response 
            %   data during incremental learning. Default values of other parameters can be placeholders.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONECOC('CLASSNAMES',ClassNames) specifies
            %   all class names ClassNames expected in the response data during incremental learning.
            %   Default values of other parameters can be placeholders.
            %
            %   MODEL=INCREMENTALLEARNER(TTMODEL) returns an ECOC classification model 
            %   for incremental learning initialized using the parameters provided in TTMODEL.
            %   For details on how INCREMENTALLEARNER is used for initialization of a incremental
            %   classification model, see the INCREMENTALLEARNER.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONECOC('PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value arguments:
            %       'BinaryLoss'                 - Binary learner loss function, specified as a built-in 
            %                                      loss function name or function handle .
            %                                      Default: 'hinge' 
            %       'ClassNames'                 - Array of class names, specified as a categorical array, logical
            %                                      vector, numeric vector, string array or
            %                                      cell array of strings.
            %                                      If MODEL is initialized from TTMODEL,
            %                                      TTMODEL.ClassNames is used.
            %                                      Default: 'ClassNames' is inferred when fit is called.
            %       'Coding'                     - Either a coding design name or a matrix of size
            %                                      K-by-L for K classes and L binary learners.
            %                                      * If you pass 'Coding' as a character vector or
            %                                        string scalar, use one of:
            %                                        'onevsone' or 'allpairs'
            %                                        'onevsall'
            %                                        'binarycomplete'
            %                                        'ternarycomplete'
            %                                        'ordinal'
            %                                        'sparserandom'
            %                                        'denserandom'
            %                                    * If you pass 'Coding' as a matrix, compose this
            %                                      matrix using the following rules:
            %                                       - Every element of this matrix must be one
            %                                         of: -1, 0, and +1.
            %                                       - Every column must contain at least one -1
            %                                         and at least one +1.
            %                                       - There are no equal columns or columns
            %                                         equal after a sign flip.
            %                                       - There are no equal rows.
            %                                      Default: 'onevsone'.
            %       'Decoding'                   - String specifying the decoding scheme, either
            %                                     'lossbased' or 'lossweighted'. 
            %                                      Default: 'lossweighted'             
            %       'Learners'                   - Binary learner templates, specified as 
            %                                      "linear", "kernel", an incremental
            %                                      learning object, a template object,
            %                                      or a cell array of supported incremental 
            %                                      learning objects and template objects. 
            %                                      * "linear" or "kernel" —
            %                                         Specify the Learners value as a
            %                                         string scalar or character vector to
            %                                         use the default linear learners or
            %                                         default kernel learners (default
            %                                         incrementalClassificationLinear or
            %                                         incrementalClassificationKernel
            %                                         objects, respectively).
            %                                      * Incremental learning object
            %                                        (incrementalClassificationLinear or
            %                                         incrementalClassificationKernel
            %                                         object) — Configure binary
            %                                         learner properties (both
            %                                         model-specific properties and
            %                                         incremental learning properties)
            %                                         when you create an incremental
            %                                         learning object, and pass the object
            %                                         to incrementalClassificationECOC as
            %                                         the Learners value.
            %                                       * Template object returned by the
            %                                         templateLinear, templateSVM, or
            %                                         templateKernel function — Configure
            %                                         model-specific properties when you create a
            %                                         template object, and pass the object to
            %                                         incrementalClassificationECOC as the
            %                                         Learners value. Use this option to specify
            %                                         model properties with a template object and
            %                                         to use the default
            %                                         incremental learning options.
            %                                       * Cell array of supported incremental
            %                                         learning objects and template objects — Use
            %                                         this option to customize each learner
            %                                         individually.
            %                                      Default: 'linear'.
            %        'MaxNumClasses'              - Positive scalar specifying the maximum number of
            %                                      classes that this incremental ecoc classifier
            %                                      will be trained on.
            %                                      Default: 'MaxNumClasses' is a required argument
            %                                      unless 'ClassNames' is specified, in which case,
            %                                      'MaxNumClasses' is the number of classes in
            %                                      'ClassNames'. If MODEL is trained from TTMODEL,
            %                                      'MaxNumClasses' is the number of classes
            %                                      TTMODEL was trained on.
            %       'Metrics'                    - String vector representing built-in
            %                                      metrics, cell array of function handles
            %                                      or a struct of function handles specifying
            %                                      metrics that are tracked during incremental
            %                                      training. 'Metrics' can be a
            %                                      combination of strings, function
            %                                      handles or struct of function handles
            %                                      passed as a cell array.
            %                                      Built-in metrics for classification: 'classiferror'.
            %                                      If you pass a function handle FUN, it
            %                                      is called as shown below:
            %                                          FUN(C,S)
            %                                      where C is an N-by-K logical matrix for N
            %                                      elements in Y and K classes in the ClassNames
            %                                      property, S is an N-by-K numeric matrix.
            %                                      C has one true per row for the true class.
            %                                      S is a matrix of predicted scores for classes
            %                                      with one row per observation, similar to score
            %                                      output from predict. 
            %                                      Metric name is extracted from provided function
            %                                      handle if it's not an anonymous
            %                                      function handle. If it is an anonymous
            %                                      function handle, metric is named
            %                                      as 'CustomMetric' and it is postfixed
            %                                      with an underscore and an integer
            %                                      identifying its relative position in
            %                                      'Metrics' property.
            %                                      If you pass a struct, fieldnames are
            %                                      treated as the metric names and
            %                                      values must be function handles.
            %                                      Incremental models track two values for
            %                                      each metric; a 'Cumulative' value and a
            %                                      'Window' value. 'MetricsWindowSize' determines
            %                                      the update frequency of 'Window' metrics.
            %                                      Default: 'classiferror'.      
            %       'MetricsWarmupPeriod'        - Nonnegative integer specifying the number of
            %                                      observations that are used for
            %                                      training the model prior to metrics
            %                                      update. If MODEL is initialized
            %                                      from TTMODEL, default is 0; otherwise, it is 1000.
            %       'MetricsWindowSize'          - Positive integer specifying the window
            %                                      size that is used for metrics
            %                                      update. Default value is 200.
            %       'NumPredictors'              - Non-negative scalar specifying the number of
            %                                      predictors.  If MODEL is initialized
            %                                      from TTMODEL, number of predictors from TTMODEL is used.
            %                                      Default: 'NumPredictors' is inferred when fit is called.
            %       'Prior'                      - Prior probabilities for each class. Specify as one
            %                                      of:
            %                                      * A string:
            %                                        - 'empirical' determines class probabilities
            %                                           from class frequencies in Y
            %                                        - 'uniform' sets all class probabilities equal
            %                                      * A vector (one scalar value for each class)
            %                                        If you pass numeric values, they are normalized to
            %                                        add up to 1.
            %                                      If MODEL is initialized from TTMODEL,
            %                                      TTMODEL.Prior is used.
            %                                      Default: 'empirical'.
            %                                      If 'ClassNames' is not provided, Prior must be 'empirical'.
            %       'ScoreTransform'             - Function handle for transforming scores, or
            %                                      string representing a built-in transformation
            %                                      function. Available functions: 'symmetric',
            %                                      'invlogit', 'ismax', 'symmetricismax', 'none',
            %                                      'logit', 'doublelogit', 'symmetriclogit', and
            %                                      'sign'.
            %                                      If MODEL is initialized from TTMODEL,
            %                                      TTMODEL.ScoreTransform is used.
            %                                      Default: 'none'
            %       'UpdateBinaryLearnerMetrics' - Flag to indicate if binary learner metrics should be tracked.
            %                                      Default: false
            %   Example 1: Initialize and train an incremental ecoc model without providing any
            %   information.
            %
            %       mdl  = incrementalClassificationECOC('MaxNumClasses',5);
            %       mdl  = updateMetricsAndFit(mdl,X,Y);
            %
            %   Example 2: Initialize an incremental ecoc model using a traditionally trained model
            %   and continue incremental training.
            %
            %       ttModel = fitcecoc(Xoffline,Yoffline);
            %       mdl = incrementalLearner(ttModel);
            %       mdl = updateMetrics(mdl,Xnew,Ynew,'Weights',Wnew);
            %       mdl = fit(mdl,Xnew,Ynew,'Weights',Wnew);
            %
            %   See also incrementalLearner.

            this = this@incremental.classif.ClassificationModel(varargin{:});           
            this.WeightsNeededForPredictorClass = true;
        end        

        function [varargout] = predict(this,X,varargin)
        %PREDICT Predict responses of the model.
        %   [LABEL,NEGLOSS,PBSCORE]=PREDICT(MODEL,X) returns predicted
        %   class labels, negated average binary losses and positive-class
        %   scores for model MODEL and predictors X. Pass X as a matrix
        %   with N observations from P predictors. Classification labels
        %   LABEL are an N-by-1 array of the same type as Y used for
        %   training, where N is the number of observations (rows in X).
        %   Scores SCORE are an N-by-K numeric matrix for N observations
        %   and K classes. Negative loss values NEGLOSS are an N-by-K
        %   matrix for N observations and K classes, where N is the number
        %   of observations (rows) in X. PBSCORE is an N-by-L matrix for N
        %   observations and L binary learners. The predicted label is
        %   assigned to the class with the largest negated average binary
        %   loss, or equivalently smallest average binary loss.
        %
        %   [LABEL,NEGLOSS,PBSCORE]=PREDICT(MODEL,X,'PARAM1',val1,'PARAM2',val2,...) specifies
        %   optional parameter name/value pairs:
        %       'ObservationsIn'   - Orientation of X, specified as either 'rows' or
        %                            'columns'. Default: 'rows'.
        %       'BinaryLoss'       - Function handle, or string representing a
        %                            built-in function for computing loss
        %                            induced by each binary learner. Available
        %                            loss functions for binary learners with
        %                            scores in the (-inf,+inf) range are:
        %                            'hamming', 'linear', 'exponential',
        %                            'binodeviance', 'hinge', 'logit', and
        %                            'quadratic'. Available loss functions for
        %                            binary learners with scores in the [0,1]
        %                            range are: 'hamming' and 'quadratic'. If
        %                            you pass a function handle FUN, PREDICT
        %                            calls it as shown below:
        %                                  FUN(M,F)
        %                            where M is a K-by-L coding matrix saved in
        %                            the CodingMatrix property and F is a
        %                            1-by-L row-vector of scores computed by
        %                            the binary learners. Default: Value of the
        %                            BinaryLoss property
        %       'Decoding'         - String specifying the decoding scheme, either
        %                            'lossbased' or 'lossweighted'. Default:
        %                            'lossweighted'

            
            validatePredictor(this);
            narginchk(2,8);            
            [predictionArgs,remainingArgs] = this.parsePredictionArgs(varargin{:});
            obsInRows = this.parseObservationsInInput(remainingArgs);
            [decoding,binaryloss,prior,scoreTransform] = validatePredictArgs(this,predictionArgs);
            [varargout{1:nargout}] = predictWithoutValidation(this,X,obsInRows,decoding,binaryloss,prior,scoreTransform);
        end
        
        function this = fit(this,X,Y,varargin)
            %FIT Train model with new data.
            %   MODEL=FIT(MODEL,X,Y) returns the modified MODEL
            %   trained incrementally with new data X and Y.
            %   MODEL.Metrics are not updated inside fit.
            %   Pass X as a matrix with N observations from P predictors.
            %   Pass Y as a categorical array, character array, logical
            %   vector, numeric vector, string
            %   array or cell array of strings. If Y is a character array,
            %   it must have one class label per row. Otherwise Y
            %   must be a vector with N elements.
            %   If MODEL.ClassNames is specified, Y must be of the same data type as
            %   MODEL.ClassNames. If MODEL.ClassNames is not defined,
            %   MODEL.ClassNames is inferred from data.
            %   Y must have 'MaxNumClasses' or less number of classes.
            %
            %   MODEL=FIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - Orientation of X, specified as either 'rows' or
            %                            'columns'. Default: 'rows'.
            %       'Weights'          - Vector of observation weights. By default the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationECOC, updateMetricsAndFit, updateMetrics, loss.
            
            
            this = fit@incremental.SimpleLearner(this,X,Y,varargin{:});
        end

        function l = loss(this,X,Y,varargin)
            %LOSS Model error.
            %   ERR=LOSS(MODEL,X,Y) returns error for model MODEL computed
            %   using predictors X and true observed values Y.
            %   Pass X as a matrix with N observations from P predictors.
            %   Y must be of the same data type as
            %   MODEL.ClassNames and have N elements. ERR is a scalar.
            %
            %   ERR=LOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'ObservationsIn' - If MODEL was trained using 'linear' binary
            %                          learners, you can pass specify
            %                          orientation of X, either 'rows' or 'columns'.
            %                          Default: 'rows'.
            %                          NOTE: Passing observations in columns for
            %                               the 'linear' learner can significantly
            %                               speed up prediction.
            %       'LossFun'    - Function handle for loss, or string
            %                      'classiferror' representing a built-in loss function.
            %                      If you pass a function handle FUN, LOSS calls it as
            %                      shown below:
            %                            FUN(C,S,W)
            %                      where C is an N-by-K logical matrix for N
            %                      elements in Y and K classes in the
            %                      ClassNames property, S is an N-by-K numeric
            %                      matrix and W is a numeric vector with N
            %                      elements. C has one true per row for the
            %                      true class. S is a matrix of  negated loss
            %                      values for classes with one row per
            %                      observation, similar to NEGLOSS output from
            %                      PREDICT. W is a vector of observation
            %                      weights. Default:
            %                      'classiferror'
            %       'Weights'    - Vector of observation weights. By default the weight
            %                      of every observation is set to 1. The length of this
            %                      vector must be equal to the number of rows in X.
            %                      If X is a table, this may be the name of a variable
            %                      in the table.
            %       'BinaryLoss' - Function handle, or string representing a built-in
            %                      function for computing loss induced by each binary
            %                      learner. Available loss functions for binary
            %                      learners with scores in the (-inf,+inf) range are:
            %                      'hamming', 'linear', 'exponential', 'binodeviance',
            %                      'hinge', and 'logit'. Available loss functions for
            %                      binary learners with scores in the [0,1] range are:
            %                      'hamming' and 'quadratic'. If you pass a function
            %                      handle FUN, PREDICT calls it as shown below:
            %                            FUN(M,F)
            %                      where M is a K-by-L coding matrix saved in the
            %                      CodingMatrix property and F is a 1-by-L row-vector
            %                      of scores computed by the binary learners. Default:
            %                      Value of the BinaryLoss property
            %       'Decoding'   - String specifying the decoding scheme, either
            %                      'lossbased' or 'lossweighted'.
            %                      Default: 'lossweighted'
            %
            %   See also incrementalClassificationECOC, updateMetrics, updateMetricsAndFit, fit, predict.

            l = loss@incremental.SimpleLearner(this,X,Y,varargin{:}); 
        end
        
        function  this = updateMetrics(this,X,Y,varargin)
            %UPDATEMETRICS Update metrics with new data.
            %   MODEL=UPDATEMETRICS(MODEL,X,Y) returns the modified MODEL
            %   for which the metrics, specified by 'Metrics' Name-Value pair
            %   during object construction, are updated with new data
            %   X and Y.
            %   Pass X as a matrix with N observations from P predictors.
            %   For classification models, Y must be of the same data type as
            %   MODEL.ClassNames and have N elements.
            %
            %   MODEL=UPDATEMETRICS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - Orientation of X, specified as either 'rows' or
            %                            'columns'. Default: 'rows'.
            %       'Weights'          - Vector of observation weights. By default, the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationECOC, updateMetricsAndFit, fit, loss, predict.
        
            this = updateMetrics@incremental.SimpleLearner(this,X,Y,varargin{:});
        end
        
        function  this = updateMetricsAndFit(this,X,Y,varargin)
            %UPDATEMETRICSANDFIT Update metrics and train model with new data.
            %   MODEL=UPDATEMETRICSANDFIT(MODEL,X,Y) returns the MODEL
            %   with the following modifications:
            %           1. Metrics, specified by 'Metrics' Name-Value pair
            %              during object construction, are updated.
            %           2. Model trained incrementally with new X and Y.
            %
            %   Pass X as a matrix with N observations from P predictors.
            %   For classification models, Y must be of the same data type as
            %   MODEL.ClassNames and have N elements.
            %
            %   MODEL=UPDATEMETRICSANDFIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - Orientation of X, specified as either 'rows' or
            %                            'columns'. Default: 'rows'.
            %       'Weights'          - Vector of observation weights. By default, the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationECOC, updateMetrics, fit, loss, predict.
        
            this = updateMetricsAndFit@incremental.SimpleLearner(this,X,Y,varargin{:});
        end

        function l = perObservationLoss(this,X,Y,varargin)
            %PEROBSERVATIONLOSS Model per observation classification error.
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y) returns per observation
            %   classification error for model MODEL computed using predictors X and
            %   true labels Y. Pass X as a matrix with N observations from P predictors. 
            %   Y must be of the same data type as MODEL.ClassNames and have N
            %   elements. ERR is a N-by-1 vector.
            %
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'ObservationsIn'   - Orientation of X, specified as either 'rows' or
            %                            'columns'. Default: 'rows'.
            %       'LossFun'          - Function handle for loss, or string
            %                            representing a built-in loss function.
            %                            Available loss functions for classification:
            %                            'classiferror'.
            %                            If you pass a function handle FUN, it
            %                            is called as shown below:
            %                                  FUN(C,S)
            %                            where C is an N-by-K logical matrix for N
            %                            elements in Y and K classes in the ClassNames
            %                            property and S is an N-by-K numeric matrix. 
            %                            C has one true per row for the true class. 
            %                            S is a matrix of predicted scores for classes
            %                            with one row per observation, similar to SCORE 
            %                            output from PREDICT.
            %                            Default: 'classiferror'.
            %
            %   See also incrementalClassificationECOC, updateMetrics, updateMetricsAndFit, fit,
            %   predict, loss.
            l = perObservationLoss@incremental.SimpleLearner(this,X,Y,varargin{:});
        end         
    end

    methods       
        function binarylearners = get.BinaryLearners(this)
            binarylearners = this.Impl.BinaryLearners;
        end 

        function codingname = get.CodingName(this)
            codingname = this.Impl.Coding;
        end 

        function codingmatrix = get.CodingMatrix(this)
            codingmatrix = this.Impl.CodingMatrix;
        end

        function decoding = get.Decoding(this)
            decoding = this.Impl.Decoding;
        end
        
        function binaryLoss = get.BinaryLoss(this)
            binaryLoss = this.Impl.BinaryLoss;
        end

    end
    
    methods (Access = private)
        function [tDecoding,tBinaryLoss,tPrior,tScoreTransform] = validatePredictArgs(this,predArgs)
            
            if ~isempty(predArgs)
                if ~isempty(predArgs.Decoding)
                    tDecoding = convertStringsToChars(predArgs.Decoding);
                else
                    tDecoding = this.Decoding;
                end

                if ~isempty(predArgs.BinaryLoss)
                    tBinaryLoss = convertStringsToChars(predArgs.BinaryLoss);
                else
                    tBinaryLoss = this.BinaryLoss;
                end

                if ~isempty(predArgs.ScoreTransform)
                    st = convertStringsToChars(predArgs.ScoreTransform);                    
                else
                    st = this.ScoreTransform;
                end
                tScoreTransform = classreg.learning.internal.convertScoreTransform(st,...
                        'handle',this.NumClasses);
            else
                tDecoding = this.Decoding;
                tBinaryLoss = this.BinaryLoss;
                tScoreTransform = this.ScoreTransform;
            end

            tPrior = this.Prior;
        end
        
        function [varargout] = predictWithoutValidation(this,X,obsInRows,decoding,binaryloss,prior,scoreTransform)
            scores = score(this,X,obsInRows,decoding,binaryloss);
            [varargout{1:nargout}] = this.LabelPredictor(this.ClassNames, ...
                prior,[],scores,scoreTransform);
        end
    end


    methods(Access = protected)        
        
        function metrics = allowedMetrics(this)
            % allowed metrics for incremental classification models            
            metrics = {this.pModelSpecificMetrics};
        end

        function losses = allowedPerObservationLosses(this)
            % allowed per observation losses for incremental classification
            % ECOC models
            % Only classification error is supported for incremental ECOC
            % models.
            losses = {this.pModelSpecificMetrics};
        end       
        
        function [this,remainingArgs] = configureForEstimation(this,parametersAreFromOffline,numPredictors,predictorClass,args) %#ok<INUSL>
             remainingArgs = args;
        end

        % There are no parameters in the model to estimate. Since 
        % allocateEstimationBuffers is abstract in the super class Learner, 
        % it needs to be implemented here. Thus the function body is blank. 
        function this = allocateEstimationBuffers(this,~,~,~,~,~,~,~)
        end
        
        % There are no parameters in the model to estimate. Since 
        % prepareDataAndUpdateEstimationState is abstract in the super class
        % Learner, it needs to be implemented here. Thus the function body 
        % is blank.
        function [this,X,Y,W] = prepareDataAndUpdateEstimationState(this,X,Y,W,~,~)
        end

        function this = updateEstimatedParameters(this,X,Y,W,obsInRows,numEstimationObservations) %#ok<INUSD>
        end
        
        function f = score(this,X,obsInRows,decoding,binaryloss)
            % score for incremental classification model
            f = score(this.Impl,X,obsInRows,decoding,binaryloss);
        end        
        
        function cantrain = canTrain(this)
            % can model train
            % This model can always be trained since we are not estimating
            % any parameters, thus no estimation period
            cantrain = canTrain@incremental.SimpleLearner(this);
        end
        
        function statesdefined = statesDefined(~)
            % just a method that says the model's states are defined and is
            % ready to predict
            statesdefined = true;
        end
        
        function this = resetModel(this,varargin)
           % Reset states
           this.Impl = reset(this.Impl,varargin{:});
           this = resetModel@incremental.classif.ClassificationModel(this,varargin{:});
        end
        
        function propList = propertiesForDisplay(this,propList)
            propList = propertiesForDisplay@incremental.classif.ClassificationModel(this,propList);
            propList.BinaryLearners = this.BinaryLearners;
            propList.CodingName = this.CodingName;
            propList.Decoding = this.Decoding;
        end       
    end
    
    methods(Static, Access = protected)
        function [labels,scores,pscore] = maxScore(classnames,prior,~,allscores,scoreTransform)
            scores = scoreTransform(allscores{1});
            pscore = allscores{2};

            N = size(scores,1);
            notNaN = ~all(isnan(scores),2);
            [~,cls] = max(prior);
            labels = repmat(classnames(cls,:),N,1);
            
            % Only use scores for seen classes when MaxNumClasses is used
            % and only a subset of classes have been seen so far.
            % If more classes are seen the size of scores will change
            scores = scores(:,1:numel(classnames));

            [~,classnum] = max(scores(notNaN,:),[],2);
            labels(notNaN,:) = classnames(classnum,:);            
        end
    end

    methods (Hidden)        
        function obsinsupported = ObservationsInSupported(this)
        % Hidden method used for tab completion
            obsinsupported = ObservationsInSupported(this.BinaryLearners{1});
        end

        function str = serialize(this)
            % Convert incremental model to deserializeable struct
            str = serializeHelper(this);
        end       
    end
    methods (Static,Hidden)
        function this = deserialize(deserializeableStr)  
            this = incrementalClassificationECOC('MaxNumClasses',1);
            this = deserialize@incremental.classif.ClassificationModel(this,deserializeableStr);
            this.Impl = deserialize(this.Impl,deserializeableStr.ModelParameters);
        end

        function this = loadobj(obj)
            % custom load method for backwards compatibility
            if isstruct(obj)
                deserializeableStr = incrementalClassificationECOC.serializeStruct(obj);
                this = incrementalClassificationECOC.deserialize(deserializeableStr);
            else
                this = obj;
            end
        end 

        function str = serializeStruct(this)
            % Convert incremental model to deserializeable struct
            str = serializeHelper(this);
        end           
    end  
end

function str = serializeHelper(this)
    str = incremental.SimpleLearner.serialize(this);
    str = incremental.classif.ClassificationModel.serialize(this,str);
    str.ModelParameters = serialize(this.Impl);
end
