classdef ClassificationKernel < ...
        classreg.learning.classif.ClassificationModel & classreg.learning.Kernel
%ClassificationKernel Kernel model for classification.
%   ClassificationKernel is a kernel model for classification for which the
%   predictors are explicitly mapped to a high dimensional space.
%
%   ClassificationKernel properties:
%       BoxConstraint          - Box constraint for SVM.
%       ClassNames             - Names of classes in Y.
%       Cost                   - Misclassification costs.
%       ExpandedPredictorNames - Names of expanded predictors.
%       NumExpansionDimensions - Number of dimensions of expanded space.
%       FittedLoss             - Fitted loss function, 'hinge' or 'logit'.
%       KernelScale            - Kernel scale.
%       Lambda                 - Regularization strength.
%       Learner                - Learned model, 'svm' or 'logistic'.
%       PredictorNames         - Names of predictors used for this model.
%       Prior                  - Prior class probabilities.
%       ResponseName           - Name of the response variable.
%       ScoreTransform         - Transformation applied to predicted classification scores.
%       Mu                     - Predictor means.
%       Sigma                  - Predictor standard deviations.
%
%   ClassificationKernel methods:
%       edge                  - Classification edge.
%       incrementalLearner    - Return an incremental binary classification kernel model.
%       loss                  - Classification loss.
%       margin                - Classification margins.
%       partialDependence     - Partial Dependence values.
%       plotPartialDependence - Plot Partial Dependence.
%       predict               - Predict responses of this model.
%       resume                - Continue fitting this model.

