#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*-
$CXXX $CXXFLAGS -O3 $0 -o $0x -lboost_unit_test_framework -lboost_timer \
`pkg-config --libs blas` \
`#-Wl,-rpath,/usr/local/Wolfram/Mathematica/12.0/SystemFiles/Libraries/Linux-x86-64 -L/usr/local/Wolfram/Mathematica/12.0/SystemFiles/Libraries/Linux-x86-64 -lmkl_intel_ilp64 -lmkl_sequential -lmkl_core` \
&&$0x&&rm $0x;exit
#endif
// © Alfredo A. Correa 2019-2020

#ifndef MULTI_ADAPTORS_BLAS_GEMM_HPP
#define MULTI_ADAPTORS_BLAS_GEMM_HPP

#include "../blas/core.hpp"

#include "../blas/numeric.hpp"
#include "../blas/operations.hpp"

#include "../blas/gemv.hpp"

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

namespace boost{
namespace multi{
namespace blas{

using multi::blas::core::gemm;
using multi::blas::core::gemv;

template<class M> bool is_c_ordering(M const& m){
	return stride(rotated(m))==1 and size(m)!=1;
}

double const& conj(double const& d){return d;}
float const& conj(float const& f){return f;}

template<class A, std::enable_if_t<not is_conjugated<A>{}, int> =0>
auto gemm_base_aux(A&& a){return base(a);}

template<class A, std::enable_if_t<    is_conjugated<A>{}, int> =0>
auto gemm_base_aux(A&& a){return underlying(base(a));}

template<class Context, class A, class B, class C>
C&& gemm(Context& ctx, typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c)
//->decltype(ctx.gemm('N', 'T', size(~c), size(a), size(b), &alpha, gemm_base_aux(b), stride( b), gemm_base_aux(a), stride(~a), &beta, gemm_base_aux(c), size(b)) , std::forward<C>(c))
{

	MULTI_MARK_SCOPE("multi::blas::gemm with context");

	if(c.is_empty()){
		assert(a.is_empty() and b.is_empty());
		return std::forward<C>(c);
	}

	assert( size(~a) == size( b) );
	assert( size( a) == size( c) );
	assert( size(~b) == size(~c) );

	auto base_a = gemm_base_aux(a);
	auto base_b = gemm_base_aux(b);
	auto base_c = gemm_base_aux(c);

	assert( stride(a)==1 or stride(~a)==1 );
	assert( stride(b)==1 or stride(~b)==1 );
	assert( stride(c)==1 or stride(~c)==1 );

	     if(stride(c)==1 and stride(~c)!=1) blas::gemm(ctx, alpha, ~b, ~a, beta, ~c);
	else if(is_conjugated<C>{}) blas::gemm(ctx, conj(alpha), conj(a), conj(b), conj(beta), conj(c));
	else{
		;;;;; if(stride(~a)==1 and stride(~b)==1 and not is_conjugated<A>{} and not is_conjugated<B>{}){
			if(size(a)==1) ctx.gemm('N', 'N', size(~c), size(a), size(b), &alpha, base_b, stride( b), base_a, size(b)   , &beta, base_c, size(b)  );
			else           ctx.gemm('N', 'N', size(~c), size(a), size(b), &alpha, base_b, stride( b), base_a, stride( a), &beta, base_c, stride(c));
		}else if(stride( a)==1 and stride(~b)==1 and     is_conjugated<A>{} and not is_conjugated<B>{}) ctx.gemm('N', 'C', size(~c), size(a), size(b), &alpha, base_b, stride( b), base_a, stride(~a), &beta, base_c, stride(c));
		else if(stride( a)==1 and stride(~b)==1 and not is_conjugated<A>{} and not is_conjugated<B>{}){
			if(size(a)==1) ctx.gemm('N', 'T', size(~c), size(a), size(b), &alpha, base_b, stride( b), base_a, stride(~a), &beta, base_c, size(b));
			else           ctx.gemm('N', 'T', size(~c), size(a), size(b), &alpha, base_b, stride( b), base_a, stride(~a), &beta, base_c, stride(c));
		}
		else if(stride(~a)==1 and stride( b)==1 and not is_conjugated<A>{} and     is_conjugated<B>{}) ctx.gemm('C', 'N', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride( a), &beta, base_c, stride(c));
		else if(stride( a)==1 and stride( b)==1 and     is_conjugated<A>{} and     is_conjugated<B>{}) ctx.gemm('C', 'C', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride(~a), &beta, base_c, stride(c));
		else if(stride( a)==1 and stride( b)==1 and not is_conjugated<A>{} and     is_conjugated<B>{}) ctx.gemm('C', 'T', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride(~a), &beta, base_c, stride(c));
		else if(stride(~a)==1 and stride( b)==1 and not is_conjugated<A>{} and not is_conjugated<B>{}){
			if(size(a)==1) ctx.gemm('T', 'N', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, size(b)   , &beta, base_c, stride(c));
			else           ctx.gemm('T', 'N', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride( a), &beta, base_c, stride(c));
		}
		else if(stride( a)==1 and stride( b)==1 and     is_conjugated<A>{} and not is_conjugated<B>{}) ctx.gemm('T', 'C', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride(~a), &beta, base_c, stride(c));
		else if(stride( a)==1 and stride( b)==1 and not is_conjugated<A>{} and not is_conjugated<B>{}) ctx.gemm('T', 'T', size(~c), size(a), size(b), &alpha, base_b, stride(~b), base_a, stride(~a), &beta, base_c, stride(c));
		else                                                                                assert(0&&" case not implemented in blas");
	}
	return std::forward<C>(c);
}

template<class A, class B, class C>
C&& gemm(typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c){
//->decltype(gemm('N', 'T', size(~c), size(a), size(b), &alpha, gemm_base_aux(b), stride( b), gemm_base_aux(a), stride(~a), &beta, gemm_base_aux(c), size(b)) , std::forward<C>(c)){
	using multi::blas::default_allocator_of;
	auto ctx = default_context_of(gemm_base_aux(a)); // ADL
	return gemm(ctx, alpha, a, b, beta, std::forward<C>(c));
}

template<class A2D, class B2D, class C2D = typename A2D::decay_type>
NODISCARD("because input arguments are const")
auto gemm(typename A2D::element a, A2D const& A, B2D const& B){
	assert(get_allocator(A) == get_allocator(B));
	return gemm(a, A, B, 0., C2D({size(A), size(rotated(B))}, get_allocator(A)));
}

template<class Context, class A2D, class B2D, class C2D = typename A2D::decay_type>
NODISCARD("because input arguments are const")
auto gemm(Context&& ctx, typename A2D::element a, A2D const& A, B2D const& B)
->std::decay_t<decltype(gemm(std::forward<Context>(ctx), a, A, B, 0., C2D({size(A), size(rotated(B))}, get_allocator(A))))>{
	assert(get_allocator(A) == get_allocator(B));
	return gemm(std::forward<Context>(ctx), a, A, B, 0., C2D({size(A), size(rotated(B))}, get_allocator(A)));
}

template<class A2D, class B2D>
auto gemm(A2D const& A, B2D const& B)
->decltype(gemm(1., A, B)){
	return gemm(1., A, B);}

template<class Context, class A2D, class B2D>
auto gemm(Context&& ctx, A2D const& A, B2D const& B)
->decltype(gemm(std::forward<Context>(ctx), 1., A, B)){
	return gemm(std::forward<Context>(ctx), 1., A, B);}

namespace operators{
	template<class A2D, class B2D>
	auto operator*(A2D const& A, B2D const& B)
	->decltype(gemm(1., A, B)){
		return gemm(1., A, B);}
}

}}}

#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_BLAS_GEMM

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

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

#include<complex>
#include<cassert>
#include<iostream>
#include<numeric>
#include<algorithm>
#include<random>

#include <boost/timer/timer.hpp>

#include "../blas/axpy.hpp"
#include "../blas/dot.hpp"
#include "../blas/nrm2.hpp"

namespace multi = boost::multi;

