// Copyright (c) 2020 CNES
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include <Eigen/Core>
#include "pyinterp/detail/gsl/interpolate1d.hpp"
#include "pyinterp/detail/math.hpp"

namespace pyinterp::detail::math {

/// Set of coordinates used for interpolation
class CoordsXY {
 public:
  /// Default constructor
  CoordsXY() = delete;

  /// Creates a new instance
  CoordsXY(const size_t x_size, const size_t y_size)
      : x_(new Eigen::VectorXd), y_(new Eigen::VectorXd) {
    auto nx = x_size << 1U;
    auto ny = y_size << 1U;
    x_->resize(nx);
    y_->resize(ny);
  }

  /// Creates a new instance from existing coordinates
  CoordsXY(std::shared_ptr<Eigen::VectorXd> x,
           std::shared_ptr<Eigen::VectorXd> y)
      : x_(std::move(x)), y_(std::move(y)) {}

  /// Default destructor
  virtual ~CoordsXY() = default;

  /// Copy constructor
  ///
  /// @param rhs right value
  CoordsXY(const CoordsXY &rhs) = default;

  /// Move constructor
  ///
  /// @param rhs right value
  CoordsXY(CoordsXY &&rhs) noexcept = default;

  /// Copy assignment operator
  ///
  /// @param rhs right value
  auto operator=(const CoordsXY &rhs) -> CoordsXY & = default;

  /// Move assignment operator
  ///
  /// @param rhs right value
  auto operator=(CoordsXY &&rhs) noexcept -> CoordsXY & = default;

  /// Get the half size of the window in abscissa.
  [[nodiscard]] inline auto nx() const noexcept -> size_t {
    return static_cast<size_t>(x_->size()) >> 1U;
  }

  /// Get the half size of the window in ordinate.
  [[nodiscard]] inline auto ny() const noexcept -> size_t {
    return static_cast<size_t>(y_->size()) >> 1U;
  }

  /// Get x-coordinates
  inline auto x() noexcept -> std::shared_ptr<Eigen::VectorXd> & { return x_; }

  /// Get x-coordinates
  [[nodiscard]] inline auto x() const noexcept
      -> const std::shared_ptr<Eigen::VectorXd> & {
    return x_;
  }

  /// Get y-coordinates
  inline auto y() noexcept -> std::shared_ptr<Eigen::VectorXd> & { return y_; }

  /// Get y-coordinates
  [[nodiscard]] inline auto y() const noexcept
      -> const std::shared_ptr<Eigen::VectorXd> & {
    return y_;
  }

  /// Get the ith x-axis.
  [[nodiscard]] inline auto x(const size_t ix) const -> double {
    return (*x_)(ix);
  }

  /// Get the ith y-axis.
  [[nodiscard]] inline auto y(const size_t jx) const -> double {
    return (*y_)(jx);
  }

  /// Set the ith x-axis.
  inline auto x(const size_t ix) -> double & { return (*x_)(ix); }

  /// Get the ith y-axis.
  inline auto y(const size_t jx) -> double & { return (*y_)(jx); }

  /// Normalizes the angle with respect to the first value of the X axis of this
  /// array.
  [[nodiscard]] inline auto normalize_angle(const double xi) const -> double {
    return math::normalize_angle(xi, (*x_)(0), 360.0);
  }

 private:
  std::shared_ptr<Eigen::VectorXd> x_{};
  std::shared_ptr<Eigen::VectorXd> y_{};
};

/// Set of coordinates/values used for interpolation
///  * q11 = (x1, y1)
///  * q12 = (x1, y2)
///  * .../...
///  * q1n = (x1, yn)
///  * q21 = (x2, y1)
///  * q22 = (x2, y2).
///  * .../...
///  * q2n = (x2, yn)
///  * .../...
///  * qnn = (xn, yn)
///
/// @code
/// Array2D({{x1, x2, ..., xn}, {y1, y2, ..., yn}},
///         {q11, q12, ..., q21, q22, ...., qnn})
/// @endcode
class XArray2D : public CoordsXY {
 public:
  /// Default constructor
  XArray2D() = delete;

  /// Creates a new Array
  XArray2D(const size_t x_size, const size_t y_size)
      : CoordsXY(x_size, y_size), q_(new Eigen::MatrixXd) {
    q_->resize(x()->size(), y()->size());
  }

  /// Creates a new Array from existing coordinates/values
  XArray2D(std::shared_ptr<Eigen::VectorXd> x,
           std::shared_ptr<Eigen::VectorXd> y,
           std::shared_ptr<Eigen::MatrixXd> q)
      : CoordsXY(std::move(x), std::move(y)), q_(std::move(q)) {}

  /// Default destructor
  ~XArray2D() override = default;

  /// Copy constructor
  ///
  /// @param rhs right value
  XArray2D(const XArray2D &rhs) = default;

  /// Move constructor
  ///
  /// @param rhs right value
  XArray2D(XArray2D &&rhs) noexcept = default;

  /// Copy assignment operator
  ///
  /// @param rhs right value
  auto operator=(const XArray2D &rhs) -> XArray2D & = default;

  /// Move assignment operator
  ///
  /// @param rhs right value
  auto operator=(XArray2D &&rhs) noexcept -> XArray2D & = default;

  /// Get the values from the array for all x and y coordinates.
  inline auto q() noexcept -> std::shared_ptr<Eigen::MatrixXd> & { return q_; }

  /// Get the values from the array for all x and y coordinates.
  [[nodiscard]] inline auto q() const noexcept
      -> const std::shared_ptr<Eigen::MatrixXd> & {
    return q_;
  }

  /// Get the value at coordinate (ix, jx).
  [[nodiscard]] inline auto q(const size_t ix, const size_t jx) const
      -> double {
    return (*q_)(ix, jx);
  }

  /// Get the value at coordinate (ix, jx).
  inline auto q(const size_t ix, const size_t jx) -> double & {
    return (*q_)(ix, jx);
  }

  /// Returns true if this instance does not contains at least one Not A Number
  /// (NaN).
  [[nodiscard]] inline auto is_valid() const -> bool { return !q_->hasNaN(); }

 private:
  std::shared_ptr<Eigen::MatrixXd> q_{};
};

/// Set of coordinates/values used for 3D-interpolation
///
/// @tparam Z-Axis type
template <typename T>
class XArray3D : public CoordsXY {
 public:
  /// Default constructor
  XArray3D() = delete;

  /// Creates a new instance
  XArray3D(const size_t x_size, const size_t y_size, const size_t z_size)
      : CoordsXY(x_size, y_size), z_(), q_() {
    auto nz = z_size << 1U;
    z_.resize(nz);
    q_.resize(nz);

    for (auto iz = 0U; iz < nz; ++iz) {
      q_(iz) = std::shared_ptr<Eigen::MatrixXd>(
          new Eigen::MatrixXd(x()->size(), y()->size()));
    }
  }

  /// Get the set of coordinates/values for the ith z-layer
  [[nodiscard]] auto xarray_2d(const Eigen::Index iz) const -> XArray2D {
    return XArray2D(x(), y(), q_(iz));
  }

  /// Default destructor
  ~XArray3D() override = default;

  /// Copy constructor
  ///
  /// @param rhs right value
  XArray3D(const XArray3D &rhs) = default;

  /// Move constructor
  ///
  /// @param rhs right value
  XArray3D(XArray3D &&rhs) noexcept = default;

  /// Copy assignment operator
  ///
  /// @param rhs right value
  auto operator=(const XArray3D &rhs) -> XArray3D & = default;

  /// Move assignment operator
  ///
  /// @param rhs right value
  auto operator=(XArray3D &&rhs) noexcept -> XArray3D & = default;

  /// Get the half size of the window in z.
  [[nodiscard]] inline auto nz() const noexcept -> size_t {
    return static_cast<size_t>(z_.size()) >> 1U;
  }

  /// Get z-coordinates
  inline auto z() noexcept -> Eigen::Matrix<T, Eigen::Dynamic, 1> & {
    return z_;
  }

  /// Get z-coordinates
  [[nodiscard]] inline auto z() const noexcept
      -> const Eigen::Matrix<T, Eigen::Dynamic, 1> & {
    return z_;
  }

  /// Get the ith z-axis.
  [[nodiscard]] inline auto z(const size_t ix) const -> T { return z_(ix); }

  /// Set the ith z-axis.
  inline auto z(const size_t ix) -> T & { return z_(ix); }

  /// Get the value at coordinate (ix, jx, kx).
  inline auto q(const size_t ix, const size_t jx, const size_t kx) -> double & {
    return (*q_(kx))(ix, jx);
  }

