/*************************************************************************************

Grid physics library, www.github.com/paboyle/Grid

Source file: ./tests/Test_simd.cc

Copyright (C) 2015

Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: neo <cossu@post.kek.jp>

This program 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 2 of the License, or
(at your option) any later version.

This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    See the full license in the file "LICENSE" in the top level distribution directory
    *************************************************************************************/
    /*  END LEGAL */
#include <Grid/Grid.h>

using namespace Grid;

class funcPlus {
public:
  funcPlus() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = i1+i2;}
  std::string name(void) const { return std::string("Plus"); }
};
class funcMinus {
public:
  funcMinus() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = i1-i2;}
  std::string name(void) const { return std::string("Minus"); }
};
class funcTimes {
public:
  funcTimes() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = i1*i2;}
  std::string name(void) const { return std::string("Times"); }
};
class funcDivide {
public:
  funcDivide() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = i1/i2;}
  std::string name(void) const { return std::string("Divide"); }
};
class funcConj {
public:
  funcConj() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = conjugate(i1);}
  std::string name(void) const { return std::string("Conj"); }
};
class funcAdj {
public:
  funcAdj() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = adj(i1);}
  std::string name(void) const { return std::string("Adj"); }
};
class funcImag {
public:
  funcImag() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = imag(i1);}
  std::string name(void) const { return std::string("imag"); }
};
class funcReal {
public:
  funcReal() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = real(i1);}
  std::string name(void) const { return std::string("real"); }
};

class funcTimesI {
public:
  funcTimesI() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = timesI(i1);}
  std::string name(void) const { return std::string("timesI"); }
};
class funcTimesMinusI {
public:
  funcTimesMinusI() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = timesMinusI(i1);}
  std::string name(void) const { return std::string("timesMinusI"); }
};
class funcInnerProduct {
public:
  funcInnerProduct() {};
  template<class vec> void operator()(vec &rr,vec &i1,vec &i2) const { rr = innerProduct(i1,i2);}
  std::string name(void) const { return std::string("innerProduct"); }
};

// FIXME still to test:
//
//  innerProduct,
//  norm2,
//  Reduce,
//
//  mac,mult,sub,add, vone,vzero,vcomplex_i, =Zero(),
//  vset,vsplat,vstore,vstream,vload, scalar*vec, vec*scalar
//  unary -,
//  *= , -=, +=
//  outerproduct,
//  zeroit
//  permute
class funcReduce {
public:
  funcReduce() {};
template<class reduce,class vec>    void vfunc(reduce &rr,vec &i1,vec &i2)   const { rr = Reduce(i1);}
template<class reduce,class scal>   void sfunc(reduce &rr,scal &i1,scal &i2) const { rr = i1;}
  std::string name(void) const { return std::string("Reduce"); }
};

template<class scal, class vec,class functor >
void Tester(const functor &func)
{
  GridSerialRNG          sRNG;
  sRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));

  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  ExtractBuffer<scal> result(Nsimd);
  ExtractBuffer<scal> reference(Nsimd);

  std::vector<vec,alignedAllocator<vec> > buf(3);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];
  vec & v_result = buf[2];


  for(int i=0;i<Nsimd;i++){
    random(sRNG,input1[i]);
    random(sRNG,input2[i]);
    random(sRNG,result[i]);
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);
  merge<vec,scal>(v_result,result);

  func(v_result,v_input1,v_input2);

  for(int i=0;i<Nsimd;i++) {
    func(reference[i],input1[i],input2[i]);
  }

  extract<vec,scal>(v_result,result);

  std::cout << GridLogMessage << " " << func.name() << std::endl;

  std::cout << GridLogDebug << v_input1 << std::endl;
  std::cout << GridLogDebug << v_input2 << std::endl;
  std::cout << GridLogDebug << v_result << std::endl;

  int ok=0;
  for(int i=0;i<Nsimd;i++){
    if ( abs(reference[i]-result[i])>1.0e-6){
      std::cout<<GridLogMessage<< "*****" << std::endl;
      std::cout<<GridLogMessage<< "["<<i<<"] "<< abs(reference[i]-result[i]) << " " <<reference[i]<< " " << result[i]<<std::endl;
      ok++;
    }
  }
  if ( ok==0 ) {
    std::cout<<GridLogMessage << " OK!" <<std::endl;
  } else {
    std::cout<<GridLogMessage << " wrong!" <<std::endl;
  }
  GRID_ASSERT(ok==0);
}

