function layer = positionEmbeddingLayer(outputSize, maxPosition, nameValueArgs)
% positionEmbeddingLayer   Position embedding layer
%
%   
%   layer = positionEmbeddingLayer(outputSize, maxPosition) creates a
%   position embedding layer that maps sequence or spatial index to
%   vectors. outputSize specifies the number of channels in the layer
%   output. maxPosition specifies the maximum sequence length or spatial
%   index in the layer input.
%
%   layer = positionEmbeddingLayer(outputSize, maxPosition, Name=Value)
%   specifies additional options using one or more name-value arguments:
%
%     PositionDimension        - Dimension of positions to embed, specified
%                                as one of the following:
%                                "auto"     - For sequence or
%                                             spatial-temporal input, embed
%                                             the temporal positions. For
%                                             one-dimensional image input,
%                                             embed the spatial positions.
%                                "temporal" - Embed the temporal positions.
%                                "spatial"  - Embed the spatial positions.
%                                The default value is "auto".
%
%     Weights                  - Weights, specified as an
%                                outputSize-by-maxPosition matrix or [].
%                                The default value is [].
%
%     WeightLearnRateFactor    - Multiplier for the learning rate for
%                                Weights.
%                                The default value is 1.
%
%     WeightL2Factor           - Multiplier for the L2 regularizer for
%                                Weights.
%                                The default value is 1.
%
%     WeightsInitializer       - Function to initialize Weights, specified
%                                as one of the following:
%                                "glorot"        - Use Glorot initializer.
%                                "he",           - Use He initializer.
%                                "orthogonal"    - Use Orthogonal
%                                                  initializer.
%                                "narrow-normal" - Use Narrow-normal
%                                                  initializer.
%                                "zeros"         - Initialize with zeros.
%                                "ones"          - Initialize with ones.
%                                function handle - Initialize using a
%                                                  function handle.
%                                The default value is "narrow-normal".
%
%     Name                     - Name for the layer.
%                                The default value is "".
%   Example:
%       % Create a position embedding layer with an output size of 256 and
%       % maximum position value of 512.
%
%       outputSize = 256;
%       maxPosition = 512;
%       layer = positionEmbeddingLayer(outputSize, maxPosition);
%
%   See also nnet.cnn.layer.PositionEmbeddingLayer, selfAttentionLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2023 The MathWorks, Inc.

arguments
    outputSize (1,1) {mustBeNumeric, mustBeInteger, mustBeNonempty, mustBePositive}
    maxPosition (1,1) {mustBeNumeric, mustBeInteger, mustBeNonempty, mustBePositive}
    nameValueArgs.PositionDimension     {mustBeTextScalar} = 'auto'
    nameValueArgs.Name                  {iAssertValidLayerName} = ''
    nameValueArgs.Weights               = []
    nameValueArgs.WeightLearnRateFactor {iAssertValidFactor} = 1
    nameValueArgs.WeightsInitializer    {iAssertValidWeightsInitializer} = 'narrow-normal'
    nameValueArgs.WeightL2Factor        {iAssertValidFactor} = 1
end

% Gather arguments to CPU and convert them to canonical form
args = nameValueArgs;
args.OutputSize = outputSize;
args.MaxPosition = maxPosition;
args.PositionDimension = iValidatePositionDimension(args.PositionDimension);
args = nnet.internal.cnn.layer.util.gatherParametersToCPU(args);
args = iConvertToCanonicalForm(args);

% Construct the layer
layer = nnet.cnn.layer.PositionEmbeddingLayer(args.Name, args.OutputSize, ...
    args.MaxPosition, args.PositionDimension);

layer.Weights = args.Weights;
layer.WeightsInitializer = args.WeightsInitializer;
layer.WeightLearnRateFactor = args.WeightLearnRateFactor;
layer.WeightL2Factor = args.WeightL2Factor;
end

function value = iValidatePositionDimension(value)
try
    value = validatestring(value, {'auto', 'spatial', 'temporal'});
catch exception
    msg = ['Invalid value for PositionDimension argument. ' exception.message];
    exception = MException(exception.identifier, msg);
    throwAsCaller(exception)
end
end

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

function iAssertValidWeightsInitializer(value)
nnet.internal.cnn.layer.paramvalidation.validateWeightsInitializer(value, 'WeightsInitializer');
end

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

function inputArguments = iConvertToCanonicalForm(params)
% Make sure integer values are converted to double and strings to char
% vectors
inputArguments = struct;
inputArguments.OutputSize = iConvertToDouble(params.OutputSize);
inputArguments.MaxPosition = iConvertToDouble(params.MaxPosition);
inputArguments.PositionDimension = params.PositionDimension;
inputArguments.WeightLearnRateFactor = iConvertToDouble(params.WeightLearnRateFactor);
inputArguments.WeightL2Factor = iConvertToDouble(params.WeightL2Factor);
inputArguments.WeightsInitializer = params.WeightsInitializer;
inputArguments.Name = char(params.Name);
inputArguments.Weights = params.Weights;
end

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