#define BOOST_TEST_MODULE "C++ Unit Tests for metaLBM"
#include <boost/test/included/unit_test.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
namespace tt = boost::test_tools;

#define NPROCS 1
#include "Lattice.h"

#include <math.h>
#include <iostream>

using namespace lbm;

BOOST_AUTO_TEST_SUITE(TestLattice)

BOOST_AUTO_TEST_CASE(TestIsotropyD1Q3) {
  constexpr LatticeType latticeType = LatticeType::D1Q3;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];

      for(int d_b = 0; d_b < L::dimD; ++d_b) {
        int idx_b = L::dimD * idx_a + d_b;
        sumWeight2Celerity[idx_b] += L::weight()[iQ]*L::celerity()[iQ][d_a]*L::celerity()[iQ][d_b];

        if(d_a == d_b) {
          sumWeight2Celerity_r[idx_b] = L::cs2;
        }

        for(int d_c = 0; d_c < L::dimD; ++d_c) {
          int idx_c = L::dimD * idx_b + d_c;
          sumWeight3Celerity[idx_c] += L::weight()[iQ]*L::celerity()[iQ][d_a]
            *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c];

          for(int d_d = 0; d_d < L::dimD; ++d_d) {
            int idx_d = L::dimD * idx_c + d_d;
            sumWeight4Celerity[idx_d] += L::weight()[iQ]*L::celerity()[iQ][d_a]
              *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d];

            if((d_a == d_b && d_c == d_d)
               && (d_a == d_c && d_b == d_d)
               && (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 3.0*L::cs2*L::cs2;
            }
            else if(((d_a == d_b && d_c == d_d) && (d_a == d_c && d_b == d_d))
                    || ((d_a == d_b && d_c == d_d) && (d_a == d_d && d_b == d_d))
                    || ((d_a == d_d && d_b == d_c) && (d_a == d_d && d_b == d_d))) {
              sumWeight4Celerity_r[idx_d] = 2.0*L::cs2*L::cs2;
            }
            else if((d_a == d_b && d_c == d_d)
                    || (d_a == d_c && d_b == d_d)
                    || (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 1.0*L::cs2*L::cs2;
            }

            for(int d_e = 0; d_e < L::dimD; ++d_e) {
              int idx_e = L::dimD * idx_d + d_e;
              sumWeight5Celerity[idx_e] += L::weight()[iQ]*L::celerity()[iQ][d_a]
                *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d]
                *L::celerity()[iQ][d_e];
            }
          }
        }
      }
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight2Celerity[i] == sumWeight2Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight3Celerity[i] == sumWeight3Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight4Celerity[i] == sumWeight4Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight5Celerity[i] == sumWeight5Celerity_r[i], tt::tolerance(1e-15));
  }
}

BOOST_AUTO_TEST_CASE(TestDoubleD1Q3) {
  constexpr LatticeType latticeType = LatticeType::D1Q3;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 1);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 3);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, 1);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 0);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, 3.0);
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, 1.0/3.0);

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 2.0/3.0);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0/6.0);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0/6.0);

  const auto c_ = L::celerity();
  const auto c_r = MathVector<MathVector<valueType, dimD_>, dimQ_>
    {{
        {{0.0}},
        {{-1.0}},
        {{1.0}}
      }};

  BOOST_CHECK_EQUAL(c_, c_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0);
  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0);
  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 1.0);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{1.0}};
  BOOST_CHECK_EQUAL(c2_, c2_r);

}

BOOST_AUTO_TEST_CASE(TestFloatD1Q3) {
  constexpr LatticeType latticeType = LatticeType::D1Q3;
  typedef float valueType;
  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 1);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 3);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, 1);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 0);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, 3.0f);
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, 1.0f/3.0f);

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 2.0f/3.0f);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0f/6.0f);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0f/6.0f);

  const auto c_ = L::celerity();
  const auto c_r = MathVector<MathVector<valueType, dimD_>, dimQ_>
    {{
        {{0.0f}},
        {{-1.0f}},
        {{1.0f}}
      }};

  BOOST_CHECK_EQUAL(c_, c_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0f);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0f);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 1.0f);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0f}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0f}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{1.0f}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
}

BOOST_AUTO_TEST_CASE(TestIsotropyD2Q5) {
  constexpr LatticeType latticeType = LatticeType::D2Q5;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
}

BOOST_AUTO_TEST_CASE(TestDoubleD2Q5) {
  constexpr LatticeType latticeType = LatticeType::D2Q5;
  typedef double valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 2);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 5);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, 3.0);
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0/3.0));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 4.0/6.0);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0/12.0);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0/12.0);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0/12.0);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0/12.0);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0, 0.0}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0, 0.0}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0, -1.0}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{1.0, 0.0}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{0.0, 1.0}};
  BOOST_CHECK_EQUAL(c4_, c4_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 1.0);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, 0.0);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, 1.0);
}

BOOST_AUTO_TEST_CASE(TestFloatD2Q5) {
  constexpr LatticeType latticeType = LatticeType::D2Q5;
  typedef float valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 2);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 5);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, 3.0f);
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0f/3.0f));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 4.0f/6.0f);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0f/12.0f);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0f/12.0f);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0f/12.0f);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0f/12.0f);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c4_, c4_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0f);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0f);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0f);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0f);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0f);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0f);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 1.0f);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0f);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, 0.0f);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, 1.0f);
}

BOOST_AUTO_TEST_CASE(TestIsotropyD2Q9) {
  constexpr LatticeType latticeType = LatticeType::D2Q9;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];

      for(int d_b = 0; d_b < L::dimD; ++d_b) {
        int idx_b = L::dimD * idx_a + d_b;
        sumWeight2Celerity[idx_b] += L::weight()[iQ]*L::celerity()[iQ][d_a]*L::celerity()[iQ][d_b];

        if(d_a == d_b) {
          sumWeight2Celerity_r[idx_b] = L::cs2;
        }

        for(int d_c = 0; d_c < L::dimD; ++d_c) {
          int idx_c = L::dimD * idx_b + d_c;
          sumWeight3Celerity[idx_c] += L::weight()[iQ]*L::celerity()[iQ][d_a]
            *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c];

          for(int d_d = 0; d_d < L::dimD; ++d_d) {
            int idx_d = L::dimD * idx_c + d_d;
            sumWeight4Celerity[idx_d] += L::weight()[iQ]*L::celerity()[iQ][d_a]
              *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d];

            if((d_a == d_b && d_c == d_d)
               && (d_a == d_c && d_b == d_d)
               && (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 3.0*L::cs2*L::cs2;
            }
            else if(((d_a == d_b && d_c == d_d) && (d_a == d_c && d_b == d_d))
                    || ((d_a == d_b && d_c == d_d) && (d_a == d_d && d_b == d_d))
                    || ((d_a == d_d && d_b == d_c) && (d_a == d_d && d_b == d_d))) {
              sumWeight4Celerity_r[idx_d] = 2.0*L::cs2*L::cs2;
            }
            else if((d_a == d_b && d_c == d_d)
                    || (d_a == d_c && d_b == d_d)
                    || (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 1.0*L::cs2*L::cs2;
            }

            for(int d_e = 0; d_e < L::dimD; ++d_e) {
              int idx_e = L::dimD * idx_d + d_e;
              sumWeight5Celerity[idx_e] += L::weight()[iQ]*L::celerity()[iQ][d_a]
                *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d]
                *L::celerity()[iQ][d_e];
            }
          }
        }
      }
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight2Celerity[i] == sumWeight2Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight3Celerity[i] == sumWeight3Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight4Celerity[i] == sumWeight4Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight5Celerity[i] == sumWeight5Celerity_r[i], tt::tolerance(1e-15));
  }
}

BOOST_AUTO_TEST_CASE(TestDoubleD2Q9) {
  constexpr LatticeType latticeType = LatticeType::D2Q9;
  typedef double valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 2);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 9);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0/3.0));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 4.0/9.0);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0/36.0);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0/9.0);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0/36.0);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0/9.0);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0/36.0);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0/9.0);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0/36.0);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0/9.0);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0, 0.0}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0, 1.0}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{-1.0, 0.0}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{-1.0, -1.0}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{0.0, -1.0}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{1.0, -1.0}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{1.0, 0.0}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{1.0, 1.0}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{0.0, 1.0}};
  BOOST_CHECK_EQUAL(c8_, c8_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 1.0);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, -1.0);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, 0.0);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, -1.0);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, -1.0);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, 0.0);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, 1.0);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, -1.0);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, 1.0);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 0.0);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, 1.0);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 1.0);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 0.0);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, 1.0);
}