template<class M> decltype(auto) print(M const& C){
	using std::cout;
	using multi::size;
	for(int i = 0; i != size(C); ++i){
		for(int j = 0; j != size(C[i]); ++j)
			cout<< C[i][j] <<' ';
		cout<<std::endl;
	}
	return cout<<std::endl;
}

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

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square){
	namespace blas = multi::blas;
	multi::array<double, 2> const a = {
		{ 1, 3},
		{ 9, 7},
	};
	multi::array<double, 2> const b = {
		{ 11, 12},
		{  7, 19},
	};
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == 148 );
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., ~a,  b, 0.,  c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE(( c[1][1] == 169 and c[1][0] == 82 ));
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1.,  a, ~b, 0.,  c); // c=ab⸆, c⸆=ba⸆
		BOOST_REQUIRE( c[1][0] == 183 );
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., ~a, ~b, 0.,  c); // c=a⸆b⸆, c⸆=ba
		BOOST_REQUIRE( c[1][0] == 117 );
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., ~a, ~b, 0., ~c); // c⸆=a⸆b⸆, c=ba
		BOOST_REQUIRE( c[0][1] == 117 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare){
	namespace blas = multi::blas;
	multi::array<double, 2> const a = {
		{ 1, 3, 1},
		{ 9, 7, 1},
	};
	multi::array<double, 2> const b = {
		{ 11, 12, 1},
		{  7, 19, 1},
		{  1,  1, 1}
	};
	{
		multi::array<double, 2> c({2, 3});
		using multi::blas::gemm;
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][2] == 17 );
	}
}

