/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2012 University of Bonn. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * ManyBodyPotential_TersoffUnitTest.cpp
 *
 *  Created on: Oct 04, 2012
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
using namespace std;
#include 
#include 
#include 
#include "ManyBodyPotential_TersoffUnitTest.hpp"
#include 
#include 
#include 
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "FunctionApproximation/FunctionArgument.hpp"
#include "Potentials/Specifics/ManyBodyPotential_Tersoff.hpp"
#include "Potentials/helpers.hpp"
using namespace boost::assign;
#ifdef HAVE_TESTRUNNER
#include "UnitTestMain.hpp"
#endif /*HAVE_TESTRUNNER*/
/********************************************** Test classes **************************************/
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( ManyBodyPotential_TersoffTest );
ManyBodyPotential_TersoffTest::configurations_t ManyBodyPotential_TersoffTest::configurations;
/** This function looks up all distances ik and jk to a given ij and
 * returns a vector of pairs.
 */
std::vector
triplefunction(const argument_t &arguments, const double cutoff)
{
  const ManyBodyPotential_TersoffTest::configuration_t &CurrentConfiguration =
      ManyBodyPotential_TersoffTest::configurations[arguments.globalid];
  std::vector result;
  // go through current configuration and gather all other distances
  ManyBodyPotential_TersoffTest::configuration_t::const_iterator firstiter =
      CurrentConfiguration.begin();
  std::advance(firstiter, arguments.indices.first);
  ManyBodyPotential_TersoffTest::configuration_t::const_iterator seconditer =
      CurrentConfiguration.begin();
  std::advance(seconditer, arguments.indices.second);
  for (ManyBodyPotential_TersoffTest::configuration_t::const_iterator iter =
      CurrentConfiguration.begin();
      iter != CurrentConfiguration.end();
      ++iter) {
    // skip k==i and k==j
    if ((iter == firstiter) || (iter == seconditer))
      continue;
    FunctionModel::arguments_t args(2);
    // ik
    args[0].distance = firstiter->distance(*iter);
    args[0].indices = std::make_pair(
        std::distance(CurrentConfiguration.begin(), firstiter),
        std::distance(CurrentConfiguration.begin(), iter)
    );
    args[0].globalid = arguments.globalid;
    // jk
    args[1].distance = seconditer->distance(*iter);
    args[1].indices = std::make_pair(
        std::distance(CurrentConfiguration.begin(), seconditer),
        std::distance(CurrentConfiguration.begin(), iter)
    );
    args[1].globalid = arguments.globalid;
    result.push_back(args);
  }
  return result;
}
void ManyBodyPotential_TersoffTest::setUp()
{
  // failing asserts should be thrown
  ASSERT_DO(Assert::Throw);
  setVerbosity(10);
  // we use parameters from tremolo/defaults/tersoff/tersoff.potentials
  // [Tersoff, '89]
  params.resize(ManyBodyPotential_Tersoff::MAXPARAMS,0.);
//  params[ManyBodyPotential_Tersoff::R] = 1.800000e+00;
//  params[ManyBodyPotential_Tersoff::S] = 2.100000e+00;
  params[ManyBodyPotential_Tersoff::A] = 1.393600e+03;
  params[ManyBodyPotential_Tersoff::B] = 3.467000e+02;
  params[ManyBodyPotential_Tersoff::lambda] = 3.487900e+00;
  params[ManyBodyPotential_Tersoff::mu] = 2.211900e+00;
//  params[ManyBodyPotential_Tersoff::lambda3] = 0.;
//  params[ManyBodyPotential_Tersoff::alpha] = 0.;
  params[ManyBodyPotential_Tersoff::beta] = 1.572400e-07;
//  params[ManyBodyPotential_Tersoff::chi] = 1.;
//  params[ManyBodyPotential_Tersoff::omega] = 1.;
  params[ManyBodyPotential_Tersoff::n] = 7.275100e-01;
  params[ManyBodyPotential_Tersoff::c] = 3.804900e+04;
  params[ManyBodyPotential_Tersoff::d] = 4.384000e+00;
  params[ManyBodyPotential_Tersoff::h] = -5.705800e-01;
  // initial configuration as in tremolo/default/tersoff/tersoff.data with
  // Si renamed to C.
  // create test configurations of 5 C atoms, constructed via:
  // for file in tersoff.vis.00?0.xyz; do
  // echo -e "\t{\n\t\tconfiguration_t positions;\n\t\tpositions +="
  // tail -n 5 $file | awk -F " " {'print "\t\t\t\tVector("$2","$3","$4")"(NR!=5 ? "," : ";")'}
  // echo -e -n "\t}\n"
  // done
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.100000e+01,1.100000e+01),
        Vector(1.120000e+01,1.000000e+01,1.000000e+01),
        Vector(8.800000e+00,1.000000e+01,1.000000e+01),
        Vector(1.000000e+01,1.200000e+01,1.000000e+01),
        Vector(1.000000e+01,1.100000e+01,1.240000e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.099315e+01,1.099482e+01),
        Vector(1.119779e+01,1.000179e+01,1.000235e+01),
        Vector(8.802208e+00,1.000179e+01,1.000235e+01),
        Vector(1.000000e+01,1.200262e+01,9.999421e+00),
        Vector(1.000000e+01,1.100066e+01,1.240107e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.097354e+01,1.098018e+01),
        Vector(1.119164e+01,1.000675e+01,1.000902e+01),
        Vector(8.808358e+00,1.000675e+01,1.000902e+01),
        Vector(1.000000e+01,1.201036e+01,9.997816e+00),
        Vector(1.000000e+01,1.100260e+01,1.240397e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.094419e+01,1.095884e+01),
        Vector(1.118308e+01,1.001364e+01,1.001884e+01),
        Vector(8.816924e+00,1.001364e+01,1.001884e+01),
        Vector(1.000000e+01,1.202283e+01,9.995566e+00),
        Vector(1.000000e+01,1.100570e+01,1.240790e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.090791e+01,1.093293e+01),
        Vector(1.117318e+01,1.002158e+01,1.003102e+01),
        Vector(8.826818e+00,1.002158e+01,1.003102e+01),
        Vector(1.000000e+01,1.203924e+01,9.993210e+00),
        Vector(1.000000e+01,1.100969e+01,1.241182e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.086664e+01,1.090321e+01),
        Vector(1.116216e+01,1.003043e+01,1.004546e+01),
        Vector(8.837839e+00,1.003043e+01,1.004546e+01),
        Vector(1.000000e+01,1.205848e+01,9.991167e+00),
        Vector(1.000000e+01,1.101403e+01,1.241470e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.082297e+01,1.087033e+01),
        Vector(1.115036e+01,1.003997e+01,1.006213e+01),
        Vector(8.849644e+00,1.003997e+01,1.006213e+01),
        Vector(1.000000e+01,1.207923e+01,9.989652e+00),
        Vector(1.000000e+01,1.101786e+01,1.241577e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.077905e+01,1.083482e+01),
        Vector(1.113831e+01,1.004988e+01,1.008085e+01),
        Vector(8.861694e+00,1.004988e+01,1.008085e+01),
        Vector(1.000000e+01,1.210048e+01,9.989022e+00),
        Vector(1.000000e+01,1.102071e+01,1.241446e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.073683e+01,1.079745e+01),
        Vector(1.112678e+01,1.005973e+01,1.010129e+01),
        Vector(8.873218e+00,1.005973e+01,1.010129e+01),
        Vector(1.000000e+01,1.212139e+01,9.989594e+00),
        Vector(1.000000e+01,1.102232e+01,1.241038e+01);
    configurations.push_back(positions);
  }
  {
    configuration_t positions;
    positions +=
        Vector(1.000000e+01,1.069834e+01,1.075920e+01),
        Vector(1.111685e+01,1.006891e+01,1.012296e+01),
        Vector(8.883151e+00,1.006891e+01,1.012296e+01),
        Vector(1.000000e+01,1.214131e+01,9.991602e+00),
        Vector(1.000000e+01,1.102252e+01,1.240327e+01);
    configurations.push_back(positions);
  }
  // cut from tersoff.etot via:
  // for i in `seq 0 1 9`; do
  // grep $i.000000e-01 tersoff.etot | awk -F" " {'print "\t\t\t\t"$2","'}
  // done
  // (though timestep 0 is missing and is added manually)
  output +=
      -1.333927e+01,
      -1.359628e+01,
      -1.420701e+01,
      -1.479974e+01,
      -1.537942e+01,
      -1.584603e+01,
      -1.615832e+01,
      -1.630598e+01,
      -1.626654e+01,
      -1.603360e+01;
  CPPUNIT_ASSERT_EQUAL( output.size(), configurations.size() );
}
void ManyBodyPotential_TersoffTest::tearDown()
{
  configurations.clear();
}
/** UnitTest for operator()
 */
