classdef incrementalOneClassSVM < incremental.anomaly.AnomalyDetector
    % 

    %   Copyright 2023 The MathWorks, Inc.

    properties(GetAccess=public, SetAccess = protected, Dependent=true)
        KernelScale

        Lambda

        NumExpansionDimensions

        SolverOptions

        Solver

        FittedLoss         
    end    

    properties (Dependent, Access=protected)
        OriginalNumPredictors
    end    

    properties (GetAccess=public, SetAccess=protected, Dependent)
        Mu
        Sigma  
        EstimationPeriod        
    end

    properties (Access=protected, Constant)
        Mode(1,1) = 1 % 1 for one-class learning
    end     

    properties (Hidden)
        Impl(:,1) {mustBeA(Impl,'incremental.impl.KernelImpl')} = incremental.impl.KernelImpl.empty
    end    

    properties (Constant, Access = protected)
        CanSupportCategoricalNatively = false
        CanSupportTables = true
    end       

    methods
        function obj = incrementalOneClassSVM(varargin)
            %

            % catch Name-Value arguments early
            ocsvmParams = {'Beta','Bias','FitBias','MaxNumClasses','Weights','ClassNames','Prior','Cost', ...
                           'ScoreTransform','Learner','CategoricalPredictors'};
            ocsvmAdditionalParams = {'MaxNumClasses'};            

            varargin = incremental.anomaly.AnomalyDetector.catchUnsupportedConstructorParameters(ocsvmParams,ocsvmAdditionalParams,varargin{:});

            obj = obj@incremental.anomaly.AnomalyDetector(mfilename,varargin{:});

        end

        function [varargout] = fit(obj,X,varargin)
            varargin = incremental.utils.parseUnsupportedParameters('ObservationsIn', varargin{:});
            internal.stats.parseArgs({},{},varargin{:});
            [varargout{1:nargout}] = fit@incremental.anomaly.AnomalyDetector(obj,X,mfilename,varargin{:});
        end


        function estimationperiod = get.EstimationPeriod(obj)
            estimationperiod = obj.Impl.EstimationPeriod;
        end

        function mu = get.Mu(obj)
            mu = anomaly.utils.getMu(obj.Impl.Mu,[]);
        end

        function sigma = get.Sigma(obj)
            sigma = anomaly.utils.getSigma(obj.Impl.Sigma,[]);          
        end  

        function ks = get.KernelScale(obj)
            ks = obj.Impl.KernelScale;
        end

        function lambda = get.Lambda(obj)
            lambda = obj.Impl.Lambda;
        end

        function ned = get.NumExpansionDimensions(obj)
            ned = obj.Impl.NumExpansionDimensions;
        end  

        function learner = get.Solver(this)
            learner = this.Impl.SolverImpl.Solver;
        end

        function solveroptions = get.SolverOptions(this)
            solveroptions = this.Impl.SolverImpl.SolverOptions;
        end

        function d = get.OriginalNumPredictors(this)
            d = this.Impl.OriginalNumPredictors;
        end

        function lossfcn = get.FittedLoss(this)
            lossfcn = this.Impl.SolverImpl.LossFunction;
        end          

        function [isAnomaly,scores] = isanomaly(obj,X,varargin)
            varargin = incremental.utils.parseUnsupportedParameters('ObservationsIn', varargin{:});
            [isAnomaly,scores] = obj.isanomalyHelper(X,'incrementalOneClassSVM',varargin{:});
        end    
    
    end

    methods (Access = protected)
        function [obj,X,removedIndices,nobs,iscat] = prepareForFit(obj,X,obsInRows,functionname, varargin)
            %

            % prepare data for training and update internal states
            % iscat will be false(1,D) 
            [obj,X,removedIndices,nobs,iscat] = prepareData(obj,X,obsInRows,functionname, varargin{:});
            obj.Impl = allocateEstimationBuffers(obj.Impl,obj.NumPredictors,obj.PredictorClass);
            [obj.Impl,X,~,~,removedIndices] = prepareDataAndUpdateEstimationState(obj.Impl,X,[],ones(nobs,1,'like',X),obsInRows,nobs,removedIndices,varargin{:});
            obj = synchronizePredictorInformation(obj,X,obsInRows);
            nobs = size(X,1);
        end           

        function [obj,remainingArgs] = makeAnomalyDetector(obj,parametersAreFromOffline,remainingArgs)

            [implParams,remainingArgs] = incremental.anomaly.impl.OneClassSVMImpl.parseMakeParameters(remainingArgs,obj.NumPredictors);
            obj.Impl = incremental.anomaly.impl.OneClassSVMImpl.make(obj.Mode,parametersAreFromOffline,implParams); 

            % extract OneClassSVM parameters
            [remainingArgs] = obj.extractOneClassSVMParameters(parametersAreFromOffline,remainingArgs);
            [trainingArgs,remainingArgs] = parseTrainingParameters(obj.Impl,remainingArgs);
            obj.Impl = fillTrainingParameters(obj.Impl,parametersAreFromOffline,trainingArgs);

            [stateParams,remainingArgs] = parseStateParameters(obj.Impl,remainingArgs);
            obj.Impl = fillStateParameters(obj.Impl,parametersAreFromOffline,stateParams);

            [obj.Impl,remainingArgs] = configureForEstimation(obj.Impl,parametersAreFromOffline,obj.NumPredictors,obj.PredictorClass,remainingArgs);

        end

        function cantrain = canTrain(obj)
            % can model train
            cantrain = canTrain(obj.Impl) && canTrain@incremental.anomaly.AnomalyDetector(obj);
        end

        function [obj] = fitWithoutValidation(obj,X,obsInRows,~,varargin)

            nobs = size(X,1);
            obj.Impl = fit(obj.Impl,X,ones(nobs,1),ones(nobs,1,'like',X),true,obsInRows,nobs);
        end 
    end

    methods (Access = protected)

        function statesdefined = statesDefined(obj)        
                 statesdefined = statesDefined(obj.Impl);
        end

        function obj = updateStateParameters(obj,numPredictors,predictorClass,~)
            obj.Impl = updateStateParameters(obj.Impl,numPredictors,predictorClass);
        end

        function [numPredictors,predictorClass] = predictorAttributes(obj)
            [numPredictors,predictorClass] = predictorAttributes(obj.Impl);
        end

        function scores = score(obj, X,obsInRows,~,varargin)
            internal.stats.parseArgs({},{},varargin{:});
            scores = -1*score(obj.Impl,X,obsInRows);
        end

         function obj = resetAnomalyDetector(obj,varargin)
            obj.Impl = reset(obj.Impl,varargin{:});
         end

        function propList = propsForDisplay(obj)
            % common properties
            propList = propsForDisplay@incremental.anomaly.AnomalyDetector(obj);
            propList.NumExpansionDimensions = obj.NumExpansionDimensions;
            if ~isnan(obj.Lambda)
                propList.Lambda = obj.Lambda;   
            end
            propList.KernelScale = obj.KernelScale;   
        end         
    end

    methods (Access = {?incremental.anomaly.AnomalyDetector,?matlab.unittest.TestCase})
         function [obj,X,removedIndices,N,iscategoric] = prepareData(obj,X,obsInRows, varargin)
            % prepareData Prepare data for creating incrementalOneClassSVM

            % prepareData from base class does not modify number of
            % observation and predictors.
            [obj,X,removedIndices,N,iscategoric] = prepareData@incremental.anomaly.AnomalyDetector(obj,X,obsInRows,varargin{:});

            % remove any observations that have nan values
            anynanrows = any(isnan(X),2); 
            if any(anynanrows)
                removedIndices = anynanrows;
                X(anynanrows,:) = [];
                N = size(X,1);
            end
         end 
    end    


    methods (Access=protected,Static=true)
        function [remainingArgs] = extractOneClassSVMParameters(parametersAreFromOffline,params)
            % extract parameters specific to OneClassSVM

            dfltStandardizeData =         'auto';

            p = inputParser;
            p.StructExpand = true;
            p.KeepUnmatched = true;
            p.PartialMatching = true;
            addParameter(p,'StandardizeData',dfltStandardizeData);
            params = incremental.utils.convertStructToCell(params);   
            parse(p,params{:});
            args = p.Results;
            remainingArgs = p.Unmatched; 

            if parametersAreFromOffline
                remainingArgs = rmfield(remainingArgs,{'ClassNames','Epsilon','ScoreTransform','Prior'});
                if isfield(remainingArgs,'Cost')
                    remainingArgs = rmfield(remainingArgs,{'Cost'});
                end
            end
            remainingArgs.Standardize = args.StandardizeData;
            if ~parametersAreFromOffline
                remainingArgs.FitBias = false;
            end
        end
      
    end    
end

% LocalWords:  iscat
