## Copyright (C) 2009, 2010   Lukas F. Reichlin
##
## This file is part of LTI Syncope.
##
## LTI Syncope is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## LTI Syncope is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with LTI Syncope.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## Common code for the time response functions step, impulse and initial.

## Author: Lukas Reichlin <lukas.reichlin@gmail.com>
## Created: October 2009
## Version: 0.2

function [y, t, x_arr] = __time_response__ (sys, resptype, plotflag, tfinal, dt, x0, sysname)

  if (! isa (sys, "ss"))
    sys = ss (sys);                                    # sys must be proper
  endif

  if (is_real_vector (tfinal) && length (tfinal) > 1)  # time vector t passed
    dt = tfinal(2) - tfinal(1);                        # assume that t is regularly spaced
    tfinal = tfinal(end);
  endif

  [A, B, C, D, tsam] = ssdata (sys);

  discrete = ! isct (sys);                             # static gains are treated as analog systems
  tsam = abs (tsam);                                   # use 1 second if tsam is unspecified (-1)

  if (discrete)
    if (! isempty (dt))
      warning ("time_response: argument dt has no effect on sampling time of discrete system");
    endif

    dt = tsam;
  endif

  [tfinal, dt] = __sim_horizon__ (A, discrete, tfinal, dt);

  if (! discrete)
    sys = c2d (sys, dt, "zoh");
  endif

  [F, G] = ssdata (sys);                               # matrices C and D don't change

  n = rows (F);                                        # number of states
  m = columns (G);                                     # number of inputs
  p = rows (C);                                        # number of outputs

  ## time vector
  t = reshape (0 : dt : tfinal, [], 1);
  l_t = length (t);

  switch (resptype)
    case "initial"
      str = ["Response of ", sysname, " to Initial Conditions"];
      yfinal = zeros (p, 1);

      ## preallocate memory
      y = zeros (l_t, p);
      x_arr = zeros (l_t, n);

      ## initial conditions
      x = reshape (x0, [], 1);                         # make sure that x is a column vector

      if (n != length (x0) || ! is_real_vector (x0))
        error ("initial: x0 must be a real vector with %d elements", n);
      endif

      ## simulation
      for k = 1 : l_t
        y(k, :) = C * x;
        x_arr(k, :) = x;
        x = F * x;
      endfor

    case "step"
      str = ["Step Response of ", sysname];
      yfinal = dcgain (sys);

      ## preallocate memory
      y = zeros (l_t, p, m);
      x_arr = zeros (l_t, n, m);

      for j = 1 : m                                    # for every input channel
        ## initial conditions
        x = zeros (n, 1);
        u = zeros (m, 1);
        u(j) = 1;

        ## simulation
        for k = 1 : l_t
          y(k, :, j) = C * x + D * u;
          x_arr(k, :, j) = x;
          x = F * x + G * u;
        endfor
      endfor

    case "impulse"
      str = ["Impulse Response of ", sysname];
      yfinal = zeros (p, m);

      ## preallocate memory
      y = zeros (l_t, p, m);
      x_arr = zeros (l_t, n, m);

      for j = 1 : m                                    # for every input channel
        ## initial conditions
        u = zeros (m, 1);
        u(j) = 1;

        if (discrete)
          x = zeros (n, 1);                            # zero by definition 
          y(1, :, j) = D * u / dt;
          x_arr(1, :, j) = x;
          x = G * u / dt;
        else
          x = B * u;                                   # B, not G!
          y(1, :, j) = C * x;
          x_arr(1, :, j) = x;
          x = F * x;
        endif

        ## simulation
        for k = 2 : l_t
          y (k, :, j) = C * x;
          x_arr(k, :, j) = x;
          x = F * x;
        endfor
      endfor

      if (discrete)
        y *= dt;
        x_arr *= dt;
      endif

    otherwise
      error ("time_response: invalid response type");

  endswitch

  
  if (plotflag)                                        # display plot

    ## TODO: Set correct titles, especially for multi-input systems

    stable = isstable (sys);
    outname = get (sys, "outname");
    outname = __labels__ (outname, "y_");

    if (strcmp (resptype, "initial"))
      cols = 1;
    else
      cols = m;
    endif

    if (discrete)                                      # discrete system
      for k = 1 : p
        for j = 1 : cols

          subplot (p, cols, (k-1)*cols+j);

          if (stable)
            stairs (t, [y(:, k, j), yfinal(k, j) * ones(l_t, 1)]);
          else
            stairs (t, y(:, k, j));
          endif

          grid ("on");

          if (k == 1 && j == 1)
            title (str);
          endif

          if (j == 1)
            ylabel (sprintf ("Amplitude %s", outname{k}));
          endif

        endfor
      endfor

      xlabel ("Time [s]");

    else                                               # continuous system
      for k = 1 : p
        for j = 1 : cols

          subplot (p, cols, (k-1)*cols+j);

          if (stable)
            plot (t, [y(:, k, j), yfinal(k, j) * ones(l_t, 1)]);
          else
            plot (t, y(:, k, j));
          endif

          grid ("on");

          if (k == 1 && j == 1)
            title (str);
          endif

          if (j == 1)
            ylabel (sprintf ("Amplitude %s", outname{k}));
          endif

        endfor
      endfor

      xlabel ("Time [s]");

    endif 
  endif

endfunction


function [tfinal, dt] = __sim_horizon__ (A, discrete, tfinal, Ts)

  ## code based on __stepimp__.m of Kai P. Mueller and A. Scottedward Hodel

  TOL = 1.0e-10;                                       # values below TOL are assumed to be zero
  N_MIN = 50;                                          # min number of points
  N_MAX = 2000;                                        # max number of points
  N_DEF = 1000;                                        # default number of points
  T_DEF = 10;                                          # default simulation time

  n = rows (A);
  eigw = eig (A);

  if (discrete)
    ## perform bilinear transformation on poles in z
    for k = 1 : n
      pol = eigw(k);
      if (abs (pol + 1) < TOL)
        eigw(k) = 0;
      else
        eigw(k) = 2 / Ts * (pol - 1) / (pol + 1);
      endif
    endfor
  endif

  ## remove poles near zero from eigenvalue array eigw
  nk = n;
  for k = 1 : n
    if (abs (real (eigw(k))) < TOL)
      eigw(k) = 0;
      nk -= 1;
    endif
  endfor

  if (nk == 0)
    if (isempty (tfinal))
      tfinal = T_DEF;
    endif

    if (! discrete)
      dt = tfinal / N_DEF;
    endif
  else
    eigw = eigw(find (eigw));
    eigw_max = max (abs (eigw));

    if (! discrete)
      dt = 0.2 * pi / eigw_max;
    endif

    if (isempty (tfinal))
      eigw_min = min (abs (real (eigw)));
      tfinal = 5.0 / eigw_min;

      ## round up
      yy = 10^(ceil (log10 (tfinal)) - 1);
      tfinal = yy * ceil (tfinal / yy);
    endif

    if (! discrete)
      N = tfinal / dt;

      if (N < N_MIN)
        dt = tfinal / N_MIN;
      endif

      if (N > N_MAX)
        dt = tfinal / N_MAX;
      endif
    endif
  endif

  if (! isempty (Ts))                                  # catch case cont. system with dt specified
    dt = Ts;
  endif

endfunction