BOOST_AUTO_TEST_CASE(multi_blas_gemm_nh){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1.-2.*I, 9.-1.*I},
		{2.+3.*I, 1.-2.*I}
	};
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, blas::hermitized(a), 0., c); // c=aa†, c†=aa†
		BOOST_TEST_REQUIRE( c[1][0] == 7.-10.*I );
		BOOST_TEST_REQUIRE( c[0][1] == 7.+10.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_blas_gemm_elongated){
	using complex = std::complex<double>; complex const I{0,1};
	multi::array<complex, 2> const a = {
		{1.-2.*I, 9.-1.*I}
	};
	{
		multi::array<complex, 2> c({1, 1});
		using multi::blas::gemm;
		using multi::blas::hermitized;
		gemm(1., a, hermitized(a), 0., c); // c=aa†, c†=aa†
		BOOST_REQUIRE( c[0][0] == 87. + 0.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bisbis){
	using complex = std::complex<double>; complex const I{0,1};
	multi::array<complex, 2> const a = {
		{1. + 2.*I},
		{9. - 1.*I},
		{1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I, 7. - 3.*I, 8. - 1.*I}
	};
	{
		auto ar = rotated(a).decay();
		multi::array<complex, 2> c({1, 1});
		using multi::blas::hermitized;
		using multi::blas::gemm;
		using multi::blas::is_c_ordering;

		BOOST_REQUIRE( size(hermitized(a)) == 1 );
		BOOST_REQUIRE( size(hermitized(b)[0]) == 1 );

		gemm(1., hermitized(a), hermitized(b), 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][0] == 84.+7.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_empty){
	multi::array<double, 2> const a({0, 5});
	BOOST_REQUIRE( size( a) == 0 );
	BOOST_REQUIRE( size(~a) == 5 );
	BOOST_REQUIRE( a.is_empty() );
	multi::array<double, 2> const b({5, 0});
	BOOST_REQUIRE( size( b) == 0 );
	BOOST_REQUIRE( size(~b) == 0 );
	BOOST_REQUIRE( b.is_empty() );
	{
		multi::array<double, 2> c;
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare2){
	multi::array<double, 2> const a = {
		{ 1, 3},
		{ 9, 7},
		{ 1, 1}
	};
	multi::array<double, 2> const b = {
		{ 11, 12},
		{  7, 19}
	};
	{
		multi::array<double, 2> c({3, 2});
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[2][1] == 31 );
	}
	{
		multi::array<double, 2> c({2, 3});
		using multi::blas::gemm;
		gemm(1., a, b, 0., ~c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][2] == 31 );
	}
	{
		auto ar = +~a;
		multi::array<double, 2> c({3, 2});
		using multi::blas::gemm;
		gemm(1., ~ar, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[2][1] == 31 );
	}
	{
		auto ar = rotated(a).decay();
		multi::array<double, 2> c({2, 3});
		using multi::blas::gemm;
		gemm(1., rotated(ar), b, 0., rotated(c)); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][2] == 31 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x2){
	multi::array<double, 2> const a = {
		{ 1, 3},
		{ 9, 4},
		{ 1, 5}
	};
	multi::array<double, 2> const b = {
		{ 11, 12},
		{  7, 19},
		{  8, 1 }
	};
	{
		multi::array<double, 2> c({2, 2});
		using multi::blas::gemm;
		gemm(1., ~a, b, 0.,  c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[1][0] == 101 );
		gemm(1., ~a, b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][1] == 101 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x2){
	multi::array<double, 2> const a = {
		{1, 9, 1}
	};
	BOOST_TEST_REQUIRE( stride(~a) == 1 );
	BOOST_TEST_REQUIRE( stride( a) == 3 );
	multi::array<double, 2> const b = {
		{ 11, 12},
		{  7, 19},
		{  8, 1 }
	};
	{
		multi::array<double, 2> c({size(a), size(~b)});
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][1] == 184 );
	}
	{
		auto ar = +~a;
		using multi::blas::gemm;
		multi::array<double, 2> c({size(~b), size(~ar)});
		gemm(1., ~ar, b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[1][0] == 184 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_3x2){
	multi::array<complex, 2> const a = {
		{1, 9, 1}
	};
	BOOST_TEST_REQUIRE( stride(~a) == 1 );
	BOOST_TEST_REQUIRE( stride( a) == 3 );
	multi::array<complex, 2> const b = {
		{ 11, 12},
		{  7, 19},
		{  8, 1 }
	};
	{
		multi::array<complex, 2> c({size(a), size(~b)});
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][1] == 184. );
	}
	{
		auto ar = +~a;
		using multi::blas::gemm;
		multi::array<complex, 2> c({size(~b), size(~ar)});
		gemm(1., ~ar, b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[1][0] == 184. );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_part_3x2){
	multi::array<double, 2> const a = {
		{1, 9, 1},
		{3, 3, 3}
	};
	BOOST_TEST_REQUIRE( stride(~a) == 1 );
	BOOST_TEST_REQUIRE( stride( a) == 3 );
	multi::array<double, 2> const b = {
		{ 11, 12},
		{  7, 19},
		{  8, 1 }
	};
	{
		multi::array<double, 2> c({size(a({0, 1})), size(~b)});
		using multi::blas::gemm;
		gemm(1., a({0, 1}), b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][1] == 184 );
	}
	{
		auto ar = +~a;
		using multi::blas::gemm;
		multi::array<double, 2> c({size(~b), size(~ar(extension(ar), {0, 1}))});
		gemm(1., ~(ar(extension(ar), {0, 1})), b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[1][0] == 184 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_part_3x2){
	multi::array<complex, 2> const a = {
		{1., 9., 1.},
		{3., 3., 3.}
	};
	BOOST_TEST_REQUIRE( stride(~a) == 1 );
	BOOST_TEST_REQUIRE( stride( a) == 3 );
	multi::array<complex, 2> const b = {
		{ 11., 12.},
		{  7., 19.},
		{  8.,  1.}
	};
	{
		multi::array<complex, 2> c({size(a({0, 1})), size(~b)});
		using multi::blas::gemm;
		gemm(1., a({0, 1}), b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][1] == 184. );
	}
	{
		auto ar = +~a;
		using multi::blas::gemm;
		multi::array<complex, 2> c({size(~b), size(~ar(extension(ar), {0, 1}))});
		gemm(1., ~(ar(extension(ar), {0, 1})), b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[1][0] == 184. );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1){
	multi::array<double, 2> const a = {
		{1, 9, 1},
		{3, 3, 3}
	};
	BOOST_TEST_REQUIRE( stride(~a) == 1 );
	BOOST_TEST_REQUIRE( stride( a) == 3 );
	multi::array<double, 2> const b = {
		{ 11},
		{  7},
		{  8}
	};
	{
		multi::array<double, 2> c({size(a), size(~b)});
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][0] == 82 );
		BOOST_REQUIRE( c[1][0] == 78 );
	}
	{
		auto ar = +~a;
		using multi::blas::gemm;
		multi::array<double, 2> c({size(~b), size(~ar(extension(ar), {0, 1}))});
		gemm(1., ~(ar(extension(ar), {0, 1})), b, 0., ~c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE( c[0][0] == 82 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1_bis){
	multi::array<double, 2> const a = {
		{1, 9, 1},
		{3, 4, 5}
	};
	multi::array<double, 2> const b = {
		{ 11},
		{  7},
		{  8}
	};

	{
		multi::array<double, 2> c({1, 2});
		using multi::blas::gemm;
		gemm(1., a, b, 0., ~c); // c⸆=ab, c=b⸆a⸆
		BOOST_REQUIRE( (~c)[0][0] ==  82 );
		BOOST_REQUIRE( (~c)[1][0] == 101 );
	}
	{
		multi::array<double, 2> c({2, 1});
		using multi::blas::gemm;
		gemm(1., a, b, 0., c); // c⸆=ab, c=b⸆a⸆
		BOOST_REQUIRE( (~c)[0][1] == 101 );
		BOOST_REQUIRE(   c [1][0] == 101 );
	}
	{
		multi::array<double, 2> c({1, 2});
		auto ar = +~a;
		using multi::blas::gemm;
		gemm(1., ~ar, b, 0., ~c); // c⸆=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][1] == 101 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x1){
	multi::array<double, 2> const a = {
		{1, 9, 1}
	};
	multi::array<double, 2> const b = {
		{ 11},
		{  7},
		{  8}
	};
	using multi::blas::gemm;
	{
		multi::array<double, 2> c({1, 1});
		gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][0] == 82 );
	}
	{
		multi::array<double, 2> c({1, 1});
		auto ar = +~a;
		gemm(1., ~ar, b, 0., c); // c=ab, c⸆=ba
		BOOST_REQUIRE( c[0][0] == 82 );
	}
	{
		multi::array<double, 2> c({1, 1});
		auto br = +~b;
		gemm(1., a, ~br, 0., c);
		BOOST_REQUIRE( c[0][0] == 82 );
	}
	{
		multi::array<double, 2> c({1, 1});
		auto br = +~b;
		using multi::blas::hermitized;
		gemm(1., a, hermitized(br), 0., c);
		BOOST_REQUIRE( c[0][0] == 82 );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square){
	multi::array<complex, 2> const a = {
		{ 1.+3.*I, 3.+2.*I},
		{ 9.+1.*I, 7.+1.*I},
	};
	multi::array<complex, 2> const b = {
		{11.+2.*I, 12.+4.*I},
		{ 7.+1.*I, 19.-9.*I},
	};
	using multi::blas::gemm;
	{
		multi::array<complex, 2> c({2, 2});
		gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == 145. + 43.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		gemm(1., ~a, b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE(( c[1][1] == 170.-8.*I and c[1][0] == 77.+42.*I ));
	}
	{
		multi::array<complex, 2> c({2, 2});
		gemm(1., a, ~b, 0., c); // c=ab⸆, c⸆=ba⸆
		BOOST_REQUIRE( c[1][0] == 177.+69.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		using namespace multi::blas;
		gemm(1., T(a), T(b), 0., c); // c=a⸆b⸆, c⸆=ba
		BOOST_REQUIRE( c[1][0] == 109. + 68.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		using multi::blas::transposed;
		using multi::blas::gemm;
		gemm(1., transposed(a), transposed(b), 0., transposed(c)); // c⸆=a⸆b⸆, c=ba
		BOOST_REQUIRE( c[0][1] == 109.+68.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x1){
	using complex = std::complex<double>; complex const I{0, 1};
	multi::array<complex, 2> const a = {
		{1. + 2.*I, 9. - 1.*I, 1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I},
		{  7. - 3.*I},
		{  8. - 1.*I}
	};
	using multi::blas::gemm;
	{
		multi::array<complex, 2> c({1, 1});
		gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][0] == 84.-7.*I );
	}
	{
		multi::array<complex, 2> c({1, 1});
		auto ar = +~a;
		gemm(1., ~ar, b, 0., c); // c=ab, c⸆=ba
		BOOST_REQUIRE( c[0][0] == 84.-7.*I );
	}
	{
		multi::array<complex, 2> c({1, 1});
		auto br = +~b;
		using multi::blas::gemm;
		gemm(1., a, ~br, 0., c);
		BOOST_REQUIRE( c[0][0] == 84.-7.*I );
	}
	{
		multi::array<complex, 2> c({1, 1});
		auto br = +~b;
		using multi::blas::hermitized;
		gemm(1., a, hermitized(br), 0., ~c);
		BOOST_TEST_REQUIRE( c[0][0] == 80. + 53.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_hermitized_square){
	multi::array<complex, 2> const a = {
		{ 1.+3.*I, 3.+2.*I},
		{ 9.+1.*I, 7.+1.*I},
	};
	multi::array<complex, 2> const b = {
		{11.+2.*I, 12.+4.*I},
		{ 7.+1.*I, 19.-9.*I},
	};
	namespace blas = multi::blas;
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c†=b†a†
		BOOST_REQUIRE( c[1][0] == 145. + 43.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba
		BOOST_REQUIRE( c[1][0] == 109. - 68.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), blas::H(b), 0., blas::H(c)); // c†=a†b†, c=ba
		BOOST_REQUIRE( c[1][0] == 184. - 40.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a
		BOOST_REQUIRE( c[1][0] == 87. - 16.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, blas::H(b), 0., c); // c=ab†, c†=ba†
		BOOST_REQUIRE( c[1][0] == 189. - 23.*I );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba
		BOOST_REQUIRE( c[1][0] == 109. - 68.*I);
	}
	{
	//	multi::array<complex, 2> c({2, 2});
	//n	blas::gemm(1., blas::hermitized(a), blas::hermitized(b), 0., ~c); // c⸆=a†b†, c=b*a*
	//	BOOST_REQUIRE( c[0][1] == 109. - 68.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I},
		{9. - 1.*I},
		{1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I},
		{  7. - 3.*I},
		{  8. - 1.*I}
	};
	{
		multi::array<complex, 2> c({1, 1});
		blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][0] == 80.-53.*I );
	}
	{
		multi::array<complex, 2> c({1, 1});
		auto ha = +blas::hermitized(a);
		blas::gemm(1., ha, b, 0., c);
		BOOST_REQUIRE( c[0][0] == 80.-53.*I );
		blas::gemm(1., blas::H(b), a, 0., c);
		BOOST_REQUIRE( c[0][0] == 80.+53.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x2){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I, 9. - 1.*I, 1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I, 5. + 2.*I},
		{  7. - 3.*I, 2. + 1.*I},
		{  8. - 1.*I, 1. + 1.*I}
	};
	{
		multi::array<complex, 2> c({1, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][1] == 20.+21.*I );
	}
	{
		auto ar = +~a;
		multi::array<complex, 2> c({1, 2});
		blas::gemm(1., blas::H(ar), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][1] == 28.+3.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x2){
	multi::array<complex, 2> const a = {
		{1. + 2.*I},
		{9. - 1.*I},
		{1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I, 5. + 2.*I},
		{  7. - 3.*I, 2. + 1.*I},
		{  8. - 1.*I, 1. + 1.*I}
	};
	namespace blas = multi::blas;
	{
		auto ar = +~a;
		multi::array<complex, 2> c({1, 2});
		blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][1] == 28.+3.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x2){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I, 5. + 2.*I},
		{9. - 1.*I, 9. + 1.*I},
		{1. + 1.*I, 2. + 2.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I, 5. + 2.*I},
		{  7. - 3.*I, 2. + 1.*I},
		{  8. - 1.*I, 1. + 1.*I}
	};
	{
		auto ar = +~a;
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == 125.-84.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x1){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I, 5. + 2.*I},
		{9. - 1.*I, 9. + 1.*I},
		{1. + 1.*I, 2. + 2.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I},
		{  7. - 3.*I},
		{  8. - 1.*I}
	};
	{
		auto ar = +~a;
		multi::array<complex, 2> c({2, 1});
		blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == 125.-84.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bis){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I},
		{9. - 1.*I},
		{1. + 1.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I},
		{  7. - 3.*I},
		{  8. - 1.*I}
	};
	{
		auto ar = rotated(a).decay();
		multi::array<complex, 2> c({1, 1});
		blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[0][0] == 80. - 53.*I );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square_automatic){
	namespace blas = multi::blas;
	multi::array<double, 2> const a = {
		{ 1., 3.},
		{ 9., 7.},
	};
	multi::array<double, 2> const b = {
		{ 11., 12.},
		{  7., 19.},
	};
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == 148 and c[1][1] == 241 );
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., a, blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][1] == 196. );
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., blas::T(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE(( c[1][1] == 169. and c[1][0] == 82. ));
	}
	{
		multi::array<double, 2> c({2, 2});
		blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][1] == 154. );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_automatic){
	namespace blas = multi::blas;
	multi::array<double, 2> const a = {
		{ 1., 3., 1.},
		{ 9., 7., 1.},
	};
	multi::array<double, 2> const b = {
		{ 11., 12., 1.},
		{  7., 19., 1.},
		{  1.,  1., 1.}
	};
	{
		multi::array<double, 2> c({2, 3});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][2] == 17. );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic){
	multi::array<complex, 2> const a = {
		{ 1. + 2.*I, 3. - 3.*I},
		{ 9. + 1.*I, 7. + 4.*I},
	};
	multi::array<complex, 2> const b = {
		{ 11. + 1.*I, 12. + 1.*I},
		{  7. + 8.*I, 19. - 2.*I},
	};
	namespace blas = multi::blas;
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == complex(115, 104) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, blas::T(b), 0., c); // c=ab⸆, c⸆=ba⸆
		BOOST_REQUIRE( c[1][0] == complex(178, 75) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::T(a), b, 0., c); // c=a⸆b, c⸆=b⸆a
		BOOST_REQUIRE(( c[1][1] == complex(180, 29) and c[1][0] == complex(53, 54) ));
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE(( c[1][1] == complex(186, 65) and c[1][0] == complex(116, 25) ));
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][0] == complex(115, 104) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a
		BOOST_REQUIRE( c[1][0] == complex(111, 64) and c[1][1] == complex(158, -51) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., a, blas::H(b), 0., c); // c=ab†, c†=ba†
		BOOST_REQUIRE( c[1][0] == complex(188, 43) and c[1][1] == complex(196, 25) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba
		BOOST_REQUIRE( c[1][0] == complex(116, -25) and c[1][1] == complex(186, -65) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::T(a), blas::H(b), 0., c); // c=a⸆b†, c†=ba⸆†
		BOOST_REQUIRE( c[1][0] == complex(118, 5) and c[1][1] == complex(122, 45) );
	}
	{
		multi::array<complex, 2> c({2, 2});
		blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=a⸆b⸆, c⸆=ba
		BOOST_REQUIRE( c[1][0] == complex(116, 25) and c[1][1] == complex(186, 65) );
	}
}

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_nonsquare_automatic){
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{ 1. + 2.*I, 3. - 3.*I, 1.-9.*I},
		{ 9. + 1.*I, 7. + 4.*I, 1.-8.*I},
	};
	multi::array<complex, 2> const b = {
		{ 11.+1.*I, 12.+1.*I, 4.+1.*I, 8.-2.*I},
		{  7.+8.*I, 19.-2.*I, 2.+1.*I, 7.+1.*I},
		{  5.+1.*I,  3.-1.*I, 3.+8.*I, 1.+1.*I}
	};
	{
		multi::array<complex, 2> c({2, 4});
		blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆
		BOOST_REQUIRE( c[1][2] == complex(112, 12) );
	}
}

