/* Copyright 2008 Mike Williams (mwill@jlab.org)
 *
 * This file is part of qft++.
 *
 * qft++ 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.
 * 
 * qft++ 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 qft++.  If not, see <http://www.gnu.org/licenses/>.
 */
//_____________________________________________________________________________
/** @file tensor.cxx
 *  @author Mike Williams
 *
 *  @brief Provides example usage of the tensor module
 *
 *  This tutorial gives a number of examples of how to perform common tensor
 *  operations using the qft++ tensor package.
 */
//_____________________________________________________________________________

#include <getopt.h>
#include "qft++/topincludes/relativistic-quantum-mechanics.hh"
#include "qft++/topincludes/tensor.hh"

using namespace std;
//_____________________________________________________________________________

/// Prints program usage to the screen
void PrintUsage();

/// Prints a line accross the screen
void PrintLine(char __c){ 
  for(int i = 0; i < 80; i++) cout << __c; 
  cout << endl;
}
//_____________________________________________________________________________

int main(int __argc,char *__argv[]){

  /*__________________________Parse the Command Line_________________________*/
  int c;
  //extern char* optarg;
  //extern int optind;
  
  while((c = getopt(__argc,__argv,"h")) != -1){
    switch(c){
    case 'h': // help option
      PrintUsage();
      return EXIT_SUCCESS;
      break;
    default:
      break;
    }
  }  

  /*_____________________________Creating Tensors____________________________*/
  // Tensor is a template class, here we'll work with double's and 
  // complex<double>'s...but you could use any data type that defines the 
  // proper operators. The constructor argument is the rank.
  Tensor<double> x1(1),x2(2),x3(3),x4(4);
  Tensor<complex<double> > y1(1);
  Vector4<double> v;
  LeviCivitaTensor eps; // totally anti-symetric 4th rank tensor
  MetricTensor g; // g_{mu,nu} 

  /*_____________________________Setting Tensors_____________________________*/
  // The metric and Levi-Civita tensors are set when they're created. To set
  // elements of our other tensors we have several options. We can either set
  // them using other Tensor's (using the = operator), or by direct access to
  // the elements.

  PrintLine(':');
  cout << "We've set up the following tensors: " << endl;
  cout << "tensors storing double's:" << endl;
  // element access
  for(int e = 0; e < 4; e++) {
    x1(e) = e;
    x2(e,e) = 1.0; // make it diagnol
  }
  cout << "->x1: " << x1 << endl;
  cout << "->x2: " << x2 << endl;
 
  // using the assignment operator
  x3 = x1 % x2; // tensor outer product
  cout << "->x3: x1^{mu}x2^{nu,rho} (printing only defined for rank <= 2)" 
       << endl;
  x4 = x1 % x3;
  cout << "->x4: x1^{mu}x3^{nu,rho,pi} (printing only defined for rank <= 2)" 
       << endl;
  // just make sure these don't throw errors
  x3.Symmetric();
  x3.AntiSymmetric();

  cout << "->metric: g^{mu,nu}: " << g << endl;
  cout << "->Levi-Civita: epsilon^{mu,nu,rho,sigma} (not printable)" 
       << endl;

  cout << "4-vectors storing double's:" << endl;
  double mass = 0.13957;
  v.SetP4(sqrt(1 + mass*mass),0,0,1.);
  cout << "->v: " << v << endl;

  cout << "tensors storing complex double's:" << endl;
  y1(0) = complex<double>(0.,1.);
  y1(3) = complex<double>(0.,1.);
  cout << "->y1: " << y1 << endl;

  /*_____________________________Basic Operations____________________________*/
  PrintLine(':');

  cout << "Tensor contractions:" << endl;

  cout << "permute: " << endl;
  cout << "x2^{mu,nu}: " << x2 << endl;

  // To contract the last index of x with the 1st index of y, just do x*y
  cout << "contraction of 1 index: " << endl;
  cout << "x1_{mu}x1^{mu}:\nx1*x1\t->\t" << x1*x1 << endl;
  cout << "x1_{mu}x2^{mu,nu}:\nx1*x2\t->\t" << x1 * x2 << endl;
  cout << "x1_{mu}x3^{mu,nu,rho}:\nx1*x3\t->\t" << x1 * x3 << endl;
  Tensor<double> x3_1=x3;
  cout << "x1 * ((x3.Permute(0,2)))\t->\t" << x1 * (x3_1.Permute(0,2)) << endl;

  cout << "x1_{rho}x3^{mu,nu,rho}:\nx3*x1\t->\t" << x3 * x1 << endl;
  Tensor<double> x3_2=x3;
  cout << "x1_{nu}x3^{mu,nu,rho}:\n(x3.Permute(1,2))*x1\t->\t" << (x3_2.Permute(1,2)) * x1 << endl;
  Tensor<double> x3_3=x3;
  cout << "x1_{nu}x3^{mu,nu,rho}:\nx1 * (x3.Permute(0,1))\t->\t" << x1 * (x3_3.Permute(0,1)) << endl;
  Tensor<double> x3_4=x3;
  cout << "x1_{nu}x3^{mu,nu,rho}:\nx1 * ((x3.Permute(1,2)).Permute(0,2))\t->\t" << x1 * ((x3.Permute(1,2)).Permute(0,1)) << endl;
 cout << "sqrt(v^{mu}v_{mu}):\nsqrt(v*v)\t->\t" << sqrt(v*v) << endl;

  // To contract the last n indicies of x with the 1st n indicies of y, do
  // x.Contract(y,n). If n is the rank of either x or y, then you can just do
  // (x|y)...a tensor inner product.
  cout << "contraction of multiple indicies:" << endl;
  cout << "x2_{mu,nu}x3^{mu,nu,rho}:\n(x2|x3)\t->\t" << (x2|x3) << endl;
  cout << "x1^{mu}x2^{nu,rho}x3_{mu,nu,rho}:\n((x1%x2)|x3)\t->\t" << ((x1%x2)|x3) << endl;
  cout << "(x1^{mu}x2^{nu,rho} + 2*x3^{mu,nu,rho})x3_{mu,nu,rho}:\n(((x1%x2) + 2*x3)|x3)\t->\t"
       << (((x1%x2) + 2*x3)|x3) << endl;
  cout << "epsilon^{mu,nu,rho,pi} x2_{mu,nu}:\n(eps|x2)\t->\t" << (eps|x2) << endl;
  cout << "x3(mu,nu,rho)x4(pi,mu,nu,rho):\nx4.Contract(x3, 3)->\t" << x4.Contract(x3, 3) << endl;
  cout << "x3(mu,nu,rho)x4(pi,mu,nu,rho):\nx4.Permute(1,x4.Rank())\t x3|x4->\t" << (x4|x3) << endl;
  cout << "x3(pi,mu,nu)x4(pi,mu,nu,rho):\nx3.Contract(x4, 3)->\t" << x3.Contract(x4, 3) << endl;
  cout << "x3(pi,mu,nu)x4(pi,mu,nu,rho):\nx(x3|x4)->\t" << (x3|x4) << endl;
  cout << "etc...as complicated as you want, it's still easy in the code."
       << endl;

  PrintLine(':');  
  // We can also obtain symmeterized versions of tensors.
  cout << "Symmetrization is easy: " << endl;
  Tensor<double> xx(2);
  for(int i = 0; i < 4; i++) xx(i,i) = 1.0;
  xx(2,1) = -2.;
  xx(2,3) = 3.;
  cout << "for a tensor:\n->" << xx << endl;
  cout << "symmetric:\nxx.Symmetric()\t->\t" << xx.Symmetric() << endl;
  cout << "anti-symmetric:\nxx.AntiSymmetric()\t->\t" << xx.AntiSymmetric() << endl;
  cout << "this works for any rank." << endl;

  // Lorentz transformations are done either thru a general transformation
  // tensor via x.Transform(lambda), where lambda_{mu,nu} is a 2nd rank
  // tensor...or to simply boost, you can use either a 1st rank tensor via
  // x.Boost(y) to boost to y's rest frame or specify the 3 boost vector
  // components and using x.Boost(bx,by,bz). You can also rotate by providing
  // the 3 euler angles via x.Rotate(alpha,beta,gamma).
  cout << "Lorentz transformations are also easy:" << endl;
  Vector4<double> v_cm(v);
  v_cm.Boost(v);
  cout << "boost to v's rest frame:\n->v_cm:" << v_cm << endl; 
  cout << "we can boost to any frame, rotate, etc..." << endl;

  /*_________________________Special 4-Vector Methods________________________*/
  PrintLine(':');
  cout << "There are also a number of special methods defined for 4-vectors:"
       << endl;
  cout << "4-vector v:->" << v << endl;
  cout << "invariant mass:-> " << v.Mass() << endl;
  cout << "beta:-> " << v.Beta() << endl;
  cout << "cos(theta):-> " << v.CosTheta() << endl;
  cout << "see the Vector4 class documentation for a complete list." << endl;
  PrintLine(':');
  /*_________________________Orbital tensors________________________*/
  Vector4<double> v1; 
  v1.SetP4(sqrt(1. + mass*mass),0,0,1.);
  Vector4<double> v2;
  v2.SetP4(sqrt(1. + mass*mass),0.2,0.5,0.3);
  Spin L0=0;
  Spin L1=1;
  Spin L2=2;

  OrbitalTensor orbTensor0(L0);
  orbTensor0.SetP4(v1, v2);

  OrbitalTensor orbTensor1(L1);
  orbTensor1.SetP4(v1, v2);

  OrbitalTensor orbTensor2(L2);
  orbTensor2.SetP4(v1, v2);

  cout << "orbTensor0:-> " << orbTensor0 << endl;
  cout << "orbTensor1:-> " << orbTensor1 << endl;
  cout << "orbTensor2:-> " << orbTensor2 << endl;

  return EXIT_SUCCESS;
}
//_____________________________________________________________________________

void PrintUsage(){ 
  cout << "Usage: tensor " << endl;
  cout << "This executable provides a number of example usages of the tensor "
       << "package. Run\nthe executable to see what's being done, then look at"
       << " the source file to see \nhow it's done in the code."
       << endl;
}
//_____________________________________________________________________________
