classdef incrementalDriftAwareLearner < incremental.Learner
    %

    %incrementalDriftAwareLearner Drift aware model for incremental learning.
    %   incrementalDriftAwareLearner creates a model that can train with and
    %   predict responses for new data.
    %
    %   incrementalDriftAwareLearner properties:
    %       BaseLearner              - Underlying incremental learner.
    %       DriftDetector            - Underlying incremental concept drift detector.
    %       TrainingPeriod           - Number of training observations BaseLearner
    %                                  will use for training.
    %       StableCountLimit         - Maximum number of consecutive observations
    %                                  that can be marked as 'Stable' by DriftDetector
    %                                  before a soft reset.
    %       WarningCountLimit        - Maximum number of consecutive observations
    %                                  that can be marked as 'Warning' by DriftDetector
    %                                  before a hard reset.
    %       PreviousDriftStatus      - Status of DriftDetector prior to training with the most
    %                                  recent data.
    %       DriftStatus              - Current status of DriftDetector.
    %       DriftDetected            - Flag indicating whether DriftStatus is
    %                                  'Drift'.
    %       WarningDetected          - Flag indicating whether DriftStatus is
    %                                  'Warning'.
    %       IsTraining               - Flag indicating whether BaseLearner
    %                                  has stopped training with incoming
    %                                  data.
    %       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.    
    %
    %   incrementalDriftAwareLearner methods:
    %       incrementalDriftAwareLearner      - Construct incremental drift aware model.
    %       fit                               - Train model with new data.
    %       loss                              - Classification or Regression loss.
    %       perObservationLoss                - Per observation classification
    %                                           or Regression 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.
    %
    %

    %   Copyright 2022-2023 The MathWorks, Inc.
    properties (Access = {?matlab.unittest.TestCase,?incrementalDriftAwareLearner})
        Buffer
        CurrentBufferSize = 0
        BufferSize
        TemporaryLearner = []
        LossFun
        StableCount = 0
        WarningCount = 0
        DriftDetectorSupportsWeights = true
        DriftDueToWarningDetected = false
        SoftDriftReset = false
    end

    properties (SetAccess = protected, GetAccess = public)
        %BASELEARNER  Underlying incremental learner.
        %    Additional properties of BaseLearner can be accessed via dot operator.
        %
        %    See also DriftDetector.

        %
        BaseLearner

        %DRIFTDETECTOR  Underlying incremental concept drift detector.
        %    Additional properties of DriftDetector can be accessed via dot operator.
        %
        %    See also BaseLearner.

        %
        DriftDetector

        %TRAININGPERIOD  Number of training observations BaseLearner
        %    will use for training. When NumTrainingObservations exceeds TrainingPeriod,
        %    BaseLearner will stop training with incoming data.
        %
        %    See also TrainingPeriod.

        %
        TrainingPeriod

        %STABLECOUNTLIMIT  Scalar specifying the allowed number of
        %     consecutive observations marked as 'Stable' by DriftDetector.
        %     When StableCountLimit is exceeded, DriftDetector is reset, but
        %     Learner is not reset.
        %
        %    See also WarningCountLimit.

        %
        StableCountLimit

        %WARNINGCOUNTLIMIT  Scalar specifying the allowed number of
        %     consecutive observations marked as 'Warning' by DriftDetector.
        %     When WarningCountLimit is exceeded, both DriftDetector and
        %     Learner are reset.
        %
        %    See also StableCountLimit.

        %
        WarningCountLimit

        %PREVIOUSDRIFTSTATUS Drift status of incrementalDriftAwareLearner 
        %   prior to training with the most recent batch of input. 
        %   Comparison of PreviousDriftStatus with DriftStatus reveals the 
        %   transition of the drift detector.
        %   PreviousDriftStatus retains its value when drift is detected.
        %   It is reset prior to training with the next batch of observations.
        %
        %   See also DriftStatus, DriftDetected, WarningDetected.

        %
        PreviousDriftStatus

        %DRIFTSTATUS  Current drift status of incrementalDriftAwareLearner. 
        %   Comparison of PreviousDriftStatus with DriftStatus reveals the 
        %   transition of the drift detector.
        %   DriftStatus retains its value when drift is detected.
        %   It is reset prior to training with the next batch of observations.
        %
        %   See also PreviousDriftStatus, DriftDetected, WarningDetected.

        %
        DriftStatus

        %DRIFTDETECTED  Flag indicating whether DriftStatus is "Drift".
        %   DriftDetected retains its value when drift is detected.
        %   It is reset prior to training with the next batch of observations.
        %
        %   See also DriftStatus, WarningDetected.

        %
        DriftDetected

        %WARNINGDETECTED  Flag indicating whether DriftStatus is "Warning".
        %
        %   See also DriftStatus, DriftDetected.

        %
        WarningDetected
    end

    properties(Dependent,GetAccess = public, SetAccess = protected)
        %ISTRAINING  Flag indicating whether Learner is actively training
        %   with incoming data.
        %
        %   See also TrainingPeriod.

        %
        IsTraining
    end    

    properties(Hidden)
        LearnerType %LearnerType is used for tab completion
    end


    methods
        function this = incrementalDriftAwareLearner(learner,NameValueArgs)
            %
            
            %INCREMENTALDRIFTAWARELEARNER Construct a drift aware model
            %   for incremental learning.
            %
            %   MODEL=INCREMENTALDRIFTAWARELEARNER(BASELEARNER) returns a drift aware model
            %   for incremental learning with default model parameters.
            %
            %   MODEL=INCREMENTALDRIFTAWARELEARNER(BASELEARNER, 'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'BufferSize'                - Size of the buffer that
            %                                   stores the loss values of BASELEARNER
            %                                   for each training observation, specified as a
            %                                   scalar integer. Default: 7000
            %       'DriftDetector'             - Incremental concept drift
            %                                   detector used fordetecting drift,
            %                                   specified as a scalar object.
            %                                   If BASELEARNER is an incremental
            %                                   classification model,
            %                                   default value is constructed using
            %                                   incrementalConceptDriftDetector('hddma').
            %                                   Otherwise, default value is constructed using
            %                                   incrementalConceptDriftDetector('hddma', 'InputType', 'continuous').
            %       'TrainingPeriod'            - Number of training observations BASELEARNER
            %                                   will use for training, specified as a scalar.
            %                                   If Inf is specified, BASELEARNER will always
            %                                   train with incoming data.
            %                                   If TrainingPeriod is smaller than
            %                                   BASELEARNER.MetricsWarmupPeriod,
            %                                   TrainingPeriod is set to BASELEARNER.MetricsWarmupPeriod. 
            %                                   Default: 10000.
            %       'StableCountLimit'          - Maximum number of consecutive observations
            %                                   that can be marked as 'Stable' by DriftDetector
            %                                   before a soft reset, specified as a scalar
            %                                   integer. Default: 40000.
            %       'WarningCountLimit'         - Maximum number of consecutive observations
            %                                   that can be marked as 'Warning' by DriftDetector
            %                                   before a soft reset, specified as a scalar
            %                                   integer. Default: 1400.
            arguments
                learner
                NameValueArgs.DriftDetector
                NameValueArgs.TrainingPeriod(1,1) {mustBeNumeric, mustBeNonsparse, mustBePositive(NameValueArgs.TrainingPeriod)} = 10000
                NameValueArgs.BufferSize(1,1) {mustBeNumeric, mustBeNonsparse, mustBeInteger(NameValueArgs.BufferSize), mustBeNonnegative(NameValueArgs.BufferSize)} = 7000
                NameValueArgs.StableCountLimit(1,1) {mustBeNumeric, mustBeNonsparse, mustBeInteger(NameValueArgs.StableCountLimit), mustBeNonnegative(NameValueArgs.StableCountLimit)} = 40000
                NameValueArgs.WarningCountLimit(1,1) {mustBeNumeric,mustBeNonsparse, mustBeInteger(NameValueArgs.WarningCountLimit), mustBeNonnegative(NameValueArgs.WarningCountLimit)} = 1400
            end
            if ~(isa(learner,'incremental.Learner') && isscalar(learner))
                error(message("stats:incremental:DriftAwareLearner:IncorrectLearner"));
            end
            this.BaseLearner = learner;
            if isa(this.BaseLearner,'incrementalDriftAwareLearner')
                learnerForLoss = this.BaseLearner.BaseLearner;
            else
                learnerForLoss = this.BaseLearner;
            end
            if isfield(NameValueArgs,'DriftDetector')
                if ~(isa(NameValueArgs.DriftDetector,'incremental.drift.ConceptDriftDetector') && isscalar(NameValueArgs.DriftDetector))
                    error(message("stats:incremental:DriftAwareLearner:IncorrectDriftDetector"));
                end
                detector = NameValueArgs.DriftDetector;
            else
                if isa(learnerForLoss,'incremental.classif.ClassificationModel')
                    detector = incrementalConceptDriftDetector("hddma");
                else
                    detector = incrementalConceptDriftDetector("hddma",InputType='continuous');
                end
            end

            if isa(learnerForLoss,'incremental.classif.ClassificationModel')
                if ~(strcmpi(detector.InputType,'binary') && isequal(detector.Alternative,'greater'))
                    error(message("stats:incremental:DriftAwareLearner:DriftDetectorClassificationIncompatible"));
                end
                this.LossFun = 'classiferror';
                this.LearnerType = 'classif';
            else
                if ~(strcmpi(detector.InputType,'continuous') && isequal(detector.Alternative,'greater'))
                    error(message("stats:incremental:DriftAwareLearner:DriftDetectorRegressionIncompatible"));
                end
                this.LossFun = 'squarederror';
                this.LearnerType = 'regr';
            end

            this.DriftDetector = detector;
            this.PreviousDriftStatus = this.DriftDetector.PreviousDriftStatus;
            this.DriftStatus = this.DriftDetector.DriftStatus;
            this.DriftDetected = this.DriftDetector.DriftDetected;
            this.WarningDetected = this.DriftDetector.WarningDetected;           

            if ~WeightsSupported(this.DriftDetector)
                this.DriftDetectorSupportsWeights = false;
            end
            if isfield(NameValueArgs,'TrainingPeriod')
                if ~isinf(NameValueArgs.TrainingPeriod)
                    validateattributes(NameValueArgs.TrainingPeriod,{'numeric'},{'integer'},mfilename,'TrainingPeriod');
                end
                this.TrainingPeriod = NameValueArgs.TrainingPeriod;
            end

            if this.TrainingPeriod < this.MetricsWarmupPeriod
                this.TrainingPeriod = this.MetricsWarmupPeriod;
            end            

            this.StableCountLimit = NameValueArgs.StableCountLimit;
            this.WarningCountLimit = NameValueArgs.WarningCountLimit;
            this.BufferSize = NameValueArgs.BufferSize;
            this = resetBuffer(this);
            
        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.
            %   For incremental classification models, 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.BaseLearner.ClassNames is specified, Y must be of the same data type as
            %   MODEL.BaseLearner.ClassNames. If MODEL.BaseLearner.ClassNames is not defined,
            %   MODEL.BaseLearner.ClassNames is inferred from data.
            %
            %   For incremental regression models, pass Y as a floating-point
            %   vector with N elements.
            %
            %   MODEL=FIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - String specifying the data orientation,
            %                            either 'rows' or 'columns'. Default: 'rows'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument.
            %       '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.
            %
            %   Refer to documentation page for FIT object function of
            %   MODEL for details of the steps taken in FIT.
            %
            %   See also updateMetricsAndFit, updateMetrics, loss, predict.

            [varargin{:}] = convertStringsToChars(varargin{:});

            % LossFun input is not allowed. If ObservationsIn is passed and
            % BaseLearner does not support it, appropriate error will be
            % thrown by BaseLearner.
            [weights,~,userInput] = internal.stats.parseArgs({'Weights','ObservationsIn'},{[],[]},varargin{:});
            lossValues = perObservationLoss(this,X,Y,'LossFun',this.LossFun,varargin{:});
            this = updateBuffer(this,lossValues);

            if this.DriftDetected % If drift was detected, reset status.
                this.PreviousDriftStatus = this.DriftDetector.PreviousDriftStatus;
                this.DriftStatus = this.DriftDetector.DriftStatus;
            end

            if userInput.Weights && this.DriftDetectorSupportsWeights
                if this.IsWarm
                    this.DriftDetector = detectdrift(this.DriftDetector,lossValues,Weights=weights);
                end
            else
                if this.IsWarm
                    this.DriftDetector = detectdrift(this.DriftDetector,lossValues);
                end
            end

            this = triageDriftStatus(this,X,Y,varargin{:});

            if this.IsTraining
                this.BaseLearner = fit(this.BaseLearner,X,Y,varargin{:});
            end

        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.BaseLearner.ClassNames and have N elements.
            %   For regression models, Y must be a vector of floating-point
            %   numbers with N elements, where N is the number of rows in X.
            %
            %   UPDATEMETRICS updates metrics with new data only if
            %   'IsWarm' is true. Otherwise, it assigns NaN values.
            %
            %   MODEL=UPDATEMETRICS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - String specifying the data orientation,
            %                            either 'rows' or 'columns'. Default: 'rows'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument.
            %       '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 updateMetrics, fit, loss, predict.
            [varargin{:}] = convertStringsToChars(varargin{:});
            this.BaseLearner = updateMetrics(this.BaseLearner,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.BaseLearner.ClassNames and have N elements.
            %   For regression models, Y must be a vector of floating-point
            %   numbers with N elements, where N is the number of rows in X.
            %
            %   UPDATEMETRICSANDFIT updates metrics with new data only if
            %   'IsWarm' is true. Otherwise, it assigns NaN values.
            %
            %   MODEL=UPDATEMETRICSANDFIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'ObservationsIn'   - String specifying the data orientation,
            %                            either 'rows' or 'columns'. Default: 'rows'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument.
            %       '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 updateMetrics, fit, loss, predict.

            [varargin{:}] = convertStringsToChars(varargin{:});
            this = updateMetrics(this,X,Y,varargin{:});
            this = fit(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.
            %   For classification models, Y must be of the same data type as
            %   MODEL.BaseLearner.ClassNames and have N elements. ERR is a scalar.
            %   For regression models, Y must be a vector of floating-point
            %   numbers with N elements, where N is the number of rows in X.
            %
            %   ERR=LOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'ObservationsIn'   - String specifying the orientation of
            %                            X, either 'rows' or 'columns'.
            %                            Default: 'rows'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument.
            %       'LossFun'          - Function handle for loss, or string
            %                            representing a built-in loss function.
            %                            Available loss functions for classification:
            %                            'binodeviance', 'classiferror', 'exponential',
            %                            'hinge', 'logit', and 'quadratic'.
            %                            If you pass a function handle FUN, it
            %                            is called 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
            %                            predicted scores for classes with one row per
            %                            observation, similar to SCORE output from
            %                            PREDICT. W is a vector of observation weights.
            %                            Default: 'classiferror'.
            %                            Available loss functions for regression: 'mse'
            %                            and 'epsiloninsensitive'. If you pass a
            %                            function handle FUN, LOSS calls it as shown
            %                            below:
            %                               FUN(Y,Yfit,W)
            %                            where Y, Yfit and W are numeric vectors of
            %                            length N. Y is observed response, Yfit is
            %                            predicted response, and W is observation
            %                            weights. Default: 'mse'.
            %       '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.
            %
            %   Refer to documentation page for LOSS object function of
            %   MODEL.BaseLearner to learn about additional behavior.
            %
            %   See also updateMetricsAndFit, updateMetrics, fit, predict.

            [varargin{:}] = convertStringsToChars(varargin{:});
            l = loss(this.BaseLearner,X,Y,varargin{:});
        end

        function varargout = predict(this,X,varargin)
            %

            %PREDICT Predict responses of the model.
            %   [LABEL,SCORE]=PREDICT(MODEL,X) returns predicted class labels and
            %   scores for MODEL and predictors X. Pass X as a matrix with
            %   N observations from P predictors. Classification labels are a N-by-1
            %   array of the same data type as Y used for training, where N is the number of
            %   observations (rows in X). Scores are an N-by-K numeric matrix for N observations
            %   and K classes. The predicted label is assigned to the class with
            %   the largest score.
            %
            %   [LABEL,SCORE]=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'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument. 
            %
            %   Refer to documentation page for PREDICT object function of
            %   MODEL.BaseLearner to learn about additional behavior.            
            %
            %   See also fit, loss.            

            [varargin{:}] = convertStringsToChars(varargin{:});
            [varargout{1:nargout}] = predict(this.BaseLearner,X,varargin{:});
        end

        function istraining = get.IsTraining(this)
            istraining = this.NumTrainingObservations <= this.TrainingPeriod;
        end

        function l = perObservationLoss(this,X,Y,varargin)
            %

            %PEROBSERVATIONLOSS Model per observation error.
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y) returns per observation error
            %   for model MODEL computed using predictors X and true observed values Y.
            %   Pass X as a matrix with N observations from P predictors.
            %   ERR is a N-by-1 vector.
            %   For classification models, Y must be of the same data type as
            %   MODEL.BaseLearner.ClassNames and have N elements.
            %   For regression models, Y must be a vector of floating-point
            %   numbers with N elements, where N is the number of rows in X.
            %
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'ObservationsIn'   - String specifying the orientation of
            %                            X, either 'rows' or 'columns'.
            %                            Default: 'rows'.
            %                            'ObservationsIn' is supported only if
            %                            MODEL.BaseLearner supports
            %                            'ObservationsIn' Name-Value argument.
            %       'LossFun'          - Function handle for loss, or string
            %                            representing a built-in loss function.
            %                            Available loss functions for classification:
            %                            'binodeviance', 'classiferror', 'exponential',
            %                            'hinge', 'logit', and 'quadratic'.
            %                            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'.
            %                            Available loss functions for regression: 'squarederror'
            %                            and 'epsiloninsensitive'. If you pass a
            %                            function handle FUN, LOSS calls it as shown
            %                            below:
            %                               FUN(Y,Yfit)
            %                            where Y, Yfit and W are numeric vectors of
            %                            length N. Y is observed response, Yfit is
            %                            predicted response, and W is observation
            %                            weights. Default: 'squarederror'.
            %
            %   Refer to documentation page for PEROBSERVATIONLOSS object function of
            %   MODEL.BaseLearner to learn about additional behavior.
            %
            %   See also updateMetricsAndFit, updateMetrics, fit, predict, loss.

            [varargin{:}] = convertStringsToChars(varargin{:});
            l = perObservationLoss(this.BaseLearner,X,Y,varargin{:});
        end

        function this = reset(this)
            %

            %RESET Reset incremental model.
            %   MODEL=RESET(MODEL) returns the MODEL with trained
            %   BaseLearner and DriftDetector reset.
            %   If any MODEL.BaseLearner hyperparameters were estimated during
            %   incremental training, these hyperparameters are reset as
            %   well; otherwise, their values are retained.
            %   RESET preserves the NumPredictors property. For incremental 
            %   classification models, it preserves the BaseLearner.ClassNames property 
            %   and resets BaseLearner.Prior property if prior class probabilities are 'empirical'.            
            %
            %   See also fit, perObservationLoss.  
            
            this = resetBuffer(this);
            this.DriftDetector = reset(this.DriftDetector);
            this.BaseLearner = reset(this.BaseLearner);
            this.TemporaryLearner = [];
            this.StableCount = 0;
            this.WarningCount = 0;
            this.PreviousDriftStatus = this.DriftDetector.PreviousDriftStatus;
            this.DriftStatus = this.DriftDetector.DriftStatus;
        end
    end

    methods (Access = protected)
        % protected get methods
        function metrics = getMetrics(this)
            metrics = this.BaseLearner.Metrics;
        end

        function numpredictors = getNumPredictors(this)
            numpredictors = this.BaseLearner.NumPredictors;
        end

        function numobs = getNumTrainingObservations(this)
            numobs = this.BaseLearner.NumTrainingObservations;
        end


        function warmupperriod = getMetricsWarmupPeriod(this)
            warmupperriod = this.BaseLearner.MetricsWarmupPeriod;
        end

        function wsize = getMetricsWindowSize(this)
            wsize = this.BaseLearner.MetricsWindowSize;
        end

        function iswarm = getIsWarm(this)
            iswarm = this.BaseLearner.IsWarm;
        end
    end

    methods(Access = {?matlab.unittest.TestCase,?incrementalDriftAwareLearner})
        function this = updateBuffer(this,lossValues)
            lossValues = lossValues(:);
            lossValues = lossValues(~isnan(lossValues),:);
            n = numel(lossValues);
            if this.CurrentBufferSize + n <= this.BufferSize
                this.Buffer(this.CurrentBufferSize+1:this.CurrentBufferSize+n) = lossValues;
                this.CurrentBufferSize = this.CurrentBufferSize+n;
            else
                if this.BufferSize <=n
                    this.Buffer = lossValues(n-this.BufferSize+1:n);
                else
                    shift = (this.CurrentBufferSize+n)-this.BufferSize;
                    this.Buffer = circshift(this.Buffer,-shift);
                    this.CurrentBufferSize = this.BufferSize-n;
                    this.Buffer(this.CurrentBufferSize+1:this.CurrentBufferSize+n) = lossValues;
                end
                this.CurrentBufferSize = this.BufferSize;
            end
        end

        function this = triageDriftStatus(this,X,Y,varargin)
            if this.IsWarm
                this.PreviousDriftStatus = this.DriftStatus;
                this.DriftStatus = this.DriftDetector.DriftStatus;
            end            
            this.SoftDriftReset = false;
            this.DriftDueToWarningDetected = false;
            this.WarningDetected = false;
            this.DriftDetected = false; 

            switch this.DriftStatus
                case getString(message('stats:drift:DriftDiagnostics:Stable'))
                    this.WarningCount = 0;
                    if ~isempty(X)
                        this.StableCount = this.StableCount+1;
                    end                    
                    switch this.PreviousDriftStatus
                        case getString(message('stats:drift:DriftDiagnostics:Stable'))
                            if this.StableCountLimit<=this.StableCount
                                this.DriftDetector = reset(this.DriftDetector);
                                this.DriftDetector = detectdrift(this.DriftDetector,this.Buffer);
                                this.StableCount = 0;
                                this.SoftDriftReset = true;
                            end
                        case getString(message('stats:drift:DriftDiagnostics:Warning'))
                            if ~isempty(this.TemporaryLearner)
                                this.TemporaryLearner = [];
                            end
                    end
                case getString(message('stats:drift:DriftDiagnostics:Warning'))
                    this.StableCount = 0;
                    if ~isempty(X)
                        this.WarningCount = this.WarningCount+1;
                    end
                    this.WarningDetected = true;
                    this.DriftDetected = false;
                    this.DriftDueToWarningDetected = false;
                    if isempty(this.TemporaryLearner)
                        this.TemporaryLearner = reset(this.BaseLearner);
                    end
                    this.TemporaryLearner = fit(this.TemporaryLearner,X,Y,varargin{:});
                    switch this.PreviousDriftStatus
                        case getString(message('stats:drift:DriftDiagnostics:Warning'))                         
                            if this.WarningCountLimit <= this.WarningCount
                                this.WarningDetected = false;
                                this.DriftDetected = true;
                                this.DriftDueToWarningDetected = true;
                                this.DriftStatus = getString(message('stats:drift:DriftDiagnostics:Drift'));
                                this = driftResponse(this);
                            end
                    end   
                otherwise % 'Drift'
                    this.WarningDetected = false;
                    this.DriftDetected = true;
                    this = driftResponse(this);
            end

            function this = driftResponse(this)
                if ~isempty(this.TemporaryLearner)
                    this.BaseLearner = this.TemporaryLearner;
                    this.TemporaryLearner = [];
                else
                    this.BaseLearner = reset(this.BaseLearner);
                end
                this.DriftDetector = reset(this.DriftDetector);
                this = resetBuffer(this);
                this.StableCount = 0;
                this.WarningCount = 0;
            end
        end

        function this = resetBuffer(this)
            this.Buffer = zeros(this.BufferSize,1,'like',this.Buffer);
            this.CurrentBufferSize = 0;
        end
    end

    methods (Access = protected)
        function propList = propertiesForDisplay(this,propList)
            propList.BaseLearner = this.BaseLearner;
            propList.DriftDetector = this.DriftDetector;
            propList.IsTraining = this.IsTraining;
        end
    end

    methods (Hidden)
        function obsinsupported = ObservationsInSupported(this)
            % Hidden method used for tab completion
            obsinsupported = ObservationsInSupported(this.BaseLearner);
        end
        function str = serialize(this)
            str.BaseLearner = serialize(this.BaseLearner);
            str.DriftDetector = this.DriftDetector;
            str.TrainingPeriod = this.TrainingPeriod;
            str.StableCountLimit = this.StableCountLimit;
            str.WarningCountLimit = this.WarningCountLimit;
            str.PreviousDriftStatus = this.PreviousDriftStatus;
            str.Buffer = this.Buffer;
            str.BufferSize = this.BufferSize;
            str.CurrentBufferSize = this.CurrentBufferSize;
            str.TemporaryLearner = this.TemporaryLearner;
            str.LossFun = this.LossFun;
            str.StableCount = this.StableCount;
            str.WarningCount = this.WarningCount;
            str.DriftDetectorSupportsWeights = this.DriftDetectorSupportsWeights;
            str.LearnerType = this.LearnerType;
            learnerFcn = class(this.BaseLearner) + ".deserialize";
            str.BaseLearnerFcn = str2func(learnerFcn);
        end
    end

    methods(Static,Hidden)
        function this = deserialize(deserializeableStr)
            learnerStr = deserializeableStr.BaseLearner;
            learner = deserializeableStr.BaseLearnerFcn(learnerStr);
            this = incrementalDriftAwareLearner(learner);
            this.DriftDetector = deserializeableStr.DriftDetector;
            this.TrainingPeriod = deserializeableStr.TrainingPeriod;
            this.StableCountLimit = deserializeableStr.StableCountLimit;
            this.WarningCountLimit = deserializeableStr.WarningCountLimit;
            this.PreviousDriftStatus = deserializeableStr.PreviousDriftStatus;
            this.Buffer = deserializeableStr.Buffer;
            this.BufferSize = deserializeableStr.BufferSize;
            this.CurrentBufferSize = deserializeableStr.CurrentBufferSize;
            this.TemporaryLearner = deserializeableStr.TemporaryLearner;
            this.StableCount = deserializeableStr.StableCount;
            this.WarningCount = deserializeableStr.WarningCount;
        end
    end
end