BOOST_AUTO_TEST_CASE(submatrix_result_issue_97){
	multi::array<complex, 2> M = {
		{2. + 3.*I, 2. + 1.*I, 1. + 2.*I},
		{4. + 2.*I, 2. + 4.*I, 3. + 1.*I},
		{7. + 1.*I, 1. + 5.*I, 0. + 3.*I}
	};

	multi::array<complex, 2> V = {
		{1. + 2.*I},
		{2. + 1.*I},
		{9. + 2.*I}
	};

	using multi::blas::gemm;
	using multi::blas::hermitized;
	using std::get;

	auto M2 = +M({0, 3}, {0, 1});
	BOOST_REQUIRE( M2 == M({0, 3}, {0, 1}) );

	BOOST_TEST( gemm(hermitized(M2               ), V)[0][0] == 83. + 6.*I );
	BOOST_TEST( gemm(hermitized(M({0, 3}, {0, 1})), V)[0][0] == 83. + 6.*I );

	using namespace multi::blas::operators;
	BOOST_REQUIRE( (hermitized(M)*V)[0][0] == 83. + 6.*I );
}

BOOST_AUTO_TEST_CASE(blas_context_gemm){

	auto rand = [d=std::normal_distribution<>{}, g=std::mt19937{}]()mutable{return complex{d(g), d(g)};};

	multi::array<complex, 2> A({30, 40});
	multi::array<complex, 2> B({40, 50});

	std::generate(A.elements().begin(), A.elements().end(), rand);
	std::generate(B.elements().begin(), B.elements().end(), rand);

	namespace blas = multi::blas;
	auto C = blas::gemm(A, B);

	using namespace multi::blas::operators;

	{
		auto sum = 0.;
		for(auto i : extension(~C))
			sum += blas::nrm2((~C)[i] - blas::gemv(A, (~B)[i]))();

		BOOST_TEST_REQUIRE(sum == 0, boost::test_tools::tolerance(1e-12));
	}

	BOOST_TEST_REQUIRE( std::inner_product(
		begin(~C), end(~C), begin(~B), 0., std::plus<>{}, [&A](auto const& Ccol, auto const& Bcol){
			return multi::blas::nrm2( Ccol - multi::blas::gemv(A, Bcol) );
	}) == 0. , boost::test_tools::tolerance(1e-12) );

	BOOST_TEST_REQUIRE( std::equal(
		begin(~C), end(~C), begin(~B), [&A](auto const& Ccol, auto const& Bcol){
			return multi::blas::nrm2( Ccol - multi::blas::gemv(A, Bcol) ) < 1e-12;
		}
	) );

	blas::context ctxt;
	auto C2 = gemm(ctxt, A, B);

	BOOST_TEST_REQUIRE( std::equal(
		begin(C), end(C), begin(C2), [](auto const& crow, auto const& c2row){return ((crow - c2row)^2) < 1e-13;}
	) );

}

#if 1
BOOST_AUTO_TEST_CASE(blas_gemm_timing){

	multi::array<complex, 2> A({1000, 2000});
	multi::array<complex, 2> B({2000, 3000});
	multi::array<complex, 2> C({size(A), size(~B)});
	multi::array<complex, 2> C2(extensions(C), complex{NAN, NAN});
	multi::array<complex, 2> C3(extensions(C), complex{NAN, NAN});
	multi::array<complex, 2> C4(extensions(C), complex{NAN, NAN});
	A[99][99] = B[11][22] = C[33][44] = 1.0;
	std::cerr<< "memory " << (A.num_elements()+ B.num_elements() + C.num_elements())*sizeof(complex)/1e6 <<" MB"<<std::endl;

	{
		boost::timer::auto_cpu_timer t;
		auto rand = [d=std::uniform_real_distribution<>{0., 10.}, g=std::mt19937{}]() mutable{return complex{d(g), d(g)};};
		std::generate(A.elements().begin(), A.elements().end(), rand);
		std::generate(B.elements().begin(), B.elements().end(), rand);
	}
	namespace blas = multi::blas;
	{
		boost::timer::auto_cpu_timer t; // 0.237581s
		C = blas::gemm(A, B);
	}
	{
		boost::timer::auto_cpu_timer t; // 4.516157s
		for(auto i : extension(~B)) (~C2)[i] = blas::gemv(A, (~B)[i]);
	}
	{
		boost::timer::auto_cpu_timer t; // 4.516157s
		for(auto i : extension(A)) C2[i] = blas::gemv(~B, A[i]);
	}
	{
		boost::timer::auto_cpu_timer t; // 32.705804s
		for(auto i:extension(A)) for(auto j:extension(~B)) C3[i][j] = blas::dot(A[i], (~B)[j]);
	}
	using namespace blas::operators;

	BOOST_TEST_REQUIRE( std::equal(
		begin(C), end(C), begin(C2), [](auto const& crow, auto const& c2row){
			return ((crow - c2row)^2) < 1e-13;
		}
	) );

	BOOST_TEST_REQUIRE( std::equal(
		begin(C), end(C), begin(C3), [](auto const& crow, auto const& c3row){
			return ((crow - c3row)^2) < 1e-13;
		}
	) );

}
#endif

#endif
#endif


// Qt5 //
#include <QApplication>
// Eigen //
#include <Eigen/Core>
// STL //
#include <vector>
// Plot //
#include <plot.h>

int main(int argc, char** argv)
{
  using namespace CppRobotics;
  using namespace std;

  // need to initialize Qt //
  QApplication app(argc, argv);

  // 2d plotting //
  Plot2D plt;

  vector<QColor> color {
    QColor(255,0,0),
    QColor(0,255,0),
    QColor(0,0,255)};

  // add points //
  for(uint i=0; i<10; i++) {
    plt.addGraph(Plot2D::GraphType::Point);
    plt.setColor(i,color[i%3]);
    plt.setSize(i,static_cast<double>((i+1)*5));
    Eigen::Vector2d p = 10000.0*Eigen::Vector2d::Random();
    plt.addPoint(i, p);
  }
  // show the plot //
  plt.showPlot();

  // app.exec() creates the loop for qt gui //
  return app.exec();
}


