//===-- test_funcs.cpp - test duals/dual ------------------------*- C++ -*-===//
//
// Part of the cppduals project.
// https://gitlab.com/tesch1/cppduals
//
// See https://gitlab.com/tesch1/cppduals/blob/master/LICENSE.txt for
// license information.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// (c)2019 Michael Tesch. tesch1@gmail.com
//
/**
 * \file test_eigen Dual number Eigen integration tests
 *
 * (c)2019 Michael Tesch. tesch1@gmail.com
 */

#include <duals/dual_eigen>
#include "type_name.hpp"
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <Eigen/StdVector>

#include <unsupported/Eigen/MatrixFunctions>
//#include <unsupported/Eigen/AutoDiff>
#include "eexpokit/padm.hpp"
#include "gtest/gtest.h"

using duals::rpart;
using duals::dpart;
using duals::dualf;
using duals::duald;
using duals::dualld;
using duals::hyperdualf;
using duals::hyperduald;
using duals::hyperdualld;
using duals::is_dual;
using duals::is_complex;
using duals::dual_traits;
using namespace duals::literals;

typedef long double ldouble;
typedef std::complex<float> complexf;
typedef std::complex<double> complexd;
typedef std::complex<long double> complexld;
typedef std::complex<dualf> cdualf;
typedef std::complex<duald> cduald;
typedef std::complex<dualld> cdualld;

template <class eT, int N=Eigen::Dynamic, int K = N> using emtx = Eigen::Matrix<eT, N, K>;
template <class eT> using smtx = Eigen::SparseMatrix<eT>;

template <int N=2, int K = N> using ecf = Eigen::Matrix<complexf, N, K> ;
template <int N=2, int K = N> using edf = Eigen::Matrix<dualf, N, K> ;
template <int N=2, int K = N> using ecdf = Eigen::Matrix<cdualf, N, K> ;

#define _EXPECT_TRUE(...)  {typedef __VA_ARGS__ tru; EXPECT_TRUE(tru::value); static_assert(tru::value, "sa"); }
#define _EXPECT_FALSE(...) {typedef __VA_ARGS__ fal; EXPECT_FALSE(fal::value); static_assert(!fal::value, "sa"); }
#define EXPECT_DEQ(A,B) EXPECT_EQ(rpart(A), rpart(B)); EXPECT_EQ(dpart(A), dpart(B))
#define ASSERT_DEQ(A,B) ASSERT_EQ(rpart(A), rpart(B)); ASSERT_EQ(dpart(A), dpart(B))
#define EXPECT_DNE(A,B) EXPECT_NE(rpart(A), rpart(B)); EXPECT_NE(dpart(A), dpart(B))
#define EXPECT_DNEAR(A,B,tol)                      \
  EXPECT_NEAR(rpart(A), rpart(B),tol);             \
  EXPECT_NEAR(dpart(A), dpart(B),tol)


TEST(Eigen, NumTraits) {
  //::testing::StaticAssertTypeEq<Eigen::NumTraits<duals::dual<float>>::Real, float>();
  _EXPECT_TRUE(std::is_same<typename Eigen::NumTraits<duals::dual<float>>::Real, dualf>);
  _EXPECT_TRUE(std::is_same<typename Eigen::NumTraits<cdualf>::Real, dualf>);

  EXPECT_EQ(Eigen::NumTraits<duals::dual<float>>::dummy_precision(),
            Eigen::NumTraits<float>::dummy_precision());
  EXPECT_EQ(Eigen::NumTraits<duals::dual<float>>::digits10(),
            Eigen::NumTraits<float>::digits10());

  EXPECT_EQ(Eigen::NumTraits<cdualf>::dummy_precision(),
            Eigen::NumTraits<float>::dummy_precision());
}

TEST(Eigen, construction)
{
  emtx<double> ed;
  emtx<float> ef;
  emtx<complexd> ecd;
  emtx<complexf> ecf;
  emtx<duald> edd;
  emtx<dualf> edf_;
  emtx<cduald> ecdd;
  emtx<cdualf> ecdf_;
}

TEST(Eigen, sparse)
{
  smtx<double> ed;
  smtx<float> ef;
  smtx<complexd> ecd;
  smtx<complexf> ecf;
  smtx<duald> edd;
  smtx<dualf> edf_;
  smtx<cduald> ecdd;
  smtx<cdualf> ecdf_;
}

