classdef (Hidden = true) piddata2 < ltipack.piddata
    % Class definition for @piddata2 for 2-dof PID data object
    % Subclasses: piddataS2 and piddataP2
    
    %   Author(s): B. Singh
    %   Copyright 2009-2015 The MathWorks, Inc.
    
    % Internal parameterization is {P I D T} in parallel form: [C2dof -C1dof]
    %       [P*b + I*(1/s) + D*c/(T+1/s) -(P + I*(1/s) + D/(T+1/s))]
    % Discretization means replace integrators above with their discrete
    % counterpart based on selected formula
    %       for 'ForwardEuler':     1/s --> Ts/(z-1)
    %       for 'BackwardEuler':    1/s --> Ts*z/(z-1)
    %       for 'Trapezoidal':      1/s --> Ts*(z+1)/2/(z-1)
    % I/O size is always 1x2 and delay structure is always 0
    
    properties
        b % setpoint weight for proportional part (real, positive, finite, scalar)
        c % setpoint weight for derivative part (real, positive, finite, scalar)
    end
    
    %% Public methods
    methods
        
        %% Characteristics
        function value = getType(this)
            % get PID type based on PID values
            value = getType@ltipack.piddata(this);
            % append type with "2" for 2-dof
            value = [value '2'];
        end
        
        function [ny,nu] = iosize(~)
            % i/o size is fixed as 1x2
            ny = 1; nu = 2;
            if nargout<2
                ny = [ny nu];
            end
        end
        
        %% Analysis
        function fr = evalfr(PID2,s)
            % EVALFR  Evaluates frequency response at a single (complex) frequency.
            [P,I,D,T,b,c] = utGetInternalParams(PID2);
            Ts = abs(PID2.Ts);
            % Compute frequency response of proportional, integral and
            % derivative parts
            [frP,frI,frD] = PID2.utGetFreqRespParts(P,I,D,T,Ts,PID2.IFormula,PID2.DFormula,s);
            % construct 2-dof PID frequency response
            fr = [frP*b + frI + frD*c -(frP + frI + frD)];
        end
        
        function [z,p,k] = iodynamics(PID2)
            % Compute zeros, poles and gain.
            [Num1,Num2,Pole1,Pole2] = getTF(PID2);
            Zeros1 = roots(Num1);
            Zeros2 = roots(Num2);
            z = {Zeros1,Zeros2};
            p = {Pole1,Pole2};
            k1 = Num1(end-length(Zeros1));
            k2 = Num2(end-length(Zeros2));
            k = [k1;k2];
        end
        
        function p = pole(PID2,varargin)
            % Compute poles based on Ts and discretization methods for each
            % channel
            
            % Poles of a 2-DOF PID controller are the same as its 1-dof
            % part
            [~,~,~,p] = getTF(PID2);
        end
        
        function [z,k] = zero(PID2)
            % Computes zeros of each IO channel
            [z,~,k] = iodynamics(PID2);
        end
        
        %% Indexing
        function D = getsubsys(D,rowIndex,colIndex,varargin)
            % Subsystem extraction: error or no-op
            if ~((ischar(rowIndex) || numel(rowIndex)==1) && (ischar(colIndex) || isequal(colIndex(:),[1 2]')))
                ctrlMsgUtils.error('Control:ltiobject:subsref9')
            end
        end
    end
    
    %% Protected methods (utilities)
    methods(Access=protected)
        % Compute num and poles from this PID
        function [Num1,Num2,Pole1,Pole2] = getTF(this)
            % convert to internal parameterization
            [P,I,D,T,b,c] = utGetInternalParams(this);
            % get Ts
            Ts = abs(this.Ts);
            % Pole1 and Pole2 can be different based on value on "d == 0"
            [Num1,Pole1] = this.utGetTFfromPIDT(P*b,I,D*c,T,Ts,this.IFormula,this.DFormula);
            [Num2,Pole2] = this.utGetTFfromPIDT(P,I,D,T,Ts,this.IFormula,this.DFormula);
            % y --> u transfer function for 2-dof PID is equal to -C1dof
            Num2 = -Num2;
        end
        
    end
    %% Static methods
    methods(Static)
        [Kp,Ki,Kd,Tf,b,c] = convert(Num,Poles,Ts,IFormula,DFormula,STDFlag)
    end
end

