classdef RegressionLinear < ...
        classreg.learning.regr.RegressionModel & classreg.learning.Linear
%RegressionLinear Linear model for regression.
%   RegressionLinear is a linear model for regression. This model can
%   predict responses for new data.
%
%   RegressionLinear properties:
%       PredictorNames         - Names of predictors used for this model.
%       ExpandedPredictorNames - Names of expanded predictors.
%       ResponseName           - Name of the response variable.
%       ResponseTransform      - Transformation applied to predicted response values.
%       Learner                - Learned model, 'svm' or 'leastsquares'.
%       Beta                   - Linear coefficients.
%       Bias                   - Bias term.
%       FittedLoss             - Fitted loss function, 'epsiloninsensitive' or 'mse'.
%       Lambda                 - Regularization strength.
%       Regularization         - Type of regularization, L1 or L2.
%       Epsilon                - Half of the width of the epsilon-insensitive band for SVM.
%
%   RegressionLinear methods:
%       incrementalLearner    - Return an incremental regression linear model.
%       loss                  - Regression loss.
%       partialDependence     - Partial Dependence values.
%       plotPartialDependence - Plot Partial Dependence.
%       predict               - Predicted response of this model.
%       selectModels          - Select a subset of fitted regularized models.
%       update                - Update model parameters for code generation.

