classdef (Sealed) NARXScriptGenerator < nnet.genscript.ScriptGenerator
    % NARXSCRIPTGENERATOR - This class is used to generate training scripts
    % for NARX networks in "ntstool" when clicking the "Simple Script" or
    % "Advanced Script" buttons on the "Save Results" page.
    
    % Copyright 2014 The MathWorks, Inc.
    
    properties(Access = private)
        InputName
        FeedbackName
        TimestepInCell
        SampleByColumn
        TrainPercent
        ValidatePercent
        TestPercent
        HiddenLayerSize
        NumDelayLines
        PredictAhead
        TrainingFunction
        PerformFunction
    end
    
    properties(Access = private, Constant)
        AdvancedCodeForProcessFunctionsForNARXNetwork = {
            '% Choose Input and Feedback Pre/Post-Processing Functions', ...
            '% Settings for feedback input are automatically applied to feedback output', ...
            '% For a list of all processing functions type: help nnprocess', ...
            '% Customize input parameters at: net.inputs{i}.processParam', ...
            '% Customize output parameters at: net.outputs{i}.processParam', ...
            'net.inputs{1}.processFcns = {''removeconstantrows'',''mapminmax''};', ...
            'net.inputs{2}.processFcns = {''removeconstantrows'',''mapminmax''};', ...
            ''
            };
        AdvancedCodeForMultiStepPredictionForNARXNetwork = {
            '% Multi-step Prediction', ...
            '% Sometimes it is useful to simulate a network in open-loop form for as', ...
            '% long as there is known output data, and then switch to closed-loop form', ...
            '% to perform multistep prediction while providing only the external input.', ...
            '% Here all but 5 timesteps of the input series and target series are used', ...
            '% to simulate the network in open-loop form, taking advantage of the higher', ...
            '% accuracy that providing the target series produces:', ...
            'numTimesteps = size(x,2);', ...
            'knownOutputTimesteps = 1:(numTimesteps-5);', ...
            'predictOutputTimesteps = (numTimesteps-4):numTimesteps;', ...
            'X1 = X(:,knownOutputTimesteps);', ...
            'T1 = T(:,knownOutputTimesteps);', ...
            '[x1,xio,aio] = preparets(net,X1,{},T1);', ...
            '[y1,xfo,afo] = net(x1,xio,aio);', ...
            '% Next the the network and its final states will be converted to', ...
            '% closed-loop form to make five predictions with only the five inputs', ...
            '% provided.', ...
            'x2 = X(1,predictOutputTimesteps);', ...
            '[netc,xic,aic] = closeloop(net,xfo,afo);', ...
            '[y2,xfc,afc] = netc(x2,xic,aic);', ...
            'multiStepPerformance = perform(net,T(1,predictOutputTimesteps),y2)', ...
            '% Alternate predictions can be made for different values of x2, or further', ...
            '% predictions can be made by continuing simulation with additional external', ...
            '% inputs and the last closed-loop states xfc and afc.', ...
            ''
            };
        CodeForMatrixOnlyDeploymentForNARXNetwork = {
            'if (false)', ...
            '  % Generate a matrix-only MATLAB function for neural network code', ...
            '  % generation with MATLAB Coder tools.', ...
            '  genFunction(net,''myNeuralNetworkFunction'',''MatrixOnly'',''yes'');', ...
            '  x1 = cell2mat(x(1,:));', ...
            '  x2 = cell2mat(x(2,:));', ...
            '  xi1 = cell2mat(xi(1,:));', ...
            '  xi2 = cell2mat(xi(2,:));', ...
            '  y = myNeuralNetworkFunction(x1,x2,xi1,xi2);', ...
            'end'
            };
    end
    
    properties(Access = private, Dependent)
        CodeForInputDataForNARXNetwork
        CodeForCreationOfNARXNetwork
        AdvancedCodeForDeploymentForNARXNetwork
    end
    
    methods
        function this = NARXScriptGenerator( state )
            this.InputName = state.inputName;
            this.FeedbackName = state.feedbackName;
            this.TimestepInCell = state.timeInCell;
            this.SampleByColumn = state.sampleByColumn;
            this.TrainPercent = state.net2.divideParam.trainRatio*100;
            this.ValidatePercent = state.net2.divideParam.valRatio*100;
            this.TestPercent = state.net2.divideParam.testRatio*100;
            this.HiddenLayerSize = state.layerSize;
            this.NumDelayLines = state.delaySize;
            this.PredictAhead = (state.net2.numFeedbackDelays > 0);
            this.TrainingFunction = state.net2.trainFcn;
            this.PerformFunction = state.net2.performFcn;
        end
        
        function code = generateSimpleScript(this)
            code = [
                this.CodeForInputDataForNARXNetwork, ...
                this.generateCodeForTrainingFunction(this.TrainingFunction), ...
                this.CodeForCreationOfNARXNetwork, ...
                this.generateCodeForDataPreparation('X,{}'), ...
                this.generateCodeForDataDivision(this.TrainPercent, this.TestPercent, this.ValidatePercent, 'time', false), ...
                this.CodeForTrainingAndTestingDynamicNetwork, ...
                this.CodeForViewingNetwork, ...
                this.CommentForPlots, ...
                this.CodeForPlotsForDynamicNetwork, ...
                this.generateCodeForClosedLoop('X'), ...
                this.generateCodeForStepAheadPrediction('y', 'X,{}')
                ];
        end
        
        function code = generateAdvancedScript(this)
            code = [
                this.CodeForInputDataForNARXNetwork, ...
                this.generateCodeForTrainingFunction(this.TrainingFunction), ...
                this.CodeForCreationOfNARXNetwork, ...
                this.AdvancedCodeForProcessFunctionsForNARXNetwork, ...
                this.generateCodeForDataPreparation('X,{}'), ...
                this.generateCodeForDataDivision(this.TrainPercent, this.TestPercent, this.ValidatePercent, 'time', true), ...
                this.generateAdvancedCodeForTrainingDynamicNetwork(this.PerformFunction), ...
                this.CodeForTrainingAndTestingDynamicNetwork, ...
                this.AdvancedCodeForRecalculatedPerformanceForDynamicNetwork, ...
                this.CodeForViewingNetwork, ...
                this.CommentForPlots, ...
                this.CodeForPlotsForDynamicNetwork, ...
                this.generateCodeForClosedLoop('X'), ...
                this.AdvancedCodeForMultiStepPredictionForNARXNetwork, ...
                this.generateCodeForStepAheadPrediction('y', 'X,{}'), ...
                this.AdvancedCodeForDeploymentForNARXNetwork;
                ];
        end
        
        function code = get.CodeForInputDataForNARXNetwork(this)
            code = [
                {
                '% Solve an Autoregression Problem with External Input with a NARX Neural Network', ...
                '% Script generated by Neural Time Series app', ...
                this.generateCommentForDateOfCreation(), ...
                '%', ...
                '% This script assumes these variables are defined:', ...
                '%', ...
                ['%   ' this.InputName ' - input time series.'], ...
                ['%   ' this.FeedbackName ' - feedback time series.'], ...
                ''
                }, ...
                this.generateCodeForDataDefinitionForNARXOrTimeDelayNetwork(this.SampleByColumn, ...
                    this.TimestepInCell, this.InputName, this.FeedbackName), ...
                {''}
                ];
        end
        
        function code = get.CodeForCreationOfNARXNetwork(this)
            startDelay = double(~this.PredictAhead);
            endDelay = startDelay + (this.NumDelayLines-1);
            code = {
                '% Create a Nonlinear Autoregressive Network with External Input', ...
                ['inputDelays = ' mat2str(startDelay) ':' mat2str(endDelay) ';'], ...
                ['feedbackDelays = ' mat2str(startDelay) ':' mat2str(endDelay) ';'], ...
                ['hiddenLayerSize = ' mat2str(this.HiddenLayerSize) ';'], ...
                'net = narxnet(inputDelays,feedbackDelays,hiddenLayerSize,''open'',trainFcn);', ...
                ''
                };
        end
        
        function code = get.AdvancedCodeForDeploymentForNARXNetwork(this)
            code = [
                this.generateCodeForDeployment('x,xi,ai'), ...
                this.CodeForMatrixOnlyDeploymentForNARXNetwork, ...
                this.CodeForSimulink
                ];
        end
    end
end