classdef BayesianOptimization
    %BayesianOptimization    The results of a Bayesian Optimization.
    %
    %   BayesianOptimization Properties:
    %
    %     Problem definition properties:
    %       ObjectiveFcn   	- The ObjectiveFcn argument that was passed to
    %                       bayesopt.
    %       VariableDescriptions - The VariableDescriptions argument that
    %                       was passed to bayesopt.
    %       Options         - A read-only struct containing options that
    %                       were used to perform the optimization. It has
    %                       the following fields:
    %
    %                             AcquisitionFunctionName
    %                             IsObjectiveDeterministic
    %                             ExplorationRatio
    %
    %                             NumSeedPoints
    %                             MaxObjectiveEvaluations
    %                             MaxTime
    %
    %                             XConstraintFcn
    %                             ConditionalVariableFcn
    %                             NumCoupledConstraints
    %                             CoupledConstraintTolerances
    %                             AreCoupledConstraintsDeterministic
    %
    %                             Verbose
    %                             OutputFcn
    %                             SaveVariableName
    %                             SaveFileName
    %                             PlotFcn
    %
    %                             InitialX
    %                             InitialObjective
    %                             InitialConstraintViolations
    %                             InitialErrorValues
    %                             InitialObjectiveEvaluationTimes
    %                             InitialIterationTimes
    %                             InitialUserData
    %
    %     Solution properties:
    %    	MinObjective	- The minimum observed feasible Objective
    %                       function value, with feasibility defined by the
    %                       final constraint models (including the Error
    %                       constraint model).
    %     	XAtMinObjective - A 1-by-D table representing the value of X at
    %                       the minimum observed Objective function
    %                       value with feasibility defined by the final
    %                       constraint models (including the Error
    %                       constraint model).
    %    	MinEstimatedObjective - The minimum estimated feasible
    %                       Objective function value, according to the
    %                       model of Objective, with feasibility defined by
    %                       the final constraint models (including the
    %                       Error constraint model).
    %    	XAtMinEstimatedObjective - A 1-by-D table representing the
    %                       value of X at the minimum estimated feasible
    %                       Objective function value, according to the
    %                       model of Objective, with feasibility defined by
    %                       the final constraint models (including the
    %                       Error constraint model).
    %       NumObjectiveEvaluations - The number of Objective function
    %                       evaluations performed.
    %       TotalElapsedTime - The total elapsed time of the optimization.
    %       NextPoint       - A 1-by-D table representing the next point to
    %                       be evaluated were the optimization to continue.
    %
    %     Trace properties:
    %    	XTrace          - A T-by-D table of the variable values on
    %                       which ObjectiveFcn was evaluated, where T =
    %                       NumObjectiveEvaluations and D is the number of
    %                       variables.
    %     	ObjectiveTrace  - A T-by-1 array of Objective values resulting
    %                       from the T evaluations of ObjectiveFcn on
    %                       XTrace.
    %     	ConstraintsTrace - A T-by-K array of constraint values for K
    %                       coupled constraints resulting from the T
    %                       evaluations of ObjectiveFcn on XTrace.
    %     	UserDataTrace   - A T-by-1 cell array of the UserData outputs
    %                       of the Objective function evaluations.
    %     	ObjectiveEvaluationTimeTrace - A T-by-1 array of the runtime of
    %                       each of	the T evaluations of ObjectiveFcn on
    %                       XTrace.
    %     	IterationTimeTrace - A T-by-1 array of the total time taken for
    %                       each iteration, including both function
    %                       evaluation time and overhead.
    %    	ErrorTrace      - A T-by-1 vector of error values in {-1,1}
    %                       indicating on which points in XTrace the
    %                       objective function returned a nonfinite value.
    %     	FeasibilityTrace - A T-by-1 logical vector indicating which
    %                       points in XTrace are feasible according to the
    %                       final constraint models (including the Error
    %                       constraint model). The tolerances defined
    %                       in the 'CoupledConstraintTolerances' argument
    %                       are used to decide feasibility for each
    %                       constraint.
    %     	FeasibilityProbabilityTrace - A T-by-1 vector of the
    %                       probabilities that each point in XTrace is
    %                       feasible according to the final constraint
    %                       models (including the Error constraint model).
    %     	IndexOfMinimumTrace - A T-by-1 array of integer indices
    %                       indicating which element of the trace had the
    %                       minimum feasible Objective up to that
    %                       iteration, with feasibility defined by the
    %                       Constraint models at that iteration (including
    %                       the Error constraint model).
    %     	ObjectiveMinimumTrace - A T-by-1 array of the minimum feasible
    %                       Objective value found up to that iteration,
    %                       with feasibility defined by the Constraint
    %                       models at that iteration (including the Error
    %                       constraint model).
    %     	EstimatedObjectiveMinimumTrace - A T-by-1 array of the
    %                       estimated minimum Objective value found up to
    %                       that iteration, with feasibility defined by the
    %                       Constraint models at that iteration (including
    %                       the Error constraint model).
    %
    %   BayesianOptimization methods:
    %       bestPoint       - Return the best point according to a
    %                       criterion.
    %       plot            - Create plots from a set of plot functions.
    %       predictObjective - Predict the Objective at a set of points.
    %       predictObjectiveEvaluationTime - Predict the Objective function
    %                       evaluation time at a set of points.
    %       predictConstraints - Predict Constraint values at a set of
    %                       points.
    %       predictError    - Predict the Error value at a set of points.
    %       resume          - Resume a Bayesian optimization with modified
    %                       settings and stopping criteria.
    %
    %   See also: BAYESOPT, OPTIMIZABLEVARIABLE

    %   Copyright 2016-2024 The MathWorks, Inc.

    %% User API
    properties(Dependent, SetAccess=protected)
        % Problem definition
        ObjectiveFcn;
        VariableDescriptions;
        Options;
        % Solution / State of optimization
        MinObjective;
        XAtMinObjective;
        MinEstimatedObjective;
        XAtMinEstimatedObjective;
        NumObjectiveEvaluations;
        TotalElapsedTime;
        NextPoint;
        % Traces
        XTrace;
        ObjectiveTrace;
        ConstraintsTrace;
        UserDataTrace;
        ObjectiveEvaluationTimeTrace;
        IterationTimeTrace;
        ErrorTrace;
        FeasibilityTrace;
        FeasibilityProbabilityTrace;
        IndexOfMinimumTrace;
        ObjectiveMinimumTrace;
        EstimatedObjectiveMinimumTrace;
    end

    methods
        [X, CriterionValue, Iteration] = bestPoint(this, varargin)
        plot(this, varargin)
        [ConstraintViolations, SDs] = predictConstraints(this, XTable)
        [Error, SD] = predictError(this, XTable)
        [Objective, SD] = predictObjective(this, XTable)
        ObjectiveEvaluationTime = predictObjectiveEvaluationTime(this, XTable)
        NewResults = resume(Results, varargin)
    end

    %% Hidden API
    methods(Hidden)
        % The constructor must be defined in the classdef file
        function this = BayesianOptimization(Options)
            this.PrivOptions = Options;
            this = initializeParallel(this);
            this = initializeTimers(this, false);
            this = intializePrintObject(this);
            this = run(this);
        end

        % Called by bayesoptim.BayesOptPrint:
        tf = acqFcnIsNonBayes(this)
        N = numCharsBeforeX(this)
        Line = commandLineXDisplay(this, XTable)
        X = transformPoints(this, XTable)

        % This is public so BayesoptParallel can call it
        [Objective, ConstraintViolations, UserData, ErrorConstraintViolation, ObjectiveEvaluationTime, ObjectiveNargout] = callObjFcn(this, X)

        % Output functions
        Stop = assignInBase(bo, State)
        Stop = saveToFile(bo, State)

        % Predefined plot functions
        Stop = plotAcquisitionFunction(bo, State)
        Stop = plotConstraintModels(bo, State)
        Stop = plotElapsedTime(bo, State)
        Stop = plotMinObjective(bo, State)
        Stop = plotObjective(bo, State)
        Stop = plotObjectiveEvaluationTime(bo, State)
        Stop = plotObjectiveEvaluationTimeModel(bo, State)
        Stop = plotObjectiveModel(bo, State)
    end

    %% Internal
    properties(Hidden, SetAccess=protected)
        % Model training data
        XTrain;
        FTrain;
        ObjectiveEvaluationTimeTrain;
        ConstraintTrain;
        ErrorTrain;
        QueuedX;
        % Parallel
        Parallel;       % A bayesoptim.BayesoptParallel object.
        FantasyModels;
        PendingObj;     % Imputed values for pending X.
        % Models
        ModelStrategy;  % keep different models (GP and RF)
        ObjectiveFcnModel;
        ObjectiveEvaluationTimeModel;
        ConstraintModels;
        ErrorModel;
        % Old model properties that existed before 2020a for backward
        % loading compatibility:
        ObjectiveFcnGP
        ObjectiveEvaluationTimeGP
        ConstraintGPs
        ErrorGP
        % Current iteration info
        IncumbentF;
        IncumbentX;
        XNext;
        PlotFcnStop;
        OutputFcnStop
        % timing
        IterationStartTime;
        ObjectiveEvaluationTime;
        PreviousTime;
        CurrentRunStartTime;
        TotalElapsedTimeTrace;
        % Properties used by specific AFs
        SampledGridIndices;                 % For use by grid search only.
        ASHARungNum;                       	% For use by ASHA only. A vector corresponding to XTrain.
        ASHAIsPromoted;                     % For use by ASHA only. A vector corresponding to XTrain.
        % Internal options and traces
        PrivOptions;
        PrivIterationTimeTrace;
        PrivIndexOfMinimumTrace;
        PrivFMinTrace;
        PrivFMinEstimatedTrace;
        PrivUserDataTrace;
        % Cached vector of canonical hyperparameter values, used for efficiency
        CanonicalVector;
    end

    methods     % Dependent property getters
        % "All functions that use dots in their names" must be defined in
        % the classdef file.
        function ObjectiveFcn = get.ObjectiveFcn(this)
            ObjectiveFcn = this.PrivOptions.ObjectiveFcn;
        end

        function VariableDescriptions = get.VariableDescriptions(this)
            VariableDescriptions = this.PrivOptions.VariableDescriptions;
        end

        function this = set.VariableDescriptions(this, VariableDescriptions)
            this.PrivOptions.VariableDescriptions = VariableDescriptions;
        end

        function s = get.Options(this)
            % Return all the visible public options properties in a struct.
            props = properties(this.PrivOptions);
            for i = 1:numel(props)
                s.(props{i}) = this.PrivOptions.(props{i});
            end
        end

        function XAtMinObjective = get.XAtMinObjective(this)
            XAtMinObjective = bestPoint(this, 'Criterion', 'min-observed');
        end

        function MinObjective = get.MinObjective(this)
            [~, MinObjective] = bestPoint(this, 'Criterion', 'min-observed');
        end

        function XAtMinEstimatedObjective = get.XAtMinEstimatedObjective(this)
            XAtMinEstimatedObjective = bestPoint(this,'Criterion', this.PrivOptions.MinEstimatedObjectiveBestPointCriterion);
        end

        function MinEstimatedObjective = get.MinEstimatedObjective(this)
            X = this.XAtMinEstimatedObjective;
            if isempty(X)
                MinEstimatedObjective = NaN;
            else
                MinEstimatedObjective = predictObjective(this, X);
            end
        end

        function NumObjectiveEvaluations = get.NumObjectiveEvaluations(this)
            NumObjectiveEvaluations = size(this.XTrain,1);
        end

        function TotalElapsedTime = get.TotalElapsedTime(this)
            if ~isempty(this.TotalElapsedTimeTrace)
                TotalElapsedTime = this.TotalElapsedTimeTrace(end);
            else
                TotalElapsedTime = toc(this.CurrentRunStartTime);
            end
        end

        function NextPoint = get.NextPoint(this)
            if isempty(this.XNext)
                NextPoint = table;
            else
                NextPoint = untransformPoints(this, this.XNext, true);
                NextPoint = applyConditionalVariableFcn(this, NextPoint);
                NextPoint = canonicalizePoints(this, NextPoint);
            end
        end

        function XTrace = get.XTrace(this)
            XTrace = conditionalizeX(this, this.XTrain);
        end

        function ObjectiveTrace = get.ObjectiveTrace(this)
            ObjectiveTrace = this.FTrain(:);
        end

        function ObjectiveEvaluationTimeTrace = get.ObjectiveEvaluationTimeTrace(this)
            ObjectiveEvaluationTimeTrace = this.ObjectiveEvaluationTimeTrain(:);
        end

        function ConstraintsTrace = get.ConstraintsTrace(this)
            ConstraintsTrace = this.ConstraintTrain;
        end

        function UserDataTrace = get.UserDataTrace(this)
            UserDataTrace = this.PrivUserDataTrace;
        end

        function ErrorTrace = get.ErrorTrace(this)
            ErrorTrace = this.ErrorTrain;
        end

        function FeasibilityTrace = get.FeasibilityTrace(this)
            FeasibilityTrace = allConstraintsSatisfied(this, this.XTrain, ...
                this.ConstraintModels, this.ErrorModel);
        end

        function FeasibilityProbabilityTrace = get.FeasibilityProbabilityTrace(this)
            FeasibilityProbabilityTrace(:,1) = ProbAllConstraintsSatisfied(this, this.XTrain);
        end

        function IndexOfMinimumTrace = get.IndexOfMinimumTrace(this)
            IndexOfMinimumTrace = this.PrivIndexOfMinimumTrace(:);
        end

        function ObjectiveMinimumTrace = get.ObjectiveMinimumTrace(this)
            ObjectiveMinimumTrace = this.PrivFMinTrace(:);
        end

        function EstimatedObjectiveMinimumTrace = get.EstimatedObjectiveMinimumTrace(this)
            EstimatedObjectiveMinimumTrace = this.PrivFMinEstimatedTrace(:);
        end

        function IterationTimeTrace = get.IterationTimeTrace(this)
            IterationTimeTrace = this.PrivIterationTimeTrace(:);
        end
    end

    methods(Access=protected)
        %% Main algorithm
        this = run(this)
        this = initializeParallel(this)
        this = checkAndSetMinWorkerUtilization(this)
        this = initializeTimers(this, isResuming)
        this = intializePrintObject(this)
        this = runSerial(this)
        Parallel = launchMultipleJobs(this, Xs)
        this = runParallel(this)
        this = updateTimingPlotsAndOutput(this, FinishedIterationNums)
        this = recordFEvalResults(this, X, Objective, ConstraintViolations, UserData, ErrorConstraintViolation, ...
            TotalJobTime, ObjectiveNargout)
        this = updateTraces(this, IterationNums)
        this = padQueuedX(this, NumWorkers)
        X = randomASHAFeasiblePoints(this, N)
        [Xs, this] = randomOrGridXFeasiblePoints(this, N)
        this = processInitializationData(this)
        this = fillTotalElapsedTimeTrace(this)
        XBest = initialXFeasiblePoints(this, N)
        this = chooseNextPoint(this)
        this = chooseNextPointParallel(this)
        ImputedObj = imputeObjective(this, PendingXTable, ParallelMethod)
        [tf, ReasonKey] = shouldChooseRandomPoint(this)
        [XBest, ChooseRandom, this] = chooseNextPointInternal(this)
        [XBest, this] = sampleGridWithoutReplacement(this)
        this = pruneGridIndices(this)
        X = XFromGridIndices(this, Indices)
        TF = exploitingTooMuch(this, X)
        tf = optimizationFinished(this, iteration)

        %% Function evaluation
        [Objective, ConstraintViolations, UserData, ErrorConstraintViolation, ObjectiveEvaluationTime] = callObjNormally(this, X)
        [Objective, ConstraintViolations, ErrorConstraintViolation, ObjectiveEvaluationTime] = finishObjEval(this, Objective, ConstraintViolations, ErrorConstraintViolation, StartTic)

        %% Modeling
        this = fitModels(this, useFitMethodNone)
        this = fitBayesOptModels(this, varargin)

        %% Proxy optimization
        [IncumbentF, IncumbentX] = findIncumbent(this, BOOptions)
        Objective = GPFMeanOnPoints(this, X)
        TF = isInputFeasible(this, X)
        InputFeasible = applyXConstraint(this, XTable)
        checkForOptimizableVariables(this)
        checkXConstraintFcnSatisfiability(this)
        TF = allConstraintsSatisfied(this, Xcanon, ConstraintModels, ErrorModel)

        %% Output and plotting
        % Plotting helper methods
        printProgress(this, iteration, NumFinishedIterations,isResuming)
        Line = verboseXNextDisplay(this)
        Line = overexploitDisplay(this, X)
        this = callPlotFcn(this, State)
        this = callOutputFcn(this, State)
        showStoppingReason(this)
        showBestPoints(this)
        showVerboseDocHelp(this)
        N = NumVars(this)
        [Xcanon, InputFeasible] = legalizePoints(this, X)
        XTrain = XTraceToXTrain(this, XTable)
        X = makeXTablePlottable(this, XTable)
        Prob = ProbAllConstraintsSatisfied(this, X)
        TF = satisfiesXConstraint(this, X)

        %% Choosing the best point (used by the bestPoint method)
        [BestXTable, MinObserved, Iteration] = minObservedPoint(this)
        [BestXTable, MinMean] = minMeanPoint(this)
        [BestXTable, MinUCI] = minUCIPoint(this, Alpha)
        [BestXTable, MinMean, Iteration] = minVisitedMeanPoint(this)
        [BestXTable, MinUCI, Iteration] = minVisitedUCIPoint(this, Alpha)
        UCI = GPF_UCI_OnPoints(this, X, Alpha)

        %% Misc
        [X, this] = gridXFeasiblePoint(this)
        [X, bo] = choosePointASHA(bo)
        X = randomXFeasiblePoints(this, N)
        X = randomXFeasiblePointsForEachCategoricalLevel(this, N,learnerIdx)
        XTable = conditionalizeX(this, X)
        NewXTable = applyConditionalVariableFcn(this, XTable)
        XTable = canonicalizePoints(this, XTable)
        XTable = untransformPoints(this, X, HonorInts)
        T = checkAndPrepareTableForPrediction(this, XTable, caller)
        tf = isPlottingObjFcnModel(this)
        tf = doingGridsearch(this)
        tf = gridFullySampled(this)
        TFs = isPointOutOfBounds(this, XTbl)
    end

    methods(Static)
        obj = loadobj(s)
    end
end