  /// Returns true if this instance does not contains at least one Not A Number
  /// (NaN).
  [[nodiscard]] inline auto is_valid() const -> bool {
    for (Eigen::Index kx = 0; kx < q_.size(); ++kx) {
      if ((*q_(kx)).hasNaN()) {
        return false;
      }
    }
    return true;
  }

 private:
  Eigen::Matrix<T, Eigen::Dynamic, 1> z_;
  Eigen::Matrix<std::shared_ptr<Eigen::MatrixXd>, Eigen::Dynamic, 1> q_;
};

/// Set of coordinates/values used for 4D-interpolation
///
/// @tparam Z-Axis type
template <typename T>
class XArray4D : public CoordsXY {
 public:
  /// Default constructor
  XArray4D() = delete;

  /// Creates a new instance
  XArray4D(const size_t x_size, const size_t y_size, const size_t z_size,
           const size_t u_size)
      : CoordsXY(x_size, y_size), z_(), u_(), q_() {
    auto nz = z_size << 1U;
    auto nu = u_size << 1U;
    z_.resize(nz);
    u_.resize(nu);
    q_.resize(nz, nu);

    for (auto iz = 0U; iz < nz; ++iz) {
      for (auto iu = 0U; iu < nu; ++iu) {
        q_(iz, iu) = std::shared_ptr<Eigen::MatrixXd>(
            new Eigen::MatrixXd(x()->size(), y()->size()));
      }
    }
  }

  /// Get the set of coordinates/values for the ith z-layer
  [[nodiscard]] auto xarray_2d(const Eigen::Index iz, const Eigen::Index iu) const
      -> XArray2D {
    return XArray2D(x(), y(), q_(iz, iu));
  }

  /// Default destructor
  ~XArray4D() override = default;

  /// Copy constructor
  ///
  /// @param rhs right value
  XArray4D(const XArray4D &rhs) = default;

  /// Move constructor
  ///
  /// @param rhs right value
  XArray4D(XArray4D &&rhs) noexcept = default;

  /// Copy assignment operator
  ///
  /// @param rhs right value
  auto operator=(const XArray4D &rhs) -> XArray4D & = default;

  /// Move assignment operator
  ///
  /// @param rhs right value
  auto operator=(XArray4D &&rhs) noexcept -> XArray4D & = default;

  /// Get the half size of the window in z.
  [[nodiscard]] inline auto nz() const noexcept -> size_t {
    return static_cast<size_t>(z_.size()) >> 1U;
  }

  /// Get the half size of the window in u.
  [[nodiscard]] inline auto nu() const noexcept -> size_t {
    return static_cast<size_t>(u_.size()) >> 1U;
  }

  /// Get z-coordinates
  inline auto z() noexcept -> Eigen::Matrix<T, Eigen::Dynamic, 1> & {
    return z_;
  }

  /// Get u-coordinates
  inline auto u() noexcept -> Eigen::VectorXd & { return u_; }

  /// Get z-coordinates
  [[nodiscard]] inline auto z() const noexcept
      -> const Eigen::Matrix<T, Eigen::Dynamic, 1> & {
    return z_;
  }

  /// Get u-coordinates
  [[nodiscard]] inline auto u() const noexcept -> const Eigen::VectorXd & {
    return u_;
  }

  /// Get the ith z-axis.
  [[nodiscard]] inline auto z(const size_t ix) const -> T { return z_(ix); }

  /// Get the ith u-axis.
  [[nodiscard]] inline auto u(const size_t ix) const -> double {
    return u_(ix);
  }

  /// Set the ith z-axis.
  inline auto z(const size_t ix) -> T & { return z_(ix); }

  /// Set the ith u-axis.
  inline auto u(const size_t ix) -> double & { return u_(ix); }

  /// Get the value at coordinate (ix, jx, kx).
  inline auto q(const size_t ix, const size_t jx, const size_t kx,
                const size_t lx) -> double & {
    return (*q_(kx, lx))(ix, jx);
  }

  /// Returns true if this instance does not contains at least one Not A Number
  /// (NaN).
  [[nodiscard]] inline auto is_valid() const -> bool {
    for (Eigen::Index kx = 0; kx < z_.size(); ++kx) {
      for (Eigen::Index lx = 0; lx < u_.size(); ++lx) {
        if ((*q_(kx, lx)).hasNaN()) {
          return false;
        }
      }
    }
    return true;
  }

 private:
  Eigen::Matrix<T, Eigen::Dynamic, 1> z_;
  Eigen::VectorXd u_;
  Eigen::Matrix<std::shared_ptr<Eigen::MatrixXd>, Eigen::Dynamic,
                Eigen::Dynamic>
      q_;
};

/// Extension of cubic interpolation for interpolating data points on a
/// two-dimensional regular grid. The interpolated surface is smoother than
/// corresponding surfaces obtained by bilinear interpolation or
/// nearest-neighbor interpolation.
class Bicubic {
 public:
  /// Default constructor
  ///
  /// @param xr Calculation window.
  /// @param type method of calculation
  explicit Bicubic(const XArray2D &xr, const gsl_interp_type *type)
      : column_(xr.x()->size()),
        interpolator_(std::max(xr.x()->size(), xr.y()->size()), type,
                      gsl::Accelerator()) {}

  /// Return the interpolated value of y for a given point x
  auto interpolate(const double x, const double y, const XArray2D &xr)
      -> double {
    return evaluate(&gsl::Interpolate1D::interpolate, x, y, xr);
  }

  /// Return the derivative for a given point x
  auto derivative(const double x, const double y, const XArray2D &xr)
      -> double {
    return evaluate(&gsl::Interpolate1D::derivative, x, y, xr);
  }

  /// Return the second derivative for a given point x
  auto second_derivative(const double x, const double y, const XArray2D &xr)
      -> double {
    return evaluate(&gsl::Interpolate1D::second_derivative, x, y, xr);
  }

 private:
  using InterpolateFunction = double (gsl::Interpolate1D::*)(
      const Eigen::VectorXd &, const Eigen::VectorXd &, const double);
  /// Column of the interpolation window (interpolation according to Y
  /// coordinates)
  Eigen::VectorXd column_;
  /// GSL interpolator
  gsl::Interpolate1D interpolator_;

  /// Evaluation of the GSL function performing the calculation.
  auto evaluate(
      const std::function<double(gsl::Interpolate1D &, const Eigen::VectorXd &,
                                 const Eigen::VectorXd &, const double)>
          &function,
      const double x, const double y, const XArray2D &xr) -> double {
    // Spline interpolation as function of Y-coordinate
    for (Eigen::Index ix = 0; ix < xr.x()->size(); ++ix) {
      column_(ix) = function(interpolator_, *(xr.y()), xr.q()->row(ix), y);
    }
    return function(interpolator_, *(xr.x()), column_, x);
  }
};

}  // namespace pyinterp::detail::math