/**
 * \file WhiteNoiseGeneratorFilter.cpp
 */

#include "WhiteNoiseGeneratorFilter.h"

#include <cmath>
#include <cstdint>

#include <boost/math/constants/constants.hpp>

namespace ATK
{
  template<typename DataType_>
  WhiteNoiseGeneratorFilter<DataType_>::WhiteNoiseGeneratorFilter()
  :Parent(0, 1), volume(1), offset(0), dist(static_cast<DataType>(1e-3), static_cast<DataType>(2e-3))
  {

  }

  template<typename DataType_>
  WhiteNoiseGeneratorFilter<DataType_>::~WhiteNoiseGeneratorFilter()
  {

  }

  template<typename DataType_>
  void WhiteNoiseGeneratorFilter<DataType_>::set_volume(DataType_ volume)
  {
    this->volume = volume;
    dist.param(typename boost::random::uniform_real_distribution<DataType_>::param_type(static_cast<DataType>(offset - volume), static_cast<DataType>(offset + volume)));
  }

  template<typename DataType_>
  DataType_ WhiteNoiseGeneratorFilter<DataType_>::get_volume() const
  {
    return volume;
  }

  template<typename DataType_>
  void WhiteNoiseGeneratorFilter<DataType_>::set_offset(DataType_ offset)
  {
    this->offset = offset;
    dist.param(typename boost::random::uniform_real_distribution<DataType_>::param_type(static_cast<DataType>(offset - volume), static_cast<DataType>(offset + volume)));
  }

  template<typename DataType_>
  DataType_ WhiteNoiseGeneratorFilter<DataType_>::get_offset() const
  {
    return offset;
  }

  template<typename DataType_>
  void WhiteNoiseGeneratorFilter<DataType_>::process_impl(int64_t size) const
  {
    DataType* ATK_RESTRICT output = outputs[0];
    for(int64_t i = 0; i < size; ++i)
    {
      *(output++) = dist(gen);
    }
  }

  template class WhiteNoiseGeneratorFilter<float>;
  template class WhiteNoiseGeneratorFilter<double>;
}


//
// Created by yaoyu on 4/8/20.
//

#ifndef NUMPYIO_HPP
#define NUMPYIO_HPP

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include <cnpy.h>
#include <Eigen/Dense>

namespace stereo_utils
{

template < typename rT >
void write_depth_map_2_npy( const std::string &fn,
        const Eigen::MatrixX<rT> &mat,
        rT minLimit = 0 ) {
    const std::size_t rows = mat.rows();
    const std::size_t cols = mat.cols();
    std::vector<rT> data;

    for ( std::size_t j = 0; j < cols; ++j ) {
        for ( std::size_t i = 0; i < rows; ++i ) {
            const rT value = mat( i, j );

            if ( value > minLimit ) {
                data.push_back(static_cast<rT>(j));
                data.push_back(static_cast<rT>(i));
                data.push_back(value);
            }
        }
    }

    const std::size_t N = data.size();

    if ( 0 == N ) {
        std::stringstream ss;
        ss << "No pixels have depth over the limit of " << minLimit;
        throw( std::runtime_error( ss.str() ) );
    }

    cnpy::npy_save( fn, &data[0], { N/3, 3 }, "w" );
}

template < typename rT >
void write_eigen_matrix_2_npy( const std::string &fn,
        const Eigen::MatrixX<rT> &mat ) {
    const std::size_t rows = mat.rows();
    const std::size_t cols = mat.cols();

    if ( mat.IsRowMajor ) {
        cnpy::npy_save( fn, mat.data(), { rows, cols }, "w" );
    } else {
        Eigen::MatrixX<rT> temp = mat.transpose();
        cnpy::npy_save( fn, temp.data(), { rows, cols }, "w" );
    }
}

template < typename rT >
void read_npy_2_eigen_matrix( const std::string &fn,
        Eigen::MatrixX<rT> &mat ) {
    // Load the .npy file.
    cnpy::NpyArray arr = cnpy::npy_load(fn);

    rT* data = arr.data<rT>();

    assert( 2 == arr.shape.size() );

    const int dim0 = arr.shape[0];
    const int dim1 = arr.shape[1];

    mat.resize( dim0, dim1 );

    Eigen::Matrix<rT, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> temp =
            Eigen::Map< Eigen::Matrix<rT, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> > (
            data, dim0, dim1 );

    // Convert the row major matrix to column major?
    mat = temp;
}

} // namespace stereo_utils

#endif //NUMPYIO_HPP


/*
 [auto_generated]
 boost/numeric/odeint/iterator/adaptive_iterator.hpp

 [begin_description]
 Iterator for iterating throught the solution of an ODE with adaptive step size. The dereferenced types containes also the time.
 [end_description]

 Copyright 2012-2013 Karsten Ahnert
 Copyright 2012 Mario Mulansky

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_NUMERIC_ODEINT_ITERATOR_ADAPTIVE_ITERATOR_HPP_INCLUDED
#define BOOST_NUMERIC_ODEINT_ITERATOR_ADAPTIVE_ITERATOR_HPP_INCLUDED

#include <boost/numeric/odeint/util/stepper_traits.hpp>
#include <boost/numeric/odeint/util/unit_helper.hpp>
#include <boost/numeric/odeint/stepper/stepper_categories.hpp>
#include <boost/numeric/odeint/stepper/controlled_step_result.hpp>
#include <boost/numeric/odeint/iterator/impl/adaptive_iterator_impl.hpp>

namespace boost {
namespace numeric {
namespace odeint {

    /* use the adaptive_iterator_impl with the right tags */
    template< class Stepper , class System , class State
#ifndef DOXYGEN_SKIP
        , class StepperTag = typename base_tag< typename traits::stepper_category< Stepper >::type >::type
#endif
    >
    class adaptive_iterator : public adaptive_iterator_impl<
            adaptive_iterator< Stepper , System , State , StepperTag > ,
            Stepper , System , State , detail::ode_state_iterator_tag , StepperTag
        >
    {
        typedef typename traits::time_type< Stepper >::type time_type;
        typedef adaptive_iterator< Stepper , System , State , StepperTag > iterator_type;

    public:
        adaptive_iterator( Stepper stepper , System sys , State &s , time_type t_start , time_type t_end , time_type dt )
            : adaptive_iterator_impl< iterator_type , Stepper , System , State , detail::ode_state_iterator_tag , StepperTag >( stepper , sys , s , t_start , t_end , dt )
        {}

        adaptive_iterator( Stepper stepper , System sys , State &s )
            : adaptive_iterator_impl< iterator_type , Stepper , System , State , detail::ode_state_iterator_tag , StepperTag >( stepper , sys , s )
        {}
    };

    template< class Stepper , class System , class State >
    adaptive_iterator< Stepper , System , State > make_adaptive_iterator_begin(
        Stepper stepper ,
        System system ,
        State &x ,
        typename traits::time_type< Stepper >::type t_start ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
    {
        return adaptive_iterator< Stepper , System , State >( stepper , system , x , t_start , t_end , dt );
    }

    template< class Stepper , class System , class State >
    adaptive_iterator< Stepper , System , State > make_adaptive_iterator_end(
        Stepper stepper ,
        System system ,
        State &x )
    {
        return adaptive_iterator< Stepper , System , State >( stepper , system , x );
    }

    template< class Stepper , class System , class State >
    std::pair< adaptive_iterator< Stepper , System , State > , adaptive_iterator< Stepper , System , State > >
    make_adaptive_range(
        Stepper stepper ,
        System system ,
        State &x ,
        typename traits::time_type< Stepper >::type t_start ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
    {
        return std::make_pair(
            adaptive_iterator< Stepper , System , State >( stepper , system , x , t_start , t_end , dt ) ,
            adaptive_iterator< Stepper , System , State >( stepper , system , x )
            );
    }

    /**
     * \class adaptive_iterator
     *
     * \brief ODE Iterator with adaptive step size. The value type of this iterator is the state type of the stepper.
     *
     * Implements an iterator representing the solution of an ODE from t_start
     * to t_end evaluated at steps with an adaptive step size dt.
     * After each iteration the iterator dereferences to the state x at the next
     * time t+dt where dt is controlled by the stepper.
     * This iterator can be used with ControlledSteppers and
     * DenseOutputSteppers and it always makes use of the all the given steppers
     * capabilities. A for_each over such an iterator range behaves similar to
     * the integrate_adaptive routine.
     *
     * adaptive_iterator is a model of single-pass iterator.
     *
     * The value type of this iterator is the state type of the stepper. Hence one can only access the state and not the current time.
     *
     * \tparam Stepper The stepper type which should be used during the iteration.
     * \tparam System The type of the system function (ODE) which should be solved.
     * \tparam State The state type of the ODE.
     */

    /**
     * \fn make_adaptive_iterator_begin( Stepper stepper , System system , State &x ,
        typename traits::time_type< Stepper >::type t_start ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
     *
     * \brief Factory function for adaptive_iterator. Constructs a begin iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state.
     * \param t_start The initial time.
     * \param t_end The end time, at which the iteration should stop.
     * \param dt The initial time step.
     * \returns The adaptive iterator.
     */

    /**
     * \fn make_adaptive_iterator_end( Stepper stepper , System system , State &x )
     * \brief Factory function for adaptive_iterator. Constructs a end iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state.
     * \returns The adaptive iterator.
     */

    /**
     * \fn make_adaptive_range( Stepper stepper , System system , State &x ,
        typename traits::time_type< Stepper >::type t_start ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
     *
     * \brief Factory function to construct a single pass range of adaptive iterators. A range is here a pair of adaptive_iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state.
     * \param t_start The initial time.
     * \param t_end The end time, at which the iteration should stop.
     * \param dt The initial time step.
     * \returns The adaptive range.
     */

} // namespace odeint
} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_ODEINT_ITERATOR_ADAPTIVE_ITERATOR_HPP_INCLUDED


