classdef (Hidden) SingleRateSystem < InputOutputModel
   % Single-rate systems.
   %
   %   This class manages the sample time of single-rate dynamic systems.

%   Author(s): P. Gahinet
%   Copyright 1986-2021 The MathWorks, Inc.
      
   properties (Access = public, Dependent)
      % Sample time.
      %
      % The sample time should be set to
      %   * zero for continuous-time systems
      %   * the sampling period for discrete-time systems (expressed in 
      %     the unit specified by the "TimeUnit" property)
      %   * -1 for discrete-time systems with unspecified sampling rate.
      % The default value is zero, except for identified polynomial and
      % state-space models (idpoly, idss) where it is -1.
      %
      % Note that changing the value of Ts has no impact on the system data  
      % and does not amount to discretizing or resampling the system dynamics. 
      % Use C2D, D2C, or D2D for sampling rate conversions. 
      Ts
      % Time unit (string, default = 'seconds').
      %
      % This property specifies the unit for the time variable, sample time Ts,
      % and time delays. It can be set to any of the following strings: 'nanoseconds',
      % 'microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'days',
      % 'weeks', 'months', or 'years'. The default value is 'seconds'.
      %
      % Setting this property has no effect on other properties and alters the
      % overall system behavior. Use CHGTIMEUNIT to change time units without
      % modifying the system behavior.
      TimeUnit
   end

   properties (Access = protected)
      % Compact storage for time units (default value stored as [])
      TimeUnit_
   end
   
   % PUBLIC METHODS
   methods      
      
      function Ts = get.Ts(sys)
         % GET function for Ts property: Delegate to getTs method (implemented
         % by subclasses)
         Ts = getTs_(sys);
      end
      
      function sys = set.Ts(sys,Ts)
         % SET function for Ts property: Validate data and defer to setTs_
         try
            sys = setTs_(sys,ltipack.utValidateTs(Ts));
         catch ME
            throw(ME)
         end
      end

      function Value = get.TimeUnit(sys)
         % GET function for TimeUnit property
         Value = sys.TimeUnit_;
         if isempty(Value)
            Value = 'seconds';  % default
         end
      end

      function sys = set.TimeUnit(sys,Value)
         % SET function for TimeUnit property
         if strcmp(Value,'')
            TU = 'seconds';  % for SITB backward compatibility
         else
            TU = ltipack.matchKey(Value,ltipack.getValidTimeUnits());
            if isempty(TU)
               error(message('Control:ltiobject:setTimeUnit'))
            end
         end
         if ~strcmp(TU,sys.TimeUnit)
            sys = setTimeUnit_(sys,TU);
         end
      end

   end
   
   %% PROTECTED METHODS
   methods (Access = protected)

      function sys = setTimeUnit_(sys,TU)
         % Default implementation
         if strcmp(TU,'seconds')
            sys.TimeUnit_ = [];
         else
            sys.TimeUnit_ = TU;
         end
      end

      sys = chgTimeUnit_(sys,newUnits)

      function boo = isct_(sys)
         % True for single-rate continuous-time dynamic systems.
         Ts = getTs_(sys);
         boo = (isscalar(Ts) && Ts==0);
      end

      function boo = isdt_(sys)
         % True for single-rate discrete-time dynamic systems.
         Ts = getTs_(sys);
         boo = (isscalar(Ts) && (Ts~=0 || isstatic_(sys)));
      end
      
   end
   

   methods (Hidden)
      
      function dispTs(sys,StaticFlag)
         % Displays system name and sampling time info
         Name = sys.Name;
         if ~isempty(Name)
            disp(getString(message('Control:ltiobject:DispName',Name)))
         end
         % Display sample time (for discrete-time models only)
         Ts = getTs_(sys); %#ok<*PROPLC> 
         if ~StaticFlag && Ts~=0
            if Ts<0
               Msg = message('Control:ltiobject:DispTs1');
            else
               Msg = message('Control:ltiobject:DispTs2',sys.TimeUnit,sprintf('%0.5g',Ts));
            end
            disp(getString(Msg))
         end
      end
        
      function sys = alignSampleTime(sys,refTs,refTU)
         % Aligns the sample time and time units of SYS with the target 
         % values REFTS and REFTU.
         % Align time units
         sys = chgTimeUnit(sys,refTU);
         % Align sample times
         Ts = getTs_(sys);
         if Ts~=refTs
            if (Ts<0 && refTs>0) || (Ts>0 && refTs<0) || isstatic(sys)
               % Inherit REFTS
               sys = setTs_(sys,refTs);
            else
               % Incompatible sample times
               error(message('Control:combination:SampleTimeMismatch'))
            end
         end
      end
      
      function [sys1,sys2] = matchSamplingTime(sys1,sys2)
         % Checks that SYS1 and SYS2 share the same sampling time and time units
         % (ignoring static models). On output, both systems have the same Ts 
         % and TimeUnit values, and an error is thrown when Ts or TimeUnit are 
         % incompatible.
         Ts1 = getTs_(sys1);
         Ts2 = getTs_(sys2);
         SameTU = isequal(sys1.TimeUnit_,sys2.TimeUnit_);
         if ~SameTU || Ts1~=Ts2
            DynamicFlag1 = ~isstatic_(sys1);
            DynamicFlag2 = ~isstatic_(sys2);
            % Reconcile time units
            if ~SameTU
               if DynamicFlag1 && DynamicFlag2
                  error(message('Control:combination:TimeUnitMismatch'))
               elseif DynamicFlag2
                  % Note: Do not use TimeUnit_ (won't correctly update stored value
                  % of frequency vector in FRD models). Comes into play in
                  % frd(1:10,1:10,'TimeUnit','min','FrequencyUnit','Hz') + tf(-1)
                  sys1.TimeUnit = sys2.TimeUnit;
               else
                  sys2.TimeUnit = sys1.TimeUnit;
               end 
            end
            % Reconcile sampling times
            if Ts1~=Ts2
               HardValue1 = (Ts1>0 || (Ts1==0 && DynamicFlag1));
               HardValue2 = (Ts2>0 || (Ts2==0 && DynamicFlag2));
               if (HardValue1 && HardValue2) || (DynamicFlag1 && DynamicFlag2 && xor(Ts1==0,Ts2==0))
                  % Inconsistent when both sample times are fully specified, or both
                  % models have dynamics and one is continuous while the other is
                  % discrete
                  error(message('Control:combination:SampleTimeMismatch'))
               elseif Ts1>0 || Ts2>0 || ~(DynamicFlag1 || DynamicFlag2)
                  % Note: Ts immaterial when both models are static
                  Ts = max(Ts1,Ts2);
               else
                  Ts = DynamicFlag1 * Ts1 + DynamicFlag2 * Ts2;
               end
               sys1 = setTs_(sys1,Ts);
               sys2 = setTs_(sys2,Ts);
            end
         end
      end
                        
      function varargout = matchSamplingTimeN(varargin)
         % Checks that SYS1,SYS2,...,SYSN share the same sampling time and time 
         % units (ignoring static models). On output, all systems have 
         % the same sampling time. This version of matchSamplingTime is
         % designed for N-ary operations like REPLACEBLOCK. 
         varargout = varargin;
         nsys = length(varargin);
         % Acquire sampling times and time units (performance optimization)
         TsLog = zeros(nsys,1);
         TU = varargin{1}.TimeUnit_;
         SameTU = true;
         for j=1:nsys
            TsLog(j) = getTs_(varargin{j});
            SameTU = SameTU && isequal(TU,varargin{j}.TimeUnit_);
         end
         % Find common sampling time TS if not all equal
         if ~SameTU || any(diff(TsLog))
            Ts = TsLog(1); %#ok<*PROP> 
            DynamicFlag = ~isstatic(varargin{1});
            for j=2:nsys
               Tsj = TsLog(j);
               dfj = ~isstatic(varargin{j});
               % Reconcile time units
               if ~(SameTU || isequal(TU,varargin{j}.TimeUnit_))
                  if DynamicFlag && dfj
                     error(message('Control:combination:TimeUnitMismatch'))
                  elseif dfj
                     TU = varargin{j}.TimeUnit_;
                  end
               end
               % Reconcile sampling times
               if Tsj~=Ts
                  HardValue = (Ts>0 || (Ts==0 && DynamicFlag));
                  hvj = (Tsj>0 || (Tsj==0 && dfj));
                  if (HardValue && hvj) || (DynamicFlag && dfj && xor(Ts==0,Tsj==0))
                     % Inconsistent when both sample times are fully specified, or both
                     % models have dynamics and one is continuous while the other is
                     % discrete
                     error(message('Control:combination:SampleTimeMismatch'))
                  elseif Ts>0 || Tsj>0 || ~(DynamicFlag || dfj)
                     % Note: Ts immaterial when both models are static
                     Ts = max(Ts,Tsj);
                  else
                     Ts = DynamicFlag * Ts + dfj * Tsj;
                  end
               end
               DynamicFlag = DynamicFlag || dfj;
            end
            % Adjust sampling times
            jx = find(TsLog~=Ts);
            for ct=1:numel(jx)
               varargout{jx(ct)} = setTs_(varargout{jx(ct)},Ts);
            end
            % Adjust time units
            if ~SameTU
               for ct=1:nsys
                  varargout{ct}.TimeUnit_ = TU;
               end
            end
         end
      end
      
   end
   
end