/*
 Copyright (c) 2013, Olumuyiwa Oluwasanmi
 All rights reserved.

 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

 3. Neither the name of the Olumuyiwa Oluwasanmi nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE ParLibTest
#include <algorithm>
#include <algorithm>
#include <numeric>
#include <iostream>
#include <functional>
#include <random>
#include <chrono>
#include <exception>

#include "ParLib.h"
//#include <boost/test/minimal.hpp>
#include <boost/test/included/unit_test.hpp>
//#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(Transform)
	BOOST_AUTO_TEST_CASE( transform_test ) {

		std::vector<int> one(100000);
		std::vector<double> two(100000);
		std::iota(one.begin(), one.end(), 1);
		parallel::transform(one.begin(), one.end(), two.begin(),
				[&](int k)->double {return 259*static_cast<double>(k);});
		BOOST_CHECK(two[20] == 21.0 * 259.0);
	}
	BOOST_AUTO_TEST_CASE( transform_test2 ) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		iota(one.begin(), one.end(), 1);
		iota(two.begin(), two.end(), 0);

		function<int(int, int)> binop = [&](int k, int x)->int {return 259*k* x;};
		parallel::transform(one.begin(), one.end(), two.begin(), three.begin(),
				[&](int k, int x)->int {return 259*k* x;});
		BOOST_CHECK(three[20] == 20 * 21 * 259);
	}

	BOOST_AUTO_TEST_CASE( fill_test ) {
		int val = 20;
		std::vector<int> one(100000);
		std::vector<double> two(100000);
		std::fill(one.begin(), one.begin() + 300, val);
		parallel::fill(two.begin(), two.begin() + 300, val);

		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()));
	}

	BOOST_AUTO_TEST_CASE( fill_n_test1 ) {
		int val = 20;
		int val2 = 99;
		std::vector<int> one(100);
		std::vector<int> two(100);
		auto it1 = std::fill_n(one.begin(), val2, val);
		auto it2 = parallel::fill_n(two.begin(), val2, val);
		it1++;
		it2++;
		//for(auto &i:two)std::cout<<i<<std::endl;
		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()));
	}
	BOOST_AUTO_TEST_CASE( generate_test1 ) {

		std::vector<int> one(100000);
		std::vector<double> two(100000);
		std::iota(one.begin(), one.end(), 500);
		std::iota(two.begin(), two.end(), 500);
		std::srand(0);
		std::function<int(void)> f = [&](void)->int {return 9;};
		std::generate(one.begin(), one.end(), f);
		parallel::generate(two.begin(), two.end(), f);

		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()));
	}

	BOOST_AUTO_TEST_CASE( generate_n_test1 ) {
		std::size_t val = 500;
		std::vector<int> one(100000);
		std::vector<int> two(100000);
		std::function<int(void)> f = [&](void)->int {return 9;};
		auto it1 = std::generate_n(one.begin(), val, f);

		auto it2 = parallel::generate_n(two.begin(), val, f);
		//it1++;
		//it2++;
		//for(auto &i:two)std::cout<<i<<std::endl;
		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()) && *it1 == *it2);
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(ForEach)
	BOOST_AUTO_TEST_CASE( foreach_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 1);
		iota(two.begin(), two.end(), 1);
		//function<void(int&)> op = [&](int &k)->void{ one[k-1]= k*30; };
		std::for_each(one.begin(), one.end(), [&](int &k)->void {one[k-1]= k*30;});
		parallel::for_each(two.begin(), two.end(), [&](int &k)->void {two[k-1]= k*30;});
		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()));

	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Accumulate)
	BOOST_AUTO_TEST_CASE( accumulate_test1) {
		using namespace std;
		long int val;
		vector<long int> one(100000);
		iota(one.begin(), one.end(), 1);
		//function<void(int&)> op = [&](int &k)->void{ one[k-1]= k*30; };
		long int val1 = std::accumulate(one.begin(), one.end(), 0);
		val = parallel::accumulate(one.begin(), one.end(), 0);
		BOOST_CHECK(val == val1);
	}

	BOOST_AUTO_TEST_CASE( accumulate_test2) {
		using namespace std;
		long int val;
		vector<long int> one(100000);
		iota(one.begin(), one.end(), 1);
		//function<void(int&)> op = [&](int &k)->void{ one[k-1]= k*30; };
		long int val1 = std::accumulate(one.begin(), one.end(), 0);
		val = parallel::accumulate(one.begin(), one.end(), 0,
				[](long int k, long int j)->long int {return k+ j;});
		BOOST_CHECK(val == val1);
	}
	BOOST_AUTO_TEST_SUITE_END()BOOST_AUTO_TEST_SUITE
(Accumulate_If)
	BOOST_AUTO_TEST_CASE( accummulate_if_test1) {
		using namespace std;
		long int val;
		vector<long int> one(100000);
		iota(one.begin(), one.end(), 1l);
		//function<void(int&)> op = [&](int &k)->void{ one[k-1]= k*30; };
		long int val1 = 0l;
		for(long int v : one)
			if((v % 10l) == 0l)
				val1 += v;

		val = parallel::accumulate_if(one.begin(), one.end(), 0l,
				[](long int k)->bool {return (k%10l)==0l;});

		BOOST_CHECK(val == val1);
	}

	BOOST_AUTO_TEST_CASE( accummulate_if_test2) {
		using namespace std;
		long int val;
		vector<long int> one(100000);
		iota(one.begin(), one.end(), 1l);
		function<long int(long int, long int)> op =
				[&](long int i, long int j)->long int {return i+j;};
		long int val1 = 0;
		for(long int v : one)
			if((v % 10l) == 0l)
				val1 += v;

		val = parallel::accumulate_if(one.begin(), one.end(), 0l,
				[](long int k)->bool {return (k%10l)==0l;}, op);

		BOOST_CHECK(val == val1);
	}
	BOOST_AUTO_TEST_SUITE_END()
//begin inner product test
BOOST_AUTO_TEST_SUITE(Inner_Product)
	BOOST_AUTO_TEST_CASE( inner_product_test1) {
		using namespace std;
		int val;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 1);
		iota(two.begin(), two.end(), 1);
		int v = std::inner_product(one.begin(), one.end(), two.begin(), 0);

		val = parallel::inner_product(one.begin(), one.end(), two.begin(), 0);
		BOOST_CHECK(val == v);
	}

	BOOST_AUTO_TEST_CASE( inner_product_test2) {
		using namespace std;
		long int val;
		vector<long int> one(100000);
		vector<long int> two(100000);
		iota(one.begin(), one.end(), 1);
		iota(two.begin(), two.end(), 1);
		function<long int(long int, long int)> op1 =
				[&](long int i, long int j)->long int {return i+j;};
		function<long int(long int, long int)> op2 =
				[&](long int i, long int j)->long int {return i*j;};

		long int v = std::inner_product(one.begin(), one.end(), two.begin(), 0, op1, op2);

		val = parallel::inner_product(one.begin(), one.end(), two.begin(), 0, op1, op2);

		BOOST_CHECK(val == v);
	}
	BOOST_AUTO_TEST_SUITE_END()BOOST_AUTO_TEST_SUITE
(Adjacent_Difference)
	BOOST_AUTO_TEST_CASE( adjacent_difference_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		iota(one.begin(), one.end(), 1);
		int j=0;
		for(auto &i : one) {
			++j;
			i += j;
		}
		std::adjacent_difference(one.begin(), one.end(), two.begin());

		parallel::adjacent_difference(one.begin(), one.end(), three.begin());
		//for(auto k: three)cout<<k<<std::endl;
		BOOST_CHECK(std::equal(two.begin(), two.end(), three.begin()));
	}
	BOOST_AUTO_TEST_CASE( adjacent_difference_test2) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		iota(one.begin(), one.end(), 1);
		int j=0;
		//function<int(int,int)> op = [&](int &a, int &b)->int{ return b-a;} ;
		for(auto &i : one) {
			++j;
			i = i + j;
		}
		std::adjacent_difference(one.begin(), one.end(), two.begin(),
				[](int a, int b)->int {return a-b;});

		parallel::adjacent_difference(one.begin(), one.end(), three.begin(),
				[](int a, int b)->int {return a-b;});
		//for(auto k: three)cout<<k<<std::endl;
		BOOST_CHECK(std::equal(two.begin(), two.end(), three.begin()));
	}

	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Partial_Sum)
	BOOST_AUTO_TEST_CASE( partial_sum_test1) {
		using namespace std;
		vector<int> one(100);
		vector<int> two(100);
		vector<int> three(100);
		iota(one.begin(), one.end(), 1);

		std::partial_sum(one.begin(), one.end(), two.begin());

		parallel::partial_sum(one.begin(), one.end(), three.begin());
		//for(auto k: three)cout<<k<<std::endl;
		BOOST_CHECK(std::equal(two.begin(), two.end(), three.begin()));
	}
	BOOST_AUTO_TEST_CASE( partial_sum_test2) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		iota(one.begin(), one.end(), 1);

		std::partial_sum(one.begin(), one.end(), two.begin(), [](int a, int b)->int {return a +b;});

		parallel::partial_sum(one.begin(), one.end(), three.begin(),
				[](int a, int b)->int {return a +b;});
		//for(auto k: three)cout<<k<<std::endl;
		BOOST_CHECK(std::equal(two.begin(), two.end(), three.begin()));
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Find)
	BOOST_AUTO_TEST_CASE( find_test1) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);

		auto val2 = parallel::find(one.begin(), one.end(), val);
		BOOST_CHECK(val == *val2);
	}
	BOOST_AUTO_TEST_CASE( find_if_test) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		function<bool(int)> op = [](int i)->bool {return i==177;};
		auto val2 = parallel::find_if(one.begin(), one.end(), op);
		BOOST_CHECK(val == *val2);

	}
	BOOST_AUTO_TEST_CASE( adjacent_find_test1) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		one[177] = 177;
		auto val2 = parallel::adjacent_find(one.begin(), one.end());
		BOOST_CHECK(val == *val2);
	}
	BOOST_AUTO_TEST_CASE( adjacent_find_test2) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		one[177] = 177;
		function<bool(int, int)> op = [](int i, int j)->bool {return i==j;};
		auto val2 = parallel::adjacent_find(one.begin(), one.end(), op);
		BOOST_CHECK(val == *val2);

	}
	BOOST_AUTO_TEST_CASE( find_if_not_test) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		function<bool(int&)> op = [](int& i)->bool {return i!=177;};
		auto val2 = parallel::find_if_not(one.begin(), one.end(), op);
		BOOST_CHECK(val == *val2);
	}
	BOOST_AUTO_TEST_CASE( search_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(20);
		iota(two.begin(), two.end(), 456);
		auto val1 = std::search(one.begin(), one.end(), two.begin(), two.end());
		auto val2 = parallel::search(one.begin(), one.end(), two.begin(), two.end());
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;
		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( search_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(20);
		iota(two.begin(), two.end(), 456);
		std::function<bool(int, int)> op = [&](int v1, int v2) {return v1==v2;};
		auto val1 = std::search(one.begin(), one.end(), two.begin(), two.end(), op);
		auto val2 = parallel::search(one.begin(), one.end(), two.begin(), two.end(), op);
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;
		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( search_n_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		int val = 173;
		int vl = 20;
		fill(one.begin() + 456, one.begin() + 456 + 21, 173);
		//std::function<bool(int,int)>  op =[&](int v1, int v2){return v1==v2 ;};
		auto val1 = std::search_n(one.begin(), one.end(), vl, val);
		auto val2 = parallel::search_n(one.begin(), one.end(), vl, val);

		BOOST_CHECK(val1 == val2);
	}

	BOOST_AUTO_TEST_CASE( search_n_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		int val = 173;
		int vl = 20;
		fill(one.begin() + 456, one.begin() + 456 + 21, 173);
		std::function<bool(int, int)> op = [&](int v1, int v2) {return v1==v2;};
		auto val1 = std::search_n(one.begin(), one.end(), vl, val, op);
		auto val2 = parallel::search_n(one.begin(), one.end(), vl, val, op);

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( find_end_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(20);
		iota(two.begin(), two.end(), 456);
		auto val1 = std::find_end(one.begin(), one.end(), two.begin(), two.end());
		auto val2 = parallel::find_end(one.begin(), one.end(), two.begin(), two.end());
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( find_end_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(20);
		iota(two.begin(), two.end(), 456);
		std::function<bool(int, int)> op = [&](int v1, int v2) {return v1==v2;};
		auto val1 = std::find_end(one.begin(), one.end(), two.begin(), two.end(), op);
		auto val2 = parallel::find_end(one.begin(), one.end(), two.begin(), two.end(), op);
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( mismatch_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(100000);
		iota(two.begin(), two.end(), 1);
		two[877] = 987;
		auto val1 = std::mismatch(one.begin(), one.end(), two.begin());
		auto val2 = parallel::mismatch(one.begin(), one.end(), two.begin());
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE( mismatch_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		vector<int> two(100000);
		iota(two.begin(), two.end(), 1);
		std::function<bool(int, int)> op = [&](int v1, int v2) {return v1==v2;};
		two[877] = 987;
		auto val1 = std::mismatch(one.begin(), one.end(), two.begin(), op);
		auto val2 = parallel::mismatch(one.begin(), one.end(), two.begin(), op);
		//cout<<*val1<<endl;
		//cout<<*val2<<endl;

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Count)
	BOOST_AUTO_TEST_CASE( count_test) {
		using namespace std;
		int val = 177;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		one[256] = 177;
		one[258] = 177;
		auto val2 = parallel::count(one.begin(), one.end(), val);

		BOOST_CHECK(val2 == 3);
	}

	BOOST_AUTO_TEST_CASE( count_if_test) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 1);
		auto val1 = std::count_if(one.begin(), one.end(), [](int k)->bool {return (k%10)==0;});
		auto val2 = parallel::count_if(one.begin(), one.end(), [](int k)->bool {return (k%10)==0;});
		BOOST_CHECK(val2 == val1);
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(SimpleLogical)
	BOOST_AUTO_TEST_CASE(all_of_test) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 10);
		auto val = parallel::all_of(one.begin(), one.end(), [](int i)->bool {return ( i>9);});

		BOOST_CHECK(val);
	}

	BOOST_AUTO_TEST_CASE(any_of_test) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 10);
		auto val = parallel::any_of(one.begin(), one.end(), [](int &i)->bool {return ( i==177);});

		BOOST_CHECK(val);
	}

	BOOST_AUTO_TEST_CASE(none_of_test) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 10);
		auto val = parallel::none_of(one.begin(), one.end(),
				[](int &i)->bool {return ( i==201300);});

		BOOST_CHECK(val);
	}

	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Equal)
	BOOST_AUTO_TEST_CASE(equal_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 10);
		iota(two.begin(), two.end(), 10);
		auto val = parallel::equal(one.begin(), one.end(), two.begin());

		BOOST_CHECK(val);
	}
	BOOST_AUTO_TEST_CASE(equal_test2) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 10);
		iota(two.begin(), two.end(), 10);
		auto val = parallel::equal(one.begin(), one.end(), two.begin(), two.end());

		BOOST_CHECK(val);
	}

	BOOST_AUTO_TEST_CASE(equal_test3) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 10);
		iota(two.begin(), two.end(), 10);
		auto val = parallel::equal(one.begin(), one.end(), two.begin(),
				[](int a, int b)->bool {return a==b;});
		BOOST_CHECK(val);
	}
	BOOST_AUTO_TEST_CASE(equal_test4) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 10);
		iota(two.begin(), two.end(), 10);
		auto val2 = parallel::equal(one.begin(), one.end(), two.begin(), two.end(),
				[](int a, int b)->bool {return a==b;});

		BOOST_CHECK(val2);
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(MinMax)
	BOOST_AUTO_TEST_CASE(min_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);

		auto val = parallel::min_element(one.begin(), one.end());

		BOOST_CHECK(*val == 100);
	}

	BOOST_AUTO_TEST_CASE(min_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);

		auto val = parallel::min_element(one.begin(), one.end(),
				[]( int i , int j)->bool {return i<j;});

		BOOST_CHECK(*val == 100);
	}
	BOOST_AUTO_TEST_CASE(max_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);
		auto val1 = std::max_element(one.begin(), one.end());
		auto val = parallel::max_element(one.begin(), one.end());

		BOOST_CHECK(*val == *val1);
	}

	BOOST_AUTO_TEST_CASE(max_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);
		auto val1 = std::max_element(one.begin(), one.end(),
				[]( int i , int j)->bool {return i<j;});
		auto val = parallel::max_element(one.begin(), one.end(),
				[]( int i , int j)->bool {return i<j;});

		BOOST_CHECK(*val == *val1);
	}

	BOOST_AUTO_TEST_CASE(minmax_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);

		auto val = parallel::minmax_element(one.begin(), one.end());

		BOOST_CHECK(*(val.first) == 100);
		BOOST_CHECK(*(val.second) == 100099);
	}
	BOOST_AUTO_TEST_CASE(minmax_test2) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);

		auto val = parallel::minmax_element(one.begin(), one.end(),
				[]( int i , int j)->bool {return i<j;});

		BOOST_CHECK(*(val.first) == 100);
		BOOST_CHECK(*(val.second) == 100099);
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(Copy)
	BOOST_AUTO_TEST_CASE(copy_test1) {
		using namespace std;
		vector<int> one(100);
		iota(one.begin(), one.end(), 100);

		std::vector<int> three(501);
		std::vector<int> four(501);
		auto val1 = std::copy(one.begin(), one.end(), three.begin());
		//std::cout<<*val1<<std::endl;

		auto val2 = parallel::copy(one.begin(), one.end(), four.begin());
		auto val = std::equal(three.begin(), three.end(), four.begin(),
				[](int a, int b)->bool {return a==b;});
		//std::cout<<*val2<<std::endl;
		//std::cout<<val<<std::endl;
		//std::cout<<"the size of one is:"<<one.size()<<std::endl;

		BOOST_CHECK(val == true && *(val1 - 1) == *(val2 - 1));
	}
	BOOST_AUTO_TEST_CASE(copy_n_test1) {
		using namespace std;
		vector<int> one(100000);
		iota(one.begin(), one.end(), 100);

		std::vector<int> three(100000);
		std::vector<int> four(100000);
		auto val1 = std::copy_n(one.begin(), 800, three.begin());
		//std::cout<<*val1<<std::endl;

		auto val2 = parallel::copy_n(one.begin(), 800, four.begin());
		auto val = std::equal(three.begin(), three.end(), four.begin(),
				[](int a, int b)->bool {return a==b;});
		//std::cout<<*val2<<std::endl;
		//std::cout<<val<<std::endl;
		//std::cout<<"the size of one is:"<<one.size()<<std::endl;

		BOOST_CHECK(val == true && *(val1 - 1) == *(val2 - 1));
	}
	BOOST_AUTO_TEST_CASE(copy_if_test1) {
		using namespace std;
		vector<int> one(200'000);
		iota(one.begin(), one.end(), 200'000);

		std::vector<int> three(100'001);
		std::vector<int> four(100'001);
		std::function<bool(int)> filt = [](int k)->bool {return k %2;};
		auto val1 = std::copy_if(one.begin(), one.end(), three.begin(), filt);
		//std::cout<<*val1<<std::endl;
		iota(one.begin(), one.end(), 200'000);
		auto val2 = parallel::copy_if(one.begin(), one.end(), four.begin(), filt);
		auto val = std::equal(three.begin(), three.end(), four.begin(),
				[](int a, int b)->bool {return a==b;});
		//std::cout<<*val2<<std::endl;
		//std::cout<<val<<std::endl;
		//std::cout<<"the size of one is:"<<one.size()<<std::endl;

		BOOST_CHECK(val == true && (*val1 == *val2));
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(replace)
	BOOST_AUTO_TEST_CASE(replace_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);
		int val1 = 455;
		int val2 = 5989;
		std::replace(one.begin(), one.end(), val1, val2);
		parallel::replace(two.begin(), two.end(), val1, val2);
		auto val = std::equal(one.begin(), one.end(), two.begin());
		BOOST_CHECK(val);
	}
	BOOST_AUTO_TEST_CASE(replace_if_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		int val2 = 6945;
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);
		std::function<bool(int)> filt = [](int k)->bool {return k %2;};
		std::replace_if(one.begin(), one.end(), filt, val2);
		parallel::replace_if(two.begin(), two.end(), filt, val2);
		auto val = std::equal(one.begin(), one.end(), two.begin());
		BOOST_CHECK(val);
	}

	BOOST_AUTO_TEST_CASE(replace_copy_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		int val1 = 455;
		int val2 = 5989;
		iota(one.begin(), one.end(), 100);
		auto ans2 = std::replace_copy(one.begin(), one.end(), two.begin(), val1, val2);
		auto ans1 = parallel::replace_copy(one.begin(), one.end(), three.begin(), val1, val2);
		auto val = std::equal(two.begin(), two.end(), three.begin());
		BOOST_CHECK(val && *(ans1 - 1) == *(ans2 - 1));
	}

	BOOST_AUTO_TEST_CASE(replace_copy_if_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		iota(one.begin(), one.end(), 100);
		int val2 = 5989;
		std::function<bool(int)> filt = [](int k)->bool {return k %2;};
		auto ans2 = std::replace_copy_if(one.begin(), one.end(), two.begin(), filt, val2);
		auto ans1 = parallel::replace_copy_if(one.begin(), one.end(), three.begin(), filt, val2);
		auto val = std::equal(two.begin(), two.end(), three.begin());
		BOOST_CHECK(val && *(ans1 - 1) == *(ans2 - 1));
	}
	BOOST_AUTO_TEST_SUITE_END()BOOST_AUTO_TEST_SUITE
(functions_based_on_sorting_test)
	BOOST_AUTO_TEST_CASE(is_sorted_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);

		auto val1 = std::is_sorted(one.begin(), one.end());
		auto val2 = parallel::is_sorted(two.begin(), two.end());

		BOOST_CHECK(val1 == val2);
	}
	BOOST_AUTO_TEST_CASE(is_sorted_test2) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);
		std::function<bool(int, int)> f = [](int a, int b)->bool {return a<b;};
		auto val1 = std::is_sorted(one.begin(), one.end(), f);
		auto val2 = parallel::is_sorted(two.begin(), two.end(), f);

		BOOST_CHECK(val1 == val2);
	}

	BOOST_AUTO_TEST_CASE(is_sorted_until_test1) {
		using namespace std;
		vector<int> one(100);
		vector<int> two(100);
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);
		one[50] = 9;
		two[50] = 9;
		auto val1 = std::is_sorted_until(one.begin(), one.end());
		auto val2 = parallel::is_sorted_until(two.begin(), two.end());
		//std::cout<<*val1<<std::endl;
		//std::cout<<*val2<<std::endl;
		BOOST_CHECK((*val1) == (*val2));
	}
	BOOST_AUTO_TEST_CASE(is_sorted_until_test2) {
		using namespace std;
		vector<int> one(100);
		vector<int> two(100);
		iota(one.begin(), one.end(), 100);
		iota(two.begin(), two.end(), 100);
		one[50] = 9;
		two[50] = 9;
		std::function<bool(int, int)> f = [](int a, int b)->bool {return a<b;};
		auto val1 = std::is_sorted_until(one.begin(), one.end(), f);
		auto val2 = parallel::is_sorted_until(two.begin(), two.end(), f);

		BOOST_CHECK((*val1) == (*val2));
	}
	BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(miscellaneous_tests)
	BOOST_AUTO_TEST_CASE(swap_ranges_test1) {
		using namespace std;
		vector<int> one(100000);
		vector<int> two(100000);
		vector<int> three(100000);
		vector<int> four(100000);
		iota(one.begin(), one.end(), 9453);
		iota(two.begin(), two.end(), 9453);

		std::swap_ranges(three.begin(), three.end(), one.begin());
		parallel::swap_ranges(four.begin(), four.end(), two.begin());

		BOOST_CHECK(std::equal(three.begin(), three.end(), four.begin()));
	}
	BOOST_AUTO_TEST_CASE(reverse_test1) {
		using namespace std;
		vector<int> one(1000000);
		vector<int> two(1000000);

		iota(one.begin(), one.end(), 1);
		iota(two.begin(), two.end(), 1);
		cout << "done" << endl;
		std::reverse(one.begin(), one.end());
		parallel::reverse(two.begin(), two.end());
		//for(auto i:two)cout<<i<<endl;
		BOOST_CHECK(std::equal(one.begin(), one.end(), two.begin()));
	}
	BOOST_AUTO_TEST_SUITE_END()BOOST_AUTO_TEST_SUITE
(Merge_Sort_Tests)
	BOOST_AUTO_TEST_CASE(merge_sort) {
		using namespace std::chrono;

		std::random_device rd;
		std::mt19937 rng(rd());
		std::uniform_int_distribution<unsigned int> dist(0,
				std::numeric_limits<unsigned int>::max());

		std::vector<unsigned int> v, back(100 * 1000000);
		/*std::vector<unsigned int> v2(20 * 10000);
		 auto len =v2.size()/2 ;
		 std::iota(v2.begin(),v2.begin()+len,0);
		 std::iota(v2.begin()+len,v2.end(),0);
		 try{
		 parallel::inplace_merge(v2.begin(),v2.begin()+len,v2.end());
		 for(auto a :v2) std::cout<<a<<std::endl;
		 }
		 catch(std::exception &e){
		 std::cout<<e.what()<<std::endl;
		 }*/
		for(int i = 0; i < 3; ++i) {
			std::cout << "Generating...\n";
			std::generate_n(back.begin(), back.size(), [&]() {return dist(rng);});

			time_point<system_clock> t0, t1;
			v = back;
			std::cout << "parallel::stable_sort: ";
			t0 = system_clock::now();
			parallel::stable_sort(v.begin(), v.end());
			t1 = system_clock::now();
			std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";
			v = back;
			std::cout << "std::sort: ";
			t0 = system_clock::now();
			std::stable_sort(v.begin(), v.end());
			t1 = system_clock::now();
			std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";

			v = back;
			std::cout << "mergesort_mt1: ";
			t0 = system_clock::now();
			parallel::mergesort_mt1(v.begin(), v.end());
			t1 = system_clock::now();
			std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";
			v = back;
			std::cout << "mergesort_mt2: ";
			t0 = system_clock::now();
			parallel::mergesort_mt2(v.begin(), v.end());
			t1 = system_clock::now();
			std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";
			v = back;
			std::cout << "mergesort_mt3: ";
			t0 = system_clock::now();
			parallel::mergesort_mt3(v.begin(), v.end());
			t1 = system_clock::now();
			std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";

			std::cout << "parallel fast unstable stort: ";
						t0 = system_clock::now();
						parallel::sort(v.begin(),v.end(),(unsigned int) std::thread::hardware_concurrency());
						t1 = system_clock::now();
						std::cout << duration_cast < milliseconds > (t1 - t0).count() << "ms\n";

		}

	}
	BOOST_AUTO_TEST_SUITE_END()
	/*init_unit_test_suite(int argc, char**argv){
	 return 0;
	 }*/
