function layer = embeddingConcatenationLayer(nameValueArgs)
% embeddingConcatenationLayer   Embedding concatenation layer
%
%   layer = embeddingConcatenationLayer() creates an embedding
%   concatenation layer that concatenates a learned embedding vector to its
%   input.
%
%   layer = embeddingConcatenationLayer(Name=Value)
%   specifies additional options using one or more name-value arguments:
%
%     Weights                  - Embedding vector, specified as a
%                                numInputChannels-by-1 vector or [], where
%                                numInputChannels is the number of channels
%                                in the layer input. 
%                                The default value is [].
%
%     WeightLearnRateFactor    - Multiplier for the learning rate for
%                                Weights.
%                                The default value is 1.
%
%     WeightL2Factor           - Multiplier for the L2 weight 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 an embedding concatenation layer.
%
%       layer = embeddingConcatenationLayer;
%
%   See also nnet.cnn.layer.EmbeddingConcatenationLayer,
%   patchEmbeddingLayer, positionEmbeddingLayer, selfAttentionLayer.

%   Copyright 2023 The MathWorks, Inc.

arguments
    nameValueArgs.Name                  {iAssertValidLayerName} = ''
    nameValueArgs.Weights               = []
    nameValueArgs.WeightLearnRateFactor {iAssertValidFactor} = 1
    nameValueArgs.WeightL2Factor        {iAssertValidFactor} = 1
    nameValueArgs.WeightsInitializer    {iAssertValidWeightsInitializer} = 'narrow-normal'
end

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

% Create the layer
layer = nnet.cnn.layer.EmbeddingConcatenationLayer(args.Name);

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

end

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

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

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

function inputArguments = iConvertToCanonicalForm(params)
% Make sure integer values are converted to double and strings to char
% vectors
inputArguments = struct;
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