BOOST_AUTO_TEST_CASE(TestFloatD2Q9) {
  constexpr LatticeType latticeType = LatticeType::D2Q9;
  typedef float valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 2);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 9);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, 1);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 0);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0f));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0f/3.0f));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 4.0f/9.0f);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0f/36.0f);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0f/9.0f);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0f/36.0f);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0f/9.0f);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0f/36.0f);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0f/9.0f);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0f/36.0f);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0f/9.0f);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{-1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c8_, c8_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0f);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0f);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0f);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 1.0f);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, -1.0f);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, 0.0f);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, -1.0f);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, -1.0f);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, 0.0f);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0f);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, 1.0f);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, -1.0f);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, 1.0f);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 0.0f);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, 1.0f);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 1.0f);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 0.0f);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, 1.0f);
}

BOOST_AUTO_TEST_CASE(TestIsotropyD3Q15) {
  constexpr LatticeType latticeType = LatticeType::D3Q15;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];

      for(int d_b = 0; d_b < L::dimD; ++d_b) {
        int idx_b = L::dimD * idx_a + d_b;
        sumWeight2Celerity[idx_b] += L::weight()[iQ]*L::celerity()[iQ][d_a]*L::celerity()[iQ][d_b];

        if(d_a == d_b) {
          sumWeight2Celerity_r[idx_b] = L::cs2;
        }

        for(int d_c = 0; d_c < L::dimD; ++d_c) {
          int idx_c = L::dimD * idx_b + d_c;
          sumWeight3Celerity[idx_c] += L::weight()[iQ]*L::celerity()[iQ][d_a]
            *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c];

          for(int d_d = 0; d_d < L::dimD; ++d_d) {
            int idx_d = L::dimD * idx_c + d_d;
            sumWeight4Celerity[idx_d] += L::weight()[iQ]*L::celerity()[iQ][d_a]
              *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d];

            if((d_a == d_b && d_c == d_d)
               && (d_a == d_c && d_b == d_d)
               && (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 3.0*L::cs2*L::cs2;
            }
            else if(((d_a == d_b && d_c == d_d) && (d_a == d_c && d_b == d_d))
                    || ((d_a == d_b && d_c == d_d) && (d_a == d_d && d_b == d_d))
                    || ((d_a == d_d && d_b == d_c) && (d_a == d_d && d_b == d_d))) {
              sumWeight4Celerity_r[idx_d] = 2.0*L::cs2*L::cs2;
            }
            else if((d_a == d_b && d_c == d_d)
                    || (d_a == d_c && d_b == d_d)
                    || (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 1.0*L::cs2*L::cs2;
            }

            for(int d_e = 0; d_e < L::dimD; ++d_e) {
              int idx_e = L::dimD * idx_d + d_e;
              sumWeight5Celerity[idx_e] += L::weight()[iQ]*L::celerity()[iQ][d_a]
                *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d]
                *L::celerity()[iQ][d_e];
            }
          }
        }
      }
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight2Celerity[i] == sumWeight2Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight3Celerity[i] == sumWeight3Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight4Celerity[i] == sumWeight4Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight5Celerity[i] == sumWeight5Celerity_r[i], tt::tolerance(1e-15));
  }
}

BOOST_AUTO_TEST_CASE(TestDoubleD3Q15) {
  constexpr LatticeType latticeType = LatticeType::D3Q15;
  typedef double valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 3);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 15);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, lengthZ_g);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 1);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0/3.0));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 2.0/9.0);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0/9.0);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0/9.0);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0/9.0);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0/72.0);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0/72.0);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0/72.0);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0/72.0);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0/9.0);
  const auto w9_ = L::weight()[9];
  BOOST_CHECK_EQUAL(w9_, 1.0/9.0);
  const auto w10_ = L::weight()[10];
  BOOST_CHECK_EQUAL(w10_, 1.0/9.0);
  const auto w11_ = L::weight()[11];
  BOOST_CHECK_EQUAL(w11_, 1.0/72.0);
  const auto w12_ = L::weight()[12];
  BOOST_CHECK_EQUAL(w12_, 1.0/72.0);
  const auto w13_ = L::weight()[13];
  BOOST_CHECK_EQUAL(w13_, 1.0/72.0);
  const auto w14_ = L::weight()[14];
  BOOST_CHECK_EQUAL(w14_, 1.0/72.0);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0, -1.0, 0.0}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{0.0, 0.0, -1.0}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{-1.0, -1.0, -1.0}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{-1.0, -1.0, 1.0}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{-1.0, 1.0, -1.0}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{-1.0, 1.0, 1.0}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{1.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c8_, c8_r);
  const auto c9_ = L::celerity()[9];
  const auto c9_r = MathVector<valueType, dimD_>{{0.0, 1.0, 0.0}};
  BOOST_CHECK_EQUAL(c9_, c9_r);
  const auto c10_ = L::celerity()[10];
  const auto c10_r = MathVector<valueType, dimD_>{{0.0, 0.0, 1.0}};
  BOOST_CHECK_EQUAL(c10_, c10_r);
  const auto c11_ = L::celerity()[11];
  const auto c11_r = MathVector<valueType, dimD_>{{1.0, 1.0, 1.0}};
  BOOST_CHECK_EQUAL(c11_, c11_r);
  const auto c12_ = L::celerity()[12];
  const auto c12_r = MathVector<valueType, dimD_>{{1.0, 1.0, -1.0}};
  BOOST_CHECK_EQUAL(c12_, c12_r);
  const auto c13_ = L::celerity()[13];
  const auto c13_r = MathVector<valueType, dimD_>{{1.0, -1.0, 1.0}};
  BOOST_CHECK_EQUAL(c13_, c13_r);
  const auto c14_ = L::celerity()[14];
  const auto c14_r = MathVector<valueType, dimD_>{{1.0, -1.0, -1.0}};
  BOOST_CHECK_EQUAL(c14_, c14_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0);
  const auto c0_2_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_2_, 0.0);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0);
  const auto c1_2_ = L::celerity()[1][2];
  BOOST_CHECK_EQUAL(c1_2_, 0.0);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0);
  const auto c2_2_ = L::celerity()[2][2];
  BOOST_CHECK_EQUAL(c2_2_, 0.0);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 0.0);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0);
  const auto c3_2_ = L::celerity()[3][2];
  BOOST_CHECK_EQUAL(c3_2_, -1.0);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, -1.0);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0);
  const auto c4_2_ = L::celerity()[4][2];
  BOOST_CHECK_EQUAL(c4_2_, -1.0);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, -1.0);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, -1.0);
  const auto c5_2_ = L::celerity()[5][2];
  BOOST_CHECK_EQUAL(c5_2_, 1.0);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, -1.0);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 1.0);
  const auto c6_2_ = L::celerity()[6][2];
  BOOST_CHECK_EQUAL(c6_2_, -1.0);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, -1.0);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 1.0);
  const auto c7_2_ = L::celerity()[7][2];
  BOOST_CHECK_EQUAL(c7_2_, 1.0);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 1.0);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, 0.0);
  const auto c8_2_ = L::celerity()[8][2];
  BOOST_CHECK_EQUAL(c8_2_, 0.0);

  const auto c9_0_ = L::celerity()[9][0];
  BOOST_CHECK_EQUAL(c9_0_, 0.0);
  const auto c9_1_ = L::celerity()[9][1];
  BOOST_CHECK_EQUAL(c9_1_, 1.0);
  const auto c9_2_ = L::celerity()[9][2];
  BOOST_CHECK_EQUAL(c9_2_, 0.0);

  const auto c10_0_ = L::celerity()[10][0];
  BOOST_CHECK_EQUAL(c10_0_, 0.0);
  const auto c10_1_ = L::celerity()[10][1];
  BOOST_CHECK_EQUAL(c10_1_, 0.0);
  const auto c10_2_ = L::celerity()[10][2];
  BOOST_CHECK_EQUAL(c10_2_, 1.0);

  const auto c11_0_ = L::celerity()[11][0];
  BOOST_CHECK_EQUAL(c11_0_, 1.0);
  const auto c11_1_ = L::celerity()[11][1];
  BOOST_CHECK_EQUAL(c11_1_, 1.0);
  const auto c11_2_ = L::celerity()[11][2];
  BOOST_CHECK_EQUAL(c11_2_, 1.0);

  const auto c12_0_ = L::celerity()[12][0];
  BOOST_CHECK_EQUAL(c12_0_, 1.0);
  const auto c12_1_ = L::celerity()[12][1];
  BOOST_CHECK_EQUAL(c12_1_, 1.0);
  const auto c12_2_ = L::celerity()[12][2];
  BOOST_CHECK_EQUAL(c12_2_, -1.0);

  const auto c13_0_ = L::celerity()[13][0];
  BOOST_CHECK_EQUAL(c13_0_, 1.0);
  const auto c13_1_ = L::celerity()[13][1];
  BOOST_CHECK_EQUAL(c13_1_, -1.0);
  const auto c13_2_ = L::celerity()[13][2];
  BOOST_CHECK_EQUAL(c13_2_, 1.0);

  const auto c14_0_ = L::celerity()[14][0];
  BOOST_CHECK_EQUAL(c14_0_, 1.0);
  const auto c14_1_ = L::celerity()[14][1];
  BOOST_CHECK_EQUAL(c14_1_, -1.0);
  const auto c14_2_ = L::celerity()[14][2];
  BOOST_CHECK_EQUAL(c14_2_, -1.0);
}