//int unit_test_main( init_unit_test_func init_func, int argc, char* argv[] ){ return 0;}


// Copyright Louis Dionne 2013-2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/assert.hpp>
#include <boost/hana/functional/placeholder.hpp>
#include <boost/hana/integral_constant.hpp>

#include "matrix/comparable.hpp"
#include "matrix/functor.hpp"
namespace hana = boost::hana;
using namespace cppcon;

int main() {
    // transform
    {
        auto m = matrix(
            row(1,              hana::int_c<2>, 3),
            row(hana::int_c<4>, 5,              6),
            row(7,              8,              hana::int_c<9>)
        );

        BOOST_HANA_CONSTEXPR_CHECK(hana::equal(
            hana::transform(m, hana::_ + hana::int_c<1>),
            matrix(
                row(2,              hana::int_c<3>, 4),
                row(hana::int_c<5>, 6,              7),
                row(8,              9,              hana::int_c<10>)
            )
        ));
    }
}


#include <boost/config.hpp>

#if !defined(BOOST_NO_CXX11_CONSTEXPR) \
    && !defined(BOOST_NO_CXX11_HDR_ARRAY)

template <class T>
constexpr int get_size(const T& val) {
    return val.size() * sizeof(typename T::value_type);
}

template <class T, T Value>
struct integral_constant {
    BOOST_STATIC_CONSTEXPR T value = Value;
    BOOST_CONSTEXPR operator T() const {
        return this->value;
    }
};

