classdef dataSet
    % dataSet creates a dataSet object.
    %
    % This class generates a dataSet object, which stores measured data.
    % Beneath the ability to store data, there are methods to
    % normalize/denormalize the data and to set a name for the whole
    % dataSet as well as for the various in- and outputs.
    %
    % PROPERTIES
    %
    % input:            (N x p)     Matrix of the inputs.
    % scaleInput        (class)     Scale input object
    %
    % output:           (N x q)     Matrix of the outputs.
    % scaleOutput       (class)     Scale output object. Empty for no
    %                               scaling. (Default = [])
    % dataWeighting     (N x 1)     Weighting of the inputs.
    % outputWeighting   (q x 1)     Weighting of the outputs.
    % info                          dataSetInfo object.
    %
    % validationInput:  (N x p)     Matrix of the validation data inputs.
    % validationOutput: (N x q)     Matrix of the validation data outputs.
    % testInput:        (N x p)     Matrix of the test data inputs.
    % testOutput:       (N x q)     Matrix of the test data outputs.
    %
    %
    % SYMBOLS AND ABBREVIATIONS:
    %
    % LM:  Local model
    %
    % p:   Number of inputs (physical inputs)
    % q:   Number of outputs
    % N:   Number of data samples
    % M:   Number of LMs
    % nx:  Number of regressors (x)
    % nz:  Number of regressors (z)
    %
    %
    % LMNtool - Local Model Network Toolbox
    % Tobias Ebert & Julian Belz, 15-March-2012
    % Institute of Mechanics & Automatic Control, University of Siegen, Germany
    % Copyright (c) 2012 by Prof. Dr.-Ing. Oliver Nelles
    
    % 2012-11-21: Attributes of the properties unscaledInput and 
    % unscaledOutput changed. Set-method of the input property changed. Now
    % the scaled and unscaled data is saved explicitly. (Julian Belz)
    %
    %

    properties (Hidden = true)
        inputScalingComplete = false;
        outputScalingComplete = false;
    end
    
    properties
        
        scaleInput = [];      % (class)    Class with information how to scale input data
        input                 % (N x p)     Matrix of the inputs.
        
        scaleOutput = []      % (class)    Class with information how to scale input data
        output                % (N x q)     Matrix of the outputs.
                
        dataWeighting = [];   % (N x 1)     Weighting of the inputs.
        outputWeighting = []; % (q x 1)     Weighting of the outputs.
        info = [];            %             dataSetInfo object.
        
        validationInput = []; % (N x p)     Matrix of the validation inputs.
        validationOutput = [];% (N x q)     Matrix of the validatino outputs.
        
        testInput = [];       % (N x p)     Matrix of the test data inputs.
        testOutput = [];      % (N x q)     Matrix of the test data outputs.
    end
    
    properties (SetAccess = private, GetAccess = public)
        unscaledInput
        unscaledOutput
    end
    
        

    
    
    methods
        
        saveDataSet(obj,matfile);
        obj = getZinputDelay(obj);
        obj = getXinputDelay(obj);
        obj = getXoutputDelay(obj)
        obj = getZoutputDelay(obj);
        
        %% Constructor
        function ds=dataSet(input,output,varargin)
            % Constructor of an dataSet object.
            %
            % ds = dataSet(input,output,dataWeighting,info)
            %
            %
            % INPUT
            %
            % input (optional):         (N x p) Matrix with the input data.
            % output (optional):        (N x q) Matrix with the output data.
            % dataWeighting (optional): (N x 1) Weighting of the inputs.
            % info (optional):                  dataSetInfo object
            %
            % OUTPUT
            %
            % ds:                               dataSet object
            
            % Set in- and output data
            if nargin == 0
                ds.input  = [];
                ds.output = [];
            elseif nargin >= 2
                ds.input    = input;
                ds.output   = output;
            elseif nargin == 1
                ds.input    = input;
                ds.output   = zeros(size(input,1),1);
            end
            
            % If passed, set the dataWeighting property and the info
            % property
            if ~isempty(varargin)
                ds.dataWeighting = varargin{1,1};
                if size(varargin,2) > 1
                    ds.info = varargin{1,2};
                end
            end
            
        end % end constructor
        
        % SET and GET methods, to prevent errors later
        
        %% get.dataWeighting
        function dW = get.dataWeighting(obj)
            % If no dataWeighting is defined previously, all data points
            % will be multiplied by one.
            if isempty(obj.dataWeighting) && ~isempty(obj.input)
                dW = ones(size(obj.input,1),1);
            elseif isempty(obj.dataWeighting) && isempty(obj.input) && ...
                    isempty(obj.output)
                dW = [];
            elseif ~isempty(obj.dataWeighting) && ...
                    size(obj.input,1) ~= size(obj.dataWeighting,1)
                dW = ones(size(obj.input,1),1);
            else
                dW = obj.dataWeighting;
            end
        end % end get.dataWeighting
        
        %% get.outputWeighting
        function dW = get.outputWeighting(obj)
            % If no outputWeighting is defined previously, all outputs
            % will be multiplied by one.
            if isempty(obj.outputWeighting) && ~isempty(obj.output)
                dW = ones(size(obj.output,2),1);
            elseif isempty(obj.outputWeighting) && isempty(obj.output) && ...
                    isempty(obj.output)
                dW = [];
            else
                dW = obj.outputWeighting;
            end
        end % end get.outputWeighting
        
        %% get.info
        function in = get.info(obj)
            if isempty(obj.info)
                % If the info property is empty, create a new dataSetInfo
                % object
                in      = dataSetInfo;
                if ~isempty(obj.input)
                    % If the input property is set already, the description
                    % of the inputs is set to u_1...u_p
                    tmp                  = cell(1,size(obj.input,2));
                    in.inputDescription  = obj.fillDescription(tmp,'u');
                end
                if ~isempty(obj.output)
                    % If the input property is set already, the description
                    % of the inputs is set to y_1...y_q
                    tmp                  = cell(1,size(obj.output,2));
                    in.outputDescription = obj.fillDescription(tmp,'y');
                end
            else
                % If the info property already contains an dataSetInfo
                % object, check if the number of inputs and the number of
                % input descriptions are equal. Otherwise overwrite the
                % existing descriptions with the standard description
                % (u_1...u_p). Outputs analogous!
                in = obj.info;
                if ~isempty(obj.input) && size(obj.input,2) ~= ...
                        size(obj.info.inputDescription,2)
                    tmp                  = cell(1,size(obj.input,2));
                    in.inputDescription  = obj.fillDescription(tmp,'u');
                end
                if ~isempty(obj.output) && size(obj.output,2) ~= ...
                        size(obj.info.outputDescription,2)
                    tmp                  = cell(1,size(obj.output,2));
                    in.outputDescription = obj.fillDescription(tmp,'y');
                end
            end
        end % end get.info
        
        %% set.input
        function obj = set.input(obj,in)
            if isfloat(in)
                % warn if NaN or INF values are present
                if any(any(isnan(in)))
                    warning(['Attention: There are NaN values in input ' num2str(find(any(isnan(in))))])
                end
                if any(any(isinf(in)))
                    warning(['Attention: There are inf values in input ' num2str(find(any(isinf(in))))])
                end
            else
                msgbox(['Wrong data type for setting the input ',...
                    'property!'],'Error','error');
            end
            
            if size(in,2) > size(in,1)
                warning('dataSet:setInput','The data has %.0f dimensions, but only %.0f data samples',size(in,2),size(in,1))
            end
            
            if ~isempty(in) && ~isempty(obj.scaleInput) && ~obj.inputScalingComplete
                % if there is data given and scaling is allowed
                %[obj.input, obj.scaleInput] = obj.scaleInput.scale(in);
                [scaledIn, scaling] = obj.scaleInput.scale(in);
                obj.scaleInput = scaling;
                obj.input = scaledIn;
                obj.inputScalingComplete = true; % remember that the input is already scaled (for save and load)
                if isprop(obj,'history') && isprop(obj.history,'displayMode')
                    displayMode = obj.history.displayMode;
                else
                    displayMode = true;
                end
                if displayMode; fprintf('\n\nInput scaling complete.\n'); end
            else
                obj.input = in;
            end
            
            obj.unscaledInput = in;
            
        end % end set.input
        
        %% set.output
        function obj = set.output(obj,out)
            if isfloat(out)
                % Daten muessen als Gleitkommazahlen vorliegen
                if any(any(isnan(out)))
                    warning(['Attention: There are NaN values in output ' num2str(find(any(isnan(out))))])
                end
                if any(any(isinf(out)))
                    warning(['Attention: There are inf values in output ' num2str(find(any(isinf(out))))])
                end
            else
                msgbox(['Wrong data type for setting the output ',...
                    'property!'],'Error','error');
            end
                        
            if size(out,2) > size(out,1)
                warning('dataSet:setOutput','The data has %.0f dimensions, but only %.0f data samples',size(out,2),size(out,1))
            end
            
            if ~isempty(out) && ~isempty(obj.scaleOutput) && ~obj.outputScalingComplete
                % if there is data given and scaling is allowed
                % [obj.output, obj.scaleOutput] = obj.scaleOutput.scale(out);
                [scaledOut, scaling] = obj.scaleOutput.scale(out);
                obj.scaleOutput = scaling;
                obj.output = scaledOut;
                obj.outputScalingComplete = true;
                if isprop(obj,'history') && isprop(obj.history,'displayMode')
                    displayMode = obj.history.displayMode;
                else
                    displayMode = true;
                end
                if displayMode; fprintf('\n\nOutput scaling complete.\n'); end
            else
                obj.output = out;
            end
            
            obj.unscaledOutput = out;
            
        end % end set.output
        
        %% get.unscaledInput
%         function unscaledInput = get.unscaledInput(obj)
%             if isempty(obj.input)
%                 unscaledInput = [];
%             elseif isempty(obj.scaleInput)
%                 unscaledInput = obj.input;
%             else
%                 unscaledInput = obj.scaleInput.unscale(obj.input);
%             end
%         end
%         
%         %% get.unscaledOutput
%         function unscaledOutput = get.unscaledOutput(obj)
%             if isempty(obj.scaleOutput)
%                 unscaledOutput = obj.output;
%             else
%                 unscaledOutput = obj.scaleOutput.unscale(obj.output);
%             end
%         end
        
        %% set scale input
        function obj = set.scaleInput(obj,scaling)
           if isempty(obj.input)
               obj.scaleInput = scaling;
           else
               warning('dataSet:scaleInput','input is not empty')
           end
        end
        
        %% set scale output
        function obj = set.scaleOutput(obj,scaling)
           if isempty(obj.output)
               obj.scaleOutput = scaling;
           else
               warning('dataSet:scaleOutput','output is not empty')
           end
        end
        
    end % end methods
    
    methods(Static=true, Hidden=true)
        ds          = loadDataSet(matfile);
        out         = fillDescription(in,bez)
    end
    
end

