/* testPMat.c

   November 2003, Z Yang

   This small program tests two methods or routines for calculating the 
   transition probability matrix for a time reversible Markov rate matrix.  
   The first is the routine matexp(), which uses repeated squaring and works 
   on a general matrix (not necesarily time reversible).  For large distances, 
   more squaring should be used, but I have not tested this extensively.  You
   can change TimeSquare to see its effect.  
   The second method, PMatQRev, uses a routine for eigen values and eigen 
   vectors for a symmetrical matrix.  It involves some copying and moving 
   around when some frequencies are 0.  
   For each algorithm, this program generates random frequencies and random rate 
   matrix and then calls an algorithm to calculate P(t) = exp(Q*t).

     cl -O2 -Ot -W4 testPMat.c tools.c
     cc -O3 testPMat.c tools.c -lm
     testPMat
*/

#include "paml.h"

int main(void)
{
   int n=400, noisy=0, i,j;
   int nr=10, ir, TimeSquare=10, algorithm; /* TimeSquare should be larger for large t */
   double t=5, *Q, *pi, *space, s;
   char timestr[96], *AlgStr[2]={"repeated squaring", "eigensolution"};
   
   if((Q=(double*)malloc(n*n*5*sizeof(double))) ==NULL) error2("oom");
   pi=Q+n*n; space=pi+n;

   for(algorithm=0; algorithm<2; algorithm++) {
      starttime();
      SetSeed(1234567);
      for (i=0; i<n; i++)  pi[i]=rndu();
      s=sum(pi,n);
      for (i=0; i<n; i++)  pi[i]/=s;

      for(ir=0; ir<nr; ir++) {
         printf("Replicate %d/%d ", ir+1,nr);

   	   for (i=0; i<n; i++)  
            for (j=0,Q[i*n+i]=0; j<i; j++)
               Q[i*n+j]=Q[j*n+i] = square(rndu());
         for (i=0; i<n; i++)
            for (j=0; j<n; j++)
               Q[i*n+j] *= pi[j];
         for(i=0,s=0; i<n; i++)  {  /* rescaling Q so that average rate is 1 */
            Q[i*n+i]=0; Q[i*n+i]=-sum(Q+i*n, n); 
            s-=pi[i]*Q[i*n+i];
         }

         if(noisy) {
            matout(stdout, pi, 1, n);
            matout(stdout, Q, n, n);
         }

         if(algorithm==0) 
            matexp(Q, 1, n, TimeSquare, space);
         else 
            PMatQRev(Q, pi, 1, n, space);
         printf("%s, time: %s\n", AlgStr[algorithm], printtime(timestr));
         if(noisy) 
            matout(stdout, Q, n, n);
      }
   }
   return (0);
}