void ManyBodyPotential_TersoffTest::operatorTest()
{
  boost::function<
      std::vector(const argument_t &, const double)
    > fct =
      triplefunction;
  ManyBodyPotential_Tersoff::ParticleTypes_t types =
      boost::assign::list_of
        (0)(1)
      ;
  ManyBodyPotential_Tersoff tersoff(types);
  tersoff.setTriplefunction(fct);
  tersoff.setParameters(params);
  const_cast(tersoff.R) = 1.8;
  const_cast(tersoff.S) = 2.1;
  for (size_t index = 0; index < configurations.size(); ++index) {
    const configuration_t &CurrentConfiguration = configurations[index];
    double temp = 0.;
    for (size_t i=0; i < CurrentConfiguration.size(); ++i)
      for (size_t j=0; j < CurrentConfiguration.size(); ++j) {
        if (i == j)
          continue;
        argument_t arg;
        arg.indices = std::make_pair(i,j);
        arg.types = std::make_pair(0,1);
        arg.distance = CurrentConfiguration[i].distance(CurrentConfiguration[j]);
        arg.globalid = index; // this is needed for the triplefunction to the configuration
        FunctionModel::arguments_t args(1,arg);
        const ManyBodyPotential_Tersoff::results_t res = tersoff(args);
        temp += res[0];
      }
    // this little precision is because tremolo does seem to handle coordinates
    // a little differently than we do, the precise difference in the x coordinate
    // of the first and second position for the second configuration is easy to
    // see as 1.119779. However, tremolo obtains 1.1197792 for unknown reasons.
    // Maybe, there is some float floating around in the code ... see strtod() bugs
//    LOG(2, "Comparing " << output[index] << " and " << .5*temp);
    CPPUNIT_ASSERT(
        Helpers::isEqual(
            output[index],
            .5*temp,
            1.e-4/std::numeric_limits::epsilon()
        )
    );
  }
}
/** UnitTest for derivative()
 */