TEST(Eigen, expr_type_dense) {
  emtx<double> ed;
  emtx<float> ef;
  emtx<complexd> ecd;
  emtx<complexf> ecf;
  emtx<duald> edd;
  emtx<dualf> edf_;
  emtx<cduald> ecdd;
  emtx<cdualf> ecdf_;

  //_EXPECT_TRUE(std::is_base_of<Eigen::EigenBase<U>,U >::value);

  //_EXPECT_TRUE(std::is_same<std::common_type<emtx<dualf>,dualf>::type, emtx<dualf>>);
  ecf = ecf * 1;
#define PO_EXPR_TYPE(...) typename std::decay<decltype( __VA_ARGS__ )>::type::PlainObject
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf*1), emtx<complexf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf + ecf), emtx<complexf>>);
  //_EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf * edf_), emtx<cdualf>>); [1]
  //_EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf * 1_ef), emtx<cdualf>>); [1]
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf * cdualf(1,2)), emtx<cdualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edf_ * 2), emtx<dualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edf_ * cdualf(2,2)), emtx<cdualf>>);
  static_assert(!duals::can_promote<dualf, emtx<dualf>>::value, "nope");
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edf_ * 2_ef), emtx<dualf>>);
  //_EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edf_ * 2_e), emtx<duald>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecf * complexf(1,2)), emtx<complexf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecdf_ * 1), emtx<cdualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecdf_ * 2_ef), emtx<cdualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecdf_ * complexf(1,2)), emtx<cdualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ecdf_ * cdualf(1_ef,2_ef)), emtx<cdualf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edd * 1_e), emtx<duald>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(edd * 1_ef), emtx<duald>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(ed*1), emtx<double>>);

  auto a1 = ecf * 1;
  auto a2 = ecf * ecf;
  //auto a3 = ecf * edf_; [1]
  //auto a4 = ecf * 1_ef; [1]
  auto a5 = ecf * cdualf(1,2);
  auto a6 = edf_ * 2;
  auto a7 = edf_ * 2_ef;
  auto a8 = ecf * complexf(1,2);
  auto a9 = ecdf_ * 1;
  ecdf_ = ecdf_ * complexf(1,2);
  ecdf_ = ecdf_ * cdualf(1_ef,2_ef);
  auto a12 = edd * 1_e;
  auto a13 = ed * 1;
  auto A1 = a1.eval(); A1 += A1;
  auto A2 = a2.eval(); A2 += A2;
  //auto A3 = a3.eval(); A3 += A3; [1]
  //auto A4 = a4.eval(); A4 += A4; [1]
  auto A5 = a5.eval(); A5 += A5;
  auto A6 = a6.eval(); A6 += A6;
  auto A7 = a7.eval(); A7 += A7;
  auto A8 = a8.eval(); A8 += A8;
  auto A9 = a9.eval(); A9 += A9;
  auto A12 = a12.eval(); A12 += A12;
  auto A13 = a13.eval(); A13 += A13;
}

TEST(init, Constant) {
  edf<3> a = edf<3>::Constant(3,3, 5 + 8_ef);
  ecdf<3> b = ecdf<3>::Constant(3,3, cdualf(4,2 + 3_ef));

  EXPECT_EQ(a(1,1), 5 + 8_ef);
  EXPECT_EQ(b(0,1), cdualf(4,2 + 3_ef));
  EXPECT_TRUE((a.array() < 6).all());
  EXPECT_FALSE((a.array() != 5 + 8_ef).any());
  EXPECT_EQ((a.array() == 5 + 8_ef).count(), 9);
}

TEST(init, setIdentity) {
  edf<3> a = edf<3>::Constant(3,3, 5 + 8_ef);
  ecdf<3> b = ecdf<3>::Constant(3,3, cdualf(4,2 + 3_ef));
  a.setIdentity();
  b.setIdentity();

  EXPECT_EQ(a(0,0), 1);
  EXPECT_EQ(a(0,1), 0);
  EXPECT_EQ(a(0,2), 0);
  EXPECT_EQ(a(1,0), 0);
  EXPECT_EQ(a(1,1), 1);
  EXPECT_EQ(a(1,2), 0);
  EXPECT_EQ(a(2,0), 0);
  EXPECT_EQ(a(2,1), 0);
  EXPECT_EQ(a(2,2), 1);

  EXPECT_EQ(b(0,0).real(), 1);
  EXPECT_EQ(b(0,1).real(), 0);
  EXPECT_EQ(b(0,2).real(), 0);
  EXPECT_EQ(b(1,0).real(), 0);
  EXPECT_EQ(b(1,1).real(), 1);
  EXPECT_EQ(b(1,2).real(), 0);
  EXPECT_EQ(b(2,0).real(), 0);
  EXPECT_EQ(b(2,1).real(), 0);
  EXPECT_EQ(b(2,2).real(), 1);
}

