/**
 * \file boost/numeric/ublasx/operation/eigen.hpp
 *
 * \brief Compute the eigenvalues and eigenvectors of a single matrix or the
 *  generalized eigenvalues and eigeinvector of a pair of matrices.
 *
 * <hr/>
 *
 * Copyright (c) 2010-2011, Marco Guazzone
 *
 * 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)
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */

#ifndef BOOST_NUMERIC_UBLASX_OPERATION_EIGEN_HPP
#define BOOST_NUMERIC_UBLASX_OPERATION_EIGEN_HPP

#include <boost/mpl/and.hpp>
#include <boost/numeric/bindings/lapack/driver/geev.hpp>
#include <boost/numeric/bindings/lapack/driver/ggev.hpp>
#include <boost/numeric/bindings/lapack/driver/heev.hpp>
#include <boost/numeric/bindings/lapack/driver/hegv.hpp>
#include <boost/numeric/bindings/lapack/driver/syev.hpp>
#include <boost/numeric/bindings/lapack/driver/sygv.hpp>
#include <boost/numeric/bindings/ublas.hpp>
#include <boost/numeric/ublas/expression_types.hpp>
#include <boost/numeric/ublas/exception.hpp>
#include <boost/numeric/ublas/hermitian.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/symmetric.hpp>
#include <boost/numeric/ublas/traits.hpp>
#include <boost/numeric/ublasx/detail/debug.hpp>
#include <boost/numeric/ublasx/detail/lapack.hpp>
#include <boost/numeric/ublasx/operation/num_columns.hpp>
#include <boost/numeric/ublasx/operation/num_rows.hpp>
#include <boost/numeric/ublasx/operation/size.hpp>
#include <boost/numeric/ublasx/traits/layout_type.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_complex.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/utility/enable_if.hpp>
#include <cmath>
#include <complex>
#include <limits>

namespace boost { namespace numeric { namespace ublasx {

using namespace ::boost::numeric::ublas;

namespace detail { namespace /*<unnamed>*/ {

/// Side of eigenvectors
enum eigenvectors_side
{
	none_eigenvectors, ///< None eigenvectors are to be selected.
	left_eigenvectors, ///< Only the left eigenvectors are to be selected.
	right_eigenvectors, ///< Only the right eigenvectors are to be selected.
	both_eigenvectors ///< Both left and right eigenvectors are to be selected.
};

//@{ Eigenvalues problem

/// Eigenvalues of a general real matrix (column-major case).
//FIXME: It seems that LAPACK (v. 3.2.1) wants that VR and VL have right dimensions even if they should be not referenced (e.g., jobvl='N' or jobvr='N').
template <
	typename MatrixExprT, 	 // must be of real type
	typename OutRealVectorT, // must be of real type
	typename OutImagVectorT, // must be of real type
	typename OutLeftMatrixT, // must be of complex type
	typename OutRightMatrixT // must be of complex type
>
void eigen_impl(matrix_expression<MatrixExprT> const& A, eigenvectors_side side, OutRealVectorT& rw, OutImagVectorT& iw, OutLeftMatrixT& LV, OutRightMatrixT& RV, column_major_tag)
{
	// precondition: A must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<MatrixExprT>::value_type>::value
	));
	// precondition: rw must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<OutRealVectorT>::value_type>::value
	));
	// precondition: iw must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<OutImagVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutLeftMatrixT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutRightMatrixT>::value_type>::value
	));

	typedef typename matrix_traits<MatrixExprT>::value_type value_type;
	typedef typename matrix_traits<MatrixExprT>::size_type size_type;
	typedef matrix<value_type, column_major> work_matrix_type;

	size_type n = num_rows(A);

	char jobvl;
	char jobvr;
	size_type work_n_LV;
	size_type out_n_LV;
	size_type work_n_RV;
	size_type out_n_RV;

	// Copy the original A matrix since LAPACK GEEV overwrites it.
	work_matrix_type tmp_A(A);

	switch (side)
	{
		case both_eigenvectors:
			jobvl = jobvr = 'V';
			work_n_LV = out_n_LV = work_n_RV = out_n_RV = n;
			break;
		case left_eigenvectors:
			jobvl = 'V';
			jobvr = 'N';
			work_n_LV = out_n_LV = n;
			work_n_RV = detail::lapack::min_array_size;
			out_n_RV = 0;
			break;
		case right_eigenvectors:
			jobvl = 'N';
			jobvr = 'V';
			work_n_LV = detail::lapack::min_array_size;
			out_n_LV = 0;
			work_n_RV = out_n_RV = n;
			break;
		case none_eigenvectors:
		default:
			jobvl = jobvr = 'N';
			work_n_LV = work_n_RV = detail::lapack::min_array_size;
			out_n_LV = out_n_RV = 0;
	}

	// Create temporary real matrices for eigenvectors
	// NOTE: LAPACK wants the arrays be correctly sized even if they are not
	// requested as output (i.e., jobvl == 'N' and/or jobvr == 'N').
	work_matrix_type tmp_LV(work_n_LV, work_n_LV);
	work_matrix_type tmp_RV(work_n_RV, work_n_RV);

	if (size(rw) != n)
	{
		rw.resize(n, false);
	}
	if (size(iw) != n)
	{
		iw.resize(n, false);
	}

	::boost::numeric::bindings::lapack::geev(
		jobvl,
		jobvr,
		tmp_A,
		rw,
		iw,
		tmp_LV,
		tmp_RV
	);

	// Resize output complex eigenvectors matrices ...

	if (num_rows(LV) != out_n_LV || num_columns(LV) != out_n_LV)
	{
		LV.resize(out_n_LV, out_n_LV, false);
	}
	if (num_rows(RV) != out_n_RV || num_columns(RV) != out_n_RV)
	{
		RV.resize(out_n_RV, out_n_RV, false);
	}

	// ... and possibly fill eigenvectors matrices LV and RV

	if (out_n_LV > 0 || out_n_RV > 0)
	{
		// The algorithm for computing left and right eigenvectors is the
		// following:
		// - The left eigenvectors u(j) are stored one
		//   after another in the columns of LV, in the same order as
		//   their eigenvalues. If the j-th eigenvalue is real, then
		//   u(j) = LV(:,j), the j-th column of LV. If the j-th and
		//   (j+1)-th eigenvalues form a complex conjugate pair, then
		//   u(j) = LV(:,j)+i*LV(:,j+1) and u(j+1) = LV(:,j)-i*LV(:,j+1).
		// - The right eigenvectors v(j) are stored one
		//   after another in the columns of RV, in the same order as
		//   their eigenvalues. If the j-th eigenvalue is real, then
		//   v(j) = RV(:,j), the j-th column of RV. If the j-th and
		//   (j+1)-th eigenvalues form a complex conjugate pair, then
		//   v(j) = RV(:,j)+i*RV(:,j+1) and v(j+1) = RV(:,j)-i*RV(:,j+1).
		// .
		// Each eigenvector is scaled so the largest component has
		// abs(real part)+abs(imag. part)=1.
		//

		for (size_type i = 0; i < n; ++i)
		{
			for (size_type j = 0; j < n; ++j)
			{
				if (iw(j) != 0.0)
				{
					if (out_n_LV > 0)
					{
						value_type lv1 = tmp_LV(i,j);
						value_type lv2 = tmp_LV(i,j+1);

						LV(i,j) = ::std::complex<value_type>(lv1, lv2);
						LV(i,j+1) = ::std::complex<value_type>(lv1, -lv2);
					}

					if (out_n_RV > 0)
					{
						value_type rv1 = tmp_RV(i,j);
						value_type rv2 = tmp_RV(i,j+1);

						RV(i,j) = ::std::complex<value_type>(rv1, rv2);
						RV(i,j+1) = ::std::complex<value_type>(rv1, -rv2);
					}

					++j;
				}
				else
				{
					if (out_n_LV > 0)
					{
						LV(i,j) = tmp_LV(i,j);
					}
					if (out_n_RV > 0)
					{
						RV(i,j) = tmp_RV(i,j);
					}
				}
			}
		}
	}
}

/// Eigenvalues of a general real matrix (row-major case).
template <
	typename MatrixExprT,
	typename OutRealVectorT,
	typename OutImagVectorT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
void eigen_impl(matrix_expression<MatrixExprT> const& A, eigenvectors_side side, OutRealVectorT& rw, OutImagVectorT& iw, OutLeftMatrixT& LV, OutRightMatrixT& RV, row_major_tag)
{
	// Note: LAPACK works with column-major matrices

	typedef typename matrix_traits<MatrixExprT>::value_type value_type;

	typedef matrix<value_type, column_major> colmaj_matrix_type;

	colmaj_matrix_type tmp_A(A);
	colmaj_matrix_type tmp_LV;
	colmaj_matrix_type tmp_RV;

	eigen_impl(tmp_A, side, rw, iw, tmp_LV, tmp_RV, column_major_tag());

	LV = tmp_LV;
	RV = tmp_RV;
}

/// Eigenvalues of a general complex matrix (column-major case).
//FIXME: It seems that LAPACK (v. 3.2.1) wants that VR and VL have right dimensions even if they should be not referenced (e.g., jobvl='N' or jobvr='N').
template <
	typename MatrixExprT,	 // must be of complex type
	typename OutVectorT, 	 // must be of complex type
	typename OutLeftMatrixT, // must be of complex type
	typename OutRightMatrixT // must be of complex type
>
typename ::boost::enable_if<
	::boost::is_complex<typename matrix_traits<MatrixExprT>::value_type>,
	void
>::type eigen_impl(matrix_expression<MatrixExprT> const& A, eigenvectors_side side, OutVectorT& w, OutLeftMatrixT& LV, OutRightMatrixT& RV, column_major_tag)
{
	// precondition: A must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<MatrixExprT>::value_type>::value
	));
	// precondition: w must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutLeftMatrixT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutRightMatrixT>::value_type>::value
	));

	typedef typename matrix_traits<MatrixExprT>::value_type value_type;
	typedef typename matrix_traits<MatrixExprT>::size_type size_type;

	size_type n = num_rows(A);
	size_type n_lv;
	size_type n_rv;

	matrix<value_type, column_major> tmp_A(A); // LAPACK GEEV overwrites the original input matrix A

	char jobvl;
	char jobvr;

	switch (side)
	{
		case both_eigenvectors:
			jobvl = jobvr = 'V';
			n_lv = n_rv = n;
			break;
		case left_eigenvectors:
			jobvl = 'V';
			jobvr = 'N';
			n_lv = n;
			n_rv = 0;
			break;
		case right_eigenvectors:
			jobvl = 'N';
			jobvr = 'V';
			n_lv = 0;
			n_rv = n;
			break;
		case none_eigenvectors:
		default:
			jobvl = jobvr = 'N';
			n_lv = n_rv = 0;
	}

	if (size(w) != n)
	{
		w.resize(n, false);
	}
	if (num_rows(LV) != n || num_columns(LV) != n)
	{
		LV.resize(n, n, false);
	}
	if (num_rows(RV) != n || num_columns(RV) != n)
	{
		RV.resize(n, n, false);
	}

	::boost::numeric::bindings::lapack::geev(jobvl, jobvr, tmp_A, w, LV, RV);

	if (num_rows(LV) != n_lv)
	{
		LV.resize(n_lv, n_lv, true);
	}
	if (num_rows(RV) != n_rv)
	{
		RV.resize(n_rv, n_rv, true);
	}
}

/// Eigenvalues of a general real matrix (column-major case).
template <
	typename MatrixExprT,
	typename OutVectorT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
