classdef (Sealed) Surrogateopt < optim.options.SingleAlgorithm
%SURROGATEOPT Options for SURROGATEOPT function.
%
%   The OPTIM.OPTIONS.SURROGATEOPT class allows the user to create a set
%   of options for the SURROGATEOPT solver. For a list of options that can
%   be set, see the documentation for SURROGATEOPT.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT creates a set of options for
%   SURROGATEOPT with the options set to their default values.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT(PARAM, VAL, ...) creates a set of
%   options for SURROGATEOPT with the named parameters altered with the
%   specified values.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT(OLDOPTS, PARAM, VAL, ...) creates a
%   copy of OLDOPTS with the named parameters altered with the specified
%   values.
%
%   See also OPTIM.OPTIONS.SINGLEALGORITHM, OPTIM.OPTIONS.SOLVEROPTIONS

%   Copyright 2018-2023 The MathWorks, Inc.
    
    properties (Dependent)
    
%DISPLAY Level of display 
        Display
        
%MAXFUNCTIONEVALUATIONS Maximum number of objective function evaluations
        MaxFunctionEvaluations

%MAXTIME Total time (in seconds) allowed for optimization
        MaxTime 

%OBJECTIVELIMIT Minimum objective function value desired
        ObjectiveLimit
        
%OUTPUTFCN Functions that get iterative data and can change options
        OutputFcn
        
%PLOTFCN Plots various measures of progress while the algorithm executes
        PlotFcn
        
%USEPARALLEL Compute the objective function of particles in parallel
        UseParallel

%INITIALPOINTS Specify X and/or Fval
        InitialPoints