char array[integral_constant<int, 10>()];

#else
#error "This code requires C++11 constexpr and std::array"
#endif

#include <array>

int main() {
    std::array<short, 5> arr;
    static_assert(get_size(arr) == 5 * sizeof(short), "");

    unsigned char data[get_size(arr)];
    (void)data;
}


#include <algorithm>
#include <iomanip>
#include <boost/io/ios_state.hpp>
#include "KCipher2.h"
#include "util.h"

template <size_t Size>
static std::ostream& operator<<(std::ostream& os, const std::array<uint32_t, Size>& data)
{
	boost::io::ios_flags_saver fs(os);
	os << std::hex << std::uppercase;
	for (uint32_t i : data)
		os << std::setfill('0') << std::setw(8) << i << " ";
	return os;
}

std::ostream& operator<<(std::ostream& os, const KCipher2& data)
{
	if (!data.current_state_) {
		os << "ik and iv are null" << std::endl;
	}
	else {
		os << "A:" << data.current_state_->reg_a << std::endl;
		os << "B:" << data.current_state_->reg_b << std::endl;
		os << "R:" << data.current_state_->reg_r << std::endl;
		os << "L:" << data.current_state_->reg_l << std::endl;
	}
	return os;
}

KCipher2::State::State(const uint32_t k[], const uint32_t iv[])
{
	reg_a = { k[4], k[3], k[2], k[1], k[0] };
	reg_b = { k[10], k[11], iv[0], iv[1], k[8], k[9], iv[2], iv[3], k[7], k[5], k[6] };
	reg_r = { 0, 0 };
	reg_l = { 0, 0 };
}