TEST(init, setZero) {
  edf<3> a = edf<3>::Constant(3,3, 5 + 8_ef);
  ecdf<3> b = ecdf<3>::Constant(3,3, cdualf(4,2 + 3_ef));

  EXPECT_EQ(a(1,1), 5 + 8_ef);

  a.setZero();
  b.setZero();

  EXPECT_EQ(a(0,0), 0);
  EXPECT_EQ(a(0,1), 0);
  EXPECT_EQ(a(0,2), 0);
  EXPECT_EQ(a(1,0), 0);
  EXPECT_EQ(a(1,1), 0);
  EXPECT_EQ(a(1,2), 0);
  EXPECT_EQ(a(2,0), 0);
  EXPECT_EQ(a(2,1), 0);
  EXPECT_EQ(a(2,2), 0);

  EXPECT_EQ(b(0,0).real(), 0);
  EXPECT_EQ(b(0,1).real(), 0);
  EXPECT_EQ(b(0,2).real(), 0);
  EXPECT_EQ(b(1,0).real(), 0);
  EXPECT_EQ(b(1,1).real(), 0);
  EXPECT_EQ(b(1,2).real(), 0);
  EXPECT_EQ(b(2,0).real(), 0);
  EXPECT_EQ(b(2,1).real(), 0);
  EXPECT_EQ(b(2,2).real(), 0);
  EXPECT_EQ(b(0,0).imag(), 0);
  EXPECT_EQ(b(0,1).imag(), 0);
  EXPECT_EQ(b(0,2).imag(), 0);
  EXPECT_EQ(b(1,0).imag(), 0);
  EXPECT_EQ(b(1,1).imag(), 0);
  EXPECT_EQ(b(1,2).imag(), 0);
  EXPECT_EQ(b(2,0).imag(), 0);
  EXPECT_EQ(b(2,1).imag(), 0);
  EXPECT_EQ(b(2,2).imag(), 0);
}

TEST(init, LinSpaced) {
  ecf<3,1> a = ecf<3,1>::LinSpaced(3, 4, complexf(6,7));
  EXPECT_EQ(a(0).real(), 4);
  EXPECT_EQ(a(1).real(), 5);
  EXPECT_EQ(a(2).real(), 6);

  edf<3,1> b = edf<3,1>::LinSpaced(3, 4, 6 + 6_ef);
  EXPECT_EQ(b(0), 4);
  EXPECT_EQ(b(1), 5 + 3_ef);
  EXPECT_EQ(b(2), 6 + 6_ef);
}

TEST(init, Random) {
  complexf c = emtx<complexf,1>::Random()(0,0);
  EXPECT_NE(c.real(), 0);
  EXPECT_NE(c.imag(), 0);

  typedef dualf Rt;
  using duals::random;

  Rt b = emtx<Rt,1>::Random()(0,0);
  EXPECT_NE(b.rpart(), 0);
  //EXPECT_NE(b.dpart(), 0); //

  edf<3> j = edf<3>::Random(3,3);
  EXPECT_NE(j(1,2).rpart(), 0);
  //EXPECT_NE(j(2,0).dpart(), 0); //

  ecdf<3> k = ecdf<3>::Random(3,3);
  EXPECT_NE(k(0,1).real().rpart(), 0);
  //EXPECT_NE(k(1,1).imag().dpart(), 0); //

  using duals::randos::random2;
  dualf x = random2<dualf>();
  EXPECT_NE(x.rpart(), 0);
  EXPECT_NE(x.dpart(), 0);

  duald y = random2<duald>();
  EXPECT_NE(y.rpart(), 0);
  EXPECT_NE(y.dpart(), 0);

  //EXPECT_NE(k(1,1).imag().dpart(), 0); //
}

TEST(assign, ops) {
  emtx<double> ed;
  emtx<float> ef;
  emtx<complexd> ecd;
  emtx<complexf> ecf;
  emtx<duald> edd;
  emtx<dualf> edf_;
  emtx<cduald> ecdd;
  emtx<cdualf> ecdf_;

  ed *= 2;
  ef *= 2;

  //ecd *= 2;
  //ecf *= 2;
  //ecd *= 2_e;
  //ecf *= 2_ef;

  edd *= 2;
  edf_ *= 2;
  edd *= 2_e;
  edf_ *= 2_ef;
  //ecdd *= 2;
  //  ecdf_ *= 2;
}

