#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*-
$CXX $0 -o $0x -lcudart `pkg-config --libs fftw3` -lboost_timer -lboost_unit_test_framework&&$0x&&rm $0x;exit
#endif
// © Alfredo A. Correa 2018-2019

#ifndef MULTI_ADAPTORS_FFTW_HPP
#define MULTI_ADAPTORS_FFTW_HPP

#include<fftw3.h> // external fftw3 library

#include "../../multi/utility.hpp"
#include "../../multi/array.hpp"

#include "../../multi/config/NODISCARD.hpp"

#include<cmath>
#include<complex>
#include<memory>
#include<numeric> // accumulate

#if HAVE_FFTW3_THREADS
#include <thread>
#endif

#include<experimental/tuple> // experimental::apply

#include<utility>
#include<type_traits>

namespace boost{
namespace multi{

namespace fftw{
//	template<class T> auto alignment_of(T* p){return ::fftw_alignment_of((double*)p);}
#if  __cpp_lib_as_const >= 201510
using std::as_const;
#else
template<class T> constexpr std::add_const_t<T>& as_const(T& t) noexcept{return t;}
#endif

}

#if 0
template<typename Size>
auto fftw_plan_dft_1d(
	Size N,
	std::complex<double> const* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
#ifndef NDEBUG
	auto check = in[N/3]; // check that const data will not been overwritten
#endif
	assert( fftw::alignment_of(in) == fftw::alignment_of(out) );
	auto ret=::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT );
	assert(check == in[N/3]); // check that const data has not been overwritten
	return ret;
}

template<typename Size>
auto fftw_plan_dft_1d(
	Size N,
	std::complex<double>* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
	assert( fftw::alignment_of(in) == fftw::alignment_of(out) );
	return ::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags);
}

template<typename Size>
auto fftw_plan_dft_2d(
	Size N1, Size N2,
	std::complex<double> const* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
	assert( fftw::alignment_of(in) == fftw::alignment_of(out) );
#ifndef NDEBUG
	auto check = in[N1*N2/3]; // check that const data will not been overwritten
#endif
	auto ret = ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT);
	assert( check == in[N1*N2/3] ); // check that const data has not been overwritten
	return ret;
}

template<typename Size>
auto fftw_plan_dft_2d(
	Size N1, Size N2,
	std::complex<double>* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
	assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out));
	return ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags);
}

template<typename Size>
auto fftw_plan_dft_3d(
	Size N1, Size N2, Size N3,
	std::complex<double>* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
	assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out));
	return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags);
}
template<typename Size>
auto fftw_plan_dft_3d(
	Size N1, Size N2, Size N3,
	std::complex<double> const* in, std::complex<double>* out, int sign,
	unsigned flags = FFTW_ESTIMATE
){
	assert( flags & FFTW_PRESERVE_INPUT );
	assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out));
	return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT);
}
#endif

#if 0
template<typename Rank>
auto fftw_plan_dft(
	Rank r, int* ns,
	std::complex<double>* in, std::complex<double>* out,
	int sign, unsigned flags = FFTW_ESTIMATE
){
	assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out));
	return ::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags);
}
template<typename RankType>
auto fftw_plan_dft(
	RankType r, int* ns,
	std::complex<double> const* in, std::complex<double>* out,
	int sign, unsigned flags = FFTW_ESTIMATE | FFTW_PRESERVE_INPUT
){
	assert( flags & FFTW_PRESERVE_INPUT );
	assert(fftw::alignment_of(in) == fftw::alignment_of(out));
#ifndef NDEBUG
	size_t ne = 1; for(RankType i = 0; i != r; ++i) ne*=ns[i];
	auto check = in[ne/3]; // check that const data will not been overwritten
#endif
	auto ret=::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags);
	assert(check == in[ne/3]); // check that const data has not been overwritten
	return ret;
}
#endif

#if 0
template<typename In, typename Out>
auto fftw_plan_dft_1d(
	In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE
){
	static_assert(in.dimensionality == 1, "!"); assert(size(in) == size(out));
	assert( in.is_compact() ); assert( out.is_compact() );
	return multi::fftw_plan_dft_1d(size(in), data_elements(in), data_elements(out), sign, flags);
}

template<class In, class Out>
auto fftw_plan_dft_2d(
	In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE
){
	static_assert(in.dimensionality == 2, "!"); assert(in.sizes() == out.sizes());
	assert( in.is_compact() ); assert( out.is_compact() );
	return multi::fftw_plan_dft_2d(
		sizes(in)[0], sizes(in)[1],
		data_elements(in), data_elements(out), sign, flags
	);
}

template<class In, class Out>
auto fftw_plan_dft_3d(
	In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE
){
	static_assert(in.dimensionality == 3, "!"); assert(in.sizes() == out.sizes());
	assert( in.is_compact() ); assert( out.is_compact() );
	return multi::fftw_plan_dft_3d(
		sizes(in)[0], sizes(in)[1], sizes(in)[2],
		data(in), data(out),
		sign, flags
	);
}
#endif

template<class T, class Tpl> constexpr auto to_array(Tpl const& t){
	return detail::to_array_impl<T>(t, std::make_index_sequence<std::tuple_size<Tpl>{}>{});
}

#if(__cpp_if_constexpr>=201606)
//https://stackoverflow.com/a/35110453/225186
template<class T> constexpr std::remove_reference_t<T> _constx(T&&t){return t;}
#define logic_assert(C, M) \
	if constexpr(noexcept(_constx(C))) static_assert((C), M); else assert((C)&& M);
#else
#define logic_assert(ConditioN, MessagE) assert(ConditioN && MessagE);
#endif

template<typename It1, class It2, std::enable_if_t<std::is_pointer<decltype(base(It2{}))>{} or std::is_convertible<decltype(base(It2{})), std::complex<double>*>{}, int> = 0>
auto fftw_plan_many_dft(It1 first, It1 last, It2 d_first, int sign, unsigned flags = FFTW_ESTIMATE)
->decltype(reinterpret_cast<fftw_complex*>(static_cast<std::complex<double>*>(base(d_first))), fftw_plan{}){
	assert(sizes(*first)==sizes(*d_first));
	auto ion      = to_array<int>(sizes(*first));

	assert(strides(*first) == strides(*last));
	auto istrides = to_array<int>(strides(*first));
	auto ostrides = to_array<int>(strides(*d_first));

//	auto inelemss = to_array<int>(first->nelemss());
//	auto onelemss = to_array<int>(d_first->nelemss());

	std::array<std::tuple<int, int, int>, std::decay_t<decltype(*It1{})>::dimensionality> ssn;
	for(std::size_t i = 0; i != ssn.size(); ++i) ssn[i] = std::make_tuple(istrides[i], ostrides[i], ion[i]);
	std::sort(ssn.begin(), ssn.end(), std::greater<>{});

	for(std::size_t i = 0; i != ssn.size(); ++i){
		istrides[i] = std::get<0>(ssn[i]);
		ostrides[i] = std::get<1>(ssn[i]);
		ion[i]      = std::get<2>(ssn[i]);
	}// = std::tuple<int, int, int>(istrides[i], ostrides[i], ion[i]);

	int istride = istrides.back();
	auto inembed = istrides; inembed.fill(0);
	int ostride = ostrides.back();
	auto onembed = ostrides; onembed.fill(0);
	for(std::size_t i = 1; i != onembed.size(); ++i){
		assert(ostrides[i-1] >= ostrides[i]); // otherwise ordering is incompatible
		assert(ostrides[i-1]%ostrides[i]==0);
		onembed[i]=ostrides[i-1]/ostrides[i]; //	assert( onembed[i] <= ion[i] );
		assert(istrides[i-1]%istrides[i]==0);
		inembed[i]=istrides[i-1]/istrides[i]; //	assert( inembed[i] <= ion[i] );
	}

	return ::fftw_plan_many_dft(
		/*int rank*/ ion.size(),
		/*const int* n*/ ion.data(),
		/*int howmany*/ last - first,
		/*fftw_complex * in */ reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(static_cast<std::complex<double> const*>(base(first)))),
		/*const int *inembed*/ inembed.data(),
		/*int*/ istride,
		/*int idist*/ stride(first),
		/*fftw_complex * out */ reinterpret_cast<fftw_complex*>(static_cast<std::complex<double>*>(base(d_first))),
		/*const int *onembed*/ onembed.data(),
		/*int*/ ostride,
		/*int odist*/ stride(d_first),
		/*int*/ sign, /*unsigned*/ flags
	);
}

template<class In, class Out, std::size_t D = std::decay_t<In>::dimensionality,
typename = std::enable_if_t<D == std::decay_t<Out>::dimensionality>,
typename = decltype(reinterpret_cast<fftw_complex*>(/*static_cast<std::complex<double> *>*/(base(std::declval<Out&>()))))
>
fftw_plan fftw_plan_dft(std::decay_t<std::array<bool, D>> which, In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE){
	using multi::sizes; using multi::strides; assert(sizes(in) == sizes(out));
	auto ion      = to_array<ptrdiff_t>(sizes(in));
	auto istrides = to_array<ptrdiff_t>(strides(in));
	auto ostrides = to_array<ptrdiff_t>(strides(out));
	std::array<fftw_iodim64, D> dims   ; auto l_dims = dims.begin();
	std::array<fftw_iodim64, D> howmany; auto l_howmany = howmany.begin();
	for(int i = 0; i != D; ++i)
		(which[i]?*l_dims++:*l_howmany++) = fftw_iodim64{ion[i], istrides[i], ostrides[i]};
	return fftw_plan_guru64_dft(
		/*int rank*/ sign?(l_dims - dims.begin()):0,
		/*const fftw_iodim64 *dims*/ dims.data(),
		/*int howmany_rank*/ l_howmany - howmany.begin(),
		/*const fftw_iodim *howmany_dims*/ howmany.data(), //nullptr, //howmany_dims.data(), //;//nullptr,
		/*fftw_complex *in*/ const_cast<fftw_complex*>(reinterpret_cast<fftw_complex const*>(static_cast<std::complex<double> const *>(base(in)))),
		/*fftw_complex *out*/ reinterpret_cast<fftw_complex*>(/*static_cast<std::complex<double> *>*/(base(out))),
		sign, flags// | FFTW_ESTIMATE
	);
}

template<class To, class From, std::enable_if_t<std::is_convertible<From, To>{},int> =0>
To implicit_cast(From&& f){return static_cast<To>(f);}

template<class In, class Out, dimensionality_type D = In::dimensionality, typename = decltype(reinterpret_cast<fftw_complex*>(implicit_cast<std::complex<double>*>(base(std::declval<Out&>()))))>
auto fftw_plan_dft(In const& in, Out&& out, int s, unsigned flags = FFTW_ESTIMATE){
	static_assert( D == std::decay_t<Out>::dimensionality , "!");
	using multi::sizes; using multi::strides; assert(sizes(in) == sizes(out));
	auto
		ion      = to_array<ptrdiff_t>(sizes(in)),
		istrides = to_array<ptrdiff_t>(strides(in)),
		ostrides = to_array<ptrdiff_t>(strides(out))
	;
	std::array<fftw_iodim64, D> dims;
	for(int i=0; i!=D; ++i) dims[i] = {ion[i], istrides[i], ostrides[i]};
	return fftw_plan_guru64_dft(
		/*int rank*/ s?D:0,
		/*const fftw_iodim64 *dims*/ dims.data(),
		/*int howmany_rank*/ 0,
		/*const fftw_iodim *howmany_dims*/ nullptr, //howmany_dims.data(), //;//nullptr,
		/*fftw_complex *in*/ const_cast<fftw_complex*>(reinterpret_cast<fftw_complex const*>(static_cast<std::complex<double> const*>(base(in)))),
		/*fftw_complex *out*/ reinterpret_cast<fftw_complex*>(implicit_cast<std::complex<double>*>(base(out))),
		s, flags
	);
}

//std::complex<double> const* base(std::complex<double> const& c){return &c;}