uint32_t KCipher2::State::zl() const
{
	return (reg_b[0] + reg_r[1]) ^ reg_r[0] ^ reg_a[4];
}

uint32_t KCipher2::State::zh() const
{
	return (reg_b[10] + reg_l[1]) ^ reg_l[0] ^ reg_a[0];
}

uint32_t KCipher2::sub(uint32_t x)
{
#ifdef USE_KCIPHER2_AES_SBOX
	const uint8_t result[] = {
		sbox[util::int32toq3(x)],
		sbox[util::int32toq2(x)],
		sbox[util::int32toq1(x)],
		sbox[util::int32toq0(x)]
	};
	const uint8_t mul2[] = {
		sbox.mul(result[0], 2),
		sbox.mul(result[1], 2),
		sbox.mul(result[2], 2),
		sbox.mul(result[3], 2)
	};
	return
		util::q3toint32(mul2[0] ^ result[3] ^ result[2] ^ mul2[1] ^ result[1]) ^
		util::q2toint32(mul2[1] ^ result[0] ^ result[3] ^ mul2[2] ^ result[2]) ^
		util::q1toint32(mul2[2] ^ result[1] ^ result[0] ^ mul2[3] ^ result[3]) ^
		util::q0toint32(mul2[3] ^ result[2] ^ result[1] ^ mul2[0] ^ result[0]);
#else
	return table[0][util::int32toq3(x)] ^ table[1][util::int32toq2(x)] ^ table[2][util::int32toq1(x)] ^ table[3][util::int32toq0(x)];
#endif
}

template <bool IsInit>
void KCipher2::next(State *old_state, State *new_state)
{
	std::rotate_copy(old_state->reg_a.begin(), old_state->reg_a.begin() + 1, old_state->reg_a.end(), new_state->reg_a.begin());
	new_state->reg_a[4] =
		old_state->reg_a[3] ^ alpha_mul(0, old_state->reg_a[0]);
	if (IsInit)
		new_state->reg_a[4] ^= old_state->zl();

	std::rotate_copy(old_state->reg_b.begin(), old_state->reg_b.begin() + 1, old_state->reg_b.end(), new_state->reg_b.begin());
	new_state->reg_b[10] =
		alpha_mul((old_state->cl1()) ? 1 : 2, old_state->reg_b[0]) ^
		((old_state->cl2()) ? alpha_mul(3, old_state->reg_b[8]) : old_state->reg_b[8]) ^
		old_state->reg_b[1] ^ old_state->reg_b[6];
	if (IsInit)
		new_state->reg_b[10] ^= old_state->zh();

	new_state->reg_r[0] = sub(old_state->reg_l[1] + old_state->reg_b[9]);
	new_state->reg_r[1] = sub(old_state->reg_r[0]);
	new_state->reg_l[0] = sub(old_state->reg_r[1] + old_state->reg_b[4]);
	new_state->reg_l[1] = sub(old_state->reg_l[0]);
}

void KCipher2::load_initial_key(const InitKey& ik, const InitVec& iv)
{
	load_initial_key(ik.data(), iv.data());
}

void KCipher2::load_initial_key(const uint32_t ik[], const uint32_t iv[])
{
	Key key;

	int i;
	for (i = 0; i < 4; ++i)
		key[i] = ik[i];
	for (; i < 12; ++i) {
		if (i % 4 == 0)
			key[i] = key[i - 4] ^ sub(util::rotl32(key[i - 1], 8)) ^ rcon[i / 4 - 1];
		else
			key[i] = key[i - 4] ^ key[i - 1];
	}

	current_state_.reset(new State(key.data(), iv));
	next_state_.reset(new State());

	for (int i = 0; i < 24; ++i) {
		next<true>(current_state_.get(), next_state_.get());
		current_state_.swap(next_state_);
	}
}

void KCipher2::next()
{
	next<false>(current_state_.get(), next_state_.get());
	current_state_.swap(next_state_);
}

void KCipher2::get_stream(KeyStream & stream)
{
	stream.first = current_state_->zh();
	stream.second = current_state_->zl();
}

KCipher2::KeyStream KCipher2::get_stream()
{
	return KeyStream(current_state_->zh(), current_state_->zl());
}


/**********************************************************************************************************************
This file is part of the Control Toolbox (https://adrlab.bitbucket.io/ct), copyright by ETH Zurich, Google Inc.
Licensed under Apache2 license (see LICENSE file in main directory)
**********************************************************************************************************************/

#pragma once

#include <boost/algorithm/string.hpp>

#include <ct/core/common/activations/Activations.h>

namespace ct {
namespace optcon {

/*!
 * \ingroup CostFunction
 *
 * \brief An interface for a term, supporting both analytical and auto-diff terms
 *
 * Derive from this term to implement your own term. You only have to implement
 * evaluateCppadCg() if you want to use auto-diff. Otherwise, you need to implement
 * evaluate() as well as the derivatives. In case you want to go for the most general
 * implementation, you can implement a local, templated evalLocal() method in derived terms,
 * which gets called by evaluate() and evaluateCppadCg(), see e.g. TermLinear.
 *
 * An example for an implementation of a custom term is given in \ref TermQuadratic.hpp
 **/
template <size_t STATE_DIM, size_t CONTROL_DIM, typename SCALAR_EVAL = double, typename SCALAR = SCALAR_EVAL>
class TermBase
{
protected:
    //! a name identifier for this term
    std::string name_;
    //! time activations for this term
    std::shared_ptr<ct::core::tpl::ActivationBase<SCALAR_EVAL>> c_i_;

public:
    typedef Eigen::Matrix<SCALAR_EVAL, STATE_DIM, STATE_DIM> state_matrix_t;
    typedef Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, CONTROL_DIM> control_matrix_t;
    typedef Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, STATE_DIM> control_state_matrix_t;
    typedef Eigen::Matrix<SCALAR_EVAL, STATE_DIM, STATE_DIM> state_matrix_double_t;
    typedef Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, CONTROL_DIM> control_matrix_double_t;
    typedef Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, STATE_DIM> control_state_matrix_double_t;

