classdef(Sealed, SupportExtensionMethods=true, InferiorClasses={?gpuArray}) dlarray < ...
        matlab.mixin.internal.indexing.Paren & ...
        matlab.mixin.internal.indexing.ParenAssign & ...
        matlab.mixin.CustomDisplay
    % DLARRAY Deep learning array
    %   Y = DLARRAY(X) creates a dlarray object Y containing the input data
    %   X. X can be of data type double, single or logical. X can be a
    %   standard numeric array, a gpuArray, or a dlarray. If X is a
    %   dlarray, Y is a copy of X. Sparse or complex data are not
    %   supported.
    %
    %   Y = DLARRAY(X,DIMS) also takes a character vector or string scalar
    %   DIMS that specifies the data format of X and assigns a label to
    %   each dimension of the input data. Each character in DIMS must be
    %   one of the following labels
    %
    %       - 'S' - spatial
    %       - 'C' - channel
    %       - 'B' - batch (for example, samples and observations)
    %       - 'T' - time (for example, sequences)
    %       - 'U' - unspecified
    %
    %   Applying a data format aids in passing deep learning data between
    %   functions. If X is a formatted dlarray, then DIMS replaces the
    %   existing format. The default value of DIMS is the empty string. A
    %   dlarray can have multiple dimensions labeled 'S' or 'U'. Labels
    %   'C', 'B', and 'T' can be used at most once. Specify at least as
    %   many dimension labels as there are dimensions of the input data. If
    %   DIMS specifies more labels than the number of dimensions of the
    %   input data, a singleton dimension is created in the output dlarray
    %   Y for each entry in DIMS beyond the last dimension of the input
    %   data.
    %
    %   A dlarray accepts formatted data with no restriction on the order
    %   of the dimension labels. Both DIMS and the underlying data are
    %   reordered to match the following fixed order - all dimensions with
    %   label 'S', then dimensions with labels 'C', 'B', and 'T', then all
    %   dimensions with label 'U'.
    %
    %   When the input data is a vector and DIMS is a single character, the
    %   output dlarray is a vector with the first dimension labeled
    %   DIMS, and the second (singleton) dimension labeled 'U'.
    %
    %   DLARRAY methods and functions:
    %     Dimension labels and data manipulation
    %       DIMS           - Data format of a dlarray
    %       FINDDIM        - Find dimensions with a given label
    %       STRIPDIMS      - Strip data format from a dlarray
    %       EXTRACTDATA    - Extract data represented by dlarray
    %       GPUARRAY       - Convert underlying data to gpuArray
    %
    %     Deep learning operations
    %       AVGPOOL        - Pool data to average value
    %       BATCHNORM      - Normalize each channel of input data
    %       CROSSCHANNELNORM - Cross channel square-normalize using local responses
    %       CROSSENTROPY   - Categorical cross-entropy loss
    %       DLCONV         - Deep learning convolution
    %       DLTRANSPCONV   - Deep learning transposed convolution
    %       EMBED          - Embed discrete data
    %       FULLYCONNECT   - Sum all weighted input data and apply a bias
    %       GROUPNORM      - Normalize across groups of channels
    %       GRU            - Gated recurrent unit
    %       LEAKYRELU      - Apply leaky rectified linear unit activation
    %       LSTM           - Long short-term memory
    %       MAXPOOL        - Pool data to maximum value
    %       MAXUNPOOL      - Unpool the output of maxpool
    %       MSE            - Half mean squared error
    %       RELU           - Apply rectified linear unit activation
    %       SIGMOID        - Apply sigmoid activation
    %       SOFTMAX        - Apply softmax operation to input channel dimension
    %
    %     Automatic differentiation
    %       DLGRADIENT     - Compute gradient of dlarray object
    %
    %   For a complete list of dlarray functions and specific math
    %   operators behaviors, see <a href="matlab:helpview('deeplearning','DLARRAY_FUNCTIONS')">List of Functions with dlarray Support</a>
    %
    %   Example 1:
    %       % Create a dlarray for input data X with no format.
    %       X = randn(10,10,3,5);
    %       Z = dlarray(X);
    %
    %   Example 2:
    %       % Create a dlarray with random data containing two spatial
    %       % dimensions of size 10, one channel dimension of size 3, and
    %       % five observations.
    %       Z = dlarray(randn(10,10,3,5),'SSCB');
    %
    %   Example 3:
    %       % Create a dlarray with random data and no format and then a
    %       % copy of it with format 'BCSS'.
    %       Z1 = dlarray(randn(32,3,24,24));
    %       Z2 = dlarray(Z1,'BCSS');
    %       % Output: Z2 = 24(S) x 24(S) x 3(C) x 32(B) dlarray double
    %
    %   Example 4:
    %       % Create a vector dlarray from a vector of ones with six
    %       % elements and with format 'T'.
    %       Z = dlarray(ones(1,6),'T');
    %
    %   Example 5:
    %       % Create a 2-dimensional gpuArray with size 128.
    %       X = gpuArray(randn(128));
    %       % Define a dlarray containing the gpuArray data and assign
    %       % 'SSCB' format to it.
    %       Z = dlarray(X,'SSCB');
    %
    %   See also DLFEVAL, DLGRADIENT, DLNETWORK.
    
    %   Copyright 2018-2020 The MathWorks, Inc.
    
    properties(Access=private, Transient)
        FormattedData = deep.internal.format.FormattedData([])
        
        % The following properties always have the same length (unless they
        % are empty). Their length is equal to that of dims (unless it is
        % empty), and is >= ndims of the stored data.
        %
        % Usage: The names are chosen to be intuitive when translating a
        % single dimension index, which makes them counterintuitive when
        % used as input to permute.
        % Examples:
        % ViewToDataMap(viewDim) == dataDim
        % DataToViewMap(dataDim) == viewDim
        % permute(underlyingData, ViewToDataMap) == viewData
        % permute(viewData, DataToViewMap) == underlyingData
        ViewToDataMap = []
        DataToViewMap = []
    end
    
    properties(Constant, Hidden, Transient)
        OrderOfDims = 'SCBTU'
        ClassVersion = 1;
    end
    
    methods
        function obj = dlarray(data, fmt)
            
            if nargin<1
                data = [];
            end
            
            % Check data:
            if ~isa(data, 'dlarray')
                deep.internal.dlarray.validateNonDlarray(data);
            end
            
            % Check fmt:
            if nargin <= 1
                fmt = '';
            else
                deep.internal.dlarray.mustBeDataFormatType(fmt, true);
                fmt = char(fmt);
                deep.internal.format.validateDataFormat(fmt);
            end
            
            if isempty(fmt)
                if ~isa(data, 'dlarray')
                    % Unformatted dlarray
                    obj.FormattedData = deep.internal.format.FormattedData(data);
                else
                    % Copy constructor
                    obj = data;
                end
            else
                % Dimension label input
                if isscalar(fmt)
                    % Special-case for scalar input
                    if nnz(size(data) ~= 1) > 1
                        error(message('deep:dlarray:OneLabelVectorOnly'));
                    else
                        data = reshape(data, [], 1);
                        fmt = [fmt 'U'];
                    end
                elseif length(fmt) < ndims(data)
                    error(message('deep:dlarray:MoreDimsThanLabels'));
                end
                
                if ~isa(data, 'dlarray')
                    obj.FormattedData = deep.internal.format.FormattedData(data);
                else
                    obj = data;
                    
                    if length(fmt) < length(obj.DataToViewMap)
                        % Since we already verified that ndims(obj) <= length(fmt),
                        % this must mean there are singleton dimensions in the
                        % underlying data of obj which are trailing singleton dimensions
                        % in obj.
                        
                        % Move all trailing singleton dimensions of obj to
                        % also be trailing in the underlying data.
                        [obj.FormattedData.Data, obj.ViewToDataMap, obj.DataToViewMap] = ...
                            squeezedata(obj.FormattedData.Data, obj.ViewToDataMap);
                        
                        % Note: DimensionMetadata is now inconsistent, but
                        % is set to fmt later on.
                    end
                    
                    if length(fmt) > length(obj.DataToViewMap)
                        % Need to extend DataToViewMap and ViewToDataMap
                        obj.DataToViewMap = [obj.DataToViewMap, length(obj.DataToViewMap)+1:length(fmt)];
                        obj.ViewToDataMap = [obj.ViewToDataMap, length(obj.ViewToDataMap)+1:length(fmt)];
                    end
                end
                
                % Reorder dimensions:
                [fmt,perm] = deep.internal.format.orderFormat(fmt);
                
                % permute(obj, perm):
                if ~isequal(perm, 1:length(perm))
                    if isempty(obj.ViewToDataMap)
                        obj.ViewToDataMap = perm;
                    else
                        obj.ViewToDataMap = obj.ViewToDataMap(perm);
                    end
                    obj.DataToViewMap(obj.ViewToDataMap) = 1:length(obj.ViewToDataMap);
                end
                
                if isempty(obj.DataToViewMap)
                    obj.FormattedData.DimensionMetadata = fmt;
                else
                    obj.FormattedData.DimensionMetadata = fmt(obj.DataToViewMap);
                end
            end
            
            % Always drop trailing singleton dimensions with label 'U'.
            obj = dropTrailingODims(obj);
        end
    end
    
    methods(Hidden)
        obj = parenReference(obj,varargin)
        out = parenAssign(obj,rhs,varargin)
        
        savedata = saveobj(obj)
    end
    
    methods(Static, Hidden)
        obj = loadobj(savedata)
        obj = empty(varargin)
    end
    
    methods(Access=private)
        function tf = hasFormat(obj)
            tf = ~isempty(obj.FormattedData.DimensionMetadata);
        end
        
        function DataDim = getDataDim(obj, ViewDim)
            viewToData = obj.ViewToDataMap;
            if isempty(viewToData)
                DataDim = ViewDim;
            else
                DataDim = ViewDim;
                ind = ViewDim <= length(viewToData);
                DataDim(ind) = viewToData(ViewDim(ind));
            end
        end
        
        function ViewDim = getViewDim(obj, DataDim)
            dataToView = obj.DataToViewMap;
            if isempty(dataToView)
                ViewDim = DataDim;
            else
                ViewDim = DataDim;
                ind = DataDim <= length(dataToView);
                ViewDim(ind) = dataToView(DataDim(ind));
            end
        end
    end
    
    % Methods that support transforming a public dlarray into an internal
    % representation
    methods(Access=?deep.internal.format.FormattedData)
        function [obj, fd] = extractInternalData(obj)
            obj = permuteExplicitly(obj);
            fd = obj.FormattedData;
            obj.FormattedData = deep.internal.format.FormattedData([]);
        end
        
        function [obj, fd] = extractInternalDataWithLayout(obj)
            fd = obj.FormattedData;
            viewToData = obj.ViewToDataMap;
            
            % If the contained data is being viewed as a column vector with a trailing
            % U dimension, we want to drop that U in our new format representation.
            % Instead it will be represented by a "dot-label".
            fmt = fd.DimensionMetadata;
            if ~isempty(fmt) && isvector(fd.Data)
                if isempty(viewToData)
                    columnIndex = 2;
                else
                    columnIndex = viewToData(2);
                end  
                if fmt(columnIndex)=='U' && size(fd.Data, columnIndex)==1
                    fmt(columnIndex) = '.';
                end
            end
            
            % Change the FormattedData so that it only contains the new data layout
            % representation.
            fd.Layout = deep.internal.format.DataLayout(fmt, viewToData);
            fd.DimensionMetadata = '';
            
            obj.FormattedData = deep.internal.format.FormattedData([]);
        end
        
        function obj = restoreInternalData(obj, fd)
            
            layout = fd.Layout;
            if ~isempty(layout)
                % Convert the DataLayout representation into a storage format that dlarray
                % uses. Dlarray does not currently support unknown format labels in the
                % stored data.
                fd.Layout = [];
                
                if hasFormat(layout)
                    % Lock any non-trailing dot-labels into a permanent "U", since the user can
                    % now see that they exist.
                    layout = freezeViewFormat(layout);
                    
                    % Ensure that the new data's view is ordered correctly for dlarray
                    layout = orderViewFormat(layout);
                    
                    % The existing dlarray storage approach does not allow singleton unlabelled
                    % dimensions to be kept around.  We need to convert the data and storage layout
                    % information to the "minimal" view which has them at the end to satisfy dlarray.
                    dlarrayStorageLayout = minimalView(layout);
                    fd.Data = storageToView(dlarrayStorageLayout, fd.Data);
                    fd.DimensionMetadata = viewFormat(dlarrayStorageLayout);
                    
                    if ~isempty(dlarrayStorageLayout.Permutation)
                        % The dlarray permutation needs to be the vector which transforms between
                        % the dlarrayStorageLayout view format and the original layout view format.
                        % The easiest way to do that is to create a new DataLayout and ask it to do
                        % the transform.
                        conversionLayout = deep.internal.format.DataLayout(fd.DimensionMetadata, []);
                        conversionLayout = expandViewTo(conversionLayout, viewFormat(layout));
                        perm = conversionLayout.Permutation;
                    else
                        % The original layout order wasn't altered for storage in the dlarray, but
                        % trailing dot-label dimensions may have been dropped.  Any permutation
                        % entries for those trailing dimensions need to be trimmed away too.
                        perm = layout.Permutation;
                        if ~isempty(perm)
                            perm = perm(1:numel(fd.DimensionMetadata));
                        end
                    end
                else
                    % When there is no format we just accept the permutation as it is.
                    perm = layout.Permutation;
                end
                
                % Update current permutations in the dlarray
                obj.ViewToDataMap = perm;
                DtoV(obj.ViewToDataMap) = 1:length(obj.ViewToDataMap);
                obj.DataToViewMap = DtoV;
            end
            
            obj.FormattedData = fd;
            
            % Ensure that we have not been given trailing singleton U dimensions.
            obj = dropTrailingODims(obj);
        end
    end
    
    
    % Methods that support gradient calculations
    methods(Hidden)
        x = independent(x)
        function obj = record(obj)
            % Turn on call graph recording
            obj.FormattedData = record(obj.FormattedData);
        end
        
        function obj = stop(obj)
            % stop call graph recording
            obj.FormattedData = stop(obj.FormattedData);
        end
        
        function rec = getRecorded(obj)
            % Get the recorded tree if there is one
            rec = getRecorded(obj.FormattedData);
        end
        
        function [dataToView,viewToData] = getRecordingPermutations(obj)
            dataToView = obj.DataToViewMap;
            viewToData = obj.ViewToDataMap;
        end
    end
    
    % Display methods
    methods(Hidden, Access = protected)
        
        displayObject(obj);
        
        function displayNonScalarObject(obj)
            displayObject(obj);
        end
        
        function displayScalarObject(obj)
            displayObject(obj);
        end
        
        function displayEmptyObject(obj)
            displayObject(obj);
        end
    end
    
    % Standard array info/manipulation methods.  These are methods that
    % only provide inspection of the properties of the data and do not
    % return a new dlarray.
    %
    % All methods that need to alter the contained FormattedData need to
    % pass through a data access strategy so that we can later implement
    % interception, tracing, lazy evaluation etc.
    methods
        [varargout] = size(obj, viewdim, varargin)
        
        nd = ndims(obj)
        
        function n = numel(obj)
            n = numel(obj.FormattedData.Data);
        end
        
        function n = length(obj)
            n = length(obj.FormattedData.Data);
        end
        
        function empty = isempty(obj)
            empty = isempty(obj.FormattedData.Data);
        end
        
        idx = subsindex(obj)
        ind = end(obj,k,n)
    end
    
    % Methods that convert a dlarray to a string or char vector
    methods
        [str, errrmsg] = sprintf(varargin)
        str = compose(varargin)
        count = fprintf(varargin)
        str = num2str(varargin)
        str = int2str(obj)
        str = mat2str(varargin)
    end
    
    % Array size related methods
    methods
        function tf = ismatrix(obj)
            viewToData = obj.ViewToDataMap;
            data = obj.FormattedData.Data;
            if isempty(viewToData)
                tf = ismatrix(data);
            elseif length(viewToData) == 2
                tf = true;
            else
                tf = all(size(data, viewToData(3:end)) == 1);
            end
        end
        
        function tf = isscalar(obj)
            tf = isscalar(obj.FormattedData.Data);
        end
        
        function tf = isvector(obj)
            viewToData = obj.ViewToDataMap;
            data = obj.FormattedData.Data;
            if isempty(viewToData)
                tf = isvector(data);
            elseif length(viewToData) == 2
                tf = any(size(data) == 1);
            else
                sz = size(data, viewToData);
                tf = all(sz(3:end) == 1) && any(sz(1:2) == 1);
            end
        end
        
        function tf = isrow(obj)
            viewToData = obj.ViewToDataMap;
            data = obj.FormattedData.Data;
            if isempty(viewToData)
                tf = isrow(data);
            elseif length(viewToData) == 2
                tf = size(data, viewToData(1)) == 1;
            else
                sz = size(data, viewToData);
                tf = all(sz([1 3:end]) == 1);
            end
        end
        
        function tf = iscolumn(obj)
            viewToData = obj.ViewToDataMap;
            data = obj.FormattedData.Data;
            if isempty(viewToData)
                tf = iscolumn(data);
            elseif length(viewToData) == 2
                tf = size(data, viewToData(2)) == 1;
            else
                sz = size(data, viewToData);
                tf = all(sz([2 3:end]) == 1);
            end
        end
        
        result = cat(dim, varargin)
        result = horzcat(varargin)
        result = vertcat(varargin)
        
        obj = permute(obj, perm)
        obj = transpose(obj)
        obj = ctranspose(obj)
        
        obj = reshape(obj,varargin)
        obj = squeeze(obj)
        
        obj = interp1(varargin)
        
        %Provided by flow-through (or error if dimensions are formatted):
        %  * ipermute
        %  * shiftdim
        %  * circshift
        %  * repmat/repelem
        %  * flip/fliplr/flipud
        %  * rot90
    end
    
    methods(Access=private) % Helpers for standard math methods
        function fmt = datadims(obj)
            fmt = obj.FormattedData.DimensionMetadata;
        end
        
        function ind = finddatadim(obj, dim)
            ind = getDataDim(obj, finddim(obj, dim));
        end
        
        [fd, z, args] = helperReduction(obj, varargin)
        
        function z = nextpow2(z)
            % For use in rescale, private method not exposed.
            [z, fdz, zdata] = extractUnaryElwiseData(z);
            
            zdata = nextpow2(zdata);
            
            z = restoreUnaryElwiseData(z, fdz, zdata);
        end
    end
    
    % Standard math methods
    methods
        % Reduction operations:
        obj = sum(obj, varargin)
        obj = prod(obj, varargin)
        obj = mean(obj, varargin)
        obj = all(obj, varargin)
        obj = any(obj, varargin)
        
        % Binary operators:
        z = plus(x, y)
        z = minus(x, y)
        z = power(x, y)
        z = rdivide(x, y)
        z = times(x, y)
        z = atan2(x, y)
        
        % Element-wise methods
        z = abs(z)
        z = cosh(z)
        z = cos(z)
        z = cot(z)
        z = csc(z)
        z = exp(z)
        z = log(z)
        z = sec(z)
        z = sign(z)
        z = sinh(z)
        z = sin(z)
        z = sqrt(z)
        z = tanh(z)
        z = tan(z)
        z = uminus(z)
        z = uplus(z)
        z = floor(z)
        z = ceil(z)
        z = round(z, varargin)
        z = fix(z)
        z = eps(z)
        z = asin(z)
        z = acos(z)
        z = asec(z)
        z = acsc(z)
        z = atan(z)
        z = acot(z)
        z = asinh(z)
        z = acosh(z)
        z = atanh(z)
        
        % Others
        z = mtimes(x, y)
        z = dlmtimes(x, y)
        z = mrdivide(x, y)
        [z, ind] = min(x, y, varargin)
        [z, ind] = max(x, y, varargin)
        c = colon(a,d,b)
        a = rescale(a, varargin)
        a = onehotdecode(b, c, featdim, varargin)
        
        % Logical operators (and, not, or)
        z = and(x, y)
        z = or(x, y)
        z = not(z)
        z = xor(x, y)
        
        % Relational operators (lt, gt, le, ge, ne, eq)
        z = eq(x, y)
        z = ge(x, y)
        z = gt(x, y)
        z = le(x, y)
        z = lt(x, y)
        z = ne(x, y)
        
        % Comparison
        tf = isequal(x, y)
        tf = isequaln(x, y)
        tf = isnumeric(obj)
        tf = islogical(obj)
        tf = isfloat(obj)
        tf = isreal(obj)

        % Query special values
        tf = isnan(x)
        tf = isinf(x)
        tf = isfinite(x)
        
        % Conversion
        X = extractdata(obj)
        z = double(z)
        obj = single(obj)
        x = logical(x)
        obj = gpuArray(obj)
        varargout = gather(varargin)
    end
    
    methods(Hidden)
        x = zerosLike(obj, varargin)
        x = onesLike(obj, varargin)
        x = infLike(obj, varargin)
        x = nanLike(obj, varargin)
        x = trueLike(obj, varargin)
        x = falseLike(obj, varargin)
        x = randLike(obj, varargin)
        x = randnLike(obj, varargin)
        x = randiLike(obj, varargin)
    end
    
    % Deep learning functions
    methods
        [x,mean,variance] = batchnorm(x,offset,scale,varargin)
        x = groupnorm(x,numGroups,offset,scale,varargin)
        x = relu(x)
        X = dlconv(X,weights,bias,varargin)
        X = embed(X,weights,varargin)
        X = avgpool(X, poolSize, varargin)
        X = crossentropy(X, T, varargin)
        X = fullyconnect(X, W, B, varargin)
        X = mse(X, T, varargin)
        X = dltranspconv(X, W, B, varargin)
        X = leakyrelu(X, varargin)
        x = sigmoid(x)
        x = softmax(x, varargin)
        [X, indices, inputSize] = maxpool(X, poolSize, varargin)
        X = maxunpool(X, indices, outputSize, varargin)
        [X, h0, c0] = lstm(X, h0, c0, W, R, B, varargin)
        X = crosschannelnorm(X, channelWindowSize, varargin)
        [X, h0] = gru(X, h0, W, R, B, varargin)
    end
    
    % Auto-diff related methods
    methods
        varargout = dlgradient(y,varargin)
    end
    
end
