/*******************************************************************************
 * Copyright (c) 2015-2016 ONERA.
 * Ce logiciel est la propriété de l’ONERA (the French Aerospace Lab).
 * Tous droits réservés.
 *
 * Ce programme et les éléments qui l'accompagnent sont mis à disposition
 * aux conditions définies par le contrat de licence logiciel CeCILL-C soumise
 * au droit français et respectant les principes de diffusion des logiciels libres.
 * Vous pouvez utiliser, modifier et/ou redistribuer ce programme
 * sous les conditions de la licence CeCILL-C (http://www.cecill.info).

 * This software is property of ONERA (the French Aerospace Lab).
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under
 * the terms of the CeCILL-C license under French law and
 * abiding by the rules of distribution of free software.
 * You can  use, modify and/ or redistribute the software under the terms of
 * the CeCILL-C license (http://www.cecill.info).
 *
 * Contributeurs/contributors:
 *      Florent Teichteil-Königsbuch (ONERA - Centre de Toulouse) - initial API and implementation
 *
 *******************************************************************************/

#ifndef UBLAS_BACKEND_HH
#define	UBLAS_BACKEND_HH

#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include <boost/numeric/ublas/operation.hpp>
#include <boost/numeric/ublas/triangular.hpp>
#include <boost/numeric/ublas/io.hpp>

#include <boost/numeric/bindings/lapack.hpp>
#include <boost/numeric/bindings/blas.hpp>
#include <boost/numeric/bindings/ublas/vector.hpp>
#include <boost/numeric/bindings/ublas/matrix.hpp>
#include <boost/numeric/bindings/ublas/banded.hpp>
#include <boost/numeric/bindings/ublas/triangular.hpp>
#include <boost/numeric/bindings/conj.hpp>
#include <boost/numeric/bindings/trans.hpp>
#include <boost/numeric/bindings/herm.hpp>

#include <memory>

#include "ScalarBackend.hh"

namespace epoch {
	namespace algebra {
		/** Ublas standard backend with upper Hessenberg matrices as banded matrices */
		struct UblasBandedHessenberg {
			/**
			 * Implementation of upper Hessenberg matrices
			 * @tparam Type of elements stored in matrices and vectors
			 */
			template <typename T>
			struct MatrixType {
				typedef boost::numeric::ublas::banded_matrix<T, boost::numeric::ublas::column_major> Implementation;

				/**
				 * Generates a square identity upper Hessenberg matrix
				 * @param dim Dimension of the matrix
				 * @return Upper Hessenberg matrix equal to identity
				 */
				inline static std::auto_ptr<Implementation> identity(const std::size_t& dim);

				/**
				 * Generates a zero upper Hessenberg matrix
				 * @param dim1 First dimension of the matrix
				 * @param dim2 Second dimension of the matrix
				 * @return Upper Hessenberg matrix equal to zero
				 */
				inline static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2);

				/**
				 * Computes the conjugate of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Conjugate of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& m);

				/**
				 * Computes the real part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Real part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& m);

				/**
				 * Computes the imaginary part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Imaginary part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& m);

				/**
				 * Prints an upper Hessenberg matrix in a given output stream
				 * @param m Upper Hessenberg matrix
				 * @param o Output stream
				 */
				inline static void print(const Implementation& m, std::ostream& o);
			};

			/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
			template <typename T, typename Tc>
			static void computeF1(const T& a, const T& b, const T& c, const typename MatrixType<Tc>::Implementation& L, typename MatrixType<T>::Implementation& res);

			/** Computes res += a * L */
			template <typename T>
			static void computeF2(const T& a, const typename MatrixType<T>::Implementation& L, typename MatrixType<T>::Implementation& res);
		};


		/** Ublas standard backend with upper Hessenberg matrices as triangular matrices plus a vector representing the additional diagonal */
		struct UblasTriangularHessenberg {
			/**
			 * Implementation of upper Hessenberg matrices
			 * @tparam Type of elements stored in matrices and vectors
			 */
			template <typename T>
			struct MatrixType {
				struct Implementation {
					typedef boost::numeric::ublas::triangular_matrix<T, boost::numeric::ublas::upper, boost::numeric::ublas::column_major> UpperTriangleType;
					typedef boost::numeric::ublas::vector<T> DiagonalVectorType;

