classdef tf < pmodel.Generic
   % SISO transfer function structure.
   
   %   Author(s): P. Gahinet
   %   Copyright 1986-2018 The MathWorks, Inc.

   methods (Static)
      function D = array(size)
         % Create a ssdata array of a given size
         D = pmodel.tf.newarray(size);
      end
      
      function M = createMIMO(num, den, iodelay)
         % Create an array of TF objects to represent a MIMO transfer
         % function. Free NaN IO delays.
         
         if ~iscell(num)
            num = {num}; den = {den};
         end
         M = pmodel.tf.newarray(size(num));
         
         if ~isequal(size(num),size(den))
            ctrlMsgUtils.error('Control:ltiobject:tfProperties2')
         end
         
         if nargin<3
            for ct = 1:numel(num)
               M(ct) = pmodel.tf(num{ct},den{ct});
            end
         else
            for ct = 1:numel(num)
               M(ct) = pmodel.tf(num{ct},den{ct},iodelay(ct));
            end
         end
      end
   end
   
   properties
      % Numerator vector (row vector of parameters).
      %
      % Use this property to read the current value of the vector of numerator
      % coefficients or to initialize, fix, or free specific coefficients in
      % the numerator.
      Numerator
      % Denominator vector (row vector of parameters).
      %
      % Use this property to read the current value of the vector of denominator
      % coefficients or to initialize, fix, or free specific coefficients in
      % the denominator.
      Denominator
      % Input-output delay (scalar). 
      %
      % Use IODelay to specify delay's parameterization (value and constraints) 
      % in applicable time units (continuous-time) or number of samples 
      % (discrete-time).
      IODelay
   end
   
   % Original property names, now aliased
   properties (Dependent, Hidden)
      num
      den
      ioDelay
   end
   
   methods (Access = protected)
      
      % PARAMETER SERIALIZATION INTERFACE (PMODEL.GENERIC)
      function ps = getParamSet(M)
         ps = [M.Numerator ; M.Denominator ; M.IODelay];
      end
      
      function M = setParamSet(M,ps)
         M.Numerator = ps(1);  M.Denominator = ps(2);
         if numel(ps)>2, M.IODelay = ps(3); end
      end
      
      function dispScalarModel(M)
         % Display of a scalar tf object.
         S = struct('Numerator',M.Numerator,'Denominator',M.Denominator,'IODelay',M.IODelay);
         disp(S)
      end
      
      function dispModelType(~,Size)
         % Display model type information.
         if prod(Size)==1
            disp(ctrlMsgUtils.message('Control:pmodel:msgTFDisp'))
         else
            disp(ctrlMsgUtils.message('Control:pmodel:msgTFDispArray',mat2str(Size)))
         end
      end
   end
   
   % PUBLIC METHODS
   methods      
      function M = tf(num,den,iodelay)
         % Constructs pmodel.tf instance
         % M = tf(num, den)
         % M = tf(num, den, ioDelay)
         % Note:
         %  The leading denominator coefficient is fixed to 1.
         %  ioDelay is inactive ([]) if not specified. If specified:
         %  - The minimum ioDelay is always >=0.
         %  - The delay value is fixed unless value is NaN.
         if nargin==0
            return
         end
         % Normalize leading denominator coefficient
         d1 = den(1);
         if d1~=1
            den = den/d1;  num = num/d1;
         end
         M.Numerator = param.Continuous('Numerator',num);
         M.Denominator = param.Continuous('Denominator',den);
         M.Denominator.Free(1) = false;
         
         if nargin>2
            M.IODelay = param.Continuous('IODelay',iodelay);
            M.IODelay.Minimum = 0;
            M.IODelay.Free = isnan(iodelay);
         end
      end
      
      function Value = get.num(M)
         Value = M.Numerator;
      end
      
      function Value = get.den(M)
         Value = M.Denominator;
      end
      
      function Value = get.ioDelay(M)
         %GET method for "IODelay" property
         Value = M.IODelay;
      end
      
      function M = set.num(M, Value)
         %SET method for "Numerator" property
         M.Numerator = Value;
      end
      
      function M = set.den(M, Value)
         %SET method for "Denominator" property
         M.Denominator = Value;
      end
      
      function M = set.ioDelay(M, Value)
         %SET method for "IODelay" property
         M.IODelay = Value;
      end
      
      function varargout = order(M, Name)
         % Determine polynomial orders.
         %
         % Syntax:
         %  [numord, denord] = order(M)
         %  ord = order(M, 'num')
         %  ord = order(M, 'den')
         
         if nargin==1
            varargout{1} = length(M.Numerator.Value)-1;
            if nargout>1, varargout{2} = length(M.Denominator.Value)-1; end
         else
            varargout{1} = length(M.(Name).Value)-1;
         end
      end
      
      function [M, K] = scaleTime(M,sf,Ts)
         % Time vector scaling tnew = sf * told
         K = [];
         if Ts==0
            % Reflect change s_new = s_old/sf (a_n -> a_n sf^n)
            num = M.Numerator;  den = M.Denominator; %#ok<*PROPLC>
            lnum = length(num.Value); lden = length(den.Value);
            lmax = max(lnum,lden);
            powSF = cumprod([1 sf(:,ones(lmax-1,1))]);
            K_ = 1./powSF;
            
            NumScale = K_(end-lnum+1:end);
            DenScale = K_(end-lden+1:end);
            if DenScale(1)~=1
               NumScale = NumScale/DenScale(1);
               DenScale = DenScale/DenScale(1);
            end
            M.Numerator = pmodel.scaleParameter(num, NumScale);
            M.Denominator = pmodel.scaleParameter(den, DenScale);
            
            K = [K, NumScale, DenScale];
            if ~isempty(M.IODelay)
               M.IODelay = pmodel.scaleParameter(M.IODelay,sf); 
               K = [K, sf];
            end
         end
      end
   end
   
   methods (Hidden)
      function s = iosize(~,~)
         % I/O sizes
         s = [1 1];
         if nargin>1
            s = 1;
         end
      end
      
      function M = checkDataType(M, Ts)
         % Validates data type of num, den and ioDelay parameters.
         if numel(M)==0, return, end
         M.Numerator = localCheckParameter(M.Numerator,'Numerator');
         M.Denominator = localCheckParameter(M.Denominator,'Denominator');
         M.IODelay = localCheckParameter(M.IODelay,'IODelay',Ts);
      end
      
      function M = utRemoveZeroPad(M, Ts)
         % Remove extra zeros from numerator and denominator.
         % If Ts==0, leading zeros from numerator are removed.
         % If Ts~=0, trailing zeros from numerator and denominator are
         % removed. Note that in the discrete-time case, the numerator and
         % the denominator polynomials are expressed in z^-1, so that the
         % trailing zeros are superfluous.
         
         num = M.Numerator;
         lnum = length(num.Value);
         if Ts==0
            % Remove leading zeros from the numerator.
            Ind = find(num.Value,1,'first');
            if isempty(Ind)
               % If numerator is all zero, leave only one.
               Ind = lnum;
            end
            if Ind>1
               num = subsrefParameter(num,Ind:lnum);
               M.Numerator = num;
            end
            %RE: Denominator is monic.
         else
            % Remove trailing zeros from numerator and denominator            
            den = M.Denominator;
            
            lden = length(den.Value);
            Ind1 = find(num.Value,1,'last');
            if isempty(Ind1)
               % If numerator is all zero, leave only one.
               Ind1 = min(1,lnum);
            end
            Ind2 = find(den.Value,1,'last');
            if Ind1<lnum
               num = subsrefParameter(num,1:Ind1);
               M.Numerator = num;
            end
            if Ind2<lden
               den = subsrefParameter(den,1:Ind2);
               M.Denominator = den;
            end
         end
      end
      
      function M = fixLeadingZeros(M, nz)
         % Fix leading zeros entries of numerators. If num is all zero, do
         % not fix it.
         
         % If nz (scalar) is provided as an input argument, only nz leading
         % zeros are fixed. Then, M.Numerator must contain nz or more leading
         % zeros.         
         CustomNz = nargin>1;  
         num = M.Numerator;
         if CustomNz
            num.Free(1:nz) = false;
         else
            Ind = find(num.Value,1,'first');
            if ~isempty(Ind)
               num.Free(1:Ind-1) = false;
            end
         end
         M.Numerator = num;
      end
      
   end
   
   methods (Static, Hidden)
      
      function blk = loadobj(s)
         % Load filter for pmodel.tf objects
         if strcmp(s.Numerator.Name,'num')
            s.Numerator = renameParam(s.Numerator,'Numerator');
            s.Denominator = renameParam(s.Denominator,'Denominator');
            if ~isempty(s.IODelay)
               s.IODelay = renameParam(s.IODelay,'IODelay');
            end
         end
         blk = s;
      end
      
   end

end

%--------------------------------------------------------------------------
function p = localCheckParameter(p,Name,Ts)
% Check num/den values.

% Initial checks:
% * Check p has the correct name
% * Make p.Value double and full, if necessary
p = pmodel.checkParameter(p,Name);
Val = p.Value;

if Name(1)~='I' % num or den
    if ~isnumeric(Val) || ~isvector(Val) || any(isinf(Val))
        error(message('Control:pmodel:finitevecrequired',Name))
    end
    % Ensure value is a row vector
    if ~isrow(Val)
        Val = Val(:).';
        p.Value = Val;
    end
    if Name(1)=='D' % denominator
        if p.Free(1) || p.Value(1) ~= 1
            error(message('Control:pmodel:monicDen'))
        end
    end
else % IO delay
    [Val, isModified] = pmodel.checkIODelay(Val, Ts);
    if isModified
        p.Value = Val;
    end
    % ioDelay bounds >=0
    X = max(p.Minimum, 0);
    if ~isequal(p.Minimum, X)
        p.Minimum = X; 
    end
    X = max(p.Maximum, 0);
    if ~isequal(p.Maximum, X)
        p.Maximum = X;
    end
end
end