void ManyBodyPotential_TersoffTest::derivativeTest()
{
//  boost::function<
//      std::vector(const argument_t &, const double)
//    > fct =
//      triplefunction;
//  ManyBodyPotential_Tersoff::ParticleTypes_t types =
//      boost::assign::list_of
//        (0)(1)
//      ;
//  ManyBodyPotential_Tersoff tersoff(types, fct);
//  tersoff.setParameters(params);
//  CPPUNIT_ASSERT(
//      Helpers::isEqual(
//          0.,
//          tersoff.derivative(
//              FunctionModel::arguments_t(1,argument_t(1.))
//          )[0]
//      )
//  );
}
/** UnitTest for parameter_derivative()
 */
void ManyBodyPotential_TersoffTest::parameter_derivativeTest()
{
//  boost::function<
//      std::vector(const argument_t &, const double)
//    > fct =
//      triplefunction;
//  ManyBodyPotential_Tersoff::ParticleTypes_t types =
//      boost::assign::list_of
//        (0)(1)
//      ;
//  ManyBodyPotential_Tersoff tersoff(types, fct);
//  tersoff.setParameters(params);
//  CPPUNIT_ASSERT(
//      Helpers::isEqual(
//          0.,
//          tersoff.parameter_derivative(
//              FunctionModel::arguments_t(1,argument_t(1.)),
//              0
//          )[0]
//      )
//  );
//  CPPUNIT_ASSERT(
//      Helpers::isEqual(
//          0.,
//          tersoff.parameter_derivative(
//              FunctionModel::arguments_t(1,argument_t(1.)),
//              1
//          )[0]
//      )
//  );
//  CPPUNIT_ASSERT(
//      Helpers::isEqual(
//          1.,
//          tersoff.parameter_derivative(
//              FunctionModel::arguments_t(1,argument_t(1.)),
//              2
//          )[0]
//      )
//  );
}