%MinSurrogatePoints Specify the number of points to use for initial surrogate (on
%top of InitialPoints.X
        MinSurrogatePoints

%MINSAMPLEDISTANCE Adaptive samples with minimum distance (from existing
%points) less than this tolerance will not be considered.
        MinSampleDistance

%CheckpointFile File name where checkpoint data is saved.
        CheckpointFile

%CONSTRAINTTOLERANCE Tolerance on the constraints
        ConstraintTolerance
        
%BatchUpdateInterval controls how often surrogate is updated (after
%first fit) and also controls the size of vectorized input when
%UseVectorized is true
        BatchUpdateInterval

%UseVectorized Boolean to indicate if objective function can be called in
%vectorized fashion. The number of points is the value of the option
%BatchUpdateInterval
        UseVectorized

    end        

    % Hidden properties
    properties (Hidden, Dependent)

%ObjectiveImprovementThreshold Tolerance used for restart of the search
        ObjectiveImprovementThreshold        
        
%MAXSTALLITERATIONS
        MaxStallIterations

%SURROGATE
        Surrogate

%INTEGERTOLERANCE Tolerance on the integer constraints
        IntegerTolerance
                
%LINEARCONSTRAINTTOLERANCE Tolerance on the linear constraints
        LinearConstraintTolerance
        
% RandomTrialGeneratorOptions        
        RandomTrialGeneratorOptions

% MINLPTrialGeneratorOptions
        MINLPTrialGeneratorOptions
        
% DistanceFcn used to calculate distance between two design points
% This option is used only when creating new trials (random or adaptive)
        DistanceFcn

% ScalingFcn scale function (and constraint) values in a surrogate fit.
% The results will be different in general when scaling is applied for
% nonlinear functions with many minimum. However, in cases where f(x) have
% unique minimum, we can test that the results loose some precision.
%
% The number of function evaluation can also be different when
% we use scaling.
        ScalingFcn

% UseRandomPointsAfterReset keep all the random points in the surrogate
% after a reset.
% Surrogateopt by default uses all the random points generated
% so far (even after a surrogate reset). The RBF fit can be very expensive
% if the problem is large. 
% The performance will improve if we drop every points after a surrogate
% reset. It is not not clear which choice is better for an arbitrary
% function because it depends on the quality of random points.
        UseRandomPointsAfterReset

% CheckpointInterval checkpoint after every 'CheckpointInterval' seconds.
% Always checkpoint after the first and the last function evaluation. 
% This option is relevant only of CheckpointFile is provided. Valid values
% are in the range [0 Inf]. A value of 0 will checkpoint after every
% function evaluation (slowest) and Inf will checkpoint after the first and
% the last function evaluation (fastest).
        CheckpointInterval
    end
    
    properties (Hidden, Dependent)
        Verbosity

        % AdaptiveSampler (v5: merged into RandomTrialGeneratorOptions)
        AdaptiveSampler
        
    end
    
    properties (Hidden, Access = protected)
        
        %OPTIONSSTORE Contains the option values and meta-data for the class
        %
        OptionsStore = createOptionsStore;
    end
    
    properties (Hidden)
        
        %SOLVERNAME Name of the solver that the options are intended for
        SolverName = 'surrogateopt';
    end
    
    properties (SetAccess = private, GetAccess = private)
        
        % Version number for the optim.options.Surrogateopt class.
        % We do not change the Version property (base class).
        surrogateoptVersion = 5
    end
    
    properties(Hidden, GetAccess=public)
        
        % Globally visible metadata about this class.
        % This data is used to spec the options in this class for internal clients
        % such as: tab-complete, and the options validation
        % Properties
        PropertyMetaInfo = genPropInfo();    
    end
    
        % Load old objects
        methods (Static = true)
            function obj = loadobj(obj)
                if obj.surrogateoptVersion < 5
                    % Three new options and one hidden deprecated options
                    obj.OptionsStore.Defaults.CheckpointInterval = 10;
                    obj.OptionsStore.SetByUser.CheckpointInterval = false;
                    obj.OptionsStore.Options.CheckpointInterval = [];

                    obj.OptionsStore.Defaults.MINLPTrialGeneratorOptions = [];
                    obj.OptionsStore.SetByUser.MINLPTrialGeneratorOptions = false;
                    obj.OptionsStore.Options.MINLPTrialGeneratorOptions = [];
                    
                    obj.OptionsStore.Defaults.RandomTrialGeneratorOptions = [];
                    obj.OptionsStore.SetByUser.RandomTrialGeneratorOptions = false;
                    obj.OptionsStore.Options.RandomTrialGeneratorOptions = [];
                    
                    % Old (hidden) option AdaptiveSampler was allowed. It
                    % allowed only random sampler (V1), nothing else.
                    if isprop(obj, 'AdaptiveSampler') && ... 
                            ~isempty(obj.AdaptiveSampler) && ...
                            strcmp(obj.AdaptiveSampler, 'random')
                        obj.OptionsStore.SetByUser.RandomTrialGeneratorOptions = true;
                        obj.OptionsStore.Options.RandomTrialGeneratorOptions = ... 
                            struct('OrderedSamplers',{'random'});

                    obj.OptionsStore.SetByUser.MINLPTrialGeneratorOptions = true;
                    obj.OptionsStore.Options.MINLPTrialGeneratorOptions = ...
                        struct('SearchInterval', intmax);
                    end

                end

                if obj.surrogateoptVersion < 4
                    obj.OptionsStore.Defaults.Surrogate = [];
                    obj.OptionsStore.SetByUser.Surrogate = false;
                    obj.OptionsStore.Options.Surrogate = [];
                    
                    obj.OptionsStore.Defaults.UseVectorized = false;
                    obj.OptionsStore.SetByUser.UseVectorized = false;               
                    obj.OptionsStore.Options.UseVectorized = false;

                    % Change the default in the OptionsStore
                    obj.OptionsStore.Defaults.BatchUpdateInterval = 1;
                    obj.OptionsStore.SetByUser.BatchUpdateInterval = false;
                    obj.OptionsStore.Options.BatchUpdateInterval = 1;
                    
                    obj.OptionsStore.Defaults.IntegerTolerance = 1e-5;
                    obj.OptionsStore.SetByUser.IntegerTolerance = false;               
                    obj.OptionsStore.Options.IntegerTolerance = 1e-5;
                    
                    obj.OptionsStore.Defaults.LinearConstraintTolerance = 1e-6;
                    obj.OptionsStore.SetByUser.LinearConstraintTolerance = false;
                    obj.OptionsStore.Options.LinearConstraintTolerance = 1e-6;
                    
                end

            end
        end
    
% -------------------------------------------------------

    methods (Hidden)
        
        function obj = Surrogateopt(varargin)
%SURROGATEOPT Options for SURROGATEOPT
%
%   The OPTIM.OPTIONS.SURROGATEOPT class allows the user to create a set
%   of options for the SURROGATEOPT solver. For a list of options that can
%   be set, see the documentation for SURROGATEOPT.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT creates a set of options for
%   SURROGATEOPT with the options set to their default values.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT(PARAM, VAL, ...) creates a set of
%   options for SURROGATEOPT with the named parameters altered with the
%   specified values.
%
%   OPTS = OPTIM.OPTIONS.SURROGATEOPT(OLDOPTS, PARAM, VAL, ...) creates a
%   copy of OLDOPTS with the named parameters altered with the specified
%   values.
%
%   See also OPTIM.OPTIONS.SINGLEALGORITHM, OPTIM.OPTIONS.SOLVEROPTIONS
            
            % Call the superclass constructor
            obj = obj@optim.options.SingleAlgorithm(varargin{:});
            
        end
        
    end
    
    % Set/get methods
    methods

        function obj = set.RandomTrialGeneratorOptions(obj, value)
            if isstruct(value)
                obj = setPropertyNoChecks(obj, 'RandomTrialGeneratorOptions', value);
            end
        end
        function val = get.RandomTrialGeneratorOptions(obj)
            val = obj.OptionsStore.Options.RandomTrialGeneratorOptions;
        end
        
        function obj = set.MINLPTrialGeneratorOptions(obj, value)
            if isstruct(value)
                obj = setPropertyNoChecks(obj, 'MINLPTrialGeneratorOptions', value);
            end
        end
        function val = get.MINLPTrialGeneratorOptions(obj)
            val = obj.OptionsStore.Options.MINLPTrialGeneratorOptions;
        end

        function obj = set.UseRandomPointsAfterReset(obj, value)
             obj = setPropertyNoChecks(obj, 'UseRandomPointsAfterReset', value);
        end      
        function val = get.UseRandomPointsAfterReset(obj)
            val = obj.OptionsStore.Options.UseRandomPointsAfterReset;
        end

        function obj = set.DistanceFcn(obj, value)
             obj = setPropertyNoChecks(obj, 'DistanceFcn', value);
        end      
        function val = get.DistanceFcn(obj)
            val = obj.OptionsStore.Options.DistanceFcn;
        end

        function obj = set.ScalingFcn(obj, value)
             obj = setPropertyNoChecks(obj, 'ScalingFcn', value);
        end      
        function val = get.ScalingFcn(obj)
            val = obj.OptionsStore.Options.ScalingFcn;
        end

        function obj = set.Surrogate(obj, value)
            if isa(value, 'APIs.SurrogateFitter')
                obj = setPropertyNoChecks(obj, 'Surrogate', value);
            else
                obj = setProperty(obj, 'Surrogate', value);
            end
        end      
        function val = get.Surrogate(obj)
            val = obj.OptionsStore.Options.Surrogate;
        end

        function obj = set.BatchUpdateInterval(obj, value)
            obj = setProperty(obj, 'BatchUpdateInterval', value);
        end      
        function val = get.BatchUpdateInterval(obj)
            val = obj.OptionsStore.Options.BatchUpdateInterval;
        end
        
        function obj = set.UseVectorized(obj, value)
            obj = setProperty(obj, 'UseVectorized', value);
        end      
        function val = get.UseVectorized(obj)
            val = obj.OptionsStore.Options.UseVectorized;
        end
        
        function obj = set.ObjectiveImprovementThreshold(obj, value)
            obj = setProperty(obj, 'ObjectiveImprovementThreshold', value);
        end      
        function val = get.ObjectiveImprovementThreshold(obj)
            val = obj.OptionsStore.Options.ObjectiveImprovementThreshold;
        end        
        
        function obj = set.ConstraintTolerance(obj, value)
            obj = setProperty(obj, 'ConstraintTolerance', value);
        end        
        function val = get.ConstraintTolerance(obj)
            val = obj.OptionsStore.Options.ConstraintTolerance;
        end

        function obj = set.IntegerTolerance(obj, value)
            obj = setProperty(obj, 'IntegerTolerance', value);
        end        
        function val = get.IntegerTolerance(obj)
            val = obj.OptionsStore.Options.IntegerTolerance;
        end

        function obj = set.LinearConstraintTolerance(obj, value)
            obj = setProperty(obj, 'LinearConstraintTolerance', value);
        end        
        function val = get.LinearConstraintTolerance(obj)
            val = obj.OptionsStore.Options.LinearConstraintTolerance;
        end        
        
        function obj = set.MinSampleDistance(obj, value)
            obj = setProperty(obj, 'MinSampleDistance', value);
        end        
        function val = get.MinSampleDistance(obj)
            val = obj.OptionsStore.Options.MinSampleDistance;
        end
        
        function obj = set.Display(obj, value)
            if strcmpi(value, 'testing')
                % Set Display to the undocumented value, 'testing'.
                obj = setPropertyNoChecks(obj, 'Display', 'testing');
            else
                % Pass the possible values that the Display option can take via
                % the fourth input of setProperty.
                obj = setProperty(obj, 'Display', value);
            end          
            
            % Define verbosity here (Later we can use options structure)
            switch value
                case {'off','none'}
                    verbosity_ = 0;
                case 'final'
                    verbosity_ = 1;
                case 'iter'
                    verbosity_ = 2;
                case 'testing'
                    verbosity_ = 3;
                otherwise
                    verbosity_ = 1;
            end
            obj = setPropertyNoChecks(obj, 'Verbosity', verbosity_);
        end
        
        function val = get.Display(obj)
            val = obj.OptionsStore.Options.Display;
        end
        
        function obj = set.MaxFunctionEvaluations(obj, value)
            obj = setProperty(obj, 'MaxFunctionEvaluations', value);
        end         
        function val = get.MaxFunctionEvaluations(obj)
            val = obj.OptionsStore.Options.MaxFunctionEvaluations;
        end                
        
        function obj = set.InitialPoints(obj, value)
            obj = setProperty(obj, 'InitialPoints', value);
        end        
        function val = get.InitialPoints(obj)
            val = obj.OptionsStore.Options.InitialPoints;
        end
        
        function obj = set.MinSurrogatePoints(obj, value)
            obj = setProperty(obj, 'MinSurrogatePoints', value);
        end        
        function val = get.MinSurrogatePoints(obj)
            val = obj.OptionsStore.Options.MinSurrogatePoints;
        end        
        
        function obj = set.MaxStallIterations(obj, value)
            obj = setProperty(obj, 'MaxStallIterations', value);
        end           
        function val = get.MaxStallIterations(obj)
            val = obj.OptionsStore.Options.MaxStallIterations;
        end         
        
        function obj = set.MaxTime(obj, value)
            obj = setProperty(obj, 'MaxTime',  value);
        end
        function val = get.MaxTime(obj)
            val = obj.OptionsStore.Options.MaxTime;
        end
        
        function obj = set.CheckpointInterval(obj, value)
            obj = setProperty(obj, 'CheckpointInterval',  value);
        end
        function val = get.CheckpointInterval(obj)
            val = obj.OptionsStore.Options.CheckpointInterval;
        end

        function obj = set.ObjectiveLimit(obj, value)
            obj = setProperty(obj, 'ObjectiveLimit', value);
        end        
        function val = get.ObjectiveLimit(obj)
            val = obj.OptionsStore.Options.ObjectiveLimit;
        end
        
        function obj = set.OutputFcn(obj, value)
            obj = setProperty(obj, 'OutputFcn', value);
        end        
        function val = get.OutputFcn(obj)
            val = obj.OptionsStore.Options.OutputFcn;
        end        
        
        function obj = set.PlotFcn(obj, value)
            obj = setProperty(obj, 'PlotFcn', value);       
        end                 
        function val = get.PlotFcn(obj)
            val = obj.OptionsStore.Options.PlotFcn;
        end

        function obj = set.UseParallel(obj, value)
            obj = setProperty(obj, 'UseParallel', value);
        end
        function val = get.UseParallel(obj)
            val = obj.OptionsStore.Options.UseParallel;
        end        

        function obj = set.CheckpointFile(obj, value)
            obj = setProperty(obj, 'CheckpointFile', value);
        end
        function val = get.CheckpointFile(obj)
            val = obj.OptionsStore.Options.CheckpointFile;
        end        
        
    end

    methods
        % SET/GET methods for deprecated properties (undocumented
        % before)

        function obj = set.AdaptiveSampler(obj, value)
            % Only undocumented but supported value is 'random'
            if strcmp(value, 'random')
                % Update the new property that is replacing
                % 'AdaptiveSampler' property
                obj = setPropertyNoChecks(obj, 'RandomTrialGeneratorOptions', ...
                    struct('OrderedSamplers',{{'random'}}));

                % Update the old property too so the get method is okay
                obj = setPropertyNoChecks(obj, 'AdaptiveSampler', value);
            end
        end

        function val = get.AdaptiveSampler(obj)
            val = obj.OptionsStore.Options.AdaptiveSampler;
        end
        
    end
    
end

function OS = createOptionsStore
%CREATEOPTIONSSTORE Create the OptionsStore
%
%   OS = createOptionsStore creates the OptionsStore structure. This
%   structure contains the options and meta-data for option display, e.g.
%   data determining whether an option has been set by the user. This
%   function is only called when the class is first instantiated to create
%   the OptionsStore structure in its default state. Subsequent
%   instantiations of this class pick up the default OptionsStore from the
%   MCOS class definition.
%
%   Class authors must create a structure containing all the options in a
%   field of OS called Defaults. This structure must then be passed to the
%   optim.options.generateSingleAlgorithmOptionsStore function to create
%   the full OptionsStore. 

% Define the option defaults 


OS.Defaults.ObjectiveLimit = -Inf;
OS.Defaults.InitialPoints = [];
OS.Defaults.Display = 'final';
OS.Defaults.MinSampleDistance = 1e-6;
OS.Defaults.MinSurrogatePoints     = 'max(20,2*numberOfVariables)';
OS.Defaults.MaxFunctionEvaluations = 'max(200,50*numberOfVariables)';
OS.Defaults.MaxTime = Inf;
OS.Defaults.OutputFcn = [];
OS.Defaults.PlotFcn = @optimplotfvalconstr;
OS.Defaults.UseParallel = false;
OS.Defaults.CheckpointFile = [];
OS.Defaults.ConstraintTolerance = 1e-3;
OS.Defaults.BatchUpdateInterval = 1;
OS.Defaults.UseVectorized = false;

% Hidden
OS.Defaults.CheckpointInterval = 10;
OS.Defaults.ObjectiveImprovementThreshold = 1e-6;
OS.Defaults.MaxStallIterations = 'min(100,10*numberofvariables)';
OS.Defaults.IntegerTolerance = 1e-5;
OS.Defaults.LinearConstraintTolerance = 1e-6;

OS.Defaults.RandomTrialGeneratorOptions = [];
OS.Defaults.MINLPTrialGeneratorOptions = [];
OS.Defaults.Surrogate = [];
OS.Defaults.DistanceFcn = @globaloptim.internal.distance.pdist2;
OS.Defaults.ScalingFcn = 'none'; % 'none','logscale', 'globaloptim.bmo.surrogates.fscale.MedianFilter(1e3)'
OS.Defaults.UseRandomPointsAfterReset = true;
OS.Defaults.Verbosity = 1;

% Deprecated (undocumented) properties
OS.Defaults.AdaptiveSampler = 'globaloptim.bmo.optimizer.SurrogateAdaptiveTrial';

% Call the package function to generate the OptionsStore
OS = optim.options.generateSingleAlgorithmOptionsStore(OS);
end

function propInfo = genPropInfo()
% Helper function to generate property metadata for the options.

import optim.options.meta.Factory
import optim.options.meta.category
import optim.options.meta.label

propInfo.ObjectiveLimit = Factory.objectiveLimitType;
propInfo.InitialPoints = Factory.matrixStructType(label('InitPoints'),category('Algorithm'));
propInfo.Display = Factory.displayType({'none','off','iter','final'});
propInfo.MinSampleDistance = Factory.toleranceType(label('MinSampleDist'));
propInfo.MinSurrogatePoints = Factory.integerType([1 Inf],label('MinSurrogatePts'),category('Algorithm'));
propInfo.MaxFunctionEvaluations = Factory.maxFunEvalsType;
propInfo.MaxTime = Factory.maxTimeType;
propInfo.OutputFcn = Factory.outputFcnType;
propInfo.PlotFcn = Factory.plotFcnType({'surrogateoptplot', ...
                                        'optimplotfvalconstr', ... 
                                        'optimplotfval', ... 
                                        'optimplotconstrviolation', ... 
                                        'optimplotx'});
propInfo.UseParallel = Factory.logicalWithEnumType(label('UseParallel'),category('Algorithm'),{'always','never'});
propInfo.CheckpointFile = Factory.fileType('.mat',label('Checkpoint'),category('Algorithm'));
propInfo.ConstraintTolerance = Factory.constraintToleranceType;
propInfo.IntegerTolerance = Factory.constraintToleranceType;
propInfo.LinearConstraintTolerance = Factory.constraintToleranceType;

propInfo.BatchUpdateInterval = ...
    Factory.integerType([1 Inf],label('BatchUpdateInterval'),category('Algorithm'));
propInfo.UseVectorized = Factory.useVectorizedType;

% Hidden options - no need for labels, categories, etc. Reuse where possible.
propInfo.Surrogate = Factory.outputFcnType;
propInfo.RandomTrialGeneratorOptions = Factory.matrixStructType('RandomTrialGeneratorOptions',category('Algorithm'));
propInfo.MINLPTrialGeneratorOptions = Factory.matrixStructType('MINLPTrialGeneratorOptions',category('Algorithm'));
propInfo.AdaptiveSampler = Factory.enumType(...
    {'auto','random'},'Adaptive Sampler','');

propInfo.DistanceFcn = Factory.fcnType(label('DistanceFcn'),'');
propInfo.ScalingFcn = Factory.fcnType('ScalingFcn','');
propInfo.UseRandomPointsAfterReset = Factory.logicalType('','');
propInfo.CheckpointInterval = Factory.maxTimeType;

propInfo.ObjectiveImprovementThreshold = Factory.numericType('scalar',[0 1],[true true],'','');
propInfo.MaxStallIterations = Factory.resourceLimitType('');
propInfo.Verbosity = Factory.integerType([0 3],'','');
end