template<class functor>
void IntTester(const functor &func)
{
  typedef Integer  scal;
  typedef vInteger vec;

  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  ExtractBuffer<scal> result(Nsimd);
  ExtractBuffer<scal> reference(Nsimd);

  std::vector<vec,alignedAllocator<vec> > buf(3);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];
  vec & v_result = buf[2];


  for(int i=0;i<Nsimd;i++){
    input1[i] = (i + 1) * 30;
    input2[i] = (i + 1) * 20;
    result[i] = (i + 1) * 10;
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);
  merge<vec,scal>(v_result,result);

  func(v_result,v_input1,v_input2);

  for(int i=0;i<Nsimd;i++) {
    func(reference[i],input1[i],input2[i]);
  }

  extract<vec,scal>(v_result,result);

  std::cout << GridLogMessage << " " << func.name() << std::endl;

  std::cout << GridLogDebug << v_input1 << std::endl;
  std::cout << GridLogDebug << v_input2 << std::endl;
  std::cout << GridLogDebug << v_result << std::endl;

  int ok=0;
  for(int i=0;i<Nsimd;i++){
    if ( reference[i]-result[i] != 0){
      std::cout<<GridLogMessage<< "*****" << std::endl;
      std::cout<<GridLogMessage<< "["<<i<<"] "<< reference[i]-result[i] << " " <<reference[i]<< " " << result[i]<<std::endl;
      ok++;
    }
  }
  if ( ok==0 ) {
    std::cout<<GridLogMessage << " OK!" <<std::endl;
  } else {
    std::cout<<GridLogMessage << " wrong!" <<std::endl;
  }
  GRID_ASSERT(ok==0);
}


template<class reduced,class scal, class vec,class functor >
void ReductionTester(const functor &func)
{
  GridSerialRNG          sRNG;
  sRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));

  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  reduced result(0);
  reduced reference(0);
  reduced tmp;

  std::vector<vec,alignedAllocator<vec> > buf(3);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];


  for(int i=0;i<Nsimd;i++){
    random(sRNG,input1[i]);
    random(sRNG,input2[i]);
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);

  func.template vfunc<reduced,vec>(result,v_input1,v_input2);

  for(int i=0;i<Nsimd;i++) {
    func.template sfunc<reduced,scal>(tmp,input1[i],input2[i]);
    reference+=tmp;
  }

  std::cout<<GridLogMessage << " " << func.name()<<std::endl;

  int ok=0;
  if ( abs(reference-result)/abs(reference) > 1.0e-6 ){ // rounding is possible for reduce order
    std::cout<<GridLogMessage<< "*****" << std::endl;
    std::cout<<GridLogMessage<< abs(reference-result) << " " <<reference<< " " << result<<std::endl;
    ok++;
  }
  if ( ok==0 ) {
    std::cout<<GridLogMessage << " OK!" <<std::endl;
  } else {
    std::cout<<GridLogMessage << " wrong!" <<std::endl;
  }
  GRID_ASSERT(ok==0);
}


template<class reduced,class scal, class vec,class functor >
void IntReductionTester(const functor &func)
{
  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  reduced result(0);
  reduced reference(0);
  reduced tmp;

  std::vector<vec,alignedAllocator<vec> > buf(3);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];

  for(int i=0;i<Nsimd;i++){
    input1[i] = (i + 1) * 30;
    input2[i] = (i + 1) * 20;
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);

  func.template vfunc<reduced,vec>(result,v_input1,v_input2);

  for(int i=0;i<Nsimd;i++) {
    func.template sfunc<reduced,scal>(tmp,input1[i],input2[i]);
    reference+=tmp;
  }

  std::cout<<GridLogMessage << " " << func.name()<<std::endl;

  int ok=0;
  if ( reference-result != 0 ){
    std::cout<<GridLogMessage<< "*****" << std::endl;
    std::cout<<GridLogMessage<< reference-result << " " <<reference<< " " << result<<std::endl;
    ok++;
  }
  if ( ok==0 ) {
    std::cout<<GridLogMessage << " OK!" <<std::endl;
  } else {
    std::cout<<GridLogMessage << " wrong!" <<std::endl;
  }
  GRID_ASSERT(ok==0);
}