typename ::boost::disable_if<
	::boost::is_complex<typename matrix_traits<MatrixExprT>::value_type>,
	void
>::type eigen_impl(matrix_expression<MatrixExprT> const& A, eigenvectors_side side, OutVectorT& w, OutLeftMatrixT& LV, OutRightMatrixT& RV, column_major_tag)
{
	// precondition: A must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<MatrixExprT>::value_type>::value
	));
	// precondition: w must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutLeftMatrixT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutRightMatrixT>::value_type>::value
	));

	typedef typename matrix_traits<MatrixExprT>::value_type value_type;
	typedef typename vector_traits<OutVectorT>::size_type size_type;
	typedef typename vector_traits<OutVectorT>::value_type out_value_type;

	size_type n_w = size(w);
	size_type n = num_rows(A);

	if (n_w != n)
	{
		w.resize(n, false);
		n_w = n;
	}

	vector<value_type> rw(n_w);
	vector<value_type> iw(n_w);

	eigen_impl(A, side, rw, iw, LV, RV, column_major_tag());

	for (size_type i = 0; i < n_w; ++i)
	{
		// Assume that out_value_type is a complex-like type
		//w(i) = ::std::complex<value_type>(rw(i), iw(i));
		w(i) = out_value_type(rw(i), iw(i));
	}
}

/// Eigenvalues of a general real/complex matrix (row-major case).
template <
	typename MatrixExprT,
	typename OutVectorT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
void eigen_impl(matrix_expression<MatrixExprT> const& A, eigenvectors_side side, OutVectorT& w, OutLeftMatrixT& LV, OutRightMatrixT& RV, row_major_tag)
{
	typedef typename promote_traits<
						typename matrix_traits<MatrixExprT>::value_type,
						typename promote_traits<
							typename matrix_traits<OutRightMatrixT>::value_type,
							typename matrix_traits<OutLeftMatrixT>::value_type
						>::promote_type
			>::promote_type value_type;

	typedef matrix<value_type, column_major> colmaj_matrix_type;

	colmaj_matrix_type tmp_A(A);
	colmaj_matrix_type tmp_LV;
	colmaj_matrix_type tmp_RV;

	eigen_impl(tmp_A, side, w, tmp_LV, tmp_RV, column_major_tag());

	LV = tmp_LV;
	RV = tmp_RV;
}

/// Eigenvalues of a hermitian matrix (column-major case).
template <
	typename ValueT,
	typename TriangularT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen_impl(hermitian_matrix<ValueT,TriangularT,column_major> const& A, eigenvectors_side side, OutVectorT& w, OutMatrixT& V)
{
	// precondition: A must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<ValueT>::value
	));
	// precondition: w must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: V must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<OutMatrixT>::value_type>::value
	));

	typedef hermitian_matrix<ValueT, TriangularT, column_major> matrix_type;
	typedef typename matrix_traits<matrix_type>::size_type size_type;
	typedef OutMatrixT out_matrix_type;
    typedef hermitian_adaptor<out_matrix_type, TriangularT> work_matrix_type;

	size_type n = num_rows(A);
	size_type n_v;

	char jobvz;

	switch (side)
	{
		case both_eigenvectors:
			jobvz = 'V';
			n_v = n;
			break;
		case none_eigenvectors:
		default:
			jobvz = 'N';
			n_v = 0;
	}

	if (size(w) != n)
	{
		w.resize(n, false);
	}
	if (num_rows(V) != n_v)
	{
		V.resize(n_v, n_v, false);
	}

	out_matrix_type aux_A(A);
	work_matrix_type tmp_A(aux_A);

	::boost::numeric::bindings::lapack::heev(jobvz, tmp_A, w);

	if (n_v > 0)
	{
		V = aux_A;
	}
}

/// Eigenvalues of a hermitian matrix (row-major case).
template <
	typename ValueT,
	typename TriangularT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen_impl(hermitian_matrix<ValueT,TriangularT,row_major> const& A, eigenvectors_side side, OutVectorT& w, OutMatrixT& V)
{
	typedef hermitian_matrix<ValueT, TriangularT, column_major> colmaj_in_matrix_type;
	typedef matrix<typename matrix_traits<OutMatrixT>::value_type, column_major> colmaj_out_matrix_type;

	colmaj_in_matrix_type tmp_A(A);
	colmaj_out_matrix_type tmp_V;

	eigen_impl(tmp_A, side, w, tmp_V);

	V = tmp_V;
}

/// Eigenvalues of a symmetric matrix (column-major case).
template <
	typename ValueT,
	typename TriangularT,
	typename OutVectorT,
	typename OutMatrixT
>
void  eigen_impl(symmetric_matrix<ValueT,TriangularT,column_major> const& A, eigenvectors_side side, OutVectorT& w, OutMatrixT& V)
{
	// NOTE: a symmetric matrix is a real hermitian matrix

	// precondition: A must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<ValueT>::value
	));
	// precondition: w must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: V must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<OutMatrixT>::value_type>::value
	));

	typedef symmetric_matrix<ValueT, TriangularT, column_major> matrix_type;
	//typedef symmetric_matrix<double, lower, column_major> matrix_type;
	typedef typename matrix_traits<matrix_type>::size_type size_type;
	//typedef matrix<ValueT, column_major> aux_matrix_type;
	typedef OutMatrixT out_matrix_type;
    typedef symmetric_adaptor<out_matrix_type, TriangularT> work_matrix_type;

	size_type n = num_rows(A);
	size_type n_v;

	char jobvz;

	switch (side)
	{
		case both_eigenvectors:
			jobvz = 'V';
			n_v = n;
			break;
		case none_eigenvectors:
		default:
			jobvz = 'N';
			n_v = 0;
	}

	if (size(w) != n)
	{
		w.resize(n, false);
	}
	if (num_rows(V) != n_v)
	{
		V.resize(n_v, n_v, false);
	}

	out_matrix_type aux_A(A);
	work_matrix_type tmp_A(aux_A);

	::boost::numeric::bindings::lapack::syev(jobvz, tmp_A, w);

	if (n_v > 0)
	{
		V = aux_A;
	}
}

/// Eigenvalues of a symmetric matrix (row-major case).
template <
	typename ValueT,
	typename TriangularT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen_impl(symmetric_matrix<ValueT,TriangularT,row_major> const& A, eigenvectors_side side, OutVectorT& w, OutMatrixT& V)
{
	typedef symmetric_matrix<ValueT, TriangularT, column_major> colmaj_in_matrix_type;
	typedef matrix<typename matrix_traits<OutMatrixT>::value_type, column_major> colmaj_out_matrix_type;

	colmaj_in_matrix_type tmp_A(A);
	colmaj_out_matrix_type tmp_V;

	eigen_impl(tmp_A, side, w, tmp_V);

	V = tmp_V;
}

//@} Eigenvalues problem

//@{ Generalized Eigenvalues problem

/// Generalized eigenvectors for real matrix pair (A,B) (column-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename AlphaRVectorT,
	typename AlphaIVectorT,
	typename BetaVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
void geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, AlphaRVectorT& alphar, AlphaIVectorT& alphai, BetaVectorT& beta, LVMatrixT& LV, RVMatrixT& RV, column_major_tag)
{
	// precondition: A must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<AMatrixExprT>::value_type>::value
	));
	// precondition: B must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<BMatrixExprT>::value_type>::value
	));
	// precondition: alphar must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<AlphaRVectorT>::value_type>::value
	));
	// precondition: alphai must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<AlphaIVectorT>::value_type>::value
	));
	// precondition: beta must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<BetaVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<LVMatrixT>::value_type>::value
	));
	// precondition: RV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<RVMatrixT>::value_type>::value
	));

	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::value_type,
				typename matrix_traits<BMatrixExprT>::value_type
			>::promote_type value_type;
	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::size_type,
				typename matrix_traits<BMatrixExprT>::size_type
			>::promote_type size_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;
	//typedef typename type_traits<typename matrix_traits<LVMatrixT>::value_type>::real_type left_real_type;
	//typedef typename type_traits<typename matrix_traits<RVMatrixT>::value_type>::real_type right_real_type;

	size_type n = num_rows(A);

	// LAPACK GEEV overwrites the original input matrix A
	colmaj_matrix_type tmp_A(A);
	// LAPACK GEEV overwrites the original input matrix B
	colmaj_matrix_type tmp_B(B);

	char jobvl;
	char jobvr;
	size_type work_n_LV;
	size_type out_n_LV;
	size_type work_n_RV;
	size_type out_n_RV;

	switch (side)
	{
		case both_eigenvectors:
			jobvl = jobvr = 'V';
			work_n_LV = out_n_LV = work_n_RV = out_n_RV = n;
			break;
		case left_eigenvectors:
			jobvl = 'V';
			jobvr = 'N';
			work_n_LV = out_n_LV = n;
			work_n_RV = detail::lapack::min_array_size;
			out_n_RV = 0;
			break;
		case right_eigenvectors:
			jobvl = 'N';
			jobvr = 'V';
			work_n_LV = detail::lapack::min_array_size;
			out_n_LV = 0;
			work_n_RV = out_n_RV = n;
			break;
		case none_eigenvectors:
		default:
			jobvl = jobvr = 'N';
			work_n_LV = work_n_RV = detail::lapack::min_array_size;
			out_n_LV = out_n_RV = 0;
	}

	// Size arrays with the proper size
	// NOTE: LAPACK always computes the eigenvalues so we need to size them with
	// the corrected size even if we do not want them (i.e.,
	// when want_eigvals == false).

	if (size(alphar) != n)
	{
		alphar.resize(n, false);
	}
	if (size(alphai) != n)
	{
		alphai.resize(n, false);
	}
	if (size(beta) != n)
	{
		beta.resize(n, false);
	}

	// Create temporary real matrices for eigenvectors
	// NOTE: LAPACK wants the arrays be correctly sized even if they are not
	// requested as output (i.e., jobvl == 'N' and/or jobvr == 'N').
	colmaj_matrix_type tmp_LV(work_n_LV, work_n_LV);
	colmaj_matrix_type tmp_RV(work_n_RV, work_n_RV);

	::boost::numeric::bindings::lapack::ggev(
		jobvl,
		jobvr,
		tmp_A,
		tmp_B,
		alphar,
		alphai,
		beta,
		tmp_LV,
		tmp_RV
	);

	// Resize output complex eigenvectors matrices ...

	if (num_rows(LV) != out_n_LV || num_columns(LV) != out_n_LV)
	{
		LV.resize(out_n_LV, out_n_LV, false);
	}
	if (num_rows(RV) != out_n_RV || num_columns(RV) != out_n_RV)
	{
		RV.resize(out_n_RV, out_n_RV, false);
	}

	// ... and possibly fill eigenvectors matrices LV and RV

	if (out_n_LV > 0 || out_n_RV > 0)
	{
		// The algorithm for computing left and right eigenvectors is the
		// following:
		// - The left eigenvectors u(j) are stored one
		//   after another in the columns of LV, in the same order as
		//   their eigenvalues. If the j-th eigenvalue is real, then
		//   u(j) = LV(:,j), the j-th column of LV. If the j-th and
		//   (j+1)-th eigenvalues form a complex conjugate pair, then
		//   u(j) = LV(:,j)+i*LV(:,j+1) and u(j+1) = LV(:,j)-i*LV(:,j+1).
		// - The right eigenvectors v(j) are stored one
		//   after another in the columns of RV, in the same order as
		//   their eigenvalues. If the j-th eigenvalue is real, then
		//   v(j) = RV(:,j), the j-th column of RV. If the j-th and
		//   (j+1)-th eigenvalues form a complex conjugate pair, then
		//   v(j) = RV(:,j)+i*RV(:,j+1) and v(j+1) = RV(:,j)-i*RV(:,j+1).
		// .
		// Each eigenvector is scaled so the largest component has
		// abs(real part)+abs(imag. part)=1.
		//

		for (size_type i = 0; i < n; ++i)
		{
			for (size_type j = 0; j < n; ++j)
			{
				if (alphai(j) != 0.0)
				{
					if (out_n_LV > 0)
					{
						value_type lv1 = tmp_LV(i,j);
						value_type lv2 = tmp_LV(i,j+1);

						LV(i,j) = ::std::complex<value_type>(lv1, lv2);
						LV(i,j+1) = ::std::complex<value_type>(lv1, -lv2);
					}

					if (out_n_RV > 0)
					{
						value_type rv1 = tmp_RV(i,j);
						value_type rv2 = tmp_RV(i,j+1);

						RV(i,j) = ::std::complex<value_type>(rv1, rv2);
						RV(i,j+1) = ::std::complex<value_type>(rv1, -rv2);
					}

					++j;
				}
				else
				{
					if (out_n_LV > 0)
					{
						LV(i,j) = tmp_LV(i,j);
					}
					if (out_n_RV > 0)
					{
						RV(i,j) = tmp_RV(i,j);
					}
				}
			}
		}
	}

	// When possible, resize to save memory