    /**
	 * \brief Default constructor
	 * @param name Name of the term
	 */
    TermBase(std::string name = "Unnamed");

    /**
	 * \brief Copy Cunstructor
	 * @param arg The term to copy
	 */
    TermBase(const TermBase& arg);

    /**
	 * \brief Deep-copy term
	 * @return
	 */
    virtual TermBase<STATE_DIM, CONTROL_DIM, SCALAR_EVAL, SCALAR>* clone() const = 0;

    /**
	 * \brief Destructor
	 */
    virtual ~TermBase();

    /**
	 * @brief      Evaluates the term at x, u, t
	 *
	 * @param[in]  x     The current state
	 * @param[in]  u     The current control
	 * @param[in]  t     The current time
	 *
	 * @return     The evaluatated cost term
	 */
    virtual SCALAR evaluate(const Eigen::Matrix<SCALAR, STATE_DIM, 1>& x,
        const Eigen::Matrix<SCALAR, CONTROL_DIM, 1>& u,
        const SCALAR& t) = 0;

    /**
	 * @brief      The evaluate method used for jit compilation in CostfunctionAD
	 *
	 * @param[in]  x     The state vector
	 * @param[in]  u     The control vector
	 * @param[in]  t     The time
	 *
	 * @return     The evaluated cost
	 */
    virtual ct::core::ADCGScalar evaluateCppadCg(const core::StateVector<STATE_DIM, ct::core::ADCGScalar>& x,
        const core::ControlVector<CONTROL_DIM, ct::core::ADCGScalar>& u,
        ct::core::ADCGScalar t);

    /**
	 * @brief      Gets called by the analytical costfunction. Adds time
	 *             dependent activations on top of the term
	 *
	 * @param[in]  x     The current state
	 * @param[in]  u     The current control
	 * @param[in]  t     The current time
	 *
	 * @return     The evaluatated cost term
	 */
    SCALAR_EVAL eval(const Eigen::Matrix<SCALAR_EVAL, STATE_DIM, 1>& x,
        const Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, 1>& u,
        const SCALAR_EVAL& t);

    /**
	 * \brief Returns if term is non-zero at a specific time
	 * By default, all terms are evaluated at all times. However, if a term is not active at a certain time, you can overload this
	 * function to spare evaluations of the term and its derivatives
	 * @param t time
	 * @return true if term is active at t
	 */
    virtual bool isActiveAtTime(SCALAR_EVAL t);

    //! compute time activation
    SCALAR_EVAL computeActivation(SCALAR_EVAL t);

    //! compute derivative of this cost term w.r.t. the state
    virtual core::StateVector<STATE_DIM, SCALAR_EVAL> stateDerivative(
        const core::StateVector<STATE_DIM, SCALAR_EVAL>& x,
        const core::ControlVector<CONTROL_DIM, SCALAR_EVAL>& u,
        const SCALAR_EVAL& t);

    //! compute second order derivative of this cost term w.r.t. the state
    virtual state_matrix_t stateSecondDerivative(const core::StateVector<STATE_DIM, SCALAR_EVAL>& x,
        const core::ControlVector<CONTROL_DIM, SCALAR_EVAL>& u,
        const SCALAR_EVAL& t);

    //! compute derivative of this cost term w.r.t. the control input
    virtual core::ControlVector<CONTROL_DIM, SCALAR_EVAL> controlDerivative(
        const core::StateVector<STATE_DIM, SCALAR_EVAL>& x,
        const core::ControlVector<CONTROL_DIM, SCALAR_EVAL>& u,
        const SCALAR_EVAL& t);

    //! compute second order derivative of this cost term w.r.t. the control input
    virtual control_matrix_t controlSecondDerivative(const core::StateVector<STATE_DIM, SCALAR_EVAL>& x,
        const core::ControlVector<CONTROL_DIM, SCALAR_EVAL>& u,
        const SCALAR_EVAL& t);

    //! compute the cross-term derivative (state-control) of this cost function term
    virtual control_state_matrix_t stateControlDerivative(const core::StateVector<STATE_DIM, SCALAR_EVAL>& x,
        const core::ControlVector<CONTROL_DIM, SCALAR_EVAL>& u,
        const SCALAR_EVAL& t);

    //! load this term from a configuration file
    virtual void loadConfigFile(const std::string& filename, const std::string& termName, bool verbose = false);

    //! set the time activation functions for this term
    void setTimeActivation(std::shared_ptr<ct::core::tpl::ActivationBase<SCALAR_EVAL>> c_i, bool verbose = false);

    //! load the time activation functions for this term from file
    void loadTimeActivation(const std::string& filename, const std::string& termName, bool verbose = false);

    /**
	 * \brief Returns the name of the term
	 * @param termName name of the term
	 */
    const std::string& getName() const;

    /**
	 * \brief Sets the name of the term
	 * @param termName
	 */
    void setName(const std::string& termName);

    //! updates the reference state for this term
    virtual void updateReferenceState(const Eigen::Matrix<SCALAR_EVAL, STATE_DIM, 1>& newRefState);

    //! updates the reference control for this term
    virtual void updateReferenceControl(const Eigen::Matrix<SCALAR_EVAL, CONTROL_DIM, 1>& newRefControl);

    //! retrieve this term's current reference state
    virtual Eigen::Matrix<SCALAR_EVAL, STATE_DIM, 1> getReferenceState() const;
};

}  // namespace optcon
}  // namespace ct


#include <EigenFace.h>
#include "EigenFaceData.h"
#include "EigenFaceParameters.h"

#include <QStringList>

#include <math.h>

#include <FeatureSet.h>
#include <GreyImage.h>
#include <ImageCache.h>
#include <ImageInfo.h>

using namespace INDI;

DDT::Return EigenFace::staticReturn;

DECLARE_NAMEDORDINAL_INSTANCE(EigenFaceStatus);
DECLARE_NAMEDORDINAL_ID(EigenFaceStatus, Active)
DECLARE_NAMEDORDINAL_ID(EigenFaceStatus, Deleted)
DECLARE_NAMEDORDINAL_ID(EigenFaceStatus, Inconsistent)
DECLARE_NAMEDORDINAL_ID(EigenFaceStatus, Error)

EigenFaceParameters * EigenFace::parameters(const QDir & initDir)
{
    EigenFaceParameters * p = EigenFaceParameters::create(initDir);
    staticReturn = EigenFaceParameters::lastReturn();
    return p;
}
/*
qreal EigenFace::distance(const QLine line)
{
	qreal dx = line.p2().x() - line.p1().x();
	qreal dy = line.p2().y() - line.p1().y();
	return sqrt(dx * dx + dy * dy);
}
*/
qreal EigenFace::pitch(QPointF p1, QPointF p2)
{
    qreal fall = p2.y() - p1.y();
    qreal run  = p2.x() - p1.x();
    if (qIsNull(run))
        return 999999.9;
    else
        return qAbs(fall / run);
}