BOOST_AUTO_TEST_CASE(TestFloatD3Q15) {
  constexpr LatticeType latticeType = LatticeType::D3Q15;
  typedef float valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 3);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 15);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, lengthZ_g);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 1);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0f));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0f/3.0f));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 2.0f/9.0f);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0f/9.0f);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0f/9.0f);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0f/9.0f);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0f/72.0f);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0f/72.0f);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0f/72.0f);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0f/72.0f);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0f/9.0f);
  const auto w9_ = L::weight()[9];
  BOOST_CHECK_EQUAL(w9_, 1.0f/9.0f);
  const auto w10_ = L::weight()[10];
  BOOST_CHECK_EQUAL(w10_, 1.0f/9.0f);
  const auto w11_ = L::weight()[11];
  BOOST_CHECK_EQUAL(w11_, 1.0f/72.0f);
  const auto w12_ = L::weight()[12];
  BOOST_CHECK_EQUAL(w12_, 1.0f/72.0f);
  const auto w13_ = L::weight()[13];
  BOOST_CHECK_EQUAL(w13_, 1.0f/72.0f);
  const auto w14_ = L::weight()[14];
  BOOST_CHECK_EQUAL(w14_, 1.0f/72.0f);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0f, -1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{-1.0f, -1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{-1.0f, -1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{-1.0f, 1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{-1.0f, 1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{1.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c8_, c8_r);
  const auto c9_ = L::celerity()[9];
  const auto c9_r = MathVector<valueType, dimD_>{{0.0f, 1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c9_, c9_r);
  const auto c10_ = L::celerity()[10];
  const auto c10_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c10_, c10_r);
  const auto c11_ = L::celerity()[11];
  const auto c11_r = MathVector<valueType, dimD_>{{1.0f, 1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c11_, c11_r);
  const auto c12_ = L::celerity()[12];
  const auto c12_r = MathVector<valueType, dimD_>{{1.0f, 1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c12_, c12_r);
  const auto c13_ = L::celerity()[13];
  const auto c13_r = MathVector<valueType, dimD_>{{1.0f, -1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c13_, c13_r);
  const auto c14_ = L::celerity()[14];
  const auto c14_r = MathVector<valueType, dimD_>{{1.0f, -1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c14_, c14_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0f);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0f);
  const auto c0_2_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_2_, 0.0f);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0f);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0f);
  const auto c1_2_ = L::celerity()[1][2];
  BOOST_CHECK_EQUAL(c1_2_, 0.0f);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0f);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0f);
  const auto c2_2_ = L::celerity()[2][2];
  BOOST_CHECK_EQUAL(c2_2_, 0.0f);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 0.0f);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0f);
  const auto c3_2_ = L::celerity()[3][2];
  BOOST_CHECK_EQUAL(c3_2_, -1.0f);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, -1.0f);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0f);
  const auto c4_2_ = L::celerity()[4][2];
  BOOST_CHECK_EQUAL(c4_2_, -1.0f);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, -1.0f);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, -1.0f);
  const auto c5_2_ = L::celerity()[5][2];
  BOOST_CHECK_EQUAL(c5_2_, 1.0f);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, -1.0f);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 1.0f);
  const auto c6_2_ = L::celerity()[6][2];
  BOOST_CHECK_EQUAL(c6_2_, -1.0f);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, -1.0f);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 1.0f);
  const auto c7_2_ = L::celerity()[7][2];
  BOOST_CHECK_EQUAL(c7_2_, 1.0f);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 1.0f);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, 0.0f);
  const auto c8_2_ = L::celerity()[8][2];
  BOOST_CHECK_EQUAL(c8_2_, 0.0f);

  const auto c9_0_ = L::celerity()[9][0];
  BOOST_CHECK_EQUAL(c9_0_, 0.0f);
  const auto c9_1_ = L::celerity()[9][1];
  BOOST_CHECK_EQUAL(c9_1_, 1.0f);
  const auto c9_2_ = L::celerity()[9][2];
  BOOST_CHECK_EQUAL(c9_2_, 0.0f);

  const auto c10_0_ = L::celerity()[10][0];
  BOOST_CHECK_EQUAL(c10_0_, 0.0f);
  const auto c10_1_ = L::celerity()[10][1];
  BOOST_CHECK_EQUAL(c10_1_, 0.0f);
  const auto c10_2_ = L::celerity()[10][2];
  BOOST_CHECK_EQUAL(c10_2_, 1.0f);

  const auto c11_0_ = L::celerity()[11][0];
  BOOST_CHECK_EQUAL(c11_0_, 1.0f);
  const auto c11_1_ = L::celerity()[11][1];
  BOOST_CHECK_EQUAL(c11_1_, 1.0f);
  const auto c11_2_ = L::celerity()[11][2];
  BOOST_CHECK_EQUAL(c11_2_, 1.0f);

  const auto c12_0_ = L::celerity()[12][0];
  BOOST_CHECK_EQUAL(c12_0_, 1.0f);
  const auto c12_1_ = L::celerity()[12][1];
  BOOST_CHECK_EQUAL(c12_1_, 1.0f);
  const auto c12_2_ = L::celerity()[12][2];
  BOOST_CHECK_EQUAL(c12_2_, -1.0f);

  const auto c13_0_ = L::celerity()[13][0];
  BOOST_CHECK_EQUAL(c13_0_, 1.0f);
  const auto c13_1_ = L::celerity()[13][1];
  BOOST_CHECK_EQUAL(c13_1_, -1.0f);
  const auto c13_2_ = L::celerity()[13][2];
  BOOST_CHECK_EQUAL(c13_2_, 1.0f);

  const auto c14_0_ = L::celerity()[14][0];
  BOOST_CHECK_EQUAL(c14_0_, 1.0f);
  const auto c14_1_ = L::celerity()[14][1];
  BOOST_CHECK_EQUAL(c14_1_, -1.0f);
  const auto c14_2_ = L::celerity()[14][2];
  BOOST_CHECK_EQUAL(c14_2_, -1.0f);
}

BOOST_AUTO_TEST_CASE(TestIsotropyD3Q19) {
  constexpr LatticeType latticeType = LatticeType::D3Q19;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];

      for(int d_b = 0; d_b < L::dimD; ++d_b) {
        int idx_b = L::dimD * idx_a + d_b;
        sumWeight2Celerity[idx_b] += L::weight()[iQ]*L::celerity()[iQ][d_a]*L::celerity()[iQ][d_b];

        if(d_a == d_b) {
          sumWeight2Celerity_r[idx_b] = L::cs2;
        }

        for(int d_c = 0; d_c < L::dimD; ++d_c) {
          int idx_c = L::dimD * idx_b + d_c;
          sumWeight3Celerity[idx_c] += L::weight()[iQ]*L::celerity()[iQ][d_a]
            *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c];

          for(int d_d = 0; d_d < L::dimD; ++d_d) {
            int idx_d = L::dimD * idx_c + d_d;
            sumWeight4Celerity[idx_d] += L::weight()[iQ]*L::celerity()[iQ][d_a]
              *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d];

            if((d_a == d_b && d_c == d_d)
               && (d_a == d_c && d_b == d_d)
               && (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 3.0*L::cs2*L::cs2;
            }
            else if(((d_a == d_b && d_c == d_d) && (d_a == d_c && d_b == d_d))
                    || ((d_a == d_b && d_c == d_d) && (d_a == d_d && d_b == d_d))
                    || ((d_a == d_d && d_b == d_c) && (d_a == d_d && d_b == d_d))) {
              sumWeight4Celerity_r[idx_d] = 2.0*L::cs2*L::cs2;
            }
            else if((d_a == d_b && d_c == d_d)
                    || (d_a == d_c && d_b == d_d)
                    || (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 1.0*L::cs2*L::cs2;
            }

            for(int d_e = 0; d_e < L::dimD; ++d_e) {
              int idx_e = L::dimD * idx_d + d_e;
              sumWeight5Celerity[idx_e] += L::weight()[iQ]*L::celerity()[iQ][d_a]
                *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d]
                *L::celerity()[iQ][d_e];
            }
          }
        }
      }
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight2Celerity[i] == sumWeight2Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight3Celerity[i] == sumWeight3Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight4Celerity[i] == sumWeight4Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight5Celerity[i] == sumWeight5Celerity_r[i], tt::tolerance(1e-15));
  }
}