//	if (num_rows(LV) != out_n_LV)
//	{
//		LV.resize(out_n_LV, out_n_LV, false);
//	}
//	if (num_rows(RV) != out_n_RV)
//	{
//		RV.resize(out_n_RV, out_n_RV, false);
//	}
	if (!want_eigvals)
	{
		alphar.resize(0, false);
		alphai.resize(0, false);
		beta.resize(0, false);
	}
}

/// Generalized eigenvectors for real matrix pair (A,B) (row-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename AlphaRVectorT,
	typename AlphaIVectorT,
	typename BetaVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
void geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, AlphaRVectorT& alphar, AlphaIVectorT& alphai, BetaVectorT& beta, LVMatrixT& LV, RVMatrixT& RV, row_major_tag)
{
	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::value_type,
				typename matrix_traits<BMatrixExprT>::value_type
			>::promote_type value_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;

	colmaj_matrix_type tmp_A(A);
	colmaj_matrix_type tmp_B(B);
	colmaj_matrix_type tmp_LV;
	colmaj_matrix_type tmp_RV;

	geigen_impl(A, B, side, want_eigvals, alphar, alphai, beta, tmp_LV, tmp_RV, column_major_tag());

	LV = tmp_LV;
	RV = tmp_RV;
}

/// Generalized eigenvectors for complex matrix pair (A,B) (column-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename AlphaVectorT,
	typename BetaVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
void geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, AlphaVectorT& alpha, BetaVectorT& beta, LVMatrixT& LV, RVMatrixT& RV, column_major_tag)
{
	// precondition: A must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<AMatrixExprT>::value_type>::value
	));
	// precondition: B must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<BMatrixExprT>::value_type>::value
	));
	// precondition: alpha must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<AlphaVectorT>::value_type>::value
	));
	// precondition: beta must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<BetaVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<LVMatrixT>::value_type>::value
	));
	// precondition: RV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<RVMatrixT>::value_type>::value
	));

	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::value_type,
				typename matrix_traits<BMatrixExprT>::value_type
			>::promote_type value_type;
	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::size_type,
				typename matrix_traits<BMatrixExprT>::size_type
			>::promote_type size_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;

	size_type n = num_rows(A);

	colmaj_matrix_type tmp_A(A); // LAPACK GEEV overwrites the original input matrix A
	colmaj_matrix_type tmp_B(B); // LAPACK GEEV overwrites the original input matrix B

	char jobvl;
	char jobvr;
	size_type n_LV;
	size_type n_RV;

	switch (side)
	{
		case both_eigenvectors:
			jobvl = jobvr = 'V';
			n_LV = n_RV = n;
			break;
		case left_eigenvectors:
			jobvl = 'V';
			jobvr = 'N';
			n_LV = n;
			n_RV = 0;
			break;
		case right_eigenvectors:
			jobvl = 'N';
			jobvr = 'V';
			n_LV = 0;
			n_RV = n;
			break;
		case none_eigenvectors:
		default:
			jobvl = jobvr = 'N';
			n_LV = n_RV = 0;
	}

	// Size matrices and arrays with the proper size
	//
	// NOTE: LAPACK wants the matrices/arrays be correctly sized even if they
	// are not requested as output (i.e., want_eigvals == false or jobvl='N' or jobvr='N').

	if (size(alpha) != n)
	{
		alpha.resize(n, false);
	}
	if (size(beta) != n)
	{
		beta.resize(n, false);
	}
	if (num_rows(LV) != n || num_columns(LV) != n)
	{
		LV.resize(n, n, false);
	}
	if (num_rows(RV) != n || num_columns(RV) != n)
	{
		RV.resize(n, n, false);
	}

	::boost::numeric::bindings::lapack::ggev(
		jobvl,
		jobvr,
		tmp_A,
		tmp_B,
		alpha,
		beta,
		LV,
		RV
	);

	// When possible, resize to save memory

	if (num_rows(LV) != n_LV)
	{
		LV.resize(n_LV, n_LV, false);
	}
	if (num_rows(RV) != n_RV)
	{
		RV.resize(n_RV, n_RV, false);
	}
	if (!want_eigvals)
	{
		alpha.resize(0, false);
		beta.resize(0, false);
	}
}

/// Generalized eigenvectors for complex matrix pair (A,B) (row-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename AlphaVectorT,
	typename BetaVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
void geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, AlphaVectorT& alpha, BetaVectorT& beta, LVMatrixT& LV, RVMatrixT& RV, row_major_tag)
{
	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::value_type,
				typename matrix_traits<BMatrixExprT>::value_type
			>::promote_type value_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;

	colmaj_matrix_type tmp_A(A);
	colmaj_matrix_type tmp_B(B);
	colmaj_matrix_type tmp_LV;
	colmaj_matrix_type tmp_RV;

	geigen_impl(tmp_A, tmp_B, side, want_eigvals, alpha, beta, tmp_LV, tmp_RV, column_major_tag());

	LV = tmp_LV;
	RV = tmp_RV;
}

/// Generalized eigenvectors for either real or complex matrix pair (A,B) (column-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
typename ::boost::disable_if<
	typename ::boost::mpl::and_<
		::boost::is_complex<typename matrix_traits<AMatrixExprT>::value_type>,
		::boost::is_complex<typename matrix_traits<BMatrixExprT>::value_type>
	>::type,
	void
>::type geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, OutVectorT& w, LVMatrixT& LV, RVMatrixT& RV, column_major_tag)
{
	// precondition: A can be either a real or a complex matrix -> no check
	// precondition: B can be either a real or a complex matrix -> no check
	// precondition: w must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<LVMatrixT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<RVMatrixT>::value_type>::value
	));

	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::size_type,
				typename matrix_traits<BMatrixExprT>::size_type
			>::promote_type size_type;
	typedef typename vector_traits<OutVectorT>::value_type eigval_value_type;
	typedef typename type_traits<eigval_value_type>::real_type eigval_real_type;

	size_type n = num_rows(A);

	vector<eigval_real_type> tmp_alphar; // (n);
	vector<eigval_real_type> tmp_alphai; // (n);
	vector<eigval_real_type> tmp_beta; // (n);

	geigen_impl(A, B, side, want_eigvals, tmp_alphar, tmp_alphai, tmp_beta, LV, RV, column_major_tag());

	if (want_eigvals)
	{
		if (size(w) != n)
		{
			w.resize(n, false);
		}
#ifdef BOOST_UBLASX_DEBUG
		// Underflow threshold (used in the 'safety check' inside the 'for' loop)
		eigval_real_type rmin(::std::numeric_limits<eigval_real_type>::min());
#endif // BOOST_UBLASX_DEBUG
		for (size_type i = 0; i < n; ++i)
		{
#ifdef BOOST_UBLASX_DEBUG
			// Safety check: when beta_i is near to zero the corresponding
			//               eigenvalue is infinite.
			//               This test was inspired by the 'f08wafe.f'
			//               subrouting found on NAG libraries.
			if ((::std::abs(tmp_alphar(i))+::std::abs(tmp_alphai(i)))*rmin >= ::std::abs(tmp_beta(i)))
			{
				BOOST_UBLASX_DEBUG_TRACE("[Warning] Eigenvalue(" << i << ") is numerically infinite or undetermined: alpha_r(" << i << ") = " << tmp_alphar(i) << ", alpha_i(" << i << ") = " << tmp_alphai(i) << ", beta(" << i << ") = " << tmp_beta(i));
			}
#endif // BOOST_UBLASX_DEBUG

			w(i) = eigval_value_type(tmp_alphar(i), tmp_alphai(i))/tmp_beta(i);
		}
	}
	else
	{
		w.resize(0, false);
	}
}

/// Generalized eigenvectors for either real or complex matrix pair (A,B) (column-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
typename ::boost::enable_if<
	typename ::boost::mpl::and_<
		::boost::is_complex<typename matrix_traits<AMatrixExprT>::value_type>,
		::boost::is_complex<typename matrix_traits<BMatrixExprT>::value_type>
	>::type,
	void
>::type geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, OutVectorT& w, LVMatrixT& LV, RVMatrixT& RV, column_major_tag)
{
	// precondition: A can be either a real or a complex matrix -> no check
	// precondition: B can be either a real or a complex matrix -> no check
	// precondition: w must be a complex vector
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename vector_traits<OutVectorT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<LVMatrixT>::value_type>::value
	));
	// precondition: LV must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<RVMatrixT>::value_type>::value
	));

	typedef typename promote_traits<
				typename matrix_traits<AMatrixExprT>::size_type,
				typename matrix_traits<BMatrixExprT>::size_type
			>::promote_type size_type;
	typedef typename vector_traits<OutVectorT>::value_type eigval_value_type;

	size_type n = num_rows(A);

	vector<eigval_value_type> tmp_alpha; // (n);
	vector<eigval_value_type> tmp_beta; // (n);

	geigen_impl(A, B, side, want_eigvals, tmp_alpha, tmp_beta, LV, RV, column_major_tag());

	if (want_eigvals)
	{
		if (size(w) != n)
		{
			w.resize(n, false);
		}
#ifdef BOOST_UBLASX_DEBUG
		typedef typename type_traits<eigval_value_type>::real_type eigval_real_type;

		// Underflow threshold (used in the 'safety check' inside the 'for' loop)
		eigval_real_type rmin(::std::numeric_limits<eigval_real_type>::min());
#endif // BOOST_UBLASX_DEBUG
		for (size_type i = 0; i < n; ++i)
		{
#ifdef BOOST_UBLASX_DEBUG
			// Safety check: when beta_i is near to zero the corresponding
			//               eigenvalue is infinite.
			//               This test was inspired by the 'f08wnfe.f'
			//               subrouting found on NAG libraries.
			if (::std::abs(tmp_alpha(i))*rmin >= ::std::abs(tmp_beta(i)))
			{
				BOOST_UBLASX_DEBUG_TRACE("[Warning] Eigenvalue(" << i << ") is numerically infinite or undetermined: alpha(" << i << ") = " << tmp_alpha(i) << ", beta(" << i << ") = " << tmp_beta(i));
			}
#endif // BOOST_UBLASX_DEBUG

			w(i) = tmp_alpha(i)/tmp_beta(i);
		}
	}
	else
	{
		w.resize(0, false);
	}
}

