classdef( InferiorClasses = {?stats.internal.Sampling}) Gridding < stats.internal.PSpace
    %Gridding Object for gridding multiple parameter values.
    %
    %   S = Gridding(NPARAM)
    %   S = Gridding(["name1", "name2", ...])
    %   S = Gridding({valueset1, valueset2, ...})
    %   S = Gridding(["name1", "name2", ...], {valueset1, valueset2, ...})
    %
    %   See also sample, GriddingOptions

    %  Copyright 2019-2023 The MathWorks, Inc.

    properties(Dependent = true)
        ParameterValues
    end

    properties
        Notes
    end

    properties(GetAccess = public, SetAccess = protected)
        Spaces
    end

    properties(Access = protected)
        Options_
    end

    methods
        function this = Gridding(varargin)  % Constructor

            this.Options_ = stats.internal.GriddingOptions();
            this.Notes = [];
            this.Spaces = cell(1,0);

            % Parse inputs
            if nargin==0
                this.slParameterSpace_ = [];
            elseif nargin==1   % Number of parameters, Names, or values
                arg = varargin{1};
                if isempty(arg)
                    this.slParameterSpace_ = [];
                elseif isnumeric(arg)   % Number of parameters
                    if ~isscalar(arg)  ||  ~isreal(arg)  ||  ~(arg >= 1)  ||  ...
                            ~(mod(arg,1) == 0)
                        error(message('stats:sensitivity:general:errGridding_Construct_NumParameters'));
                    end

                    % Populate parameter names and distributions
                    this.slParameterSpace_ = createDefaultParamSpace(this,arg);

                elseif ischar(arg) || iscellstr(arg)  || isstring(arg) || ... % Names of parameters
                        (iscell(arg) && all(cellfun(@(x) isstring(x),arg)))
                    %Convert all names to string array
                    if ischar(arg) || iscell(arg) || iscellstr(arg) %#ok<ISCLSTR>
                        arg = string(arg);
                    end
                    error(this.checkInputNames(arg));   % check input names

                    % Populate parameter names
                    this.slParameterSpace_ = createDefaultParamSpace(this,arg);
                elseif iscell(arg)
                    %Value set value for parameters
                    this.slParameterSpace_ = createDefaultParamSpace(this,arg);
                elseif isa(arg,'multisim.design.internal.ValueSetParameter')
                    %Wrap value set parameter objects to a parameter space
                    switch this.Options.Method
                        case 'exhaustive'
                            pSpace = slsim.design.Exhaustive(arg);
                        case 'sequential'
                            pSpace = slsim.design.Sequential(arg);
                    end
                    this.slParameterSpace_ = pSpace;
                elseif isa(arg,'multisim.design.internal.ParameterSpace')
                    this.slParameterSpace_ = arg;
                else
                    error(message('stats:sensitivity:general:errGridding_Construct'));
                end
            elseif nargin == 2
                names = varargin{1};
                values = varargin{2};
                if numel(names) ~= numel(values)
                    error(message('stats:sensitivity:general:errGridding_Construct_name_values'));
                end
                for ct=1:numel(names)
                    slP = multisim.design.internal.Variable(names(ct),'base');
                    param(ct) = multisim.design.internal.ValueSetParameter(slP,values{ct}); %#ok<AGROW>
                end
                switch this.Options.Method
                    case 'exhaustive'
                        pSpace = multisim.design.internal.Exhaustive(param);
                    case 'sequential'
                        pSpace = multisim.design.internal.Sequential(param);
                end
                this.slParameterSpace_ = pSpace;
            else
                error(message('stats:sensitivity:general:errGridding_Construct_NumArguments'));
            end

            % Set remaining properties
            this.ParameterNamesReadOnly_ = false;
        end

        % Property methods: Parameter Values
        function values = get.ParameterValues(this)
            values = getParameterValues(this);
        end
        function this = set.ParameterValues(this, values)
            try
                this = setParameterValues(this, values);
            catch E
                rethrow(E);
            end
        end

        function aSample = sample(this, nSamples)
            %SAMPLE generate values from the space
            %
            %  values = sample(this, [NumRandomSample]);
            %
            %  Generate values from the hierarchy of parameter spaces
            %  defined by this parameter space. The method generates
            %  combinations of parameter values forming combinations as
            %  specified by the parameter space method properties.
            %
            %  If the space contains random parameters optionally specify
            %  the number of random samples to sample from any random
            %  parameters spaces in the space.
            %
            %  Inputs
            %    NumRandomSamples - Positive integer specifying the number of
            %                       random samples to sample from any random
            %                       parameter space. If omitted the default
            %                       value is 2*n, where n is the number of
            %                       random parameters. This number may be
            %                       overwritten if the random parameter
            %                       space is combined sequentially with
            %                       a gridded space as in that case the two
            %                       spaces need to have the same number of
            %                       samples.

            %Specify how many random parameter values to generate
            [~,nRandom] = getParameterCounts(this);
            haveNumSamples = nargin > 1;
            if nRandom > 0
                if ~haveNumSamples
                    nSamples = 2*max(nRandom); %potentially multiple random subspaces
                end
                this = setNumSamples(this,nSamples);
            elseif haveNumSamples
                %Have specified number of random samples but there are no
                %random parameters
                warning(message('stats:sensitivity:general:warnSample_NoRandomParameters'))
            end

            % Generate array of sample values from slParamSpace definition
            slParamSpace = getSLParamSpace(this);
            sampler = createSampler(slParamSpace);
            npts = getNumDesignPoints(sampler);
            if npts > 1e6
                warning(message('stats:sensitivity:general:warnSample_LargeNumberOfSamples',mat2str(npts)))
            end
            pts = createDesignPoints(sampler);
            values = lConvertDesignPoints2Table(pts);
            
            % Make a Sample, containing sample values
            aSample = stats.internal.Sample(values, this);
        end


        function this = setValues(this, pID, values)
            %SETVALUESt values of parameter(s)
            %
            % S = setValues(S, P_NAME, VALUES) sets the
            % parameter named P_NAME so that its values are
            % VALUES.  P_NAME needs to be a parameter in Gridding
            % object S.
            %
            % S = setValues(S, P_IDX, VALUES) sets the
            % parameter located at index P_IDX so that its values are
            % VALUES.
            %
            % See also addParameter, removeParameter

            % Verify that the object is not empty
            nP = getNumParameters(this);
            if nP == 0
                error(message('stats:sensitivity:general:errGridding_setValues_empty'));
            end

            % Check input
            if isnumeric(pID)  ...   % parameter index
                    &&  isscalar(pID)  &&  isreal(pID)  &&  ...
                    (pID >= 1)  &&  (pID <= nP)  &&  ...
                    (mod(pID,1) == 0)
                idx = pID;

            elseif (ischar(pID)  || (isstring(pID) && isscalar(pID))) &&  ...   % parameter name
                    any( strcmp(pID, this.ParameterNames) )
                idx = find( strcmp(pID, this.ParameterNames) );

            else
                error(message('stats:sensitivity:general:errGridding_setValues_MatchingName'));
            end

            % Set the values
            this.ParameterValues{idx} = values;
        end

        function this = addParameter(this, varargin)
            %ADDPARAMETER Add parameter to parameter list
            %
            % S = addParameter(S, NAMES)
            % S = addParameter(S, VALUES)
            % S = addParameter(S, NAMES, VALUES)

            if ~isempty(this.Spaces)
                error(message('stats:sensitivity:general:errGridding_addParameter_leafnode'));
            end

            narg = numel(varargin);
            switch narg
                case 0   % 0 arguments - Use default name and distribution
                    newSpace = createDefaultParamSpace(this,1);
                    newName = matlab.lang.makeUniqueStrings("Param_"+(getNumParameters(this)+1), ...
                        this.ParameterNames);
                    newParam = newSpace.ParameterSpaces(1);
                    newParam.ParameterType.Name = newName;
                case 1   % 1 argument - Either parameter names or values
                    arg = varargin{1};
                    if ischar(arg) || iscellstr(arg) || isstring(arg) || ... % parameter names
                            (iscell(arg) && all(cellfun(@(x) isstring(x),arg)))
                        %Convert all names to string array
                        if ischar(arg) || iscell(arg) || iscellstr(arg) %#ok<ISCLSTR>
                            arg = string(arg);
                        end
                        error( this.checkInputNames([arg, this.ParameterNames]) )
                        for ct = 1:numel(arg)
                            name = arg(ct);
                            slP = multisim.design.internal.Variable(name,'base');
                            newParam(ct) = multisim.design.internal.ValueSetParameter(slP,{-1 1}); %#ok<AGROW>
                        end
                    elseif iscell(arg)
                        if all(cellfun(@(x) iscell(x),arg))
                            % multiple values
                            for ct = 1:numel(arg)
                                newName = matlab.lang.makeUniqueStrings("Param_"+(getNumParameters(this)+ct), ...
                                    this.ParameterNames);
                                slP = multisim.design.internal.Variable(newName,'base');
                                newParam(ct) = multisim.design.internal.ValueSetParameter(slP, arg{ct}); %#ok<AGROW>
                            end
                        else
                            newName = matlab.lang.makeUniqueStrings("Param_"+(getNumParameters(this)+1), ...
                                this.ParameterNames);
                            slP = multisim.design.internal.Variable(newName,'base');
                            newParam = multisim.design.internal.ValueSetParameter(slP, arg);
                        end
                    else
                        error(message('stats:sensitivity:general:errGridding_addParameter_names_or_values'));
                    end


                case 2   % 2 arguments - parameter names and values
                    names = varargin{1};
                    %Convert all names to string array
                    if ischar(names) || iscell(names) || iscellstr(names) %#ok<ISCLSTR>
                        names = string(names);
                    else
                        error(message('stats:sensitivity:general:errGridding_addParameter_names'));
                    end
                    np = numel(names);
                    values = varargin{2};
                    ok = iscell(values);
                    if np > 1
                        ok = ok && all(cellfun(@(x) iscell(x), values));
                        ok = ok && (numel(values)==np);
                    end
                    if ~ok
                        error(message('stats:sensitivity:general:errGridding_addParameter_values'));
                    end
                    newParam = [];
                    error( this.checkInputNames([names, this.ParameterNames]) )
                    for ct=1:np
                        slP = multisim.design.internal.Variable(names(ct),'base');
                        if np > 1
                            vp = multisim.design.internal.ValueSetParameter(slP,values{ct});
                        else
                            vp = multisim.design.internal.ValueSetParameter(slP,values);
                        end
                        newParam = [newParam, vp]; %#ok<AGROW>
                    end

                otherwise
                    error(message('stats:sensitivity:general:errGridding_Construct_NumArguments'));
            end

            % Add new parameter names and values
            nP = getNumParameters(this);
            if nP == 0   % object is empty
                switch this.Options.Method
                    case 'exhaustive'
                        slSpace = multisim.design.internal.Exhaustive(newParam);
                    case 'sequential'
                        slSpace = multisim.design.internal.Sequential(newParam);
                end
                this.slParameterSpace_ = slSpace;

            else   % object has contents

                %Collect the existing parameter definitions and combine
                %with the new definitions in a new parameter space
                % for ct=1:nP
                %     oldParam(ct) = this.slParameterSpace_.ParameterSpaces(ct);
                % end
                oldParam = this.slParameterSpace_.ParameterSpaces;
                switch this.Options.Method
                    case 'exhaustive'
                        slSpace = multisim.design.internal.Exhaustive([oldParam,newParam]);
                    case 'sequential'
                        slSpace = multisim.design.internal.Sequential([oldParam,newParam]);
                end
                this.slParameterSpace_ = slSpace;
            end
        end

        function this = removeParameter(this, value)
            %REMOVEPARAMETER Remove parameters from parameter list
            %
            % S = removeParameter(S, ParamNames)
            % S = removeParameter(S, idx)

            if isempty(this.slParameterSpace_)
                error(message('stats:sensitivity:general:errGridding_removeParameter_leafnode'));
            end
            if ischar(value) || iscell(value) || iscellstr(value) %#ok<ISCLSTR>
                value = string(value);
            end

            if isstring(value)   % names of parameters
                [tf, loc] = ismember(value, this.ParameterNames);
                if ~all(tf)   % check match to existing parameter names
                    error(message('stats:sensitivity:general:errGridding_removeParameter_names'));
                end
                iRemove = loc;   % indices to remove

            elseif isnumeric(value)   % indices of parameters
                if ~(...   % check index values
                        isreal(value)  &&  all( mod(value,1) == 0 )  &&  ...
                        all(value >= 1)  &&  all(value <= getNumParameters(this) )  )
                    error(message('stats:sensitivity:general:errGridding_removeParameter_indices'));
                end
                iRemove = value;   % indices to remove
            else
                error(message('stats:sensitivity:general:errGridding_removeParameter_format'));
            end

            % Remove parameters and update properties
            nP = getNumParameters(this);
            iKeep = setdiff(1:nP, iRemove);   % row of indices to keep
            idx = 1;
            if isempty(iKeep)
                %Remove all the parameters
                this.slParameterSpace_ = [];
            else
                for ct=1:nP
                    if any(ct==iKeep)
                        newParam(idx) = this.slParameterSpace_.ParameterSpaces(ct); %#ok<AGROW>
                        idx = idx + 1;
                    end
                end
                switch this.Options.Method
                    case 'exhaustive'
                        slSpace = multisim.design.internal.Exhaustive(newParam);
                    case 'sequential'
                        slSpace = multisim.design.internal.Sequential(newParam);
                end
                this.slParameterSpace_ = slSpace;
            end
        end

        function new = combine(this,sp2,option)
            %COMBINE Combine parameter spaces
            %
            %   new = combine(this,sp2,option)
            %

            %Handle case where Gridded.combine is dispatched by inferior
            %class.
            if ~isa(this,'stats.internal.Gridding')
                tmp = this;
                this = sp2;
                sp2 = tmp;
            end
            if nargin < 2
                error(message('stats:sensitivity:general:errGridding_combine_NumArguments'));
            end
            if nargin < 3
                %Default option setting
                option = 'exhaustive';
            end

            new = this.createEmpty(); %Call factory to create correct empty class instance
            new.Spaces = {this, sp2};
            new.Options.Method = option;
        end
    end

    methods (Access = protected)
        function names = getParameterNames(this)
            %GETPARAMETERNAMES
            %
            %    Return string array of all parameters in the space.
            %

            names = string.empty;
            pSpace = this.slParameterSpace_;
            if isempty(pSpace)
                %Composite space, recurse
                for ct=1:numel(this.Spaces)
                    names = [names, getParameterNames(this.Spaces{ct})]; %#ok<AGROW>
                end
            else
                %Leaf space, get names from the space
                names = string.empty;
                for ct=numel(this.slParameterSpace_.ParameterSpaces):-1:1
                    names(ct) = this.slParameterSpace_.ParameterSpaces(ct).ParameterType.Name;
                end
            end
        end
        function this = setParameterNames(this,newNames)
            %SETPARAMETERNAMES
            %
            %   this = setParameterNames(this,newNames)
            %
            %   Set the names of parameters in the space

            pSpace = this.slParameterSpace_;
            if isempty(pSpace)
                %Composite space, recurse and send correct subset of names
                %to subspaces
                nS = numel(this.Spaces);
                offset = 1;
                for ct=1:nS
                    nP = getNumParameters(this.Spaces{ct});
                    setParameterNames(this.Spaces{ct},newNames(offset:(offset+nP-1)));
                    offset = offset+nP;
                end
            else
                %Leaf space, set names directly
                nP = getNumParameters(this);
                for ct=1:nP
                    this.slParameterSpace_.ParameterSpaces(ct).ParameterType.Name = newNames(ct);
                end
            end
        end

        function values = getParameterValues(this)
            %GETPARAMETERVALUES
            %
            %   values = getParameterValues(this)
            %
            %   Return cell array of cells. The outer cell array has
            %   elements for each parameter and the inner cell array
            %   contains scalar numeric values.
            %
            pSpace = this.slParameterSpace_;
            values = cell(1,0);
            if isempty(pSpace)
                %Composite space, recurse
                for ct=1:numel(this.Spaces)
                    addValues = getParameterValues(this.Spaces{ct});
                    if iscell(addValues)
                        values = [values, addValues]; %#ok<AGROW>
                    else
                        nV = numel(addValues);
                        tmp = cell(1,numel(addValues));
                        for ctV = 1:nV
                            tmp{ctV} = addValues(ctV);
                        end
                        values = [values, tmp]; %#ok<AGROW>
                    end
                end
            else
                %Leaf space, get values directly.
                nP = numel(pSpace.ParameterSpaces);
                values = cell(1,nP);
                for ct=1:nP
                    values{ct} = pSpace.ParameterSpaces(ct).Values;
                end
            end
        end
        function this = setParameterValues(this,newValues)
            %SETPARAMETERVALUES
            %
            %   this = setParameterValues(this,newValues)
            %
            %   Expect newValues to be a cell array of cell arrays. The
            %   outer cell array has elements for each parameter and the
            %   inner cell array contains scalar values.
            %

            pSpace = this.slParameterSpace_;
            nP = getNumParameters(this);
            ok = iscell(newValues) && numel(newValues) == nP;
            % If this is a leaf space  newValues must be a cell array of
            % cells. For non-leaf spaces newValues must be a cell array but
            % could contain distribution objects.
            ok = ok && (isempty(pSpace) || all(cellfun(@(x) iscell(x), newValues)));
            if ~ok
                error(message('stats:sensitivity:general:errGridding_ParameterValues_property'));
            end

            if isempty(pSpace)
                %Composite space, recurse and send correct subset of values
                %to each subspace.
                nS = numel(this.Spaces);
                offset = 1;
                for ct=1:nS
                    nP = getNumParameters(this.Spaces{ct});
                    setParameterValues(this.Spaces{ct},newValues(offset:(offset+nP-1)));
                    offset = offset+nP;
                end
            else
                nP = getNumParameters(this);
                if strcmp(this.Options.Method,'exhaustive')
                    for ct=1:nP
                        this.slParameterSpace_.ParameterSpaces(ct).Values = newValues{ct};
                    end
                else
                    %Space is combined sequentially and parameters need to have
                    %same number of values. If the number of values is
                    %changing need to recreate the parameter space, as
                    %cannot modify the number of values one parameter at a
                    %time in the sequentially combined space.
                    nValues = numel(this.slParameterSpace_.ParameterSpaces(1).Values);
                    if nValues == numel(newValues{1})
                        %No change in the number of values can use existing parameter space
                        for ct=1:nP
                            this.slParameterSpace_.ParameterSpaces(ct).Values = newValues{ct};
                        end
                    else
                        %Change in number of values, get the parameter
                        %definitions, change the parameter values and
                        %recombine the parameters into a sequential space
                        param = this.slParameterSpace_.ParameterSpaces;
                        for ct=1:nP
                            param(ct).Values = newValues{ct};
                        end
                        this.slParameterSpace_ = multisim.design.internal.Sequential(param);
                    end
                end
            end
        end

        function n = getNumParameters(this)
            %GETNUMPARAMETERS
            %

            n = 0;
            if isempty(this.slParameterSpace_)
                %Composite space, recurse to find number of parameters
                nS = numel(this.Spaces);
                for ct=1:nS
                    n = n + getNumParameters(this.Spaces{ct});
                end
            else
                %Leaf space, get number of parameters directly
                n = numel(this.slParameterSpace_.ParameterSpaces);
            end
        end

        function [nParam,nRandom,nGridded] = getParameterCounts(this)
            %GETPARAMETERCOUNTS
            %
            %   [nParam,nRandom,nGridded] = getParameterCounts(this)
            %
            %   Outputs
            %     nParam   - number of parameters
            %     nRandom  - array with length equal to the number of random
            %                spaces and the elements equal to the number of
            %                random parameters in that space
            %     nGridded - number of combinations of gridded parameters

            nParam = numel(this.ParameterNames);
            nGridded = getNumCombinations(this);
            nRandom = getNumRandom(this);
        end

        function n = getNumCombinations(this)
            %GETNUMCOMBINATIONS
            %

            %Define the aggregation function use to compute the number of
            %combinations
            switch this.Options.Method
                case 'exhaustive'
                    aggFcn = @(x,y) x*y;
                    n = 1;
                case 'sequential'
                    aggFcn = @(x,y) min(x,y);
                    n = inf;
            end

            if isempty(this.Spaces)
                values = this.ParameterValues;
                for ct=1:numel(values)
                    n = aggFcn(n,numel(values{ct}));
                end
            else
                %Recurse into gridded subspaces only
                for ct=1:numel(this.Spaces)
                    space = this.Spaces{ct};
                    if isa(space,'stats.internal.Gridding')
                        n = aggFcn(n,getNumCombinations(space));
                    end
                end
            end
        end
        function nRandom = getNumRandom(this)
            %GETNUMRANDOM
            %
            %  nRandom = getNumRandom(this)
            %
            %  Outputs:
            %     nRandom - array with length equal to the number of random
            %               spaces and the elements equal to the number of
            %               random parameters in that space
            %

            nRandom = [];
            spaces = this.Spaces;
            for ct=1:numel(spaces)
                if isa(spaces{ct}, 'stats.internal.Sampling')
                    nR = getNumParameters(spaces{ct});
                    nRandom = [nRandom,nR]; %#ok<AGROW>
                else
                    nR = getNumRandom(spaces{ct});
                    nRandom = [nRandom,nR]; %#ok<AGROW>
                end
            end
        end

        function slParamSpace = getSLParamSpace(this)
            %GETSLPARAMSPACE
            %
            %   Combine all slParameterSpace definitions using the options
            %   specified. This method recurses nested spaces.
            %
            if isempty(this.slParameterSpace_)
                %This object is composed of other spaces that need to be
                %combined
                %
                % ~isempty(this.Spaces)
                v = cell(numel(this.Spaces),1);
                for ct=1:numel(this.Spaces)
                    v{ct} = getSLParamSpace(this.Spaces{ct});
                end
                switch this.Options.Method
                    case 'exhaustive'
                        slParamSpace = multisim.design.internal.Exhaustive([v{:}]);
                    case 'sequential'
                        %Check for any random spaces and make sure their
                        %NumSamples is the same as the gridded space
                        idxC = cellfun(@(x) isa(x,'multisim.design.internal.CombinatorialParameterSpace'),v);
                        idxR = cellfun(@(x) isa(x,'stats.internal.RandomParameterSpace'),v);
                        nSamples = v{find(idxC,1)}.NumDesignPoints;
                        if any(idxR)
                            v{idxR}.NumSamples = nSamples;
                        end
                        slParamSpace = multisim.design.internal.Sequential([v{:}]);
                end
            else
                slParamSpace = this.slParameterSpace_;
            end
        end

        function this = setNumSamples(this,nSamples)
            %SETNUMSAMPLES
            %
            %   Recurse all parameter spaces to find random parameter
            %   spaces and set their NumSamples property.
            %

            for ct=1:numel(this.Spaces)
                this.Spaces{ct} = setNumSamples(this.Spaces{ct},nSamples);
            end
        end

        function opts = getOptions(this)

            opts = stats.internal.GriddingOptions;
            if isempty(this.slParameterSpace_)
                opts = this.Options_;
                return
            end
            switch class(this.slParameterSpace_)
                case 'multisim.design.internal.Exhaustive'
                    opts.Method = 'exhaustive';
                case 'multisim.design.internal.Sequential'
                    opts.Method = 'sequential';
            end
        end
        function this = setOptions(this, newValue)
            if ~isa(newValue, 'stats.internal.GriddingOptions')  ||  ~isscalar(newValue)
                error(message('stats:sensitivity:general:errGridding_Options_property'));
            else
                if isempty(this.slParameterSpace_) && isempty(this.Spaces)
                    %Empty space, no options to set
                    return
                elseif ~isequal(this.Options.Method,newValue.Method)
                    if isempty(this.slParameterSpace_)
                        this.Options_ = newValue;
                    else
                        %Leaf space, switch space from exhaustive to
                        %sequential or vice versa. Extract the parameter
                        %definitions from the current space and package
                        %them in a new space
                        for ct=1:getNumParameters(this)
                            pss(ct) = this.slParameterSpace_.ParameterSpaces(ct); %#ok<AGROW>
                        end
                        switch newValue.Method
                            case 'exhaustive'
                                if isa(this.slParameterSpace_,'multisim.design.internal.Sequential')
                                    this.slParameterSpace_ = multisim.design.internal.Exhaustive(pss);
                                end
                            case 'sequential'
                                if isa(this.slParameterSpace_,'multisim.design.internal.Exhaustive')
                                    this.slParameterSpace_ = multisim.design.internal.Sequential(pss);
                                end
                        end
                    end
                end
            end
        end

        function pSpace = createDefaultParamSpace(this,names)
            % Default names for parameters, numbered from "Param_1" to "Param_XXX"

            if isnumeric(names)
                %Have number of parameters but no names. Set names to "Param_1",
                %"Param_2", "Param_XXX" etc.
                for ct = 1:names
                    slP = multisim.design.internal.Variable("Param_"+ct,'base');
                    param(ct) = multisim.design.internal.ValueSetParameter(slP,{-1 1}); %#ok<AGROW>
                end
            elseif iscell(names)
                %Have value sets but no names. Set names to "Param_1",
                %"Param_2", "Param_XXX" etc.
                for ct = 1:numel(names)
                    slP = multisim.design.internal.Variable("Param_"+ct,'base');
                    param(ct) = multisim.design.internal.ValueSetParameter(slP,names{ct}); %#ok<AGROW>
                end
            else
                for ct=1:numel(names)
                    slP = multisim.design.internal.Variable(names(ct),'base');
                    param(ct) = multisim.design.internal.ValueSetParameter(slP,{-1 1}); %#ok<AGROW>
                end
            end
            switch this.Options.Method
                case 'exhaustive'
                    slSpace = multisim.design.internal.Exhaustive(param);
                case 'sequential'
                    slSpace = multisim.design.internal.Sequential(param);
            end

            %pSpace = stats.internal.Gridding(slSpace);
            pSpace = slSpace;
        end
        function [this,rps] = extractRandomParameterSpace(this)

            rps = [];
            for ct=1:numel(this.Spaces)
                if isa(this.Spaces{ct},'stats.internal.Sampling')
                    rps = this.Spaces{ct};
                    this.Spaces(ct) = [];
                    if numel(this.Spaces) == 1
                        %There was only one gridded space and the random
                        %space, return the gridded space as the remaining
                        %space. In all other cases there were multiple
                        %gridded spaces making up the space.
                        this = this.Spaces{1};
                    end
                    break
                end
            end
        end
    end

    methods (Access = protected, Static = true)
        function obj = createEmpty()
            obj = stats.internal.Gridding();
        end
    end

    methods (Access = protected,  Static,  Sealed)

        function x = ensureCell(x)
            % Ensure that X is in a cell array For example, converts X from
            % a single character vector to a cell array of character vector
            if ~iscell(x)
                x = {x};
            end
        end

        function msg = checkInputNames(names, varargin)
            % Make sure names are unique, valid variable names
            %
            % checkInputNames(NEW) checks that the names in cell array
            % NEW are unique, valid variable names
            %
            % checkInputNames(NEW, EXISTING) checks that the names in
            % the cell arrays NEW and EXISTING are all unique, valid
            % variable names

            if ~all(cellfun(@(c) isvarname(c), names))  ||  ...
                    ( numel(unique(names)) < numel(names) )
                msg = message('stats:sensitivity:general:errGridding_checkInputNames');
            else
                msg = [];
            end
        end

        function msg = checkInputDims(A,B)
            % Make sure inputs are vectors with same number of elements
            if isvector(A)  &&  isvector(B)  &&  (numel(A) == numel(B))
                msg = [];
            else
                msg = message('stats:sensitivity:general:errGridding_checkInputDims');
            end
        end
    end

    methods (Hidden)
        function y = utMethod(~, fcnName, varargin)
            % UTMETHOD For testing methods
            %
            % fcnName is a character vector with the name of the method
            % varargin contains input arguments for the method
            %
            y = feval(fcnName, varargin{:} );
        end
    end
end

function values = lConvertDesignPoints2Table(designPoint)
numSamples = numel(designPoint);
numParam = numel(designPoint(1).ParameterSamples);
values = cell(numSamples,numParam);
names = strings(1,numParam);
for ctS=1:numSamples
    dp = designPoint(ctS);
    for ctP=1:numParam
        values{ctS,ctP} = dp.ParameterSamples(ctP).Value;
        if ctS == 1
            names(ctP) = dp.ParameterSamples(ctP).ParameterType.Name;
        end
    end
end
values = cell2table(values,'VariableNames',names);
end

% LocalWords:  gridding NPARAM valueset multisim leafnode REMOVEPARAMETER sp GETPARAMETERNAMES
% LocalWords:  SETPARAMETERNAMES GETPARAMETERVALUES SETPARAMETERVALUES GETNUMPARAMETERS
% LocalWords:  GETPARAMETERCOUNTS GETNUMCOMBINATIONS GETNUMRANDOM GETSLPARAMSPACE SETNUMSAMPLES
% LocalWords:  UTMETHOD