BOOST_AUTO_TEST_CASE(TestDoubleD3Q19) {
  constexpr LatticeType latticeType = LatticeType::D3Q19;
  typedef double valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 3);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 19);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, lengthZ_g);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 1);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0/3.0));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 1.0/3.0);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0/18.0);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0/18.0);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0/18.0);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0/36.0);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0/36.0);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0/36.0);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0/36.0);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0/36.0);
  const auto w9_ = L::weight()[9];
  BOOST_CHECK_EQUAL(w9_, 1.0/36.0);
  const auto w10_ = L::weight()[10];
  BOOST_CHECK_EQUAL(w10_, 1.0/18.0);
  const auto w11_ = L::weight()[11];
  BOOST_CHECK_EQUAL(w11_, 1.0/18.0);
  const auto w12_ = L::weight()[12];
  BOOST_CHECK_EQUAL(w12_, 1.0/18.0);
  const auto w13_ = L::weight()[13];
  BOOST_CHECK_EQUAL(w13_, 1.0/36.0);
  const auto w14_ = L::weight()[14];
  BOOST_CHECK_EQUAL(w14_, 1.0/36.0);
  const auto w15_ = L::weight()[15];
  BOOST_CHECK_EQUAL(w15_, 1.0/36.0);
  const auto w16_ = L::weight()[16];
  BOOST_CHECK_EQUAL(w16_, 1.0/36.0);
  const auto w17_ = L::weight()[17];
  BOOST_CHECK_EQUAL(w17_, 1.0/36.0);
  const auto w18_ = L::weight()[18];
  BOOST_CHECK_EQUAL(w18_, 1.0/36.0);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0, -1.0, 0.0}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{0.0, 0.0, -1.0}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{-1.0, -1.0, 0.0}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{-1.0, 1.0, 0.0}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{-1.0, 0.0, -1.0}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{-1.0, 0.0, 1.0}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{0.0, -1.0, -1.0}};
  BOOST_CHECK_EQUAL(c8_, c8_r);
  const auto c9_ = L::celerity()[9];
  const auto c9_r = MathVector<valueType, dimD_>{{0.0, -1.0, 1.0}};
  BOOST_CHECK_EQUAL(c9_, c9_r);
  const auto c10_ = L::celerity()[10];
  const auto c10_r = MathVector<valueType, dimD_>{{1.0, 0.0, 0.0}};
  BOOST_CHECK_EQUAL(c10_, c10_r);
  const auto c11_ = L::celerity()[11];
  const auto c11_r = MathVector<valueType, dimD_>{{0.0, 1.0, 0.0}};
  BOOST_CHECK_EQUAL(c11_, c11_r);
  const auto c12_ = L::celerity()[12];
  const auto c12_r = MathVector<valueType, dimD_>{{0.0, 0.0, 1.0}};
  BOOST_CHECK_EQUAL(c12_, c12_r);
  const auto c13_ = L::celerity()[13];
  const auto c13_r = MathVector<valueType, dimD_>{{1.0, 1.0, 0.0}};
  BOOST_CHECK_EQUAL(c13_, c13_r);
  const auto c14_ = L::celerity()[14];
  const auto c14_r = MathVector<valueType, dimD_>{{1.0, -1.0, 0.0}};
  BOOST_CHECK_EQUAL(c14_, c14_r);
  const auto c15_ = L::celerity()[15];
  const auto c15_r = MathVector<valueType, dimD_>{{1.0, 0.0, 1.0}};
  BOOST_CHECK_EQUAL(c15_, c15_r);
  const auto c16_ = L::celerity()[16];
  const auto c16_r = MathVector<valueType, dimD_>{{1.0, 0.0, -1.0}};
  BOOST_CHECK_EQUAL(c16_, c16_r);
  const auto c17_ = L::celerity()[17];
  const auto c17_r = MathVector<valueType, dimD_>{{0.0, 1.0, 1.0}};
  BOOST_CHECK_EQUAL(c17_, c17_r);
  const auto c18_ = L::celerity()[18];
  const auto c18_r = MathVector<valueType, dimD_>{{0.0, 1.0, -1.0}};
  BOOST_CHECK_EQUAL(c18_, c18_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0);
  const auto c0_2_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_2_, 0.0);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0);
  const auto c1_2_ = L::celerity()[1][2];
  BOOST_CHECK_EQUAL(c1_2_, 0.0);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0);
  const auto c2_2_ = L::celerity()[2][2];
  BOOST_CHECK_EQUAL(c2_2_, 0.0);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 0.0);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0);
  const auto c3_2_ = L::celerity()[3][2];
  BOOST_CHECK_EQUAL(c3_2_, -1.0);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, -1.0);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0);
  const auto c4_2_ = L::celerity()[4][2];
  BOOST_CHECK_EQUAL(c4_2_, 0.0);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, -1.0);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, 1.0);
  const auto c5_2_ = L::celerity()[5][2];
  BOOST_CHECK_EQUAL(c5_2_, 0.0);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, -1.0);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 0.0);
  const auto c6_2_ = L::celerity()[6][2];
  BOOST_CHECK_EQUAL(c6_2_, -1.0);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, -1.0);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 0.0);
  const auto c7_2_ = L::celerity()[7][2];
  BOOST_CHECK_EQUAL(c7_2_, 1.0);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 0.0);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, -1.0);
  const auto c8_2_ = L::celerity()[8][2];
  BOOST_CHECK_EQUAL(c8_2_, -1.0);

  const auto c9_0_ = L::celerity()[9][0];
  BOOST_CHECK_EQUAL(c9_0_, 0.0);
  const auto c9_1_ = L::celerity()[9][1];
  BOOST_CHECK_EQUAL(c9_1_, -1.0);
  const auto c9_2_ = L::celerity()[9][2];
  BOOST_CHECK_EQUAL(c9_2_, 1.0);

  const auto c10_0_ = L::celerity()[10][0];
  BOOST_CHECK_EQUAL(c10_0_, 1.0);
  const auto c10_1_ = L::celerity()[10][1];
  BOOST_CHECK_EQUAL(c10_1_, 0.0);
  const auto c10_2_ = L::celerity()[10][2];
  BOOST_CHECK_EQUAL(c10_2_, 0.0);

  const auto c11_0_ = L::celerity()[11][0];
  BOOST_CHECK_EQUAL(c11_0_, 0.0);
  const auto c11_1_ = L::celerity()[11][1];
  BOOST_CHECK_EQUAL(c11_1_, 1.0);
  const auto c11_2_ = L::celerity()[11][2];
  BOOST_CHECK_EQUAL(c11_2_, 0.0);

  const auto c12_0_ = L::celerity()[12][0];
  BOOST_CHECK_EQUAL(c12_0_, 0.0);
  const auto c12_1_ = L::celerity()[12][1];
  BOOST_CHECK_EQUAL(c12_1_, 0.0);
  const auto c12_2_ = L::celerity()[12][2];
  BOOST_CHECK_EQUAL(c12_2_, 1.0);

  const auto c13_0_ = L::celerity()[13][0];
  BOOST_CHECK_EQUAL(c13_0_, 1.0);
  const auto c13_1_ = L::celerity()[13][1];
  BOOST_CHECK_EQUAL(c13_1_, 1.0);
  const auto c13_2_ = L::celerity()[13][2];
  BOOST_CHECK_EQUAL(c13_2_, 0.0);

  const auto c14_0_ = L::celerity()[14][0];
  BOOST_CHECK_EQUAL(c14_0_, 1.0);
  const auto c14_1_ = L::celerity()[14][1];
  BOOST_CHECK_EQUAL(c14_1_, -1.0);
  const auto c14_2_ = L::celerity()[14][2];
  BOOST_CHECK_EQUAL(c14_2_, 0.0);

  const auto c15_0_ = L::celerity()[15][0];
  BOOST_CHECK_EQUAL(c15_0_, 1.0);
  const auto c15_1_ = L::celerity()[15][1];
  BOOST_CHECK_EQUAL(c15_1_, 0.0);
  const auto c15_2_ = L::celerity()[15][2];
  BOOST_CHECK_EQUAL(c15_2_, 1.0);

  const auto c16_0_ = L::celerity()[16][0];
  BOOST_CHECK_EQUAL(c16_0_, 1.0);
  const auto c16_1_ = L::celerity()[16][1];
  BOOST_CHECK_EQUAL(c16_1_, 0.0);
  const auto c16_2_ = L::celerity()[16][2];
  BOOST_CHECK_EQUAL(c16_2_, -1.0);

  const auto c17_0_ = L::celerity()[17][0];
  BOOST_CHECK_EQUAL(c17_0_, 0.0);
  const auto c17_1_ = L::celerity()[17][1];
  BOOST_CHECK_EQUAL(c17_1_, 1.0);
  const auto c17_2_ = L::celerity()[17][2];
  BOOST_CHECK_EQUAL(c17_2_, 1.0);

  const auto c18_0_ = L::celerity()[18][0];
  BOOST_CHECK_EQUAL(c18_0_, 0.0);
  const auto c18_1_ = L::celerity()[18][1];
  BOOST_CHECK_EQUAL(c18_1_, 1.0);
  const auto c18_2_ = L::celerity()[18][2];
  BOOST_CHECK_EQUAL(c18_2_, -1.0);

}