%   Copyright 2017-2023 The MathWorks, Inc.

    properties(GetAccess=protected,SetAccess=protected,Hidden=true)
        FitInfo = [];
    end

    properties (GetAccess=public,SetAccess=protected)
        %BoxConstraint Regularization strength.
        %   The BoxConstraint property is a non negative scalar used to
        %   control the strength of regularization. BoxConstraint applies
        %   to SVM learners only. BoxConstraint and Lambda are related by:
        %
        %               BoxConstraint * Lambda = 1 / N
        %
        %   where N is the number of observation in the training set.
        %
        %   See also ClassificationKernel, RegressionKernel.
        BoxConstraint = [];
    end

    methods(Hidden)
        function this = ClassificationKernel(dataSummary,classSummary,scoreTransform)
        % Protect against being called by the user
            if ~isstruct(dataSummary)
                error(message('stats:ClassificationKernel:DoNotUseConstructor'));
            end

            this = this@classreg.learning.classif.ClassificationModel(...
                dataSummary,classSummary,scoreTransform,[]);
            this = this@classreg.learning.Kernel;
            this.CategoricalVariableCoding = 'dummy';
        end

        function cmp = compact(this)
            cmp = this;
        end

        function compareHoldout(~,varargin)
            error(message('stats:ClassificationKernel:DoNotUseCompareHoldout'));
        end

    end

    
    methods(Access=protected)
        function cl = getContinuousLoss(this)
            cl = [];
            lossfun = this.Impl.LossFunction;
            switch lossfun
              case 'logit'
                if     isequal(this.PrivScoreTransform,@classreg.learning.transform.identity)
                    cl = @classreg.learning.loss.logit;
                elseif isequal(this.PrivScoreTransform,@classreg.learning.transform.logit)
                    cl = @classreg.learning.loss.quadratic;
                end
              case 'hinge'
                if isequal(this.PrivScoreTransform,@classreg.learning.transform.identity)
                    cl = @classreg.learning.loss.hinge;
                end
            end
        end

        function s = propsForDisp(this,s)
            s = propsForDisp@classreg.learning.classif.ClassificationModel(this,s);
            s = propsForDisp@classreg.learning.Kernel(this,s);
            if strcmp(this.Learner,'svm')
                s.BoxConstraint  = this.BoxConstraint;
            end
            s = rmfield(s,'CategoricalPredictors');
            if strcmp(s.ScoreTransform,'none')
                s = rmfield(s,'ScoreTransform');
            end
        end

        function S = score(this,X)
        % Implements abstract ClassificationModel/score, assumes
        % observations in columns.
            internal.stats.checkSupportedNumeric('X',X)
            obsInRows = false;

            if any(this.CategoricalPredictors)
                if ~this.TableInput
                    % Explicit treatment of numerical predictors as categoricals
                    % (for tables index encoding is done in table2FitMatrix
                    % or table2PredictMatrix)
                    X = classreg.learning.internal.encodeCategorical(X,this.VariableRange,obsInRows);
                end
                X = classreg.learning.internal.expandCategorical(X,...
                                                                 this.CategoricalPredictors,this.OrdinalPredictors,...
                                                                 this.VariableRange,this.ReferenceLevels,obsInRows);
            end

            X = X'; %Because ObsInRows is false
            if this.ModelParameters.StandardizeData
                X = classreg.learning.Kernel.standardizePredict(X,this.Mu,this.Sigma);
            end
            S = scoreWithoutDataChecks(this,X);
        end

        function n = getExpandedPredictorNames(this)
            n = classreg.learning.internal.expandPredictorNamesWithReference(...
                this.PredictorNames,this.CategoricalPredictors,...
                this.OrdinalPredictors,this.VariableRange,...
                this.ReferenceLevels);
        end
    end

    methods(Access={?OneClassSVM})
        function S = scoreWithoutDataChecks(this,X)
            [n,p] = size(X);
            if p~=this.FeatureMapper.d
                error(message('stats:ClassificationKernel:XSizeMismatch',this.FeatureMapper.d))
            end

            maxChunkSize = ClassificationKernel.estimateMaxChunkSize(this.FeatureMapper.n,this.ModelParams.BlockSize,false);
            numberChunks = ceil(n / maxChunkSize);

            if numberChunks<=1
                Xm = map(this.FeatureMapper,X,this.KernelScale);
                S1 = score(this.Impl,Xm,true,true); % doClass = true , obsInRows = true
            else
                % Block-wise
                S1 = cell(numberChunks,1);
                j = 1;
                for i = 1:numberChunks
                    k = min(n,j+maxChunkSize-1);
                    Xm = map(this.FeatureMapper,X(j:k,:),this.KernelScale);
                    S1{i} = score(this.Impl,Xm,true,true); % doClass = true , obsInRows = true
                    j = j + maxChunkSize;
                end
                S1 = cell2mat(S1);
            end

            % Expand a linear model score to scores for each class
            S = expandLinearMdlScoreForEachClass(this.ClassSummary, S1);
        end
    end

    methods
        function  [model,fitinfo] = resume(this,X,Y,varargin)
        %RESUME Resume training of the model.
        %   MODEL_OUT = RESUME(MODEL,X,Y) continues training the same model
        %   starting at the current estimated parameters. X must be a table if
        %   MODEL was originally trained on a table and a numeric matrix if MODEL
        %   was originally trained on a matrix. If X is a table, it must contain
        %   all the predictors used for training the model. If X is a matrix, it
        %   must have size N-by-P, where P is the number of predictors used for
        %   training. Y must be categorical array, string array, character array,
        %   logical vector, numeric vector, or cell array of character vectors. If
        %   Y is a character array, it must have one class label per row. Otherwise
        %   Y must be a vector with N elements. To effectively continue the
        %   training, X and Y should be the same arrays used to initially fit the
        %   model MODEL.
        %
        %   [MODEL_OUT,FITINFO] = RESUME(...) also returns FITINFO, a struct
        %   containing fit information.
        %
        %   [...] = RESUME(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
        %   optional name-value parameter pairs:
        %
        %            'Weights'  - Vector of observation weights, one weight per
        %                         observation. RESUME normalizes the weights to
        %                         add up to the value of the prior probability in
        %                         the respective class. Default is equal weights
        %                         within each class.
        %      'BetaTolerance'  - Relative tolerance on linear coefficients and
        %                         bias term. A non-negative scalar. Default
        %                         is same BetaTolerance value used to train
        %                         MODEL.
        %   'GradientTolerance' - Absolute gradient tolerance. A non-negative
        %                         scalar. Default is the same GradientTolerance
        %                         value used to train MODEL.
        %     'IterationLimit'  - Maximal number of extra optimization iterations.
        %                         A positive integer. Default is 1000 if the
        %                         transformed data fits in memory, 100 if the
        %                         fitting algorithm switches to a block-wise
        %                         strategy, or 20 if inputs are tall.
        %
        %   See also ClassificationKernel, fitckernel.
            if nargin > 2
                Y = convertStringsToChars(Y);
            end
            [varargin{:}] = convertStringsToChars(varargin{:});

            args = {'weights'  'betatolerance'                    'gradienttolerance'                    'iterationlimit'};
            defs = {[]         this.ModelParameters.BetaTolerance this.ModelParameters.GradientTolerance []};
            [W,betaTolerance,gradientTolerance,iterationLimit] = internal.stats.parseArgs(args,defs,varargin{:});
            temp = ClassificationKernel.template('Weights',W,...
                                                 'BetaTolerance',betaTolerance,...
                                                 'GradientTolerance',gradientTolerance,...
                                                 'FitBias',this.ModelParameters.FitBias,...
                                                 'HessianHistorySize',this.ModelParameters.HessianHistorySize,...
                                                 'Learner',this.ModelParameters.Learner,...
                                                 'Lambda',this.Lambda,... % Taken from object as ModelParams may have 'auto'
                                                 'LineSearch',this.ModelParameters.LineSearch,...
                                                 'LossFunction',this.ModelParameters.LossFunction,...
                                                 'Regularization',this.ModelParameters.Regularization,...
                                                 'Solver',this.ModelParameters.Solver,...
                                                 'RandomStream',this.ModelParameters.Stream,...
                                                 'Verbose',this.ModelParameters.VerbosityLevel,...
                                                 'NumExpansionDimensions',this.NumExpansionDimensions,... % Taken from object as ModelParams may have 'auto'
                                                 'KernelScale',this.KernelScale,... % Taken from object as ModelParams may have 'auto'
                                                 'Transformation',this.ModelParameters.Transformation,...
                                                 'BlockSize',this.ModelParameters.BlockSize,...
                                                 'ADMMIterationLimit',0,... % No initialization needed when resuming
                                                 'Consensus',this.ModelParameters.Consensus,...
                                                 'InitialStepSize',this.ModelParameters.InitialStepSize,...
                                                 'Standardize',this.ModelParameters.StandardizeData);

            % IterationLimit is handled differently as two parameters
            % depend on it (IterationLimit and IterationLimitBlockWise),
            % if these parameters were defaulted in the original fit, they have
            % *different* values and we need to set them individually in the new
            % template. We cannot set them with the template constructor (as all
            % other parameters) because they would be set to the same value.
            if ~isempty(iterationLimit)
                tempDummy = ClassificationKernel.template('iterationLimit',iterationLimit);
                temp.ModelParams.IterationLimit = tempDummy.ModelParams.IterationLimit;
                temp.ModelParams.IterationLimitBlockWise = tempDummy.ModelParams.IterationLimitBlockWise;
            end

            %Starting point
            temp.ModelParams.InitialBeta = this.Beta;
            temp.ModelParams.InitialBias = this.Bias;
            temp.ModelParams.FeatureMapper = this.FeatureMapper;


            if istall(X) || istall(Y) || istall(W)
                if getNumberPredictors(this) ~= gather(size(X,2))
                    error(message('stats:ClassificationKernel:XSizeMismatch',this.FeatureMapper.d))
                end
                [model,fitinfo] = fitckernel(X,Y,'template',temp);
            elseif ~istable(X)
                if getNumberPredictors(this) ~= size(X,2)
                    error(message('stats:ClassificationKernel:XSizeMismatch',this.FeatureMapper.d))
                end
                if any(this.CategoricalPredictors)
                    % Check that the input data encodes similarly to model,
                    % otherwise fitted coefficients will not match.
                    D =getNumberPredictors(this);
                    foundvrange = cell(1,D);
                    iscat = false(1,D);
                    iscat(this.CategoricalPredictors) = true;
                    for k=1:D
                        if iscat(k)
                            x = X(:,k);
                            vrk = unique(x);
                            foundvrange{k} = vrk(~isnan(vrk));
                        end
                    end
                    if ~isequal(foundvrange,this.VariableRange)
                        error(message('stats:ClassificationKernel:CategoricalEncoding'))
                    end
                end
                temp = setBaseArg(temp,'CategoricalPredictors',this.CategoricalPredictors);
                [model,fitinfo] = fit(temp,X,Y);
            else

                if internal.stats.isString(Y)
                    Y = this.ResponseName;
                end

                % Check the input table has the required Predictors, also
                % checks their type is appropriate as it is trying to
                % encode as prescribed by the model
                [~,Yt] = classreg.learning.internal.table2PredictMatrix(X,Y,W,...
                    this.VariableRange,this.CategoricalPredictors,...
                    this.PredictorNames,true); % ObsInRows is true

                % Filter potential extra predictors in data before encoding
                Xt = X(:,this.PredictorNames);
                if internal.stats.isString(Y)
                    Xt.(this.ResponseName) = Yt;
                end

                % Check that the input data encodes similarly to model,
                % otherwise fitted coefficients will not match.
                [~,~,foundvrange] = classreg.learning.internal.table2FitMatrix(Xt,Y,...
                                                                               'CategoricalPredictors',this.CategoricalPredictors);

                if ~isequal(foundvrange,this.VariableRange)
                    error(message('stats:ClassificationKernel:CategoricalEncoding'))
                end

                temp = setBaseArg(temp,'CategoricalPredictors',this.CategoricalPredictors);
                temp = setBaseArg(temp,'PredictorNames',this.PredictorNames);
                temp = setBaseArg(temp,'OrdinalPredictors',this.OrdinalPredictors);
                temp = setBaseArg(temp,'ResponseName',this.ResponseName);

                [model,fitinfo] = fit(temp,Xt,Y);
            end

            % Check if it is needed to concatenate the History of a
            % previous fit:
            if ~isempty(this.FitInfo.History)
                if isempty(fitinfo.History)
                    % Current fit did not return History, so only the
                    % previous History is copied:
                    fitinfo.History = this.FitInfo.History;
                else
                    % Concatenate both History structures
                    fns = fieldnames(fitinfo.History);
                    for i = 1:numel(fns)
                        fitinfo.History.(fns{i}) = [this.FitInfo.History.(fns{i});fitinfo.History.(fns{i})];
                    end
                end
                model.FitInfo.History = fitinfo.History;
            end

        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 the classification model MODEL and predictors X. X must be a
        %   table if MODEL was originally trained on a table and a numeric matrix
        %   if MODEL was originally trained on a matrix. If X is a table, it must
        %   contain all the predictors used for training the model. If X is a
        %   matrix, it must have size N-by-P, where P is the number of predictors
        %   used for training. LABEL is N-by-1 array of classification labels with
        %   the same data type as the response variable used for training. SCORE is
        %   an N-by-K numeric matrix of scores for N observations, and K classes.
        %   The predicted label is assigned to the class with the largest score.
        %
        %   See also ClassificationKernel.
            [varargout{1:nargout}] = predict@classreg.learning.classif.ClassificationModel(this,X,varargin{:});
        end

        function [varargout] = margin(this,X,varargin)
        %MARGIN Classification margins.
        %   M=MARGIN(MODEL,X,Y) returns classification margins obtained by the
        %   classification model MODEL for predictors X and class labels Y. X must
        %   be a table if MODEL was originally trained on a table and a numeric
        %   matrix if MODEL was originally trained on a matrix. If X is a table, it
        %   must contain all the predictors used for training the model. If X is a
        %   matrix, it must have size N-by-P, where P is the number of predictors
        %   used for training. Y must have the same data type as MODEL.ClassNames
        %   and have N rows. You can omit Y if it appears in the table X. The
        %   classification margin is the difference between the classification
        %   score for the true class and the maximal classification score for the
        %   false classes. The returned M is a numeric column vector of length N.
        %
        %   See also ClassificationKernel.
            [varargout{1:nargout}] = margin@classreg.learning.classif.ClassificationModel(this,X,varargin{:});
        end

        function [varargout] = edge(this,X,varargin)
        %EDGE Classification edge.
        %   E=EDGE(MODEL,X,Y) returns the classification edge obtained by the
        %   classification model MODEL for predictors X and class labels Y. X must
        %   be a table if MODEL was originally trained on a table and a numeric
        %   matrix if MODEL was originally trained on a matrix. If X is a table, it
        %   must contain all the predictors used for training the model. If X is a
        %   matrix, it must have size N-by-P, where P is the number of predictors
        %   used for training. Y must have the same data type as MODEL.ClassNames
        %   and  have N rows. You can omit Y if it appears in the table X. The
        %   classification edge is the classification margin averaged over the
        %   entire data.
        %
        %   E=EDGE(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
        %   name-value parameter pairs:
        %       'Weights'          - Observation weights, a numeric vector of length
        %                            size(X,1). By default, all observation weights
        %                            are set to 1. If you supply weights, EDGE
        %                            computes weighted classification edge.
        %
        %
        %   See also ClassificationKernel.
            [varargout{1:nargout}] = edge@classreg.learning.classif.ClassificationModel(this,X,varargin{:});
        end

        function [varargout] = loss(this,X,varargin)
        %LOSS Classification error.
        %   ERR=LOSS(MODEL,X,Y) returns the classification error for the model
        %   MODEL using predictors X and true class labels Y. X must be a table if
        %   the MODEL was originally trained on a table and a numeric matrix if
        %   MODEL was originally trained on a matrix. If X is a table, it must
        %   contain all the predictors used for training the model. If X is a
        %   matrix, it must have size N-by-P, where P is the number of predictors
        %   used for training. Y must have the data same type as MODEL.ClassNames
        %   and have N rows. You can omit Y if it appears in the table X.
        %
        %   ERR=LOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
        %   name-value parameter pairs:
        %       'LossFun'          - Function handle for loss, or string
        %                            representing a built-in loss function.
        %                            Available loss functions for classification:
        %                            'binodeviance', 'classiferror', 'classifcost',
        %                            'exponential', 'hinge', 'logit', 'mincost',
        %                            and 'quadratic'. If you pass a function handle
        %                            FUN, LOSS calls it as shown below:
        %                                  FUN(C,S,W,COST)
        %                            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, W is
        %                            a numeric vector with N elements, and COST is
        %                            a K-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. W is a vector of observation weights.
        %                            COST is a matrix of misclassification costs.
        %                            Default: 'classiferror'
        %       'Weights'          - Vector of observation weights. The length of
        %                            this vector must be equal to the number of
        %                            rows in X. Default is equal weights per class.
        %
        %
        %   See also ClassificationKernel.
            [varargout{1:nargout}] = loss@classreg.learning.classif.ClassificationModel(this,X,varargin{:});
        end

        function incrmdl = incrementalLearner(this,varargin)
        %INCREMENTALLEARNER Convert classification kernel model to incremental learner.
        %   INCREMENTALMODEL=INCREMENTALLEARNER(MODEL) returns an incremental binary
        %   classification kernel model INCREMENTALMODEL initialized
        %   using the parameters provided in MODEL.
        %
        %   INCREMENTALLEARNER initializes these INCREMENTALMODEL
        %   properties by using the properties of MODEL:
        %
        %       'ClassNames'
        %       'FitBias'
        %       'Learner'
        %       'Prior'
        %       'KernelScale'
        %       'Mu'                      - Mu is copied to INCREMENTALMODEL if Standardize is true.
        %       'Sigma'                   - Sigma is copied to INCREMENTALMODEL if Standardize is true.      
        %       'NumExpansionDimensions'
        %       'RandomStream'       
        %
        %   INCREMENTALMODEL=INCREMENTALLEARNER(MODEL,'PARAM1',val1,'PARAM2',val2,...)
        %   specifies optional parameter name/value pairs:
        %       'BatchSize'               - Mini-batch size for the stochastic
        %                                   solvers, specified as a positive
        %                                   scalar.
        %                                   Default is 10 for stochastic solvers.
        %                                   'BatchSize' is not supported
        %                                   for scale-invariant solver.
        %       'EstimationPeriod'        - Nonnegative integer specifying the number of
        %                                   observations that are used for
        %                                   estimation of hyperparameters prior to
        %                                   training. For a positive integer, if
        %                                   model has unknown hyperparameters
        %                                   that require estimation,
        %                                   hyperparameters are estimated.
        %                                   Default value is 1000 when estimation is needed.
        %                                   Otherwise, default value is 0.
        %                                   Refer to documentation for more information.
        %       'Lambda'                  - Ridge (L2) regularization strength specified as a
        %                                   nonnegative scalar.
        %                                   'Lambda' is not supported
        %                                   for scale-invariant solver.
        %                                   Default: 1e-5 for stochastic solvers.
        %       'LearnRate'               - Learning rate for the stochastic
        %                                   solvers, specified either as 'auto' or as a positive
        %                                   scalar. If 'Solver' is specified
        %                                   as 'sgd' or 'asgd', default is 'auto'.
        %                                   'LearnRate' is not supported for scale-invariant solver.
        %                                   Refer to documentation for info
        %                                   on 'auto' behavior.
        %       'LearnRateSchedule'       - String, one of 'decaying' or 'constant'.
        %                                   For stochastic solvers, if 'decaying',
        %                                   learning rate decreases over
        %                                   iterations. If 'constant', learning
        %                                   rate stays constant. Default is 'decaying'.
        %                                   'LearnRateSchedule' is not
        %                                   supported for scale-invariant solver.
        %       '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:
        %                                   '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, 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 will be extracted from provided function
        %                                   handle if it's not an anonymous
        %                                   function handle. If it is an anonymous
        %                                   function handle, Metric will be named
        %                                   as 'CustomMetric' and it will be post-fixed
        %                                   with an underscore and an integer
        %                                   identifying its relative position in
        %                                   the Metrics list.
        %                                   If you pass a struct, fieldnames will
        %                                   be 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 will be used for
        %                                   training the model prior to metrics
        %                                   update. Default is 0.
        %       'MetricsWindowSize'       - Positive integer specifying the window
        %                                   size that will be used for metrics
        %                                   update. Default value is 200.
        %       'Shuffle'                 - Logical value specifying
        %                                   whether observations
        %                                   are shuffled at each step.
        %                                   Default: true.
        %                                   'Shuffle' is not supported for
        %                                   stochastic solvers.
        %                                   Observations are always
        %                                   shuffled for stochastic
        %                                   solvers.
        %       'Solver'                  - String, one of: 'sgd', 'asgd' or 'scale-invariant'.
        %                                   If 'sgd', stochastic gradient descent
        %                                   solver is used. If 'asgd', average
        %                                   stochastic gradient descent solver is
        %                                   used. If 'scale-invariant', adaptive
        %                                   scale-invariant solver is used.
        %                                   Default is 'scale-invariant'.
        %
        %   Example: Initialize an incremental kernel model using a traditionally trained model
        %   and continue incremental training.
        %
        %       ttModel = fitckernel(Xoffline,Yoffline);
        %       mdl = incrementalLearner(ttModel);
        %       mdl = updateMetricsAndFit(mdl,Xnew,Ynew,'Weights',Wnew);
        %
        %   See also incrementalClassificationKernel.
        if size(this.ClassNames,1) == 1 % error out for one class models
            error(message('stats:incremental:Offline:OneClass'));
        end
		
        try
            args = this.extractIncrementalParameters(varargin{:});
        catch ME
            throw(ME);
        end

        incrmdl = incrementalClassificationKernel(args);
        incrmdl.Impl  = setFeatureMapper(incrmdl.Impl,this.FeatureMapper); % use the same feature mapper

        end
    end

    methods(Static,Hidden)

        function [lambda,bc] = resolveAutoLambdaOrEmptyBoxConstraint(lambda,bc,n)
            if strcmp(lambda,'auto')
                lambda = 1/bc/n;
            elseif isempty(bc)
                bc = 1/lambda/n;
            end
        end

        function numExpansionDimensions = resolveAutoNumExpansionDimensions(numExpansionDimensions,d)
            if strcmp(numExpansionDimensions,'auto')
                numExpansionDimensions = 2.^ceil(min(log2(d)+5,15));
            end
        end

        function kernelScale = resolveAutoKernelScale(kernelScale,X,Y,type)
            if strcmp(kernelScale,'auto')
                kernelScale = classreg.learning.svmutils.optimalKernelScale(X,Y,type);
            end
        end

        function featureMapper = resolveEmptyFeatureMapper(featureMapper,d,expDim,transformation,stream)
            if isempty(featureMapper)
                featureMapper = classreg.learning.rkeutils.featureMapper(stream,d,expDim,transformation);
            end
        end

        function initialBeta = resolveEmptyInitialBeta(initialBeta,expDim)
            if isempty(initialBeta)
                initialBeta = zeros(expDim,1);
            end
        end

        function initialBias = resolveEmptyInitialBias(initialBias,lossFunction,Y,W)
            if isempty(initialBias)
                switch lossFunction
                  case 'hinge'
                    initialBias = 0;
                  case 'logit'
                    initialBias = sum(W'*Y)/sum(W);
                end
            end
        end

        function maxChunkSize = estimateMaxChunkSize(numExpansionDimensions,blockSize,isFitting)
            memoryPerExpandedRow = numExpansionDimensions * 8;
            memoryBlock = blockSize * 1e6;
            maxChunkSize = floor(memoryBlock ./ memoryPerExpandedRow); %RowsPerBlock (Maximum)
            if isFitting && maxChunkSize<1000 %Only check for few rows per chunk when we are fitting
                error(message('stats:ClassificationKernel:FewObservationsPerBlock'))
            end
        end

        function temp = template(varargin)
            classreg.learning.FitTemplate.catchType(varargin{:}); %You are not allowed to pass 'type' argument.
            temp = classreg.learning.FitTemplate.make('Kernel',...
                                                      'type','classification',varargin{:});
        end

        function [varargout] = fit(X,Y,varargin)
            temp = ClassificationKernel.template(varargin{:});
            [varargout{1:nargout}] = fit(temp,X,Y);
        end

        function [X,Y,W,dataSummary,classSummary,scoreTransform] = ...
                prepareData(X,Y,varargin)
        % Reuse ClassificationLinear.prepareData, but first turn off
        % some warnings about data being removed, as differently to
        % Classification.Linear, filtering them does not impact
        % significantly on memory/speed.
            warnState  = warning('query','all');
            warning('off','stats:classreg:learning:Linear:prepareDataCR:XwithNaNs');
            warning('off','stats:classreg:learning:Linear:prepareDataCR:WwithNaNs');
            warning('off','stats:ClassificationLinear:prepareData:YwithMissingValues');
            cleanupObj = onCleanup(@() warning(warnState));

            % now we can call  ClassificationLinear.prepareData
            [X,Y,W,dataSummary,classSummary,scoreTransform] = ...
                ClassificationLinear.prepareData(X,Y,varargin{:});
            % ... but we also restrict sparse data
            internal.stats.checkSupportedNumeric('X',X,false,false,false)
        end

        function obj = makeNoFit(beta,bias,lambda,featureMapper,kernelScale,boxConstraint,modelParams,dataSummary,classSummary,fitinfo,scoreTransform,Mu,Sigma)
        % Makes a ClassificationKernel object without fitting by
        % feeding model parameters (beta and bias). input model
        % parameters (modelParams) and featureMapper

            if isempty(scoreTransform)
                switch lower(modelParams.LossFunction)
                  case 'hinge'
                    scoreTransform = @classreg.learning.transform.identity;
                  case 'logit'
                    scoreTransform = @classreg.learning.transform.logit;
                end
            end

            obj = ClassificationKernel(dataSummary,classSummary,scoreTransform);

            obj.DefaultLoss = @classreg.learning.loss.classiferror;
            obj.LabelPredictor = @classreg.learning.classif.ClassificationModel.maxScore;
            obj.DefaultScoreType = 'inf';

            switch lower(modelParams.LossFunction)
              case 'hinge'
                obj.Learner = 'svm';
              case 'logit'
                obj.Learner = 'logistic';
                if isequal(obj.PrivScoreTransform,@classreg.learning.transform.identity)
                    obj.ScoreTransform = 'logit';
                    obj.ScoreType = 'probability';
                end
            end

            obj.KernelScale = kernelScale;
            obj.FeatureMapper = featureMapper;
            obj.BoxConstraint = boxConstraint;

            modelParams.Lambda = lambda; % Impl keeps the real lambda in case modelParams is still 'auto'
            obj.Impl = classreg.learning.impl.LinearImpl.makeNoFit(modelParams,beta,bias,fitinfo);

            obj.ModelParams = modelParams;

            % save fitInfo into a private property, used only by resume method to
            % concatenate history information. User should only obtain
            % fitInfo from the second output argument.
            obj.FitInfo = fitinfo;
            obj.Mu = Mu;
            obj.Sigma = Sigma;
        end


        function [obj,fitInfo,fittedScore] = fitClassificationKernel(...
            X,Y,W,modelParams,dataSummary,classSummary,scoreTransform,...
            fitoneclass,isExpandedX)

            returnScore = false;
            if nargout > 2
                returnScore = true;
                fittedScore =[];
            end

            if nargin < 9
                isExpandedX = false;
            end

            if nargin < 8
                fitoneclass = false;
            end

            [X,Y,W,dataSummary,classSummary] = ...
                ClassificationLinear.removeNaNsFromX(X,Y,W,dataSummary,classSummary);

            modelParams = fillIfNeeded(modelParams,X,Y,W,dataSummary,classSummary);
            obj = ClassificationKernel(dataSummary,classSummary,scoreTransform);

            obj.DefaultLoss = @classreg.learning.loss.classiferror;
            obj.LabelPredictor = @classreg.learning.classif.ClassificationModel.maxScore;
            obj.DefaultScoreType = 'inf';

            switch lower(modelParams.LossFunction)
              case 'hinge'
                obj.Learner = 'svm';

              case 'logit'
                obj.Learner = 'logistic';
                if isequal(obj.PrivScoreTransform,@classreg.learning.transform.identity)
                    obj.ScoreTransform = 'logit';
                    obj.ScoreType = 'probability';
                end
            end

            % Map classes to -1 and +1
            gidx = grp2idx(Y,obj.ClassSummary.NonzeroProbClasses);
            if any(gidx==2)
                doclass = 2;
                gidx(gidx==1) = -1;
                gidx(gidx==2) = +1;
            else
                doclass = 1;
            end

            % Expand categorical predictors if necessary
            if any(obj.CategoricalPredictors) && ~isExpandedX
                [X,iscat] = classreg.learning.internal.expandCategorical(X,...
                                                                 obj.CategoricalPredictors,obj.OrdinalPredictors,...
                                                                 obj.VariableRange,obj.ReferenceLevels,false);
                if isempty(X)                
                    error(message('stats:classreg:learning:impl:SVMImpl:make:OneOrdinalOneLvl'));                
                end
            else
                iscat = false(1,size(X,1));
            end

            if modelParams.StandardizeData
                [X,obj.Mu,obj.Sigma] = classreg.learning.Kernel.standardize(X,W,iscat);
            end

            if doclass==2
                W = classreg.learning.internal.adjustPrior(classSummary,Y,W);
            end

            [d,n] = size(X); % Number of predictors and observations
            if d>=4096  %2^12
                        % fitckernel shall not be used in high dimensional datasets
                error(message('stats:ClassificationKernel:InvalidInputDimensions'))
            end

            % Resolve parameters that may still be set to 'auto' or empty
            % (Order matters!). All these modifications are not placed back
            % into modelParams; the actual lambda is saved in obj.impl after
            % fitting, and the actual numExpansionDimensions is saved in
            % obj.FeatureMapper after fitting.
            [lambda,obj.BoxConstraint] = ClassificationKernel.resolveAutoLambdaOrEmptyBoxConstraint(modelParams.Lambda,modelParams.BoxConstraint,n);
            numExpansionDimensions =  ClassificationKernel.resolveAutoNumExpansionDimensions(modelParams.NumExpansionDimensions,d);
            if numExpansionDimensions<= d
                error(message('stats:ClassificationKernel:InvalidNumExpansionDimensions'))
            end
            obj.FeatureMapper = ClassificationKernel.resolveEmptyFeatureMapper(modelParams.FeatureMapper,d,numExpansionDimensions,modelParams.Transformation,modelParams.Stream);
            obj.KernelScale = ClassificationKernel.resolveAutoKernelScale(modelParams.KernelScale,X',gidx,doclass);

            initialBeta = ClassificationKernel.resolveEmptyInitialBeta(modelParams.InitialBeta,numExpansionDimensions);
            initialBias = ClassificationKernel.resolveEmptyInitialBias(modelParams.InitialBias,modelParams.LossFunction,gidx,W);

            % Figure out maxChunkSize from BlockSize
            maxChunkSize = ClassificationKernel.estimateMaxChunkSize(numExpansionDimensions,modelParams.BlockSize,true);
            numberChunks = ceil(n / maxChunkSize);

            if numberChunks == 1
                %%%%%%%% FIT THE DATA IN ONE MEMORY BLOCK %%%%%%%%%%%%%%
                if modelParams.IterationLimit == 0
                    error(message('stats:ClassificationKernel:InvalidIterationLimit'))
                end
                Xm = map(obj.FeatureMapper,X',obj.KernelScale)';

                obj.Impl = classreg.learning.impl.LinearImpl.make(doclass, ...
                                                                  initialBeta,initialBias,...
                                                                  Xm,gidx,W,...
                                                                  modelParams.LossFunction,...
                                                                  strcmp(modelParams.Regularization,'ridge'),...
                                                                  lambda,...
                                                                  [],...  % PassLimit
                                                                  [],...  % BatchLimit
                                                                  [],...  % NumCheckConvergence
                                                                  [],...  % BatchIndex
                                                                  [],...  % BatchSize
                                                                  modelParams.Solver,...
                                                                  modelParams.BetaTolerance,...
                                                                  modelParams.GradientTolerance,...
                                                                  1e-6,... % DeltaGradientTolerance
                                                                  [],...   % LearnRate
                                                                  [],...   % OptimizeLearnRate
                                                                  [],...   % ValidationX
                                                                  [],...   % ValidationY
                                                                  [],...   % ValidationW
                                                                  modelParams.IterationLimit,...
                                                                  [],...   % TruncationPeriod,...
                                                                  modelParams.FitBias,...
                                                                  modelParams.PostFitBias,...
                                                                  [],... % Epsilon
                                                                  modelParams.HessianHistorySize,...
                                                                  modelParams.LineSearch,...
                                                                  0, ... % Consensus
                                                                  [],... % Stream
                                                                  modelParams.VerbosityLevel,...
                                                                  fitoneclass);

                fiHis = obj.Impl.FitInfo.History;
                if isempty(fiHis)
                    fiHisNew = [];
                else
                    % Adapt the history of the Fast Linear Solver to
                    % ClassificationKernel's history
                    fiHisNew = struct('ObjectiveValue',fiHis.Objective,...
                                      'GradientMagnitude',fiHis.Gradient,...
                                      'Solver',categorical(repmat({'LBFGS-fast'},numel(fiHis.Solver),1)),...
                                      'IterationNumber',fiHis.NumIterations,...
                                      'DataPass',cumsum(fiHis.NumPasses),...
                                      'RelativeChangeInBeta',fiHis.RelativeChangeInBeta,...
                                      'ElapsedTime',[nan(numel(fiHis.Solver)-1,1);obj.Impl.FitInfo.FitTime]);
                end

                fitInfo = struct('Solver','LBFGS-fast',...
                                 'LossFunction',modelParams.LossFunction,...
                                 'Lambda',lambda,...
                                 'BetaTolerance', modelParams.BetaTolerance,...
                                 'GradientTolerance',modelParams.GradientTolerance,...
                                 'ObjectiveValue',obj.Impl.FitInfo.Objective,...
                                 'GradientMagnitude',obj.Impl.FitInfo.GradientNorm,...
                                 'RelativeChangeInBeta',obj.Impl.FitInfo.RelativeChangeInBeta,...
                                 'FitTime',obj.Impl.FitInfo.FitTime,...
                                 'History',fiHisNew);

                if returnScore
                    S1 = score(obj.Impl,Xm,true,false); % doClass = true , obsInRows = false
                    % Expand a linear model score to scores for each class
                    fittedScore = expandLinearMdlScoreForEachClass(obj.ClassSummary, S1);
                end

            else
                if (modelParams.IterationLimit + modelParams.ADMMIterationLimit) == 0
                    error(message('stats:ClassificationKernel:InvalidIterationLimitADMM'))
                end
                %%%%%%%% FIT THE DATA WITH A BLOCK-WISE SOLVER %%%%%%%%%%%%%%
                X = X';
                if modelParams.VerbosityLevel>0
                    fprintf(getString(message('stats:ClassificationKernel:FoundNBlocks',numberChunks)))
                end

                if fitoneclass
                    Beta = initialBeta;
                else
                    Beta = [initialBias;initialBeta]; %including Bias
                end

                doridge = strcmp(modelParams.Regularization,'ridge');
                rho = modelParams.Consensus;

                Wk = zeros(numberChunks,1);
                X_ = cell(numberChunks,1);
                gidx_ = cell(numberChunks,1);
                W_ = cell(numberChunks,1);
                h = 0;
                for i = 1:maxChunkSize:n
                    h = h + 1;
                    j = min(i+maxChunkSize-1,n);
                    Wk(h) = sum(W(i:j,:),1);
                    X_{h} = X(i:j,:);
                    gidx_{h} = gidx(i:j,:);
                    W_{h} = W(i:j,:);
                end
                hfixedchunkfun = @fixedchunkfun;
                hmarkforreuse = @(x)x; % Doesn't matter because X is not tall
                hupdateforreuse = @(y,x)x; % Doesn't matter because X is not tall

                betaTol = modelParams.BetaTolerance;
                gradTol = modelParams.GradientTolerance;
                admmIterationLimit = modelParams.ADMMIterationLimit;
                tallPassLimit = intmax;
                verbose = modelParams.VerbosityLevel;

                doBias = modelParams.FitBias;
                lossfun = modelParams.LossFunction;

                % Parameters for the LBFGS inside ADMM iterations:
                iterationlimit_ADMMLBFGS = [modelParams.WarmStartIterationLimit modelParams.ADMMUpdateIterationLimit];
                hessianHistorySize_ADMMLBFGS = 15;  % fixed, cannot be controlled by user
                dowolfe_ADMMLBFGS = true;           % fixed, cannot be controlled by user
                doBias_ADMMLBFGS  = doBias;         % already restricted to always true above
                gradTol_ADMMLBFGS = gradTol;        % same as outer loop
                betaTol_ADMMLBFGS = betaTol;        % same as outer loop
                doridge_ADMMLBFGS = doridge;        % already restricted to always true above
                lossfun_ADMMLBFGS = lossfun;        % already set to 'mse' or 'hinge' above

                % Make the objective function
                expType = modelParams.Transformation;
                objgraF = makeobjgradF(X_,gidx_,W_,lossfun,lambda./numberChunks,...
                    obj.FeatureMapper,obj.KernelScale,fitoneclass);
                % Wrap objective function with verbosity and progress tracking behavior
                hclientfun = @(x)(x); % Doesn't matter because X is not tall
                keepHist = true;
                progressF = classreg.learning.linearutils.linearSolverProgressFunction(objgraF,false,admmIterationLimit>1,verbose>0,keepHist,hclientfun);

                % One-class shortcut
                % Replicates ClassificationLinear.fitClassificationLinear behavior
                if doclass==1 && ~fitoneclass
                    if strcmp(lossfun,'logit')
                        Beta = [inf;zeros(numExpansionDimensions,1)];
                    elseif strcmp(lossfun,'hinge')
                        Beta = [1;zeros(numExpansionDimensions,1)];
                    end
                end

                if fitoneclass
                    doBias_ADMMLBFGS = false;
                end

                %%%%%%%%%%%%%%%%%%%%%%%%%%% ADMM FIT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

                Beta = classreg.learning.linearutils.ADMMimpl(X_,gidx_,W_,Beta,rho,lambda,doridge,...
                                                              numberChunks,Wk,hmarkforreuse,hupdateforreuse,hfixedchunkfun,...
                                                              betaTol,gradTol,admmIterationLimit,tallPassLimit,progressF,verbose,...
                                                              lossfun_ADMMLBFGS,betaTol_ADMMLBFGS,gradTol_ADMMLBFGS,...
                                                              iterationlimit_ADMMLBFGS, doridge_ADMMLBFGS,...
                                                              hessianHistorySize_ADMMLBFGS,dowolfe_ADMMLBFGS,doBias_ADMMLBFGS,...
                                                              obj.FeatureMapper,expType,obj.KernelScale,fitoneclass);

                %%%%%%%%%%%%%%%%%%%%%%%%%%% CENTRALIZED LBFGS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                hessianHistorySize = modelParams.HessianHistorySize;
                lineSearch = modelParams.LineSearch;
                iterationlimit = modelParams.IterationLimitBlockWise;
                initialStepSize = modelParams.InitialStepSize;

                Beta = classreg.learning.linearutils.LBFGSimpl(Beta,progressF,verbose,...
                                                               betaTol,gradTol,iterationlimit,tallPassLimit,...
                                                               hessianHistorySize,lineSearch,initialStepSize);

                if fitoneclass
                    beta = Beta;
                    bias = 0;
                else
                    beta = Beta(2:end);
                    bias = Beta(1);
                end

                if verbose>0
                    printLastLine(progressF);
                end

                modelParams.Lambda = lambda; % Impl keeps the real lambda in case modelParams is still 'auto'

                obj.Impl = classreg.learning.impl.LinearImpl.makeNoFit(modelParams,beta,bias,progressF.History);

                fiHis = obj.Impl.FitInfo;
                if verbose>0
                    % Adapt the history of linearSolverProgressFunction to
                    % ClassificationKernel's history
                    fiHisNew = struct('ObjectiveValue',fiHis.ObjectiveValue(:),...
                                      'GradientMagnitude',fiHis.GradientMagnitude(:),...
                                      'Solver',categorical(repmat({'LBFGS-blockwise'},numel(fiHis.Solver),1)),...
                                      'IterationNumber',fiHis.IterationNumber(:),...
                                      'DataPass',fiHis.DataPass(:),...
                                      'RelativeChangeInBeta',fiHis.RelativeChangeBeta(:),...
                                      'ElapsedTime',fiHis.ElapsedTime(:));
                else
                    fiHisNew = [];
                end

                fitInfo = struct('Solver','LBFGS-blockwise',...
                                 'LossFunction',modelParams.LossFunction,...
                                 'Lambda',modelParams.Lambda,...
                                 'BetaTolerance', modelParams.BetaTolerance,...
                                 'GradientTolerance',modelParams.GradientTolerance,...
                                 'ObjectiveValue',obj.Impl.FitInfo.ObjectiveValue(end),...
                                 'GradientMagnitude',obj.Impl.FitInfo.GradientMagnitude(end),...
                                 'RelativeChangeInBeta',obj.Impl.FitInfo.RelativeChangeBeta(end),...
                                 'FitTime',obj.Impl.FitInfo.ElapsedTime(end),...
                                 'History',fiHisNew);


            end

            obj.ModelParams = modelParams;

            % save fitInfo into a private property, used only by resume method to
            % concatenate history information. User should only obtain
            % fitInfo from the second output argument.
            obj.FitInfo = fitInfo;
        end

        function obj = fromStruct(s)
            % Make a Kernel object from a codegen struct.

            s.ScoreTransform = s.ScoreTransformFull;

            % Remove fields added for codegen
            s.DataSummary = rmfield(s.DataSummary,{'NumExpandedPredictors','NumCategories'});
            s = rmfield(s,'MaximumChunkSize');
            
            modelTrainedWithTallArray = s.DataSummary.ModelTrainedWithTallArray;
            s.DataSummary = rmfield(s.DataSummary,'ModelTrainedWithTallArray');
            s = classreg.learning.coderutils.structToClassif(s);

            if isfield(s.DataSummary,'RemoveObservationsWithMissingValues')
                s.DataSummary = rmfield(s.DataSummary,'RemoveObservationsWithMissingValues');
            end

            if modelTrainedWithTallArray
                % Model trained with non-table data but has PredictorNames,
                % must have been trained with tall data.
                s.DataSummary.VariableRange = {};
                s.DataSummary = rmfield(s.DataSummary,'BinnedX');
                s.DataSummary = rmfield(s.DataSummary,'BinEdges');
            end

            beta = s.Impl.Beta;
            bias = s.Impl.Bias;
            
            featureMapper = classreg.learning.rkeutils.featureMapper.fromStruct(s.FeatureMapper);
            kernelScale = s.ModelParams.KernelScale;
            boxConstraint = s.ModelParams.BoxConstraint;
            modelParams = classreg.learning.coderutils.coderStructToKernelParams(s.ModelParams,modelTrainedWithTallArray);
            lambda = s.ModelParams.Lambda;
            dataSummary = s.DataSummary;
            classSummary = s.ClassSummary;
            fitinfo = s.Impl.FitInfo;
            scoreTransform = s.ScoreTransform;
            if ~isfield(s,'Mu') %for 23a or earlier releases
                Mu=[];
                Sigma=[];
            else
                Mu = s.Mu;
                Sigma = s.Sigma;
            end

            % Make an object
            obj = ClassificationKernel.makeNoFit(beta,bias,lambda,featureMapper,kernelScale,boxConstraint,modelParams,...
                                                                         dataSummary,classSummary,fitinfo,scoreTransform,Mu,Sigma);

            % Score handling
            if isempty(s.ScoreType)
                obj.ScoreType         = 'none';
            else
                obj.ScoreType         = s.ScoreType;
            end

            obj.DefaultLoss       = s.DefaultLoss;
            obj.LabelPredictor    = s.LabelPredictor;
            obj.DefaultScoreType  = s.DefaultScoreType; 
            obj.FitInfo = s.FitInfo;

            % Override Impl using fromStruct
            try
                obj.Impl = classreg.learning.impl.LinearImpl.fromStruct(s.Impl);
            catch ME
                if contains(ME.identifier,'stats:classreg:learning:impl:LinearImpl:make:BadStatus') % reset termination code MaxIter reached
                    s.Impl.FitInfo.TerminationCode = 0;
                    obj.Impl = classreg.learning.impl.LinearImpl.fromStruct(s.Impl);
                else
                    rethrow(ME);
                end
            end            
        end

        function name = matlabCodegenRedirect(~)
            name = 'classreg.learning.coder.classif.ClassificationKernel';
        end
    end

    methods(Hidden)
        function s = toStruct(this,varargin)
            % Convert to a struct for codegen.

            warnState  = warning('query','all');
            warning('off','MATLAB:structOnObject');
            cleanupObj = onCleanup(@() warning(warnState));

            % convert common properties to struct
            s = classreg.learning.coderutils.classifToStruct(this);

            modelTrainedWithTallArray = false;
            if ischar(s.DataSummary.PredictorNames) && isfield(s.DataSummary,'TableInput') && ~s.DataSummary.TableInput
                modelTrainedWithTallArray = true;
            end    
            s.DataSummary.ModelTrainedWithTallArray = modelTrainedWithTallArray;             
            
            s.Impl = toStruct(this.Impl);
            s.FeatureMapper = toStruct(this.FeatureMapper);

            % model params
            s.ModelParams = classreg.learning.coderutils.kernelParamsToCoderStruct(...
                this.ModelParams);            

            % save the path to the fromStruct method
            s.FromStructFcn = 'ClassificationKernel.fromStruct';

            s.DataSummary.NumExpandedPredictors = numel(getExpandedPredictorNames(this));
            s.DataSummary.NumCategories = cellfun(@numel,this.VariableRange);
            s.MaximumChunkSize = ClassificationKernel.estimateMaxChunkSize(this.FeatureMapper.n,this.ModelParams.BlockSize,false);
            s.FitInfo = this.FitInfo;
            s.Mu = this.Mu;
            s.Sigma = this.Sigma;


        end
		
        function mergedargs = extractIncrementalParameters(this,varargin)
            [args,additionalargs] = incremental.utils.offlineLinearParameters(this,true,varargin{:});
            [args,additionalargs] = incremental.utils.offlineClassificationParameters(this,args,additionalargs);
            mergedargs = incremental.utils.mergeOfflineParameters(args,additionalargs);
            % set parameters exclusive to kernel models : KernelScale,Mu,Sigma
            % RandomStream, and NumExpansionDimensions
            mergedargs.KernelScale = this.KernelScale;
            mergedargs.Mu = this.Mu;
            mergedargs.Sigma = this.Sigma;
            mergedargs.NumExpansionDimensions = this.NumExpansionDimensions;
            mergedargs.RandomStream = this.ModelParameters.Stream; % user passed stream
            mergedargs.NumPredictors = numel(this.PredictorNames); % explicitly pass NumPredictors because they are not the same size as Beta
        end		
    end    

end

function objgraF = makeobjgradF(X,Y,W,lossfun,lambdaK,FM,kernelScale,fitoneclass)
    objgraF = @fcn;
    function [obj,gra] = fcn(Beta)
        [obj,gra] = fixedchunkfun(1,@(x,y,w) ...
            chunkObjGraFun(Beta,x,y,w,lossfun,lambdaK,FM,kernelScale,fitoneclass), ...
            X,Y,W);
        obj = sum(obj,1);
        gra = sum(gra,1);
    end
end

function  varargout = fixedchunkfun(FixedNumSlices,fcn,varargin)
% Replicates tall.fixedchunkfun on non-tall inputs so we can use the same
% implementation of the solvers. Assumes all variables in varargin will be
% partitioned (i.e. no broadcasted inputs). This functions is called in two
% places: 1) from the objgra functor (above), and 2) from the Beta update
% in the ADMM algorithm.

    n = size(varargin{1},1); % height
    nia = numel(varargin);
    varargout = cell(1,nargout);
    h = 0;
    for i = 1:FixedNumSlices:n
        h = h +1;
        j = min(i+FixedNumSlices-1,n);
        invar = cell(1,nia);
        outvar = cell(1,nargout);
        for k = 1:nia
            invar{k} = varargin{k}(i:j,:);
        end
        [outvar{1:nargout}] = fcn(invar{:});
        for k = 1:nargout
            varargout{k}(h,:) = outvar{k};
        end
    end

end

function [obj,gra] = chunkObjGraFun(Beta,x,y,w,lossfun,lambda,FM,kernelScale,fitoneclass)
x = x{1};
y = y{1};
w = w{1};
epsilon = 0; % never used
useBias = true;
doridge = true;
if fitoneclass
    if isempty(x)
        obj = (Beta'*Beta)*lambda/2;
        gra = (Beta*lambda)';
    else
        xm = map(FM,x,kernelScale);
        [obj,gra] = classreg.learning.linearutils.objgrad( Beta,0,xm',y,w,...
            lossfun,doridge,lambda,epsilon,useBias,...
            zeros(numel(Beta),1,'like',Beta),0,0 );
        gra = gra(1:end-1)';
    end
else
    if isempty(x)
        obj = (Beta(2:end)'*Beta(2:end))*lambda/2;
        gra = [0;Beta(2:end)*lambda]';
    else
        xm = map(FM,x,kernelScale);
        [obj,gra] = classreg.learning.linearutils.objgrad( Beta(2:end),Beta(1),xm',y,w,...
                                                           lossfun,doridge,lambda,epsilon,useBias,...
                                                           zeros(numel(Beta)-1,1,'like',Beta),0,0 );
        gra = [gra(end);gra(1:end-1)]';
        % obj = sum(max(0,1-(x*Beta(2:end)+Beta(1)).*y).*w)+(Beta(2:end)'*Beta(2:end))*lambda/2;
        % gra = (sum((y.*(x*Beta(2:end)+Beta(1))<1) .* (-y.*[ones(numel(w),1) x].*w))' + [0;Beta(2:end)*lambda])';
    end
end
end

function S = expandLinearMdlScoreForEachClass(ClassSummary, S1)
% Expand a linear model score to scores for each class
K = numel(ClassSummary.ClassNames);
% Find column index to match the class name order
[~,pos] = ismember(ClassSummary.NonzeroProbClasses,...
                   ClassSummary.ClassNames);
S = repmat(-S1,1,K);
if numel(pos)==1 % one-class learning
    S(:,pos) = S1;
else             % binary learning
    S(:,pos(2)) = S1;
end
end

% LocalWords:  INCREMENTALLEARNER INCREMENTALMODEL Warmup tt Xoffline Yoffline Xnew Ynew Wnew Xwith
% LocalWords:  Wwith Ywith NBlocks fixedchunkfun objgra gra
