function X = internal_dltranspconv(X, W, B, croppingSize, stride, dilation, numGroups, outputSizeOffset)
% This function is undocumented and will change in a future release.

% Applies d-dimensional transposed convolution on the CPU. 
%
% Inputs:
%   X           = N1 x N2 x...x Nd x Cin  x N array.
%   W           = M1 x M2 x...x Md x Cout X Cin array. Cout = C/G, G is the
%               number of groups and G must divide C and Cin.
%   B           = 1  x 1  x...x 1  x  Cout array.
%   croppingSize = [L1,H1,...,Ld,Hd] where Li and Hi are the start and end
%                 cropping sizes in the ith dimension.
%   stride      = [s1,...,sd] where si is stride in the ith dimension.
%   dilation    = [c1,...,cd] where ci is dilation in the ith dimension.
%   numGroups   = number of groups G.
%   outputSizeOffset = [o1,...,od] where oi is the offset in the ith
%                      dimension.
%
% Outputs:
%   Z           = O1 x O2 x...x Od x Cout x N array.

%   Copyright 2019 The MathWorks, Inc.

% Ensure all inputs have the same data type
[X, W, B] = nnet.internal.cnnhost.castToSuperiorFloat(X, W, B);

[croppingStart, croppingEnd] = iConvertCroppingToBuiltinFormat(croppingSize);

outputSize = iComputeOutputSize(X, W, croppingStart, croppingEnd, stride, dilation, outputSizeOffset);

% Format filter grouping information for the builtin
numFilters = size(W, numel(stride)+2);
filterGrouping = repmat(numFilters/numGroups, 1, numGroups);

% Call the backward convolution builtin method with X in place of dZ
X = builtin('_batchconvBackwardData', outputSize, X, W, ...
    [croppingStart croppingEnd], stride, dilation, filterGrouping);
X = X + B;

end

function [croppingStart, croppingEnd] = iConvertCroppingToBuiltinFormat(croppingSize)
n = numel(croppingSize);
croppingStart = croppingSize(1:2:n);
croppingEnd = croppingSize(2:2:n);
end

function outputSize = iComputeOutputSize(X, W, croppingStart, croppingEnd, stride, dilation, outputSizeOffset)
% This function computes the output size of the tranposed convolution layer
% for N dimensions. The output is a vector containing [O1 x O2 x...x Od]
% where Oi is the output size of the i-th dimenstion.

d = numel(stride);
sizeX = size(X, 1:d);
sizeW = size(W, 1:d);

outputSize = stride.*(sizeX-1) + sizeW.*dilation - (dilation-1) - (croppingStart+croppingEnd) + outputSizeOffset;
end