BOOST_AUTO_TEST_CASE(TestFloatD3Q19) {
  constexpr LatticeType latticeType = LatticeType::D3Q19;
  typedef float valueType;

  typedef Lattice<valueType, latticeType> L;

  const auto dimD_ = L::dimD;
  BOOST_CHECK_EQUAL(dimD_, 3);
  const auto dimQ_ = L::dimQ;
  BOOST_CHECK_EQUAL(dimQ_, 19);

  const auto lX_g_ = L::lX_g;
  BOOST_CHECK_EQUAL(lX_g_, lengthX_g);
  const auto lY_g_ = L::lY_g;
  BOOST_CHECK_EQUAL(lY_g_, lengthY_g);
  const auto lZ_g_ = L::lZ_g;
  BOOST_CHECK_EQUAL(lZ_g_, lengthZ_g);

  const auto hX_ = L::hX;
  BOOST_CHECK_EQUAL(hX_, 1);
  const auto hY_ = L::hY;
  BOOST_CHECK_EQUAL(hY_, 1);
  const auto hZ_ = L::hZ;
  BOOST_CHECK_EQUAL(hZ_, 1);

  const auto inv_cs2_ = L::inv_cs2;
  BOOST_CHECK_EQUAL(inv_cs2_, (3.0f));
  const auto cs2_ = L::cs2;
  BOOST_CHECK_EQUAL(cs2_, (1.0f/3.0f));

  const auto w0_ = L::weight()[0];
  BOOST_CHECK_EQUAL(w0_, 1.0f/3.0f);
  const auto w1_ = L::weight()[1];
  BOOST_CHECK_EQUAL(w1_, 1.0f/18.0f);
  const auto w2_ = L::weight()[2];
  BOOST_CHECK_EQUAL(w2_, 1.0f/18.0f);
  const auto w3_ = L::weight()[3];
  BOOST_CHECK_EQUAL(w3_, 1.0f/18.0f);
  const auto w4_ = L::weight()[4];
  BOOST_CHECK_EQUAL(w4_, 1.0f/36.0f);
  const auto w5_ = L::weight()[5];
  BOOST_CHECK_EQUAL(w5_, 1.0f/36.0f);
  const auto w6_ = L::weight()[6];
  BOOST_CHECK_EQUAL(w6_, 1.0f/36.0f);
  const auto w7_ = L::weight()[7];
  BOOST_CHECK_EQUAL(w7_, 1.0f/36.0f);
  const auto w8_ = L::weight()[8];
  BOOST_CHECK_EQUAL(w8_, 1.0f/36.0f);
  const auto w9_ = L::weight()[9];
  BOOST_CHECK_EQUAL(w9_, 1.0f/36.0f);
  const auto w10_ = L::weight()[10];
  BOOST_CHECK_EQUAL(w10_, 1.0f/18.0f);
  const auto w11_ = L::weight()[11];
  BOOST_CHECK_EQUAL(w11_, 1.0f/18.0f);
  const auto w12_ = L::weight()[12];
  BOOST_CHECK_EQUAL(w12_, 1.0f/18.0f);
  const auto w13_ = L::weight()[13];
  BOOST_CHECK_EQUAL(w13_, 1.0f/36.0f);
  const auto w14_ = L::weight()[14];
  BOOST_CHECK_EQUAL(w14_, 1.0f/36.0f);
  const auto w15_ = L::weight()[15];
  BOOST_CHECK_EQUAL(w15_, 1.0f/36.0f);
  const auto w16_ = L::weight()[16];
  BOOST_CHECK_EQUAL(w16_, 1.0f/36.0f);
  const auto w17_ = L::weight()[17];
  BOOST_CHECK_EQUAL(w17_, 1.0f/36.0f);
  const auto w18_ = L::weight()[18];
  BOOST_CHECK_EQUAL(w18_, 1.0f/36.0f);

  const auto c0_ = L::celerity()[0];
  const auto c0_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c0_, c0_r);
  const auto c1_ = L::celerity()[1];
  const auto c1_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c1_, c1_r);
  const auto c2_ = L::celerity()[2];
  const auto c2_r = MathVector<valueType, dimD_>{{0.0f, -1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c2_, c2_r);
  const auto c3_ = L::celerity()[3];
  const auto c3_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c3_, c3_r);
  const auto c4_ = L::celerity()[4];
  const auto c4_r = MathVector<valueType, dimD_>{{-1.0f, -1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c4_, c4_r);
  const auto c5_ = L::celerity()[5];
  const auto c5_r = MathVector<valueType, dimD_>{{-1.0f, 1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c5_, c5_r);
  const auto c6_ = L::celerity()[6];
  const auto c6_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c6_, c6_r);
  const auto c7_ = L::celerity()[7];
  const auto c7_r = MathVector<valueType, dimD_>{{-1.0f, 0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c7_, c7_r);
  const auto c8_ = L::celerity()[8];
  const auto c8_r = MathVector<valueType, dimD_>{{0.0f, -1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c8_, c8_r);
  const auto c9_ = L::celerity()[9];
  const auto c9_r = MathVector<valueType, dimD_>{{0.0f, -1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c9_, c9_r);
  const auto c10_ = L::celerity()[10];
  const auto c10_r = MathVector<valueType, dimD_>{{1.0f, 0.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c10_, c10_r);
  const auto c11_ = L::celerity()[11];
  const auto c11_r = MathVector<valueType, dimD_>{{0.0f, 1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c11_, c11_r);
  const auto c12_ = L::celerity()[12];
  const auto c12_r = MathVector<valueType, dimD_>{{0.0f, 0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c12_, c12_r);
  const auto c13_ = L::celerity()[13];
  const auto c13_r = MathVector<valueType, dimD_>{{1.0f, 1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c13_, c13_r);
  const auto c14_ = L::celerity()[14];
  const auto c14_r = MathVector<valueType, dimD_>{{1.0f, -1.0f, 0.0f}};
  BOOST_CHECK_EQUAL(c14_, c14_r);
  const auto c15_ = L::celerity()[15];
  const auto c15_r = MathVector<valueType, dimD_>{{1.0f, 0.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c15_, c15_r);
  const auto c16_ = L::celerity()[16];
  const auto c16_r = MathVector<valueType, dimD_>{{1.0f, 0.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c16_, c16_r);
  const auto c17_ = L::celerity()[17];
  const auto c17_r = MathVector<valueType, dimD_>{{0.0f, 1.0f, 1.0f}};
  BOOST_CHECK_EQUAL(c17_, c17_r);
  const auto c18_ = L::celerity()[18];
  const auto c18_r = MathVector<valueType, dimD_>{{0.0f, 1.0f, -1.0f}};
  BOOST_CHECK_EQUAL(c18_, c18_r);

  const auto c0_0_ = L::celerity()[0][0];
  BOOST_CHECK_EQUAL(c0_0_, 0.0f);
  const auto c0_1_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_1_, 0.0f);
  const auto c0_2_ = L::celerity()[0][1];
  BOOST_CHECK_EQUAL(c0_2_, 0.0f);

  const auto c1_0_ = L::celerity()[1][0];
  BOOST_CHECK_EQUAL(c1_0_, -1.0f);
  const auto c1_1_ = L::celerity()[1][1];
  BOOST_CHECK_EQUAL(c1_1_, 0.0f);
  const auto c1_2_ = L::celerity()[1][2];
  BOOST_CHECK_EQUAL(c1_2_, 0.0f);

  const auto c2_0_ = L::celerity()[2][0];
  BOOST_CHECK_EQUAL(c2_0_, 0.0f);
  const auto c2_1_ = L::celerity()[2][1];
  BOOST_CHECK_EQUAL(c2_1_, -1.0f);
  const auto c2_2_ = L::celerity()[2][2];
  BOOST_CHECK_EQUAL(c2_2_, 0.0f);

  const auto c3_0_ = L::celerity()[3][0];
  BOOST_CHECK_EQUAL(c3_0_, 0.0f);
  const auto c3_1_ = L::celerity()[3][1];
  BOOST_CHECK_EQUAL(c3_1_, 0.0f);
  const auto c3_2_ = L::celerity()[3][2];
  BOOST_CHECK_EQUAL(c3_2_, -1.0f);

  const auto c4_0_ = L::celerity()[4][0];
  BOOST_CHECK_EQUAL(c4_0_, -1.0f);
  const auto c4_1_ = L::celerity()[4][1];
  BOOST_CHECK_EQUAL(c4_1_, -1.0f);
  const auto c4_2_ = L::celerity()[4][2];
  BOOST_CHECK_EQUAL(c4_2_, 0.0f);

  const auto c5_0_ = L::celerity()[5][0];
  BOOST_CHECK_EQUAL(c5_0_, -1.0f);
  const auto c5_1_ = L::celerity()[5][1];
  BOOST_CHECK_EQUAL(c5_1_, 1.0f);
  const auto c5_2_ = L::celerity()[5][2];
  BOOST_CHECK_EQUAL(c5_2_, 0.0f);

  const auto c6_0_ = L::celerity()[6][0];
  BOOST_CHECK_EQUAL(c6_0_, -1.0f);
  const auto c6_1_ = L::celerity()[6][1];
  BOOST_CHECK_EQUAL(c6_1_, 0.0f);
  const auto c6_2_ = L::celerity()[6][2];
  BOOST_CHECK_EQUAL(c6_2_, -1.0f);

  const auto c7_0_ = L::celerity()[7][0];
  BOOST_CHECK_EQUAL(c7_0_, -1.0f);
  const auto c7_1_ = L::celerity()[7][1];
  BOOST_CHECK_EQUAL(c7_1_, 0.0f);
  const auto c7_2_ = L::celerity()[7][2];
  BOOST_CHECK_EQUAL(c7_2_, 1.0f);

  const auto c8_0_ = L::celerity()[8][0];
  BOOST_CHECK_EQUAL(c8_0_, 0.0f);
  const auto c8_1_ = L::celerity()[8][1];
  BOOST_CHECK_EQUAL(c8_1_, -1.0f);
  const auto c8_2_ = L::celerity()[8][2];
  BOOST_CHECK_EQUAL(c8_2_, -1.0f);

  const auto c9_0_ = L::celerity()[9][0];
  BOOST_CHECK_EQUAL(c9_0_, 0.0f);
  const auto c9_1_ = L::celerity()[9][1];
  BOOST_CHECK_EQUAL(c9_1_, -1.0f);
  const auto c9_2_ = L::celerity()[9][2];
  BOOST_CHECK_EQUAL(c9_2_, 1.0f);

  const auto c10_0_ = L::celerity()[10][0];
  BOOST_CHECK_EQUAL(c10_0_, 1.0f);
  const auto c10_1_ = L::celerity()[10][1];
  BOOST_CHECK_EQUAL(c10_1_, 0.0f);
  const auto c10_2_ = L::celerity()[10][2];
  BOOST_CHECK_EQUAL(c10_2_, 0.0f);

  const auto c11_0_ = L::celerity()[11][0];
  BOOST_CHECK_EQUAL(c11_0_, 0.0f);
  const auto c11_1_ = L::celerity()[11][1];
  BOOST_CHECK_EQUAL(c11_1_, 1.0f);
  const auto c11_2_ = L::celerity()[11][2];
  BOOST_CHECK_EQUAL(c11_2_, 0.0f);

  const auto c12_0_ = L::celerity()[12][0];
  BOOST_CHECK_EQUAL(c12_0_, 0.0f);
  const auto c12_1_ = L::celerity()[12][1];
  BOOST_CHECK_EQUAL(c12_1_, 0.0f);
  const auto c12_2_ = L::celerity()[12][2];
  BOOST_CHECK_EQUAL(c12_2_, 1.0f);

  const auto c13_0_ = L::celerity()[13][0];
  BOOST_CHECK_EQUAL(c13_0_, 1.0f);
  const auto c13_1_ = L::celerity()[13][1];
  BOOST_CHECK_EQUAL(c13_1_, 1.0f);
  const auto c13_2_ = L::celerity()[13][2];
  BOOST_CHECK_EQUAL(c13_2_, 0.0f);

  const auto c14_0_ = L::celerity()[14][0];
  BOOST_CHECK_EQUAL(c14_0_, 1.0f);
  const auto c14_1_ = L::celerity()[14][1];
  BOOST_CHECK_EQUAL(c14_1_, -1.0f);
  const auto c14_2_ = L::celerity()[14][2];
  BOOST_CHECK_EQUAL(c14_2_, 0.0f);

  const auto c15_0_ = L::celerity()[15][0];
  BOOST_CHECK_EQUAL(c15_0_, 1.0f);
  const auto c15_1_ = L::celerity()[15][1];
  BOOST_CHECK_EQUAL(c15_1_, 0.0f);
  const auto c15_2_ = L::celerity()[15][2];
  BOOST_CHECK_EQUAL(c15_2_, 1.0f);

  const auto c16_0_ = L::celerity()[16][0];
  BOOST_CHECK_EQUAL(c16_0_, 1.0f);
  const auto c16_1_ = L::celerity()[16][1];
  BOOST_CHECK_EQUAL(c16_1_, 0.0f);
  const auto c16_2_ = L::celerity()[16][2];
  BOOST_CHECK_EQUAL(c16_2_, -1.0f);

  const auto c17_0_ = L::celerity()[17][0];
  BOOST_CHECK_EQUAL(c17_0_, 0.0f);
  const auto c17_1_ = L::celerity()[17][1];
  BOOST_CHECK_EQUAL(c17_1_, 1.0f);
  const auto c17_2_ = L::celerity()[17][2];
  BOOST_CHECK_EQUAL(c17_2_, 1.0f);

  const auto c18_0_ = L::celerity()[18][0];
  BOOST_CHECK_EQUAL(c18_0_, 0.0f);
  const auto c18_1_ = L::celerity()[18][1];
  BOOST_CHECK_EQUAL(c18_1_, 1.0f);
  const auto c18_2_ = L::celerity()[18][2];
  BOOST_CHECK_EQUAL(c18_2_, -1.0f);
}

BOOST_AUTO_TEST_CASE(TestIsotropyD3Q27) {
  constexpr LatticeType latticeType = LatticeType::D3Q27;
  typedef double valueType;
  typedef Lattice<valueType, latticeType> L;

  valueType sumWeight = 0.0;
  valueType sumWeight_r = 1.0;

  MathVector<valueType, L::dimD> sumWeightCelerity{{0.0}};
  MathVector<valueType, L::dimD> sumWeightCelerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD> sumWeight2Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD> sumWeight3Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD> sumWeight4Celerity_r{{0.0}};

  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity{{0.0}};
  MathVector<valueType, L::dimD*L::dimD*L::dimD*L::dimD*L::dimD> sumWeight5Celerity_r{{0.0}};

  for(int iQ = 0; iQ < L::dimQ; ++iQ) {
    sumWeight += L::weight()[iQ];

    for(int d_a = 0; d_a < L::dimD; ++d_a) {
      int idx_a = d_a;
      sumWeightCelerity[idx_a] += L::weight()[iQ]*L::celerity()[iQ][d_a];

      for(int d_b = 0; d_b < L::dimD; ++d_b) {
        int idx_b = L::dimD * idx_a + d_b;
        sumWeight2Celerity[idx_b] += L::weight()[iQ]*L::celerity()[iQ][d_a]*L::celerity()[iQ][d_b];

        if(d_a == d_b) {
          sumWeight2Celerity_r[idx_b] = L::cs2;
        }

        for(int d_c = 0; d_c < L::dimD; ++d_c) {
          int idx_c = L::dimD * idx_b + d_c;
          sumWeight3Celerity[idx_c] += L::weight()[iQ]*L::celerity()[iQ][d_a]
            *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c];

          for(int d_d = 0; d_d < L::dimD; ++d_d) {
            int idx_d = L::dimD * idx_c + d_d;
            sumWeight4Celerity[idx_d] += L::weight()[iQ]*L::celerity()[iQ][d_a]
              *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d];

            if((d_a == d_b && d_c == d_d)
               && (d_a == d_c && d_b == d_d)
               && (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 3.0*L::cs2*L::cs2;
            }
            else if(((d_a == d_b && d_c == d_d) && (d_a == d_c && d_b == d_d))
                    || ((d_a == d_b && d_c == d_d) && (d_a == d_d && d_b == d_d))
                    || ((d_a == d_d && d_b == d_c) && (d_a == d_d && d_b == d_d))) {
              sumWeight4Celerity_r[idx_d] = 2.0*L::cs2*L::cs2;
            }
            else if((d_a == d_b && d_c == d_d)
                    || (d_a == d_c && d_b == d_d)
                    || (d_a == d_d && d_b == d_c)) {
              sumWeight4Celerity_r[idx_d] = 1.0*L::cs2*L::cs2;
            }

            for(int d_e = 0; d_e < L::dimD; ++d_e) {
              int idx_e = L::dimD * idx_d + d_e;
              sumWeight5Celerity[idx_e] += L::weight()[iQ]*L::celerity()[iQ][d_a]
                *L::celerity()[iQ][d_b]*L::celerity()[iQ][d_c]*L::celerity()[iQ][d_d]
                *L::celerity()[iQ][d_e];
            }
          }
        }
      }
    }
  }

  BOOST_TEST(sumWeight == sumWeight_r, tt::tolerance(1e-15));
  for(int i = 0; i < L::dimD; ++i) {
    BOOST_TEST(sumWeightCelerity[i] == sumWeightCelerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight2Celerity[i] == sumWeight2Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight3Celerity[i] == sumWeight3Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight4Celerity[i] == sumWeight4Celerity_r[i], tt::tolerance(1e-15));
  }
  for(int i = 0; i < L::dimD*L::dimD*L::dimD*L::dimD*L::dimD; ++i) {
    BOOST_TEST(sumWeight5Celerity[i] == sumWeight5Celerity_r[i], tt::tolerance(1e-15));
  }
}

// TODO: Add doubleD3Q27 and floatD3Q27 tests.

BOOST_AUTO_TEST_SUITE_END()


#ifndef VISUALIZER_H
#define VISUALIZER_H
#include "SFML/Graphics.hpp"
#include "SFML/Window.hpp"
#include <armadillo>
#include <string>
#include "colormaps.hpp"
#include "lowPassFilter.hpp"
#include "gaussianKernel.hpp"

namespace visa
{
/** Matrix visualizer using the SFML libray */
class Visualizer
{
public:
  Visualizer();
  ~Visualizer();

  /** Initialize the window handler */
  virtual void init( const char *windowName );

  /** Initialize the window handler */
  void init();

  /** Set values to visualize. Depricated use setImg instead */
  void fillVertexArray( arma::mat &values );

  /** Creates an image from the matrix */
  void setImg( arma::mat &values );

  /** Set values to visualize */
  virtual void fillVertexArray( arma::vec &values ){};

  /** Set upper limit of the colorscale */
  void setColorMax( double max ){ colorMax = max; };

  /** Set the lower limit for the colorscale */
  void setColorMin( double min ){ colorMin = min; };

  /** Set both colorlimits (provided for convenience) */
  void setColorLim( double min, double max );

  /** Set color map */
  void setCmap( Colormaps::Colormap_t cm ){ cmaps.setMap(cm); };

  /** Sets the opacity */
  void setOpacity( double alpha );

  /** Get current image */
  const sf::Image& getImg() const { return *img; };

  /** Return the name of the plot */
  const std::string& getName() const { return name; };

  /** Returns the transparency factor */
  sf::Uint8 getAlpha() const { return alpha; };

  /** Set upper and lower y-limit. Only relevant for 1D plots */
  virtual void setLimits( double min, double max ){};

  /** True of the image can be plotted */
  bool isReady() const;
protected:
  sf::VertexArray *vArray{NULL};
  std::string name;
  sf::Image *img{NULL};

  unsigned int width{640};
  unsigned int height{480};
  static const unsigned int defaultWidth{640};
  static const unsigned int defaultHeight{480};
  double colorMax{1.0};
  double colorMin{0.0};
  unsigned int vArrayNrow{0};
  unsigned int vArrayNcol{0};
  Colormaps cmaps;
  bool resizingEnabled{true};
  bool colorLimitsSetByUser{false};
  sf::Uint8 *pixels{NULL};
  sf::Uint8 alpha{255};
  unsigned int nPix{0};

  /** Set color corresponding to value */
  void setColor( double value, sf::Color &color ) const;

  /** Run the matrix through the low pass filter */
  void filterMatrix( arma::mat &mat );

  /** Resize the window to match the matrix */
  void resizeWindow( unsigned int newWidth, unsigned int newHeight );

  /** Resize width */
  void resizeWidth( unsigned int newWidth );

  /** Resize height */
  void resizeHeight( unsigned int newHeight );

  /** Restores the default window height and width */
  void restoreDefaultWindowSize();

  /** Filter horizontally */
  void filterHorizontal( arma::mat &mat );

  /** Filter vertically */
  void filterVertical( arma::mat &mat );

  /** Set limits based on max and min value */
  void setMaxMinColors( arma::mat &mat );
};
}; // namespace
#endif


#include <Eigen/Geometry>
#include <aikido/statespace/SE2.hpp>

namespace aikido {
namespace statespace {
//==============================================================================
SE2::State::State() : mTransform(Isometry2d::Identity())
{
}

//==============================================================================
SE2::State::State(const Isometry2d& _transform) : mTransform(_transform)
{
}

//==============================================================================
auto SE2::State::getIsometry() const -> const Isometry2d&
{
  return mTransform;
}

//==============================================================================
void SE2::State::setIsometry(const Isometry2d& _transform)
{
  mTransform = _transform;
}

//==============================================================================
auto SE2::createState() const -> ScopedState
{
  return ScopedState(this);
}

//==============================================================================
SE2::ScopedState SE2::cloneState(const StateSpace::State* stateIn) const
{
  auto newState = createState();
  copyState(stateIn, newState);

  return newState;
}

//==============================================================================
auto SE2::getIsometry(const State* _state) const -> const Isometry2d&
{
  return _state->getIsometry();
}

//==============================================================================
void SE2::setIsometry(State* _state, const Isometry2d& _transform) const
{
  _state->setIsometry(_transform);
}

//==============================================================================
std::size_t SE2::getStateSizeInBytes() const
{
  return sizeof(State);
}

//==============================================================================
StateSpace::State* SE2::allocateStateInBuffer(void* _buffer) const
{
  return new (_buffer) State;
}

//==============================================================================
void SE2::freeStateInBuffer(StateSpace::State* _state) const
{
  static_cast<State*>(_state)->~State();
}

//==============================================================================
void SE2::compose(
    const StateSpace::State* _state1,
    const StateSpace::State* _state2,
    StateSpace::State* _out) const
{
  // TODO: Disable this in release mode.
  if (_state1 == _out || _state2 == _out)
    throw std::invalid_argument("Output aliases input.");

  auto state1 = static_cast<const State*>(_state1);
  auto state2 = static_cast<const State*>(_state2);
  auto out = static_cast<State*>(_out);

  out->mTransform = state1->mTransform * state2->mTransform;
}

//==============================================================================
std::size_t SE2::getDimension() const
{
  return 3;
}

//==============================================================================
void SE2::getIdentity(StateSpace::State* _out) const
{
  auto out = static_cast<State*>(_out);
  setIsometry(out, Isometry2d::Identity());
}

//==============================================================================
void SE2::getInverse(
    const StateSpace::State* _in, StateSpace::State* _out) const
{
  // TODO: Disable this in release mode.
  if (_out == _in)
    throw std::invalid_argument("Output aliases input.");

  auto in = static_cast<const State*>(_in);
  auto out = static_cast<State*>(_out);
  setIsometry(out, getIsometry(in).inverse());
}

//==============================================================================
void SE2::copyState(
    const StateSpace::State* _source, StateSpace::State* _destination) const
{
  auto source = static_cast<const State*>(_source);
  auto dest = static_cast<State*>(_destination);
  setIsometry(dest, getIsometry(source));
}

//==============================================================================
void SE2::expMap(const Eigen::VectorXd& _tangent, StateSpace::State* _out) const
{
  auto out = static_cast<State*>(_out);

  if (_tangent.rows() != 3)
  {
    std::stringstream msg;
    msg << "_tangent has incorrect size: expected 3"
        << ", got " << _tangent.rows() << ".\n";
    throw std::runtime_error(msg.str());
  }

  double angle = _tangent(2);
  Eigen::Vector2d translation = _tangent.head<2>();

  Isometry2d transform(Isometry2d::Identity());
  transform.linear() = Eigen::Rotation2Dd(angle).matrix();
  transform.translation() = translation;

  out->mTransform = transform;
}

//==============================================================================
void SE2::logMap(const StateSpace::State* _in, Eigen::VectorXd& _tangent) const
{
  if (_tangent.rows() != 3)
    _tangent.resize(3);

  auto in = static_cast<const State*>(_in);

  Isometry2d transform = getIsometry(in);
  _tangent.head<2>() = transform.translation();
  Eigen::Rotation2Dd rotation = Eigen::Rotation2Dd::Identity();
  rotation.fromRotationMatrix(transform.rotation());
  _tangent[2] = rotation.angle();
}

//==============================================================================
void SE2::print(const StateSpace::State* _state, std::ostream& _os) const
{
  auto state = static_cast<const State*>(_state);
  auto transform = getIsometry(state);

  Eigen::IOFormat cleanFmt(
      Eigen::StreamPrecision, Eigen::DontAlignCols, ",", ",", "", "", "[", "]");
  Eigen::Rotation2Dd rotation = Eigen::Rotation2Dd::Identity();
  rotation.fromRotationMatrix(transform.rotation());
  _os << Eigen::Vector3d(
             transform.translation()[0],
             transform.translation()[1],
             rotation.angle())
             .format(cleanFmt);
}
} // namespace statespace
} // namespace aikido


/**
 * @file fastpam1.cpp
 * @date 2021-08-03
 *
 * This file contains the primary C++ implementation of the FastPAM1 code follows
 * from the paper: Erich Schubert and Peter J. Rousseeuw: Faster k-Medoids Clustering:
 * Improving the PAM, CLARA, and CLARANS Algorithms. The paper can be assessed at
 * https://arxiv.org/pdf/1810.05691.pdf. Also the original PAM papers:
 * 1) Leonard Kaufman and Peter J. Rousseeuw: Clustering by means of medoids.
 * 2) Leonard Kaufman and Peter J. Rousseeuw: Partitioning around medoids (program pam).
 *
 */
#include "fastpam1.hpp"

#include <carma>
#include <armadillo>
#include <unordered_map>
#include <regex>

/**
 * \brief Runs FastPAM1 algorithm.
 *
 * Run the FastPAM1 algorithm to identify a dataset's medoids.
 *
 * @param input_data Input data to cluster
 */
void FastPAM1::fit_fastpam1(const arma::mat& input_data) {
  data = input_data;
  data = arma::trans(data);
  arma::rowvec medoid_indices(n_medoids);
  FastPAM1::build_fastpam1(data, medoid_indices);
  steps = 0;
  medoid_indices_build = medoid_indices;
  arma::rowvec assignments(data.n_cols);
  size_t iter = 0;
  bool medoidChange = true;
  while (iter < max_iter && medoidChange) {
    auto previous{medoid_indices};
    FastPAM1::swap_fastpam1(data, medoid_indices, assignments);
    medoidChange = arma::any(medoid_indices != previous);
    iter++;
  }
  medoid_indices_final = medoid_indices;
  labels = assignments;
  steps = iter;
}

/**
 * \brief Build step for the FastPAM1 algorithm
 *
 * Runs build step for the FastPAM1 algorithm. Loops over all datapoint and
 * checks its distance from every other datapoint in the dataset, then checks if
 * the total cost is less than that of the medoid (if a medoid exists yet).
 *
 * @param data Transposed input data to cluster
 * @param medoid_indices Uninitialized array of medoids that is modified in place
 * as medoids are identified
 */
void FastPAM1::build_fastpam1(
  const arma::mat& data,
  arma::rowvec& medoid_indices
) {
  size_t N = data.n_cols;
  int p = (buildConfidence * N); // reciprocal
  bool use_absolute = true;
  arma::rowvec estimates(N, arma::fill::zeros);
  arma::rowvec best_distances(N);
  best_distances.fill(std::numeric_limits<double>::infinity());
  arma::rowvec sigma(N); // standard deviation of induced losses on reference points
  for (size_t k = 0; k < n_medoids; k++) {
    double minDistance = std::numeric_limits<double>::infinity();
    int best = 0;
    sigma = km::KMedoids::build_sigma(
                          data, best_distances, batchSize, use_absolute);
    // fixes a base datapoint
    for (int i = 0; i < data.n_cols; i++) {
      double total = 0;
      for (size_t j = 0; j < data.n_cols; j++) {
        // computes distance between base and all other points
        double cost = (this->*lossFn)(data, i, j);
        // compares this with the cached best distance
        if (best_distances(j) < cost) {
          cost = best_distances(j);
        }
        total += cost;
      }
      if (total < minDistance) {
        minDistance = total;
        best = i;
      }
    }
    medoid_indices(k) = best;

    // update the medoid assignment and best_distance for this datapoint
    for (size_t l = 0; l < N; l++) {
        double cost = (this->*lossFn)(data, l, medoid_indices(k));
        if (cost < best_distances(l)) {
            best_distances(l) = cost;
        }
    }
    use_absolute = false; // use difference of loss for sigma and sampling,
                          // not absolute
    logHelper.loss_build.push_back(minDistance/N);
    logHelper.p_build.push_back(static_cast<float>(1)/static_cast<float>(p));
    logHelper.comp_exact_build.push_back(N);
  }
}

/**
 * \brief Swap step for the FastPAM1 algorithm
 *
 * Runs swap step for the FastPAM1 algorithm. Loops over all datapoint and
 * compute the loss change when a medoid is replaced by the datapoint. The
 * loss change is stored in an array of size n_medoids and the update is
 * based on an if conditional outside of the loop. The best medoid is chosen
 * according to the best loss change.
 *
 * @param data Transposed input data to cluster
 * @param medoid_indices Array of medoid indices created from the build step
 * that is modified in place as better medoids are identified
 * @param assignments Uninitialized array of indices corresponding to each
 * datapoint assigned the index of the medoid it is closest to
 */
void FastPAM1::swap_fastpam1(
  const arma::mat& data,
  arma::rowvec& medoid_indices,
  arma::rowvec& assignments
) {
  double bestChange = 0;
  double minDistance = std::numeric_limits<double>::infinity();
  size_t best = 0;
  size_t medoid_to_swap = 0;
  size_t N = data.n_cols;
  int p = (N * n_medoids * swapConfidence); // reciprocal
  arma::mat sigma(n_medoids, N, arma::fill::zeros);
  arma::rowvec best_distances(N);
  arma::rowvec second_distances(N);
  arma::rowvec delta_td(n_medoids, arma::fill::zeros);

  // calculate quantities needed for swap, best_distances and sigma
  km::KMedoids::calc_best_distances_swap(
    data, medoid_indices, best_distances, second_distances, assignments);

  sigma = km::KMedoids::swap_sigma(data,
                                   batchSize,
                                   best_distances,
                                   second_distances,
                                   assignments);

  // write the sigma distribution to logfile
  km::KMedoids::sigma_log(sigma);
  // for every point in our dataset, let it serve as a new medoid
  for (size_t i = 0; i < data.n_cols; i++) {
      double di = best_distances(i);
      // loss change for making i a medoid
      delta_td.fill(-di);
      for (size_t j = 0; j < data.n_cols; j++) {
          if (j != i) {
              double dij = (this->*lossFn)(data, i, j);
              // update loss change for the current
              if (dij < second_distances(j)) {
                  delta_td.at(assignments(j)) += (dij - best_distances(j));
              } else {
                  delta_td.at(assignments(j)) += (second_distances(j) - best_distances(j));
              }
              // reassignment check
              if (dij < best_distances(j)) {
                  // update loss change for others
                  delta_td += (dij -  best_distances(j));
                  // remove the update for the current
                  delta_td.at(assignments(j)) -= (dij -  best_distances(j));
              }
          }
      }
      // choose the best medoid-to-swap
      arma::uword min_medoid = delta_td.index_min();
      // if the loss change is better than the best loss change
      // update the best index identified so far
      if (delta_td.min() < bestChange) {
          bestChange = delta_td.min();
          best = i;
          medoid_to_swap = min_medoid;
      }
  }
  // update the loss and medoid if the loss is improved
  if (bestChange < 0) {
      minDistance = arma::sum(best_distances) + bestChange;
      medoid_indices(medoid_to_swap) = best;
  } else {
      minDistance = arma::sum(best_distances);
  }
  logHelper.loss_swap.push_back(minDistance/N);
  logHelper.p_swap.push_back(static_cast<float>(1)/static_cast<float>(p));
  logHelper.comp_exact_swap.push_back(N*n_medoids);
}


#include <string>
#include <boost/asio.hpp>
#include <udho/router.h>
#include <udho/logging.h>
#include <udho/server.h>
#include <udho/context.h>
#include <iostream>
#include <cmath>

namespace data{

struct planet: udho::prepare<planet>{
    std::string name;
    long double radius;
    long double mass;

    double escape_velocity() const{
        const static long double G = 6.67L * std::pow(10L, -11L);
        return std::sqrt((2 * G * mass) / radius);
    }

    template <typename DictT>
    auto dict(DictT assoc) const{
        return assoc | var("planet",  &planet::name)
                     | var("radius",  &planet::radius)
                     | fn ("escape",  &planet::escape_velocity);
    }
};

struct person: udho::prepare<person>{
    std::string name;

    template <typename DictT>
    auto dict(DictT assoc) const{
        return assoc | var("name",  &person::name);
    }
};

struct ship: udho::prepare<ship>{
    std::string name;

    template <typename DictT>
    auto dict(DictT assoc) const{
        return assoc | var("ship",  &ship::name);
    }
};

}

std::string world(udho::contexts::stateless ctx){
    return "{'planet': 'Earth'}";
}
std::string planet(udho::contexts::stateless ctx, std::string name){
    data::planet planet;
    planet.name = name;

    data::person person;
    person.name = "Neel Bose";

    data::ship ship;
    ship.name = "Alpha Traveller";

    return ctx.render("planet.html", planet, person, ship);
}

int main(){
    boost::asio::io_service io;
    udho::servers::ostreamed::stateless server(io, std::cout);
    server[udho::configs::server::template_root] = TMPL_PATH;
    server[udho::configs::server::document_root] = WWW_PATH;

    auto urls = udho::router() | "/world"          >> udho::get(&world).json()
                               | "/planet/(\\w+)"  >> udho::get(&planet).html();

    server.serve(urls, 9198);

    io.run();
    return 0;
}