/// Generalized eigenvectors for real/complex matrix pair (A,B) (row-major case).
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename LVMatrixT,
	typename RVMatrixT
>
//typename ::boost::enable_if<
//	typename ::boost::mpl::and_<
//		::boost::is_complex<typename matrix_traits<AMatrixExprT>::value_type>,
//		::boost::is_complex<typename matrix_traits<BMatrixExprT>::value_type>
//	>::type,
//	void
//>::type
void geigen_impl(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, eigenvectors_side side, bool want_eigvals, OutVectorT& w, LVMatrixT& LV, RVMatrixT& RV, row_major_tag)
{
	typedef typename promote_traits<
						typename matrix_traits<AMatrixExprT>::value_type,
						typename promote_traits<
							typename matrix_traits<BMatrixExprT>::value_type,
							typename promote_traits<
								typename matrix_traits<LVMatrixT>::value_type,
								typename matrix_traits<RVMatrixT>::value_type
							>::promote_type
						>::promote_type
			>::promote_type value_type;

	typedef matrix<value_type, column_major> colmaj_matrix_type;

	colmaj_matrix_type tmp_A(A);
	colmaj_matrix_type tmp_B(B);
	colmaj_matrix_type tmp_LV;
	colmaj_matrix_type tmp_RV;

	geigen_impl(tmp_A, tmp_B, side, want_eigvals, w, tmp_LV, tmp_RV, column_major_tag());

	LV = tmp_LV;
	RV = tmp_RV;
}

/// Generalized eigenvectors for real symmetric matrix pair (A,B).
template <
	typename AValueT,
	typename TriangularT,
	typename BValueT,
	typename WVectorT,
	typename VMatrixT
>
void geigen_impl(symmetric_matrix<AValueT,TriangularT,column_major> const& A, symmetric_matrix<BValueT,TriangularT,column_major> const& B, eigenvectors_side side, bool want_eigvals, WVectorT& w, VMatrixT& V)
{
	// precondition: A must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<AValueT>::value
	));
	// precondition: B must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<BValueT>::value
	));
	// precondition: w must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<WVectorT>::value_type>::value
	));
	// precondition: V must be a real matrix
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename matrix_traits<VMatrixT>::value_type>::value
	));

	typedef typename promote_traits<AValueT,BValueT>::promote_type value_type;
	typedef symmetric_matrix<AValueT,TriangularT,column_major> A_matrix_type;
	typedef symmetric_matrix<BValueT,TriangularT,column_major> B_matrix_type;
	typedef typename promote_traits<
				typename matrix_traits<A_matrix_type>::size_type,
				typename matrix_traits<B_matrix_type>::size_type
			>::promote_type size_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;
    typedef symmetric_adaptor<colmaj_matrix_type, TriangularT> work_matrix_type;

	size_type n = num_rows(A);

	::fortran_int_t itype = 1; // We always solve A*v=w*B*v
	char jobz;
	size_type n_V;

	switch (side)
	{
		case both_eigenvectors:
			jobz = 'V';
			n_V = n;
			break;
		case none_eigenvectors:
		default:
			jobz = 'N';
			n_V = 0;
	}

	// Size arrays with the proper size
	// NOTE: LAPACK wants the arrays be correctly sized even if they are not
	// requested as output (i.e., want_eigvals == false).

	if (size(w) != n)
	{
		w.resize(n, false);
	}

	//TODO:
	// OPTIMIZATION: maybe using SPGV in place of SYGV may save us from
	// copying A and B into a dense temporary matrix.

	// LAPACK GEEV overwrites the original input matrix A
	colmaj_matrix_type aux_A(A);
	work_matrix_type tmp_A(aux_A);
	// LAPACK GEEV overwrites the original input matrix B
	colmaj_matrix_type aux_B(B);
	work_matrix_type tmp_B(aux_B);

	::boost::numeric::bindings::lapack::sygv(
		itype,
		jobz,
		tmp_A,
		tmp_B,
		w
	);

	// When possible, resize to save memory

	if (!want_eigvals)
	{
		w.resize(0, false);
	}
	if (n_V == 0)
	{
		V.resize(0, 0, false);
	}
	else
	{
		V = aux_A;
	}
}

/// Generalized eigenvectors for real symmetric matrix pair (A,B).
/// (row-major case)
template <
	typename AValueT,
	typename TriangularT,
	typename BValueT,
	typename WVectorT,
	typename VMatrixT
>
void geigen_impl(symmetric_matrix<AValueT,TriangularT,row_major> const& A, symmetric_matrix<BValueT,TriangularT,row_major> const& B, eigenvectors_side side, bool want_eigvals, WVectorT& w, VMatrixT& V)
{
	typedef symmetric_matrix<AValueT, TriangularT, column_major> colmaj_A_matrix_type;
	typedef symmetric_matrix<BValueT, TriangularT, column_major> colmaj_B_matrix_type;
	typedef matrix<typename matrix_traits<VMatrixT>::value_type, column_major> colmaj_V_matrix_type;

	colmaj_A_matrix_type tmp_A(A);
	colmaj_B_matrix_type tmp_B(B);
	colmaj_V_matrix_type tmp_V;

	geigen_impl(tmp_A, tmp_B, side, want_eigvals, w, tmp_V);

	V = tmp_V;
}

/// Generalized eigenvectors for complex hermitian matrix pair (A,B)
/// (column-major case).
template <
	typename AValueT,
	typename TriangularT,
	typename BValueT,
	typename WVectorT,
	typename VMatrixT
>
void geigen_impl(hermitian_matrix<AValueT,TriangularT,column_major> const& A, hermitian_matrix<BValueT,TriangularT,column_major> const& B, eigenvectors_side side, bool want_eigvals, WVectorT& w, VMatrixT& V)
{
	// precondition: A must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<AValueT>::value
	));
	// precondition: B must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<BValueT>::value
	));
	// precondition: w must be a real vector
	BOOST_STATIC_ASSERT((
		!::boost::is_complex<typename vector_traits<WVectorT>::value_type>::value
	));
	// precondition: V must be a complex matrix
	BOOST_STATIC_ASSERT((
		::boost::is_complex<typename matrix_traits<VMatrixT>::value_type>::value
	));

	typedef typename promote_traits<AValueT,BValueT>::promote_type value_type;
	typedef symmetric_matrix<AValueT,TriangularT,column_major> A_matrix_type;
	typedef symmetric_matrix<BValueT,TriangularT,column_major> B_matrix_type;
	typedef typename promote_traits<
				typename matrix_traits<A_matrix_type>::size_type,
				typename matrix_traits<B_matrix_type>::size_type
			>::promote_type size_type;
	typedef matrix<value_type, column_major> colmaj_matrix_type;
    typedef hermitian_adaptor<colmaj_matrix_type, TriangularT> work_matrix_type;

	size_type n = num_rows(A);

	::fortran_int_t itype = 1; // We always solve A*v=w*B*v
	char jobz;
	size_type n_V;

	switch (side)
	{
		case both_eigenvectors:
			jobz = 'V';
			n_V = n;
			break;
		case none_eigenvectors:
		default:
			jobz = 'N';
			n_V = 0;
	}

	// Size arrays with the proper size
	// NOTE: LAPACK wants the arrays be correctly sized even if they are not
	// requested as output (i.e., want_eigvals == false).

	if (size(w) != n)
	{
		w.resize(n, false);
	}

	//TODO:
	// OPTIMIZATION: maybe using HPGV in place of HEGV may save us from
	// copying A and B into a dense temporary matrix.

	// LAPACK GEEV overwrites the original input matrix A
	colmaj_matrix_type aux_A(A);
	work_matrix_type tmp_A(aux_A);
	// LAPACK GEEV overwrites the original input matrix B
	colmaj_matrix_type aux_B(B);
	work_matrix_type tmp_B(aux_B);

	::boost::numeric::bindings::lapack::hegv(
		itype,
		jobz,
		tmp_A,
		tmp_B,
		w
	);

	// When possible, resize to save memory

	if (!want_eigvals)
	{
		w.resize(0, false);
	}
	if (n_V == 0)
	{
		V.resize(0, 0, false);
	}
	else
	{
		V = aux_A;
	}
}

/// Generalized eigenvectors for complex hermitian matrix pair (A,B).
/// (row-major case)
template <
	typename AValueT,
	typename TriangularT,
	typename BValueT,
	typename WVectorT,
	typename VMatrixT
>
void geigen_impl(hermitian_matrix<AValueT,TriangularT,row_major> const& A, hermitian_matrix<BValueT,TriangularT,row_major> const& B, eigenvectors_side side, bool want_eigvals, WVectorT& w, VMatrixT& V)
{
	typedef hermitian_matrix<AValueT, TriangularT, column_major> colmaj_A_matrix_type;
	typedef hermitian_matrix<BValueT, TriangularT, column_major> colmaj_B_matrix_type;
	typedef matrix<typename matrix_traits<VMatrixT>::value_type, column_major> colmaj_V_matrix_type;

	colmaj_A_matrix_type tmp_A(A);
	colmaj_B_matrix_type tmp_B(B);
	colmaj_V_matrix_type tmp_V;

	geigen_impl(tmp_A, tmp_B, side, want_eigvals, w, tmp_V);

	V = tmp_V;
}

//@} Generalized Eigenvalues problem

}} // Namespace detail::<unnamed>

