function varargout = predict(this, varargin)
% predict   Make predictions on data with network
%
%   Y = predict(net, X) computes predictions of the single input single
%   output network net on the data X. The format of X depends on the input
%   layer of the network.
%
%   For a network with an image input layer, X must be:
%       - A single image.
%       - A 4-D array of images, where the first three dimensions index the
%         height, width and channels of an image, and the fourth dimension
%         indexes the individual images.
%       - A 5-D array of images, where the first four dimensions index the
%         height, width, depth and channels of an image, and the fifth
%         dimension indexes the individual images.
%       - A datastore, where the output of the datastore read function is
%         one image, a cell array of images, or a table whose first column
%         contains images.
%       - A table, where the first column contains either image paths or
%         images.
%
%   For a network with a sequence input layer, X must be:
%       - A numeric array representing a single sequence.
%       - A cell array of sequences.
%       - A datastore, where the output of the datastore read function is
%         a cell array or a table containing sequences in the first
%         column.
%   Each sequence must be either:
%       - A vector sequence, specified by a C-by-S matrix, where C is the
%         number of features and S is the number of time steps.
%       - A 2-D image sequence, specified by an H-by-W-by-C-by-S array,
%         where H-by-W-by-C is the 2-D image size and S is the number of
%         time steps.
%       - A 3-D image sequence, specified by an H-by-W-by-D-by-C-by-S
%         array, where H-by-W-by-D-by-C is the 3-D image size and S is the
%         number of time steps.
%
%   For a network with a feature input layer, X must be:
%       - A single observation specified as a row vector.
%       - A 2-D array of observations, where the first dimension indexes
%         the observations and the second dimension indexes the features.
%       - A datastore, where the output of the datastore read function is
%         a cell array or a table containing feature vectors in the first
%         column.
%       - A table, where each column represents a feature of the dataset.
%
%   For a network with a point cloud input layer, X must be:
%       - A single observation of point cloud data specified as a 2-D or
%         3-D numeric array.
%       - A 3-D or 4-D numeric array of point clouds, where the second to
%         last dimension indexes the channels and the last dimension
%         indexes the individual observations.
%       - A datastore, where the output of the datastore read function is
%         a cell array containing point cloud data as a numeric array.
%
%   For a classification problem, Y will contain the predicted scores,
%   arranged in an N-by-K matrix, where N is the number of observations,
%   and K is the number of classes.
%
%   For a regression problem, Y will contain the predicted responses,
%   arranged in an N-by-R matrix, where N is the number of observations,
%   and R is the number of responses, or in an H-by-C-by-N 3-D array, where
%   N is the number of observations and H-by-C is the size of a single
%   response, or in an H-by-W-by-C-by-N 4-D array, where N is the number of
%   observations and H-by-W-by-C is the size of a single response.
%
%   [Y1, ..., YM] = predict(net, X) computes predictions with the data X,
%   where M is the number of network outputs. The output layers determine
%   the format of the output data as described above.
%
%   __ = predict(net, cds) computes predictions for a multiple input
%   network with the combined datastore cds. The combined datastore must
%   return a 1-by-N cell array, where N is the number of network inputs.
%
%   __ = predict(net, X1, ..., XN) computes predictions for a multiple
%   input network with N inputs, where the input data is X1, ..., XN.
%
%   __ = predict(__, 'PARAM1', VAL1, ...) will compute predictions with
%   the following optional name/value pairs:
%
%       'MiniBatchSize'         - The size of the mini-batches for
%                                 computing predictions. Larger mini-batch
%                                 sizes lead to faster predictions, at the
%                                 cost of more memory. The default is 128.
%       'ExecutionEnvironment'  - The execution environment for the
%                                 network. This determines what hardware
%                                 resources will be used to run the
%                                 network.
%                                   - 'auto' - Use a GPU if it is
%                                     available, otherwise use the CPU.
%                                   - 'gpu' - Use the GPU. To use a
%                                     GPU, you must have Parallel Computing
%                                     Toolbox(TM) and a supported GPU
%                                     device. If a suitable GPU is not
%                                     available, predict returns an error
%                                     message.
%                                   - 'cpu' - Use the CPU.
%                                   - 'multi-gpu' - Use multiple GPUs on one
%                                     machine, using a local parallel pool.
%                                     If no pool is open, one is opened with
%                                     one worker per supported GPU device.
%                                   - 'parallel' - Use a compute cluster. If
%                                     no pool is open, one is opened using
%                                     the default cluster profile. If the
%                                     pool has access to GPUs then they will
%                                     be used and excess workers will be
%                                     idle. If the pool has no GPUs then
%                                     prediction will take place on all cluster
%                                     CPUs.
%                                 The default is 'auto'.
%       'Acceleration'          - Optimizations that can improve
%                                 performance at the expense of some
%                                 overhead on the first call and possible
%                                 additional memory usage.
%                                   - 'auto' - Automatically select
%                                   optimizations suitable for the input
%                                   network and environment.
%                                   - 'mex' - (GPU only) Generate and
%                                   execute a MEX function. Subsequent
%                                   calls with the same network and options
%                                   use the MEX function. This option is not
%                                   supported when 'ExecutionEnvironment'
%                                   value is either 'parallel' or 'multi-gpu'.
%                                   - 'none' - Disable all acceleration.
%                                 The default is 'auto'.
%       'ReturnCategorical'     - Option to return outputs of
%                                 classification layers as categorical,
%                                 specified as true or false. The default
%                                 is false.
%       'SequenceLength'        - Strategy to determine the length of the
%                                 sequences used per mini-batch. Options
%                                 are:
%                                   - 'longest' to pad all sequences in a
%                                     batch to the length of the longest
%                                     sequence.
%                                   - 'shortest' to truncate all sequences
%                                     in a batch to the length of the
%                                     shortest sequence.
%                                   - Positive integer - For each
%                                     mini-batch, pad the sequences to the
%                                     nearest multiple of the specified
%                                     length that is greater than the
%                                     longest sequence length in the
%                                     mini-batch, and then split the
%                                     sequences into smaller sequences of
%                                     the specified length. If splitting
%                                     occurs, then the software creates
%                                     extra mini-batches.
%                                 The default is 'longest'.
%       'SequencePaddingValue'  - Scalar value used to pad sequences where
%                                 necessary. The default is 0.
%       'SequencePaddingDirection'
%                               - Direction of padding or truncation,
%                                 specified as:
%                                  - 'right' - Pad or truncate sequences
%                                  on the right.
%                                  - 'left' - Pad or truncate sequences on
%                                  the left.
%                                 The default is 'right'.

