classdef (SupportExtensionMethods=true) lti < ltipack.SystemArray
% Linear Time-Invariant Model objects.
%
%   Linear time-invariant (LTI) models represent linear systems with
%   time-invariant dynamics. The main categories of LTI models include:
%     * Numeric LTI models (see NUMLTI), whose coefficients have fixed
%       numerical values
%     * Identified LTI models (see IDLTI), whose coefficients can be
%       estimated using time- or frequency-domain data
%     * Generalized LTI models (see GENLTI), which combine numeric LTI
%       models with parametric components to support tasks such as
%       control system tuning
%     * Uncertain LTI models (see ULTI), which combine numeric LTI models
%       with uncertain elements to support tasks such as robustness
%       analysis.
%   All LTI model types derive from the @lti superclass. This class is
%   not user-facing and cannot be instantiated.
%
%   Example: Specify the transfer function G(s)=s/(s+1):
%
%       s = tf('s');  G = s/(s+1);
%
%   This creates a numeric LTI model G of type TF.
%
%   Example: Create a tunable model of the feedback loop
%
%             r --->O--->[ C ]--->[ G ]---+---> y
%                 - |                     |
%                   +---------------------+
%
%   where C is a PI controller:
%
%       C = tunablePID('C','pi');  % tunable PI block
%       T = feedback(G*C,1);
%
%   This returns a generalized LTI model T of type GENSS which can be
%   tuned with commands like HINFSTRUCT.
%
%   See also NUMLTI, IDLTI, GENLTI, ULTI, DYNAMICSYSTEM.

%   Copyright 2011-2012 The MathWorks, Inc.

