#!../src/tops  -i -s ../sys  -u ../usr
/*
Program Tops - a stack-based computing environment
Copyright (C) 1999-2006  Dale R. Williamson

Author: Dale R. Williamson <dale.williamson@prodigy.net>

File test/infix_equ  November 2005
Test infix forms of matrix equations.

*/
   if(missing("zggev")) 
      halt(nl(dot("Lapack not found for test infix_equ")));

/* Note: Function zggev() is used here because matrix A is complex. 
   If A were real, dgeev2 could be used--see example in sys/lapack.v 
   where left and right eigenvectors are verified and normalized. */

   indexbase(1);

//----------------------------------------------------------------------

// Ways to declare the unit imaginary number used below:
   i = complex(0,1);
   i = sqrt(-1);
   i = -1^0.5;

/* Frequency response problem.

      Second order model:

         m*xdd + k*x + i*(alpha*k)*x = F*sin(Omega*t)

      Converting second order model into first order state space model:

             |xd|
         y = |  |
             | x|

         B * yd + A * y = Y(t)

         |m   0| |xdd|   | 0   k*(1 + i*alpha)| |xd|   |F|
         |     |*|   | + |                    |*|  | = | |*sin(Omega*t)
         |0   1| | xd|   |-1   0              | | x|   |0| 

      Model parameters:
         m = 2
         k = 200
         Viscous damping: zeta = 0.02 (2% crit)
         Equivalent strain proportional damping: i*(alpha*k)*x
         alpha = 2*zeta = 0.04

      Although m and k in this example are 1-by-1 matrices, most of the
      expressions below are written in a form that works for any matrix
      size.

      Forcing function:
         F = 2

      Expected response:
         Static amplitude: Xo = F/k = 2/200 = 0.01
         Resonant frequency: omeg = (k/m)^0.5 = 10
         Resonant amplitude: X = 1/(2*zeta)*Xo = (1/0.04)*.01 = 0.25
         Resonant velocity: Xd = X*omeg = 0.25*10 = 2.5

      Function zggev() computes eigenvalues and right and left eigen-
      vectors of the complex, unsymmetric system:

         (Alpha, Beta, VL, VR) = zggev(A, B*(1 + 0*i));

      Left and right eigenvectors, VL and VR, satisfy these relation-
      ships:

         A * VR = B * VR *\ lam
         conj(VL)' * A = lam \* conj(VL)' * B

      where multiply operators \* or *\ perform matrix multiplication 
      when, respectively, the left or right term is a diagonal matrix 
      stored in a column vector, and conj(X)' is the transpose of the
      complex conjugate of X.

      Left and right eigenvectors diagonalize the A and B matrices and 
      allow a frequency response solution to be computed using set of 
      scalar, uncoupled generalized coordinates. 

      The complex column vector of eigenvalues, lam, is given by numera-
      tor Alpha over denominator Beta returned by zggev():

         lam = Alpha ./ Beta

----------------------------------------------------------------------*/

   m = [2];
   k = [200];
   F = [1];

   (phi, omeg2) = dsygv(m, k); // real eigenanalysis on sym matrices

   omeg = omeg2^0.5;

   zeta = 0.02;
   alpha = 2*zeta;
   eps = 1E-08;

   X = 1/(2*zeta)*F/k;
   Xd = X*omeg;

/* Within brackets, comma and semicolon are operators that park and 
   pile matrices, respectively (the Note below shows precedence of
   the comma and semicolon operators).  
   Here is building the A and B matrices shown above: */ 

      A = [null(dims(k)) , k ; -ones(rows(k)) , null(dims(k))] 
          + i*[null(2*rows(k),cols(k)) , [alpha*k ; null(dims(k))]];

      B = [m , null(dims(m)) ; null(dims(m)) , identity(rows(m))];

/* Run zggev().  Since zggev() requires complex matrices, real matrix B
   is multiplied by (1 + 0*i) to make it complex: */

      (Alpha, Beta, VL, VR) = zggev(A, B*(1 + 0*i));

      lam = Alpha ./ Beta; // complex frequencies

// Eigenanalysis check:
      null1 = A * VR - B * VR *\ lam;
      null2 = conj(VL)' * A - lam \* conj(VL)' * B; 

      NULL = filter([null1 , null2], eps);

// Function null?() returns true if a matrix is null:
      if(!null?(NULL)) nl(dot("Error in eigenanalysis"));
      else nl(dot("Eigenanalysis check ok"));

/* Normalize VR and VL so that conj(VL)' * B * VR = Identity.  
   Function mpydg() returns, in a column vector, just the diagonal of 
   the square matrix produced by this triple multiplication (off-dia-
   gonal terms are zero, and need not be computed), and then sq() puts
   it into a square matrix: */
      fac = sq(mpydg(conj(VL)', (B * VR)).^-0.5); // square
      psi = VR * fac;
      gam = conj(VL * fac)';

/* This shows a much faster way to normalize VR and VL using multiply 
   operator *\ for diagonal matrices that are stored as column vectors.
   Here, fac1 is a column vector while fac above is a diagonal square 
   matrix: */
      fac1 = mpydg(conj(VL)', B * VR).^-0.5; // vector
      psi1 = VR *\ fac1;
      gam1 = conj(VL *\ fac1)';

/* Checking normalization of complex left- and right-hand modes: */
      null3 = ones(rows(psi)) - mpydg((gam * B), psi);
      null4 = lam - dg(gam * A * psi);

      NULL = filter([psi1 - psi , gam1 - gam , null3, null4], eps);
      if(!null?(NULL)) nl(dot("Error normalizing modes"));
      else nl(dot("Modes normalized ok"));
  
// Transfer function at resonance:
      Omeg = omeg; // driving Omeg is at natural frequency omeg
      (lamr, lami) = (Re(lam), Im(lam));
      NUMER = lamr - i*(lami + Omeg); // add scalar Omeg to vector lami
      DENOM = lamr^2 + (lami + Omeg)^2;
      H = NUMER./DENOM; // transfer function

/* Modal response = eta = H * left half col partition of gam.  Transfer
   function matrix H is a diagonal matrix stored in a column vector, so
   operator \* is used to do the multiplication: */
      eta = H \* gam[*, 1:cols(gam)/2]; // gam all rows, left half cols

// State space response:
      y = psi * eta;

// Displacement and velocity from state space response:
      (xd, x) = (Re(y[1,*]), Im(y[2,*]));

      NULL = filter([abs(xd) - Xd , abs(x) - X], eps);
      if(!null?(NULL)) nl(dot("Error in frequency response"));
      else nl(dot("Frequency response ok"));

/*----------------------------------------------------------------------

   Note 

   Precedence of comma and semicolon used inside brackets are compared 
   to precedence of the math operators + and *.

   This is taken from file test/parser:

   Parsing 3: bracket tests.

   Brackets are used for constructing arrays.  Inside brackets, comma
   and semicolon are operators.  Semicolon piles two submatrices on top
   of each other, and comma parks them beside each other.

   Precedence of semicolon and comma can be compared to precedence of
   math operators + and *.  In mathematical operations, * has higher
   precedence than + and so multiplcation is performed before addition
   unless there are parentheses to control the operations.
  
   In terms of precedence, but controlled by [ ] instead of ( ), semi-
   colon is to + as comma is to *.  Here are some examples:

      Precedence for numbers:     A=((1+2)*3)   B=(1+2*3)

      Precedence for submatrices: A=[[a;b],c]   B=[a;b,c]

      (Precedence for submatrices also requires the appropriate rows
      and columns to be compatible)
*/