class funcPermute {
public:
  int n;
  funcPermute(int _n) { n=_n;};
  template<class vec>    void operator()(vec &rr,vec &i1,vec &i2) const { permute(rr,i1,n);}
  template<class scal>   void apply(ExtractBuffer<scal> &rr,ExtractBuffer<scal> &in)  const {
    int sz=in.size();
    int msk = sz>>(n+1);
    for(int i=0;i<sz;i++){
      rr[i] = in[ i^msk ];
    }
  }
  std::string name(void) const { return std::string("Permute"); }
};

class funcExchange {
public:
  int n;
  funcExchange(int _n) { n=_n;};
  template<class vec>    void operator()(vec &r1,vec &r2,vec &i1,vec &i2) const { exchange(r1,r2,i1,i2,n);}
  template<class scal>   void apply(ExtractBuffer<scal> &r1,
				    ExtractBuffer<scal> &r2,
				    ExtractBuffer<scal> &in1,
				    ExtractBuffer<scal> &in2)  const
  {
    int sz=in1.size();
    int msk = sz>>(n+1);

    for(int i=0;i<sz;i++) {
      int j1 = i&(~msk);
      int j2 = i|msk;
      if  ( (i&msk) == 0 ) { r1[i]=in1[j1];}
      else                 { r1[i]=in2[j1];}

      if  ( (i&msk) == 0 ) { r2[i]=in1[j2];}
      else                 { r2[i]=in2[j2];}
    }
  }
  std::string name(void) const { return std::string("Exchange"); }
};

class funcRotate {
public:
  int n;
  funcRotate(int _n) { n=_n;};
  template<class vec>    void operator()(vec &rr,vec &i1,vec &i2) const { rr=rotate(i1,n);}
  template<class scal>   void apply(ExtractBuffer<scal> &rr,ExtractBuffer<scal> &in)  const {
    int sz = in.size();
    for(int i=0;i<sz;i++){
      rr[i] = in[(i+n)%sz];
    }
  }
  std::string name(void) const { return std::string("Rotate"); }
};


template<class scal, class vec,class functor >
void PermTester(const functor &func)
{
  GridSerialRNG          sRNG;
  sRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));

  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  ExtractBuffer<scal> result(Nsimd);
  ExtractBuffer<scal> reference(Nsimd);

  std::vector<vec,alignedAllocator<vec> > buf(3);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];
  vec & v_result = buf[2];

  for(int i=0;i<Nsimd;i++){
    random(sRNG,input1[i]);
    random(sRNG,input2[i]);
    random(sRNG,result[i]);
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);
  merge<vec,scal>(v_result,result);

  func(v_result,v_input1,v_input2);

  func.apply(reference,input1);

  extract<vec,scal>(v_result,result);
  std::cout<<GridLogMessage << " " << func.name() << " " <<func.n <<std::endl;

  int ok=0;
  if (0) {
    std::cout<<GridLogMessage<< "*****" << std::endl;
    for(int i=0;i<Nsimd;i++){
      std::cout<< input1[i]<<" ";
    }
    std::cout <<std::endl;
    for(int i=0;i<Nsimd;i++){
      std::cout<< result[i]<<" ";
    }
    std::cout <<std::endl;
    for(int i=0;i<Nsimd;i++){
      std::cout<< reference[i]<<" ";
    }
    std::cout <<std::endl;
    std::cout<<GridLogMessage<< "*****" << std::endl;
  }
  for(int i=0;i<Nsimd;i++){
    if ( abs(reference[i]-result[i])>1.0e-7){
      std::cout<<GridLogMessage<< "*****" << std::endl;
      std::cout<<GridLogMessage<< "["<<i<<"] "<< abs(reference[i]-result[i]) << " " <<reference[i]<< " " << result[i]<<std::endl;
      ok++;
    }
  }
  if ( ok==0 ) {
    std::cout<<GridLogMessage << " OK!" <<std::endl;
  } else {
    std::cout<<GridLogMessage << " wrong!" <<std::endl;
  }
  GRID_ASSERT(ok==0);
}