/**
 * \brief Compute the eigenvalues and the left and right eigenvectors
 *  of the given matrix expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutLeftMatrixT The type of the left eigenvectors matrix.
 * \tparam OutRightMatrixT The type of the right eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param LV The output left eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 * \param RV The output right eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, \a LV, and \a RV contain the eigenvalues, the
 *  left eigenvectors, and the right eigenvector of \a A, respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename MatrixExprT,
	typename OutVectorT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
void eigen(matrix_expression<MatrixExprT> const& A, vector_container<OutVectorT>& v, matrix_container<OutLeftMatrixT>& LV, matrix_container<OutRightMatrixT>& RV)
{
	typedef typename matrix_traits<MatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutLeftMatrixT>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutRightMatrixT>::orientation_category orientation_category3;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	//matrix<value_type, typename layout_type<MatrixExprT>::type> tmp_A(A);

	detail::eigen_impl(A, detail::both_eigenvectors, v(), LV(), RV(), orientation_category1());
}

/**
 * \brief Compute the eigenvalues and the eigenvectors of the given symmetric
 *  matrix.
 *
 * \tparam ValueT The type of the input matrix elements.
 * \tparam TriangularT The type of the triangular shape.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  eigenvectors of \a A, respectively.
 *
 * \note
 * Since \a A is a symmetric matrix, the left and right eigenvectors are simply
 * each other's transpose.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen(symmetric_matrix<ValueT,TriangularT,LayoutT> const& A, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef symmetric_matrix<ValueT, TriangularT, LayoutT> matrix_type;
	typedef typename matrix_traits<matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	detail::eigen_impl(A, detail::both_eigenvectors, v(), V());

	// If eigen_impl assume that the input matrix can be changed, then use
	// these instructions below instead of the above one.
	//matrix_type tmp_A(A);
	//detail::eigen_impl(tmp_A, detail::both_eigenvectors, v, V);
}

/**
 * \brief Compute the eigenvalues and the eigenvectors of the given hermitian
 *  matrix.
 *
 * \tparam ValueT The type of the input matrix elements.
 * \tparam TriangularT The type of the triangular shape.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  eigenvectors of \a A, respectively.
 *
 * \note
 * Since \a A is a hermitian matrix, the left and right eigenvectors are simply
 * each other's conjugate transpose.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen(hermitian_matrix<ValueT,TriangularT,LayoutT> const& A, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef hermitian_matrix<ValueT, TriangularT, LayoutT> matrix_type;
	typedef typename matrix_traits<matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	detail::eigen_impl(A, detail::both_eigenvectors, v(), V());

	// If eigen_impl assume that the input matrix can be changed, then use
	// these instructions below instead of the above one.
	//matrix_type tmp_A(A);
	//detail::eigen_impl(tmp_A, detail::both_eigenvectors, v, V);
}

/**
 * \brief Compute the eigenvalues and the left and right eigenvectors
 *  of the given matrix expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutLeftMatrixT The type of the left eigenvectors matrix.
 * \tparam OutRightMatrixT The type of the right eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param LV The output left eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 * \param RV The output right eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, \a LV, and \a RV contain the eigenvalues, the
 *  left eigenvectors, and the right eigenvector of \a A, respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
void eigen(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, vector_container<OutVectorT>& v, matrix_container<OutLeftMatrixT>& LV, matrix_container<OutRightMatrixT>& RV)
{
	typedef typename matrix_traits<AMatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<BMatrixExprT>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutLeftMatrixT>::orientation_category orientation_category3;
	typedef typename matrix_traits<OutRightMatrixT>::orientation_category orientation_category4;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::mpl::and_<
				::boost::is_same<orientation_category1,orientation_category3>,
				::boost::is_same<orientation_category1,orientation_category4>
			>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);
	// precondition: B is square
	BOOST_UBLAS_CHECK(
		(num_rows(B) == num_columns(B)),
		bad_argument()
	);

	detail::geigen_impl(A, B, detail::both_eigenvectors, true, v(), LV(), RV(), orientation_category1());
}

/**
 * \brief Compute the eigenvalues and the eigenvectors of the given symmatrix
 *  matrix expression.
 *
 * \tparam AValueT The type of the elements of the first input matrix
 *  expression.
 * \tparam ATriangularT The type of the triangular shape of the first input
 *  matrix expression.
 * \tparam BValueT The type of the elements of the second input matrix
 *  expression.
 * \tparam BTriangularT The type of the triangular shape of the second input
 *  matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The first input matrix expression.
 * \param B The second input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, \a LV, and \a RV contain the eigenvalues, the
 *  left eigenvectors, and the right eigenvector of \a A, respectively.
 *
 * \note
 *  The second input matrix \a B is assumed to be positive definite.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename AValueT,
	typename ATriangularT,
	typename ALayoutT,
	typename BValueT,
	typename BTriangularT,
	typename BLayoutT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen(symmetric_matrix<AValueT,ATriangularT,ALayoutT> const& A, symmetric_matrix<BValueT,BTriangularT,BLayoutT> const& B, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef symmetric_matrix<AValueT,ATriangularT,ALayoutT> A_matrix_type;
	typedef symmetric_matrix<BValueT,BTriangularT,BLayoutT> B_matrix_type;
	typedef typename matrix_traits<A_matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<B_matrix_type>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category3;

	// precondition: same triangular shape
	BOOST_STATIC_ASSERT((
		::boost::is_same<ATriangularT,BTriangularT>::value
	));
	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);
	// precondition: B is square
	BOOST_UBLAS_CHECK(
		(num_rows(B) == num_columns(B)),
		bad_argument()
	);

	detail::geigen_impl(A, B, detail::both_eigenvectors, true, v(), V());
}

/**
 * \brief Compute the eigenvalues and the eigenvectors of the given symmatrix
 *  matrix expression.
 *
 * \tparam AValueT The type of the elements of the first input matrix
 *  expression.
 * \tparam ATriangularT The type of the triangular shape of the first input
 *  matrix expression.
 * \tparam BValueT The type of the elements of the second input matrix
 *  expression.
 * \tparam BTriangularT The type of the triangular shape of the second input
 *  matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The first input matrix expression.
 * \param B The second input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, \a LV, and \a RV contain the eigenvalues, the
 *  left eigenvectors, and the right eigenvector of \a A, respectively.
 *
 * \note
 *  The second input matrix \a B is assumed to be positive definite.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename AValueT,
	typename ATriangularT,
	typename ALayoutT,
	typename BValueT,
	typename BTriangularT,
	typename BLayoutT,
	typename OutVectorT,
	typename OutMatrixT
>
void eigen(hermitian_matrix<AValueT,ATriangularT,ALayoutT> const& A, hermitian_matrix<BValueT,BTriangularT,BLayoutT> const& B, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef symmetric_matrix<AValueT,ATriangularT,ALayoutT> A_matrix_type;
	typedef symmetric_matrix<BValueT,BTriangularT,BLayoutT> B_matrix_type;
	typedef typename matrix_traits<A_matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<B_matrix_type>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category3;

	// precondition: same triangular shape
	BOOST_STATIC_ASSERT((
		::boost::is_same<ATriangularT,BTriangularT>::value
	));
	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);
	// precondition: B is square
	BOOST_UBLAS_CHECK(
		(num_rows(B) == num_columns(B)),
		bad_argument()
	);

	detail::geigen_impl(A, B, detail::both_eigenvectors, true, v(), V());
}

/**
 * \brief Compute the eigenvalues and the left eigenvectors of the given matrix
 *  expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the left eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output left eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  left eigenvectors of \a A, respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename MatrixExprT,
	typename OutVectorT,
	typename OutMatrixT
>
void left_eigen(matrix_expression<MatrixExprT> const& A, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef typename matrix_traits<MatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;
	typedef typename layout_type<OutMatrixT>::type out_layout_type;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	//OutMatrixT dummy_RV;
	matrix<typename matrix_traits<OutMatrixT>::value_type, out_layout_type> dummy_RV;

	detail::eigen_impl(A, detail::left_eigenvectors, v(), V(), dummy_RV, orientation_category1());
}

/**
 * \brief Compute the generazlied eigenvalues and the left generazlied
 *  eigenvectors of the given matrix pair expression.
 *
 * \tparam AMatrixExprT The type of the first input matrix expression.
 * \tparam BMatrixExprT The type of the second input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the left eigenvectors matrix.
 *
 * \param A The first input matrix expression.
 * \param B The second input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output left eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  left eigenvectors of \a (A,B), respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename OutMatrixT
>
void left_eigen(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef typename matrix_traits<AMatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<BMatrixExprT>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category3;
	typedef typename layout_type<OutMatrixT>::type out_layout_type;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);
	// precondition: B is square
	BOOST_UBLAS_CHECK(
		(num_rows(B) == num_columns(B)),
		bad_argument()
	);

	//OutMatrixT dummy_RV;
	matrix<typename matrix_traits<OutMatrixT>::value_type, out_layout_type> dummy_RV;

	detail::geigen_impl(A, B, detail::left_eigenvectors, true, v(), V(), dummy_RV, orientation_category1());
}

/**
 * \brief Compute the eigenvalues and the right eigenvectors of the given matrix
 *  expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the right eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output right eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  right eigenvectors of \a A, respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename MatrixExprT,
	typename OutVectorT,
	typename OutMatrixT
>
void right_eigen(matrix_expression<MatrixExprT> const& A, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef typename matrix_traits<MatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;
	typedef typename layout_type<OutMatrixT>::type out_layout_type;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	//OutMatrixT dummy_LV;
	matrix<typename matrix_traits<OutMatrixT>::value_type, out_layout_type> dummy_LV;

	detail::eigen_impl(A, detail::right_eigenvectors, v(), dummy_LV, V(), orientation_category1());
}

/**
 * \brief Compute the generazlied eigenvalues and the right generazlied
 *  eigenvectors of the given matrix pair expression.
 *
 * \tparam AMatrixExprT The type of the first input matrix expression.
 * \tparam BMatrixExprT The type of the second input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 * \tparam OutMatrixT The type of the right eigenvectors matrix.
 *
 * \param A The first input matrix expression.
 * \param B The second input matrix expression.
 * \param v The output eigenvalues vector.
 * \param V The output right eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing, but \a v, and \a V contain the eigenvalues, and the
 *  right eigenvectors of \a (A,B), respectively.
 *
 *  \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename AMatrixExprT,
	typename BMatrixExprT,
	typename OutVectorT,
	typename OutMatrixT
>
void right_eigen(matrix_expression<AMatrixExprT> const& A, matrix_expression<BMatrixExprT> const& B, vector_container<OutVectorT>& v, matrix_container<OutMatrixT>& V)
{
	typedef typename matrix_traits<AMatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<BMatrixExprT>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category3;
	typedef typename layout_type<OutMatrixT>::type out_layout_type;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);
	// precondition: B is square
	BOOST_UBLAS_CHECK(
		(num_rows(B) == num_columns(B)),
		bad_argument()
	);

	//OutMatrixT dummy_LV;
	matrix<typename matrix_traits<OutMatrixT>::value_type, out_layout_type> dummy_LV;

	detail::geigen_impl(A, B, detail::right_eigenvectors, true, v(), dummy_LV, V(), orientation_category1());
}

/**
 * \brief Compute the eigenvalues of the given matrix expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutVectorT The type of the eigenvalues vector.
 *
 * \param A The input matrix expression.
 * \param v The output eigenvalues vector.
 * \return Nothing; however the parameter \a v will store on exit the
 *  eigenvalues vector of \a A.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename MatrixExprT,
	typename OutVectorT
>
void eigenvalues(matrix_expression<MatrixExprT> const& A, vector_container<OutVectorT>& v)
{
	typedef typename vector_traits<OutVectorT>::value_type out_value_type;
	typedef typename matrix_traits<MatrixExprT>::orientation_category orientation_category;

	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	matrix<out_value_type, typename layout_type<MatrixExprT>::type> tmp_LV;
	matrix<out_value_type, typename layout_type<MatrixExprT>::type> tmp_RV;

	detail::eigen_impl(A, detail::none_eigenvectors, v(), tmp_LV, tmp_RV, orientation_category());
}

/**
 * \brief Compute the eigenvalues of the given symmetrix matrix.
 *
 * \tparam ValueT The type of the elements of the input symmetric matrix.
 * \tparam Triangular The triangular type of the input symmetric matrix.
 * \tparam LayoutT The storage layout type of the input symmetric matrix.
 * \tparam OutVectorT The type of the eigenvalues vector.
 *
 * \param A The input symmetric matrix.
 * \param v The output eigenvalues vector.
 * \return Nothing; however the parameter \a v will store on exit the
 *  eigenvalues vector of \a A.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutVectorT
>
void eigenvalues(symmetric_matrix<ValueT,TriangularT,LayoutT> const& A, vector_container<OutVectorT>& v)
{
	typedef typename vector_traits<OutVectorT>::value_type out_value_type;

	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	matrix<out_value_type, LayoutT> tmp_V;

	detail::eigen_impl(A, detail::none_eigenvectors, v(), tmp_V);
}

/**
 * \brief Compute the eigenvalues of the given hermitian matrix.
 *
 * \tparam ValueT The type of the elements of the input hermitian matrix.
 * \tparam Triangular The triangular type of the input hermitian matrix.
 * \tparam LayoutT The storage layout type of the input hermitian matrix.
 * \tparam OutVectorT The type of the eigenvalues vector.
 *
 * \param A The input hermitian matrix.
 * \param v The output eigenvalues vector.
 * \return Nothing; however the parameter \a v will store on exit the
 *  eigenvalues vector of \a A.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutVectorT
>
void eigenvalues(hermitian_matrix<ValueT,TriangularT,LayoutT> const& A, vector_container<OutVectorT>& v)
{
	typedef ValueT in_value_type;

	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	matrix<in_value_type, LayoutT> tmp_V;

	detail::eigen_impl(A, detail::none_eigenvectors, v(), tmp_V);
}

/**
 * \brief Compute the left and right eigenvectors of the given matrix
 *  expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam OutLeftMatrixT The type of the left eigenvectors matrix.
 * \tparam OutRightMatrixT The type of the right eigenvectors matrix.
 *
 * \param A The input matrix expression.
 * \param LV The output left eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 * \param RV The output right eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing; however parameter \a LV and \a RV will store on exit the
 *  left and right eigenvectors matrix of \a A, respectively.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename MatrixExprT,
	typename OutLeftMatrixT,
	typename OutRightMatrixT
>
void eigenvectors(matrix_expression<MatrixExprT> const& A, matrix_container<OutLeftMatrixT>& LV, matrix_container<OutRightMatrixT>& RV)
{
	typedef typename promote_traits<
				typename matrix_traits<OutLeftMatrixT>::value_type,
				typename matrix_traits<OutRightMatrixT>::value_type
			>::promote_type out_value_type;
	typedef typename matrix_traits<MatrixExprT>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutLeftMatrixT>::orientation_category orientation_category2;
	typedef typename matrix_traits<OutRightMatrixT>::orientation_category orientation_category3;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::mpl::and_<
			::boost::is_same<orientation_category1,orientation_category2>,
			::boost::is_same<orientation_category1,orientation_category3>
		>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	vector<out_value_type> tmp_v;

	detail::eigen_impl(A, detail::both_eigenvectors, tmp_v, LV(), RV(), orientation_category1());
}

/**
 * \brief Compute the left and right eigenvectors of the given symmetric matrix.
 *
 * \tparam ValueT The type of the elements of the input symmetric matrix.
 * \tparam TriangularT The triangular type of the input symmetric matrix.
 * \tparam LayoutT The storage layout type of the input symmetric matrix.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The input symmetric matrix.
 * \param V The output (right) eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing; however parameter \a V will store on exit the right
 *  eigenvectors matrix of \a A.
 *
 * \note
 * Since \a A is a symmetric matrix, the left and right eigenvectors are simply
 * each other's transpose.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutMatrixT
>
void eigenvectors(symmetric_matrix<ValueT, TriangularT, LayoutT> const& A, matrix_container<OutMatrixT>& V)
{
	typedef symmetric_matrix<ValueT, TriangularT, LayoutT> in_matrix_type;
	typedef typename matrix_traits<OutMatrixT>::value_type out_value_type;
	typedef typename matrix_traits<in_matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	vector<out_value_type> tmp_v;

	detail::eigen_impl(A, detail::both_eigenvectors, tmp_v, V());
}

/**
 * \brief Compute the left and right eigenvectors of the given hermitian matrix.
 *
 * \tparam ValueT The type of the elements of the input hermitian matrix.
 * \tparam TriangularT The triangular type of the input hermitian matrix.
 * \tparam LayoutT The storage layout type of the input hermitian matrix.
 * \tparam OutMatrixT The type of the eigenvectors matrix.
 *
 * \param A The input hermitian matrix.
 * \param V The output (right) eigenvectors matrix (each eigenvector is stored
 *  column-wise).
 *
 * \return Nothing; however parameter \a V will store on exit the right
 *  eigenvectors matrix of \a A.
 *
 * \note
 * Since \a A is a hermitian matrix, the left and right eigenvectors are simply
 * each other's conjugate transpose.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <
	typename ValueT,
	typename TriangularT,
	typename LayoutT,
	typename OutMatrixT
>
void eigenvectors(hermitian_matrix<ValueT, TriangularT, LayoutT> const& A, matrix_container<OutMatrixT>& V)
{
	typedef hermitian_matrix<ValueT, TriangularT, LayoutT> in_matrix_type;
	typedef typename matrix_traits<OutMatrixT>::value_type out_value_type;
	typedef typename matrix_traits<in_matrix_type>::orientation_category orientation_category1;
	typedef typename matrix_traits<OutMatrixT>::orientation_category orientation_category2;

	// precondition: same orientation category
	BOOST_STATIC_ASSERT((
		::boost::is_same<orientation_category1,orientation_category2>::value
	));
	// precondition: A is square
	BOOST_UBLAS_CHECK(
		(num_rows(A) == num_columns(A)),
		bad_argument()
	);

	vector<out_value_type> tmp_v;

	detail::eigen_impl(A, detail::both_eigenvectors, tmp_v, V());
}

//TODO
//template <
//	typename MatrixExprT,
//	typename OutMatrixT
//>
//void left_eigenvectors(matrix_expression<MatrixExprT> const& A, OutMatrixT& V)
//{
//}

//TODO
//template <
//	typename MatrixExprT,
//	typename OutMatrixT
//>
//void right_eigenvectors(matrix_expression<MatrixExprT> const& A, OutMatrixT& V)
//{
//}

}}} // Namespace boost::numeric::ublasx

#endif // BOOST_NUMERIC_UBLASX_OPERATION_EIGEN_HPP


#include <Eigen/Dense>
#include "ray.h"
#include "model.h"

#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <fstream>
#include <iostream>
using namespace std;
using namespace Eigen;
using vec = Eigen::Vector3d;
void print_Vec(const vec& v){
  cout << v(0) << " " << v(1) << " " << v(2) << endl;
}

vec radiance(const Ray &r, int depth,  unsigned short *Xi, const Scene &scene, const vector<unique_ptr<KD_tree_tris>>& KD_forest) {
  cout << endl << endl <<endl;
  cout << "Xi" << Xi[0] <<" "<<Xi[1] << " " <<Xi[2] <<endl;
  cout << "ray origin is " << endl<< r.origin_(0) << " "<<r.origin_(1) << " " << r.origin_(2) << endl;
  cout << "dire is "<< endl << r.dire_(0) << " "<<r.dire_(1)<< " " <<r.dire_(2) << endl;

  size_t mtl_id;   // id of material of intersected triangle
  Ray next;     // intersection point and normal
  //TODO: replace with my intersect
  if (!r.intersect_forest(KD_forest, mtl_id, next)) return vec::Zero(); // return blaock

  cout << "origin is " << endl<< next.origin_(0) << " " << next.origin_(1) << " " << next.origin_(2) << endl <<"mtl id is " <<mtl_id << endl;
  cout << "dire is "<< endl << next.dire_(0) << " "<< next.dire_(1) << " " << next.dire_(2) << endl;
  const tinyobj::material_t &mtl = scene.materials[mtl_id];
  const vec &x = next.origin_;
  const vec &n = next.dire_;

  vec f,ff,ke, em, ambient;
  auto set_value = [](const double v_[3], vec& v){
    for(size_t i = 0; i < 3; ++i){
      v(i) = v_[i];
    }
  };
  set_value(mtl.diffuse, f);
  set_value(mtl.specular, ff);
  set_value(mtl.emission, em);
  set_value(mtl.ambient, ambient);
  // copy(f.data(), f.data() + 3, mtl.diffuse);
  // copy(ff.data(), ff.data() + 3, mtl.specular);
  // copy(em.data(), em.data() + 3, mtl.emission);
  // copy(ambient.data(), ambient.data() + 3, mtl.ambient);
  ke = em+ ambient;  // TODO: now ka is ke
  cout << "ke is " << ke(0) <<" " << ke(1) << " " << ke(2) << endl;

  double p = f.maxCoeff() ;// max refl
  double pp = ff.maxCoeff(); // max refl
  double mp = max(p, pp);
  cout<<"mp" << mp << endl;
  bool checkke = ke(0) != 0 && ke(1) != 0 && ke(2) != 0 ;

  if (ke(0) != 0 && ke(1) != 0 && ke(2) != 0){ //find light
    cout << "return ke notnull" << endl;
    print_Vec(ke);
    return ke;
  }

  // FIXME: there is a bug that causes infinite recursive loop when mp = 1.
  if (++depth > 5)
    if (erand48(Xi) < mp) // Russian roulette
      f = f * (1 / mp);
    else {
      cout << "depth > 5"<< endl;
      print_Vec(ke);
     return ke;
    }

  const vec nl = n.dot(r.dire_) < 0 ? n : n * -1;

  if (mtl.dissolve < 1) {                                             // REFRACTION
    cout <<"mtl.dissolve " << endl;
    Ray reflRay(x, r.dire_ - n * 2 * n.dot(r.dire_));

    bool into = n.dot(nl) > 0;
    double nc = 1, nt = mtl.ior;//1.5
    double nnt = into ? nc / nt : nt / nc, ddn = r.dire_.dot(nl), cos2t;

    if ((cos2t = 1 - nnt * nnt * (1 - ddn * ddn)) < 0)                // Total internal reflection
      return ke + vec(f.array() * radiance(reflRay, depth, Xi, scene,KD_forest ).array());

    vec tdir = (r.dire_ * nnt - n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t))));
    tdir /= tdir.norm();
    double a = nt - nc, b = nt + nc, R0 = a * a / (b * b), c = 1 - (into ? -ddn : tdir.dot(n));
    double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re, P = .25 + .5 * Re, RP = Re / P, TP = Tr / (1 - P);

    vec res;
    if(depth > 2){
      if(erand48(Xi) < P)
        res = radiance(reflRay, depth, Xi, scene,KD_forest) * RP;
      else
        res = radiance(Ray(x, tdir), depth, Xi, scene, KD_forest) * TP;
    }
    else{
      res = radiance(reflRay, depth, Xi, scene, KD_forest) * Re +
          radiance(Ray(x, tdir), depth, Xi, scene, KD_forest) * Tr;
    }
    cout << "dissolve other " << endl;
    print_Vec(ke + res);
    return ke + res;

  }

  if (mtl.shininess > 1) { // REFLECTION
    cout <<"mtl.shininess " << endl;
    if (erand48(Xi) < pp / (p + pp)) {// Russian roulette
      double xx = erand48(Xi);
      double yy = erand48(Xi);
      double nn = mtl.shininess;

      vec w = r.dire_ - n * 2 * n.dot(r.dire_);
      vec u = w.cross(nl), v= w.cross(u);
      u /= u.norm(); v /= v.norm();

      double c = pow(xx, 1 / (nn + 1));
      double r1 = sqrt(1 - c * c);
      double r2 = 2 * M_PI * yy;
      double a = r1 * cos(r2);
      double b = r1 * sin(r2);

      vec dir = (w * c + u * a + v * b);
      dir /= dir.norm();
      if (dir.dot(nl) < 0)
        dir = w;

      vec res = vec(ff.array() * radiance(Ray(x, dir), depth, Xi, scene, KD_forest).array());
      cout << "shininess"<< endl;
      print_Vec(ke + res);
      return ke + res;
    }
  }

  double r1 = 2 * M_PI * erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);
  cout << "rrr" << endl;
  cout << r1 << " " << r2 << " " << r2s;
  vec w = nl, u = vec::Zero();{
    if(fabs(w(0)) > .1)
      u(1) = 1;
    else
      u(0) = 1;
    u = u.cross(w);
    u /= u.norm();
  }

  vec v = w.cross(u);
  vec d = (u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1 - r2));
  d /= d.norm();
  cout << "final " << endl;
  auto res = vec(f.array() * radiance(Ray(x, d), depth, Xi, scene, KD_forest).array());
  print_Vec(ke + res);
  return ke + res;

}
inline double clamp(double x) { return x < 0 ? 0 : x > 1 ? 1 : x; }

inline int toInt(double x) { return int(pow(clamp(x), 1 / 2.2) * 255 + .5); }

inline unsigned char toChar(double x) {//Gamma correction
  return static_cast<unsigned char>(toInt(x));
}

void save_ppm(const std::string &name, const std::vector<vec> &img, const int w, const int h) {
  FILE *f = fopen(name.c_str(), "w");
  fprintf(f, "P6\n%i %i 255\n", w, h);
  for (int i = 0; i < w * h; ++i) {
    for(size_t dim = 0; dim < 3; ++dim){
      fputc(toChar(img[i](dim)), f);
    }
  }
  fclose(f);
}

class Setting {
public:
  int spp, w, h;
  double fovy, aspect;
  Ray cam;
  vec up;

  Setting(const string &file) {
    ifstream fin(file);
    double fovy_angle = 0;
    fin >> spp >> w >> h >> fovy_angle;
    aspect = w * 1.0 / h;
    fovy = tan(M_PI * fovy_angle / 2 / 180.0);

    fin >> cam.origin_(0) >> cam.origin_(1) >> cam.origin_(2);
    vec lookat;
    fin >> lookat(0) >> lookat(1) >> lookat(2);
    cam.dire_ = (lookat - cam.origin_);
    cam.dire_ /= cam.dire_.norm();

    fin >> up(0) >> up(1) >> up(2);
    up /= up.norm();
  }
};

int main(int argc, char *argv[]) {
  if (argc < 2) return printf("usage: %s input.obj cam.txt\n", argv[0]);
  const Scene scene(argv[1]);
  const Setting st(argv[2]);
  const int samps = st.spp / 4;// using 4 subpiexel

  //cout << "[INFO]>>>>>>>>>>>>>>>>>>>forest<<<<<<<<<<<<<<<<<<" << endl;
  size_t height = 25;
  size_t num_model = scene.models.size();
  //cout << "num model is "<< num_model << endl;
  vector<unique_ptr<KD_tree_tris>> KD_forest(num_model);
  #pragma omp parallel for
  for(size_t m_id = 0; m_id < num_model; ++m_id){
    KD_forest[m_id] = unique_ptr<KD_tree_tris>(new KD_tree_tris(scene.models[m_id]->tris_, height, 0, nullptr, true));
  }

  //cout << "[INFO]>>>>>>>>>>>>>>>>>>>forest<<<<<<<<<<<<<<<<<<" << endl;

  // camera coordinate
  vec cx = (st.cam.dire_.cross(st.up)) * st.aspect * st.fovy, r = vec::Zero();
  vec cy = (cx.cross(st.cam.dire_));
  cy /= cy.norm() * st.fovy;
  vector<vec> c(st.w * st.h);// color buffer

  for (int y = 0; y < 1; y++) {
    fprintf(stderr, "\rRendering (%d spp) %5.2f%%", st.spp, 100. * y / (st.h - 1));
    unsigned short Xi[3] = {0, 0, y * y * y};
    for (int x = 17; x < 18 ; x++) {
      for (int sy = 0, i = (st.h - y - 1) * st.w + x; sy < 2; sy++) { // 2x2 subpixel
        for (int sx = 0; sx < 2; sx++, r = vec::Zero()) {
          for (int s = 1; s <= samps; s++) {
            double r1 = 2 * erand48(Xi), dx = r1 < 1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);
            double r2 = 2 * erand48(Xi), dy = r2 < 1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);
            vec d = cx * (((sx + .5 + dx) / 2 + x) / st.w - .5) +
                    cy * (((sy + .5 + dy) / 2 + y) / st.h - .5) + st.cam.dire_;

            d /= d.norm();
            cout << "before plus " << endl;print_Vec(r);
            r = r + radiance(Ray(st.cam.origin_, d), 0, Xi, scene, KD_forest) * (1. / samps);
            cout << "this is r " << endl << r(0)<< " "<< r(1) << " " << r(2) << endl;

          }
          // TODO: check whether we need lock here
          vec delt_c;
          delt_c << clamp(r(0)), clamp(r(1)), clamp(r(2));
          c[i] = c[i] + delt_c * .25;
        }
      }
    }
  }

}


#include <boost/test/unit_test.hpp>

#include <random>

#include "rfr/data_containers/default_data_container.hpp"
#include "rfr/splits/classification_split.hpp"
#include "rfr/nodes/temporary_node.hpp"
#include "rfr/nodes/k_ary_node.hpp"
#include "rfr/trees/tree_options.hpp"
#include "rfr/trees/k_ary_tree.hpp"
#include "rfr/forests/classification_forest.hpp"
#include "rfr/forests/forest_options.hpp"

typedef double num_type;
typedef unsigned int response_type;
typedef unsigned int index_type;
typedef std::default_random_engine rng_type;

typedef rfr::data_containers::default_container<num_type, response_type, index_type> data_container_type;

typedef rfr::splits::classification_split<rng_type, num_type, response_type, index_type> split_type;
typedef rfr::nodes::k_ary_node<2, split_type, rng_type, num_type, response_type, index_type> node_type;
typedef rfr::nodes::temporary_node<num_type, index_type> tmp_node_type;

typedef rfr::trees::k_ary_random_tree<2, split_type, rng_type, num_type, response_type, index_type> tree_type;

BOOST_AUTO_TEST_CASE( data_container_tests ){
    data_container_type data;

    char *filename = (char*) malloc(1024*sizeof(char));

    strcpy(filename, boost::unit_test::framework::master_test_suite().argv[1]);
    //strcat(filename, "toy_data_set_features.csv");
    strcat(filename, "Skin_NonSkin_features.csv");
    std::cout<<filename<<"\n";
    data.read_feature_file(filename);

    strcpy(filename, boost::unit_test::framework::master_test_suite().argv[1]);
	//strcat(filename, "toy_data_set_responses.csv");
	strcat(filename, "Skin_NonSkin_responses.csv");
	std::cout<<filename<<"\n";
    data.read_response_file(filename);

	rfr::trees::tree_options<num_type, response_type, index_type> tree_opts;
	tree_opts.min_samples_to_split = 10;
	tree_opts.min_samples_in_leaf = 5;
	tree_opts.max_features = 10;

	rfr::forests::forest_options<num_type, response_type, index_type> forest_opts(tree_opts);

	forest_opts.num_data_points_per_tree = 2*data.num_data_points();
	forest_opts.num_trees = 10;
	forest_opts.do_bootstrapping = true;

	rfr::forests::classification_forest< tree_type, rng_type, num_type, response_type, index_type> the_forest(forest_opts);

	rng_type rng;

	the_forest.fit(data, rng);
	the_forest.print_info();

	std::tuple<response_type, num_type> tmp;
	std::vector<response_type> data_point_five = data.retrieve_data_point(5);
	// there should be the predicted class
	tmp = the_forest.predict_class(data_point_five.data());

	the_forest.save_latex_representation("/tmp/tree");

	std::cout<<std::get<0>(tmp) <<" "<<std::get<1>(tmp)<<"\n";
    free(filename);
}


// Copyright 2015-2022 The ALMA Project Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

/// @file
/// Definitions corresponding to symmetry.hpp.

extern "C" {
#include <spglib/spglib.h>
}

#include <symmetry.hpp>
#include <Eigen/LU>

namespace alma {
Symmetry_operations::Symmetry_operations(const Crystal_structure& structure,
                                         double _symprec)
    : symprec(_symprec) {
    int natoms = structure.get_natoms();
    // Create some C-friendly structures from the data
    // provided.
    double c_lattvec[3][3];

    for (auto i = 0; i < 3; ++i)
        for (auto j = 0; j < 3; ++j)
            c_lattvec[i][j] = structure.lattvec(i, j);
    std::vector<int> c_types;

    for (decltype(structure.numbers.size()) i = 0; i < structure.numbers.size();
         ++i)
        for (auto j = 0; j < structure.numbers[i]; ++j)
            c_types.push_back(i);
    auto c_positions = new double[natoms][3];

    for (decltype(natoms) i = 0; i < natoms; ++i)
        for (auto j = 0; j < 3; ++j)
            c_positions[i][j] = structure.positions(j, i);
    // Obtain a SpglibDataset structure containing
    // all information.
    SpglibDataset* data = spg_get_dataset(
        c_lattvec, c_positions, c_types.data(), natoms, this->symprec);

    if (data == nullptr)
        throw value_error("spglib's spg_get_dataset"
                          " returned a NULL pointer");
    // Copy all interesting information out of the
    // SpglibDataset.
    this->sg_number = data->spacegroup_number;
    this->sg_symbol = data->international_symbol;

    for (decltype(natoms) i = 0; i < natoms; ++i) {
        this->wyckoff += constants::alphabet[data->wyckoffs[i]];
        this->equivalences.push_back(data->equivalent_atoms[i]);
    }
    auto nsymm = data->n_operations;

    for (decltype(nsymm) i = 0; i < nsymm; ++i) {
        Eigen::VectorXd trans(3);

        for (auto j = 0; j < 3; ++j)
            trans(j) = data->translations[i][j];
        this->translations.emplace_back(trans);
        Eigen::MatrixXd rot(3, 3);

        for (auto j = 0; j < 3; ++j)
            for (auto k = 0; k < 3; ++k)
                rot(j, k) = data->rotations[i][j][k];
        this->rotations.emplace_back(rot);
    }
    // Free the allocated memory.
    delete[] c_positions;
    spg_free_dataset(data);

    // Compute the Cartesian representations of rotations
    // and translations.
    for (const auto& t : translations)
        this->ctranslations.emplace_back(structure.lattvec * t);
    auto solver = structure.lattvec.transpose().colPivHouseholderQr();

    for (const auto& r : rotations)
        this->crotations.emplace_back(
            (solver.solve(r.transpose()) * structure.lattvec.transpose())
                .transpose());
    // Express the rotations in direct reciprocal coordinates
    // as well.
    auto hermitian = structure.lattvec.transpose() * structure.lattvec;
    auto qsolver = hermitian.colPivHouseholderQr();

    for (const auto& r : rotations)
        this->qrotations.emplace_back(
            (qsolver.solve(r.transpose()) * hermitian).transpose());
    // Fill symmetry_map.
    this->fill_map(structure);
    // Compute the determinant of each rotation matrix.
    this->determinants.resize(nsymm);

    for (decltype(nsymm) i = 0; i < nsymm; ++i)
        this->determinants[i] = rotations[i].determinant();

    // Compute the displacements of the original image of each
    // atom.
    for (decltype(nsymm) i = 0; i < nsymm; ++i) {
        Eigen::MatrixXd delta(3, natoms);

        for (decltype(natoms) j = 0; j < natoms; ++j) {
            std::size_t jp = this->symmetry_map[j][i];
            delta.col(j) =
                structure.positions.col(jp) - structure.positions.col(j);
        }
        this->displacements.emplace_back(delta);
    }
}

void Symmetry_operations::fill_map(const Crystal_structure& structure) {
    auto natoms = static_cast<std::size_t>(structure.get_natoms());
    auto nops = this->get_nsym();
    Eigen::Vector3d r;
    Eigen::Vector3d newr;
    Eigen::Vector3d delta;

    for (std::size_t ia = 0; ia < natoms; ++ia) {
        r = structure.positions.col(ia);
        std::vector<std::size_t> images(nops);

        // Try all the symmetry operations on each atom.
        for (decltype(nops) iop = 0; iop < nops; ++iop) {
            newr = this->transform_v(r, iop);
            // Look for the corresponding atom.
            bool found = false;

            for (std::size_t ja = 0; ja < natoms; ++ja) {
                delta = newr - structure.positions.col(ja);

                for (auto i = 0; i < 3; ++i)
                    delta(i) -= std::round(delta(i));

                if (delta.lpNorm<Eigen::Infinity>() < this->symprec) {
                    images[iop] = ja;
                    found = true;
                    break;
                }
            }

            if (!found) {
                throw value_error("inconsistent symmetries");
            }
        }
        this->symmetry_map.emplace_back(images);
    }
}

std::vector<std::vector<Transformed_pair>>
Symmetry_operations::get_pair_classes() const {
    auto natoms = this->symmetry_map.size();
    auto nops = this->translations.size();

    // Only internal translations are considered.
    Eigen::Matrix3d eye{Eigen::Matrix3d::Identity()};

    std::vector<bool> toskip(nops);

    for (std::size_t iop = 0; iop < nops; ++iop)
        toskip[iop] = (this->rotations[iop] - eye).norm() >
                      std::numeric_limits<float>::epsilon();
    std::vector<std::vector<Transformed_pair>> nruter;

    for (std::size_t a1 = 0; a1 < natoms; ++a1)
        for (std::size_t a2 = a1; a2 < natoms; ++a2) {
            std::size_t iop;

            for (iop = 1; iop < nops; ++iop) {
                // Check that the operation is an internal
                // translation.
                if (toskip[iop] ||
                    !almost_equal(0.,
                                  (this->displacements[iop].col(a1) -
                                   this->displacements[iop].col(a2))
                                      .norm()))
                    continue;
                // And look for an equivalence class containing
                // the image of the pair.
                std::size_t b1 = this->symmetry_map[a1][iop];
                std::size_t b2 = this->symmetry_map[a2][iop];
                bool found = false;

                for (auto& eq : nruter) {
                    if ((b1 == eq[0].pair[0]) && (b2 == eq[0].pair[1])) {
                        eq.push_back(Transformed_pair(a1, a2, iop, false));
                        found = true;
                        break;
                    }
                    else if ((b1 == eq[1].pair[0]) && (b2 == eq[1].pair[1])) {
                        eq.push_back(Transformed_pair(a1, a2, iop, true));
                        found = true;
                        break;
                    }
                }

                if (found)
                    break;
            }

            // If a suitable equivalence class is not found, create
            // a new one.
            if (iop == nops) {
                nruter.push_back(std::vector<Transformed_pair>());
                nruter.back().push_back(Transformed_pair(a1, a2, 0, false));
            }
        }

    // For each pair (a1, a2) with a1 != a2, add (a2, a1) to the
    // same class.
    for (auto& eq : nruter) {
        std::size_t s = eq.size();

        for (std::size_t i = 0; i < s; ++i) {
            if (eq[i].pair[0] != eq[i].pair[1])
                eq.push_back(Transformed_pair(eq[i].pair[1],
                                              eq[i].pair[0],
                                              eq[i].operation,
                                              !eq[i].exchange));
            ;
        }
    }
    return nruter;
}

Eigen::MatrixXcd calc_big_rotation_matrix(const Q_orbit& orbit,
                                          const Crystal_structure& structure,
                                          const Symmetry_operations& symms) {
    std::size_t n_atoms = structure.get_natoms();
    Eigen::MatrixXcd nruter(Eigen::MatrixXcd::Zero(3 * n_atoms, 3 * n_atoms));
    auto pos = orbit.get_representative_index();
    Eigen::Vector3d q_repr = orbit.q_images[pos];
    auto i_rot = orbit.i_rot[pos];
    Eigen::Vector3d trans = symms.translations[i_rot];
    Eigen::Matrix3d d_rot = symms.rotations[i_rot];
    Eigen::Matrix3d c_rot = symms.crotations[i_rot];
    for (decltype(n_atoms) i_atom = 0; i_atom < n_atoms; ++i_atom) {
        auto j_atom = symms.map_atom(i_atom, i_rot);
        Eigen::Vector3d old_pos = structure.positions.col(i_atom);
        Eigen::Vector3d new_pos = structure.positions.col(j_atom);
        Eigen::Vector3d delta = new_pos - (d_rot * old_pos + trans);
        double arg = 2. * alma::constants::pi * q_repr.dot(delta);
        std::complex<double> exp_factor(std::cos(arg), std::sin(arg));
        nruter.block<3, 3>(3 * j_atom, 3 * i_atom) = exp_factor * c_rot;
    }
    return nruter.inverse();
}
} // namespace alma


/*
Copyright 2014 Rogier van Dalen.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/** \file
Define magma properties for number types.
*/

