/*
 *  This file is a part of TiledArray.
 *  Copyright (C) 2014  Virginia Tech
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Justus Calvin
 *  Department of Chemistry, Virginia Tech
 *
 *  math_outer.cpp
 *  Apr 9, 2014
 *
 */

#include "TiledArray/math/outer.h"
#include "unit_test_config.h"

struct OuterFixture {

  OuterFixture() :
    left(TILEDARRAY_LOOP_UNWIND * 4.5, 0),
    right(TILEDARRAY_LOOP_UNWIND * 2.5, 0),
    result(left.size() * right.size(), 1)
  {
    rand_fill(left, 23);
    rand_fill(right, 42);
    rand_fill(result, 79);
  }

  ~OuterFixture() { }

  static void rand_fill(std::vector<int>& vec, const int seed) {
    GlobalFixture::world->srand(seed);
    for(std::size_t i = 0ul; i < vec.size(); ++i)
      vec[i] = GlobalFixture::world->rand() % 101;
  }

  template <std::size_t N>
  static void rand_fill(int (&vec)[N], const int seed) {
    GlobalFixture::world->srand(seed);
    for(std::size_t i = 0ul; i < N; ++i)
      vec[i] = GlobalFixture::world->rand() % 101;
  }

  static void add_subt(int& result, const int left, const int right) {
    result += left - right;
  }

  static int subt(const int left, const int right) {
    return left - right;
  }

  std::vector<int> left;
  std::vector<int> right;
  std::vector<int> result;
}; // OuterFixture

BOOST_FIXTURE_TEST_SUITE( math_outer_suite, OuterFixture )

BOOST_AUTO_TEST_CASE( outer_kernel )
{
  const std::vector<int> reference = result;

  // Perform outer with the block kernel.
  TiledArray::math::OuterVectorOpUnwindN::outer( & left.front(), & right.front(),
      & result.front(), right.size(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < TILEDARRAY_LOOP_UNWIND; ++i) {
    for(std::size_t j = 0ul; j < TILEDARRAY_LOOP_UNWIND; ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer )
{
  std::vector<int> reference = result;

  // Perform outer
  TiledArray::math::outer(left.size(), right.size(), & left.front(), & right.front(),
      & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_small_left )
{
  std::vector<int> reference = result;
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer(m, right.size(), & left.front(), & right.front(),
      & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_small_right )
{
  std::vector<int> reference = result;
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.75;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer(left.size(), n, & left.front(), & right.front(),
      & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], reference[i * n + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_small_left_right )
{
  std::vector<int> reference = result;
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.75;
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND.
  TiledArray::math::outer(m, n, & left.front(), & right.front(),
      & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], reference[i * n + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_fill_kernel )
{
  // Perform outer with the block kernel.
  TiledArray::math::OuterVectorOpUnwindN::fill( & left.front(), & right.front(),
      & result.front(), right.size(), &OuterFixture::subt);

  // Check the result
  for(std::size_t i = 0ul; i < TILEDARRAY_LOOP_UNWIND; ++i) {
    for(std::size_t j = 0ul; j < TILEDARRAY_LOOP_UNWIND; ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_fill )
{
  // Perform outer
  TiledArray::math::outer_fill(left.size(), right.size(), & left.front(), & right.front(),
      & result.front(), &OuterFixture::subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_fill_small_left )
{
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer_fill(m, right.size(), & left.front(), & right.front(),
      & result.front(), &OuterFixture::subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_fill_small_right )
{
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.75;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer_fill(left.size(), n, & left.front(), & right.front(),
      & result.front(), &OuterFixture::subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_fill_small_left_right )
{
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.75;
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND.
  TiledArray::math::outer_fill(m, n, & left.front(), & right.front(),
      & result.front(), &OuterFixture::subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], left[i] - right[j]);
    }
  }

}


BOOST_AUTO_TEST_CASE( outer_and_fill_kernel )
{
  const std::vector<int> reference = result;
  std::fill(result.begin(), result.end(), 0);

  // Perform outer with the block kernel.
  TiledArray::math::OuterVectorOpUnwindN::fill( & left.front(), & right.front(),
      & reference.front(), & result.front(), right.size(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < TILEDARRAY_LOOP_UNWIND; ++i) {
    for(std::size_t j = 0ul; j < TILEDARRAY_LOOP_UNWIND; ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_and_fill )
{
  std::vector<int> reference = result;
  std::fill(result.begin(), result.end(), 0);

  // Perform outer
  TiledArray::math::outer_fill(left.size(), right.size(), & left.front(), & right.front(),
      & reference.front(), & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_and_fill_small_left )
{
  std::vector<int> reference = result;
  std::fill(result.begin(), result.end(), 0);
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer_fill(m, right.size(), & left.front(), & right.front(),
      & reference.front(), & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < right.size(); ++j) {
      BOOST_CHECK_EQUAL(result[i * right.size() + j], reference[i * right.size() + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_and_fill_small_right )
{
  std::vector<int> reference = result;
  std::fill(result.begin(), result.end(), 0);
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.75;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND for right.
  TiledArray::math::outer_fill(left.size(), n, & left.front(), & right.front(),
      & reference.front(), & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < left.size(); ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], reference[i * n + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_CASE( outer_and_fill_small_left_right )
{
  std::vector<int> reference = result;
  std::fill(result.begin(), result.end(), 0);
  const std::size_t m = TILEDARRAY_LOOP_UNWIND * 0.75;
  const std::size_t n = TILEDARRAY_LOOP_UNWIND * 0.5;

  // Perform outer with sizes that are smaller than TILEDARRAY_LOOP_UNWIND.
  TiledArray::math::outer_fill(m, n, & left.front(), & right.front(),
      & reference.front(), & result.front(), &OuterFixture::add_subt);

  // Check the result
  for(std::size_t i = 0ul; i < m; ++i) {
    for(std::size_t j = 0ul; j < n; ++j) {
      BOOST_CHECK_EQUAL(result[i * n + j], reference[i * n + j] + left[i] - right[j]);
    }
  }

}

BOOST_AUTO_TEST_SUITE_END()
