function vals = optimvalues(p, varargin)
%OPTIMVALUES Create values for an OptimizationProblem
% 
%   X = OPTIMVALUES(P, NAME1, VALUE1, ...) creates NVAL values for the
%   specified optimization problem. Each NAME is the name of a variable,
%   objective or constraint in the problem. Each VALUE contains a double 
%   array holding the values for NAME. The size of each VALUE is defined 
%   below
% 
%      P.<Property>.NAME |            size(VALUE)  
%      ------------------+----------------------------------
%      Scalar or Vector  |  numel(P.<Property>.NAME)-by-NVAL
%      ------------------+----------------------------------
%      Matrix or Array   |  size(P.<Property>.NAME)-by-NVAL
%    
%   where <Property> is either Variable, Objective or Constraints.
% 
%   X = OPTIMVALUES(P, NAME1, VALUE1, ..., 'Objective', OBJVALUE) creates
%   values that include data for an objective that is not labelled in the
%   problem.
%
%   X = OPTIMVALUES(P, NAME1, VALUE1, ..., 'Constraints', CONVALUE) creates
%   values that include data for constraints that are not labelled in the
%   problem.
%   
%   X = OPTIMVALUES(P) creates 1-by-0 OptimizationValues vector with properties
%   that are the variables, objectives and constraints in P.
%
%   X = OPTIMVALUES(P, VOLD) creates values from the values VOLD. X only has 
%   data for variables, objectives and constraints that are in both VOLD and P. 
% 
%     Example: 
%
%     % Create an optimization problem
%     p = optimproblem('Description', 'Simple Example');
%    
%     % Create optimization variables
%     x = optimvar('x', 2, 1, 'LowerBound', 0);
%     y = optimvar('y', 'LowerBound', 0);
%  
%     % Define the objective
%     p.Objective = -5*x(1) - x(2) + 3*y;
%
%     % Create a 5-by-1 vector of optimization values. Each value holds a
%     % 2-by-1 random vector for x and a random scalar for y
%     xD = rand(2, 5);
%     yD = rand(1, 5);
%     v = optimvalues(p, 'x', xD, 'y', yD);
%    
%     % Add objective values
%     v.Objective = -5*xD(:, 1) – xD(:, 2) + 3*yD;

%   Copyright 2021-2023 The MathWorks, Inc.

% Error checking

% Must supply at least one input
narginchk(1, inf);

% First input must be an OptimizationProblem
if ~isa(p, 'optim.problemdef.OptimizationProblem')
    error(message('optim_problemdef:optimvalues:NotOptimizationProblem'));
end

% Get the value data
valueStruct = iParseInputs(p, varargin);

% Create the OptimizationValues vector
vals = optim.problemdef.OptimizationValues(p, valueStruct);

end

function [valueData, varNames, objNames, conNames] = iParseInputs(prob, inputs)

% Get the OptimizationValues properties
[varNames, objNames, conNames] = getQuantityNames(prob);
defaultPropertyValue = [];

% Create an input parser and add the properties as name-value pairs
p = inputParser;
for i = 1:numel(varNames)
    addParameter(p, varNames{i}, defaultPropertyValue, ...
        @(x)iValidateValues(x, prob, 'Variables', varNames{i}));
end
for i = 1:numel(objNames)
    addParameter(p, objNames{i}, defaultPropertyValue, ...
        @(x)iValidateValues(x, prob, 'Objective', objNames{i}));
end
for i = 1:numel(conNames)
    addParameter(p, conNames{i}, defaultPropertyValue, ...
        @(x)iValidateValues(x, prob, 'Constraints', conNames{i}));
end

% Parse the inputs
parse(p, inputs{:});

% Extract the inputs
valueData = p.Results;

% Check that the same number of values has been specified for each quantity
numVarValues = optim.problemdef.OptimizationValues.numQuantityValues(valueData, prob);
fns = fieldnames(valueData);
idxZero = numVarValues == 0;
numNonZeroValues = numVarValues(~idxZero);
quantityNonZeroNames = fns(~idxZero);
[uniqueNumValues, idxValueData] = unique(numNonZeroValues);
if numel(uniqueNumValues) > 1
    ME = MException(message('optim_problemdef:optimvalues:NotSameNumberOfValues', ...
        numNonZeroValues(idxValueData(1)), quantityNonZeroNames{idxValueData(1)}, ...
        numNonZeroValues(idxValueData(2)), quantityNonZeroNames{idxValueData(2)}));
    throw(ME);
end

end

function iValidateValues(vals, p, problemProperty, quantityName)

if ~isnumeric(vals) && ~islogical(vals)
    error(message('optim_problemdef:optimvalues:ValueNotNumericOrLogical'));
end

if isstruct(p.(problemProperty))
    thisQuantity = p.(problemProperty).(quantityName);
else
    thisQuantity = p.(problemProperty);
end

% Check the value size for scalar and vector quantities
if isscalar(thisQuantity) || isvector(thisQuantity)
    
    % Number of elements in the problem quantity
    numVector = numel(thisQuantity);

    % The number of rows in the specified value must match the number of
    % elements in the vector
    if ~ismatrix(vals) || size(vals, 1) ~= numVector
        sizeStr = numVector + "-by-NUMVALUES";
        error(message('optim_problemdef:optimvalues:VectorIncorrectSize', sizeStr));
    end

else

    % Quantity size specified in the problem
    sizeQuantityInProblem = size(thisQuantity);

    % Quantity size specified in the value
    sizeValues = size(vals);
    if numel(sizeValues) > numel(sizeQuantityInProblem)
        sizeQuantityFromValue = sizeValues(1:end-1);
    else
        sizeQuantityFromValue = sizeValues;
    end

    % Values must be passed as [sizeQuantity, numValues] arrays. 
    if ~isequal(sizeQuantityFromValue, sizeQuantityInProblem)
        sizeStr = strjoin(string(sizeQuantityInProblem), "-by-");
        sizeStr = sizeStr + "-by-NUMVALUES";
        error(message('optim_problemdef:optimvalues:ArrayIncorrectSize', sizeStr));
    end

end

end



