/* 
   MatrixExp
   Function for calculating exponential of real square matrix.
   Based on pijt.c by Chris Jackson Copyright (C) 2001

   Changes:
   * Matrices are assumed to be filled by columns, not rows
   * Use LAPACK routines instead of EISPACK
   * const correctness
   * Ported to C++ with corresponding minor syntactic changes
*/ 

#include <config.h>
#include <matrix/lapack.h>

#include <stdexcept>
#include <cmath>

#include <matrix/matexp.h>

using std::pow;
using std::exp;
using std::log;
using std::runtime_error;

static int c_1 = 1;

/* index to treat a vector as a matrix, assuming matrix is filled by columns */
inline int MI(int i, int j, int ncols) {return j*ncols + i;}

/*
static void MatTranspose(double *AT, double const *A, int n)
{
  // Transpose a square matrix
  for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
      AT[MI(i,j,n)] = A[MI(j,i,n)];
}
*/

static void MultMat(double *AB, double const *A, double const *B, int n)
{
  /* Multiply two square matrices together */
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
      AB[MI(i, j, n)] = 0;
      for (int k = 0; k < n; ++k) 
	AB[MI(i, j, n)] += A[MI(i, k, n)] * B[MI(k, j, n)];
    }
  }
}

/*
static void 
MultMatDiag(double *AB, double const *diagA, double const *B, int n)
{
// Pre-multiply a square matrix by a diagonal matrix (given by a vector)
  for (int i = 0; i < (n*n); ++i) {
    AB[i] = 0;
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      AB[MI(i, j, n)] += diagA[i] * B[MI(i, j, n)];
    }
  }
}
*/

static void FormIdentity(double *A, int n)
{
  /* Set A to be an n x n identiy matrix */   
  for (int i = 0; i < (n*n); ++i) {
    A[i] = 0;
  }
  for (int i = 0; i < n; ++i) {
    A[MI(i, i, n)] = 1;
  }
}

/*
static bool repeated_entries(double const *vec, int n)
{
// Test whether a vector has any non-unique entries
  for (int i = 1; i < n; ++i) {
    for (int j = 0; j < i; ++j) {
      if (vec[j] == vec[i]) {
	return true;
      }
    }
  }
  return false;
}
*/

static void solve (double *X, double const *A, double const *B, int n)
{
  /* Solve AX = B, where all matrices are square */

  int N = n*n;
  double *Acopy = new double[N];
  F77_DCOPY(&N, A, &c_1, Acopy, &c_1);
  F77_DCOPY(&N, B, &c_1, X, &c_1);
  /*
  for (int i = 0; i < N; i++) {
    Acopy[i] = A[i];
    X[i] = B[i];
  }
  */
  int *ipiv = new int[n];
  int info = 0;
  F77_DGESV (&n, &n, Acopy, &n, ipiv, X, &n, &info);
  if (info != 0) {
    throw runtime_error("Unable to solve linear equations");
  }
  
  delete [] Acopy;
  delete [] ipiv;
}

static void MatrixExpSeries(double *expmat, double const *A, int n, double t)
{
  /* Calculate a matrix exponential using a power series approximation */
  /* Adapted from mexp in Jim Lindsey's rmutil library */  
  
  const int order = 20;   /* number of terms in series */
  const int underflow_correct = 3;
    
  double *At = new double[n*n];
  double *Apower = new double[n*n];
  double *Temp = new double[n*n];

  for (int i = 0; i < (n*n); ++i) {
    At[i] = A[i] * t / pow(2.0, underflow_correct);
  }
  FormIdentity(expmat, n);
  FormIdentity(Apower, n);
  for (int i = 1; i <= order; i++) {
    MultMat(Temp, At, Apower, n);
    for (int j = 0; j < (n*n); ++j){
      Apower[j] = Temp[j] / i;
      expmat[j] += Apower[j];
    }
  }
  for (int i = 0; i < underflow_correct; ++i){
    MultMat(Temp, expmat, expmat, n);
    for (int j = 0; j < (n*n); ++j)
      expmat[j] = Temp[j];
  }
    
  delete [] At;
  delete [] Apower;
  delete [] Temp;
}

/* Debugging: suppress the LAPACK routine */

void MatrixExp(double *expmat, double const *mat, int n, double t) {
    MatrixExpSeries(expmat, mat, n, t);
}

static void
padeseries (double *Sum, double const *A, int n, int order, 
            double scale, double *Temp)
{
  double zero = 0;
  int N = n*n;
  FormIdentity(Sum, n);
  for (int j = order; j >= 1; --j) {
    double s = (order-j+1) / (j*(2*order-j+1) * scale);
    F77_DGEMM ("n","n", &n, &n, &n, &s, Sum, &n, A, &n, &zero, Temp, &n);
    F77_DCOPY(&N, Temp, &c_1, Sum, &c_1);
    /*
    MultMat(Temp, Sum, A, n);
    for (int i = 0; i < N; ++i) {
      Sum[i] = Temp[i] * s;
    }
    */
    for (int r = 0; r < n; ++r) {
      Sum[r*n+r] += 1;
    }

  }
}

void 
MatrixExpPade(double *ExpAt, double const *A, const int n, double t)
{
  /* Calculate exp(A*t) by diagonal Pade approximation with scaling and
     squaring */

  int order = 8;
  int N = n*n;
  double *workspace = new double[4*N];
  double * const Temp = workspace;
  double * const At = workspace + N;
  double * const Num = workspace + 2*N;
  double * const Denom = workspace + 3*N;

  // Take a copy of the matrix and scale it by t
  F77_DCOPY(&N, A, &c_1, At, &c_1);
  F77_DSCAL(&N, &t, At, &c_1);
  /*
  for (int i = 0; i < N; ++i) {
    At[i] = A[i] * t;
  }
  */

  // Scale the matrix by a power of 2 
  double l1 = F77_DLANGE("1", &n, &n, At, &n, 0); //L-1 norm
  double linf = F77_DLANGE("i", &n, &n, At, &n, Temp); //L-Infinity norm
  /* 
     sqrt(l1 * linf) is an upper bound on the L2 norm of the matrix At
     (i.e the largest eigenvalue). We want to take the log, to base 2
     of this to get the smallest K, st ||At/2^K|| <= 1.
  */
  double K = (log(l1) + log(linf))/log(4.0);
  int npower = static_cast<int>(K) + 1;
  if (npower < 0) {
    npower = 0;
  }
  double scale = pow(2.0, npower);

  // Calculate exp(A/scale) by Pade series 

  padeseries (Num, At, n, order, scale, Temp);
  for (int i = 0; i < N; ++i) {
    At[i] = -At[i];
  }
  padeseries (Denom, At, n, order, scale, Temp);
  solve(ExpAt, Denom, Num, n);

  // Now repeatedly square the result

  for (int i = 0; i < npower; ++i) {
    /*
      F77_DCOPY(&N, ExpAt, &c_1, Temp, &c_1);
      double one = 1;
      double zero = 0;
      F77_DGEMM ("n","n", &n, &n, &n, &one, Temp, &n, Temp, &n, &zero, ExpAt,&n);
    */
    for (int j = 0; j < N; ++j) {
      Temp[j] = ExpAt[j];
    }
    MultMat(ExpAt, Temp, Temp, n);
  }

  delete [] workspace;
}


syntax highlighted by Code2HTML, v. 0.9.1