TEST(Array, math) {
  int N = 5;

  emtx<float> x(N,1);
  emtx<float> y(N,1);
  emtx<float> z(N,1);

  x.array() += 1;
  x += y + z;
  x *= 2;

  emtx<duald> a(N,1);
  emtx<duald> A(N,1);
  emtx<duald> c(N,1);
  a.array() = 1 + 1_e;
  a.array() += 3_e;
  a.array() = 0;
  a.array() += 1;
  A.array() += 2;
  c = a + A;
  c = a * 2;
  c = a * 2_e;
  c = 2 * a;
  c = (3 + 2_e) * a;
  c *= 3;
  c *= 3_e;
  EXPECT_EQ(c(N-1), 27_e);
}

TEST(access, CwiseRpartOp) {
  // on float matrices (pass-through)
  emtx<float,3> a, b = emtx<float,3>::Random();
  a = b.unaryExpr(duals::CwiseRpartOp<float>());
  EXPECT_NE(a.norm(), 0);

  // on dual<float> matrices
  emtx<dualf,3> d;
  a = d.unaryExpr(duals::CwiseRpartOp<dualf>());
  a = d.unaryExpr(duals::CwiseDpartOp<dualf>());
  a = rpart(d);
  a = dpart(d);

  // on complex<dual<float>> matrices
  emtx<complexf,3> g, f = emtx<complexf,3>::Random();
  emtx<cdualf,3> e;
  e.array() = f.array().cast<cdualf>() - 1_ef * f.array().cast<cdualf>();
  g = e.unaryExpr(duals::CwiseRpartOp<cdualf>());
  EXPECT_EQ((f-g).norm(), 0);
  g = f.unaryExpr(duals::CwiseRpartOp<complexf>());
  EXPECT_EQ((f-g).norm(), 0);
  g = rpart(f);
  EXPECT_EQ((f-g).norm(), 0);

  g = e.unaryExpr(duals::CwiseDpartOp<cdualf>());
  EXPECT_EQ((f+g).norm(), 0);
  g = f.unaryExpr(duals::CwiseDpartOp<complexf>());
  EXPECT_EQ((g).norm(), 0);
  g = dpart(f);
  EXPECT_EQ((g).norm(), 0);
}

TEST(access, CwiseDpartOp) {
  // on float matrices (pass-through)
  emtx<float,3> a, b = emtx<float,3>::Random();
  a = b.unaryExpr(duals::CwiseDpartOp<float>());
  EXPECT_EQ(a.norm(), 0);

  // on dual<float> matrices
  emtx<dualf,3> d = b - 1_ef * b;
  a = d.unaryExpr(duals::CwiseDpartOp<dualf>());
  EXPECT_NE(a.norm(), 0);
  EXPECT_EQ((a+b).norm(), 0);

  // on complex<dual<float>> matrices
  emtx<complexf,3> g, f = emtx<complexf,3>::Random();
  emtx<cdualf,3> e;
  e.array() = f.array().cast<cdualf>() - 1_ef * f.array().cast<cdualf>();

  g = e.unaryExpr(duals::CwiseDpartOp<cdualf>());
  EXPECT_EQ((g+f).norm(), 0);
  d = e.real();
  EXPECT_NE(d.norm(), 0);
}

TEST(measure, norm) {
  typedef emtx<complexf, 3> MatrixC;
  MatrixC c = (MatrixC() << 1,2,3, 4,5,6, 7,8,9).finished();

  EXPECT_EQ(c.cwiseAbs().colwise().sum().maxCoeff(), complexf(18));
  //EXPECT_EQ(c.maxCoeff(), complexf(9));

  //typedef duald Rt;
  typedef dualf Rt;
  //typedef cdualf Rt;
  typedef emtx<Rt, 3> MatrixD;
  Rt b = 1+0_ef;
  b = 3;
  Rt d(1);
  MatrixD x;
  x.array() = d;
  MatrixD a = (MatrixD() << 1,2,3, 4,5+5_ef,6, 7,8,9).finished();
  //typename MatrixD::Index index;

  EXPECT_EQ(a.sum(), 45 + 5_ef);
  EXPECT_EQ(x.sum(), 9);
  EXPECT_NEAR(rpart(a.norm()), 16.8819430161341337282, 1e-5);
  EXPECT_NEAR(rpart(a.mean()), 5, 1e-5);
  EXPECT_NEAR(dpart(a.mean()), 0.555555555555555, 1e-5);
  EXPECT_EQ(a.minCoeff(), 1);
  EXPECT_EQ(a.maxCoeff(), 9);
  EXPECT_EQ(a.trace(), 15 + 5_ef);
  EXPECT_EQ(a.squaredNorm(), 285+0_e);
  EXPECT_EQ(a.lpNorm<1>(), 45 + 5_ef);
  //EXPECT_EQ(a.lpNorm<Eigen::Infinity>(), 9);
  // 1-norm
  //EXPECT_EQ(a.colwise().lpNorm<1>().maxCoeff(), 45 + 5_ef);
  EXPECT_EQ(a.cwiseAbs().colwise().sum().maxCoeff(), 18);

}