namespace fftw{

#if HAVE_FFTW3_THREADS
void initialize_threads(){int good = fftw_init_threads(); assert(good);}
#else
void initialize_threads(){}
#endif

class plan{
	plan() : impl_{nullptr, &fftw_destroy_plan}{}
	std::unique_ptr<std::remove_pointer_t<fftw_plan>, decltype(&fftw_destroy_plan)> impl_;
public:
	plan(plan const&) = delete;//default;
	plan(plan&&) = default;
	template<typename... As,
		typename = decltype(fftw_plan_dft(std::declval<As&&>()...))
	> plan(As&&... as) : impl_{fftw_plan_dft(std::forward<As>(as)...), &fftw_destroy_plan}{
		assert(impl_);
	}
	template<typename... As>
	static auto many(As&&... as)
	->std::decay_t<decltype(fftw_plan_many_dft(std::forward<As>(as)...) , std::declval<plan>())>
	{
		plan r; r.impl_.reset(fftw_plan_many_dft(std::forward<As>(as)...)); return r;
	}
private:
	void execute() const{fftw_execute(impl_.get());}
	template<class I, class O>
	void execute_dft(I&& i, O&& o) const{
		::fftw_execute_dft(impl_.get(), const_cast<fftw_complex*>(reinterpret_cast<fftw_complex const*>(static_cast<std::complex<double> const*>(base(i)))), reinterpret_cast<fftw_complex*>(static_cast<std::complex<double>*>(base(o))));
	}
	template<class I, class O> void execute(I&& i, O&& o) const{execute_dft(std::forward<I>(i), std::forward<O>(o));}
	friend void execute(plan const& p){p.execute();}
public:
	plan& operator=(plan&&) = default;
	plan& operator=(plan const&) = delete;//default;
	void operator()() const{execute();} // http://www.fftw.org/fftw3_doc/Thread-safety.html#Thread-safety
	template<class I, class O> void operator()(I&& i, O&& o) const{return execute(std::forward<I>(i), std::forward<O>(o));}
	double cost() const{return fftw_cost(impl_.get());}
	auto flops() const{
		struct{double add; double mul; double fma; operator double() const{return add + mul + 2*fma;}} r;
		fftw_flops(impl_.get(), &r.add, &r.mul, &r.fma);
		return r;
	}
#if HAVE_FFTW3_THREADS
public:
	static void make_thread_safe(){
		fftw_make_planner_thread_safe(); // needs linking to -lfftw3_threads, requires FFTW-3.3.6 or greater
		is_thread_safe_ = true;
	}
	static int with_nthreads(int n){fftw_plan_with_nthreads(n); nthreads_ = n; return n;}
	static int with_nthreads(){
		int n=std::thread::hardware_concurrency(); return with_nthreads(n?n:2);
	//	return with_nthreads(std::thread::hardware_concurrency()?:2);
	}
	static bool is_thread_safe(){return is_thread_safe_;}
	static bool nthreads(){return nthreads_;}
private:
	static bool is_thread_safe_;
	static int nthreads_;
	static bool initialized_threads_;
#else
	static constexpr bool is_thread_safe(){return false;}
	static constexpr bool nthreads(){return 1;}
	static constexpr int with_nthreads(){return 1;}
#endif
};

#if HAVE_FFTW3_THREADS
bool plan::is_thread_safe_ = (plan::make_thread_safe(), true);
int plan::nthreads_ = (initialize_threads(), with_nthreads());
#endif

//enum sign: decltype(FFTW_FORWARD){forward = FFTW_FORWARD, none = 0, backward = FFTW_BACKWARD };
using sign = int;
constexpr sign forward = FFTW_FORWARD;
constexpr sign none = 0;
constexpr sign backward = FFTW_BACKWARD;

static_assert( forward != none and none != backward and backward != forward, "!");

enum strategy: decltype(FFTW_ESTIMATE){ estimate = FFTW_ESTIMATE, measure = FFTW_MEASURE };

template<class In, class Out>
auto dft(In const& i, Out&& o, int s)
->decltype(fftw::plan{i, o, s}(), std::forward<Out>(o)){
	return fftw::plan{i, o, s}(), std::forward<Out>(o);}

template<class In, class Out, std::size_t = std::decay_t<In>::dimensionality>
Out&& transpose(In const& i, Out&& o){
	return dft(i, std::forward<Out>(o), fftw::none);
}

//template<class In, class Out>//, std::size_t D = >
//decltype(auto) dft(std::array<bool, In::dimensionality> which, In const& i, Out&& o, sign s)
//->decltype(plan{which, i, o, s}(), std::forward<Out>(o)){
//{	return plan{which, i, o, s}(), std::forward<Out>(o);}

//template<class In, class Out, std::size_t W>//class Array = std::array<bool, In::dimensionality> >//, std::size_t D = >
//decltype(auto) dft(std::array<bool, W> which, In const& i, Out&& o, sign s)
//->decltype(plan{which, i, o, s}(), std::forward<Out>(o)){
//{	return plan{which, i, o, s}(), std::forward<Out>(o);}

using std::decay_t;

template<class In, class Out, std::size_t D = In::dimensionality>
auto dft(std::array<bool, D> which, In const& i, Out&& o, sign s)
->decltype(plan{which, i, o, s}(), std::forward<Out>(o)){
	return plan{which, i, o, s}(), std::forward<Out>(o);}

/*
template<dimensionality_type R, class In, class Out, std::size_t D = std::decay_t<In>::dimensionality>
Out&& dft(In const& i, Out&& o, sign s){
	static_assert( R <= D , "dimension of transpformation cannot be larger than total dimension" );
	std::array<bool, D> which; std::fill(std::fill_n(begin(which), R, false), end(which), true);
	plan{which, i, o, s}();//(i, std::forward<Out>(o));
	return std::forward<Out>(o);
}
*/

template<typename In, class Out, std::size_t D = In::dimensionality, std::size_t = std::decay_t<Out>::dimensionality>
auto dft(std::array<sign, D> w, In const& i, Out&& o){
	std::array<bool, D> fwd, /*non,*/ bwd;

	std::transform(begin(w), end(w), begin(fwd), [](auto e){return e==FFTW_FORWARD;});
	dft(fwd, i, o, fftw::forward);

//	std::transform(begin(w), end(w), begin(non), [](auto e){return e==sign::none;});
	std::transform(begin(w), end(w), begin(bwd), [](auto e){return e==FFTW_BACKWARD;});
	if(std::accumulate(begin(bwd), end(bwd), false)) dft(bwd, o, o, FFTW_BACKWARD);

	return std::forward<Out>(o);
}

template<typename It1, typename It2>
auto many_dft(It1 first, It1 last, It2 d_first, int sign)
->decltype(plan::many(first, last, d_first, sign)(), d_first + (last - first)){
	return plan::many(first, last, d_first, sign)(), d_first + (last - first);}

template<typename In, typename R = multi::array<typename In::element_type, In::dimensionality, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when first argument is const")
auto dft(In const& i, sign s)
->std::decay_t<decltype(dft(i, R(extensions(i), get_allocator(i)), s))>{
	return dft(i, R(extensions(i), get_allocator(i)), s);}

template<typename In, typename R = multi::array<typename In::element_type, In::dimensionality, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when first argument is const")
R transpose(In const& i){
	return transpose(i, R(extensions(i), get_allocator(i)));
}

template<typename T, dimensionality_type D, class... Args>
decltype(auto) rotate(multi::array<T, D, Args...>& i, int = 1){
	multi::array_ref<T, D, typename multi::array<T, D, Args...>::element_ptr> before(data_elements(i), extensions(i));
//	std::cout << "1. "<< size(i) <<' '<< size(rotated(i)) << std::endl;
	i.reshape(extensions(rotated(before) ));
//	auto x = extensions(i);
//	std::cout << "2. "<< size(i) <<' '<< size(rotated(i)) << std::endl;
	fftw::dft(before, i, fftw::none);
//	std::cout << "3. "<< size(i) <<' '<< size(rotated(i)) << std::endl;
	return i;
//	assert( extensions(i) == x );
//	return i;
}

template<typename In, std::size_t D = In::dimensionality, typename R = multi::array<typename In::element_type, D, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when first argument is const")
auto dft(std::array<bool, D> which, In const& i, sign s)
->std::decay_t<decltype(fftw::dft(which, i, R(extensions(i), get_allocator(i)), s))>{
	return fftw::dft(which, i, R(extensions(i), get_allocator(i)), s);}

template<typename In, std::size_t D = std::decay_t<In>::dimensionality>
auto dft(std::array<bool, std::decay_t<In>::dimensionality> which, In&& i, sign s)
->decltype(dft(which, i, i, s), std::forward<In>(i)){
	return dft(which, i, i, s), std::forward<In>(i);}

/*
template<typename In, std::size_t D = In::dimensionality, typename R = multi::array<typename In::element_type, In::dimensionality, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when second argument is const")
R dft(std::array<sign, D> which, In const& i){
	return dft(which, i, R(extensions(i), get_allocator(i)));
}*/

template<typename In, std::size_t D = In::dimensionality, typename R = multi::array<typename In::element_type, D, decltype(get_allocator(std::declval<In>()))>>
void dft(std::array<bool, D> which, In const& i) = delete;

template<dimensionality_type Rank, typename In, typename R = multi::array<typename In::element_type, In::dimensionality, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when second argument is const")
R dft(In const& i, sign s){
	return dft<Rank>(i, R(extensions(i), get_allocator(i)), s);
}

/*
template<typename T, dimensionality_type D, class... As, typename R = multi::array<T, D, As...>>//typename std::decay_t<In>::element_type, std::decay_t<In>::dimensionality>>
NODISCARD("when first argument can be destroyed")
R dft(multi::array<T, D, As...>&& i, sign s){
//	R ret(extensions(i), get_allocator(i));
//	plan{i, ret, s, static_cast<unsigned>(fftw::estimate) | FFTW_DESTROY_INPUT}();//(i, ret); // to do destroy input for move iterators
	return R{std::move(dft(i, s))};
}
*/

template<typename T> decltype(auto) dft(std::initializer_list<T> il, sign s){return dft(multi::array<T, 1>(il), s);}
template<typename T> decltype(auto) dft(std::initializer_list<std::initializer_list<T>> il, sign s){return dft(multi::array<T, 2>(il), s);}

template<typename... A> auto            dft_forward(A&&... a)
->decltype(fftw::dft(std::forward<A>(a)..., fftw::forward)){
	return fftw::dft(std::forward<A>(a)..., fftw::forward);}

template<typename Array, typename A>
NODISCARD("when input argument is read only")
auto dft_forward(Array which, A const& a)
->decltype(fftw::dft(which, a, fftw::forward)){
	return fftw::dft(which, a, fftw::forward);}

template<typename Array, typename A>
NODISCARD("when input argument is read only")
auto dft_forward(A const& a)
->decltype(fftw::dft(a, fftw::forward)){
	return fftw::dft(a, fftw::forward);}

template<typename... A> auto            dft_backward(A&&... a)
->decltype(dft(std::forward<A>(a)..., fftw::backward)){
	return dft(std::forward<A>(a)..., fftw::backward);}

template<typename T, typename... As> decltype(auto) dft_forward(As&... as, std::initializer_list<T> il){return dft_forward(std::forward<As>(as)..., multi::array<T, 1>(il));}
template<typename T, typename... As> decltype(auto) dft_forward(As&... as, std::initializer_list<std::initializer_list<T>> il){return dft_forward(std::forward<As>(as)..., multi::array<T, 2>(il));}

template<typename T, typename... As> decltype(auto) dft_backward(As&... as, std::initializer_list<T> il){return dft_backward(std::forward<As>(as)..., multi::array<T, 1>(il));}
template<typename T, typename... As> decltype(auto) dft_backward(As&... as, std::initializer_list<std::initializer_list<T>> il){return dft_backward(std::forward<As>(as)..., multi::array<T, 2>(il));}

template<class In> In&& dft_inplace(In&& i, sign s){
	fftw::plan{i, i, (int)s}();//(i, i);
	return std::forward<In>(i);
}

}

namespace fft{
	using fftw::many_dft;
	using fftw::dft;
	using fftw::dft_forward;
	using fftw::dft_backward;

	static constexpr int forward = fftw::forward;//FFTW_FORWARD;
	static constexpr int none = 0;
	static constexpr int backward = fftw::backward;//FFTW_BACKWARD;

	static_assert( forward != none and none != backward and backward != forward, "!");
}

}}

#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_FFTW

#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW adaptor"
#define BOOST_TEST_DYN_LINK
#include<boost/test/unit_test.hpp>

#include <boost/timer/timer.hpp>

//#include "../adaptors/fftw/allocator.hpp"
#include<iostream>
#include "../array.hpp"
#include<complex>
#include<numeric>

#include<experimental/array>
#include<experimental/tuple>

#include<random>

#include "../adaptors/cuda.hpp"

#include<chrono>

#include "../../multi/complex.hpp"

namespace{

	using std::cout;
	namespace multi = boost::multi;
	namespace fftw = multi::fftw;

	using complex = std::complex<double>;
	complex const I{0, 1};

	template<class M>
	auto power(M const& m){
		auto sum_norm = [](auto& a, auto& b){return a + std::norm(b);};
		using multi::num_elements; using multi::data_elements; using std::accumulate;
		return accumulate(data_elements(m), data_elements(m) + num_elements(m), double{}, sum_norm);
	}

	constexpr int N = 16;
}

struct watch : private std::chrono::high_resolution_clock{
	std::string label_; time_point  start_;
	watch(std::string label ="") : label_{label}, start_{now()}{}
	~watch(){
		std::cerr<< label_<<": "<< std::chrono::duration<double>(now() - start_).count() <<" sec"<<std::endl;
	}
};

template<class T> struct randomizer{
	template<class M> void operator()(M&& m) const{for(auto&& e:m) operator()(e);}
	void operator()(T& e) const{
		static std::random_device r; static std::mt19937 g{r()}; static std::normal_distribution<T> d;
		e = d(g);
	}
};

template<class T> struct randomizer<std::complex<T>>{
	template<class M> void operator()(M&& m) const{for(auto&& e:m) operator()(e);}
	void operator()(std::complex<T>& e) const{
		static std::random_device r; static std::mt19937 g{r()}; static std::normal_distribution<T> d;
		e = std::complex<T>(d(g), d(g));
	}
};

BOOST_AUTO_TEST_CASE(fftw_2D_identity_2, *boost::unit_test::tolerance(0.0001)){
	multi::array<complex, 2> const in = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
	multi::array<complex, 2> fwd(extensions(in));
//	multi::fftw::dft({false, false}, in, fwd, multi::fftw::forward);
//	multi::fftw::dft<2>(in, fwd, multi::fftw::forward);
//	multi::fftw::dft({multi::fftw::none, multi::fftw::none}, in, fwd);
	multi::fftw::dft(in, fwd, fftw::none);
//	multi::fftw::transpose(in, fwd);
//	BOOST_REQUIRE( fwd == in );
}