// 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/bool.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/functional/always.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/applicative.hpp>
#include <laws/base.hpp>
#include <laws/functor.hpp>
#include <laws/monad.hpp>
#include <laws/monad_plus.hpp>
namespace hana = boost::hana;
using hana::test::ct_eq;

int main() {
    auto eq_tuples = hana::make_tuple(
          hana::make_tuple()
        , hana::make_tuple(ct_eq<0>{})
        , hana::make_tuple(ct_eq<0>{}, ct_eq<1>{})
        , hana::make_tuple(ct_eq<0>{}, ct_eq<1>{}, ct_eq<2>{})
        , hana::make_tuple(ct_eq<0>{}, ct_eq<1>{}, ct_eq<2>{}, ct_eq<3>{})
        , hana::make_tuple(ct_eq<0>{}, ct_eq<1>{}, ct_eq<2>{}, ct_eq<3>{}, ct_eq<4>{})
        , hana::make_tuple(ct_eq<0>{}, ct_eq<1>{}, ct_eq<2>{}, ct_eq<3>{}, ct_eq<4>{}, ct_eq<5>{})
    );

    auto eq_values = hana::make_tuple(
        ct_eq<0>{},
        ct_eq<2>{},
        ct_eq<4>{}
    );

    auto predicates = hana::make_tuple(
        hana::equal.to(ct_eq<0>{}),
        hana::equal.to(ct_eq<2>{}),
        hana::equal.to(ct_eq<4>{}),
        hana::always(hana::true_c),
        hana::always(hana::false_c)
    );

    auto nested_eqs = hana::make_tuple(
          hana::make_tuple()
        , hana::make_tuple(
            hana::make_tuple(ct_eq<0>{})
        )
        , hana::make_tuple(
            hana::make_tuple(ct_eq<0>{}),
            hana::make_tuple(ct_eq<1>{}, ct_eq<2>{})
        )
        , hana::make_tuple(
            hana::make_tuple(ct_eq<0>{}),
            hana::make_tuple(ct_eq<1>{}, ct_eq<2>{}),
            hana::make_tuple(ct_eq<3>{}, ct_eq<4>{})
        )
    );

    hana::test::TestFunctor<hana::tuple_tag>{eq_tuples, eq_values};
    hana::test::TestApplicative<hana::tuple_tag>{eq_tuples};
    hana::test::TestMonad<hana::tuple_tag>{eq_tuples, nested_eqs};
    hana::test::TestMonadPlus<hana::tuple_tag>{eq_tuples, predicates, eq_values};
}


#include "MexPackUnpack.h"
#include "mex.h"
#include <Eigen>
#include<typeindex>

using namespace MexPackUnpackTypes;
struct userStruct{
  int a;
  double b;
  EDRM c;
  std::string d;
};

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {

  MexUnpacker<userStruct> my_unpack(nrhs, prhs);

  try {

    auto [d] = my_unpack.unpackMex();
    d.a=4;

    std::map<std::type_index,std::vector<std::string> > my_name_map;
    my_name_map[typeid(userStruct)] = {"my_int","my_double","my_array", "my_string"};

    MexPacker<userStruct >my_pack(nlhs, plhs,my_name_map);
    my_pack.PackMex(d);

  } catch (std::string s) {
    mexPrintf(s.data());
  }
}


/* ====================================================================== */
/* === umfpack_zi_demo ================================================== */
/* ====================================================================== */

/* ---------------------------------------------------------------------- */
/* UMFPACK Version 4.1 (Apr. 30, 2003), Copyright (c) 2003 by Timothy A.  */
/* Davis.  All Rights Reserved.  See ../README for License.               */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.        */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                   */
/* ---------------------------------------------------------------------- */

/***********************************************************************/
/*         UMFPACK Copyright, License and Availability                 */
/***********************************************************************/
/*
 *
 * UMFPACK Version 4.1 (Apr. 30, 2003),  Copyright (c) 2003 by Timothy A.
 * Davis.  All Rights Reserved.
 *
 * UMFPACK License:
 *
 *   Your use or distribution of UMFPACK or any modified version of
 *   UMFPACK implies that you agree to this License.
 *
 *   THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *   EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 *   Permission is hereby granted to use or copy this program, provided
 *   that the Copyright, this License, and the Availability of the original
 *   version is retained on all copies.  User documentation of any code that
 *   uses UMFPACK or any modified version of UMFPACK code must cite the
 *   Copyright, this License, the Availability note, and "Used by permission."
 *   Permission to modify the code and to distribute modified code is granted,
 *   provided the Copyright, this License, and the Availability note are
 *   retained, and a notice that the code was modified is included.  This
 *   software was developed with support from the National Science Foundation,
 *   and is provided to you free of charge.
 *
 * Availability:
 *
 *   http://www.cise.ufl.edu/research/sparse/umfpack
 *
 */

/* Used by permission. */

/* modified by Kresimir Fresl, 2003              */

/* UMFPACK bindings & ublas::compressed_matrix<> */

/*
  A demo of UMFPACK:   umfpack_zi_* version.

  First, factor and solve a 5-by-5 system, Ax=b, using default parameters.
  Then solve A'x=b using the factors of A.   Modify one entry (A (1,4) = 0,
  where the row and column indices range from 0 to 4.  The pattern of A
  has not changed (it has explicitly zero entry), so a reanalysis with
  umfpack_zi_symbolic does not need to be done.  Refactorize (with
  umfpack_zi_numeric), and solve Ax=b.  Note that the pivot ordering has
  changed.  Next, change all of the entries in A, but not the pattern.

  Finally, compute C = A', and do the symbolic and numeric factorization of C.
  Factorizing A' can sometimes be better than factorizing A itself (less work
  and memory usage).  Solve C'x=b; the solution is the same as the
  solution to Ax=b.
*/

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <algorithm>
#include <complex>
#include <math.h>
#include <boost/mpl/and.hpp>
#include <boost/numeric/bindings/traits/c_array.hpp>
#include <boost/numeric/bindings/traits/std_vector.hpp>
#include <boost/numeric/bindings/traits/ublas_sparse.hpp>
#include <boost/numeric/bindings/umfpack/umfpack.hpp>

using std::max;
using std::cout;
using std::cin;
using std::endl;
using std::exit;

namespace ublas = boost::numeric::ublas;
namespace umf = boost::numeric::bindings::umfpack;
namespace traits = boost::numeric::bindings::traits;

typedef ublas::compressed_matrix<
  std::complex<double>,
  ublas::column_major,
  0,
  ublas::unbounded_array<int>,
  ublas::unbounded_array<std::complex<double> >
> cm_t;

typedef std::vector<std::complex<double> > v_t;

/* --------------------------------------------------------------- */
/* wait for 'y'                                                    */
/* --------------------------------------------------------------- */

void wait4y() {
  char yes = 'n';
  while (1) {
    cout << "Continue (y/n) -> ";
    cin >> yes;
    if (yes == 'y') break;
    if (yes == 'n') exit (0);
  }
}

/* --------------------------------------------------------------- */
/* error: print a message and exit                                 */
/* --------------------------------------------------------------- */

void error (char *message) {
  cout << "\n\n====== error: " << message << " =====\n" << endl;
  exit (1);
}