					UpperTriangleType upperTriangle;
					DiagonalVectorType diagonalVector;

					const T& operator()(const std::size_t& i, const std::size_t& j) const;
					T& operator()(const std::size_t& i, const std::size_t& j);
				};

				/**
				 * Generates a square identity upper Hessenberg matrix
				 * @param dim Dimension of the matrix
				 * @return Upper Hessenberg matrix equal to identity
				 */
				inline static std::auto_ptr<Implementation> identity(const std::size_t& dim);

				/**
				 * Generates a zero upper Hessenberg matrix
				 * @param dim1 First dimension of the matrix
				 * @param dim2 Second dimension of the matrix
				 * @return Upper Hessenberg matrix equal to zero
				 */
				inline static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2);

				/**
				 * Computes the conjugate of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Conjugate of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& m);

				/**
				 * Computes the real part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Real part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& m);

				/**
				 * Computes the imaginary part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Imaginary part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& m);

				/**
				 * Prints an upper Hessenberg matrix in a given output stream
				 * @param m Upper Hessenberg matrix
				 * @param o Output stream
				 */
				inline static void print(const Implementation& m, std::ostream& o);
			};

			/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
			template <typename T, typename Tc>
			static void computeF1(const T& a, const T& b, const T& c, const typename MatrixType<Tc>::Implementation& L, typename MatrixType<T>::Implementation& res);