% Public properties
properties (Access = public, Dependent)
      % Input delays (numeric vector, default = all zeros).
      %
      % The "InputDelay" property specifies a time delay for each input 
      % channel. For continuous-time systems, specify input delays in
      % the time unit stored in the "TimeUnit" property. For discrete-
      % time systems, specify input delays as integer multiples of the
      % sampling period "Ts", for example, InputDelay=2 to mean a delay
      % of two sampling periods.
      %
      % Set "InputDelay" to a Nu-by-1 vector for a system with Nu inputs.
      % You can also set this property to a scalar value to apply the same
      % delay to all input channels.
      %
      % Example: sys.InputDelay = [0 ; 1.7] specifies a zero delay for 
      % the first input channel and a delay of 1.7 time units for the
      % second input channel.
      InputDelay
      % Output delays (numeric vector, default = all zeros).
      %
      % Counterpart of "InputDelay" for output channels. Type
      % "help lti.InputDelay" for details.
      OutputDelay
   end
   
   methods
      
      function Value = get.InputDelay(sys)
         % GET method for InputDelay property
         Nsys = numel(sys.Data_);
         Nu = sys.IOSize_(2);
         if Nsys==0
            Value = zeros(Nu,1);
         elseif Nsys==1
            Value = getInputDelay(sys.Data_);
         else
            Value = zeros([Nu,1,size(sys.Data_)]);
            if Nsys>1
               RefValue = getInputDelay(sys.Data_(1));
               isUniform = true;
               for ct=1:Nsys
                  Value(:,ct) = getInputDelay(sys.Data_(ct));
                  isUniform = isUniform && isequal(Value(:,ct),RefValue);
               end
               if isUniform
                  Value = Value(:,1);
               end
            end
         end
      end
      
      function Value = get.OutputDelay(sys)
         % GET method for InputDelay property
         Nsys = numel(sys.Data_);
         Ny = sys.IOSize_(1);
         if Nsys==0
            Value = zeros(Ny,1);
         elseif Nsys==1
            Value = getOutputDelay(sys.Data_);
         else
            Value = zeros([Ny,1,size(sys.Data_)]);
            if Nsys>1
               Data = sys.Data_;
               RefValue = getOutputDelay(Data(1));
               isUniform = true;
               for ct=1:Nsys
                  Value(:,ct) = getOutputDelay(Data(ct));
                  isUniform = isUniform && isequal(Value(:,ct),RefValue);
               end
               if isUniform
                  Value = Value(:,1);
               end
            end
         end
      end
      
      function sys = set.InputDelay(sys,Value)
         % SET function for InputDelay property
         if ~(isnumeric(Value) && isreal(Value) && allfinite(Value))
            % Note: Do not require Value>=0 to accommodate negative delays in SITB
            error(message('Control:ltiobject:setLTI7','InputDelay'))
         else
            Value = double(full(Value));
         end
         % Check compatibility of RHS with model array sizes
         Data = ltipack.utCheckAssignValueSize(sys.Data_,Value,2);
         CheckFlag = sys.CrossValidation_;
         for ct=1:numel(Data)
            Data(ct) = setInputDelay(Data(ct),Value(:,:,min(ct,end)),CheckFlag);
         end
         sys.Data_ = Data;
      end
      
      function sys = set.OutputDelay(sys,Value)
         % SET function for OutputDelay property
         if ~(isnumeric(Value) && isreal(Value) && allfinite(Value) && all(Value(:)>=0))
             error(message('Control:ltiobject:setLTI1','OutputDelay'))
         else
            Value = double(full(Value));
         end
         % Check compatibility of RHS with model array sizes
         Data = ltipack.utCheckAssignValueSize(sys.Data_,Value,2);
         CheckFlag = sys.CrossValidation_;
         for ct=1:numel(Data)
            Data(ct) = setOutputDelay(Data(ct),Value(:,:,min(ct,end)),CheckFlag);
         end
         sys.Data_ = Data;
      end
         
   end
      
   %% ABSTRACT SUPERCLASS INTERFACES
   methods (Access=protected)

      function Ts = getTs_(sys)
         % Implementation of @SingleRateSystem:getTs_
         if isempty(sys.Data_)
            Ts = 0;
         else
            Ts = sys.Data_(1).Ts;
         end
      end
      
      function sys = setTs_(sys,Ts)
         % Implementation of @SingleRateSystem:setTs_
         Data = sys.Data_;
         for ct=1:numel(Data)
            Data(ct).Ts = Ts;
            if sys.CrossValidation_
               % Check for fractional delays with Ts~=0
               Data(ct) = checkDelay(Data(ct));
            end
         end
         sys.Data_ = Data;
      end
      
   end      
      
      
   %% DATA ABSTRACTION INTERFACE
   methods (Access=protected)
      
      %% MODEL CHARACTERISTICS
      function sys = checkDataConsistency(sys)
         % Check data consistency
         % Note: Because there is no syntax for creating an array of LTI
         % models with different I/O sizes, this code assumes that if all
         % @ltidata objects are self-consistent, their I/O size is uniform
         % across the LTI array
         D = sys.Data_;
         for ct=1:numel(D)
            D(ct) = checkDelay(checkData(D(ct)));
         end
         sys.Data_ = D;
         % I/O size is derived from data to support I/O resizing
         if ~isempty(D)
            sys.IOSize_ = iosize(D(1));
         end
      end
      
      %% INDEXING
      function sys = createLHS(rhs)
         % Creates LHS in assignment.
         % Returns 0x0 system of the same class as RHS and with the same sample time
         if isa(rhs,'FRDModel')
            sys = feval([class(rhs) '.convert'],[],rhs);
         else
            sys = setTs_(feval(class(rhs)),getTs_(rhs));
         end
      end
      
      %% CONVERSIONS
      function sysOut = ss_(sys,optflag)
         % Converts to state-space.
         % Note: @DynamicSystem metadata is not transferred!
         Data = sys.Data_;
         ssData = ltipack.ssdata.array(size(Data));
         SingularFlag = false;
         for ct=1:numel(Data)
            % Use dot syntax here to avoid calling constructor for data 
            % objects that don't overload ss(), e.g., frddata.
            [ssData(ct),sflag] = Data(ct).ss();
            SingularFlag = SingularFlag || sflag;
         end
         % Flag singular algebraic loops in LFT models
         if SingularFlag
            warning(message('Control:lftmodel:SingularAlgebraicLoop'))
         end
         % Handle "minimal" and "explicit" flags
         if nargin>1
            switch optflag
               case 'explicit'
                  % Explicit realization
                  for ct=1:numel(Data)
                     [isp,ssData(ct)] = isproper(ssData(ct),true);
                     if ~isp
                        error(message('Control:transformation:ss'))
                     end
                  end
               case 'minimal'
                  % Minimal realization
                  hw = ctrlMsgUtils.SuspendWarnings;
                  for ct=1:numel(Data)
                     ssData(ct) = minreal(ssData(ct),sqrt(eps));
                  end
                  delete(hw)
            end
         end
         sysOut = ss.make(ssData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = tf_(sys)
         % Converts to transfer function
         Data = sys.Data_;
         tfData = ltipack.tfdata.array(size(Data));
         for ct=1:numel(Data)
            tfData(ct) = Data(ct).tf();
         end
         sysOut = tf.make(tfData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = zpk_(sys)
         % Converts to zero-pole-gain form
         Data = sys.Data_;
         zpkData = ltipack.zpkdata.array(size(Data));
         for ct=1:numel(Data)
            zpkData(ct) = Data(ct).zpk();
         end
         sysOut = zpk.make(zpkData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = frd_(sys,varargin)
         % Converts to FRD with FrequencyUnit=rad/TimeUnit. Second input  
         % argument specifies the frequency grid in rad/TimeUnit (optional  
         % when SYS is a subclass of FRDModel).
         Data = sys.Data_;
         frData = ltipack.frddata.array(size(Data));
         for ct=1:numel(Data)
            frData(ct) = Data(ct).frd(varargin{:});
         end
         sysOut = frd.make(frData,sys.IOSize_); % units = seconds,rad/TimeUnit
         % Note: Setting time units has no side effect when frequency in rad/TimeUnit
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = pid_(sys,Options)
         % Convert data
         Data = sys.Data_;
         pidData = ltipack.piddataP.array(size(Data));
         for ct=1:numel(Data)
            pidData(ct) = Data(ct).pid(Options);
         end
         sysOut = pid.make(pidData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = pidstd_(sys,Options)
         % Convert data
         Data = sys.Data_;
         pidData = ltipack.piddataS.array(size(Data));
         for ct=1:numel(Data)
            pidData(ct) = Data(ct).pidstd(Options);
         end
         sysOut = pidstd.make(pidData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = pid2_(sys,Options)
         % Convert data
         Data = sys.Data_;
         pidData2 = ltipack.piddataP2.array(size(Data));
         for ct=1:numel(Data)
            pidData2(ct) = Data(ct).pid2(Options);
         end
         sysOut = pid2.make(pidData2,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = pidstd2_(sys,Options)
         % Convert data
         Data = sys.Data_;
         pidData2 = ltipack.piddataS2.array(size(Data));
         for ct=1:numel(Data)
            pidData2(ct) = Data(ct).pidstd2(Options);
         end
         sysOut = pidstd2.make(pidData2,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = genss_(sys)
         % Converts to @genss
         Data = sys.Data_;
         lftData = ltipack.lftdataSS.array(size(Data));
         for ct=1:numel(Data)
            lftData(ct) = Data(ct).genss();
         end
         sysOut = genss.make(lftData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = genfrd_(sys,varargin)
         % Converts to GENFRD. Second input argument is the frequency grid in 
         % rad/TimeUnit (optional when SYS is a subclass of FRDModel).
         Data = sys.Data_;
         lftData = ltipack.lftdataFRD.array(size(Data));
         for ct=1:numel(Data)
            lftData(ct) = Data(ct).genfrd(varargin{:});
         end
         sysOut = genfrd.make(lftData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
     end
      
      function sysOut = uss_(sys)
         % Converts to @uss
         Data = sys.Data_;
         lftData = ltipack.lftdataSS.array(size(Data));
         for ct=1:numel(Data)
            lftData(ct) = ucast(Data(ct).genss());
         end
         sysOut = uss.make(lftData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = ufrd_(sys,varargin)
         % Converts to @ufrd
         Data = sys.Data_;
         lftData = ltipack.lftdataFRD.array(size(Data));
         for ct=1:numel(Data)
            lftData(ct) = ucast(Data(ct).genfrd(varargin{:}));
         end
         sysOut = ufrd.make(lftData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = idss_(sys,Type)
         % Converts to @idss
         Data = sys.Data_;
         try
            IdssData = idpack.ssdata.array(size(Data));
         catch %#ok<CTCH>
            error(message('Control:transformation:IdentRequired','idss'))
         end
         for ct = 1:numel(Data)
            IdssData(ct) = Data(ct).idss(Type);
            IdssData(ct) = setLastOperation(IdssData(ct),class(sys));
         end
         if isempty(Data)
            IOSize = sys.IOSize_;
            if lower(Type(1))=='s'
               IOSize(2) = IOSize(2)-IOSize(1);
            end
            sysOut = idss.make(IdssData, IOSize);
            % note: sample time would differ if sys.Ts==0
         else
            sysOut = idss.make(IdssData);
         end
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = idpoly_(sys, Type)
         % Converts to @idpoly
         Data = sys.Data_;
         try
            PolyData = idpack.polydata.array(size(Data));
         catch %#ok<CTCH>
            error(message('Control:transformation:IdentRequired','idpoly'))
         end
         
         for ct = 1:numel(Data)
            PolyData(ct) = Data(ct).idpoly(Type);
            PolyData(ct) = setLastOperation(PolyData(ct),class(sys));
         end
         if isempty(Data)
            IOSize = sys.IOSize_;
            if lower(Type(1))=='s'
               IOSize(2) = IOSize(2)-IOSize(1);
            end
            sysOut = idpoly.make(PolyData, IOSize);
         else
            sysOut = idpoly.make(PolyData);
         end
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = idtf_(sys, Type)
         % Converts to @idtf
         Data = sys.Data_;
         try
            IdtfData = idpack.tfdata.array(size(Data));
         catch %#ok<CTCH>
            error(message('Control:transformation:IdentRequired','idtf'))
         end
         for ct = 1:numel(Data)
            IdtfData(ct) = Data(ct).idtf(Type);
            IdtfData(ct) = setLastOperation(IdtfData(ct),class(sys));
         end
         if isempty(Data)
            IOSize = sys.IOSize_;
            if lower(Type(1))=='s'
               IOSize(2) = IOSize(2)-IOSize(1);
            end
            sysOut = idtf.make(IdtfData, IOSize);
         else
            sysOut = idtf.make(IdtfData);
         end
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = idfrd_(sys,varargin)
         % Converts to IDFRD with FrequencyUnit=rad/TimeUnit. Second input  
         % argument specifies the frequency grid in rad/TimeUnit (optional  
         % when SYS is a subclass of FRDModel).
         Data = sys.Data_;
         frData = idpack.frddata.array(size(Data));
         for ct = 1:numel(Data)
            frData(ct) = Data(ct).idfrd(varargin{:});
            frData(ct) = setLastOperation(frData(ct),class(sys));
         end
         sysOut = idfrd.make(frData,sys.IOSize_); % units = seconds,rad/TimeUnit
         % Note: Setting time units has no side effect when frequency in rad/TimeUnit
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = sparss_(sys)
         % Converts to sparse state-space.
         Data = sys.Data_;
         ssData = ltipack.spssdata.array(size(Data));
         for ct=1:numel(Data)
            D = Data(ct);
            if issparse(D)
               ssData(ct) = sparss(D);
            else
               ssData(ct) = sparss(D.ss());
            end
         end
         sysOut = sparss.make(ssData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end
      
      function sysOut = mechss_(sys)
         % Converts to sparse second-order state space.
         Data = sys.Data_;
         ssData = ltipack.mechdata.array(size(Data));
         for ct=1:numel(Data)
            D = Data(ct);
            if issparse(D)
               ssData(ct) = mechss(D);
            else
               ssData(ct) = mechss(D.ss());
            end
         end
         % Flag singular algebraic loops in LFT models
         sysOut = mechss.make(ssData,sys.IOSize_);
         sysOut.TimeUnit_ = sys.TimeUnit_;
         sysOut.SamplingGrid_ = sys.SamplingGrid_;
      end

      function sysOut = ltvss_(sys)
         % Converts to linear time-varying
         Data = sys.Data_;
         if numel(Data)~=1
            error(message('Control:transformation:LTV1'))
         end
         Data = Data.ss();
         a = Data.a;  b = Data.b;  c = Data.c;  d = Data.d;  e = Data.e;
         [dx0,x0,u0,y0] = ltvpack.util.compressOffsets(Data.dx0,Data.x0,Data.u0,Data.y0);
         Delay = ltvpack.util.lti2ltvDelay(Data.Delay);
         if isempty(a) && isempty(Delay)
            F = @(t) ltvpack.staticDF(d,u0,y0);
         else
            F = @(t) ltvpack.constantDF(a,b,c,d,e,Delay,dx0,x0,u0,y0);
         end
         sysOut = ltvss(F,Data.Ts);
         sysOut.StateName = Data.StateName;
         sysOut.StatePath = Data.StatePath;
         sysOut.StateUnit = Data.StateUnit;
         sysOut.TimeUnit_ = sys.TimeUnit_;
      end

      function sysOut = lpvss_(sys)
         % Converts to linear parameter-varying
         Data = sys.Data_;
         if numel(Data)~=1
            error(message('Control:transformation:LTV1'))
         end
         Data = Data.ss();
         a = Data.a;  b = Data.b;  c = Data.c;  d = Data.d;  e = Data.e;
         [dx0,x0,u0,y0] = ltvpack.util.compressOffsets(Data.dx0,Data.x0,Data.u0,Data.y0);
         Delay = ltvpack.util.lti2ltvDelay(Data.Delay);
         if isempty(a) && isempty(Delay)
            F = @(t,p) ltvpack.staticDF(d,u0,y0);
         else
            F = @(t,p) ltvpack.constantDF(a,b,c,d,e,Delay,dx0,x0,u0,y0);
         end
         sysOut = lpvss(strings(0,1),F,Data.Ts);
         sysOut.StateName = Data.StateName;
         sysOut.StatePath = Data.StatePath;
         sysOut.StateUnit = Data.StateUnit;
         sysOut.TimeUnit_ = sys.TimeUnit_;
      end

   end
   
   
   % UTILITIES
   methods (Access=protected)
      
      %-------------
      function Value = getIODelay(sys)
         % Generic GET function for I/O delays in LTI models
         Data = sys.Data_;
         Nsys = numel(Data);
         [Ny,Nu] = iosize(sys);
         if Nsys==0
            Value = zeros(Ny,Nu);
         else
            Value = zeros([Ny,Nu,size(Data)]);
            isUniform = true;
            for ct=1:Nsys
               iod = getIODelay(Data(ct));
               if ~allfinite(iod)
                  error(message('Control:ltiobject:get3'))
               end
               Value(:,:,ct) = iod;
               isUniform = isUniform && isequal(iod,Value(:,:,1));
            end
            if Nsys>1 && isUniform
               Value = Value(:,:,1);
            end
         end
      end
         
      %-------------
      function sys = setIODelay(sys,Value)
         % Generic SET function for I/O delays in LTI models
         if ~(isnumeric(Value) && isreal(Value) && allfinite(Value) && all(Value(:)>=0))
            error(message('Control:ltiobject:setLTI1','ioDelay'))
         else
            Value = double(full(Value));
         end
         Data = ltipack.utCheckAssignValueSize(sys.Data_,Value,2);
         CheckFlag = sys.CrossValidation_;
         for ct=1:numel(Data)
            % Modify I/O delays if there are no fractional delays
            iod = getIODelay(Data(ct));
            if ~allfinite(iod)
               error(message('Control:ltiobject:get3'))
            end
            Data(ct) = setIODelay(Data(ct),Value(:,:,min(ct,end)),CheckFlag);
         end
         sys.Data_ = Data;
      end
            
      %-----------
      function sys = reload(sys,s)
         % Restore data when loading object
         if isfield(s,'Version_')
            % Version 10- (MCOS)
         elseif isfield(s,'dynamicsys')
            % Versions 5-9 (LTI2)
            sys = reload@DynamicSystem(sys,s.dynamicsys);
            sys.Data_ = s.Data;
         else
            % Versions 1-4
            sys.Ts = s.Ts;
            
            % Collect metadata and delay settings
            MetaData = struct('Name',[]);
            MetaData.InputName = s.InputName;
            MetaData.OutputName = s.OutputName;
            MetaData.Notes = s.Notes;
            MetaData.UserData = s.UserData;
            DelaySettings = cell(1,0);
            
            % Flags to determine if repeated i/o channels and names are modified during load
            GInputChannelsModified = false;
            GInputNamesModified = false;
            GOutputChannelsModified = false;
            GOutputNamesModified = false;
            
            if s.Version==1
               % Copy properties specific to Version 1 object S to SYS
               % Note: Td was equivalent to InputDelay'
               DelaySettings = [DelaySettings , {'InputDelay',s.Td'}];
               MetaData.InputGroup = struct;
               MetaData.OutputGroup = struct;
            else
               % I/O groups
               [MetaData.InputGroup, GInputChannelsModified, GInputNamesModified]  = LocalGroupUpdate(s.InputGroup);
               [MetaData.OutputGroup, GOutputChannelsModified, GOutputNamesModified]  = LocalGroupUpdate(s.OutputGroup);
               % I/O delays
               DelaySettings = [DelaySettings , {'InputDelay',s.InputDelay,'OutputDelay',s.OutputDelay}];
               if s.Version==2 && any(s.ioDelayMatrix(:))  % g188500
                  DelaySettings = [DelaySettings , {'ioDelay',s.ioDelayMatrix}];
               elseif s.Version>2 && any(s.ioDelay(:))
                  DelaySettings = [DelaySettings , {'ioDelay',s.ioDelay}];
               end
            end
            
            % Display warnings if groups names or channels have been modified
            if GInputChannelsModified || GOutputChannelsModified
               warning(message('Control:ltiobject:UpdateGroupIndex'))
            end
            if GInputNamesModified || GOutputNamesModified
               warning(message('Control:ltiobject:UpdateGroupName'))
            end
            
            % Restore metadata and delays
            sys = reload@DynamicSystem(sys,MetaData);
            sys = set(sys,DelaySettings{:});
         end
      end
      
      %-------------
      function S = getSettings(sys)
         % Gets values of public LTI properties. Needed to support
         % obsolete syntax tf(num,den,LTI)
         S = struct(...
            'InputDelay',sys.InputDelay,...
            'OutputDelay',sys.OutputDelay,...
            'Ts',sys.Ts,...
            'TimeUnit',sys.TimeUnit,...
            'InputName',sys.InputName_,...
            'InputUnit',sys.InputUnit_,...
            'InputGroup',{sys.InputGroup},...
            'OutputName',sys.OutputName_,...
            'OutputUnit',sys.OutputUnit_,...
            'OutputGroup',{sys.OutputGroup},...
            'Name',sys.Name,...
            'Notes',{sys.Notes},...
            'UserData',{sys.UserData});
      end
                  
   end
   
   %% RESPPACK interface, obsolete methods
   methods (Hidden)

      function varargout = reduce(varargin)
         %   REDUCE is obsolete, use REDUCESPEC instead.
         %
         %   See also REDUCESPEC.
         [varargout{1:nargout}] = reduceImpl(varargin{:});
      end
      
      function D = getPlotLTIData(sys)
         % Returns array of ltipack.ltidata objects used to compute and plot
         % the system response (see resppack.ltisource)
         D = sys.Data_;
      end
      
   end
      
      
   methods (Static, Access=protected)
      
      function PVPairs = struct2pv(S)
         % Converts struct into property/value pair list
         f = fieldnames(S);
         n = length(f);
         PVPairs = cell(1,2*n);
         PVPairs(:,1:2:2*n) = f;
         PVPairs(:,2:2:2*n) = struct2cell(S);
      end
      
   end
   
   % STATIC METHODS
   methods(Static, Hidden)
      
      function sys = loadobj(s)
         % Load filter, needed to prevent warnings in LOADOBJ (see g582297)
         sys = s;
      end
      
      function [ConstructFlag,InputList] = isContructorCall(F,InputList)
         % Helper function to detect deprecated constructor call F(...,sys) 
         % where sys is an @lti objects. These calls end up in the converter
         % functions and need to be redirected to the constructor.
         nsys = 0;
         for ct=1:numel(InputList)
            if isa(InputList{ct},'DynamicSystem')
               nsys = nsys + 1;   isys = ct;
            end
         end
         if nsys>1
            error(message('Control:ltiobject:construct4',F))
         end
         % Look for syntax F(...,SYS) where SYS is an @lti model
         ConstructFlag = (isys>1);
         if ConstructFlag
            InputList{isys} = getSettings(InputList{isys});
         end
      end
      
   end
      
end

%----------------------- Local Functions ---------------------------------

function [g, GChannelsModified, GNamesModified]  = LocalGroupUpdate(g)
% Update groups to account for new restrictions with struct-based groups

% Boolean variables to determine if repeated i/o channels and names are
% modified during update
GChannelsModified = false;
GNamesModified  = false;

ng = size(g,1);
if isa(g,'struct')
   return
elseif ng==0
   % Migrate to struct when no group specified
   g = struct;
   return
elseif size(g,2)==1
   g(:,2) = {''};
end

% Remove multiplicity of group channels and spaces in group name
for cnt = 1:ng
    gcurrent = g{cnt,1};
    [gunique, gidx] = unique(gcurrent);
    if length(gunique) < length(gcurrent)
        gnew = gcurrent(sort(gidx));
        g{cnt,1} = gnew;
        GChannelsModified = true;
    end
    if ~isempty(strfind(g{cnt,2},' '))
        g{cnt,2} = regexprep(g{cnt,2},' ','_');
        GNamesModified  = true;
    end
end
    
idxNoName = find(cellfun('length',g(:,2))==0);
if ~isempty(idxNoName)
   % Clear names starting with group
   g(:,strncmp(g(:,2),'Group',5)) = {''};
   idxNoName = find(cellfun('length',g(:,2))==0);
end
for ct=1:length(idxNoName)
   g{idxNoName(ct),2} = sprintf('Group%d',ct);
end
end

