classdef ss < pmodel.Generic
   % Parameterization of linear state-space models.
   %
   %   The PMODEL.SS class parameterizes state-space models of the form
   %       dx/dt = Ax + Bu + Ke
   %         y   = Cx + Dy + e
   %
   %   M = pmodel.ss(A,B,C,D,K) returns a parametric state-space model M
   %   where the model parameters are initialized to the values A,B,C,D,K.
   %
   %   M = pmodel.ss(A,B,C,D) constructs a parametric state-space model M
   %   without noise input e. This is equivalent to setting M.K to empty.
   %
   %   See also PMODEL.TF, PMODEL.PID
   
   
   %   Author(s): P. Gahinet, Rajiv Singh
   %   Copyright 2009-2015 The MathWorks, Inc.
   
   properties
      % A matrix (matrix-valued parameter).
      %
      % Use this property to read the current value of the state matrix A,
      % to initialize A, or to fix/free specific entries of A.
      A
      % B matrix (matrix-valued parameter).
      %
      % Use this property to read the current value of the input-to-state
      % matrix B, to initialize B, or to fix/free specific entries of B.
      B
      % C matrix (matrix-valued parameter).
      %
      % Use this property to read the current value of the state-to-output
      % matrix C, to initialize C, or to fix/free specific entries of C.
      C
      % D matrix (matrix-valued parameter).
      %
      % Use this property to read the current value of the feedthrough
      % matrix D, to initialize D, or to fix/free specific entries of D.
      % For example, you can fix D to zero by typing
      %    M.D.Value = 0;  M.D.Free = false;
      D
      % K matrix (matrix-valued parameter).
      %
      % Use this property to read the current value of K, initialize K,
      % or to fix/free specific entries of K. You can also set K to []
      % to eliminate the noise input e.
      K
      % State names (string vector, default = 0x1 string vector).
      %
      % You can set this property to:
      %   * A string for first-order models, for example, "position"
      %   * A string vector for models with two or more states, for example,
      %     ["position" ; "velocity"]
      % Use the empty string "" for unnamed states.
      StateName = strings(0,1)
      % State units (string vector, default = 0x1 string vector).
      %
      % Use this property to keep track of the units each state is expressed in.
      % You can set "StateUnit" to:
      %   * A string for first-order models, for example, "m/s"
      %   * A string vector for models with two or more states, for example,
      %    ["m" ; "m/s"]
      StateUnit = strings(0,1)
   end
   
   % Original property names, now aliased
   properties (Dependent, Hidden)
      a
      b
      c
      d
      k
   end
   
   methods (Access = protected)
      
      % PARAMETER SERIALIZATION INTERFACE (PMODEL.GENERIC)
      function ps = getParamSet(M)
         ps = [M.A; M.B; M.C; M.D; M.K];
      end
      
      function M = setParamSet(M,ps)
         M.A = ps(1);  M.B = ps(2);  M.C = ps(3);  M.D = ps(4);
         if numel(ps)>4
            M.K = ps(5);
         end
      end
      
      function dispScalarModel(M)
         % Display of a scalar ss object.
         S = struct('A',M.A,'B',M.B,'C',M.C,'D',M.D);
         if ~isequal(M.K,[])
            S.K = M.K;
         end
         S.StateName = M.StateName;
         S.StateUnit = M.StateUnit;
         disp(S)
      end
      
      function dispModelType(~,Size)
         % Display model type information.
         if prod(Size)==1
            disp(ctrlMsgUtils.message('Control:pmodel:msgSSDisp'))
         else
            disp(ctrlMsgUtils.message('Control:pmodel:msgSSDispArray',mat2str(Size)))
         end
      end
   end
   
   % PUBLIC METHODS
   methods
      function M = ss(a,b,c,d,k)
         % Constructs pmodel.ss instance
         ni = nargin;
         if ni==0
            return
         end
         M.A = param.Continuous('A',a);
         M.B = param.Continuous('B',b);
         M.C = param.Continuous('C',c);
         M.D = param.Continuous('D',d);
         if ni>4
            M.K = param.Continuous('K',k);
         end
      end
      
      % Aliases for renamed properties
      function Value = get.a(M)
         Value = M.A;
      end
      function M = set.a(M, Value)
         M.A = Value;
      end
      function Value = get.b(M)
         Value = M.B;
      end
      function M = set.b(M, Value)
         M.B = Value;
      end
      function Value = get.c(M)
         Value = M.C;
      end
      function M = set.c(M, Value)
         M.C = Value;
      end
      function Value = get.d(M)
         Value = M.D;
      end
      function M = set.d(M, Value)
         M.D = Value;
      end
      function Value = get.k(M)
         Value = M.K;
      end
      function M = set.k(M, Value)
         M.K = Value;
      end
      
      function M = scaleTime(M,sf,Ts)
         % Time vector scaling tnew = sf * told
         if Ts==0
            M.A = pmodel.scaleParameter(M.A, 1/sf);
            M.B = pmodel.scaleParameter(M.B, 1/sf);
            if ~isempty(M.K)
               M.K = pmodel.scaleParameter(M.K, 1/sf);
            end
         end
      end
   end
   
   methods (Hidden)
      
      function nx = order(M)
         % Number of states
         if isempty(M.A)
            nx = 0;   % for ltiblock.ss()
         else
            nx = size(M.A.Value,1);
         end
      end
      
      function s = iosize(M,dim)
         % I/O sizes
         s = size(M.D.Value);
         if nargin>1
            if dim<3
               s = s(dim);
            else
               s = 1;
            end
         end
      end
      
      function M1 = iocat(dim, M1, M2)
         % Concatenate models along input (2) or output (1) dimension.
         nx1 = order(M1); nx2 = order(M2);
         kFlag = ~(isempty(M1.K) || isempty(M2.K));
         M1.A = pmodel.appendParameter(M1.A,M2.A,'A');
         if dim==1
            % Output concatenation.
            % Input sizes of M1 and M2 are assumed to match.
            M1.B = catParameter(1,M1.B,M2.B,'B');
            M1.C = pmodel.appendParameter(M1.C,M2.C,'C');
            if kFlag
               M1.K = pmodel.appendParameter(M1.K,M2.K,'K');
            end
         else
            % Input concatenation.
            % Output sizes of M1 and M2 are assumed to match.
            M1.B = pmodel.appendParameter(M1.B,M2.B,'B');
            M1.C = catParameter(2,M1.C,M2.C,'C');
            if kFlag
               M1.K = catParameter(1,M1.K,M2.K,'K');
            end
         end
         M1.D = catParameter(dim,M1.D,M2.D,'D');
         % State names and units
         if ~(isempty(M1.StateName) && isempty(M2.StateName))
            M1.StateName = [ltipack.fullstring(M1.StateName,nx1) ; ...
               ltipack.fullstring(M2.StateName,nx2)];
         end
         if ~(isempty(M1.StateUnit) && isempty(M2.StateUnit))
            M1.StateUnit = [ltipack.fullstring(M1.StateUnit,nx1) ; ...
               ltipack.fullstring(M2.StateUnit,nx2)];
         end
         
      end
      
      %-------------------------------------------
      function M = getsubsys(M,rowIndex,colIndex)
         % Extracts submodel.
         M.B = subsrefParameter(M.B,{':',colIndex},'B');
         M.C = subsrefParameter(M.C,{rowIndex,':'},'C');
         M.D = subsrefParameter(M.D,{rowIndex,colIndex},'D');
         if ~isempty(M.K)
            M.K = subsrefParameter(M.K,{':',rowIndex},'K');
         end
      end
      
      %-------------------------------------------------------------------
      function M = checkDataSize(M)
         % Checks size consistency for A,B,C,D,K parameters.
         Nx = size(M.A.Value,1);
         [Ny,Nu] = size(M.D.Value);
         if ~(isequal(getSize(M.A),[Nx Nx]) && isequal(getSize(M.B),[Nx Nu]) && ...
               isequal(getSize(M.C),[Ny Nx]) && isequal(getSize(M.D),[Ny Nu]) && ...
               (isempty(M.K) || isequal(getSize(M.K),[Nx Ny])))
            error(message('Control:pmodel:ssbadsize'))
         elseif ~any(numel(M.StateName)==[0 Nx])
            error(message('Control:ltiobject:ssProperties3','StateName'))
         elseif ~any(numel(M.StateUnit)==[0 Nx])
            error(message('Control:ltiobject:ssProperties3','StateUnit'))
         end
      end
      
      %--------------------------------------------------------------------
      function M = checkDataType(M, varargin)
         % Validates data type of A,B,C,D,K parameters.
         M.A = pmodel.checkParameter(M.A,'A');
         M.B = pmodel.checkParameter(M.B,'B');
         M.C = pmodel.checkParameter(M.C,'C');
         M.D = pmodel.checkParameter(M.D,'D');
         if ~isequal(M.K,[])
            M.K = pmodel.checkParameter(M.K,'K');
         end
         M.StateName = ltipack.mustBeStringVector(M.StateName,'StateName',false);
         M.StateUnit = ltipack.mustBeStringVector(M.StateUnit,'StateUnit',false);
      end
   end
   
   methods (Static, Hidden)
      
      function blk = loadobj(s)
         % Load filter for pmodel.ss objects
         if strcmp(s.A.Name,'a')
            s.A = renameParam(s.A,'A');
            s.B = renameParam(s.B,'B');
            s.C = renameParam(s.C,'C');
            s.D = renameParam(s.D,'D');
            if ~isempty(s.K)
               s.K = renameParam(s.K,'K');
            end
         end
         s.StateName = string(s.StateName(:));
         s.StateUnit = string(s.StateUnit(:));
         blk = s;
      end
      
   end

end