#ifndef MATH_ARITHMETIC_MAGMA_HPP_INCLUDED
#define MATH_ARITHMETIC_MAGMA_HPP_INCLUDED

#include <cmath>
#include <limits>
#include <type_traits>

#include <boost/mpl/bool.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/not.hpp>
#include <boost/utility/enable_if.hpp>

#include "magma.hpp"

#include "detail/is_close.hpp"

namespace math {

template <class Type> struct arithmetic_magma_tag;

template <class Type> struct decayed_magma_tag <Type, typename
    boost::enable_if_c <(
        std::numeric_limits <Type>::is_specialized
        && !std::is_same <Type, bool>::value)
    >::type>
{ typedef arithmetic_magma_tag <Type> type; };

namespace operation {

    /* Queries. */

    template <class Type>
        struct is_member <arithmetic_magma_tag <Type>, typename
            boost::enable_if_c <std::numeric_limits <Type>::has_quiet_NaN>::type
        >
    // Detect NaN.
    { bool operator() (Type const & v) const { return v == v; } };

    template <class Type> struct equal <arithmetic_magma_tag <Type>>
    : operator_equal <bool> {};

    template <class Type>
        struct approximately_equal <arithmetic_magma_tag <Type>, typename
            boost::enable_if_c <!std::numeric_limits <Type>::is_exact>::type>
    {
        bool operator() (Type const & left, Type const & right) const {
            static float const tolerance = 1e-5;
            return math::detail::is_close (left, right, tolerance);
        }
    };