BOOST_AUTO_TEST_CASE(fftw_1D){
	multi::array<complex, 1> in = {1. + 2.*I, 2. + 3. *I, 4. + 5.*I, 5. + 6.*I};
	auto fwd = multi::fftw::dft(in, fftw::forward); // Fourier[in, FourierParameters -> {1, -1}]
	BOOST_TEST( size(fwd) == size(in) );
//	auto fwd = multi::fftw::dft({multi::fftw::forward}, in); // Fourier[in, FourierParameters -> {1, -1}]
//	auto fwd = multi::fftw::dft<0>(in, multi::fftw::forward); // Fourier[in, FourierParameters -> {1, -1}]

	BOOST_REQUIRE(fwd[2] == -2. - 2.*I);
	BOOST_REQUIRE( in[1] == 2. + 3.*I );

	auto bwd = multi::fftw::dft(in, FFTW_BACKWARD); // InverseFourier[in, FourierParameters -> {-1, -1}]
	BOOST_REQUIRE(bwd[2] == -2. - 2.*I);
}
#if 1
/*
BOOST_AUTO_TEST_CASE(fftw_1D_cuda){
	multi::cuda::managed::array<complex, 1> in = {1. + 2.*I, 2. + 3. *I, 4. + 5.*I, 5. + 6.*I};
	auto fwd = multi::fftw::dft(in, multi::fftw::forward); // Fourier[in, FourierParameters -> {1, -1}]
//	auto fwd = multi::fftw::dft(in, multi::fftw::forward); // Fourier[in, FourierParameters -> {1, -1}]
//	auto fwd = multi::fftw::dft({multi::fftw::forward}, in); // Fourier[in, FourierParameters -> {1, -1}]
//	auto fwd = multi::fftw::dft<0>(in, multi::fftw::forward); // Fourier[in, FourierParameters -> {1, -1}]
	BOOST_REQUIRE(fwd[2] == -2. - 2.*I);
	BOOST_REQUIRE( in[1] == 2. + 3.*I );

	auto bwd = multi::fftw::dft(in, multi::fftw::backward); // InverseFourier[in, FourierParameters -> {-1, -1}]
	BOOST_REQUIRE(bwd[2] == -2. - 2.*I);
}
*/

BOOST_AUTO_TEST_CASE(fftw_2D_identity, *boost::unit_test::tolerance(0.0001)){
	multi::array<complex, 2> const in = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
	auto fwd = multi::fftw::dft(in, fftw::none);
//	auto fwd = multi::fftw::dft<0>(in, multi::fftw::none);
//	BOOST_REQUIRE( fwd == in );
}

BOOST_AUTO_TEST_CASE(fftw_2D, *boost::unit_test::tolerance(0.0001)){
	multi::array<complex, 2> const in = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
//	using multi::fftw::forward;
	auto fwd = multi::fftw::dft(in, fftw::forward);
//	auto fwd = multi::fftw::dft<0>(in, forward);
//	auto fwd = multi::fftw::dft({forward, forward}, in);
//	auto fwd = dft({true, true}, in, forward);

	BOOST_TEST( real(fwd[3][1]) == -19.0455 ); // Fourier[in, FourierParameters -> {1, -1}][[4]][[2]]
	BOOST_TEST( imag(fwd[3][1]) == - 2.22717 );

	multi::array<complex, 1> const in0 = { 1. + 2.*I, 9. - 1.*I, 2. + 4.*I};
	using multi::fftw::dft_forward;

	BOOST_REQUIRE( dft_forward(in[0]) == dft_forward(in0) );
//	BOOST_REQUIRE( dft_forward(in[3]) == dft_forward({3.-1.*I, 8.+7.*I, 2.+1.*I}) );
//	BOOST_REQUIRE( dft_forward(rotated(in)[0]) == dft_forward({1.+2.*I, 3.+3.*I, 4. + 1.*I,  3. - 1.*I, 31. - 1.*I}) );
}

BOOST_AUTO_TEST_CASE(fftw_2D_rotated, *boost::unit_test::tolerance(0.0001)){
	multi::array<complex, 2> const in = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
//	using multi::fftw::forward;
	auto fwd = multi::fftw::dft(in, fftw::forward);
//	auto fwd = multi::fftw::dft<0>(in, forward);
//	auto fwd = dft({true, true}, in, forward);
//	auto fwd = multi::fftw::dft({forward, forward}, in);

	using multi::fftw::dft_forward;
//	BOOST_REQUIRE( dft_forward(rotated(in)[0]) == dft_forward({1.+2.*I, 3.+3.*I, 4. + 1.*I,  3. - 1.*I, 31. - 1.*I}) );
//	BOOST_REQUIRE( dft_forward(rotated(in)) == rotated(fwd) );// rotated(fwd) );
}

BOOST_AUTO_TEST_CASE(fftw_2D_many, *boost::unit_test::tolerance(0.0001)){
	multi::array<complex, 2> const in = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
	multi::array<complex, 2> out(extensions(in));
//	multi::fftw::dft<1>(in, out, multi::fftw::forward);
//	dft({false, true}, in, out, multi::fftw::forward);
	multi::fftw::dft({fftw::none, fftw::forward}, in, out);

	using multi::fftw::dft_forward;
	BOOST_REQUIRE( dft_forward(in[0]) == out[0] );

	multi::fftw::dft({false, true}, rotated(in), rotated(out), fftw::forward);
	BOOST_REQUIRE( dft_forward(rotated(in)[0]) == rotated(out)[0] );

	multi::fftw::dft({false, false}, rotated(in), rotated(out), fftw::forward);
	BOOST_REQUIRE( in == out );

	multi::fftw::many_dft(begin(in), end(in), begin(out), fftw::forward);
	using multi::fftw::dft_forward;
	BOOST_REQUIRE( dft_forward(in[0]) == out[0] );
}

BOOST_AUTO_TEST_CASE(fftw_many1_from_2){
	multi::array<complex, 2> in({3, 10}); randomizer<complex>{}(in);
	multi::array<complex, 2> out({3, 10});
	fftw::dft({false, true}, in, out, fftw::forward);

	multi::array<complex, 2> out2({3, 10});
	for(int i = 0; i!=size(in); ++i)
		fftw::dft(in[i], out2[i], fftw::forward);

	BOOST_REQUIRE(out2 == out);
}

BOOST_AUTO_TEST_CASE(fftw_many2_from_3){
	multi::array<complex, 3> in({3, 5, 6}); randomizer<complex>{}(in);
	multi::array<complex, 3> out({3, 5, 6});
	fftw::dft({false, true, true}, in, out, FFTW_FORWARD);

	multi::array<complex, 3> out2({3, 5, 6});
	for(int i = 0; i!=size(in); ++i)
		fftw::dft(in[i], out2[i], FFTW_FORWARD);

	BOOST_REQUIRE(out2 == out);
}

BOOST_AUTO_TEST_CASE(fftw_many2_from_2){
	multi::array<complex, 2> in({5, 6}); randomizer<complex>{}(in);
	multi::array<complex, 2> out({5, 6});
	fftw::dft({true, true}, in, out, FFTW_FORWARD);

	multi::array<complex, 2> out2({5, 6});
	fftw::dft(in, out2, FFTW_FORWARD);
	BOOST_REQUIRE(out2 == out);
}

BOOST_AUTO_TEST_CASE(fftw_3D){
	multi::array<complex, 3> in({10, 10, 10});
	in[2][3][4] = 99.;
	auto fwd = multi::fftw::dft(in, fftw::forward);
	BOOST_REQUIRE(in[2][3][4] == 99.);
}

BOOST_AUTO_TEST_CASE(fftw_4D){
	multi::array<complex, 4> const in = []{
		multi::array<complex, 4> in({10, 10, 10, 10}); in[2][3][4][5] = 99.; return in;
	}();
	auto fwd = multi::fftw::dft({true, true, true, true}, in, fftw::forward);
	BOOST_REQUIRE(in[2][3][4][5] == 99.);
}

BOOST_AUTO_TEST_CASE(fftw_4D_many){

	auto const in = []{multi::array<complex, 4> in({97, 95, 101, 10}); in[2][3][4][5] = 99.; return in;}();
	auto fwd = multi::fftw::dft({true, true, true, false}, in, fftw::forward);
	BOOST_REQUIRE( in[2][3][4][5] == 99. );

	multi::array<complex, 4> out(extensions(in));
	multi::fftw::many_dft(begin(unrotated(in)), end(unrotated(in)), begin(unrotated(out)), fftw::forward);
	BOOST_REQUIRE( fwd == out );

//	multi::array<complex, 4> out2({10, 97, 95, 101});
//	multi::fftw::many_dft(begin(unrotated(in)), end(unrotated(in)), begin(out2), multi::fftw::forward);
//	BOOST_REQUIRE( fwd == rotated(out2) );

}

BOOST_AUTO_TEST_CASE(cufft_many_2D){
	auto const in = []{
		multi::array<complex, 3> ret({10, 10, 10});
		std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(),
			[](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};}
		);
		return ret;
	}();
	multi::array<complex, 3> out(extensions(in));
	multi::fftw::many_dft((in<<1).begin(), (in<<1).end(), (out<<1).begin(), multi::fftw::forward);

	multi::array<complex, 3> out2(extensions(in));
	multi::fftw::dft({true, false, true}, in, out2, multi::fftw::forward);
	BOOST_REQUIRE( out == out2 );
}

BOOST_AUTO_TEST_CASE(fftw_5D){
	multi::array<complex, 5> in({4, 5, 6, 7, 8});
	in[2][3][4][5][6] = 99.;
	auto fwd = multi::fftw::dft(in, fftw::forward);
	BOOST_REQUIRE(in[2][3][4][5][6] == 99.);

	BOOST_REQUIRE( std::get<2>(sizes(in)) == 6 );
	auto sizes_as_int = std::experimental::apply(
		[](auto... n){
			auto safe = [](auto i){assert(i<=std::numeric_limits<int>::max()); return static_cast<int>(i);};
			return std::array<int, sizeof...(n)>{safe(n)...};
		},
		sizes(in)
	);
	BOOST_REQUIRE( sizes_as_int[2] == 6 );
}

BOOST_AUTO_TEST_CASE(fftw_1D_power){
	multi::array<complex, 1> in(N, 0.); assert( size(in) == N );
	std::iota(begin(in), end(in), 1.);
	multi::array<complex, 1> out(extensions(in));
	static_assert(dimensionality(in)==dimensionality(out), "!");
	auto p = multi::fftw_plan_dft(in, out, fftw::forward, FFTW_PRESERVE_INPUT);
	fftw_execute(p);
	fftw_destroy_plan(p);
	BOOST_REQUIRE( (power(in) - power(out)/num_elements(out)) < 1e-17 );
}

/*
BOOST_AUTO_TEST_CASE(fftw_1D_allocator_power){
	using multi::fftw::allocator;
	multi::array<complex, 1, allocator<complex>> in(16, 0.); std::iota(begin(in), end(in), 1.);
	assert( size(in) == N );
	multi::array<complex, 1, allocator<complex>> out(extensions(in));
	auto p = multi::fftw_plan_dft(in, out, fftw::forward, FFTW_PRESERVE_INPUT);
	fftw_execute(p);
	fftw_destroy_plan(p);
	BOOST_REQUIRE( (power(in) - power(out)/num_elements(out)) < 1e-12 );
}
*/

BOOST_AUTO_TEST_CASE(fftw_2D_power){
	multi::array<complex, 2> in({N, N});
	std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	multi::array<complex, 2> out(extensions(in));
	auto p = multi::fftw_plan_dft(in, out, fftw::forward, FFTW_PRESERVE_INPUT);
	fftw_execute(p); fftw_destroy_plan(p);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-12 );
}

BOOST_AUTO_TEST_CASE(fftw_2D_power_plan){
	multi::array<complex, 2> in({16, 16});
	std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	multi::array<complex, 2> out(extensions(in));
	multi::fftw::plan const p{in, out, fftw::forward, FFTW_PRESERVE_INPUT};
	p(); //execute(p); //p.execute();
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-8 );
}

BOOST_AUTO_TEST_CASE(fftw_2D_power_dft){
	multi::array<complex, 2> in({16, 16}); std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	multi::array<complex, 2> out(extensions(in));
	multi::fftw::dft(in, out, fftw::forward);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-8 );
}

BOOST_AUTO_TEST_CASE(fftw_2D_power_dft_out){
	multi::array<complex, 2> in({16, 16}); std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	auto out = multi::fftw::dft(in, fftw::forward);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-8 );
}

BOOST_AUTO_TEST_CASE(fftw_2D_power_dft_out_default){
	multi::array<complex, 2> in({16, 16}); std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	auto out = multi::fftw::dft(in, fftw::forward);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-8 );
}

/*
BOOST_AUTO_TEST_CASE(fftw_2D_carray_power){
	int const N = 16;
	complex in[N][N];
	using multi::data_elements;	using multi::num_elements;
	std::iota(data_elements(in), data_elements(in) + num_elements(in), 1.2);
	complex out[N][N];
	auto p = multi::fftw_plan_dft(in, out, fftw::forward | FFTW_PRESERVE_INPUT);
	fftw_execute(p); fftw_destroy_plan(p);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-8 );
}
*/