/* ------------------------------------------------------------------------- */
/* resid: compute the residual, r = Ax-b or r = A'x=b and return maxnorm (r) */
/* A' is the complex conjugate transpose, not the array transpose            */
/* note: works only with compressed column matrices                          */
/* ------------------------------------------------------------------------- */

template <typename M, typename V>
double resid (M const& A, V const& x, V const& b, V& r, int transpose = 0) {

  double norm;
  int const* Ap = traits::spmatrix_index1_storage (A);
  int const* Ai = traits::spmatrix_index2_storage (A);
  std::complex<double> const* Ax = traits::spmatrix_value_storage (A);

  int n = traits::vector_size (r);

  for (int i = 0; i < n; i++)
    r [i] = -b [i];

  if (transpose) {
    int i;
    for (int j = 0; j < n; j++)
      for (int p = Ap [j]; p < Ap [j+1]; p++) {
        i = Ai [p];
        r [j] += std::conj (Ax [p]) * x [i];
      }
  } else {
    int i;
    for (int j = 0; j < n; j++)
      for (int p = Ap [j]; p < Ap [j+1]; p++) {
        i = Ai [p];
        r [i] += Ax [p] * x [j];
      }
  }

  norm = 0. ;
  for (int i = 0 ; i < n ; i++)
    norm = max (std::abs (r [i]), norm);
  return (norm) ;
}

/* ------------------------------------------------------------------- */
/* main program                                                        */
/* ------------------------------------------------------------------- */

int main (int argc, char **argv) {
  bool call_wait4y = true;
  if (argc > 1) {
    char *s = argv [1];
    if (s [0] == '-' && s [1] == 'y') {
      call_wait4y = false;
    }
  }

  /* ---------------------------------------------------------------- */
  /* initialisations                                                  */
  /* ---------------------------------------------------------------- */

  cout << "\n" << UMFPACK_VERSION << " demo: _di_ version\n" << endl;

  // get the default control parameters
  umf::control_type<std::complex<double> > Control;

  // change the default print level for this demo
  // (otherwise, nothing will print)
  Control [UMFPACK_PRL] = 6;
  // print the license agreement
  umf::report_status (Control, UMFPACK_OK);
  Control [UMFPACK_PRL] = 5;
  // print the control parameters
  umf::report_control (Control);

  umf::info_type<std::complex<double> > Info;

  // matrix A

  int    n = 5, nz = 12 ;
  int    Arow [ ] = { 0,  4,  1,  1,   2,   2,  0,  1,  2,  3,  4,  4} ;
  int    Acol [ ] = { 0,  4,  0,  2,   1,   2,  1,  4,  3,  2,  1,  2} ;
  double Aval [ ] = {2., 1., 3., 4., -1., -3., 3., 6., 2., 1., 4., 2.} ;
  double Avalz[ ] = {1., .4, .1, .2, -1., -.2, 0., 6., 3., 0., .3, .3} ;

  cm_t A (n, n, nz);
  for (int i = 0; i < nz; ++i)
    A (Arow[i], Acol[i]) = std::complex<double> (Aval[i], Avalz[i]);

  cout << "matrix A: ";
  umf::report_matrix (A, Control);

  // right-hand-side etc.

  double bx[ ] = {8., 45., -3., 3., 19.};
  double bz[ ] = {1., -5., -2., 0., 2.2};

  v_t b (n), x (n), r (n);
  for (int i = 0; i < n; ++i)
    b[i] = std::complex<double> (bx[i], bz[i]);

  cout << "vector B: ";
  umf::report_vector (b, Control);

  /* ---------------------------------------------------------------- */
  /* symbolic factorisation                                           */
  /* ---------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSymbolic factorisation:" << endl;

  umf::symbolic_type<std::complex<double> > Symbolic;

  int status = umf::symbolic (A, Symbolic, Control, Info);

  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::symbolic failed");
  }
  umf::report_symbolic (Symbolic, Control);

  /* ---------------------------------------------------------------- */
  /* numeric factorisation                                            */
  /* ---------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nNumeric factorisation:" << endl;

  umf::numeric_type<std::complex<double> > Numeric;

  status = umf::numeric (A, Symbolic, Numeric,Control, Info);

  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::numeric failed");
  }
  umf::report_numeric (Numeric, Control);

  /* ---------------------------------------------------------------- */
  /* solve Ax=b                                                       */
  /* ---------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSolve Ax = b:" << endl;

  status = umf::solve (UMFPACK_A, A, x, b, Numeric, Control, Info);

  umf::report_status (Control, status);
  umf::report_info (Control, Info);
  if (status < 0)
    error ("umf::solve failed");

  cout << "\nx (solution of Ax=b): ";
  umf::report_vector (x, Control);

  double rnorm = resid (A, x, b, r);
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;

  /* ---------------------------------------------------------------- */
  /* solve Ax=b, broken down into steps                               */
  /* ---------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSolve Ax = b (broken down into steps):" << endl;

  // Rb = R*b
  std::vector<std::complex<double> > Rb (n), y (n);
  status = umf::scale (Rb, b, Numeric);
  if (status < 0)
    error ("umf::scale failed");

  // solve Ly = P*(Rb)
  status = umf::solve (UMFPACK_Pt_L, A, y, Rb, Numeric, Control, Info);
  if (status < 0)
    error ("first umf::solve failed");

  // solve UQ'x=y
  status = umf::solve (UMFPACK_U_Qt, A, x, y, Numeric, Control, Info);
  if (status < 0)
    error ("second umf::solve failed");

  cout << "\nx (solution of Ax=b, solve is split into 3 steps): ";
  umf::report_vector (x, Control);

  rnorm = resid (A, x, b, r);
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;

  /* ------------------------------------------------------------------- */
  /* solve A'x=b                                                         */
  /* note that this is the complex conjugate transpose, A'               */
  /* ------------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSolve A'x = b:" << endl;

  status = umf::solve (UMFPACK_At, A, x, b, Numeric, Control, Info);

  umf::report_status (Control, status);
  umf::report_info (Control, Info);
  if (status < 0)
    error ("umf::solve failed");

  cout << "\nx (solution of A'x=b): ";
  umf::report_vector (x, Control);

  rnorm = resid (A, x, b, r, 1); // '1' -- transpose
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;

  /* ----------------------------------------------------------------- */
  /* modify one numerical value in the column-form of A                */
  /* ----------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nModify A (1,4) if present:" << endl;

  int row = 1, col = 4;
  cm_t::iterator1 i1 = A.begin1();
  ++i1; // go to row 1
  cm_t::iterator2 i2 = i1.begin();
  for (; i2 != i1.end(); ++i2)
    if (i2.index2() == 4) break;
  if (i2 != i1.end()) {
    if (i2.index1() != 1 || i2.index2() != 4)
      error ("something\'s very wrong");
    cout << "\nchanging A (1,4) to zero" << endl;
    A (1, 4) = 0.0;
  }

  cout << "\nmodified A: ";
  umf::report_matrix (A, Control);

  /* ----------------------------------------------------------------- */
  /* redo the numeric factorization                                    */
  /* ----------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nRedo the numeric factorization:" << endl;

  // The pattern (Ap and Ai) hasn't changed, so the symbolic factorisation
  // doesn't have to be redone, no matter how much we change Ax.

  // We don't need the Numeric object any more, so free it.
  umf::free (Numeric);  // or: Numeric.free();
  // Note that a memory leak would have occured if the old Numeric
  // had not been free'd.

  status = umf::numeric (A, Symbolic, Numeric, Control, Info);

  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::numeric failed");
  }

  cout << "\nNumeric factorization of modified A: ";
  umf::report_numeric (Numeric, Control);

  /* ------------------------------------------------------------------ */
  /* solve Ax=b, with the modified A                                    */
  /* ------------------------------------------------------------------ */

  if (call_wait4y) wait4y();

  cout << "\nSolve Ax = b with the modified A:" << endl;

  status = umf::solve (A, x, b, Numeric, Control, Info);

  umf::report_status (Control, status);
  umf::report_info (Control, Info);
  if (status < 0)
    error ("umf::solve failed");

  cout << "\nx (with modified A): ";
  umf::report_vector (x, Control);

  rnorm = resid (A, x, b, r);
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;

  /* ------------------------------------------------------------------ */
  /* modify all of the numerical values of A, but not the pattern       */
  /* ------------------------------------------------------------------ */

  if (call_wait4y) wait4y();

  cout << "\nModify numerical values of A:" << endl;

  for (i2 = A.begin2(); i2 != A.end2(); ++i2)
    for (i1 = i2.begin(); i1 != i2.end(); ++i1) {
      std::complex<double> c = *i1;
      cout << "changing A ("
           << i1.index1() << "," << i1.index2() << ") from " << c;
      double a = std::real (c) + 10 * i1.index2() - i1.index1();
      double ai = std::imag (c);
      c = std::complex<double> (a, ai);
      cout << " to " << c << endl;
      *i1 = c;
    }
  cout << endl;

  cout << "\ncompletely modified A (same pattern): ";
  umf::report_matrix (A, Control);

  /* ------------------------------------------------------------------ */
  /* redo the numeric factorization                                     */
  /* ------------------------------------------------------------------ */

  if (call_wait4y) wait4y();

  cout << "\nRedo the numeric factorization:" << endl;

  umf::free (Numeric);
  status = umf::numeric (A, Symbolic, Numeric, Control, Info);
  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::numeric failed");
  }

  cout << "\nNumeric factorization of completely modified A: ";
  umf::report_numeric (Numeric, Control);

  /* ------------------------------------------------------------------ */
  /* solve Ax=b, with the modified A                                    */
  /* ------------------------------------------------------------------ */

  if (call_wait4y) wait4y();

  cout << "\nSolve Ax = b with the modified A:" << endl;

  status = umf::solve (A, x, b, Numeric, Control, Info);

  umf::report_status (Control, status);
  umf::report_info (Control, Info);
  if (status < 0)
    error ("umf::solve failed");

  cout << "\nx (with completely modified A): ";
  umf::report_vector (x, Control);

  rnorm = resid (A, x, b, r);
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;

  /* ---------------------------------------------------------------- */
  /* free the symbolic and numeric factorization                      */
  /* ---------------------------------------------------------------- */

  Symbolic.free();
  Numeric.free();

  /* ---------------------------------------------------------------- */
  /* C = transpose of A                                               */
  /* ---------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nC = transpose of A:" << endl;

  cm_t C (herm (A));

  umf::report_matrix (C, Control);

  /* ----------------------------------------------------------------- */
  /* symbolic factorization of C                                       */
  /* ----------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSymbolic factorization of C:" << endl;

  status = umf::symbolic (C, Symbolic,Control, Info);

  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::symbolic failed");
  }
  umf::report_symbolic (Symbolic, Control);

  /* ----------------------------------------------------------------- */
  /* numeric factorization of C                                        */
  /* ----------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nNumeric factorization of C:" << endl;

  status = umf::numeric (C, Symbolic, Numeric,Control, Info);

  umf::report_status (Control, status);
  if (status < 0) {
    umf::report_info (Control, Info);
    error ("umf::numeric failed");
  }
  umf::report_numeric (Numeric, Control);

  /* ----------------------------------------------------------------- */
  /* solve C'x=b                                                       */
  /* ----------------------------------------------------------------- */

  if (call_wait4y) wait4y();

  cout << "\nSolve C'x = b:" << endl;

  status = umf::solve (UMFPACK_At, C, x, b, Numeric, Control, Info);
  umf::report_status (Control, status);
  umf::report_info (Control, Info);
  if (status < 0)
    error ("umf::solve failed");

  cout << "\nx (solution of C'x=b): ";
  umf::report_vector (x, Control);

  rnorm = resid (C, x, b, r, 1);
  cout << "\nmaxnorm of residual: " << rnorm << endl << endl;
}