    /* Produce. */

    // Return the not-a-number value.
    template <class Type> struct non_member <arithmetic_magma_tag <Type>,
        typename std::enable_if <std::numeric_limits <Type>::has_quiet_NaN
        >::type>
    {
        Type operator() () const
        { return std::numeric_limits <Type>::quiet_NaN(); }
    };

    template <class Type>
        struct identity <arithmetic_magma_tag <Type>, callable::times>
    { Type operator() () const { return Type (1); } };

    template <class Type>
        struct identity <arithmetic_magma_tag <Type>, callable::plus>
    { Type operator() () const { return Type (0); } };

    template <class Type>
        struct annihilator <arithmetic_magma_tag <Type>, callable::times>
    { Type operator() () const { return Type (0); } };

    /* Operations. */

    template <class Type> struct compare <arithmetic_magma_tag <Type>>
    : operator_less<> {};

    // "choose" is not defined: it is not known whether it should be the
    // maximum or the minimum.

    template <class Type> struct times <arithmetic_magma_tag <Type>>
    : operator_times <Type>, associative, commutative,
        approximate_if <boost::mpl::bool_ <
            !std::numeric_limits <Type>::is_exact>> {};

    template <class Type> struct plus <arithmetic_magma_tag <Type>>
    : operator_plus <Type>, associative, commutative,
        approximate_if <boost::mpl::bool_ <
            !std::numeric_limits <Type>::is_exact>> {};