template<class scal, class vec,class functor >
void ExchangeTester(const functor &func)
{
  GridSerialRNG          sRNG;
  sRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));

  int Nsimd = vec::Nsimd();

  ExtractBuffer<scal> input1(Nsimd);
  ExtractBuffer<scal> input2(Nsimd);
  ExtractBuffer<scal> result1(Nsimd);
  ExtractBuffer<scal> result2(Nsimd);
  ExtractBuffer<scal> reference1(Nsimd);
  ExtractBuffer<scal> reference2(Nsimd);
  ExtractBuffer<scal> test1(Nsimd);
  ExtractBuffer<scal> test2(Nsimd);

  std::vector<vec,alignedAllocator<vec> > buf(6);
  vec & v_input1 = buf[0];
  vec & v_input2 = buf[1];
  vec & v_result1 = buf[2];
  vec & v_result2 = buf[3];
  vec & v_test1 = buf[4];
  vec & v_test2 = buf[5];

  for(int i=0;i<Nsimd;i++){
    random(sRNG,input1[i]);
    random(sRNG,input2[i]);
    random(sRNG,result1[i]);
    random(sRNG,result2[i]);
  }

  merge<vec,scal>(v_input1,input1);
  merge<vec,scal>(v_input2,input2);
  merge<vec,scal>(v_result1,result1);
  merge<vec,scal>(v_result2,result1);

  func(v_result1,v_result2,v_input1,v_input2);
  func.apply(reference1,reference2,input1,input2);

  func(v_test1,v_test2,v_result1,v_result2);

  extract<vec,scal>(v_result1,result1);
  extract<vec,scal>(v_result2,result2);
  extract<vec,scal>(v_test1,test1);
  extract<vec,scal>(v_test2,test2);

  std::cout<<GridLogMessage << " " << func.name() << " " <<func.n <<std::endl;

  //for(int i=0;i<Nsimd;i++) std::cout << " i "<<i<<" ref "<<reference1[i]<<" res "<<result1[i]<<std::endl;
  //for(int i=0;i<Nsimd;i++) std::cout << " i "<<i<<" ref "<<reference2[i]<<" res "<<result2[i]<<std::endl;

  for(int i=0;i<Nsimd;i++){
    int found=0;
    for(int j=0;j<Nsimd;j++){
      if(reference1[j]==result1[i]) {
	found=1;
	//	std::cout << " i "<<i<<" j "<<j<<" "<<reference1[j]<<" "<<result1[i]<<std::endl;
      }
    }
    //    GRID_ASSERT(found==1);
    GRID_ASSERT(found==1||found==0);
  }
  for(int i=0;i<Nsimd;i++){
    int found=0;
    for(int j=0;j<Nsimd;j++){
      if(reference2[j]==result2[i]) {
	found=1;
	//	std::cout << " i "<<i<<" j "<<j<<" "<<reference2[j]<<" "<<result2[i]<<std::endl;
      }
    }
    //    GRID_ASSERT(found==1);
    GRID_ASSERT(found==1||found==0);
  }

  /*
  for(int i=0;i<Nsimd;i++){
    std::cout << " i "<< i
	      <<" result1  "<<result1[i]
	      <<" result2  "<<result2[i]
	      <<" test1  "<<test1[i]
	      <<" test2  "<<test2[i]
	      <<" input1 "<<input1[i]
	      <<" input2 "<<input2[i]<<std::endl;
  }
  */
  for(int i=0;i<Nsimd;i++){
    GRID_ASSERT(test1[i]==input1[i]);
    GRID_ASSERT(test2[i]==input2[i]);
  }
}