/*
 * NOTE:
 *
 * Linker flags required for openssl:
 *
 * -lssl -lcrypto
 */
#include <algorithm>
#include <array>
#include <charconv>
#include <iostream>
#include <iterator>
#include <limits>

#include <openssl/md5.h>

#include <boost/range/irange.hpp>

// openssl requires unsigned char for its interfaces
using uchar = unsigned char;

auto solution(const uchar key[], std::size_t keylen) {

	auto hash = std::array<uchar, MD5_DIGEST_LENGTH>{};

	auto md5 = MD5_CTX{};

	MD5_Init(&md5);
	MD5_Update(&md5, key, keylen);

	using uint32 = std::uint32_t;
	using uint32_limits = std::numeric_limits<uint32>;

	// this is where the suffixes to the key will be stored -
	// size of array is equal to number of digits of maximum
	// value of an unsigned int
	auto outstream = std::array<char, uint32_limits::digits>{};

	const auto begin = uint32{117946 + 1}; // avoid repeating computations from Part 1
	const auto end   = uint32_limits::max();

	const auto range = boost::irange(begin, end);

	const auto result = std::find_if(range.begin(), range.end(), [&] (auto i) {

		const auto begin = outstream.begin();
		const auto end   = outstream.end();

		const auto ptr = std::to_chars(begin, end, i).ptr;

		const auto data = reinterpret_cast<uchar*>(begin);
		const auto outlen = std::distance(begin, ptr);

		auto md5_copy = md5;

		MD5_Update(&md5_copy, data, outlen);
		MD5_Final(hash.data(), &md5_copy);

		const auto prefix = boost::irange(uint32{3});

		// each element in hash is one-byte-sized (char) and represents two hexadecimal characters -
		// in other words, an all-zero hash[0-2] (3 elements) is equal to "000000"
		const auto has_prefix = std::all_of(prefix.begin(), prefix.end(), [&hash] (auto i) {
			return (hash[i] == 0);
		});

		return has_prefix;
	});

	return *result;
}

int main() {

  const uchar input[] = "ckczppom";
	std::cout << solution(input, (sizeof(input) - 1)) << std::endl;

}


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_BITINTEGER_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_BITINTEGER_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-ieee
    This function object transforms a floating point value
    in an integer with a preserving order formula.
    This function is the converse of @ref bitfloating.

    @par Header <boost/simd/function/bitinteger.hpp>

    @par Note:
    This function is related to the computation of
    floating successors or predecessors as the ordering of the integers obtained
    by @c bifloating is the same as the original order on the floating elements,
    an increment can be used to find the successor.

    @see next, prev, successor,  predecessor,  nextafter, bitfloating

    @par Example:

      @snippet bitinteger.cpp bitinteger

    @par Possible output:

      @snippet bitinteger.txt bitinteger

  **/
  as_integer_t<Value> bitinteger(Value const& x);
} }
#endif

#include <boost/simd/function/scalar/bitinteger.hpp>
#include <boost/simd/function/simd/bitinteger.hpp>

#endif


//////////////////////////////////////////////////////////////////////////////
///   Copyright 2003 and onward LASMEA UMR 6602 CNRS/U.B.P Clermont-Ferrand
///   Copyright 2009 and onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
///
///          Distributed under the Boost Software License, Version 1.0
///                 See accompanying file LICENSE.txt or copy at
///                     http://www.boost.org/LICENSE_1_0.txt
//////////////////////////////////////////////////////////////////////////////
#ifndef NT2_TOOLBOX_POLYNOMIALS_FUNCTION_SCALAR_PLEVL_HPP_INCLUDED
#define NT2_TOOLBOX_POLYNOMIALS_FUNCTION_SCALAR_PLEVL_HPP_INCLUDED
#include <nt2/include/functions/tchebeval.hpp>
#include <nt2/include/functions/fma.hpp>
#include <nt2/sdk/meta/fusion.hpp>
#include <boost/fusion/adapted/array.hpp>

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is arithmetic_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::plevl_, tag::cpu_,
                       (A0)(A1),
                       (arithmetic_<A0>)(fusion_sequence_<A1>)
                      )

namespace nt2 { namespace ext
{
  template<class Dummy>
  struct call<tag::plevl_(tag::arithmetic_,tag::fusion_sequence_),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0,class A1>
    struct result<This(A0, A1)> :
      std::tr1::result_of<meta::floating(A0)>{};

    NT2_FUNCTOR_CALL(2)
    {
      typedef typename NT2_RETURN_TYPE(2)::type type;
      return plevl(type(a0), a1);
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is real_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::plevl_, tag::cpu_,
                       (A0)(A1),
                       (real_<A0>)(fusion_sequence_<A1>)
                      )

namespace nt2 { namespace ext
{
  template<class Dummy>
  struct call<tag::plevl_(tag::real_,tag::fusion_sequence_),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0,class A1>
    struct result<This(A0, A1)> :
      std::tr1::result_of<meta::floating(A0)>{};

    NT2_FUNCTOR_CALL(2)
    {
      typename A1::const_iterator p = a1.begin();
      A0 ans = a0+*p++;
      do
      ans = fma(ans, a0, *p);
      while( ++p !=  a1.end());
      return ans;
    }
  };
} }

#endif
// modified by jt the 26/12/2010
// modified manually by jt the 26/12/2010