			/** Computes res += a * L */
			template <typename T>
			static void computeF2(const T& a, const typename MatrixType<T>::Implementation& L, typename MatrixType<T>::Implementation& res);
		};


		/** Ublas BLAS backend with dense upper Hessenberg matrices (time efficient, but memory inefficient) */
		struct UblasDenseHessenberg {
			/**
			 * Implementation of upper Hessenberg matrices
			 * @tparam Type of elements stored in matrices and vectors
			 */
			template <typename T>
			struct MatrixType {
				typedef boost::numeric::ublas::matrix<T, boost::numeric::ublas::column_major> Implementation;

				/**
				 * Generates a square identity upper Hessenberg matrix
				 * @param dim Dimension of the matrix
				 * @return Upper Hessenberg matrix equal to identity
				 */
				inline static std::auto_ptr<Implementation> identity(const std::size_t& dim);

				/**
				 * Generates a zero upper Hessenberg matrix
				 * @param dim1 First dimension of the matrix
				 * @param dim2 Second dimension of the matrix
				 * @return Upper Hessenberg matrix equal to zero
				 */
				inline static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2);

				/**
				 * Computes the conjugate of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Conjugate of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& m);

				/**
				 * Computes the real part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Real part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& m);

				/**
				 * Computes the imaginary part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Imaginary part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& m);

				/**
				 * Prints an upper Hessenberg matrix in a given output stream
				 * @param m Upper Hessenberg matrix
				 * @param o Output stream
				 */
				inline static void print(const Implementation& m, std::ostream& o);
			};

			/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
			template <typename T, typename Tc>
			static void computeF1(const T& a, const T& b, const T& c, const typename MatrixType<Tc>::Implementation& L, typename MatrixType<T>::Implementation& res);

			/** Computes res += a * L */
			template <typename T>
			static void computeF2(const T& a, const typename MatrixType<T>::Implementation& L, typename MatrixType<T>::Implementation& res);
		};

		/** Standard Ublas backend */
		struct UblasBackendStandard {};

		/** Optimized BLAS Ublas backend */
		struct UblasBackendBLAS {};
		
		/**
		 * Class representing uBlas-based backend for EPOCH algorithms
		 * @tparam T Type of real numbers
		 */
		template <typename Thessenberg = UblasBandedHessenberg, typename Tbackend = UblasBackendStandard>
		class UblasBackend {
		public :
			/**
			 * Default matrix
			 * @tparam T Type of elements of the matrix
			 */
			template <typename T>
			struct Matrix {
				/** Actual implementation */
				typedef boost::numeric::ublas::matrix<T, boost::numeric::ublas::column_major> Implementation;

				/**
				 * Generates a square identity dense matrix
				 * @param dim Dimension of the matrix
				 * @return Dense matrix equal to identity
				 */
				static std::auto_ptr<Implementation> identity(const std::size_t& dim);

				/**
				 * Generates a zero dense matrix
				 * @param dim1 First dimension of the matrix
				 * @param dim2 Second dimension of the matrix
				 * @return Dense matrix equal to zero
				 */
				static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2);

				/**
				 * Computes the conjugate of a given matrix
				 * @param m Matrix
				 * @return Conjugate of the given matrix
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& m);

				/**
				 * Computes the real part of a given matrix
				 * @param m matrix
				 * @return Real part of the given matrix
				 */
				inline static std::auto_ptr<typename Matrix<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& m);

				/**
				 * Computes the imaginary part of a given matrix
				 * @param m Matrix
				 * @return Imaginary part of the given matrix
				 */
				inline static std::auto_ptr<typename Matrix<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& m);

				/**
				 * Prints a matrix in a given output stream
				 * @param m Matrix
				 * @param o Output stream
				 */
				inline static void print(const Implementation& m, std::ostream& o);

				/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
				template <typename Tc>
				static void computeF1(const T& a, const T& b, const T& c, const typename Matrix<Tc>::Implementation& L, Implementation& res);

				/** Computes res += a * L */
				static void computeF2(const T& a, const Implementation& L, Implementation& res);
			};

			/**
			 * Upper Hessenberg matrix
			 * @tparam T Type of elements of the matrix
			 */
			template <typename T>
			struct HessenbergMatrix {
				/** Actual implementation */
				typedef typename Thessenberg::template MatrixType<T>::Implementation Implementation;

				/**
				 * Generates a square identity upper Hessenberg matrix
				 * @param dim Dimension of the matrix
				 * @return Upper Hessenberg matrix equal to identity
				 */
				inline static std::auto_ptr<Implementation> identity(const std::size_t& dim) {
					return Thessenberg::template MatrixType<T>::identity(dim);
				}

				/**
				 * Generates a zero upper Hessenberg matrix
				 * @param dim1 First dimension of the matrix
				 * @param dim2 Second dimension of the matrix
				 * @return Upper Hessenberg matrix equal to zero
				 */
				inline static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2) {
					return Thessenberg::template MatrixType<T>::zero(dim1, dim2);
				}

				/**
				 * Computes the conjugate of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Conjugate of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& m) {
					return Thessenberg::template MatrixType<T>::conj(m);
				}

				/**
				 * Computes the real part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Real part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename Thessenberg::template MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& m) {
					return Thessenberg::template MatrixType<T>::real(m);
				}

				/**
				 * Computes the imaginary part of a given upper Hessenberg matrix
				 * @param m Upper Hessenberg matrix
				 * @return Imaginary part of the given upper Hessenberg matrix
				 */
				inline static std::auto_ptr<typename Thessenberg::template MatrixType<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& m) {
					return Thessenberg::template MatrixType<T>::imag(m);
				}

				/**
				 * Prints an upper Hessenberg matrix in a given output stream
				 * @param m Upper Hessenberg matrix
				 * @param o Output stream
				 */
				inline static void print(const Implementation& m, std::ostream& o) {
					Thessenberg::template MatrixType<T>::print(m, o);
				}

				/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
				template <typename Tc>
				inline static void computeF1(const T& a, const T& b, const T& c, const typename HessenbergMatrix<Tc>::Implementation& L, Implementation& res) {
					Thessenberg::template computeF1(a, b, c, L, res);
				}

				/** Computes res += a * L */
				inline static void computeF2(const T& a, const Implementation& L, Implementation& res) {
					Thessenberg::template computeF2(a, L, res);
				}
			};

			/**
			 * Vector type
			 * @tparam T Type of elements of the matrix
			 */
			template <typename T>
			struct Vector {
				/** Actual implementation */
				typedef boost::numeric::ublas::vector<T> Implementation;

				/**
				 * Generates a vector whose all entries are equal to 1
				 * @param dim Dimension of the vector
				 * @return Vector with all entries equal to 1
				 */
				static std::auto_ptr<Implementation> identity(const std::size_t& dim);

				/**
				 * Generates a zero vector
				 * @param dim1 Dimension of the vector
				 * @param dim2 Useless
				 * @return Zero vector
				 */
				static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2);

				/**
				 * Computes the conjugate of a given vector
				 * @param v Vector
				 * @return Conjugate of the given vector
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& v);

				/**
				 * Computes the real part of a given vector
				 * @param v Vector
				 * @return Real part of the given vector
				 */
				inline static std::auto_ptr<typename Vector<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> real(const Implementation& v);

				/**
				 * Computes the imaginary part of a given vector
				 * @param v Vector
				 * @return Imaginary part of the given vector
				 */
				inline static std::auto_ptr<typename Vector<typename boost::numeric::ublas::type_traits<T>::real_type>::Implementation> imag(const Implementation& v);

				/**
				 * Prints a vector in a given output stream
				 * @param v Vector
				 * @param o Output stream
				 */
				inline static void print(const Implementation& v, std::ostream& o);

				/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
				template <typename Tc>
				static void computeF1(const T& a, const T& b, const T& c, const typename Vector<Tc>::Implementation& L, Implementation& res);

				/** Computes res += a * L */
				static void computeF2(const T& a, const Implementation& L, Implementation& res);
			};

			/** NoTransformation functor */
			struct NoTransformation : public ScalarBackend::NoTransformation {
				template <typename T>
				inline const T& opsM(const T& t) const {return t;}

				template <typename T>
				inline const T& opbM(const T& t) const {return t;}

				template <typename T>
				inline const T& opsV(const T& t) const {return t;}

				template <typename T>
				inline const T& opbV(const T& t) const {return t;}
			};

			/** ConjugateTransformation functor */
			struct ConjugateTransformation : public ScalarBackend::ConjugateTransformation {
				template <typename E>
				inline typename boost::numeric::ublas::matrix_unary1_traits<E, boost::numeric::ublas::scalar_conj<typename E::value_type> >::result_type
					opsM(const boost::numeric::ublas::matrix_expression<E> &e) const {
						return boost::numeric::ublas::conj(e);
				}

				template <typename M>
				typename boost::numeric::bindings::result_of::conj<const M>::type const opbM(const M& m) const {
					return boost::numeric::bindings::conj(m);
				}

				template <typename E>
				typename boost::numeric::ublas::vector_unary_traits<E, boost::numeric::ublas::scalar_conj<typename E::value_type> >::result_type
					opsV(const boost::numeric::ublas::vector_expression<E> &e) const {
						return boost::numeric::ublas::conj(e);
				}

				template <typename V>
				typename boost::numeric::bindings::result_of::conj<const V>::type const opbV(const V& v) const {
					return boost::numeric::bindings::conj(v);
				}
			};

			/** MakeRealTransformation functor */
			struct MakeRealTransformation : public ScalarBackend::MakeRealTransformation {
				template <typename T>
				struct makeRealFunctor {
					typedef T value_type;
					typedef typename T::value_type result_type;
					inline makeRealFunctor() {}

					inline static result_type apply(const value_type& x) {
						return (2.0 * real(x));
					}
				};

				// (op v) [i] = op( v [i] )
				template<class OP, class E> 
				BOOST_UBLAS_INLINE
				typename boost::numeric::ublas::vector_unary_traits<E, OP>::result_type
				vectorApplyToAll (const boost::numeric::ublas::vector_expression<E> &e, const OP& op = OP() ) const {
					typedef typename boost::numeric::ublas::vector_unary_traits<E, OP>::expression_type expression_type;
					return expression_type (e ());
				}

				// (op m) [i] = op( m [i] )
				template<class OP, class E> 
				BOOST_UBLAS_INLINE
				typename boost::numeric::ublas::matrix_unary1_traits<E, OP>::result_type
				matrixApplyToAll (const boost::numeric::ublas::matrix_expression<E> &e, const OP& op = OP() ) const {
					typedef typename boost::numeric::ublas::matrix_unary1_traits<E, OP>::expression_type expression_type;
					return expression_type (e ());
				}

				template <typename E>
				inline typename boost::numeric::ublas::matrix_unary1_traits<E, makeRealFunctor<typename E::value_type> >::result_type
					opsM(const boost::numeric::ublas::matrix_expression<E> &e) const {
						return matrixApplyToAll<makeRealFunctor<typename E::value_type> >(e);
				}

				template <typename E>
				inline typename boost::numeric::ublas::matrix_unary1_traits<E, makeRealFunctor<typename E::value_type> >::result_type
					opbM(const boost::numeric::ublas::matrix_expression<E> &e) const {
						return matrixApplyToAll<makeRealFunctor<typename E::value_type> >(e);
				}

				template <typename E>
				inline typename boost::numeric::ublas::vector_unary_traits<E, makeRealFunctor<typename E::value_type> >::result_type
					opsV(const boost::numeric::ublas::vector_expression<E> &e) const {
						return vectorApplyToAll<makeRealFunctor<typename E::value_type> >(e);
				}

				template <typename E>
				inline typename boost::numeric::ublas::vector_unary_traits<E, makeRealFunctor<typename E::value_type> >::result_type
					opbV(const boost::numeric::ublas::vector_expression<E> &e) const {
						return vectorApplyToAll<makeRealFunctor<typename E::value_type> >(e);
				}
			};

			/**
			 * Computes the real Schur decomposition of a matrix: A -> sv * A * trans(sv)
			 * @param A Input/Output matrix to decompose
			 * @param eig Output vector of eigenvalues
			 * @param sv Output matrix of Schur vectors
			 * @tparam TrealMatrix Type of input/output matrix
			 * @tparam TcomplexVector Type of output eigenvalue vector
			 */
			template <typename TrealMatrix, typename TcomplexVector>
			static void realSchurDecomposition(TrealMatrix& A, TcomplexVector& eig, TrealMatrix& sv);

			/**
			 * Computes a 1x(dim-i) vector res such that:
			 * res = M(i-1, i:(dim-i))
			 * M must be square, dim is its dimension
			 */
			template <typename T>
			static void subvector(const typename Matrix<T>::Implementation& M,
			                      const std::size_t& index,
			                      typename Vector<T>::Implementation& res);

			/**
			 * Computes a 2x(dim-i) matrix res such that:
			 * res(0) = a * M(i-2, i:(dim-i)) + b * M(i-1, i:(dim-i))
			 * res(1) = c * M(i-2, i:(dim-i)) + d * M(i-1, i:(dim-i))
			 * M must be square, dim is its dimension
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void submatrix(const typename Matrix<T1>::Implementation& M,
			                      const std::size_t& index,
			                      const T2& a,
			                      const T3& b,
			                      const T4& c,
			                      const T5& d,
			                      typename Matrix<T6>::Implementation& res);

			/** computeO1a(a, M, tm, V, tv, res): res = a * tv(trans(V)) * tm(M)
			 * Operates on the square submatrix of M starting at row i and column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO1a(const T1& a,
			                              const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                              const typename Vector<T4>::Implementation& V, const T5& vTransformation,
			                              typename Vector<T6>::Implementation& res,
			                              const std::size_t& index);

			/**
			 * computeO1b(a, M, tm, V, tv, res): res = a * tv(V) * tm(M)
			 * Operates on the square submatrix of M starting at row i and column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO1b(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       const typename Matrix<T4>::Implementation& V, const T5& vTransformation,
			                       typename Matrix<T6>::Implementation& res,
			                       const std::size_t& index);

			/** computeO1c(a, M, tm, V, tv, res): res = a * tm(M) * tv(V) */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO1c(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       const typename Vector<T4>::Implementation& V, const T5& vTransformation,
			                       typename Vector<T6>::Implementation& res);

			/**
			 * computeO2pa(V, tv, res): res += tv(V)
			 * Operates on the subvector of res at row i, starting from column i+1
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO2pa(const typename Vector<T1>::Implementation& V, const T2& vTransformation,
			                        typename HessenbergMatrix<T3>::Implementation& res,
			                        const std::size_t& index);

			/**
			 * computeO2ma(V, tv, res): res -= tv(V)
			 * Operates on the subvector of res at row i, starting from column i+1
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO2ma(const typename Vector<T1>::Implementation& V, const T2& vTransformation,
			                        typename HessenbergMatrix<T3>::Implementation& res,
			                        const std::size_t& index);

			/**
			 * computeO2pb(M, tm, res): res += tm(M)
			 * Operates on the submatrix of res at rows i and i+1, starting from column i+2
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO2pb(const typename Matrix<T1>::Implementation& M, const T2& mTransformation,
			                        typename HessenbergMatrix<T3>::Implementation& res,
			                        const std::size_t& index);

			/**
			 * computeO2mb(M, tm, res): res -= tm(M)
			 * Operates on the submatrix of res at rows i and i+1, starting from column i+2
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO2mb(const typename Matrix<T1>::Implementation& M, const T2& mTransformation,
			                        typename HessenbergMatrix<T3>::Implementation& res,
			                        const std::size_t& index);

			/** computeO2pc(V, tv, res): res += tv(V) */
			template <typename T1, typename T2, typename T3>
			static void computeO2pc(const typename Vector<T1>::Implementation& V, const T2& vTransformation,
			                        typename Vector<T3>::Implementation& res);

			/** computeO2mc(V, tv, res): res += tv(V) */
			template <typename T1, typename T2, typename T3>
			static void computeO2mc(const typename Vector<T1>::Implementation& V, const T2& vTransformation,
			                        typename Vector<T3>::Implementation& res);

			/**
			 * computeO3a(a, M, tm, V, tv, res): res = a * ((tv(trans(V)) * tm(M)) - res)
			 * Operates on the square submatrix of M starting at row i and column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO3a(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       const typename Vector<T4>::Implementation& V, const T5& vTransformation,
			                       typename Vector<T6>::Implementation& res,
			                       const std::size_t& index);

			/**
			 * computeO3b(a, M, tm, V, tv, res): res = a * ((tv(V) * tm(M)) - res)
			 * Operates on the square submatrix of M starting at row i and column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO3b(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       const typename Matrix<T4>::Implementation& V, const T5& vTransformation,
			                       typename Matrix<T6>::Implementation& res,
			                       const std::size_t& index);

			/**
			 * computeO3c(a, M, tm, V, tv, res): res = a * ((tm(M) * tv(V)) - res)
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO3c(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       const typename Vector<T4>::Implementation& V, const T5& vTransformation,
			                       typename Vector<T6>::Implementation& res);

			/**
			 * computeO4pa(M, tM, V, tV, res, mr): res += mr(tv(trans(V)) * tM(M))
			 * Operates on the square submatrix of M starting at row i and column i
			 * Operates on the subvector of res at row i-1, starting from column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4pa(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Vector<T3>::Implementation& V, const T4& vTransformation,
			                        typename HessenbergMatrix<T5>::Implementation& res, const T6& resTransformation,
			                        const std::size_t& index);

			/**
			 * computeO4ma(M, tM, V, tV, res, mr): res -= mr(tv(trans(V)) * tM(M))
			 * Operates on the square submatrix of M starting at row i and column i
			 * Operates on the subvector of res at row i-1, starting from column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4ma(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Vector<T3>::Implementation& V, const T4& vTransformation,
			                        typename HessenbergMatrix<T5>::Implementation& res, const T6& resTransformation,
			                        const std::size_t& index);

			/**
			 * computeO4pb(M, tM, V, tV, res, mr): res += mr(tv(V) * tM(M))
			 * Operates on the square submatrix of M starting at row i and column i
			 * Operates on the submatrix of res at rows i-2 and i-1, starting from column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4pb(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Matrix<T3>::Implementation& V, const T4& vTransformation,
			                        typename HessenbergMatrix<T5>::Implementation& res, const T6& resTransformation,
			                        const std::size_t& index);

			/**
			 * computeO4mb(M, tM, V, tV, res, mr): res -= mr(tv(V) * tM(M))
			 * Operates on the square submatrix of M starting at row i and column i
			 * Operates on the submatrix of res at rows i-2 and i-1, starting from column i
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4mb(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Matrix<T3>::Implementation& V, const T4& vTransformation,
			                        typename HessenbergMatrix<T5>::Implementation& res, const T6& resTransformation,
			                        const std::size_t& index);

			/**
			 * computeO4pc(M, tM, V, tV, res, mr): res += mr(tM(M) * tv(V))
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4pc(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Vector<T3>::Implementation& V, const T4& vTransformation,
			                        typename Vector<T5>::Implementation& res, const T6& resTransformation);

			/**
			 * computeO4mc(M, tM, V, tV, res, mr): res -= mr(tM(M) * tv(V))
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO4mc(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                        const typename Vector<T3>::Implementation& V, const T4& vTransformation,
			                        typename Vector<T5>::Implementation& res, const T6& resTransformation);

			/**
			 * computeO5a(a, M, tm, res): res = a * tm(M)
			 */
			template <typename T1, typename T2, typename T3, typename T4>
			static void computeO5a(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M, const T3& mTransformation,
			                       typename HessenbergMatrix<T4>::Implementation& res);

			/**
			 * computeO5b(a, M, tm, res): res = a * tv(V)
			 */
			template <typename T1, typename T2, typename T3, typename T4>
			static void computeO5b(const T1& a,
			                       const typename Vector<T2>::Implementation& V, const T3& vTransformation,
			                       typename Vector<T4>::Implementation& res);

			/**
			 * computeO6a(a, M1, tm1, M2, tm2, res): res = a * (tm1(M1) - tm2(M2))
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO6a(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M1, const T3& m1Transformation,
			                       const typename HessenbergMatrix<T4>::Implementation& M2, const T5& m2Transformation,
			                       typename HessenbergMatrix<T6>::Implementation& res);

			/**
			 * computeO6b(a, V1, tv1, V2, tv2, res): res = a * (tv1(V1) - tv2(V2))
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			static void computeO6b(const T1& a,
			                       const typename Vector<T2>::Implementation& V1, const T3& v1Transformation,
			                       const typename Vector<T4>::Implementation& V2, const T5& v2Transformation,
			                       typename Vector<T6>::Implementation& res);

			/**
			 * computeO7(M, tm, V, tv, res): res = tm(M) * tv(V)
			 */
			template <typename T1, typename T2, typename T3, typename T4, typename T5>
			static void computeO7(const typename HessenbergMatrix<T1>::Implementation& M, const T2& mTransformation,
			                      const typename Vector<T3>::Implementation& V, const T4& vTransformation,
			                      typename Vector<T5>::Implementation& res);

			/**
			 * computeO8a(a, res): res *= a
			 */
			template <typename T1, typename T2>
			static void computeO8a(const T1& a, typename HessenbergMatrix<T2>::Implementation& res);

			/**
			 * computeO8b(a, res): res *= a
			 */
			template <typename T1, typename T2>
			static void computeO8b(const T1& a, typename Vector<T2>::Implementation& res);

			/**
			 * computeO9a(a, M, res): res = a * (res - M)
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO9a(const T1& a,
			                       const typename HessenbergMatrix<T2>::Implementation& M,
			                       typename HessenbergMatrix<T3>::Implementation& res);

			/**
			 * computeO9b(a, M, res): res = a * (res - V)
			 */
			template <typename T1, typename T2, typename T3>
			static void computeO9b(const T1& a,
			                       const typename Vector<T2>::Implementation& V,
			                       typename Vector<T3>::Implementation& res);

			/**
			 * computeO10(V, M): V = trans(M) * V
			 */
			template <typename T1, typename T2>
			static void computeO10(const typename Matrix<T1>::Implementation& M, typename Vector<T2>::Implementation& V);

			/**
			 * computeO11(V, M): V = M * V
			 */
			template <typename T1, typename T2>
			static void computeO11(const typename Matrix<T1>::Implementation& M, typename Vector<T2>::Implementation& V);
		};
	} // namespace algebra
} // namespace epoch

#include "details/UblasBackendImpl.hh"

#endif // UBLAS_BACKEND_HH