%   Copyright 2015-2023 The MathWorks, Inc.

    properties(GetAccess=public,SetAccess=protected,Dependent=true)
        %Epsilon Half of the width of the epsilon-insensitive band for SVM.
        %   The Epsilon property is a scalar specifying half the width of
        %   the epsilon-insensitive band for SVM. If the Learner property
        %   is not 'svm', this property is empty.
        %
        %   See also RegressionLinear, Learner.
        Epsilon;
    end

    methods
        function e = get.Epsilon(this)
            e = this.Impl.Epsilon;
        end
    end


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

            this = this@classreg.learning.regr.RegressionModel(...
                dataSummary,responseTransform,defaultResponse);
            this = this@classreg.learning.Linear;
            this.CategoricalVariableCoding = 'dummy';
            this.AllowedPredictArgs = struct('Name','PredictionForMissingValue','Value','Median');
        end

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


    methods(Access=protected)
        function s = propsForDisp(this,s)
            s = propsForDisp@classreg.learning.regr.RegressionModel(this,s);
            s = propsForDisp@classreg.learning.Linear(this,s);
            s = rmfield(s,'CategoricalPredictors');
        end

        function S = response(this,X,obsInRows,predMissing)
            if any(this.CategoricalPredictors)
                if ~this.TableInput
                    % Explicit treatment of numerical predictors as categorical
                    % (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

            S = score(this.Impl,X,false,obsInRows);

            if obsInRows
                nanX = any(isnan(X),2);
            else
                nanX = any(isnan(X),1);
            end

            if any(nanX)
                S(nanX,:) = classreg.learning.internal.predictionForMissingValue(...
                    predMissing,this.DefaultResponse);
            end
        end

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

        function this = gatherOne(this)
            % Gathers a single RegressionLinear object. Used by
            % RegressionLinear/gather
            this = gatherOne@classreg.learning.regr.RegressionModel(this);
            this = gatherOne@classreg.learning.Linear(this);
        end
    end


    methods
        function Yfit = predict(this,X,varargin)
        %PREDICT Predict response of the model.
        %   YFIT=PREDICT(MODEL,X) returns the predicted response YFIT for linear
        %   model MODEL and predictors X. X must be a numeric matrix if MODEL was
        %   originally trained on a matrix and a table if the MODEL was originally
        %   trained on a table. If X is a matrix, it must have size N-by-P, where P
        %   is the number of predictors used for training. If X is a table, it must
        %   contain all the predictors used for training the model. YFIT is an
        %   N-by-L matrix with same data type as Y, where N is the number of
        %   observations (rows) in X and L is the number of values in Lambda.
        %
        %   YFIT=(MODEL,X,'PARAM1',val1,'PARAM2',val2,...) specifies optional
        %   name/value arguments:
        %       'ObservationsIn'    - String specifying the data orientation,
        %                             either 'rows' or 'columns'. Default: 'rows'
        %                             NOTE: Passing observations in columns can
        %                                   significantly speed up prediction.
        %       'PredictionForMissingValue' - Numeric scalar, character vector, or string scalar. 
        %                                     If a character vector or string scalar, must be one of: 
        %                                     'median' or 'mean'. If a numeric scalar, PREDICT uses
        %                                     this response for observations with missing predictor 
        %                                     values. If 'mean' or 'median', PREDICT uses the mean 
        %                                     or median of observed response values in the training data. 
        %                                     Default: 'median'
        %
        %   See also RegressionLinear, Lambda.

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

            args = {'observationsin' 'predictionformissingvalue'};
            defs = {          'rows'                    'median'};
            [obsInRows,predMissing] = internal.stats.parseArgs(args,defs,varargin{:});

            % Handle input data such as "tall" requiring a special adapter
            adapter = classreg.learning.internal.makeClassificationModelAdapter(this,X,varargin{:});
            if ~isempty(adapter)
                Yfit = predict(adapter,X,varargin{:});
                return
            end

            % Convert to matrix X if necessary
            if this.TableInput || istable(X)
                X = classreg.learning.internal.table2PredictMatrix(X,[],[],...
                    this.VariableRange,this.CategoricalPredictors,this.PredictorNames);
            end

            internal.stats.checkSupportedNumeric('X',X,false,true,false,true);

            % Detect the orientation (do not transpose as Linear models
            % know how to handle both orientations)
            [X,obsInRows] = classreg.learning.internal.orientX(...
                X,false,false,'ObservationsIn',obsInRows);

            % Check num of predictors in data
            classreg.learning.internal.numPredictorsCheck(X,...
                getNumberPredictors(this),obsInRows)

            % Validate predMissing
            predMissing = classreg.learning.internal.validatePredictionForMissing(predMissing,mfilename);

            % Predictions for empty X
            if isempty(X)
                outputPrototype = internal.stats.dominantType(X, this.Beta, this.Bias);
                Yfit = NaN(0,1,"like",outputPrototype);
                return;
            end

            % Predict
            Yfit = this.PrivResponseTransform(response(this,X,obsInRows,predMissing));
        end

        function l = loss(this,X,varargin)
        %LOSS Regression error.
        %   ERR=LOSS(MODEL,X,Y) returns the mean squared error for MODEL computed
        %   using predictors X and observed response Y. X must be a numeric matrix
        %   if MODEL was originally trained on a matrix and a table if MODEL was
        %   originally trained on a table. If X is a matrix, it must have size
        %   N-by-P, where P is the number of predictors used for training. If X is
        %   a table, it must contain all the predictors used for training the model.
        %   Y must be a vector of N floating-point numbers, where N is the number
        %   of observations (columns) in X. You can omit Y if it appears in the
        %   table X. ERR is a row vector with L values, where L is the number of
        %   elements in Lambda.
        %
        %   ERR=LOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
        %   name/value arguments:
        %       'ObservationsIn'   - String specifying the orientation of
        %                            X, either 'rows' or 'columns'.
        %                            Default: 'rows'
        %                          NOTE: Passing observations in columns can
        %                                significantly speed up prediction.
        %       'LossFun'          - Function handle for loss, or string
        %                            representing a built-in loss function.
        %                            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 columns in X.
        %       'PredictionForMissingValue' - Numeric scalar, character vector, or
        %                                     string scalar. If a character vector or
        %                                     string scalar, must be one of:
        %                                     'median', 'mean', or 'omitted'. If a
        %                                     numeric scalar, LOSS uses this response 
        %                                     for observations with missing predictor 
        %                                     values. If 'mean' or 'median', LOSS uses 
        %                                     the mean or median of observed response 
        %                                     values in the training data. If 'omitted',
        %                                     LOSS excludes observations with missing 
        %                                     predictor values from the computation. 
        %                                     Default: 'median'
        %
        %   See also RegressionLinear, predict, Lambda.

            [varargin{:}] = convertStringsToChars(varargin{:});
        
            % Handle input data such as "tall" requiring a special adapter
            adapter = classreg.learning.internal.makeClassificationModelAdapter(this,X,varargin{:});
            if ~isempty(adapter)
                l = loss(adapter,X,varargin{:});
                return
            end

            [Y,varargin] = classreg.learning.internal.inferResponse(this.ResponseName,X,varargin{:});

            % Decode arguments
            [obsInRows,~,varargin] = classreg.learning.internal.orientation(varargin{:});
            if obsInRows
                N = size(X,1);
            else
                N = size(X,2);
            end
            args = {                  'lossfun'  'weights' 'predictionformissingvalue'};
            defs = {@classreg.learning.loss.mse  ones(N,1)                    'median'};
            [funloss,W,predMissing,~,extraArgs] = ...
                internal.stats.parseArgs(args,defs,varargin{:});
            if obsInRows
                extraArgs = [extraArgs {'ObservationsIn' 'rows'}];
            else
                extraArgs = [extraArgs {'ObservationsIn' 'columns'}];
            end

            % Validate predMissing
            predMissing = classreg.learning.internal.validatePredictionForMissing(predMissing,mfilename,'loss');
            if strcmp(predMissing,'omitted')
                cleanNaNsInX = true;
            else
                cleanNaNsInX = false;
                extraArgs = [extraArgs {'predictionformissingvalue' predMissing}];
            end

            % Prepare data
            [X,Y,W] = prepareDataForLoss(this,X,Y,W,this.VariableRange,false,obsInRows,cleanNaNsInX);
            internal.stats.checkSupportedNumeric('Weights',W,true,false,false,true);

            % Loss function
            if strncmpi(funloss,'epsiloninsensitive',length(funloss))
                if isempty(this.Epsilon) % this is empty for models other than SVM
                    error(message('stats:RegressionLinear:loss:UseEpsilonInsensitiveForSVM'));
                end
                funloss = @(Y,Yfit,W) classreg.learning.loss.epsiloninsensitive(...
                    Y,Yfit,W,this.Epsilon);
            end
            funloss = classreg.learning.internal.lossCheck(funloss,'regression');

            % Get predictions
            Yfit = predict(this,X,extraArgs{:});

            % Check
            classreg.learning.internal.regrCheck(Y,Yfit(:,1),W);

            % Get loss
            R = size(Yfit,2);
            l = NaN(1,R,"like",Yfit);
            for r=1:R
                l(r) = funloss(Y,Yfit(:,r),W);
            end
        end

        function newobj = update(obj,varargin)
        %UPDATE Update a RegressionLinear model for code generation
        %   by using new parameters
        %
        %   UPDATEDMDL = UPDATE(MDL,PARAMS) returns an updated version of MDL
        %   that contains the new parameters in PARAMS.
        %
        %   Use the output of validatedUpdateInputs (<a href="matlab:doc classreg.learning.coder.config.svm.classificationsvmcoderconfigurer/validatedUpdateInputs">validatedUpdateInputs</a>) as the input
        %   PARAMS, a structure with a field for each parameter to
        %   update in the RegressionLinear model.
        %
        %   The update parameters in PARAMS include Beta and Bias.
        %
        %   See also learnerCoderConfigurer, validatedUpdateInputs,
        %      	classreg.learning.coder.config.linear.RegressionLinearCoderConfigurer.


            % list of parameters that can be updated
            updateableParams = {'Beta','Bias'};

            upperbound = 2*numel(updateableParams)+1;
            narginchk(2,upperbound);

            try
                %validate inputs and return a parameter struct with new values
                paramStr = classreg.learning.coderutils.linear.validateLinearParamStruct(obj,false,updateableParams,varargin{:});

                % get struct representation of Impl object
                updatedModelStr = toStruct(obj);

                if isfield(updatedModelStr,'DefaultResponse') && ~isempty(updatedModelStr.DefaultResponse)
                    warning(message('stats:coder:config:config:DefaultResponseRemoved'));
                    updatedModelStr.DefaultResponse.Median = NaN;
                    updatedModelStr.DefaultResponse.Mean   = NaN;
                end

                updatedModelStr.Impl.Beta = paramStr.Beta;
                updatedModelStr.Impl.Bias = paramStr.Bias;

                newobj = RegressionLinear.fromStruct(updatedModelStr);
            catch ME
                throw(ME);
            end
        end

        function incrmdl = incrementalLearner(this,varargin)
        %INCREMENTALLEARNER Convert regression linear model to incremental learner.
        %   INCREMENTALMODEL=INCREMENTALLEARNER(MODEL) returns an
        %   incremental regression linear model INCREMENTALMODEL initialized
        %   using the parameters provided in MODEL.
        %
        %   INCREMENTALLEARNER uses these properties of MODEL to initialize
        %   INCREMENTALMODEL:
        %       'BatchSize'               - If MODEL.ModelParameters.Solver is 'sgd' or 'asgd',
        %                                   MODEL.BatchSize value is preserved.
        %                                   MODEL.BatchSize is ignored
        %                                   for scale-invariant solver.
        %       'Beta'
        %       'Bias'
        %       'Epsilon'                 - If MODEL.Learner is 'svm',
        %                                   MODEL.Epsilon is preserved.
        %
        %       'FitBias'
        %       'Lambda'                  - If MODEL.Regularization is 'ridge (L2)':
        %                                       - If MODEL.Lambda is scalar, value is
        %                                         preserved.
        %                                       - If MODEL.Lambda is not scalar,
        %                                         use 'selectModels' prior to conversion
        %                                         in order to select a model with
        %                                         desired 'Lambda' value.
        %                                 - If MODEL.Regularization is 'lasso (L1)',
        %                                   MODEL.Lambda is ignored and 'Solver'
        %                                   is set to default 'scale-invariant' solver.
        %       'Learner'
        %       'LearnRate'               - If MODEL.ModelParameters.Solver is 'sgd' or 'asgd',
        %                                   MODEL.LearnRate value is preserved.
        %                                   Otherwise, default 'LearnRate' for default solver
        %                                   'scale-invariant' is used.
        %       'Solver'                  - If MODEL.ModelParameters.Solver is 'sgd' or 'asgd',
        %                                   MODEL.ModelParameters.Solver value is preserved.
        %                                   Otherwise, default solver 'scale-invariant'
        %                                   is used.
        %
        %   INCREMENTALMODEL=INCREMENTALLEARNER(MODEL,'PARAM1',val1,'PARAM2',val2,...)
        %   specifies optional name/value arguments:
        %       '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.
        %                                   If MODEL.ModelParameters.Solver is 'sgd' or
        %                                   'asgd' and MODEL.Regularization is
        %                                   'ridge (L2)', 'Lambda' is not supported.
        %                                   Otherwise, default value is 1e-5.
        %                                   'Lambda' is not supported for scale-invariant solver.
        %       'LearnRate'               - Learning rate for the stochastic
        %                                   solvers, specified either as 'auto' or as a positive
        %                                   scalar. If MODEL.ModelParameters.Solver is not 'sgd'
        %                                   and 'Solver' is specified
        %                                   as 'sgd', default is 'auto'.
        %                                   If MODEL.ModelParameters.Solver is not 'asgd'
        %                                   and 'Solver' is specified
        %                                   as '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. If MODEL.ModelParameters.Solver is
        %                                   not 'sgd' or 'asgd'
        %                                   and 'Solver' is specified
        %                                   as 'sgd' or 'asgd', 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 regression:
        %                                       - 'epsiloninsensitive'
        %                                       - 'mse'
        %                                   'epsiloninsensitive' can be selected
        %                                   only when MODEL.Learner is 'svm'.
        %                                   If you pass a function handle FUN, it
        %                                   is called as shown below:
        %                                       FUN(Y,Yfit)
        %                                   where Y and Yfit are numeric vectors of
        %                                   length N. Y is observed response, Yfit is
        %                                   predicted response.
        %                                   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 postfixed
        %                                   with an underscore and an integer
        %                                   identifying its relative position in
        %                                   'Metrics' property.
        %                                   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 is 'mse' if 'Learner' is
        %                                   'leastsquares'. Otherwise, default is
        %                                   'epsiloninsensitive'.
        %       '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.
        %       'Standardize'             - Logical scalar. If true, standardize training data
        %                                   by centering and dividing columns by
        %                                   their standard deviations.
        %                                   Default is false.
        %       '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.
        %                                   If MODEL.ModelParameters.Solver is 'sgd' or 'asgd',
        %                                   default is MODEL.ModelParameters.Solver.
        %                                   Otherwise, default is 'scale-invariant'.
        %
        %   Example: Initialize an incremental linear model using a traditionally trained model
        %   and continue incremental training.
        %
        %       ttModel = fitrlinear(Xoffline,Yoffline);
        %       mdl = incrementalLearner(ttModel);
        %       mdl = updateMetricsAndFit(mdl,Xnew,Ynew,'Weights',Wnew);
        %
        %   See also incrementalRegressionLinear.
            [args,additionalargs] = incremental.utils.offlineLinearParameters(this,false,varargin{:});
            [args,additionalargs] = incremental.utils.offlineRegressionParameters(this,args,additionalargs);
            mergedargs = incremental.utils.mergeOfflineParameters(args,additionalargs);
            incrmdl = incrementalRegressionLinear(mergedargs);
        end
    end
    methods(Hidden)
        function s = toStruct(this)
            % 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.regrToStruct(this);

            % Support only scalar lambda
            if ~isscalar(this.Lambda)
                error(message('stats:ClassificationLinear:toStruct:NonScalarLambda'));
            end

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

            % learner char vector
            s.Learner = this.Learner;

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

            % impl
            s.Impl = toStruct(this.Impl);

            s.DataSummary.NumExpandedPredictors = numel(getExpandedPredictorNames(this));
            s.DataSummary.NumCategories = cellfun(@numel,this.VariableRange);
            
            % if model has been trained with tall data, make necessary
            % modifications in compactStruct 
            s = classreg.learning.coderutils.linear.handleModelTrainedWithTall(this,s);
        end
    end

    methods(Static,Hidden)
        function temp = template(varargin)
            classreg.learning.FitTemplate.catchType(varargin{:});
            temp = classreg.learning.FitTemplate.make('Linear',...
                'type','regression',varargin{:});
        end

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

        function [obj,fitInfo] = fitRegressionLinear(...
                X,Y,W,modelParams,dataSummary,responseTransform)
            defaultResponse.Median = classreg.learning.internal.wnanmedian(Y,W);
            defaultResponse.Mean   = classreg.learning.internal.wnanmean(Y,W);

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

            obj = RegressionLinear(dataSummary,responseTransform,defaultResponse);

            modelParams = fillIfNeeded(modelParams,X,Y,W,dataSummary,[]);

            if isa(X, "gpuArray") || isa(Y, "gpuArray") || isa(W, "gpuArray")
                iCheckGpuArrayLimitations(modelParams.Solver);
            end

            lossfun = modelParams.LossFunction;

            switch lower(lossfun)
                case 'epsiloninsensitive'
                    obj.Learner = 'svm';
                case 'mse'
                    obj.Learner = 'leastsquares';
            end

            lambda = modelParams.Lambda;
            if strcmp(lambda,'auto')
                lambda = 1/numel(Y);
            end

            % Expand categorical predictors if necessary
            if any(obj.CategoricalPredictors)
                X = classreg.learning.internal.expandCategorical(...
                    X,obj.CategoricalPredictors,obj.OrdinalPredictors,...
                    obj.VariableRange,obj.ReferenceLevels,false);
                modelParams.ValidationX = classreg.learning.internal.expandCategorical(...
                    modelParams.ValidationX,obj.CategoricalPredictors,obj.OrdinalPredictors,...
                    obj.VariableRange,obj.ReferenceLevels,false);
            end

            obj.Impl = classreg.learning.impl.LinearImpl.make(0,...
                modelParams.InitialBeta,modelParams.InitialBias,...
                X,Y,W,lossfun,...
                strcmp(modelParams.Regularization,'ridge'),...
                lambda,...
                modelParams.PassLimit,...
                modelParams.BatchLimit,...
                modelParams.NumCheckConvergence,...
                modelParams.BatchIndex,...
                modelParams.BatchSize,...
                modelParams.Solver,...
                modelParams.BetaTolerance,...
                modelParams.GradientTolerance,...
                modelParams.DeltaGradientTolerance,...
                modelParams.LearnRate,...
                modelParams.OptimizeLearnRate,...
                modelParams.ValidationX,modelParams.ValidationY,modelParams.ValidationW,...
                modelParams.IterationLimit,...
                modelParams.TruncationPeriod,...
                modelParams.FitBias,...
                modelParams.PostFitBias,...
                modelParams.Epsilon,...
                modelParams.HessianHistorySize,...
                modelParams.LineSearch,...
                0,... % consensus strength
                modelParams.Stream,...
                modelParams.VerbosityLevel);

            modelParams = toStruct(modelParams);

            modelParams = rmfield(modelParams,'ValidationX');
            modelParams = rmfield(modelParams,'ValidationY');
            modelParams = rmfield(modelParams,'ValidationW');

            obj.ModelParams = modelParams;

            fitInfo                 = obj.Impl.FitInfo;
            fitInfo.Solver          = obj.Impl.Solver;
        end

        function obj = makebeta(beta,bias,modelParams,dataSummary,fitinfo,responseTransform,defaultResponse)
            % Makes a RegressionLinear object without fitting by
            % feeding model parameters (beta and bias) and input model
            % parameters (modelParams)

            if isempty(responseTransform)
                responseTransform = @classreg.learning.transform.identity;
            end

            obj = RegressionLinear(dataSummary,responseTransform,defaultResponse);

            switch lower(modelParams.LossFunction)
                case 'epsiloninsensitive'
                    obj.Learner = 'svm';
                case 'mse'
                    obj.Learner = 'leastsquares';
            end

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

            % Convert modelParams to a struct and remove Validation fields
            % to be consistent with fitRegressionLinear
            modelParams = toStruct(modelParams);
            modelParams = rmfield(modelParams,'ValidationX');
            modelParams = rmfield(modelParams,'ValidationY');
            modelParams = rmfield(modelParams,'ValidationW');
            obj.ModelParams = modelParams;
        end

        function [X,Y,W,dataSummary] = removeNaNsFromX(X,Y,W,dataSummary)
        % Remove observations (columns) that have at least one missing value

            nanX = any(isnan(X),1);
            if any(nanX)
                X(:,nanX)   = [];
                Y(nanX)     = [];
                W(nanX)     = [];
                rowsused = dataSummary.RowsUsed;
                if isempty(rowsused)
                    rowsused = ~nanX;
                else
                    rowsused(rowsused) = ~nanX;
                end
                dataSummary.RowsUsed = rowsused;
            end
            if all(nanX)
                error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:NoGoodXData'));
            end
            if any(nanX)
                warning(message('stats:classreg:learning:Linear:prepareDataCR:XwithNaNs'));
            end

            if any(nanX)
                % Renormalize weights
                W = W/sum(W);
            end
        end


        function [X,Y,W,dataSummary,responseTransform] = prepareData(X,Y,varargin)

            [X,Y,vrange,wastable,varargin] = iPreprocessPredictorsAndResponse(X,Y,varargin{:});

            % Process input args
            args = {'weights', 'responsetransform'};
            defs = {       [],                  []};
            [W,transformer,~,crArgs] = ...
                internal.stats.parseArgs(args,defs,varargin{:});

            % Process Weights before arguments are passed to
            % classreg.learning.Linear.prepareDataCR to ensure it has the
            % desired precision (single or double) and storage type (for
            % example, gpuArray). It should be dense and have the same
            % storage type as the dominant type of X and Y, and the same
            % precision as X. We have already ensured X and Y have the same
            % storage type above, so X can be used as a prototype. 
            prototypeW = full(X([]));
            if isempty(W)
                W = ones(length(Y), 1, "like", prototypeW);
            else
                % If weights is supplied ensure that the type of the
                % weights input does not influence how the model is fitted.
                W = cast(W, "like", prototypeW);
            end

            % Pre-process
            [X,Y,W,dataSummary] = classreg.learning.Linear.prepareDataCR(...
                                  X,Y,crArgs{:},'Weights',W,'VariableRange',vrange,'TableInput',wastable);

            % Check Y type
            if ~isfloat(Y) || ~isvector(Y)
                error(message('stats:classreg:learning:regr:FullRegressionModel:prepareData:BadYType'));
            end
            internal.stats.checkSupportedNumeric('Y',Y,true, false, false, true);
            Y = Y(:);

            % Remove NaN's in Y
            if any(isnan(Y))
                warning(message('stats:RegressionLinear:prepareData:YwithMissingValues'));
            end
            [X,Y,W,dataSummary] = ...
                classreg.learning.regr.FullRegressionModel.removeNaNs(...
                X,Y,W,dataSummary);

            % Renormalize weights
            W = W/sum(W);

            % Make output response transformation
            responseTransform = ...
                classreg.learning.regr.FullRegressionModel.processResponseTransform(transformer);
        end

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

            s.ResponseTransform = s.ResponseTransformFull;

            % record modelParams for reconstruction
            modelParams = s.ModelParams;

            s = classreg.learning.coderutils.structToRegr(s);

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

            % if model has been trained with tall data, make necessary
            % modifications in compactStruct             
            s = classreg.learning.coderutils.linear.handleModelTrainedWithTall(s);

            % Make an object
            obj = RegressionLinear(s.DataSummary,s.ResponseTransform,s.DefaultResponse);

            % Learner
            obj.Learner = s.Learner;

            % Model params
            obj.ModelParams = classreg.learning.coderutils.coderStructToLinearParams(modelParams);

            % restore Impl
            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.regr.RegressionLinear';
        end
    end

    methods(Static,Hidden)        
        function this = loadobj(obj)
            if isempty(obj.AllowedPredictArgs)
                obj.AllowedPredictArgs = struct('Name','PredictionForMissingValue','Value','Median');
            end
            this = obj;
        end
    end
end

function iCheckGpuArrayLimitations(solver)
% Error if necessary for functionality not supported for GPU data.

% Only BFGS, LBFGS and SpaRSA solvers are supported
supportedSolvers = ["bfgs", "lbfgs", "sparsa"];
isSolverSupported = ismember(solver, supportedSolvers);
if ~all(isSolverSupported)
    unsupportedSolvers = solver(~isSolverSupported);
    throwAsCaller(MException(message("stats:RegressionLinear:fitRegressionLinear:GpuArraySolverNotSupported", unsupportedSolvers{1})));
end
end


function [X,Y,vrange,wastable,varargin] = iPreprocessPredictorsAndResponse(X,Y,varargin)
[X,Y,vrange,wastable,varargin] = classreg.learning.internal.table2FitMatrix(X,Y,varargin{:});

% We need to ensure that:
% 1. Y has the same precision (for example, single) as X. This is because
% the model is fit in the precision of X, so any pre-processing should also
% be done in that precision.
% 2. X and Y have the same dominant storage type (excluding sparsity).
% 3. Y is dense.

% We know X and Y have numeric or logical underlying type,
% because they have been returned out of table2FitMatrix, so we
% can use isobject as a proxy for say, isa(x,'gpuArray').
if isobject(X) && ~isobject(Y)
    Y = cast(Y, "like", full(X([])));
else
    if ~isobject(X) && isobject(Y)
        X = cast(X, "like", cast(Y([]), underlyingType(X)));
    end
    Y = cast(Y, underlyingType(X));
end
end

% LocalWords:  predictionformissingvalue UPDATEDMDL classificationsvmcoderconfigurer Configurer tt
% LocalWords:  Xoffline Yoffline Xnew Ynew Wnew Xwith Ywith