%   Copyright 2018-2022 The MathWorks, Inc.

if ~isscalar(this)
    error(message('nnet_cnn:DAGNetwork:NonScalarNetwork'));
end

if this.NumInputLayers == 1
    % Network has one input
    X = varargin{1};
    nameValuePairs = varargin(2:end);
elseif iDataForMultipleInputNetworkPassedAsOneArgument(nargin-1, varargin) && ...
        iIsCombinedOrTransformedDatastore(varargin)
    % Network has multiple inputs, but the inputs are passed in as one
    % argument.
    X = varargin{1};
    nameValuePairs = varargin(2:end);
elseif iDataForMultipleInputNetworkPassedAsMultipleArguments(nargin-1, varargin, this.NumInputLayers)
    % Network has multiple inputs, and the inputs are passed in as multiple
    % arguments.
    X = varargin(1:this.NumInputLayers);
    nameValuePairs = varargin((this.NumInputLayers+1):end);
else
    error( message( 'nnet_cnn:DAGNetwork:InvalidPredictDataForMultipleInputNetwork', this.NumInputLayers) );
end

statefulPredict = false;
[options, executionEnvironment, acceleration, returnCategorical] = ...
    this.parseAndValidatePredictNameValuePairs(statefulPredict, nameValuePairs{:});

% Calculate predictions
try
    Y = this.calculatePredict( ...
        X, options, executionEnvironment, acceleration );
catch ex
    nnet.internal.cnn.util.rethrowDLExceptions(ex)
end

% Convert outputs to categorical if necessary
if returnCategorical
    Y = this.convertMultiOutputToCategorical(Y);
end

varargout = Y;
end

function tf = iDataForMultipleInputNetworkPassedAsOneArgument( ...
    numArguments, arguments )
tf = (numArguments == 1) || ...
    iNameValuePairsShouldStartAtThisPosition(numArguments, arguments, 2);
end

function tf = iIsCombinedOrTransformedDatastore(arguments)
tf = isa(arguments{1},'matlab.io.datastore.CombinedDatastore') || ...
    isa(arguments{1},'matlab.io.datastore.TransformedDatastore');
end

function tf = iDataForMultipleInputNetworkPassedAsMultipleArguments( ...
    numArguments, arguments, numInputs )
tf = (numArguments == numInputs) || ...
    iNameValuePairsShouldStartAtThisPosition(numArguments, arguments, numInputs+1);
end

function tf = iNameValuePairsShouldStartAtThisPosition(numArguments, arguments, position)
% Note that this check takes place before we parse arguments, so it does
% not definitively prove that name/value pairs start at this position (i.e.
% the test is necessary but not sufficient). However, in the event that
% there is a string in this position, but it is not an N-V pair, the parser
% will find the error.
tf = (numArguments >= position) && ...
    iFirstStringOrArrayIsAtThisPosition(arguments, position);
end

function tf = iFirstStringOrArrayIsAtThisPosition(inputCell, position)

firstStringOrCharArray = find( ...
    cellfun(@iIsValidStringOrCharArray, inputCell), 1 );

if isempty(firstStringOrCharArray)
    tf = false;
else
    tf = ( position == firstStringOrCharArray );
end

end

function tf = iIsValidStringOrCharArray(value)
tf = nnet.internal.cnn.layer.paramvalidation.isValidStringOrCharArray(value);
end
