classdef (SupportExtensionMethods=true) InputOutputModel < ...
      matlab.mixin.internal.PreferPublicProperties & ...
      matlab.mixin.indexing.RedefinesParen & ...
      matlab.mixin.indexing.ForbidsPublicDotMethodCall
   % Input/Output Model objects.
   %
   %   Input/output models describe the static or dynamic behavior of a system   
   %   or system component. They are similar to the concept of "block" or 
   %   "block diagram" in Simulink. Examples of input/output models include 
   %   static gains, linear systems (e.g., transfer functions), and tunable
   %   control system components (e.g., tunable PID).
   %
   %   All input/output model objects in Control System Toolbox and System
   %   Identification Toolbox derive from the @InputOutputModel base class.
   %   This class is not user-facing and cannot be instantiated.
   %   User-facing subclasses include:
   %     * Numeric LTI models (TF, SS, PID, FRD,...)
   %     * Identified LTI models (IDTF, IDSS, IDPROC, IDFRD, ...)
   %     * Control Design blocks (REALP, tunableSS, tunablePID,...)
   %     * Generalized matrices (GENMAT) 
   %     * Generalized LTI models (GENSS, GENFRD)
   %   Generalized models arise when combining ordinary matrices or LTI models
   %   with Control Design blocks. These models are useful to perform certain 
   %   tasks like compensator tuning (see SYSTUNE).
   %
   %   See also LTI, GENMAT, CONTROLDESIGNBLOCK, DYNAMICSYSTEM.
   
   %   Author(s): P. Gahinet
   %   Copyright 2009-2021 The MathWorks, Inc.
   
   properties (Access = public, Dependent)
      % Model name (char vector, default = '').
      %
      % Example: sys.Name = 'pitch axis' labels SYS as a pitch axis model.
      Name
   end
      
   properties (Access = protected)
      % Char vector, default value = ''
      Name_ = '';
      % IOSize_ = [ny nu]
      IOSize_
      % Version
      Version_ = ltipack.ver();
   end
   
   %% ABSTRACT METHODS
   methods (Abstract, Access = protected)
      % Characteristics
      displaySize(M,sizes) % display for "size(M)"
   end
      
   %% TYPE MANAGEMENT IN BINARY OPERATIONS
   methods (Static, Hidden)
      
      function T = toClosed(~)
         % A class T is "closed" for the operation OP iff op(A,B) is of class
         % T whenener both operands are of class T. @ss is closed for all
         % operations but @pid is closed only for STACK.
         %
         % Use the static method
         %    T = <classname>.toClosed(op)
         % to specify how to convert non-closed classes to closed classes for
         % a particular operation.
         T = [];
      end

      function T = superiorTypes()
         T = cell(1,0);
      end
      
      function A = defaultAttributes()
         % Attributes define the coercion rules for equalizing the operand
         % classes in binary operations. Each attribute is a dominant trait,
         % i.e., if one operand has it, then all operands inherit it by
         % coercion. Leaf classes that are closed under binary operations
         % should
         %   1) Use getAttributes to override default attributes
         %   2) For each dominant attribute they miss, provide a
         %      "to<AttributeName>" static methods for converting to a
         %      class with this attribute.
         % (Note: This is superfluous for non-closed classes since they get
         % converted to a closed class right away).
         %
         % Dynamic     To be dynamic rather than static.
         %             @zpk is dynamic but @genmat is not.
         % Varying     To be time or parameter varying.
         %             @lpvss is varying but @ss is not.
         % Structured  To be a block diagram rather then a lumped model.
         %             @genss and @uss are structured but @idss is not.
         % FRD         To be an FRD model or not.
         %             @frd is FRD but @realp or @tf are not.
         % Sparse      To use sparse rather than dense data storage.
         %             @sparss is sparse but @ss is not.
         %
         % Classes with the same attributes form a peer group. Within such
         % peer groups, operand classes are further aligned using precedence
         % rules and the superiorclass tree.
         %
         % Note: Default values are all true so that each getAttributes
         % default overwrite must be accompanied with a "to<AttributeName>"
         % method. Note that there is no class with all these attributes.
         A = struct(...
            'Dynamic',true,...
            'Varying',true,...
            'Structured',true,...
            'FRD',true,...
            'Sparse',true);
      end
      
      function A = getAttributes(A)
         % Override default attributes
      end

   end

   methods (Access = protected)
      % Indexing operations (see RedefinesParen)

      function result = parenReference(M, indexOp)
         % Evaluate M(indices)
         try
            sizes = size(M);
            % Format subscripts
            indices = ltipack.formatSubs(indexOp(1).Indices,sizes);
            % Turn name references into regular indices (first 2 dimensions only)
            for j=1:2
               indices{j} = name2index(M,indices{j},j);
            end
            % Check indices
            indices = ltipack.checkRefIndices(indices,sizes);
            indrow = indices{1};
            indcol = indices{2};
            % Data and metadata
            M = indexref_(M,indrow,indcol,indices(3:end));
            M = parenReferenceMetaData(M,indrow,indcol);
            if isnumeric(indrow)
               M.IOSize_(1) = length(indrow);
            end
            if isnumeric(indcol)
               M.IOSize_(2) = length(indcol);
            end
            % Process additional indexing operations
            if numel(indexOp)>1
               result = M.(indexOp(2:end));
            else
               result = M;
            end
         catch ME
            ltipack.throw(ME,'subsref',class(M))
         end
      end
      
      function M = parenAssign(M, indexOp, rhs)
         % Assign into M(indices)
         try
            if numel(indexOp)>1
               Msub = parenReference(M, indexOp(1));
               Msub.(indexOp(2:end)) = rhs;
               rhs = Msub;
            end
            % Left with M(indices) = rhs
            if isequal(M,[])
               % LHS created by assignment
               M = createLHS(rhs);
            end
            % Cast RHS to LHS type (needed, e.g., M=[] and rhs=tunableLTI)
            rhs = dynamicCast(M,rhs);
            % Harmonize LHS/RHS attributes
            [M,rhs] = matchAttributes(M,rhs);
            % Sizes
            sizes = size(M);
            rsizes = size(rhs);

            % Check and format indices
            indices = ltipack.formatSubs(indexOp(1).Indices,sizes,false);
            for j=1:2
               indices{j} = name2index(M,indices{j},j);
            end
            indices = ltipack.checkAsgnIndices(indices,sizes,false);
            indrow = indices{1};
            indcol = indices{2};
            iscolon = strcmp(indices,':');

            % Determine new I/O size (rely on assignment code for size checking)
            ioMask = zeros(sizes(1:2));
            % Perform assignment along I/O dimensions
            % RE: Throws error if I/O sizes are incompatible
            ioMask(indices{1:2}) = zeros(rsizes(1:2));
            ioSize = size(ioMask);

            % Determine new array size and which portion of the model array
            % is being (re)assigned
            if numel(indices)>2
               ArrayMask = zeros(sizes(3:end));  % initial model array
               % Perform assignment along model array dimensions. In resulting
               % ASGNMASK, J>0 means J-th model in RHS array.
               % RE: Keep track of RHS model associated with each modified LHS model
               % (necessary because indices are not always monotonic, see g147948)
               rArraySize = [rsizes(3:end) 1 1];
               ArrayMask(indices{3:end}) = reshape(1:prod(rArraySize),rArraySize);
            else
               ArrayMask = 1;
            end

            % Update data
            M = indexasgn_(M,indices,rhs,ioSize,ArrayMask);

            % Metadata
            if isequal(rsizes(1:2),size(ioMask(indrow,indcol)))
               % Note: Ignore rhs in scalar assignments
               M = parenAssignMetaData(M,indrow,indcol,iscolon,rhs,ioSize);
            end

            % Update metadata
            M.IOSize_ = ioSize;

            % Consistency checks (e.g., consistent block definition)
            checkModelArray_(M)
         catch ME
            ltipack.throw(ME,'subsasgn',class(M))
         end
      end

      function M = parenDelete(M, indexOp)
         % Note: Called only for single-level deletion M(idx) = []
         try
            sizes = size(M);
            % Check and format indices
            indices = ltipack.formatSubs(indexOp.Indices,sizes,true);
            for j=1:2
               indices{j} = name2index(M,indices{j},j);
            end
            indices = ltipack.checkAsgnIndices(indices,sizes,true);
            indrow = indices{1};
            indcol = indices{2};
            iscolon = strcmp(indices,':');

            % RE: For M(:,:,:)=[], only delete outputs to mimic a(:,:,:)=[] for matrices
            M = indexdel_(M,indices);

            % Metadata
            M = parenDeleteMetaData(M,indrow,indcol,iscolon);

            % Update I/O size
            if all(iscolon) || ~iscolon(1)
               % Output deletion
               ikeep = 1:sizes(1);   ikeep(indrow) = [];
               M.IOSize_(1) = length(ikeep);
            elseif ~iscolon(2)
               % Input deletion
               ikeep = 1:sizes(2);    ikeep(indcol) = [];
               M.IOSize_(2) = length(ikeep);
            end
         catch ME
            ltipack.throw(ME,'subsasgn',class(M))
         end
      end

      function n = parenListLength(~,~,~)
         n = 1;
      end
   end

   %% DATA ABSTRACTION INTERFACE
   % This interface is designed so that:
   % 1) User-facing methods have a single implementation in some
   %    easily recognizable class (@InputOutputModel, @StaticModel, 
   %    @DynamicSystem, @FRDModel, or @StateSpaceModel)
   % 2) The input parsing and error checking for user-facing methods
   %    is never duplicated.
   % Calls to a public method "foo" typically delegate all computations to
   % a protected method "foo_" that is implemented by appropriate subclasses. 
   % The method "foo_" acts as an adaptor between the user-facing, 
   % class-independent API and the low-level algorithms and data classes.
   methods (Access = protected)
      %% MODEL CHARACTERISTICS
      function boo = isLTI_(~)
         % Default implementation
         boo = true;
      end
      function boo = isLTV_(~)
         % Default implementation
         boo = false;
      end
      function boo = isempty_(M)
         boo = any(size(M)==0);
      end
      function boo = isreal_(varargin)
         boo = true;
      end
      function boo = isstatic_(varargin)
         boo = true;
      end
      function boo = isfinite_(varargin)
         boo = true;
      end
      function boo = hasdelay_(varargin)
         boo = false;
      end
      function boo = hasInternalDelay_(varargin)
         boo = false;
      end
      function boo = isParametric_(~)
         boo = false;
      end
      function boo = isLinear_(~)
         boo = true;
      end
      function boo = isUncertain_(~)
         boo = false;
      end
      function nb = nblocks_(M)
         nb = zeros(getArraySize(M));
      end
      function Ts = getTs_(~)
         Ts = 0;   % default
      end
      function sys = setTs_(sys,~)
         % No-op
      end
      % RCTB
      varargout = lftdata_(M,varargin)
      M = uscale_(M,fact)
      
      %% CONVERSIONS
      % Note: metadata is not transferred
      M = double_(M)
      M = ss_(M,optflag)
      M = tf_(M)
      M = zpk_(M)
      M = frd_(M,freq)
      M = pid_(M,Options)
      M = pidstd_(M,Options)
      M = pid2_(M,Options)
      M = pidstd2_(M,Options)
      M = sparss_(M)
      M = mechss_(M)
      M = genmat_(M)
      M = genss_(M)
      M = genfrd_(M,freq)
      M = umat_(M)
      M = uss_(M)
      M = ufrd_(M,freq)
      M = idpoly_(M, Type)
      M = idss_(M, Type)
      M = idtf_(M, Type)
      M = idfrd_(M,freq)
      M = ltvss_(M)
      M = lpvss_(M)
      function M = getValue_(M)
         % Default = no op
      end
      function M = getNominal_(M)
         % Default = no op
      end

      %% BINARY OPERATIONS, TO BE IMPLEMENTED BY "COMBINABLE" SUBCLASSES
      % Note: op_ is never called for classes that are not "closed" for
      %       this operation.
      M = iocat_(dim,M1,M2)
      M = append_(M1,M2)
      M = stack_(arraydim,M1,M2)
      [M,SingularFlag] = lft_(M1,M2,indu1,indy1,indu2,indy2)
      [M,SingularFlag] = feedback_(M1,M2,indu,indy,sign)
      M = plus_(M1,M2)
      M = mtimes_(M1,M2,ScalarFlags)
      M = times_(M1,M2,ScalarFlags)
      [M,SingularFlag] = mldivide_(M1,M2)
      [M,SingularFlag] = mrdivide_(M1,M2)
      function boo = hasCustomScalarAdd_(~)
         % Default: No special handling of sys+SCALAR, SCALAR+sys
         boo = false;
      end
      function boo = hasCustomScalarMultiply_(~,~)
         % Default: No special handling of sys*SCALAR, SCALAR*sys
         boo = false;
      end
      M = addScalar_(M,A)
      M = leftMultiplyByScalar_(M,A)
      M = rightMultiplyByScalar_(M,A)
      function boo = hasSimpleInverse_(~)
         % Default implementation
         boo = false;
      end
      function [M1,M2] = matchAttributes(M1,M2)
         % Default implementation: no-op
      end
      function varargout = matchAttributesN(varargin)
         % Default implementation: no-op
         varargout = varargin;
      end

      %% INDEXING AND ARRAY MANIPULATIONS
      % Indexing
      M = createLHS(rhs)
      M = indexref_(M,indrow,indcol,ArrayIndices)
      M = indexdel_(M,indices)
      M = indexasgn_(M,indices,rhs,ioSize,ArrayMask)
      [M1,M2] = matchArraySize(M1,M2)
      varargout = matchArraySizeN(varargin)
      function M = permute_(M,~)
         % Default = no-op
      end
      function M = squeeze_(M)
         % Default = no-op
      end
      
      %% TRANSFORMATIONS
      M = reshape_(M,s)
      M = repmat_(M,s)
      M = uminus_(M)
      M = conj_(M)
      M = transpose_(M)
      M = ctranspose_(M)
      [M,SingularFlag] = inv_(M,varargin)
      [M,SingularFlag] = imp2exp_(M,yidx,uidx,varargin)
      [M,SingularFlag] = mpower_(M,k)
      [M,xkeep] = sminreal_(M,varargin)
      M = replaceB2B_(M,BlockNames,BlockValues)
      M = replaceB2V_(M,BlockNames,BlockValues)
      [M,S] = sampleBlock_(M,SamplingSpec)
      M = setBlockValue_(M,S)
      function M = inheritBlockValue_(M,~)
         % Default = no-op
      end
      M = normalize_(M)
      M = voidModel_(M,iVoid)
      M = simplify_(M,LevelOverride,MaxLevel)

      %% ANALYSIS
      [WCG,WCU,Info] = wcgain_(M,Opt)
   end
   
   methods

      function Value = get.Name(M)
         % GET function for Name property
         Value = M.Name_;
      end
      
      function M = set.Name(M,Value)
         % SET function for Name property (stored as char vector)
         if isempty(Value)
            Value = '';
         else
            if isstring(Value)
               Value = char(Value);
            end
            if ~(ischar(Value) && isrow(Value))
               error(message('Control:ltiobject:setLTI2'))
            end
         end
         M = setName_(M,Value);
      end
      
      % Default implementations
      function boo = issparse(~)
         %ISSPARSE  True for models with sparse data.
         %
         %   ISSPARSE(M) returns true for SPARSS and MECHSS models and
         %   false otherwise.
         %
         %   See also SPARSS, MECHSS.
         boo = false;
      end
      
      function boo = isParametric(M)
         %ISPARAMETRIC  True for models with tunable blocks.
         %
         %   ISPARAMETRIC(M) returns true for input/output models M that contain 
         %   parametric (tunable) Control Design blocks such as tunableSS or 
         %   REALP.
         %
         %   See also tunableBlock, INPUTOUTPUTMODEL.
         boo = isParametric_(M);
      end
      
      function boo = isLinear(M)
         %ISLINEAR  True for models with linear dynamics.
         %
         %   ISLINEAR(M) returns true for input/output models M that do not 
         %   contain any nonlinear components.
         %
         %   See also CONTROLDESIGNBLOCK, INPUTOUTPUTMODEL.
         boo = isLinear_(M);
      end
      
      function boo = isUncertain(M)
         %ISUNCERTAIN  True for models with uncertain components.
         %
         %   ISUNCERTAIN(M) returns true for input/output models M that 
         %   contain uncertain Control Design blocks such as UREAL or
         %   ULTIDYN.
         %
         %   See also UNCERTAINBLOCK, INPUTOUTPUTMODEL.
         boo = isUncertain_(M);
      end
      
      function boo = isGeneralized(M)
         %isGeneralized  True for generalized input/output models.
         %
         %   isGeneralized(M) returns true for input/output models M that 
         %   contain Control Design blocks.
         %
         %   See also ControlDesignBlock, genmat, genss, genfrd.
         boo = isa(M,'ControlDesignBlock') || isa(M,'ltipack.LFTModelArray');
      end

      function n = nmodels(M)
         %NMODELS  Returns number of models in model array.
         %
         %   N = NMODELS(M) returns the number of models, N, in the array M
         %   of static or dynamic models.
         %
         %   See also SIZE, NDIMS, INPUTOUTPUTMODEL.
         n = prod(getArraySize(M));  % generic definition
      end
      
      function nb = nblocks(M)
         %NBLOCKS  Counts number of blocks in generalized model.
         %
         %   N = NBLOCKS(M) returns the number of Control Design blocks, N, in the
         %   input/output model M. Only generalized models such as GENSS or GENMAT
         %   contain such blocks. For arrays of models, N is an array of the same 
         %   size listing the number of blocks in each model.
         %
         %   See also GENSS, GENFRD, GENMAT, CONTROLDESIGNBLOCK, INPUTOUTPUTMODEL.
         nb = nblocks_(M);
      end
      
      function [ny,nu] = iosize(M)
         %IOSIZE  I/O size of input/output model.
         %
         %   [NY,NU] = IOSIZE(M) returns the number of inputs NU and the 
         %   number of outputs NY of the model M. For static models, NY
         %   and NU correspond to the number of rows and columns in M.
         %
         %   S = IOSIZE(M) returns S = [NY NU].
         %
         %   See also SIZE, INPUTOUTPUTMODEL.
         ios = M.IOSize_;
         if nargout<2
            ny = ios;
         else
            ny = ios(1);  nu = ios(2);
         end
      end
      
      function n = ndims(M)
         %NDIMS  Number of dimensions in model array.
         %
         %   N = NDIMS(M) returns the number of dimensions in the array M
         %   of input/output models. A single model has two dimensions 
         %   (outputs and inputs, rows and columns). An array of models has 
         %   N=2+p dimensions, where p>=2 is the number of array dimensions. 
         %   For example, a 2-by-3-by-4 array of systems has 2+3=5 dimensions.
         %
         %   See also SIZE, INPUTOUTPUTMODEL. 
         n = numel(size(M));
      end
      
      function boo = issiso(M)
         %ISSISO  True for SISO input/output models.
         %
         %   ISSISO(M) returns true if M is a single-input, single-output 
         %   (SISO) model or model array, and false otherwise.
         %
         %   See also IOSIZE, SIZE, ISEMPTY, INPUTOUTPUTMODEL.
         boo = all(M.IOSize_==1);
      end
      
      function boo = isempty(M)
         %ISEMPTY  True for empty input/output model.
         %
         %   ISEMPTY(M) returns true if the model M has no inputs or no outputs 
         %   and false otherwise. For FRD models, ISEMPTY(M) also returns true 
         %   when the frequency vector is empty.
         %
         %   For model arrays, ISEMPTY(M) is true if the array is empty or if
         %   the models themselves are empty.
         %
         %   See also SIZE, IOSIZE, INPUTOUTPUTMODEL. 
         boo = isempty_(M);
      end
      
      function boo = isstatic(M,varargin)
         %ISSTATIC  Checks if input/output model is static or dynamic.
         %
         %   B = ISSTATIC(M) returns TRUE if the model M is static and FALSE if
         %   M has dynamics (states or delays). For model arrays, ISSTATIC(M) is 
         %   true if all models are static.
         %
         %   B = ISSTATIC(M,'elem') checks each model in a model array M and 
         %   returns a logical array B of the same size as M indicating
         %   which models are static.
         %
         %   See also pole, zero, hasdelay, InputOutputModel.
         boo = isstatic_(M,varargin{:});
      end
         
      function boo = hasdelay(M,varargin)
         %HASDELAY  True for dynamic systems with time delays.
         %
         %   B = HASDELAY(M) returns TRUE if the input/output model M has input,
         %   output, or internal delays, and FALSE otherwise. For model arrays, 
         %   HASDELAY(M) is true if at least one model has delays.
         %
         %   B = HASDELAY(M,'elem') checks each model in a model array M and 
         %   returns a logical array B of the same size as M indicating which 
         %   models have delays.
         %
         %   See also ss/getDelayModel, absorbDelay, DynamicSystem.
         boo = hasdelay_(M,varargin{:});
      end
      
      function boo = hasInternalDelay(M,varargin)
         %hasInternalDelay  True for dynamic systems with internal delays.
         %
         %   B = HASINTERNALDELAY(M) returns TRUE if the model M has internal 
         %   delays and FALSE otherwise. For model arrays, HASINTERNALDELAY(M) 
         %   is true if at least one model has internal delays.
         %
         %   B = HASINTERNALDELAY(M,'elem') checks each model in a model array 
         %   M and returns a logical array B of the same size as M indicating
         %   which models have internal delays.
         %
         %   See also ss/getDelayModel, hasdelay, DynamicSystem.
         boo = hasInternalDelay_(M,varargin{:});
      end
      
      function boo = isreal(M,varargin)
         %ISREAL  True for models with real-valued coefficients.
         %
         %   B = ISREAL(M) returns TRUE if the input/output model M has
         %   real-valued coefficients and FALSE otherwise. For model arrays,
         %   ISREAL(M) is true if all models have real-valued coefficients.
         %
         %   B = ISREAL(M,'elem') checks each model in a model array M and 
         %   returns a logical array B of the same size as M indicating
         %   which models have real coefficients.
         %
         %   See also isfinite, InputOutputModel.
         boo = isreal_(M,varargin{:});
      end
            
      function boo = isfinite(M,varargin)
         %ISFINITE  Checks if input/output model has finite coefficients.
         %
         %   B = ISFINITE(M) returns TRUE if the model M has finite coefficients
         %   and FALSE otherwise. For model arrays, ISFINITE(M) is true if all 
         %   models have finite coefficients.
         %
         %   B = ISFINITE(M,'elem') checks each model in a model array M and 
         %   returns a logical array B of the same size as M indicating
         %   which models have finite coefficients.
         %
         %   See also isreal, InputOutputModel. 
         boo = isfinite_(M,varargin{:});
      end
      
      function M = permute(M,order)
         %PERMUTE  Permutes array dimensions in input/output models.
         %
         %   M = PERMUTE(M,ORDER) rearranges the array dimensions of the model array
         %   M so that they are in the order specified by the vector ORDER. Note that
         %   the I/O dimensions are not counted as array dimensions.
         %
         %   Example:
         %      sys = rss(2,2,2,1,2,3);    % 1-by-2-by-3 array of state-space models
         %      size(permute(sys,[3 2 1])) % now it's 3-by-2-by-1.
         %
         %   See also NDIMS, SIZE, RESHAPE, INPUTOUTPUTMODEL.
         if nargin~=2
            error(message('Control:ltiobject:permute1','permute'))
         elseif length(order)<ndims(M)-2
            error(message('Control:ltiobject:permute2','permute'))
         end
         try
            M = permute_(M,order);
         catch ME
            throw(ME)
         end
      end
      
      function M = ipermute(M,order)
         %IPERMUTE  Inverse permutes array dimensions in input/output models.
         % 
         %   M2 = IPERMUTE(M1,ORDER) is the inverse of PERMUTE. IPERMUTE
         %   rearranges the dimensions of the input/output model M1 so that 
         %   PERMUTE(M2,ORDER) will produce M1.  
         %
         %   See also NDIMS, SIZE, RESHAPE, INPUTOUTPUTMODEL.
         if nargin~=2
            error(message('Control:ltiobject:permute1','ipermute'))
         elseif length(order)<ndims(M)-2
            error(message('Control:ltiobject:permute2','ipermute'))
         end
         try
            inverseorder(order) = 1:numel(order);   % Inverse permutation order
            M = permute_(M,inverseorder);
         catch ME
            throw(ME)
         end
      end      
      
      function M = squeeze(M)
         %SQUEEZE   Removes singleton array dimensions in input/output models.
         % 
         %   M = SQUEEZE(M) returns a model array with the same number of elements
         %   but with all the singleton dimensions removed. SQUEEZE has no effect
         %   on the I/O dimensions.
         %
         %   See also NDIMS, SIZE, RESHAPE, INPUTOUTPUTMODEL.
         M = squeeze_(M);
      end
      
      function M = flipud(M)
         %FLIPUD   Flips output channels of input/output model.
         % 
         %   M = FLIPUD(M) flips the output channels (rows) of the input/output 
         %   model M.
         %
         %   See also SIZE, RESHAPE, INPUTOUTPUTMODEL.
         M = M(M.IOSize_(1):-1:1,:);
      end
      
      function M = fliplr(M)
         %FLIPLR   Flips input channels of input/output model.
         % 
         %   M = FLIPLR(M) flips the input channels (columns) of the input/output 
         %   model M.
         %
         %   See also SIZE, RESHAPE, INPUTOUTPUTMODEL.
         M = M(:,M.IOSize_(2):-1:1);
      end
      
      function M = uplus(M)
         %UPLUS  Unary plus for input/output models.
         %
         %   See also UMINUS, INPUTOUTPUTMODEL.
      end
      
      function M = uminus(M)
         %UMINUS  Unary minus for input/output models.
         %
         %   MM = UMINUS(M) is invoked by MM = -M.
         %
         %   See also MINUS, INPUTOUTPUTMODEL.
         try
            M = uminus_(M);
         catch E
            ltipack.throw(E,'expression','-M','M',class(M))
         end
      end
      
      function M = minus(M1,M2)
         %MINUS  Subtraction for input/output models.
         %
         %   M = MINUS(M1,M2) is invoked by M = M1-M2.
         %
         %   See also PLUS, UMINUS, INPUTOUTPUTMODEL.
         M = M1 + (-M2);
      end
      
      function M = transpose(M)
         %TRANSPOSE  Transposition of input/output models.
         %
         %   TM = TRANSPOSE(M) is invoked by TM = M.' where M is any 
         %   input/output model. For static models, this is equivalent to 
         %   matrix transposition. For dynamic systems with transfer 
         %   function H(s), this returns the system with transfer function 
         %   H(s).' .
         %
         %   See also CTRANSPOSE, INPUTOUTPUTMODEL.
         try
            M = transpose_(M);
         catch E
            ltipack.throw(E,'expression','M.''','M',class(M))
         end
         M.IOSize_ = M.IOSize_([2 1]);
         if ~isa(M,'ControlDesignBlock')
            M = resetMetaData(M);
         end
      end
      
      function M = ctranspose(M)
         %CTRANSPOSE  Complex conjugate transpose for input/output models.
         %
         %   CTM = CTRANSPOSE(M) is invoked by CTM = M'.
         %
         %   For static models, this operation corresponds to taking the
         %   complex conjugate transpose of a complex matrix.
         %   
         %   For continuous-time dynamic systems with transfer function H(s),  
         %   this operation returns the model with transfer function H(-s).'
         %
         %   For discrete-time dynamic systems with transfer function H(z), 
         %   this operation returns the model with transfer function H(1/z).'
         %
         %   See also TRANSPOSE, INPUTOUTPUTMODEL.
         try
            M = ctranspose_(M);
         catch E
            ltipack.throw(E,'expression','M''','M',class(M))
         end
         M.IOSize_ = M.IOSize_([2 1]);
         if ~isa(M,'ControlDesignBlock')
            M = resetMetaData(M);
         end
      end
      
      function M = conj(M)
         %CONJ  Forms model with complex conjugate coefficients.
         %
         %   MC = CONJ(M) constructs a complex conjugate model MC
         %   by applying complex conjugation to all coefficients of the
         %   input/output model M.  For example, if M is the transfer 
         %   function (2+i)/(s+5i), then CONJ(M) produces the transfer 
         %   function (2-i)/(s-5i).
         %
         %   This operation is useful for manipulating partial fraction
         %   expansions.
         %
         %   See also TF, ZPK, SS, RESIDUE, INPUTOUTPUTMODEL.
         try
            M = conj_(M);
         catch E
            ltipack.throw(E,'command','conj',class(M))
         end
      end

      function sys = makeStateSpace(sys)
         % Converts to state-space representation.
         %
         %   SYS = makeStateSpace(M) converts the model M to one of the
         %   following state-space model types: SS, SPARSS, MECHSS, GENSS, 
         %   or USS. The class of SYS is selected to preserve all information 
         %   in M.
         %
         %   See also SS, SPARSS, MECHSS, GENSS, USS.
         if isa(sys,'ControlDesignBlock')
            % Blocks are converted to GENSS or USS
            if isUncertain(sys)
               sys = uss(sys);
            else
               sys = genss(sys);
            end
         elseif ~(isa(sys,'ss') || isa(sys,'genss') || isa(sys,'sparss') || isa(sys,'genss'))
            % Try converting to SS
            try
               sys = ss(sys);
            catch ME
               throw(ME)
            end
         end
      end
      
   end
   
   
   %% INDEXING SUPPORT
   methods (Access = protected)
      
      function indices = name2index(M,indstr,~)
         % Default implementation: name-based reference not supported
         if isnumeric(indstr) || islogical(indstr) || (ischar(indstr) && strcmp(indstr,':'))
            indices = indstr;
         else
            error(message('Control:ltiobject:NoStringIndexing',class(M))) 
         end
      end
      
      function M = parenReferenceMetaData(M,varargin)
         % Metadata management (default = no-op)
      end

      function M = parenAssignMetaData(M,varargin)
         % Metadata management (default = no-op)
      end
      
      function M = parenDeleteMetaData(M,varargin)
         % Metadata management (default = no-op)
      end

      function M = dynamicCast(refsys,M)
         % Attempts to cast SYS to class of REFSYS
         refClass = class(refsys);
         initClass = class(M);
         if ~strcmp(initClass,refClass)
            try %#ok<TRYNC>
               M = feval(refClass,M);
            end
            if ~strcmp(class(M),refClass)
               error(message('Control:ltiobject:subsasgn3',initClass,refClass))
            end
         end
      end

   end
   
   
   %% MATH OPERATION SUPPORT
   methods (Access = protected)
      % Metadata management in binary operations (default = no-op)
      function M = copyMetaData(refM,M)
         M.Name_ = refM.Name_;
      end
      function M = resetMetaData(M)
         M.Name_ = '';
      end
      function M = iocatMetaData(~,M,~,~)
         M.Name_ = '';
      end
      function M = plusMetaData(M,~,~)
         M.Name_ = '';
      end
      function M = feedbackMetaData(M,~,~)
         M.Name_ = '';
      end
      function M = lftMetaData(M,varargin)
         M.Name_ = '';
      end
      function M = mtimesMetaData(M,M1,M2,ScalarFlags)
         if ScalarFlags(1)
            M = copyMetaData(M2,M);
         elseif ScalarFlags(2)
            M = copyMetaData(M1,M);
         else
            M.Name_ = '';
         end
      end
      % Metadata management in unary math operations
      function M = imp2expMetaData(M,~,~)
         M.Name_ = '';
      end
      function M = invMetaData(M)
         if ~isempty(M.Name_)
            M.Name = sprintf('inv(%s)',M.Name_);
         end
      end
      function M = mpowerMetaData(M,~)
         M.Name_ = '';
      end
      function M = repmatMetaData(M,~)
         M.Name_ = '';
      end
      function M = transferInputOutput(M,varargin)
      end
      % Array consistency checks
      function checkModelArray_(~)
      end
   end
   
   
   %% UTILITIES
   methods (Access = protected)
      
      function sys = setName_(sys,Value)
         % Can be overloaded to impose additional restrictions on Name
         sys.Name_ = Value;
      end
      
      function s = getPropStruct(obj)
         % Build structure of property values for GET(M)
         PublicProps = properties(obj);
         Np = length(PublicProps);
         s = cell2struct(cell(Np,1),PublicProps,1);
         hw = ctrlMsgUtils.SuspendWarnings; %#ok<NASGU>
         for ct=1:Np
            p = PublicProps{ct};
            try
               s.(p) = obj.(p);
            catch ME
               s.(p) = ME.message;
            end
         end
      end

      function showModelProperties(M)
         % Hotlink to show model properties in display
         if matlab.internal.display.isHot
            try %#ok<TRYNC>
               txt = getString(message('Control:general:ModelProperties'));
               fprintf('<a href="matlab:%s">%s</a>\n',getPropertyDisplay(M),txt);
            end
         end
      end
      
   end
   
   %% OBSOLETE/HIDDEN
   methods (Hidden)
      
      function s = getArraySize(~)
         % Default model array size 
         s = [1 1];
      end
      
      function S = getBlocks(~)
         % Get LFT blocks
         S = struct;
      end

      function M = setBlocks(M,~)
         % Set LFT blocks
      end

      function str = displayShort(M)
         % Support for preview panel in data browser
         try
            ModelName = M.Name;
         catch  %#ok<CTCH>
            ModelName = '';
         end
         SizeStr = evalc('displaySize(M,size(M))');
         if isempty(ModelName)
            str = SizeStr;
         else
            str = sprintf('%s:\n%s',ModelName,SizeStr);
         end
      end
      
      function str = getPropertyDisplay(M)
         % Create static display for "Model Properties" hyperlink.
         s = split(evalc('get(M)'),newline);
         str = 'disp(char('''',';
         for ct=1:numel(s)-1
            str = sprintf('%s''%s '',',str,strrep(s{ct},"'","''"));
         end
         str = sprintf('%s))',str(1:end-1));
      end

   end
   
   %% STATIC METHODS
   methods(Static)  % , Access=protected)  % Reenable when class function have access to protected API
      
      function [indy1,indu2] = matchChannelNames(Names1,Names2)
         % Matches channels names.
         %   [IND1,IND2] = matchChannelNames(NAMES1,NAMES2) returns
         %   index vectors such that NAMES2(IND2) = NAMES1(IND1)
         % This function is used for named-based interconnections.
         if isempty(Names1) || isempty(Names2) || any(strcmp(Names1,'')) || any(strcmp(Names2,''))
            error(message('Control:combination:UndefinedIONames'))
         end
         [~,indy1,indu2] = intersect(Names1,Names2);
      end
      
      % Part of RedefinesParen API, not relevant here
      function sys = empty() %#ok<STOUT> 
         error(message('Control:ltiobject:empty'))
      end

   end
   
end