QImage EigenFace::normalize(const QImage & input,
                            const Eyes inputEyes,
                            const QSize outSize,
                            const Eyes outEyes,
                            QImage::Format newFormat)
{
    //double width = outSize.width();
    QPointF outCenter = (outEyes.p1() + outEyes.p2()) / 2;
    QPointF eyeCenter = (inputEyes.p1() + inputEyes.p2()) / 2;
    double fall = inputEyes.p2().y() - inputEyes.p1().y();
    double run  = qMax(1, inputEyes.p2().x() - inputEyes.p1().x());
    double theta = atan(fall / run);
    double eyeDistance = sqrt(fall * fall + run * run);
    double outDistance = outEyes.p2().x() - outEyes.p1().x();
    double scale = eyeDistance / outDistance;
    // T (top) point locates line above eyes
    QPointF T = eyeCenter + QPointF(scale * outCenter.y() *  sin(theta),
                                    scale * outCenter.y() * -cos(theta));
    // O (origin) is origin of normalized image
    QPointF O = T - QPointF(scale * outCenter.x() * cos(theta),
                            scale * outCenter.x() * sin(theta));

    if (QImage::Format_Invalid == newFormat)
        newFormat = input.format();
    QImage outImage(outSize, newFormat);
    if (QImage::Format_Indexed8 == newFormat)
    {
        outImage.setNumColors(256);
        outImage.setColorTable(DDT::GreyImage::greyColorTable());
        outImage.fill(128);
    }
    else
        outImage.fill(Qt::gray);

    double scaledSin = scale * sin(theta);
    double scaledCos = scale * cos(theta);
    for (int r = 0; r < outSize.height(); ++r)
    {
        for (int c = 0; c < outSize.width(); ++c)
        {
            double x = O.x() - (double)r * scaledSin + (double)c * scaledCos,
            y = O.y() + (double)r * scaledCos + (double)c * scaledSin;
            int ix = x + 0.5, iy = y + 0.5;
            if (ix >= 0 && ix < input.width()
                && iy >= 0 && iy < input.height())
                {
                if (QImage::Format_Indexed8 == newFormat
                    && QImage::Format_Indexed8 == input.format())
                {
                    int x = input.pixelIndex(ix, iy);
                    outImage.setPixel(c, r, x);
                }
                else if (QImage::Format_Indexed8 == newFormat
                         && QImage::Format_Indexed8 != input.format())
                {
                    QRgb pixel = input.pixel(ix, iy);
                    int x = qGray(pixel);
                    outImage.setPixel(c, r, x);
                }
                else
                {
                    QRgb pixel = input.pixel(ix, iy);
                    outImage.setPixel(c, r, pixel);
                }
            }
        } // for(c)
    } // for(r)

    DDT::FeatureSet fSet;
    DDT::ImageInfo ii;
    fSet.set(DDT::Feature::LeftEye, outEyes.p1());
    fSet.set(DDT::Feature::RightEye, outEyes.p2());
    fSet.calculate();
    ii.addFace(fSet);
    ii.setImageText(&outImage);

    return outImage;
} // normalize()

QImage EigenFace::normalize(const DDT::GreyImage & gi,
                            const Eyes inputEyes,
                            const QSize outSize,
                            const Eyes outEyes,
                            QImage::Format newFormat)
{
    //double width = outSize.width();
    QPointF outCenter = (outEyes.p1() + outEyes.p2()) / 2;
    QPointF eyeCenter = (inputEyes.p1() + inputEyes.p2()) / 2;
    double fall = inputEyes.p2().y() - inputEyes.p1().y();
    double run  = qMax(1, inputEyes.p2().x() - inputEyes.p1().x());
    double theta = atan(fall / run);
    double eyeDistance = sqrt(fall * fall + run * run);
    double outDistance = outEyes.p2().x() - outEyes.p1().x();
    double scale = eyeDistance / outDistance;
    // T (top) point locates line above eyes
    QPointF T = eyeCenter + QPointF(scale * outCenter.y() *  sin(theta),
                                    scale * outCenter.y() * -cos(theta));
    // O (origin) is origin of normalized image
    QPointF O = T - QPointF(scale * outCenter.x() * cos(theta),
                            scale * outCenter.x() * sin(theta));

    if (QImage::Format_Invalid == newFormat)
        newFormat = QImage::Format_Indexed8;
    QImage outImage(outSize, newFormat);
    if (QImage::Format_Indexed8 == newFormat)
    {
        outImage.setNumColors(256);
        outImage.setColorTable(DDT::GreyImage::greyColorTable());
        outImage.fill(128);
    }
    else
        outImage.fill(Qt::gray);

    double scaledSin = scale * sin(theta);
    double scaledCos = scale * cos(theta);
    for (int r = 0; r < outSize.height(); ++r)
    {
        for (int c = 0; c < outSize.width(); ++c)
        {
            double x = O.x() - (double)r * scaledSin + (double)c * scaledCos,
            y = O.y() + (double)r * scaledCos + (double)c * scaledSin;
            int ix = x + 0.5, iy = y + 0.5;
            if (ix >= 0 && ix < gi.width()
                && iy >= 0 && iy < gi.height())
                {
                if (QImage::Format_Indexed8 == newFormat)
                {
                    int x = gi.pixel(ix, iy);
                    outImage.setPixel(c, r, x);
                }
                else
                {
                    int grey = gi.pixel(ix, iy);
                    QRgb pixel = qRgb(grey, grey, grey);
                    outImage.setPixel(c, r, pixel);
                }
            }
        } // for(c)
    } // for(r)

    DDT::FeatureSet fSet;
    DDT::ImageInfo ii;
    fSet.set(DDT::Feature::LeftEye, outEyes.p1());
    fSet.set(DDT::Feature::RightEye, outEyes.p2());
    fSet.calculate();
    ii.addFace(fSet);
    ii.setImageText(&outImage);

    return outImage;
} // normalize()


#ifndef BART_SRC_FORMULATION_SCALAR_DRIFT_DIFFUSION_I_HPP_
#define BART_SRC_FORMULATION_SCALAR_DRIFT_DIFFUSION_I_HPP_

#include <deal.II/lac/vector.h>
#include <deal.II/lac/full_matrix.h>
#include <quadrature/quadrature_types.h>

#include "domain/domain_types.hpp"
#include "formulation/formulation_types.hpp"
#include "system/system_types.h"

namespace bart::formulation::scalar {

/*! \brief Interface for classes that provide the drift diffusion formulation terms.
 *
 * Classes the derive from this provide two terms from the drift-diffusion formulation used by the non-linear diffusion
 * acceleration method (Park, et. al. 2012). The drift-diffusion
 * formulation for the _k_-eigenvalue problem is:
 *
 * \f[
 * -D_g(\vec{r})\vec{\nabla} \cdot \vec{\nabla} \phi_g(\vec{r}) - \vec{D}_g(\vec{r})\vec{\nabla}\phi_g(\vec{r}) +
 * (\Sigma_{t,g} - \Sigma_{s}^{g\to g})\phi_g(\vec{r})
 * = \sum_{g' \neq g}\Sigma_{g}^{g' \to g}\phi_{g'}(\vec{r}) + \frac{\chi_g}{k}\sum_{g'}\nu_{g'}\Sigma_{f,g'}\phi_{g'}(\vec{r})
 * \f]
 *
 * Where \f$\vec{D}_g\f$ is the drift-diffusion term. There are multiple ways to describe this term, and this class is
 * not expected to calculate it directly, but using a class of type calculator::drift_diffusion::DriftDiffusionVectorCalculatorI.
 * This calculation requires the current, so the function to fill that term requires the current at each quadrature
 * point.
 *
 * @tparam dim
 */
template <int dim>
class DriftDiffusionI {
 public:
  using BoundaryType = formulation::BoundaryType;
  using CellPtr = typename domain::CellPtr<dim>;
  using EnergyGroup = system::EnergyGroup;
  using Matrix = typename dealii::FullMatrix<double>;
  using Vector = typename dealii::Vector<double>;
  using VectorMap = std::map<quadrature::QuadraturePointIndex, std::shared_ptr<Vector>>;
  virtual ~DriftDiffusionI() = default;

  /*! \brief Integrates the bilinear boundary term over a cell and fills a given matrix.
   *
   * For a given cell and face in the triangulation, \f$\partial K \in \partial T_K\f$, with basis functions
   * \f$\varphi\f$, this function integrates the bilinear drift-diffusion boundary term for
   * one group using the cell quadrature and adds them to the provided
   * local cell matrix
   *
   * @param to_fill cell matrix to fill
   * @param group_angular_flux angular flux for all groups
   */
  virtual auto FillCellBoundaryTerm(Matrix& to_fill, const CellPtr&, domain::FaceIndex, BoundaryType,
                                    const VectorMap& group_angular_flux) const -> void = 0;

  /*! \brief Integrates the bilinear drift-diffusion term over a cell and fills a given matrix.
   *
   * For a given cell in the triangulation, \f$K \in T_K\f$, with basis functions
   * \f$\varphi\f$, this function integrates the bilinear SAAF streaming term for
   * one group using the cell quadrature and adds them to the provided
   * local cell matrix, \f$\mathbf{A}\f$.
   *
   * \f[
   * \mathbf{A}(i,j)_{K,g}' = \mathbf{A}(i,j)_{K,g} + \int_{K}\vec{D}_g\varphi_j(\vec{r})\nabla\varphi_i(\vec{r})dV
   * \f]
   *
   * @param to_fill
   */
  virtual auto FillCellDriftDiffusionTerm(Matrix& to_fill, const CellPtr&, system::EnergyGroup,
                                          const Vector& group_scalar_flux,
                                          const std::array<Vector, dim>& current) const -> void = 0;
};

} // namespace bart::formulation::scalar

#endif //BART_SRC_FORMULATION_SCALAR_DRIFT_DIFFUSION_I_HPP_