TEST(dual_decay, stat) {
  emtx<float> ef;
  emtx<complexd> ecd;
  emtx<complexf> ecf;
  emtx<duald> edd;
  emtx<dualf> edf_;
  emtx<cduald> ecdd;
  emtx<cdualf> ecdf_;

  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(rpart(ef)), emtx<float>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(dpart(ef)), emtx<float>>);

  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(rpart(edf_)), emtx<float>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(dpart(edf_)), emtx<float>>);

  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(rpart(ecdf_)), emtx<complexf>>);
  _EXPECT_TRUE(std::is_same<PO_EXPR_TYPE(dpart(ecdf_)), emtx<complexf>>);
}

TEST(dpart, matrix) {
  emtx<float,3> a = emtx<float,3>::Random(3,3);
  emtx<float,3> b = emtx<float,3>::Random(3,3);
  emtx<float,3> c = emtx<float,3>::Random(3,3);
  emtx<float,3> d = emtx<float,3>::Random(3,3);
  emtx<dualf,3> A = a + 1_ef * b;
  emtx<dualf,3> B = c + 1_ef * d;
  emtx<cdualf,3> AA;
  emtx<complexf,3> BB,CC;
  AA.real() = A;
  AA.imag() = B;
  BB.real() = a;
  BB.imag() = c;
  CC.real() = b;
  CC.imag() = d;

  EXPECT_EQ((rpart(A) - a).norm(),0);
  EXPECT_EQ((dpart(A) - b).norm(),0);

  EXPECT_EQ((rpart(AA) - BB).norm(),0);
  EXPECT_EQ((dpart(AA) - CC).norm(),0);
}

TEST(eigen, exp_typechecks) {
  typedef emtx<dualf,3> Mat;
  typedef emtx<duald,3> Matd;
  EXPECT_FALSE(Eigen::internal::is_exp_known_type<typename Mat::Scalar>::value);
  EXPECT_FALSE(Eigen::internal::is_exp_known_type<typename Matd::Scalar>::value);

  typedef emtx<cdualf,3> Matc;
  typedef emtx<cduald,3> Matcd;
  EXPECT_FALSE(Eigen::internal::is_exp_known_type<typename Matc::Scalar>::value);
  EXPECT_FALSE(Eigen::internal::is_exp_known_type<typename Matcd::Scalar>::value);
}

const bool _exp = true;
const bool _padm = false;

#define TEST_EXP(SCALAR_T, SIDE, EXP_OR_PADM) \
  TEST(func, exp_##SCALAR_T##_##SIDE##EXP_OR_PADM) { \
    emtx<SCALAR_T, SIDE> a,b; \
    a = emtx<SCALAR_T, SIDE>::Random(); \
    a.setZero(); \
    if (EXP_OR_PADM == _exp) a = a.exp(); \
    else a = eexpokit::padm(a); \
    EXPECT_LT((a - emtx<SCALAR_T, SIDE>::Identity()).norm(), 1e-6) << "a=" << a << "\n"; \
}

#define TEST_EXP_SIZES(SCALAR_T, EXP_OR_PADM) \
  TEST_EXP(SCALAR_T, 3, EXP_OR_PADM) \
  TEST_EXP(SCALAR_T, 4, EXP_OR_PADM) \
  TEST_EXP(SCALAR_T, 17, EXP_OR_PADM)

#define TEST_EXP_SIZES_EOP(SCALAR_T) \
  TEST_EXP_SIZES(SCALAR_T, _padm) \
  TEST_EXP_SIZES(SCALAR_T, _exp)

// just make sure padm is working
TEST_EXP_SIZES_EOP(float)
TEST_EXP_SIZES_EOP(double)
TEST_EXP_SIZES_EOP(ldouble)
TEST_EXP_SIZES_EOP(complexf)
TEST_EXP_SIZES_EOP(complexd)
TEST_EXP_SIZES_EOP(complexld)

/* testing engine */
#define QUOTE(...) STRFY(__VA_ARGS__)
#define STRFY(...) #__VA_ARGS__

int main(int argc, char **argv)
{
  std::cout << "OPT_FLAGS=" << QUOTE(OPT_FLAGS) << "\n";
  std::cout << "INSTRUCTIONSET=" << Eigen::SimdInstructionSetsInUse() << "\n";
  ::testing::InitGoogleTest(&argc, argv);
  std::cout.precision(20);
  std::cerr.precision(20);
  return RUN_ALL_TESTS();
}