BOOST_AUTO_TEST_CASE(fftw_3D_power){
	multi::array<complex, 3> in({4, 4, 4}); std::iota(in.data_elements(), in.data_elements() + in.num_elements(), 1.2);
	multi::array<complex, 3> out = fftw::dft(in, fftw::forward);
	BOOST_REQUIRE( std::abs(power(in) - power(out)/num_elements(out)) < 1e-10 );
}

BOOST_AUTO_TEST_CASE(fftw_3D_power_in_place){
	multi::array<complex, 3> io({4, 4, 4}); std::iota(io.data_elements(), io.data_elements() + io.num_elements(), 1.2);
	auto powerin = power(io);
	fftw::dft_inplace(io, fftw::forward);
	BOOST_REQUIRE( powerin - power(io)/num_elements(io) < 1e-10 );
}

BOOST_AUTO_TEST_CASE(fftw_3D_power_in_place_over_ref_inplace){
	multi::array<complex, 3> io({4, 4, 4}); std::iota(io.data_elements(), io.data_elements() + io.num_elements(), 1.2);
	auto powerin = power(io);
//	fftw::dft_inplace(multi::array_ref<complex, 3>(io.data(), io.extensions()), fftw::forward);
	fftw::dft_inplace(multi::array_ref<complex, 3>(data_elements(io), extensions(io)), fftw::forward);
	BOOST_REQUIRE( powerin - power(io)/num_elements(io) < 1e-10 );
}

BOOST_AUTO_TEST_CASE(fftw_3D_power_out_of_place_over_ref){
	multi::array<complex, 3> in({4, 4, 4}); std::iota(data_elements(in), data_elements(in)+num_elements(in), 1.2);
	multi::array<complex, 3> out({4, 4, 4});
	multi::array_ref<complex, 3>(data_elements(out), extensions(out)) = fftw::dft(multi::array_cref<complex, 3>(data_elements(in), extensions(in)), fftw::forward);
	BOOST_REQUIRE( power(in) - power(out)/num_elements(out) < 1e-10 );
}

BOOST_AUTO_TEST_CASE(fftw_3D_power_out_of_place_over_temporary){
	double powerin;
	auto f = [&](){
		multi::array<complex, 3> in({4, 4, 4});
		std::iota(data_elements(in), data_elements(in)+num_elements(in), 1.2);
		powerin = power(in);
		return in;
	};
	auto out = fftw::dft(f(), fftw::forward);
	BOOST_REQUIRE( std::abs(powerin - power(out)/num_elements(out)) < 1e-10 );
}

BOOST_AUTO_TEST_CASE(fftw_2D_transposition_square_inplace){
	multi::array<complex, 2> in = {
		{11., 12.},
		{21., 22.}
	};
	BOOST_REQUIRE( in[1][0] == 21. );

	multi::fftw::transpose(in, rotated(in));
//	BOOST_REQUIRE( in[1][0] == 12. );
}

BOOST_AUTO_TEST_CASE(fftw_3D_power_benchmark){
	multi::array<complex, 3> in({100, 100, 10});
	std::iota(in.data_elements(), in.data_elements() + in.num_elements(), 1.2);
	complex sum = 1.2;
	{//boost::timer::auto_cpu_timer t;
		for(int i = 0; i != 100; ++i){
			multi::array<complex, 3> out = fftw::dft({true, true, true}, in, fftw::forward);
			sum += out[0][0][0];
		}
	}
	std::cout << sum << std::endl;
}

namespace utf = boost::unit_test::framework;