int main (int argc, char ** argv)
{
  Grid_init(&argc,&argv);

  auto latt_size   = GridDefaultLatt();
  auto simd_layout = GridDefaultSimd(4,vComplex::Nsimd());
  auto mpi_layout  = GridDefaultMpi();
  Coordinate simd1_layout({1,1,1,1});
  
  {
    std::cout << " Constructing Test({1,2,3,4,5,6}) " << std::endl;
    Coordinate Test({1,2,3,4,5,6});
    std::cout << " Test({1,2,3,4,5,6}) = " << Test <<std::endl;
  }

  GridCartesian     Grid(latt_size,simd_layout,mpi_layout);
  std::vector<int> seeds({1,2,3,4});

  // Insist that operations on random scalars gives
  // identical results to on vectors.
  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing sRealF "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  Tester<RealF,sRealF>(funcPlus());
  Tester<RealF,sRealF>(funcMinus());
  Tester<RealF,sRealF>(funcTimes());
  Tester<RealF,sRealF>(funcDivide());
  Tester<RealF,sRealF>(funcAdj());
  Tester<RealF,sRealF>(funcConj());
  Tester<RealF,sRealF>(funcInnerProduct());
  ReductionTester<RealF,RealF,sRealF>(funcReduce());

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealF permutes (there are none) "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< sRealF::Nsimd();i++){
    PermTester<RealF,sRealF>(funcPermute(i));
  }
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealF exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< sRealF::Nsimd();i++){
    ExchangeTester<RealF,sRealF>(funcExchange(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealF rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<sRealF::Nsimd();r++){
    PermTester<RealF,sRealF>(funcRotate(r));
  }



  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing sRealD "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;

  Tester<RealD,sRealD>(funcPlus());
  Tester<RealD,sRealD>(funcMinus());
  Tester<RealD,sRealD>(funcTimes());
  Tester<RealD,sRealD>(funcDivide());
  Tester<RealD,sRealD>(funcAdj());
  Tester<RealD,sRealD>(funcConj());
  Tester<RealD,sRealD>(funcInnerProduct());
  ReductionTester<RealD,RealD,sRealD>(funcReduce());


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealD permutes "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< sRealD::Nsimd();i++){
    PermTester<RealD,sRealD>(funcPermute(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealD exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< sRealD::Nsimd();i++){
    ExchangeTester<RealD,sRealD>(funcExchange(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sRealD rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<sRealD::Nsimd();r++){
    PermTester<RealD,sRealD>(funcRotate(r));
  }



  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing sComplexF "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;

  Tester<ComplexF,sComplexF>(funcTimesI());
  Tester<ComplexF,sComplexF>(funcTimesMinusI());
  Tester<ComplexF,sComplexF>(funcPlus());
  Tester<ComplexF,sComplexF>(funcMinus());
  Tester<ComplexF,sComplexF>(funcTimes());
  Tester<ComplexF,sComplexF>(funcConj());
  Tester<ComplexF,sComplexF>(funcAdj());
  Tester<ComplexF,sComplexF>(funcReal());
  Tester<ComplexF,sComplexF>(funcImag());
  Tester<ComplexF,sComplexF>(funcInnerProduct());
  ReductionTester<ComplexF,ComplexF,sComplexF>(funcReduce());


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexF permutes "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< sComplexF::Nsimd();i++){
    PermTester<ComplexF,sComplexF>(funcPermute(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexF exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< sComplexF::Nsimd();i++){
    ExchangeTester<ComplexF,sComplexF>(funcExchange(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexF rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<sComplexF::Nsimd();r++){
    PermTester<ComplexF,sComplexF>(funcRotate(r));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexD "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;


  Tester<ComplexD,sComplexD>(funcTimesI());
  Tester<ComplexD,sComplexD>(funcTimesMinusI());
  Tester<ComplexD,sComplexD>(funcPlus());
  Tester<ComplexD,sComplexD>(funcMinus());
  Tester<ComplexD,sComplexD>(funcTimes());
  Tester<ComplexD,sComplexD>(funcConj());
  Tester<ComplexD,sComplexD>(funcAdj());
  Tester<ComplexD, sComplexD>(funcReal());
  Tester<ComplexD, sComplexD>(funcImag());

  Tester<ComplexD, sComplexD>(funcInnerProduct());
  ReductionTester<ComplexD, ComplexD, sComplexD>(funcReduce());

  std::cout << GridLogMessage
            << "===================================" << std::endl;
  std::cout << GridLogMessage << "Testing sComplexD permutes " << std::endl;
  std::cout << GridLogMessage
            << "===================================" << std::endl;

  // Log2 iteration
  for (int i = 0; (1 << i) < sComplexD::Nsimd(); i++) {
    PermTester<ComplexD, sComplexD>(funcPermute(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexD exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< sComplexD::Nsimd();i++){
    ExchangeTester<ComplexD,sComplexD>(funcExchange(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sComplexD rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<sComplexD::Nsimd();r++){
    PermTester<ComplexD,sComplexD>(funcRotate(r));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing sInteger                   "<<  std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  IntTester(funcPlus());
  IntTester(funcMinus());
  IntTester(funcTimes());
  IntReductionTester<Integer, Integer, sInteger>(funcReduce());



  
  
  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing vRealF "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;


  Tester<RealF,vRealF>(funcPlus());
  Tester<RealF,vRealF>(funcMinus());
  Tester<RealF,vRealF>(funcTimes());
  Tester<RealF,vRealF>(funcDivide());
  Tester<RealF,vRealF>(funcAdj());
  Tester<RealF,vRealF>(funcConj());
  Tester<RealF,vRealF>(funcInnerProduct());
  ReductionTester<RealF,RealF,vRealF>(funcReduce());


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealF permutes "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< vRealF::Nsimd();i++){
    PermTester<RealF,vRealF>(funcPermute(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealF exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< vRealF::Nsimd();i++){
    ExchangeTester<RealF,vRealF>(funcExchange(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealF rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<vRealF::Nsimd();r++){
    PermTester<RealF,vRealF>(funcRotate(r));
  }

  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing vRealD "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;

  Tester<RealD,vRealD>(funcPlus());
  Tester<RealD,vRealD>(funcMinus());
  Tester<RealD,vRealD>(funcTimes());
  Tester<RealD,vRealD>(funcDivide());
  Tester<RealD,vRealD>(funcAdj());
  Tester<RealD,vRealD>(funcConj());
  Tester<RealD,vRealD>(funcInnerProduct());
  ReductionTester<RealD,RealD,vRealD>(funcReduce());


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealD permutes "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< vRealD::Nsimd();i++){
    PermTester<RealD,vRealD>(funcPermute(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealD exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< vRealD::Nsimd();i++){
    ExchangeTester<RealD,vRealD>(funcExchange(i));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vRealD rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<vRealD::Nsimd();r++){
    PermTester<RealD,vRealD>(funcRotate(r));
  }



  std::cout << GridLogMessage <<"==================================="<<  std::endl;
  std::cout << GridLogMessage <<"Testing vComplexF "<<std::endl;
  std::cout << GridLogMessage <<"==================================="<<  std::endl;

  Tester<ComplexF,vComplexF>(funcTimesI());
  Tester<ComplexF,vComplexF>(funcTimesMinusI());
  Tester<ComplexF,vComplexF>(funcPlus());
  Tester<ComplexF,vComplexF>(funcMinus());
  Tester<ComplexF,vComplexF>(funcTimes());
  Tester<ComplexF,vComplexF>(funcConj());
  Tester<ComplexF,vComplexF>(funcAdj());
  Tester<ComplexF,vComplexF>(funcReal());
  Tester<ComplexF,vComplexF>(funcImag());
  Tester<ComplexF,vComplexF>(funcInnerProduct());
  ReductionTester<ComplexF,ComplexF,vComplexF>(funcReduce());


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexF permutes "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;

  // Log2 iteration
  for(int i=0;(1<<i)< vComplexF::Nsimd();i++){
    PermTester<ComplexF,vComplexF>(funcPermute(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexF exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< vComplexF::Nsimd();i++){
    ExchangeTester<ComplexF,vComplexF>(funcExchange(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexF rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<vComplexF::Nsimd();r++){
    PermTester<ComplexF,vComplexF>(funcRotate(r));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexD "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;


  Tester<ComplexD,vComplexD>(funcTimesI());
  Tester<ComplexD,vComplexD>(funcTimesMinusI());
  Tester<ComplexD,vComplexD>(funcPlus());
  Tester<ComplexD,vComplexD>(funcMinus());
  Tester<ComplexD,vComplexD>(funcTimes());
  Tester<ComplexD,vComplexD>(funcConj());
  Tester<ComplexD,vComplexD>(funcAdj());
  Tester<ComplexD, vComplexD>(funcReal());
  Tester<ComplexD, vComplexD>(funcImag());

  Tester<ComplexD, vComplexD>(funcInnerProduct());
  ReductionTester<ComplexD, ComplexD, vComplexD>(funcReduce());

  std::cout << GridLogMessage
            << "===================================" << std::endl;
  std::cout << GridLogMessage << "Testing vComplexD permutes " << std::endl;
  std::cout << GridLogMessage
            << "===================================" << std::endl;

  // Log2 iteration
  for (int i = 0; (1 << i) < vComplexD::Nsimd(); i++) {
    PermTester<ComplexD, vComplexD>(funcPermute(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexD exchanges "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  // Log2 iteration
  for(int i=0;(1<<i)< vComplexD::Nsimd();i++){
    ExchangeTester<ComplexD,vComplexD>(funcExchange(i));
  }


  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vComplexD rotate "<<std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  for(int r=0;r<vComplexD::Nsimd();r++){
    PermTester<ComplexD,vComplexD>(funcRotate(r));
  }

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing vInteger                   "<<  std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  IntTester(funcPlus());
  IntTester(funcMinus());
  IntTester(funcTimes());
  IntReductionTester<Integer, Integer, vInteger>(funcReduce());

  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  std::cout<<GridLogMessage << "Testing precisionChange            "<<  std::endl;
  std::cout<<GridLogMessage << "==================================="<<  std::endl;
  {
    GridSerialRNG          sRNG;
    sRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));
    const int Ndp = 16;
    const int Nsp = Ndp/2;
    const int Nhp = Ndp/4;
    std::vector<vRealH,alignedAllocator<vRealH> > H (Nhp);
    std::vector<vRealF,alignedAllocator<vRealF> > F (Nsp);
    std::vector<vRealF,alignedAllocator<vRealF> > FF(Nsp);
    std::vector<vRealD,alignedAllocator<vRealD> > D (Ndp);
    std::vector<vRealD,alignedAllocator<vRealD> > DD(Ndp);
    for(int i=0;i<16;i++){
      random(sRNG,D[i]);
    }
    // Double to Single
    precisionChange(&F[0],&D[0],Ndp);
    precisionChange(&DD[0],&F[0],Ndp);
    std::cout << GridLogMessage<<"Double to single";
    for(int i=0;i<Ndp;i++){
      //      std::cout << "DD["<<i<<"] = "<< DD[i]<<" "<<D[i]<<" "<<DD[i]-D[i] <<std::endl;
      DD[i] = DD[i] - D[i];
      decltype(innerProduct(DD[0],DD[0])) nrm;
      nrm = innerProduct(DD[i],DD[i]);
      auto tmp = Reduce(nrm);
      //      std::cout << tmp << std::endl;
      GRID_ASSERT( tmp < 1.0e-14 );
    }
    std::cout <<" OK ! "<<std::endl;

#ifdef USE_FP16
    // Double to Half
    std::cout << GridLogMessage<< "Double to half" ;
    precisionChange(&H[0],&D[0],Ndp);
    precisionChange(&DD[0],&H[0],Ndp);
    for(int i=0;i<Ndp;i++){
      //      std::cout << "DD["<<i<<"] = "<< DD[i]<<" "<<D[i]<<" "<<DD[i]-D[i]<<std::endl;
      DD[i] = DD[i] - D[i];
      decltype(innerProduct(DD[0],DD[0])) nrm;
      nrm = innerProduct(DD[i],DD[i]);
      auto tmp = Reduce(nrm);
      //      std::cout << tmp << std::endl;
      GRID_ASSERT( tmp < 1.0e-3 );
    }
    std::cout <<" OK ! "<<std::endl;

    std::cout << GridLogMessage<< "Single to half";
    // Single to Half
    precisionChange(&H[0] ,&F[0],Nsp);
    precisionChange(&FF[0],&H[0],Nsp);
    for(int i=0;i<Nsp;i++){
      //      std::cout << "FF["<<i<<"] = "<< FF[i]<<" "<<F[i]<<" "<<FF[i]-F[i]<<std::endl;
      FF[i] = FF[i] - F[i];
      decltype(innerProduct(FF[0],FF[0])) nrm;
      nrm = innerProduct(FF[i],FF[i]);
      auto tmp = Reduce(nrm);
      //      std::cout << tmp << std::endl;
      GRID_ASSERT( tmp < 1.0e-3 );
    }
    std::cout <<" OK ! "<<std::endl;
#endif

  }
  Grid_finalize();
}
