function layer = transposedConv1dLayer(filterSize, numFilters, NameValueArgs)
% transposedConv1dLayer   1-D transposed convolution layer
%
%   layer = transposedConv1dLayer(filterSize, numFilters) creates a
%   transposed 1D convolution layer. Use this layer to upsample feature
%   maps. filterSize is a scalar specifying the length of
%   the filters. numFilters specifies the number of filters, which 
%   determines the number of channels in the output feature map.
%
%   layer = transposedConv1dLayer(filterSize, numFilters, 'PARAM1', VAL1, ...)
%   specifies optional parameter name/value pairs for creating the layer:
%
%       'Stride'                  - Upsampling factor of the input, 
%                                   specified as a scalar. When used with 
%                                   Cropping 'same', the output size equals 
%                                   inputSize .* stride. 
%                                   The default is 1 and no upsampling is
%                                   performed.
%
%       'Cropping'                - Amount to trim the edges of the full
%                                   transposed convolution, specified as
%                                   one of the following: 
%                                   - 'same' - Apply cropping such that the 
%                                     output size equals inputSize .*
%                                     stride, where inputSize is the length
%                                     of the input.
%                                   - scalar - Apply cropping with the 
%                                     specified size on both sides of the 
%                                     input.
%                                   - vector [l r] - Apply cropping with 
%                                     size l and r on the left and right 
%                                     sides of the input, respectively.
%                                   The default is 0.
%
%       'NumChannels'             - Number of channels for each filter, 
%                                   specified as one of the following:
%                                   - 'auto' - Automatically determine the 
%                                     number of channels at training time.
%                                   - positive integer - Use the specified 
%                                     number of channels.
%                                   The default is 'auto'.
%
%       'Weights'                 - Layer weights, specified as a
%                                   filterSize-by-numFilters-by-numChannels 
%                                   array or []. 
%                                   The default is []. 
%
%       'Bias'                    - Layer biases, specified as a
%                                   1-by-numFilters array or [].
%                                   The default is [].
%
%       'WeightLearnRateFactor'   - Number that specifies multiplier for
%                                   the learning rate of the weights.
%                                   The default is 1.
%
%       'BiasLearnRateFactor'     - Number that specifies a multiplier
%                                   for the learning rate for the biases.
%                                   The default is 1.
%
%       'WeightL2Factor'          - Number that specifies a multiplier
%                                   for the L2 weight regularizer for the
%                                   weights. 
%                                   The default is 1.
%
%       'BiasL2Factor'            - Number that specifies a multiplier
%                                   for the L2 weight regularizer for the
%                                   biases. 
%                                   The default is 0.
%
%       'WeightsInitializer'      - Function to initialize the weights,
%                                   specified as 'glorot', 'he',
%                                   'narrow-normal', 'zeros', 'ones' or a
%                                   function handle. 
%                                   The default is 'glorot'.
%
%       'BiasInitializer'         - Function to initialize the bias,
%                                   specified as 'narrow-normal', 'zeros',
%                                   'ones' or a function handle.
%                                   The default is 'zeros'.
%
%       'Name'                    - Name for the layer. 
%                                   The default is ''.
%
%   Example:
%       % Create a 1-D transposed convolutional layer with 32 filters that have
%       % a length of 4, and that upsamples the input by a factor of 2.
%
%       layer = transposedConv1dLayer(4, 32, 'Cropping', 'same', ...
%           'Stride', 2);
%
%   See also nnet.cnn.layer.TransposedConvolution1DLayer, convolution1dLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2021-2023 The MathWorks, Inc.

% Parse the input arguments.

arguments
    filterSize                           {iAssertValidFilterSize}
    numFilters                           {iAssertValidNumFilters}
    NameValueArgs.Name                   {iAssertValidLayerName} = ''
    NameValueArgs.Stride                 {iAssertValidStride} = 1
    NameValueArgs.Cropping               {iAssertValidCropping} = 0
    NameValueArgs.NumChannels            {iAssertValidNumChannels} = 'auto'
    NameValueArgs.WeightLearnRateFactor  {iAssertValidFactor} = 1
    NameValueArgs.BiasLearnRateFactor    {iAssertValidFactor} = 1
    NameValueArgs.WeightsInitializer     = 'glorot'
    NameValueArgs.BiasInitializer        = 'zeros'
    NameValueArgs.WeightL2Factor         {iAssertValidFactor} = 1
    NameValueArgs.BiasL2Factor           {iAssertValidFactor} = 0
    NameValueArgs.Weights                = []
    NameValueArgs.Bias                   = []
