classdef (Abstract) RegressionLayer < nnet.cnn.layer.Layer
    % RegressionLayer   Interface for regression layers

    %   Copyright 2017-2019 The MathWorks, Inc.
    
    properties
        % Name (char vector)   A name for the layer
        Name = '';
        
        % ResponseNames (cellstr)   The names of the responses
        %   A cell array containing the names of the responses. Set by
        %   passing unique names as strings vector or cell vector of
        %   character vectors. Missing values will be skipped. During
        %   training, this property will be automatically set according to
        %   training data.
        ResponseNames = {};
    end
    
    properties (SetAccess = protected)
        % Description (char vector or scalar string)   A one line 
        %   description for the layer
        Description = ''
        
        % Type (char vector or scalar string)   The type of layer
        Type = ''
    end
    
    methods (Abstract)
        % forwardLoss    Return the loss between the output obtained from
        % the network and the expected output
        %
        % Inputs
        %   this - the output layer to forward the loss through
        %   Y    - Predictions made by network. If backwardLoss it not
        %          overridden, then Y is an unlabelled dlarray and all
        %          operations in forwardLoss must support dlarrays.
        %   T    - Targets (actual values)
        %
        % Outputs
        %   loss - the loss between Y and T
        loss = forwardLoss( this, Y, T)
    end
    
    methods
        % backwardLoss    Back propagate the derivative of the loss function
        % If backwardLoss is not overridden then automatic differentiation
        % is used to compute derivatives during training.
        %
        % Inputs
        %   this - the output layer to backprop the loss through
        %   Y    - Predictions made by network
        %   T    - Targets (actual values)
        %
        % Outputs
        %   dLdY - the derivative of the loss (L) with respect to the input Y
        function dLdY = backwardLoss( this, Y, T) %#ok<INUSD,STOUT>
            error(message('nnet_cnn:internal:cnn:layer:CustomOutputLayer:CustomlayerUnusedBackwardLoss', class(this)))
        end
    end
    
    methods(Hidden, Access = protected)
        function layer = RegressionLayer()
            try
                nnet.internal.cnn.layer.util.CustomLayerVerifier.validateMethodSignatures(layer);
            catch e
                % Wrap exception in a CNNException, which reports the error in a custom way
                err = nnet.internal.cnn.util.CNNException.hBuildCustomError( e );
                throwAsCaller(err);
            end
        end
        
        function [description, type] = getOneLineDisplay( layer )
            if strlength( layer.Description ) == 0
                description = iGetMessageString( 'nnet_cnn:layer:RegressionLayer:oneLineDisplay' );
            else
                description = layer.Description;
            end
            
            if strlength( layer.Type ) == 0
                type = iGetMessageString( 'nnet_cnn:layer:RegressionLayer:Type' );
            else
                type = layer.Type;
            end
        end
    end
    
    methods
        function layer = set.Name( layer, val )
            iAssertValidLayerName( val );
            layer.Name = convertStringsToChars( val );
        end
        
        function layer = set.ResponseNames( layer, val )
            iAssertValidResponseNames(val);
            val = iConvertResponseNamesToCanonicalForm(val);
            layer.ResponseNames = val;
        end
        
        function layer = set.Description( layer, val )
            val = iValidateText( val, "Description" );
            layer.Description = val;
        end
        
        function layer = set.Type( layer, val )
            val = iValidateText( val, "Type" );
            layer.Type = val;
        end
    end
end

function messageString = iGetMessageString( messageID )
messageString = getString( message( messageID ) );
end

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

function iAssertValidResponseNames(names)
iEvalAndThrow(@()...
     nnet.internal.cnn.layer.paramvalidation.validateResponseNames(names));
end

function names = iConvertResponseNamesToCanonicalForm(names)
names = nnet.internal.cnn.layer.paramvalidation...
    .convertResponseNamesToCanonicalForm(names);
end

function iEvalAndThrow(func)
% Omit the stack containing internal functions by throwing as caller
try
    func();
catch exception
    throwAsCaller(exception)
end
end

function paramValue = iValidateText( paramValue, paramName )
isValidText = nnet.internal.cnn.layer.paramvalidation.isValidStringOrCharArray(paramValue);
if isempty(paramValue)
    paramValue = '';
elseif ~isValidText
    error(message('nnet_cnn:layer:Layer:ParamMustBeStringOrChar',paramName))
end
end