    template <class Type>
        struct is_semiring <arithmetic_magma_tag <Type>, either,
            callable::times, callable::plus>
    : rime::true_type {};

    template <class Type> struct divide <arithmetic_magma_tag <Type>, either,
        typename boost::enable_if_c <!std::numeric_limits <Type>::is_integer
        >::type>
    : operator_divide <Type>, approximate {};

    template <class Type> struct minus <arithmetic_magma_tag <Type>, either,
        typename boost::enable_if_c <std::numeric_limits <Type>::is_signed
            >::type>
    : operator_minus <Type>, approximate_if <boost::mpl::bool_ <
        !std::numeric_limits <Type>::is_exact>> {};

    template <class Type>
        struct invert <arithmetic_magma_tag <Type>, either, callable::plus,
            typename boost::enable_if_c <std::numeric_limits <Type>::is_signed
                >::type>
    { Type operator() (Type const & v) const { return Type (-v); } };

    template <class Type>
        struct invert <arithmetic_magma_tag <Type>, either, callable::times,
            typename boost::disable_if_c <std::numeric_limits <Type>::is_integer
                >::type>
    : approximate_if <boost::mpl::bool_ <!std::numeric_limits <Type>::is_exact>>
    { Type operator() (Type const & v) const { return Type (Type (1) / v); } };

    template <class Type> struct print <arithmetic_magma_tag <Type>>
    : operator_shift_left_stream {};

} // namespace operation

} // namespace math

#endif // MATH_ARITHMETIC_MAGMA_HPP_INCLUDED


// File: vector_reduction.cpp

#include <iostream>
#include <boost/numeric/mtl/mtl.hpp>

int main(int, char**)
{
    using namespace mtl;

    typedef std::complex<double>  cdouble;
    dense_vector<cdouble>         v(100);

    for (unsigned i= 0; i < size(v); i++)
	v[i]= cdouble(i+1, 100-i);

    std::cout << "sum(v) is " << sum(v)<< "\n";

    std::cout << "product(v) is " <<  product(v)<< "\n";

    std::cout << "sum<6>(v) is " <<  sum<6>(v)<< "\n";

    return 0;
}


#include <cstddef>
#include <valarray>
#include <boost/numeric/ublas/matrix.hpp>
#include "../peer_sampling/hierarchical/hierarchical.h++"

#ifndef PEERS_GRAPH
class PeersGraph
{
    boost::numeric::ublas::matrix<double> W;

public:
    PeersGraph(void);

    PeersGraph(const PeersGraph&);

    PeersGraph(HierarchicalGossipConnector[], size_t);

    ~PeersGraph(void);

    PeersGraph& operator=(const PeersGraph&);

    std::valarray<unsigned> inDegs(void) const;

    std::valarray<unsigned> outDegs(void) const;

    std::valarray<unsigned> totDegs(void) const;

    std::valarray<unsigned> biDegs(void) const;

    std::valarray<unsigned> degs(void) const;

    std::valarray<double> inWeights(void) const;

    std::valarray<double> outWeights(void) const;

    std::valarray<double> totWeights(void) const;

    std::valarray<double> biWeights(void) const;

    std::valarray<double> weights(void) const;

    double avgIODeg(void) const;

    double avgTotDeg(void) const;

    double avgBiDeg(void) const;

    double avgDeg(void) const;

    double avgIOWeight(void) const;

    double avgTotWeight(void) const;

    double avgBiWeight(void) const;

    double avgWeight(void) const;

    std::valarray<double> bidirShares(void) const;

    double bidirShare(void) const;

    std::valarray<int> difDegs(void) const;

    std::valarray<double> difWeights(void) const;

    double avgAbsDifDeg(void) const;

    double avgAbsDifWeight(void) const;

    std::valarray<double> locUwClustCoefs(void) const;

    std::valarray<double> locClustCoefs(void) const;

    double avgUwClustCoef(void) const;

    double avgClustCoef(void) const;

    unsigned diameter(void) const;

    double avgShortPathLength(void) const;
};
#define PEERS_GRAPH
#endif