end

% Store all input arguments in one struct
args = NameValueArgs;
args.FilterSize = filterSize;
args.NumFilters = numFilters;

% Gather arguments to CPU and convert them to canonical form
args = nnet.internal.cnn.layer.util.gatherParametersToCPU(args);
args = iConvertToCanonicalForm(args);

% Create an internal representation of a 1-D convolutional layer.
internalLayer = nnet.internal.cnn.layer.TransposedConvolution1D(args);

internalLayer.Weights.L2Factor = args.WeightL2Factor;
internalLayer.Weights.LearnRateFactor = args.WeightLearnRateFactor;

internalLayer.Bias.L2Factor = args.BiasL2Factor;
internalLayer.Bias.LearnRateFactor = args.BiasLearnRateFactor;

% Pass the internal layer to a function to construct a user visible
% convolutional layer.
layer = nnet.cnn.layer.TransposedConvolution1DLayer(internalLayer);
layer.WeightsInitializer = args.WeightsInitializer;
layer.BiasInitializer = args.BiasInitializer;
layer.Weights = args.Weights;
layer.Bias = args.Bias;
end

function inputArguments = iConvertToCanonicalForm(params)
% Make sure integral values are converted to double and strings to char vectors
inputArguments = struct;
inputArguments.FilterSize = iConvertToDouble( params.FilterSize );
inputArguments.NumFilters = iConvertToDouble( params.NumFilters );
inputArguments.Stride = iConvertToDouble( params.Stride );
inputArguments.CroppingMode = iCalculateCroppingMode(params.Cropping);
inputArguments.CroppingSize = iConvertToDouble( iCalculateCroppingSize(params.Cropping) );
inputArguments.NumChannels = iConvertToDouble( iConvertToEmptyIfAuto(params.NumChannels) );
inputArguments.WeightLearnRateFactor = params.WeightLearnRateFactor;
inputArguments.BiasLearnRateFactor = params.BiasLearnRateFactor;
inputArguments.WeightsInitializer = params.WeightsInitializer;
inputArguments.BiasInitializer = params.BiasInitializer;
inputArguments.WeightL2Factor = params.WeightL2Factor;
inputArguments.BiasL2Factor = params.BiasL2Factor;
inputArguments.Weights = params.Weights;
inputArguments.Bias = params.Bias;
inputArguments.Name = char(params.Name);
end

function iAssertValidFilterSize(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validateSizeParameter(value, 'FilterSize', dims);
end

function iAssertValidNumFilters(value)
validateattributes(value, {'numeric'}, ...
    {'scalar','integer','positive'});
end

function iAssertValidStride(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validateSizeParameter(value, 'Stride', dims);
end

function iAssertValidCropping(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validatePadding(value, dims, 'Cropping');
end

function iAssertValidNumChannels(value)
nnet.internal.cnn.layer.paramvalidation.validateNumChannels(value);
end

function iAssertValidFactor(value)
nnet.internal.cnn.layer.paramvalidation.validateLearnFactor(value);
end

function iAssertValidLayerName(name)
nnet.internal.cnn.layer.paramvalidation.validateLayerName(name)
end

function y = iConvertToEmptyIfAuto(x)
% We already validated that the user-supplied value for NumChannels is
% either numeric or a partial match of 'auto'
if (ischar(x) || isstring(x))
    y = [];
else
    y = x;
end
end

function croppingMode = iCalculateCroppingMode(value)
croppingMode = nnet.internal.cnn.layer.padding.calculatePaddingMode(value);
end

function croppingSize = iCalculateCroppingSize(value)
dims = 1;
croppingSize = nnet.internal.cnn.layer.padding.calculatePaddingSize(value, dims);
end

function value = iConvertToDouble(value)
value = nnet.internal.cnn.layer.util.convertToDouble(value);
end