BOOST_AUTO_TEST_CASE(fft_combinations, *boost::unit_test::tolerance(0.00001)){

	auto const in = []{
		multi::array<complex, 4> ret({32, 90, 98, 96});
		std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(),
			[](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};}
		);
		return ret;
	}();
	std::cout<<"memory size "<< in.num_elements()*sizeof(complex)/1e6 <<" MB\n";

	std::vector<std::array<bool, 4>> cases = {
		{false, true , true , true },
		{false, true , true , false},
		{true , false, false, false},
		{true , true , false, false},
		{false, false, true , false},
		{false, false, false, false},
	};

	using std::cout;
	for(auto c : cases){
		cout<<"case "; copy(begin(c), end(c), std::ostream_iterator<bool>{cout,", "}); cout<<"\n";
		multi::array<complex, 4> out = in;
		{
			boost::timer::auto_cpu_timer t{"cpu_oplac %ws wall, CPU (%p%)\n"};
			multi::fftw::dft_forward(c, in, out);
		}
		{
			multi::fftw::plan p(c, in, out, fftw::forward);
			boost::timer::auto_cpu_timer t{"cpu_oplac planned %ws wall, CPU (%p%)\n"};
			p();
		}
		{
			auto in_rw = in;
			boost::timer::auto_cpu_timer t{"cpu_iplac %ws wall, CPU (%p%)\n"};
			multi::fftw::dft_forward(c, in_rw);
		//	BOOST_TEST( abs( in_rw[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}
		{
			auto in_rw = in;
			multi::fftw::plan p(c, in_rw, in_rw, fftw::forward);
			boost::timer::auto_cpu_timer t{"cpu_iplac planned %ws wall, CPU (%p%)\n"};
			p();
		//	BOOST_TEST( abs( in_rw[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}
		{
			auto in_rw = in;
			multi::fftw::plan p(c, in_rw, in_rw, fftw::forward);// | FFTW_MEASURE);
			boost::timer::auto_cpu_timer t{"cpu_iplac planned measured %ws wall, CPU (%p%)\n"};
			p();
		//	BOOST_TEST( abs( in_rw[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}
		{
			boost::timer::auto_cpu_timer t{"cpu_alloc %ws wall, CPU (%p%)\n"};
			auto out_cpy = multi::fftw::dft_forward(c, in);
			BOOST_TEST( abs( out_cpy[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}
		{
			auto in_rw = in;
			boost::timer::auto_cpu_timer t{"cpu_move %ws wall, CPU (%p%)\n"};
			auto out_cpy = multi::fftw::dft_forward(c, std::move(in_rw));
			BOOST_REQUIRE( in_rw.empty() );
			BOOST_TEST( abs( out_cpy[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}
	}
}

BOOST_AUTO_TEST_CASE(fftw_4D_power_benchmark, *boost::unit_test::disabled() ){
	auto x = multi::array<complex, 4>::extensions_type({64, 128, 128, 128});
	multi::array<complex, 4> in(x);
	std::iota(in.data_elements(), in.data_elements() + in.num_elements(), 1.2);

	BOOST_REQUIRE( in[0][0][0][0] == 1.2 );
	std::array<bool, 4> c = {false, true, true, true};
	[&, _ = watch{utf::current_test_case().full_name()+" inplace FTTT"}]{
		fftw::dft(c, in, fftw::forward);
	}();
	[&, _ = watch{utf::current_test_case().full_name()+" inplace FTTT"}]{
		fftw::dft(c, in, fftw::forward);
	}();
	auto in0000 = in[0][0][0][0];
	BOOST_REQUIRE( in0000 != 1.2 );

	multi::array<complex, 4> out(x);
	[&, _ = watch{utf::current_test_case().full_name()+" outofplace FTTT"}]{
		fftw::dft(c, in, out, fftw::forward);
	}();
	[&, _ = watch{utf::current_test_case().full_name()+" outofplace FTTT"}]{
		fftw::dft(c, in, out, fftw::forward);
	}();
	[&, _ = watch{utf::current_test_case().full_name()+" outofplace FTTT"}]{
		fftw::dft(c, in, out, fftw::forward);
	}();
	[&, _ = watch{utf::current_test_case().full_name()+" outofplace+alloc FTTT"}]{
		multi::array<complex, 4> out2(x);
		fftw::dft(c, in, out2, fftw::forward);
	}();
	[&, _ = watch{utf::current_test_case().full_name()+" outofplace+alloc FTTT"}]{
		multi::array<complex, 4> out2(x);
		fftw::dft(c, in, out2, fftw::forward);
	}();
	BOOST_REQUIRE( in0000 == in[0][0][0][0] );
}

#endif
#endif
#endif


// 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/contains.hpp>
#include <boost/hana/core/make.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/not.hpp>
#include <boost/hana/range.hpp>

#include <support/cnumeric.hpp>
namespace hana = boost::hana;

int main() {
    BOOST_HANA_CONSTANT_CHECK(hana::not_(hana::contains(
        hana::make_range(hana::int_c<0>, hana::int_c<0>),
        cnumeric<int, 0>
    )));

    BOOST_HANA_CONSTANT_CHECK(hana::contains(
        hana::make_range(hana::int_c<0>, hana::int_c<1>),
        cnumeric<int, 0>
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::contains(
        hana::make_range(hana::int_c<0>, hana::int_c<10>),
        cnumeric<int, 3>
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::contains(
        hana::make_range(hana::int_c<0>, hana::int_c<10>),
        cnumeric<int, 9>
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::contains(
        hana::make_range(hana::int_c<-10>, hana::int_c<10>),
        cnumeric<int, -10>
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::contains(
        hana::make_range(hana::int_c<-10>, hana::int_c<10>),
        cnumeric<int, -5>
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::not_(hana::contains(
        hana::make_range(hana::int_c<-10>, hana::int_c<0>),
        cnumeric<int, 3>
    )));

    BOOST_HANA_CONSTANT_CHECK(hana::not_(hana::contains(
        hana::make_range(hana::int_c<0>, hana::int_c<10>),
        cnumeric<int, 15>
    )));
}


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Modules/Math/ConvertRealToComplexMatrix.h>
#include <Modules/Math/ConvertComplexToRealMatrix.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/DenseColumnMatrix.h>
#include <Core/Datatypes/SparseRowMatrix.h>
#include <Testing/ModuleTestBase/ModuleTestBase.h>
#include <Eigen/Dense>

using namespace SCIRun;
using namespace SCIRun::Testing;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Modules::Math;
using namespace SCIRun::Dataflow::Networks;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::DefaultValue;
using ::testing::Return;

class ConvertComplexToRealMatrixModuleTests : public ModuleTest
{

};

TEST_F(ConvertComplexToRealMatrixModuleTests, ThrowsForNullFieldInput)
{
  auto csdf = makeModule("ConvertComplexToRealMatrix");
  FieldHandle nullField;
  stubPortNWithThisData(csdf, 0, nullField);
  EXPECT_THROW(csdf->execute(), NullHandleOnPortException);
}

TEST_F(ConvertComplexToRealMatrixModuleTests, ThrowsForNullMatrixInput)
{
  auto csdf = makeModule("ConvertComplexToRealMatrix");
  MatrixHandle nullMatrix;
  stubPortNWithThisData(csdf, 0, nullMatrix);
  EXPECT_THROW(csdf->execute(), NullHandleOnPortException);
}

TEST_F(ConvertComplexToRealMatrixModuleTests, SparseMatrixOnInputPort)
{
  SparseRowMatrixHandle m(boost::make_shared<SparseRowMatrix>(3,3));
  m->insert(0,0) = 1;
  m->insert(1,1) = 2;
  m->insert(2,2) = 3;
  m->makeCompressed();
  auto csdf = makeModule("ConvertComplexToRealMatrix");
  stubPortNWithThisData(csdf, 0, m);
  EXPECT_THROW(csdf->execute(), WrongDatatypeOnPortException);
}

TEST_F(ConvertComplexToRealMatrixModuleTests, DenseMatrixOnInputPort)
{
  DenseColumnMatrixHandle m(boost::make_shared<DenseColumnMatrix>(3));
  (*m)(0) = 1;
  (*m)(1) = 2;
  (*m)(2) = 3;
  auto csdf = makeModule("ConvertComplexToRealMatrix");
  stubPortNWithThisData(csdf, 0, m);
  EXPECT_THROW(csdf->execute(), WrongDatatypeOnPortException);
}

TEST_F(ConvertComplexToRealMatrixModuleTests, ColumnMatrixOnInputPort)
{
  DenseMatrixHandle m(boost::make_shared<DenseMatrix>(3,1));
  (*m)(0,0) = 1;
  (*m)(1,0) = 2;
  (*m)(2,0) = 3;
  auto csdf = makeModule("ConvertComplexToRealMatrix");
  stubPortNWithThisData(csdf, 0, m);
  EXPECT_THROW(csdf->execute(), WrongDatatypeOnPortException);
}

TEST_F(ConvertComplexToRealMatrixModuleTests, ThisShouldWork_dense)
{
  DenseMatrixHandle m(boost::make_shared<DenseMatrix>(2,2));
  (*m)(0,0) = 1;
  (*m)(0,1) = 2;
  (*m)(1,0) = 3;
  (*m)(1,1) = 4;
  DenseMatrixHandle n(boost::make_shared<DenseMatrix>(2,2));
  (*n)(0,0) = -1;
  (*n)(0,1) = -2;
  (*n)(1,0) = -3;
  (*n)(1,1) = -4;
  auto real2complex = makeModule("ConvertRealToComplexMatrix");
  stubPortNWithThisData(real2complex, 0, m);
  stubPortNWithThisData(real2complex, 1, n);
  EXPECT_NO_THROW(real2complex->execute());
  auto real2complex_output = getDataOnThisOutputPort(real2complex, 0);

  ASSERT_TRUE(real2complex_output != nullptr);
  auto  real2complex_result = boost::dynamic_pointer_cast<ComplexDenseMatrix>(real2complex_output);

  auto complex2real = makeModule("ConvertComplexToRealMatrix");
  stubPortNWithThisData(complex2real, 0, real2complex_result);
  connectDummyOutputConnection(complex2real, 0);
  connectDummyOutputConnection(complex2real, 1);
  EXPECT_NO_THROW(complex2real->execute());
  auto complex2real_output1 = getDataOnThisOutputPort(complex2real, 0);
  auto complex2real_output2 = getDataOnThisOutputPort(complex2real, 1);

  ASSERT_TRUE(complex2real_output1 != nullptr);
  ASSERT_TRUE(complex2real_output2 != nullptr);

  auto  complex2real_result1 = boost::dynamic_pointer_cast<DenseMatrix>(complex2real_output1);
  auto  complex2real_result2 = boost::dynamic_pointer_cast<DenseMatrix>(complex2real_output2);

  for(int x=0; x<complex2real_result1->nrows(); x++)
   for (int y=0; y<complex2real_result1->ncols(); y++)
    {
     ASSERT_TRUE((*complex2real_result1)(x,y) == (*m)(x,y));
     ASSERT_TRUE((*complex2real_result2)(x,y) == (*n)(x,y));
    }
}

TEST_F(ConvertComplexToRealMatrixModuleTests, ThisShouldWork_sparse)
{
  auto mat_dim=3;
  auto m(boost::make_shared<SparseRowMatrix>(mat_dim, mat_dim));
  m->insert(0, 0) = 1;
  m->insert(0, 2) = 1;
  m->insert(1, 0) = 3;
  m->insert(1, 1) = 5;
  m->insert(2, 1) = 1;
  m->insert(2, 2) = 3;
  m->makeCompressed();
  auto n(boost::make_shared<SparseRowMatrix>(mat_dim, mat_dim));
  n->insert(0, 1) = 1;
  n->insert(0, 2) = 2;
  n->insert(1, 0) = 8;
  n->insert(1, 2) = -3;
  n->insert(2, 0) = 9;
  n->insert(2, 2) = 8;
  n->makeCompressed();
  auto real2complex = makeModule("ConvertRealToComplexMatrix");
  stubPortNWithThisData(real2complex, 0, m);
  stubPortNWithThisData(real2complex, 1, n);
  EXPECT_NO_THROW(real2complex->execute());
  auto real2complex_output = getDataOnThisOutputPort(real2complex, 0);
  ASSERT_TRUE(real2complex_output != nullptr);
  auto  real2complex_result = boost::dynamic_pointer_cast<ComplexSparseRowMatrix>(real2complex_output);

  auto complex2real = makeModule("ConvertComplexToRealMatrix");
  stubPortNWithThisData(complex2real, 0, real2complex_result);
  connectDummyOutputConnection(complex2real, 0);
  connectDummyOutputConnection(complex2real, 1);
  EXPECT_NO_THROW(complex2real->execute());

  auto complex2real_output1 = getDataOnThisOutputPort(complex2real, 0);
  auto complex2real_output2 = getDataOnThisOutputPort(complex2real, 1);

  ASSERT_TRUE(complex2real_output1 != nullptr);
  ASSERT_TRUE(complex2real_output2 != nullptr);

  auto  complex2real_result1 = boost::dynamic_pointer_cast<SparseRowMatrix>(complex2real_output1);
  auto  complex2real_result2 = boost::dynamic_pointer_cast<SparseRowMatrix>(complex2real_output2);

  for (int k = 0; k <complex2real_result1 ->outerSize(); ++k)
  {
    for (SparseRowMatrix::InnerIterator it(*complex2real_result1, k); it; ++it)
    {
      auto tmp = it.value();
      ASSERT_TRUE(m->coeffRef(it.row(), it.col()) == tmp);
    }
  }

  for (int k = 0; k <complex2real_result2 ->outerSize(); ++k)
  {
    for (SparseRowMatrix::InnerIterator it(*complex2real_result2, k); it; ++it)
    {
      auto tmp = it.value();
      ASSERT_TRUE(n->coeffRef(it.row(), it.col()) == tmp);

    }
  }

}


#include <iostream>
#include <boost/multi_array.hpp>
#include <vector>
#include "settings.h"
#include "atom.h"
#include "bin.h"
#include "functions.h"
#include "grid_info.h"
#include "simulation_cell.h"
#include "output_analysis_info.h"
#include "output_surface_atoms.h"
#include "output_voids.h"

using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::vector;

typedef boost::multi_array<bin, 3> grid_bin_type;
typedef grid_bin_type::index bin_index;

int main(int argc, char** argv) {
	//const settings config("./test/first.xyz");
	//simulation_cell sc(228.520769 - 12.904845, 77.119013 - 2.776971, 70.576620 - 9.413978,
	//	12.904845, 2.776971, 9.413978);
	//settings config("./test/core.xyz");
	//simulation_cell sc(181.5, 181.5, 28, 0, 0, -12.25);

	auto parsed_options = parse(argc, argv);
	auto sc_ptr = std::make_shared<simulation_cell>(parsed_options);
	settings config(parsed_options, *sc_ptr);

	grid_info gr_nf(config, *sc_ptr);
	// Create bin array
	const auto [nx, ny, nz] = gr_nf.get_grid_no();
	grid_bin_type grid_bins(boost::extents[nx][ny][nz]);
	for (bin_index i = 0; i != nx; ++i)
		for (bin_index j = 0; j != ny; ++j)
			for (bin_index k = 0; k != nz; ++k) { grid_bins[i][j][k].set_bin(i, j, k, gr_nf.get_grid_size()); }

	vector<atom> vec_atoms;
	read_xyz_file(vec_atoms, grid_bins, config.get_path_structure_input(), *sc_ptr, gr_nf, cout);
	vector<const bin*> empty_bins = check_empty_bin(grid_bins, config);
	auto bin_voids_ptr = find_voids(empty_bins, gr_nf, config);
	filter_bins(bin_voids_ptr, config, gr_nf);
	auto bin_voids_surface_ptr = find_voids_sruface(bin_voids_ptr, grid_bins, gr_nf, config);
	output_voids op_v(config.get_filename_voids_position_xyz(), sc_ptr);
	op_v.write_xyz(bin_voids_ptr);
	output_surface_atoms op_s(config.get_filename_voids_surface_position_xyz(), sc_ptr);
	op_s.write_xyz(bin_voids_surface_ptr);
	output_analysis_info op_a(config.get_filename_voids_analysis_info(), sc_ptr, gr_nf);
	op_a.write_info(bin_voids_ptr, bin_voids_surface_ptr);
	//write_xyz_voids_surface_atom(bin_voids_surface, sc, config);
	//write_xyz_voids(bin_voids, sc, config);
	return 0;
}


#include <boost/smart_ptr/shared_ptr.hpp>
#include <caffe/blob.hpp>
#include <caffe/common.hpp>
#include <caffe/filler.hpp>
#include <caffe/layers/gauss_conv_layer.hpp>
#include <caffe/proto/caffe.pb.h>
#include <caffe/test/test_caffe_main.hpp>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <vector>

namespace caffe {

template <typename Dtype>
void compare_blobs(Blob<Dtype>& a, Blob<Dtype>& b, bool compare_diff, Dtype eps) {
	Dtype* data_a = compare_diff ? a.mutable_cpu_diff() : a.mutable_cpu_data();
	Dtype* data_b = compare_diff ? b.mutable_cpu_diff() : b.mutable_cpu_data();
	for (int i = 0; i < a.count(); ++i) {
		EXPECT_NEAR(data_a[i], data_b[i], eps);
	}
}

// Reference convolution for checking results:
// accumulate through explicit loops over input, output, and filters.
template <typename Dtype>
void caffe_conv(const Blob<Dtype>* in, ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<Dtype> > >& weights,
    Blob<Dtype>* out) {
  const bool has_depth = (out->num_axes() == 5);
  if (!has_depth) { CHECK_EQ(4, out->num_axes()); }
  // Kernel size, stride, and pad
  int kernel_h, kernel_w;
  if (conv_param->has_kernel_h() || conv_param->has_kernel_w()) {
    kernel_h = conv_param->kernel_h();
    kernel_w = conv_param->kernel_w();
  } else {
    kernel_h = kernel_w = conv_param->kernel_size(0);
  }
  int pad_h, pad_w;
  if (conv_param->has_pad_h() || conv_param->has_pad_w()) {
    pad_h = conv_param->pad_h();
    pad_w = conv_param->pad_w();
  } else {
    pad_h = pad_w = conv_param->pad_size() ? conv_param->pad(0) : 0;
  }
  int stride_h, stride_w;
  if (conv_param->has_stride_h() || conv_param->has_stride_w()) {
    stride_h = conv_param->stride_h();
    stride_w = conv_param->stride_w();
  } else {
    stride_h = stride_w = conv_param->stride_size() ? conv_param->stride(0) : 1;
  }
  int kernel_d, pad_d, stride_d;
  if (has_depth) {
    kernel_d = kernel_h;
    stride_d = stride_h;
    pad_d = pad_h;
  } else {
    kernel_d = stride_d = 1;
    pad_d = 0;
  }
  // Groups
  int groups = conv_param->group();
  int o_g = out->shape(1) / groups;
  int k_g = in->shape(1) / groups;
  int o_head, k_head;
  // Convolution
  vector<int> weight_offset(4 + has_depth);
  vector<int> in_offset(4 + has_depth);
  vector<int> out_offset(4 + has_depth);
  Dtype* out_data = out->mutable_cpu_data();
  for (int n = 0; n < out->shape(0); n++) {
    for (int g = 0; g < groups; g++) {
      o_head = o_g * g;
      k_head = k_g * g;
      for (int o = 0; o < o_g; o++) {
        for (int k = 0; k < k_g; k++) {
          for (int z = 0; z < (has_depth ? out->shape(2) : 1); z++) {
            for (int y = 0; y < out->shape(2 + has_depth); y++) {
              for (int x = 0; x < out->shape(3 + has_depth); x++) {
                for (int r = 0; r < kernel_d; r++) {
                  for (int p = 0; p < kernel_h; p++) {
                    for (int q = 0; q < kernel_w; q++) {
                      int in_z = z * stride_d - pad_d + r;
                      int in_y = y * stride_h - pad_h + p;
                      int in_x = x * stride_w - pad_w + q;
                      if (in_z >= 0 && in_z < (has_depth ? in->shape(2) : 1)
                          && in_y >= 0 && in_y < in->shape(2 + has_depth)
                          && in_x >= 0 && in_x < in->shape(3 + has_depth)) {
                        weight_offset[0] = o + o_head;
                        weight_offset[1] = k;
                        if (has_depth) { weight_offset[2] = r; }
                        weight_offset[2 + has_depth] = p;
                        weight_offset[3 + has_depth] = q;
                        in_offset[0] = n;
                        in_offset[1] = k + k_head;
                        if (has_depth) { in_offset[2] = in_z; }
                        in_offset[2 + has_depth] = in_y;
                        in_offset[3 + has_depth] = in_x;
                        out_offset[0] = n;
                        out_offset[1] = o + o_head;
                        if (has_depth) { out_offset[2] = z; }
                        out_offset[2 + has_depth] = y;
                        out_offset[3 + has_depth] = x;
                        out_data[out->offset(out_offset)] +=
                            in->data_at(in_offset)
                            * weights[0]->data_at(weight_offset);
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  // Bias
  if (conv_param->bias_term()) {
    const Dtype* bias_data = weights[1]->cpu_data();
    for (int n = 0; n < out->shape(0); n++) {
      for (int o = 0; o < out->shape(1); o++) {
        for (int z = 0; z < (has_depth ? out->shape(2) : 1); z++) {
          for (int y = 0; y < out->shape(2 + has_depth); y++) {
            for (int x = 0; x < out->shape(3 + has_depth); x++) {
              out_offset[0] = n;
              out_offset[1] = o;
              if (has_depth) { out_offset[2] = z; }
              out_offset[2 + has_depth] = y;
              out_offset[3 + has_depth] = x;
              out_data[out->offset(out_offset)] += bias_data[o];
            }
          }
        }
      }
    }
  }
}

template void caffe_conv(const Blob<float>* in,
    ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<float> > >& weights,
    Blob<float>* out);
template void caffe_conv(const Blob<double>* in,
    ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<double> > >& weights,
    Blob<double>* out);

template <typename TypeParam>
class GaussConvolutionLayerTest : public MultiDeviceTest<TypeParam> {
  typedef typename TypeParam::Dtype Dtype;

 protected:
  GaussConvolutionLayerTest()
      : blob_bottom_(new Blob<Dtype>(2, 3, 6, 4)),
        blob_bottom_2_(new Blob<Dtype>(2, 3, 6, 4)),
		blob_bottom_3_(new Blob<Dtype>(1, 1, 4, 4)),
		//blob_bottom_3_(new Blob<Dtype>(2, 3, 32, 48)),
        blob_top_(new Blob<Dtype>()),
		blob_top_2_(new Blob<Dtype>()),
		blob_top_3_(new Blob<Dtype>()) {}
  virtual void SetUp() {
    // fill the values
    FillerParameter filler_param;
    filler_param.set_value(1.);
    GaussianFiller<Dtype> filler(filler_param);
    filler.Fill(this->blob_bottom_);
    filler.Fill(this->blob_bottom_2_);
    filler.Fill(this->blob_bottom_3_);
    blob_bottom_vec_.push_back(blob_bottom_);
    blob_top_vec_.push_back(blob_top_);
  }

  virtual ~GaussConvolutionLayerTest() {
    delete blob_bottom_;
    delete blob_bottom_2_;
    delete blob_bottom_3_;
    delete blob_top_;
    delete blob_top_2_;
    delete blob_top_3_;
  }

  virtual Blob<Dtype>* MakeReferenceTop(Blob<Dtype>* top) {
    this->ref_blob_top_.reset(new Blob<Dtype>());
    this->ref_blob_top_->ReshapeLike(*top);
    return this->ref_blob_top_.get();
  }

  Blob<Dtype>* const blob_bottom_;
  Blob<Dtype>* const blob_bottom_2_;
  Blob<Dtype>* const blob_bottom_3_;
  Blob<Dtype>* const blob_top_;
  Blob<Dtype>* const blob_top_2_;
  Blob<Dtype>* const blob_top_3_;
  shared_ptr<Blob<Dtype> > ref_blob_top_;
  vector<Blob<Dtype>*> blob_bottom_vec_;
  vector<Blob<Dtype>*> blob_top_vec_;
};

TYPED_TEST_CASE(GaussConvolutionLayerTest, TestDtypesAndDevices);

TYPED_TEST(GaussConvolutionLayerTest, TestSetup) {
  /*typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(4);
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);
  shared_ptr<Layer<Dtype> > layer(
      new GaussianConvLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  EXPECT_EQ(this->blob_top_->num(), 2);
  EXPECT_EQ(this->blob_top_->channels(), 4);
  EXPECT_EQ(this->blob_top_->height(), 2);
  EXPECT_EQ(this->blob_top_->width(), 1);
  EXPECT_EQ(this->blob_top_2_->num(), 2);
  EXPECT_EQ(this->blob_top_2_->channels(), 4);
  EXPECT_EQ(this->blob_top_2_->height(), 2);
  EXPECT_EQ(this->blob_top_2_->width(), 1);
  // setting group should not change the shape
  convolution_param->set_num_output(3);
  convolution_param->set_group(3);
  layer.reset(new ConvolutionLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  EXPECT_EQ(this->blob_top_->num(), 2);
  EXPECT_EQ(this->blob_top_->channels(), 3);
  EXPECT_EQ(this->blob_top_->height(), 2);
  EXPECT_EQ(this->blob_top_->width(), 1);
  EXPECT_EQ(this->blob_top_2_->num(), 2);
  EXPECT_EQ(this->blob_top_2_->channels(), 3);
  EXPECT_EQ(this->blob_top_2_->height(), 2);
  EXPECT_EQ(this->blob_top_2_->width(), 1);*/
}
/*
TYPED_TEST(GaussConvolutionLayerTest, TestSimpleConvolution) {
  typedef typename TypeParam::Dtype Dtype;
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();

  convolution_param->add_kernel_size(7);
  convolution_param->add_stride(1);
  convolution_param->add_pad(3);

  convolution_param->set_number_gauss(2);

  convolution_param->set_num_output(16);

  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_weight_filler()->set_std(0.01);

  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);

  convolution_param->mutable_sigma_filler()->set_type("constant");
  convolution_param->mutable_sigma_filler()->set_value(0.8);

  convolution_param->set_gmm_component_border_bound(1.5);
  convolution_param->set_gmm_sigma_lower_bound(0.5);

  convolution_param->set_gmm_weight_normalization(false);
  convolution_param->set_gmm_gauss_normalization(true);
  convolution_param->set_gmm_square_gauss_normalization(true);

  shared_ptr<Layer<Dtype> > layer(
      new GaussianConvLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  layer->Forward(this->blob_bottom_vec_, this->blob_top_vec_);
  // Check against reference convolution.
  const Dtype* top_data;
  const Dtype* ref_top_data;
  caffe_conv(this->blob_bottom_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_));
  top_data = this->blob_top_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
  caffe_conv(this->blob_bottom_2_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_2_));
  top_data = this->blob_top_2_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
}
*/

TYPED_TEST(GaussConvolutionLayerTest, TestKernelPrecompute) {
  typedef typename TypeParam::Dtype Dtype;
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);

  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
	  layer_param.mutable_convolution_param();

  convolution_param->add_kernel_size(7);
  convolution_param->add_stride(1);
  convolution_param->add_pad(3);

  convolution_param->set_number_gauss(2);

  convolution_param->set_num_output(32);

  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_weight_filler()->set_std(0.01);

  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);

  convolution_param->mutable_sigma_filler()->set_type("constant");
  convolution_param->mutable_sigma_filler()->set_value(0.8);

  convolution_param->set_gmm_component_border_bound(1.5);
  convolution_param->set_gmm_sigma_lower_bound(0.5);

  convolution_param->set_gmm_weight_normalization(false);
  convolution_param->set_gmm_gauss_normalization(true);
  for (int gmm_sqrt_norm = 0; gmm_sqrt_norm <= 1; gmm_sqrt_norm++) {
	convolution_param->set_gmm_square_gauss_normalization((bool)gmm_sqrt_norm);

	shared_ptr<GaussianConvLayer<Dtype> > layer(new GaussianConvLayer<Dtype>(layer_param));
	layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);

	// RUN CPU version
	layer->precompute_guassian_weights(true); // run CPU first since it will force buffers to cpu and would cause errors if gpu would be allocated first

	// store output

	Blob<Dtype> weight_cpu, deriv_error_cpu, deriv_weight_cpu, deriv_mu1_cpu, deriv_mu2_cpu, deriv_sigma_cpu;

	weight_cpu.CopyFrom(*layer->weight_buffer_, false, true);

	deriv_error_cpu.CopyFrom(*layer->deriv_error_buffer_, false, true);
	deriv_weight_cpu.CopyFrom(*layer->deriv_weight_buffer_, false, true);
	deriv_mu1_cpu.CopyFrom(*layer->deriv_mu1_buffer_, false, true);
	deriv_mu2_cpu.CopyFrom(*layer->deriv_mu2_buffer_, false, true);
	deriv_sigma_cpu.CopyFrom(*layer->deriv_sigma_buffer_, false, true);

	// RUN CPU version
	layer->precompute_guassian_weights_gpu(true);

	// Check both versions
	Dtype* data_gpu;
	Dtype* data_cpu;

	data_cpu = weight_cpu.mutable_cpu_data();
	data_gpu = layer->weight_buffer_->mutable_cpu_data();
	for (int i = 0; i < weight_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }

	data_cpu = deriv_error_cpu.mutable_cpu_data();
	data_gpu = layer->deriv_error_buffer_->mutable_cpu_data();
	for (int i = 0; i < deriv_error_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }

	data_cpu = deriv_weight_cpu.mutable_cpu_data();
	data_gpu = layer->deriv_weight_buffer_->mutable_cpu_data();
	for (int i = 0; i < deriv_weight_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }

	data_cpu = deriv_mu1_cpu.mutable_cpu_data();
	data_gpu = layer->deriv_mu1_buffer_->mutable_cpu_data();
	for (int i = 0; i < deriv_mu1_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }

	data_cpu = deriv_mu2_cpu.mutable_cpu_data();
	data_gpu = layer->deriv_mu2_buffer_->mutable_cpu_data();
	for (int i = 0; i < deriv_mu2_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }

	data_cpu = deriv_sigma_cpu.mutable_cpu_data();
	data_gpu = layer->deriv_sigma_buffer_->mutable_cpu_data();
	for (int i = 0; i < deriv_sigma_cpu.count(); ++i) { EXPECT_NEAR(data_gpu[i], data_cpu[i], 1e-4); }
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestSeperableConvolution) {

 if (Caffe::mode() == Caffe::GPU)
	 return;
  typedef typename TypeParam::Dtype Dtype;
  vector<Blob<Dtype>*> blob_bottom_vec_;
  vector<Blob<Dtype>*> blob_top_vec_;

  blob_bottom_vec_.push_back(this->blob_bottom_3_);
  blob_top_vec_.push_back(this->blob_top_3_);

  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
	  layer_param.mutable_convolution_param();

  convolution_param->add_kernel_size(7);
  convolution_param->add_stride(1);
  convolution_param->add_pad(3);

  convolution_param->set_number_gauss(2);

  convolution_param->set_num_output(16);

  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_weight_filler()->set_std(0.01);

  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);

  convolution_param->mutable_sigma_filler()->set_type("constant");
  convolution_param->mutable_sigma_filler()->set_value(0.8);

  convolution_param->set_gmm_component_border_bound(1.5);
  convolution_param->set_gmm_sigma_lower_bound(0.5);

  convolution_param->set_gmm_weight_normalization(false);
  convolution_param->set_gmm_gauss_normalization(true);

  for (int gmm_sqrt_norm = 0; gmm_sqrt_norm < 1; gmm_sqrt_norm++) {
	convolution_param->set_gmm_square_gauss_normalization((bool)gmm_sqrt_norm);

	convolution_param->set_gmm_seperable_forward_pass(false);
	shared_ptr<GaussianConvLayer<Dtype> > layer(new GaussianConvLayer<Dtype>(layer_param));

	layer->SetUp(blob_bottom_vec_, blob_top_vec_);

	// RUN non-seperable
	layer->Forward(blob_bottom_vec_, blob_top_vec_);

	// store output
	Blob<Dtype> top_org;

	top_org.CopyFrom(*blob_top_vec_[0], false, true);

	layer->use_gmm_seperable_kernels = true;

	// RUN seperable version
	layer->Forward(blob_bottom_vec_, blob_top_vec_);

	// Check both versions
	Dtype* data_non_sep = top_org.mutable_cpu_data();
	Dtype* data_sep = blob_top_vec_[0]->mutable_cpu_data();
	for (int i = 0; i < top_org.count(); ++i) { EXPECT_NEAR(data_non_sep[i], data_sep[i], 1e-4); }

	Dtype* data_diff = top_org.mutable_cpu_diff();
	caffe_sub(top_org.count(), data_non_sep, data_sep, data_diff);
	Dtype mean_error = caffe_cpu_asum(top_org.count(), data_diff) /top_org.count();
	EXPECT_NEAR(mean_error, 0, 1e-4);
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestCuDNNConvolution) {

 if (Caffe::mode() == Caffe::CPU)
	 return;

  typedef typename TypeParam::Dtype Dtype;

  vector<Blob<Dtype>*> blob_bottom_vec_;
  vector<Blob<Dtype>*> blob_top_vec_;

  vector<Blob<Dtype>*> blob_bottom_vec_org_;
  vector<Blob<Dtype>*> blob_top_vec_org_;

  vector<bool> propagate_down;

  if (sizeof(Dtype) > 4)
	  return;

  propagate_down.push_back(true);
  blob_bottom_vec_.push_back(this->blob_bottom_3_);
  blob_top_vec_.push_back(this->blob_top_3_);

  FillerParameter filler_param;
  filler_param.set_value(0.1);

  Blob<Dtype>* blob_top_diff = new Blob<Dtype>();

  Blob<Dtype>* blob_bottom_3_org_ = new Blob<Dtype>();
  Blob<Dtype>* blob_top_3_org_ = new Blob<Dtype>();

  blob_bottom_3_org_->CopyFrom(*this->blob_bottom_3_, false, true);

  blob_bottom_vec_org_.push_back(blob_bottom_3_org_);
  blob_top_vec_org_.push_back(blob_top_3_org_);

  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
	  layer_param.mutable_convolution_param();

  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(1);
  convolution_param->add_pad(1);

  convolution_param->set_number_gauss(2);

  convolution_param->set_num_output(8);

  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_weight_filler()->set_std(0.01);

  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);

  convolution_param->mutable_sigma_filler()->set_type("constant");
  convolution_param->mutable_sigma_filler()->set_value(0.8);

  convolution_param->set_gmm_component_border_bound(1.5);
  convolution_param->set_gmm_sigma_lower_bound(0.5);

  convolution_param->set_gmm_weight_normalization(false);
  convolution_param->set_gmm_gauss_normalization(true);

  for (int gmm_sqrt_norm = 0; gmm_sqrt_norm < 1; gmm_sqrt_norm++) {
	  convolution_param->set_gmm_square_gauss_normalization((bool)gmm_sqrt_norm);

	shared_ptr<BaseGaussianConvLayer<Dtype> > layer(new CuDNNGaussianConvLayer<Dtype>(layer_param));
	shared_ptr<BaseGaussianConvLayer<Dtype> > layer_org(new GaussianConvLayer<Dtype>(layer_param));

	layer->SetUp(blob_bottom_vec_, blob_top_vec_);
	layer_org->SetUp(blob_bottom_vec_org_, blob_top_vec_org_);

	layer_org->param_buffer_w_->CopyFrom(*layer->param_buffer_w_, false, false);
	layer_org->param_buffer_mu1_->CopyFrom(*layer->param_buffer_mu1_, false, false);
	layer_org->param_buffer_mu2_->CopyFrom(*layer->param_buffer_mu2_, false, false);
	layer_org->param_buffer_sigma_->CopyFrom(*layer->param_buffer_sigma_, false, false);
	layer_org->param_buffer_bias_->CopyFrom(*layer->param_buffer_bias_, false, false);

	// RUN forward
	layer->Forward(blob_bottom_vec_, blob_top_vec_);

	layer_org->Forward(blob_bottom_vec_org_, blob_top_vec_org_);

	blob_top_diff->ReshapeLike(*blob_top_vec_[0]);
	GaussianFiller<Dtype> filler(filler_param);
	filler.Fill(blob_top_diff);

	caffe_copy( blob_top_vec_[0]->count(), blob_top_diff->cpu_data(), blob_top_vec_[0]->mutable_cpu_diff());

	blob_top_vec_[0]->cpu_data();
	blob_top_vec_[0]->gpu_diff();

	blob_top_vec_org_[0]->CopyFrom(*blob_top_vec_[0], true, true);

//	blob_top_vec_org_[0]->cpu_diff();

	const Dtype* gpu_data = blob_top_vec_[0]->cpu_data();
	const Dtype* cpu_data = blob_top_vec_org_[0]->cpu_data();
	for (int i = 0; i < blob_top_vec_[0]->count(); ++i) { EXPECT_NEAR(gpu_data[i], cpu_data[i], 1e-4); }

	// set all back-propagated error values to 1 to ease debugging
//	caffe_gpu_set(blob_top_vec_org_[0]->count(), (Dtype)3.0, blob_top_vec_org_[0]->mutable_gpu_diff());
//	caffe_gpu_set(blob_top_vec_[0]->count(), (Dtype)3.0, blob_top_vec_[0]->mutable_gpu_diff());

//	caffe_gpu_set(blob_bottom_vec_org_[0]->count(), (Dtype)2.0, blob_bottom_vec_org_[0]->mutable_gpu_data());
//	caffe_gpu_set(blob_bottom_vec_[0]->count(), (Dtype)2.0, blob_bottom_vec_[0]->mutable_gpu_data());

	// RUN backward

	layer->Backward(blob_top_vec_, propagate_down, blob_bottom_vec_);

	layer_org->Backward(blob_top_vec_org_, propagate_down, blob_bottom_vec_org_);

	const Dtype* gpu_diff = blob_bottom_vec_[0]->cpu_diff();
	const Dtype* cpu_diff = blob_bottom_vec_org_[0]->cpu_diff();
	for (int i = 0; i < blob_bottom_vec_[0]->count(); ++i) { EXPECT_NEAR(gpu_diff[i], cpu_diff[i], 1e-4); }

//	layer->param_buffer_w_->cpu_data();
//	layer_org->param_buffer_w_->cpu_data();

	compare_blobs(*layer->param_buffer_w_, *layer_org->param_buffer_w_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_mu1_, *layer_org->param_buffer_mu1_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_mu2_, *layer_org->param_buffer_mu2_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_sigma_, *layer_org->param_buffer_sigma_, true, (Dtype)1e-4);

  }

  delete blob_bottom_3_org_;
  delete blob_top_3_org_;
  delete blob_top_diff;
}
TYPED_TEST(GaussConvolutionLayerTest, TestCuDNNConvolutionExtensive) {

 if (Caffe::mode() == Caffe::CPU)
	 return;

  typedef typename TypeParam::Dtype Dtype;

  if (sizeof(Dtype) > 4)
 	  return;

  // run with different combinations of settings
  // num images: 1, 2, 3, 5, 8, 11
  // num subfeatures: 1, 2, 3, 5, 8, 11
  // width: 4, 9, 16, 32, 33,
  // height: 4, 9, 16, 32, 33,
  // num features: 4, 6, 8, 16
  // num gauss: 2,3,4

  vector<int> num_imgs_args = {1, 11};
  vector<int> num_subfeat_args = {1, 2, 3, 8, 11};
  vector<int> width_args = {4, 9, 32, 33};
  vector<int> height_args = {4, 9, 32, 33,};
  vector<int> num_feat_args = {4, 16};
  vector<int> num_gauss_args = {2,3,4}; // do not forget, this is squared, so in effect with 4,9 and 16 gauss per filter

  for (int img_i = 0; img_i < num_imgs_args.size(); ++img_i) {
  for (int subfeat_i = 0; subfeat_i < num_subfeat_args.size(); ++subfeat_i) {
  for (int width_i = 0; width_i < width_args.size(); ++width_i) {
  for (int height_i = 0; height_i < height_args.size(); ++height_i) {
  for (int feat_i = 0; feat_i < num_feat_args.size(); ++feat_i) {
  for (int gauss_i = 0; gauss_i < num_gauss_args.size(); ++gauss_i) {

  int num_imgs = num_imgs_args[img_i];
  int num_subfeat = num_subfeat_args[subfeat_i];
  int width = width_args[width_i];
  int height = height_args[height_i];
  int num_feat = num_feat_args[feat_i];
  int num_gauss = num_gauss_args[gauss_i];

  std::cout << "testing num_imgs " << num_imgs << ", num_subfeat " << num_subfeat << ", width " << width << ", height " << height << ", num_feat " << num_feat << ",num_gauss " << num_gauss << std::endl;
  vector<Blob<Dtype>*> blob_bottom_vec_;
  vector<Blob<Dtype>*> blob_top_vec_;

  vector<Blob<Dtype>*> blob_bottom_vec_org_;
  vector<Blob<Dtype>*> blob_top_vec_org_;

  vector<bool> propagate_down;

  Blob<Dtype>* blob_bottom_3_ = new Blob<Dtype>(num_imgs, num_subfeat, height, width);
  Blob<Dtype>* blob_top_3_ = new Blob<Dtype>();

  {
	  FillerParameter filler_param;
	  filler_param.set_value(1.);
	  GaussianFiller<Dtype> filler(filler_param);
	  filler.Fill(blob_bottom_3_);
  }
  propagate_down.push_back(true);
  blob_bottom_vec_.push_back(blob_bottom_3_);
  blob_top_vec_.push_back(blob_top_3_);

  FillerParameter filler_param;
  filler_param.set_value(0.1);

  Blob<Dtype>* blob_top_diff = new Blob<Dtype>();

  Blob<Dtype>* blob_bottom_3_org_ = new Blob<Dtype>();
  Blob<Dtype>* blob_top_3_org_ = new Blob<Dtype>();

  blob_bottom_3_org_->CopyFrom(*blob_bottom_3_, false, true);

  blob_bottom_vec_org_.push_back(blob_bottom_3_org_);
  blob_top_vec_org_.push_back(blob_top_3_org_);

  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
	  layer_param.mutable_convolution_param();

  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(1);
  convolution_param->add_pad(1);

  convolution_param->set_number_gauss(num_gauss);

  convolution_param->set_num_output(num_feat);

  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_weight_filler()->set_std(0.01);

  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);

  convolution_param->mutable_sigma_filler()->set_type("constant");
  convolution_param->mutable_sigma_filler()->set_value(0.8);

  convolution_param->set_gmm_component_border_bound(1.5);
  convolution_param->set_gmm_sigma_lower_bound(0.5);

  convolution_param->set_gmm_weight_normalization(false);
  convolution_param->set_gmm_gauss_normalization(true);

  for (int gmm_sqrt_norm = 0; gmm_sqrt_norm < 1; gmm_sqrt_norm++) {
	  convolution_param->set_gmm_square_gauss_normalization((bool)gmm_sqrt_norm);

	shared_ptr<BaseGaussianConvLayer<Dtype> > layer(new CuDNNGaussianConvLayer<Dtype>(layer_param));
	shared_ptr<BaseGaussianConvLayer<Dtype> > layer_org(new GaussianConvLayer<Dtype>(layer_param));

	layer->SetUp(blob_bottom_vec_, blob_top_vec_);
	layer_org->SetUp(blob_bottom_vec_org_, blob_top_vec_org_);

	layer_org->param_buffer_w_->CopyFrom(*layer->param_buffer_w_, false, false);
	layer_org->param_buffer_mu1_->CopyFrom(*layer->param_buffer_mu1_, false, false);
	layer_org->param_buffer_mu2_->CopyFrom(*layer->param_buffer_mu2_, false, false);
	layer_org->param_buffer_sigma_->CopyFrom(*layer->param_buffer_sigma_, false, false);
	layer_org->param_buffer_bias_->CopyFrom(*layer->param_buffer_bias_, false, false);

	// RUN forward
	layer->Forward(blob_bottom_vec_, blob_top_vec_);

	layer_org->Forward(blob_bottom_vec_org_, blob_top_vec_org_);

	blob_top_diff->ReshapeLike(*blob_top_vec_[0]);
	GaussianFiller<Dtype> filler(filler_param);
	filler.Fill(blob_top_diff);

	caffe_copy( blob_top_vec_[0]->count(), blob_top_diff->cpu_data(), blob_top_vec_[0]->mutable_cpu_diff());

	blob_top_vec_[0]->cpu_data();
	blob_top_vec_[0]->gpu_diff();

	blob_top_vec_org_[0]->CopyFrom(*blob_top_vec_[0], true, true);

//	blob_top_vec_org_[0]->cpu_diff();

	const Dtype* gpu_data = blob_top_vec_[0]->cpu_data();
	const Dtype* cpu_data = blob_top_vec_org_[0]->cpu_data();
	for (int i = 0; i < blob_top_vec_[0]->count(); ++i) { EXPECT_NEAR(gpu_data[i], cpu_data[i], 1e-4); }

	// set all back-propagated error values to 1 to ease debugging
	//caffe_gpu_set(blob_top_vec_org_[0]->count(), (Dtype)1.0, blob_top_vec_org_[0]->mutable_gpu_diff());
	//caffe_gpu_set(blob_top_vec_[0]->count(), (Dtype)1.0, blob_top_vec_[0]->mutable_gpu_diff());

	// RUN backward

	layer->Backward(blob_top_vec_, propagate_down, blob_bottom_vec_);

	layer_org->Backward(blob_top_vec_org_, propagate_down, blob_bottom_vec_org_);

	const Dtype* gpu_diff = blob_bottom_vec_[0]->cpu_diff();
	const Dtype* cpu_diff = blob_bottom_vec_org_[0]->cpu_diff();
	for (int i = 0; i < blob_bottom_vec_[0]->count(); ++i) { EXPECT_NEAR(gpu_diff[i], cpu_diff[i], 1e-4); }

//	layer->param_buffer_w_->cpu_data();
//	layer_org->param_buffer_w_->cpu_data();

	compare_blobs(*layer->param_buffer_w_, *layer_org->param_buffer_w_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_mu1_, *layer_org->param_buffer_mu1_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_mu2_, *layer_org->param_buffer_mu2_, true, (Dtype)1e-4);
	compare_blobs(*layer->param_buffer_sigma_, *layer_org->param_buffer_sigma_, true, (Dtype)1e-4);

  }

  delete blob_top_3_;
  delete blob_bottom_3_;
  delete blob_bottom_3_org_;
  delete blob_top_3_org_;
  delete blob_top_diff;

  } } } } } }

}

/*
TYPED_TEST(GaussConvolutionLayerTest, Test0DConvolution) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  const int kNumOutput = 3;
  convolution_param->set_num_output(kNumOutput);
  convolution_param->set_axis(3);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  shared_ptr<Layer<Dtype> > layer(
      new ConvolutionLayer<Dtype>(layer_param));
  vector<int> top_shape = this->blob_bottom_->shape();
  top_shape[3] = kNumOutput;
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  EXPECT_EQ(top_shape, this->blob_top_->shape());
  layer->Forward(this->blob_bottom_vec_, this->blob_top_vec_);
  // Check against reference convolution.
  vector<int> weight_offset(2);
  const Blob<Dtype>* weight = layer->blobs()[0].get();
  const Blob<Dtype>* bias = layer->blobs()[1].get();
  const int num = this->blob_top_->count(3);
  const int dim = this->blob_top_->shape(3);
  const int bottom_dim = this->blob_bottom_->shape(3);
  for (int n = 0; n < num; ++n) {
    for (int d = 0; d < dim; ++d) {
      weight_offset[0] = d;
      Dtype value = bias->cpu_data()[d];
      for (int bottom_d = 0; bottom_d < bottom_dim; ++bottom_d) {
        weight_offset[1] = bottom_d;
        value += weight->data_at(weight_offset) *
                 this->blob_bottom_->cpu_data()[n * bottom_dim + bottom_d];
      }
      EXPECT_NEAR(value, this->blob_top_->cpu_data()[n * dim + d], 1e-4);
    }
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestSimple3DConvolution) {
  typedef typename TypeParam::Dtype Dtype;
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);
  vector<int> bottom_shape(5);
  bottom_shape[0] = this->blob_bottom_vec_[0]->shape(0);
  bottom_shape[1] = this->blob_bottom_vec_[0]->shape(1);
  bottom_shape[2] = 5;
  bottom_shape[3] = this->blob_bottom_vec_[0]->shape(2);
  bottom_shape[4] = this->blob_bottom_vec_[0]->shape(3);
  FillerParameter filler_param;
  GaussianFiller<Dtype> filler(filler_param);
  for (int i = 0; i < this->blob_bottom_vec_.size(); ++i) {
    this->blob_bottom_vec_[i]->Reshape(bottom_shape);
    filler.Fill(this->blob_bottom_vec_[i]);
  }
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(4);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  shared_ptr<Layer<Dtype> > layer(
      new ConvolutionLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  layer->Forward(this->blob_bottom_vec_, this->blob_top_vec_);
  // Check against reference convolution.
  const Dtype* top_data;
  const Dtype* ref_top_data;
  caffe_conv(this->blob_bottom_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_));
  top_data = this->blob_top_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
  caffe_conv(this->blob_bottom_2_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_2_));
  top_data = this->blob_top_2_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
}

TYPED_TEST(GaussConvolutionLayerTest, Test1x1Convolution) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->add_kernel_size(1);
  convolution_param->add_stride(1);
  convolution_param->set_num_output(4);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);
  shared_ptr<Layer<Dtype> > layer(
      new ConvolutionLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  layer->Forward(this->blob_bottom_vec_, this->blob_top_vec_);
  // Check against reference convolution.
  const Dtype* top_data;
  const Dtype* ref_top_data;
  caffe_conv(this->blob_bottom_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_));
  top_data = this->blob_top_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestSimpleConvolutionGroup) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(3);
  convolution_param->set_group(3);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("constant");
  convolution_param->mutable_bias_filler()->set_value(0.1);
  shared_ptr<Layer<Dtype> > layer(
      new ConvolutionLayer<Dtype>(layer_param));
  layer->SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
  layer->Forward(this->blob_bottom_vec_, this->blob_top_vec_);
  // Check against reference convolution.
  const Dtype* top_data;
  const Dtype* ref_top_data;
  caffe_conv(this->blob_bottom_, convolution_param, layer->blobs(),
      this->MakeReferenceTop(this->blob_top_));
  top_data = this->blob_top_->cpu_data();
  ref_top_data = this->ref_blob_top_->cpu_data();
  for (int i = 0; i < this->blob_top_->count(); ++i) {
    EXPECT_NEAR(top_data[i], ref_top_data[i], 1e-4);
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestNDAgainst2D) {
  typedef typename TypeParam::Dtype Dtype;
  const int kernel_h = 11;
  const int kernel_w = 13;
  vector<int> bottom_shape(4);
  bottom_shape[0] = 15;
  bottom_shape[1] = 18;
  bottom_shape[2] = kernel_h * 2;
  bottom_shape[3] = kernel_w * 2;
  FillerParameter filler_param;
  GaussianFiller<Dtype> filler(filler_param);
  for (int i = 0; i < this->blob_bottom_vec_.size(); ++i) {
    this->blob_bottom_vec_[i]->Reshape(bottom_shape);
    filler.Fill(this->blob_bottom_vec_[i]);
  }
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->set_num_output(12);
  convolution_param->set_bias_term(false);
  convolution_param->set_group(6);
  convolution_param->set_kernel_h(kernel_h);
  convolution_param->set_kernel_w(kernel_w);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  Blob<Dtype> weights;
  Blob<Dtype> top_diff;
  // Shape and fill weights and top_diff.
  bool copy_diff;
  bool reshape;
  {
    ConvolutionLayer<Dtype> layer(layer_param);
    layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
    top_diff.ReshapeLike(*this->blob_top_);
    filler.Fill(&top_diff);
    ASSERT_EQ(1, layer.blobs().size());
    copy_diff = false; reshape = true;
    weights.CopyFrom(*layer.blobs()[0], copy_diff, reshape);
  }
  vector<bool> propagate_down(1, true);
  Blob<Dtype> result_2d;
  Blob<Dtype> backward_result_2d;
  Blob<Dtype> backward_weight_result_2d;
  // Test with 2D im2col
  {
    caffe_set(this->blob_top_->count(), Dtype(0),
              this->blob_top_->mutable_cpu_data());
    caffe_set(this->blob_bottom_->count(), Dtype(0),
              this->blob_bottom_->mutable_cpu_diff());
    caffe_set(weights.count(), Dtype(0), weights.mutable_cpu_diff());
    // Do SetUp and Forward; save Forward result in result_2d.
    convolution_param->set_force_nd_im2col(false);
    ConvolutionLayer<Dtype> layer_2d(layer_param);
    layer_2d.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
    ASSERT_EQ(1, layer_2d.blobs().size());
    copy_diff = false; reshape = false;
    layer_2d.blobs()[0]->CopyFrom(weights, copy_diff, reshape);
    layer_2d.Forward(this->blob_bottom_vec_, this->blob_top_vec_);
    copy_diff = false; reshape = true;
    result_2d.CopyFrom(*this->blob_top_, copy_diff, reshape);
    // Copy pre-generated top diff into actual top diff;
    // do Backward and save result in backward_result_2d.
    ASSERT_EQ(this->blob_top_->shape(), top_diff.shape());
    caffe_copy(top_diff.count(), top_diff.cpu_data(),
               this->blob_top_->mutable_cpu_diff());
    layer_2d.Backward(this->blob_top_vec_, propagate_down,
                      this->blob_bottom_vec_);
    copy_diff = true; reshape = true;
    backward_result_2d.CopyFrom(*this->blob_bottom_, copy_diff, reshape);
    backward_weight_result_2d.CopyFrom(weights, copy_diff, reshape);
  }
  Blob<Dtype> result_nd;
  Blob<Dtype> backward_result_nd;
  Blob<Dtype> backward_weight_result_nd;
  // Test with ND im2col
  {
    caffe_set(this->blob_top_->count(), Dtype(0),
              this->blob_top_->mutable_cpu_data());
    caffe_set(this->blob_bottom_->count(), Dtype(0),
              this->blob_bottom_->mutable_cpu_diff());
    caffe_set(weights.count(), Dtype(0), weights.mutable_cpu_diff());
    // Do SetUp and Forward; save Forward result in result_nd.
    convolution_param->set_force_nd_im2col(true);
    ConvolutionLayer<Dtype> layer_nd(layer_param);
    layer_nd.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
    ASSERT_EQ(1, layer_nd.blobs().size());
    copy_diff = false; reshape = false;
    layer_nd.blobs()[0]->CopyFrom(weights, copy_diff, reshape);
    layer_nd.Forward(this->blob_bottom_vec_, this->blob_top_vec_);
    copy_diff = false; reshape = true;
    result_nd.CopyFrom(*this->blob_top_, copy_diff, reshape);
    // Copy pre-generated top diff into actual top diff;
    // do Backward and save result in backward_result_nd.
    ASSERT_EQ(this->blob_top_->shape(), top_diff.shape());
    caffe_copy(top_diff.count(), top_diff.cpu_data(),
               this->blob_top_->mutable_cpu_diff());
    layer_nd.Backward(this->blob_top_vec_, propagate_down,
                      this->blob_bottom_vec_);
    copy_diff = true; reshape = true;
    backward_result_nd.CopyFrom(*this->blob_bottom_, copy_diff, reshape);
    backward_weight_result_nd.CopyFrom(weights, copy_diff, reshape);
  }
  ASSERT_EQ(result_nd.count(), result_2d.count());
  for (int i = 0; i < result_2d.count(); ++i)  {
    EXPECT_EQ(result_2d.cpu_data()[i], result_nd.cpu_data()[i]);
  }
  ASSERT_EQ(backward_result_nd.count(), backward_result_2d.count());
  for (int i = 0; i < backward_result_2d.count(); ++i) {
    EXPECT_EQ(backward_result_2d.cpu_diff()[i],
              backward_result_nd.cpu_diff()[i]);
  }
  ASSERT_EQ(backward_weight_result_nd.count(),
            backward_weight_result_2d.count());
  for (int i = 0; i < backward_weight_result_2d.count(); ++i) {
    EXPECT_EQ(backward_weight_result_2d.cpu_diff()[i],
              backward_weight_result_nd.cpu_diff()[i]);
  }
}

TYPED_TEST(GaussConvolutionLayerTest, TestGradient) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(2);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  ConvolutionLayer<Dtype> layer(layer_param);
  GradientChecker<Dtype> checker(1e-2, 1e-3);
  checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
      this->blob_top_vec_);
}

TYPED_TEST(GaussConvolutionLayerTest, TestGradient3D) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  vector<int> bottom_shape(5);
  bottom_shape[0] = this->blob_bottom_vec_[0]->shape(0);
  bottom_shape[1] = this->blob_bottom_vec_[0]->shape(1);
  bottom_shape[2] = 5;
  bottom_shape[3] = this->blob_bottom_vec_[0]->shape(2);
  bottom_shape[4] = this->blob_bottom_vec_[0]->shape(3);
  FillerParameter filler_param;
  GaussianFiller<Dtype> filler(filler_param);
  for (int i = 0; i < this->blob_bottom_vec_.size(); ++i) {
    this->blob_bottom_vec_[i]->Reshape(bottom_shape);
    filler.Fill(this->blob_bottom_vec_[i]);
  }
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(2);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  ConvolutionLayer<Dtype> layer(layer_param);
  GradientChecker<Dtype> checker(1e-2, 1e-3);
  checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
      this->blob_top_vec_);
}

TYPED_TEST(GaussConvolutionLayerTest, Test1x1Gradient) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  this->blob_bottom_vec_.push_back(this->blob_bottom_2_);
  this->blob_top_vec_.push_back(this->blob_top_2_);
  convolution_param->add_kernel_size(1);
  convolution_param->add_stride(1);
  convolution_param->set_num_output(2);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  ConvolutionLayer<Dtype> layer(layer_param);
  GradientChecker<Dtype> checker(1e-2, 1e-3);
  checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
      this->blob_top_vec_);
}

TYPED_TEST(GaussConvolutionLayerTest, TestGradientGroup) {
  typedef typename TypeParam::Dtype Dtype;
  LayerParameter layer_param;
  ConvolutionParameter* convolution_param =
      layer_param.mutable_convolution_param();
  convolution_param->add_kernel_size(3);
  convolution_param->add_stride(2);
  convolution_param->set_num_output(3);
  convolution_param->set_group(3);
  convolution_param->mutable_weight_filler()->set_type("gaussian");
  convolution_param->mutable_bias_filler()->set_type("gaussian");
  ConvolutionLayer<Dtype> layer(layer_param);
  GradientChecker<Dtype> checker(1e-2, 1e-3);
  checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
      this->blob_top_vec_);
}
*/
#ifdef USE_CUDNN

#endif

}  // namespace caffe


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE ScatterTest
#include <poplibs_support/TestDevice.hpp>

#include <iostream>

#include <boost/test/unit_test.hpp>

#include <poplar/Engine.hpp>
#include <popops/ElementWise.hpp>
#include <popops/Scatter.hpp>
#include <popops/codelets.hpp>
#include <poputil/TileMapping.hpp>
#include <poputil/exceptions.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poputil;
using namespace popops;
using namespace poplibs_support;

template <typename T, std::size_t N1, std::size_t N2, std::size_t N3>
std::array<T, N1> deviceScatter(
    std::array<T, N1> in, std::vector<std::size_t> in_shape,
    std::array<int, N2> indices, std::vector<std::size_t> indices_shape,
    std::array<T, N3> updates, std::vector<std::size_t> updates_shape,
    std::size_t index_vector_dim, std::vector<unsigned> update_window_dims,
    std::vector<std::size_t> insert_window_dims,
    std::vector<unsigned> scatter_dims_to_operand_dims) {
  auto device = createTestDevice(TEST_TARGET, 1, 4);
  Graph graph(device.getTarget());
  auto seq = Sequence();
  popops::addCodelets(graph);

  Tensor tIn = graph.addVariable(equivalent_device_type<T>().value, in_shape);
  Tensor tIndices =
      graph.addVariable(equivalent_device_type<int>().value, indices_shape);
  Tensor tUpdates =
      graph.addVariable(equivalent_device_type<T>().value, updates_shape);

  poputil::mapTensorLinearly(graph, tIn);
  poputil::mapTensorLinearly(graph, tIndices);
  poputil::mapTensorLinearly(graph, tUpdates);

  BOOST_REQUIRE_EQUAL(tIn.numElements(), N1);
  BOOST_REQUIRE_EQUAL(tIndices.numElements(), N2);
  BOOST_REQUIRE_EQUAL(tUpdates.numElements(), N3);

  popops::UpdateComputationFunc update = [](Graph &g, Tensor &a, Tensor &b,
                                            Sequence &prog) {
    return add(g, a, b, prog);
  };

  scatter(graph, tIn, tIndices, tUpdates, index_vector_dim, update_window_dims,
          insert_window_dims, scatter_dims_to_operand_dims, update, seq);

  graph.createHostWrite("in", tIn);
  graph.createHostWrite("indices", tIndices);
  graph.createHostWrite("update", tUpdates);
  graph.createHostRead("out", tIn);

  std::array<T, N1> out;
  Engine eng(graph, seq);
  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in", in.data(), in.data() + in.size());
    eng.writeTensor("indices", indices.data(), indices.data() + indices.size());
    eng.writeTensor("update", updates.data(), updates.data() + updates.size());
    eng.run();

    eng.readTensor("out", out.data(), out.data() + out.size());
  });

  return out;
}

// Test that the scatter sums the elements of the update tensor
BOOST_AUTO_TEST_CASE(ScatterUpdateTestCase0) {
  std::array<int, 1> operand = {0};
  std::array<int, 9> indices = {0, 0, 0, 0, 0, 0, 0, 0, 0};
  std::array<int, 9> updates = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  std::array<int, 1> result = {45};

  BOOST_TEST(deviceScatter(operand, {1}, indices, {9}, updates, {9}, 1, {1},
                           {0}, {0}) == result,
             boost::test_tools::per_element());
}