// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_COLLECTION_INCLUDE
#define MTL_COLLECTION_INCLUDE

#include <boost/type_traits/remove_const.hpp>
#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <vector>

#ifdef __GXX_CONCEPTS__
#  include <concepts>
#else
#  include <boost/numeric/linear_algebra/pseudo_concept.hpp>
#  include <boost/numeric/mtl/concept/std_concept.hpp>
#endif

#include <boost/numeric/mtl/utility/transposed_orientation.hpp>

namespace mtl {

/** @addtogroup Concepts
 *  @{
 */

#ifdef __GXX_CONCEPTS__
    auto concept Collection<typename T>
    {
	typename value_type;
	typename const_reference;
	typename size_type;
    };
#else
    /// Concept Collection
    template <typename T>
    struct Collection
    {
	/// Associated type: elements in the collection
	typedef associated_type value_type;

	/// Associated type: return type of const element access (however implemented)
	typedef associated_type const_reference;

	/// Associated type: size type used for indexing in collection
	typedef associated_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    auto concept MutableCollection<typename T>
      : Collection<T>
    {
	typename reference;
    }
#else
    /// Concept MutableCollection
    template <typename T>
    struct MutableCollection
	: public Collection<T>
    {
	/// Associated type: return type of non-const element access (however implemented)
	typedef associated_type reference;
    };
#endif

#ifdef __GXX_CONCEPTS__
    concept ConstantSizeCollection<typename T>
      : Collection<T>
    {};
#else
    /// Concept ConstantSizeCollection: size parameters of collection are completely given at compile time
    /* Which parameters determine collection size depends on type of collection, e.g. different for vector and matrix
       \par Refinement of:
       - Collection < T >
    */
    template <typename T>
    struct ConstantSizeCollection
	: Collection<T>
    {};
#endif

#ifdef __GXX_CONCEPTS__
    auto concept AlgebraicCollection<typename T>
      : Collection<T>
    {
	size_type num_rows(T);
	size_type num_cols(T);
	size_type size(T);
    };
#else
    /// Concept AlgebraicCollection: common requirements of matrices, vectors, and scalars in computations
    /** For more design clarity we consider them all as matrices (as Matlab does) and we regard
	Scalar and Vector as special cases (see there).  However, the implementation of vectors
	is supposed to differ from the ones of matrices in order to provide efficient computations and storage.
        \par Refinement of:
	- Collection < T >
	\par Notation:
	- X is a type that models AlgebraicCollection
	- x is an object of type X
	\par Valid expressions:
	- Number of rows: \n num_rows(x) \n Return Type: size_type
	- Number of columns: \n num_cols(x) \n Return Type: size_type
	- Number of elements: \n size(x) \n Return Type: size_type
	  \n Sematics: num_rows(x) * num_cols(x) (but possibly faster implemented)
    */
    template <typename T>
    struct AlgebraicCollection
	: public Collection<T>
    {};
#endif

#ifdef __GXX_CONCEPTS__
    auto concept ConstantSizeAlgebraicCollection<typename T>
      : AlgebraicCollection<T>,
        ConstantSizeCollection<T>
    {
#if 0
	// Is there a way to require static members????
	static Collection<T>::size_type T::static_num_rows;
	static Collection<T>::size_type T::static_num_cols;
	static Collection<T>::size_type T::static_size;
#endif
    };
#else
    /// Concept ConstantSizeAlgebraicCollection: extension of AlgebraicCollection with meta-functions
    /** This concept is used for algebraic collections with sizes known at compile time.
	The motivation is that if the size of the collection is
	is small, arithmetic operations can be unrolled at compile time.

        \par Refinement of:
	- Collection < T >
	\par Notation:
	- X is a type that models ConstantSizeAlgebraicCollection
	- x is an object of type X
	\par Valid expressions:
	- Number of rows: \n static_num_rows<X>::value
	- Number of columns: \n static_num_cols<X>::value
	- Number of elements: \n static_size<X>::value
	  \n Sematics: static_num_rows<X>::value * static_size<X>::value
	\note
	-# For more design clarity we consider them all as matrices (as Matlab does) and we regard
	   Scalar and Vector as special cases (see there).  However, the implementation of vectors
	   is supposed to differ from the ones of matrices in order to provide efficient computations and storage.

    */
    template <typename T>
    struct ConstantSizeAlgebraicCollection
      : public AlgebraicCollection<T>,
        public ConstantSizeCollection<T>
    {
	/// Associated type: meta-function for number of rows
	typedef associated_type static_num_rows;
	/// Associated type: meta-function for number of columns
	typedef associated_type static_num_cols;
	/// Associated type: meta-function for number of elements
	typedef associated_type static_size;
    };
#endif

#ifdef __GXX_CONCEPTS__
    auto concept TraversableCollection<typename Tag, typename C>
      : Collection<C>
    {
#if 0
	// This might be impossible to declare with concepts
	typename cursor_type;

	cursor_type begin<Tag>(const C& c);
	cursor_type   end<Tag>(const C& c);
#endif

	// Maybe we switch to this syntax for the sake of concepts
	typename cursor_type;

	cursor_type begin(const C& c, Tag);
	cursor_type   end(const C& c, Tag);
    }
#else
    /// Concept TraversableCollection: collections that can be traversed by cursor or iterator
    template <typename Tag, typename C>
    struct TraversableCollection
	: public Collection<C>
    {
	/// Associated type: return type of tagged begin and end function
	typedef associated_type cursor_type;

	/// Tagged free function that returns a cursor or iterator at the begin of an interval
	/** The interval is specified by the Tag, i.e. the function is called begin<Tag>(c); */
	cursor_type begin(const C& c);

	/// Tagged free function that returns a cursor or iterator at the end of an interval
	/** The interval is specified by the Tag, i.e. the function is called end<Tag>(c);  */
	cursor_type end(const C& c);
    };
#endif

#ifdef __GXX_CONCEPTS__
    auto concept TraversableMutableCollection<typename Tag, typename C>
      : MutableCollection<C>
    {
#if 0
	typename cursor_type;

	cursor_type begin<Tag>(C& c);
	cursor_type   end<Tag>(C& c);
#endif

	// Maybe we switch to this syntax for the sake of concepts
	typename cursor_type;

	cursor_type begin(C& c, Tag);
	cursor_type   end(C& c, Tag);
    }
#else
    /// Concept TraversableMutableCollection: collections that can be traversed by (mutable) iterator
    template <typename Tag, typename C>
    struct TraversableMutableCollection
	: public MutableCollection<C>
    {
	/// Associated type: return type of tagged begin function
	typedef associated_type cursor_type;

	/// Tagged free function that returns a cursor or iterator at the begin of an interval
	/** The interval is specified by the Tag, i.e. the function is called begin<Tag>(c); */
	cursor_type begin(const C& c);

	/// Tagged free function that returns a cursor or iterator at the end of an interval
	/** The interval is specified by the Tag, i.e. the function is called end<Tag>(c);  */
	cursor_type end(const C& c);
    };
#endif

#ifdef __GXX_CONCEPTS__
#if 0
    concept CategorizedType<typename T>
    {
	typedef associated_type type;
    };
#endif
#endif

#ifdef __GXX_CONCEPTS__
    concept OrientedCollection<typename T>
      : Collection<T>
    {
	typename orientation;

    };
#else
    /// Concept OrientedCollection: collections with concept-awareness in terms of associated type
    /** Concept-awareness is given for matrices as well as for vectors consistent to the unification in
	AlgebraicCollection. The orientation of vectors determines whether it is a row or a column vector.
	The orientation of matrices only characterizes the internal representation and has no semantic consequences.
        \par Refinement of:
	- Collection < T >
	\par Associated type:
	- orientation
    */
    template <typename T>
    struct OrientedCollection
      : public Collection<T>
    {
	/// Associated type for orientation; by default identical with member type
	typedef typename T::orientation orientation;
    };
#endif

// ============================================
// Concept maps (and emulations as type traits)
// ============================================

#ifdef __GXX_CONCEPTS__
    // Needs redefinition in refinements (at least in conceptg++)
    // -> as a consequence we define it directly there
#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::mat::dense2D<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
        // Alleged ambiguity with mtl::tag::dense2D on MSVC
        typedef typename mtl::mat::dense2D<Value, Parameters>::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Value, std::size_t Mask, typename Parameters>
    struct Collection<mtl::mat::morton_dense<Value, Mask, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename Parameters::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map Collection<mtl::mat::compressed2D<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef Value            const_reference;
	typedef typename mtl::mat::compressed2D<Value, Parameters>::size_type size_type;
    };
#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::mat::compressed2D<Value, Parameters> >
    {
	typedef Value            	       value_type;
	typedef Value            	       const_reference;
	typedef typename Parameters::size_type size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::mat::coordinate2D<Value, Parameters> >
    {
	typedef Value            	       value_type;
	typedef Value            	       const_reference;
	typedef typename Parameters::size_type size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::mat::sparse_banded<Value, Parameters> >
    {
	typedef Value            	       value_type;
	typedef Value            	       const_reference;
	typedef typename Parameters::size_type size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
    template <typename Vector>
    concept_map Collection<mtl::mat::multi_vector<Vector> >
    {
	typedef typename mtl::mat::multi_vector<Vector>::value_type       value_type;
	typedef typename mtl::mat::multi_vector<Vector>::const_reference  const_reference;
	typedef typename mtl::mat::multi_vector<Vector>::size_type        size_type;
    };
#else
    template <typename Vector>
    struct Collection<mtl::mat::multi_vector<Vector> >
    {
	typedef typename mtl::mat::multi_vector<Vector>::value_type       value_type;
	typedef typename mtl::mat::multi_vector<Vector>::const_reference  const_reference;
	typedef typename mtl::mat::multi_vector<Vector>::size_type        size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
    template <typename Vector>
    concept_map Collection<mtl::mat::multi_vector_range<Vector> >
    {
	typedef typename mtl::mat::multi_vector_range<Vector>::value_type       value_type;
	typedef typename mtl::mat::multi_vector_range<Vector>::const_reference  const_reference;
	typedef typename mtl::mat::multi_vector_range<Vector>::size_type        size_type;
    };
#else
    template <typename Vector>
    struct Collection<mtl::mat::multi_vector_range<Vector> >
    {
	typedef typename mtl::mat::multi_vector_range<Vector>::value_type       value_type;
	typedef typename mtl::mat::multi_vector_range<Vector>::const_reference  const_reference;
	typedef typename mtl::mat::multi_vector_range<Vector>::size_type        size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Value>
    struct Collection<mat::element_structure<Value> >
    {
	typedef Value            value_type;
      // typedef Value            const_reference;
      // typedef typename mtl::mat::compressed2D<Value, Parameters>::size_type size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Value, typename Parameters>
    struct Collection<mat::ell_matrix<Value, Parameters> >
    {
	typedef Value            	       value_type;
	typedef Value            	       const_reference;
	typedef typename Parameters::size_type size_type;
    };

#endif

#ifdef __GXX_CONCEPTS__
    template <typename Scaling, typename Coll>
    concept_map Collection<mat::scaled_view<Scaling, Coll> >
    {
	typedef typename mat::scaled_view<Scaling, Coll>::value_type        value_type;
	typedef typename mat::scaled_view<Scaling, Coll>::const_reference   const_reference;
	typedef typename mat::scaled_view<Scaling, Coll>::size_type         size_type;
    };
#else
    template <typename Scaling, typename Coll>
    struct Collection<mat::scaled_view<Scaling, Coll> >
    {
	typedef typename mat::scaled_view<Scaling, Coll>::value_type        value_type;
	typedef typename mat::scaled_view<Scaling, Coll>::const_reference   const_reference;
	typedef typename mat::scaled_view<Scaling, Coll>::size_type         size_type;
    };
#endif

// added by Hui Li
#ifdef __GXX_CONCEPTS__
    template <typename Coll, typename RScaling>
    concept_map Collection<mat::rscaled_view<Coll,RScaling> >
    {
	typedef typename mat::rscaled_view<Coll,RScaling>::value_type        value_type;
	typedef typename mat::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	typedef typename mat::rscaled_view<Coll,RScaling>::size_type         size_type;
    };
#else
    template <typename Coll, typename RScaling>
    struct Collection<mat::rscaled_view<Coll,RScaling> >
    {
	typedef typename mat::rscaled_view<Coll,RScaling>::value_type        value_type;
	typedef typename mat::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	typedef typename mat::rscaled_view<Coll,RScaling>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    // template <typename Functor, typename Coll>
    // concept_map Collection< vec::map_view<Functor, Coll> >
    // {
    // 	typedef typename vec::map_view<Functor, Coll>::value_type        value_type;
    // 	typedef typename vec::map_view<Functor, Coll>::const_reference   const_reference;
    // 	typedef typename vec::map_view<Functor, Coll>::size_type         size_type;
    // };
#else
    template <typename Functor, typename Coll>
    struct Collection< vec::map_view<Functor, Coll> >
    {
	// typedef typename Functor::result_type              value_type;
	// typedef typename Functor::result_type              const_reference;
	// typedef typename Collection<Coll>::size_type     size_type;

	typedef typename vec::map_view<Functor, Coll>::value_type        value_type;
	typedef typename vec::map_view<Functor, Coll>::const_reference   const_reference;
	typedef typename vec::map_view<Functor, Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Scaling, typename Coll>
    struct Collection<vec::scaled_view<Scaling, Coll> >
      : Collection< vec::map_view<tfunctor::rscale<typename Collection<Coll>::value_type, Scaling>, Coll> >
    {
	// typedef typename vec::scaled_view<Scaling, Coll>::value_type        value_type;
	// typedef typename vec::scaled_view<Scaling, Coll>::const_reference   const_reference;
	// typedef typename vec::scaled_view<Scaling, Coll>::size_type         size_type;
    };
#endif

// added by Hui Li
#ifdef __GXX_CONCEPTS__
    template <typename Coll, typename RScaling>
    concept_map Collection<vec::rscaled_view<Coll,RScaling> >
    {
	typedef typename vec::rscaled_view<Coll,RScaling>::value_type        value_type;
	typedef typename vec::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	typedef typename vec::rscaled_view<Coll,RScaling>::size_type         size_type;
    };
#else
    template <typename Coll, typename RScaling>
    struct Collection<vec::rscaled_view<Coll,RScaling> >
      : Collection< vec::map_view<tfunctor::rscale<typename Collection<Coll>::value_type, RScaling>, Coll> >
    {
	// typedef typename vec::rscaled_view<Coll,RScaling>::value_type        value_type;
	// typedef typename vec::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	// typedef typename vec::rscaled_view<Coll,RScaling>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<vec::conj_view<Coll> >
    {
	typedef typename vec::conj_view<Coll>::value_type        value_type;
	typedef typename vec::conj_view<Coll>::const_reference   const_reference;
	typedef typename vec::conj_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<vec::conj_view<Coll> >
    {
	typedef typename vec::conj_view<Coll>::value_type        value_type;
	typedef typename vec::conj_view<Coll>::const_reference   const_reference;
	typedef typename vec::conj_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Coll>
    struct Collection<vec::real_view<Coll> >
    {
	typedef typename vec::real_view<Coll>::value_type        value_type;
	typedef typename vec::real_view<Coll>::const_reference   const_reference;
	typedef typename vec::real_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Coll>
    struct Collection<vec::imag_view<Coll> >
    {
	typedef typename vec::imag_view<Coll>::value_type        value_type;
	typedef typename vec::imag_view<Coll>::const_reference   const_reference;
	typedef typename vec::imag_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<vec::negate_view<Coll> >
    {
	typedef typename vec::negate_view<Coll>::value_type        value_type;
	typedef typename vec::negate_view<Coll>::const_reference   const_reference;
	typedef typename vec::negate_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<vec::negate_view<Coll> >
    {
	typedef typename vec::negate_view<Coll>::value_type        value_type;
	typedef typename vec::negate_view<Coll>::const_reference   const_reference;
	typedef typename vec::negate_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2, typename SFunctor>
    struct Collection< vec::vec_scal_aop_expr<E1, E2, SFunctor> >
    {
	typedef typename Collection<E1>::value_type                   value_type;
	typedef value_type                                            const_reference;
	typedef typename Collection<E1>::size_type                    size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2>
    struct Collection<vec::vec_scal_asgn_expr<E1, E2> >
      : Collection< vec::vec_scal_aop_expr<E1, E2, mtl::sfunctor::assign<typename Collection<E1>::value_type, E2> > >
    {};
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <unsigned BSize, typename Vector>
    struct Collection<vec::unrolled1<BSize, Vector> >
    {
	typedef typename Collection<Vector>::value_type value_type;
	typedef value_type                              const_reference;
	typedef typename Collection<Vector>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<mat::conj_view<Coll> >
    {
	typedef typename mat::conj_view<Coll>::value_type        value_type;
	typedef typename mat::conj_view<Coll>::const_reference   const_reference;
	typedef typename mat::conj_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<mat::conj_view<Coll> >
    {
	typedef typename mat::conj_view<Coll>::value_type        value_type;
	typedef typename mat::conj_view<Coll>::const_reference   const_reference;
	typedef typename mat::conj_view<Coll>::size_type         size_type;
    };
#endif

// Refrain from concept definition for newly added types
// Forward to mat::map_view
template <typename Matrix>
struct Collection<mat::exp_view<Matrix> >
//  : Collection<mat::map_view<mtl::sfunctor::exp<typename Collection<Matrix>::value_type>, Matrix> > // doesn't work for unknown reasons
{
    typedef typename mat::exp_view<Matrix>::value_type        value_type;
    typedef typename mat::exp_view<Matrix>::const_reference   const_reference;
    typedef typename mat::exp_view<Matrix>::size_type         size_type;
};

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<mat::imag_view<Coll> >
    {
	typedef typename mat::imag_view<Coll>::value_type        value_type;
	typedef typename mat::imag_view<Coll>::const_reference   const_reference;
	typedef typename mat::imag_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<mat::imag_view<Coll> >
    {
	typedef typename mat::imag_view<Coll>::value_type        value_type;
	typedef typename mat::imag_view<Coll>::const_reference   const_reference;
	typedef typename mat::imag_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<mat::negate_view<Coll> >
    {
	typedef typename mat::negate_view<Coll>::value_type        value_type;
	typedef typename mat::negate_view<Coll>::const_reference   const_reference;
	typedef typename mat::negate_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<mat::negate_view<Coll> >
    {
	typedef typename mat::negate_view<Coll>::value_type        value_type;
	typedef typename mat::negate_view<Coll>::const_reference   const_reference;
	typedef typename mat::negate_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection<mat::real_view<Coll> >
    {
	typedef typename mat::real_view<Coll>::value_type        value_type;
	typedef typename mat::real_view<Coll>::const_reference   const_reference;
	typedef typename mat::real_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection<mat::real_view<Coll> >
    {
	typedef typename mat::real_view<Coll>::value_type        value_type;
	typedef typename mat::real_view<Coll>::const_reference   const_reference;
	typedef typename mat::real_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Functor>
    concept_map Collection<mat::implicit_dense<Functor> >
    {
	typedef typename mat::implicit_dense<Functor>::value_type        value_type;
	typedef typename mat::implicit_dense<Functor>::const_reference   const_reference;
	typedef typename mat::implicit_dense<Functor>::size_type         size_type;
    };
#else
    template <typename Functor>
    struct Collection<mat::implicit_dense<Functor> >
    {
	typedef typename mat::implicit_dense<Functor>::value_type        value_type;
	typedef typename mat::implicit_dense<Functor>::const_reference   const_reference;
	typedef typename mat::implicit_dense<Functor>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value>
    concept_map Collection<mat::ones_matrix<Value> >
    {
	typedef typename mat::ones_matrix<Value>::value_type        value_type;
	typedef typename mat::ones_matrix<Value>::const_reference   const_reference;
	typedef typename mat::ones_matrix<Value>::size_type         size_type;
    };
#else
    template <typename Value>
    struct Collection<mat::ones_matrix<Value> >
    {
	typedef typename mat::ones_matrix<Value>::value_type        value_type;
	typedef typename mat::ones_matrix<Value>::const_reference   const_reference;
	typedef typename mat::ones_matrix<Value>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value>
    concept_map Collection<mat::hilbert_matrix<Value> >
    {
	typedef typename mat::hilbert_matrix<Value>::value_type        value_type;
	typedef typename mat::hilbert_matrix<Value>::const_reference   const_reference;
	typedef typename mat::hilbert_matrix<Value>::size_type         size_type;
    };
#else
    template <typename Value>
    struct Collection<mat::hilbert_matrix<Value> >
    {
	typedef typename mat::hilbert_matrix<Value>::value_type        value_type;
	typedef typename mat::hilbert_matrix<Value>::const_reference   const_reference;
	typedef typename mat::hilbert_matrix<Value>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Vector1, typename Vector2>
    concept_map Collection<mat::outer_product_matrix<Vector1, Vector2> >
    {
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::value_type        value_type;
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::const_reference   const_reference;
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::size_type         size_type;
    };
#else
    template <typename Vector1, typename Vector2>
    struct Collection<mat::outer_product_matrix<Vector1, Vector2> >
    {
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::value_type        value_type;
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::const_reference   const_reference;
	typedef typename mat::outer_product_matrix<Vector1, Vector2>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Matrix>
    concept_map Collection<mtl::mat::transposed_view<Matrix> >
    {
	typedef typename mtl::mat::transposed_view<Matrix>::value_type        value_type;
	typedef typename mtl::mat::transposed_view<Matrix>::const_reference   const_reference;
	typedef typename mtl::mat::transposed_view<Matrix>::size_type         size_type;
    };
#else
    template <typename Matrix>
    struct Collection<mtl::mat::transposed_view<Matrix> >
    {
	typedef typename mtl::mat::transposed_view<Matrix>::value_type        value_type;
	typedef typename mtl::mat::transposed_view<Matrix>::const_reference   const_reference;
	typedef typename mtl::mat::transposed_view<Matrix>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Matrix>
    concept_map Collection<mat::hermitian_view<Matrix> >
    {
	typedef typename Collection<Matrix>::value_type        value_type;
	typedef typename Collection<Matrix>::const_reference   const_reference;
	typedef typename Collection<Matrix>::size_type         size_type;
    };
#else
    template <typename Matrix>
    struct Collection<mat::hermitian_view<Matrix> >
    {
	typedef typename Collection<Matrix>::value_type        value_type;
	typedef typename Collection<Matrix>::const_reference   const_reference;
	typedef typename Collection<Matrix>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map Collection< mat::banded_view<Coll> >
    {
	typedef typename mat::banded_view<Coll>::value_type        value_type;
	typedef typename mat::banded_view<Coll>::const_reference   const_reference;
	typedef typename mat::banded_view<Coll>::size_type         size_type;
    };
#else
    template <typename Coll>
    struct Collection< mat::banded_view<Coll> >
    {
	typedef typename mat::banded_view<Coll>::value_type        value_type;
	typedef typename mat::banded_view<Coll>::const_reference   const_reference;
	typedef typename mat::banded_view<Coll>::size_type         size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Matrix>
    struct Collection< mat::indirect<Matrix> >
    {
	typedef typename Collection<Matrix>::value_type               value_type;
	typedef value_type                                            const_reference; // maybe change later
	typedef typename Collection<Matrix>::size_type                size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Matrix, typename Tag, int level>
    struct Collection<traits::detail::sub_matrix_cursor<Matrix, Tag, level> >
    {
	typedef typename Collection<Matrix>::value_type               value_type;
	typedef typename Collection<Matrix>::const_reference          const_reference;
	typedef typename Collection<Matrix>::size_type                size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename E1, typename E2>
    struct Collection<mat::mat_mat_times_expr<E1, E2> >
    {
	typedef typename Collection<E1>::value_type                   ft;
	typedef typename Collection<E2>::value_type                   st;

	typedef typename Multiplicable<ft, st>::result_type           value_type;
	typedef const value_type&                                     const_reference;
	typedef typename Collection<E1>::size_type                    size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename E1, typename E2>
    struct Collection<mat::mat_mat_plus_expr<E1, E2> >
    {
	typedef typename Collection<E1>::value_type                   ft;
	typedef typename Collection<E2>::value_type                   st;

	typedef typename Addable<ft, st>::result_type                 value_type;
	typedef const value_type&                                     const_reference;
	typedef typename Collection<E1>::size_type                    size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename E1, typename E2>
    struct Collection<mat::mv_mv_plus_expr<E1, E2> >
      : Collection<mat::mat_mat_plus_expr<E1, E2> >
    {};
#endif

#ifdef __GXX_CONCECPTS__

#else
    template< typename Matrix, typename Vector>
    struct Collection<mtl::mat_cvec_times_expr<Matrix, Vector> >
    {
	typedef typename Collection< Matrix >::value_type value_type;
	typedef const value_type& const_reference;
	typedef typename Collection< Matrix >::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::vec::dense_vector<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename Parameters::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::vec::strided_vector_ref<Value, Parameters> >
    {
	typedef typename boost::remove_const<Value>::type            value_type;
	typedef const Value&                                         const_reference;
	typedef typename mtl::vec::strided_vector_ref<Value, Parameters>::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Value, typename Parameters>
    struct Collection<mtl::vec::sparse_vector<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef value_type       const_reference;
	typedef typename Parameters::size_type size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2, typename SFunctor>
    struct Collection<mtl::vec::vec_vec_ele_prod_expr<E1, E2, SFunctor> >
    {
	typedef typename Multiplicable<typename Collection<E1>::value_type,
				       typename Collection<E2>::value_type>::result_type value_type;
	typedef const value_type&     const_reference;
	typedef typename Collection<E1>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2, typename SFunctor>
    struct Collection<mtl::vec::vec_vec_pmop_expr<E1, E2, SFunctor> >
    {
	typedef typename Addable<typename Collection<E1>::value_type,
				 typename Collection<E2>::value_type>::result_type value_type;
	typedef const value_type&     const_reference;
	typedef typename Collection<E1>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2, typename SFunctor>
    struct Collection<mtl::vec::vec_vec_op_expr<E1, E2, SFunctor> >
    {
	typedef typename SFunctor::result_type value_type;
	typedef const value_type&     const_reference;
	typedef typename Collection<E1>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <class E1, class E2, typename SFunctor>
    struct Collection<mtl::vec::vec_vec_aop_expr<E1, E2, SFunctor> >
    {
	typedef typename Collection<E1>::value_type value_type;
	typedef const value_type&                   const_reference;
	typedef typename Collection<E1>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Matrix, typename VectorIn>
    struct Collection<mtl::vec::mat_cvec_multiplier<Matrix, VectorIn> >
    {
	typedef typename Multiplicable<typename Collection<Matrix>::value_type,
				       typename Collection<VectorIn>::value_type>::result_type value_type;
	typedef const value_type&                   const_reference;
	typedef typename Collection<Matrix>::size_type  size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__

#else
    // Dunno if this is really a good idea
    template <typename T>
    struct Collection<T const>
      : Collection<T>
    {};
#endif

#ifdef __GXX_CONCEPTS__

#else
    template <typename Value>
    struct Collection<std::vector<Value> >
    {
	typedef typename std::vector<Value>::value_type      value_type;
	typedef typename std::vector<Value>::const_reference const_reference;
	typedef typename std::vector<Value>::size_type       size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Vector, typename Functor>
    struct Collection<mtl::vec::lazy_reduction<Vector, Functor> >
    {
	typedef std::size_t size_type;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map MutableCollection<mtl::mat::dense2D<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::mat::dense2D<Value, Parameters>::size_type size_type;

	typedef Value&           reference;
    };
#else
    template <typename Value, typename Parameters>
    struct MutableCollection<mtl::mat::dense2D<Value, Parameters> >
	: public Collection<mtl::mat::dense2D<Value, Parameters> >
    {
	typedef Value&           reference;
    };
#endif

#ifdef __GXX_CONCEPTS__

    template <typename Value, unsigned long Mask, typename Parameters>
    concept_map MutableCollection<mtl::mat::morton_dense<Value, Mask, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::mat::morton_dense<Value, Mask, Parameters>::size_type size_type;

	typedef Value&           reference;
    };

#else

    template <typename Value, unsigned long Mask, typename Parameters>
    struct MutableCollection<mtl::mat::morton_dense<Value, Mask, Parameters> >
	: public Collection<mtl::mat::morton_dense<Value, Mask, Parameters> >
    {
	typedef Value&           reference;
    };

#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map MutableCollection<mtl::vec::strided_vector_ref<Value, Parameters> >
    {
	typedef typename boost::remove_const<Value>::type            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::vec::strided_vector_ref<Value, Parameters>::size_type size_type;

	typedef Value&           reference;
    };
#else
    template <typename Value, typename Parameters>
    struct MutableCollection<mtl::vec::strided_vector_ref<Value, Parameters> >
	: public Collection<mtl::vec::strided_vector_ref<Value, Parameters> >
    {
	typedef Value&           reference;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value>
    concept_map MutableCollection<<std::vector<Value> >
    {
	typedef typename std::vector<Value>::value_type      value_type;
	typedef typename std::vector<Value>::const_reference const_reference;
	typedef typename std::vector<Value>::size_type       size_type;

	typedef typename std::vector<Value>::reference       reference;
    };
#else
    template <typename Value>
    struct MutableCollection<std::vector<Value> >
	: public Collection<std::vector<Value> >
    {
	typedef typename std::vector<Value>::reference       reference;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename T>
    struct OrientedCollection<const T>
      : OrientedCollection<T>
    {};
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map OrientedCollection<mtl::mat::dense2D<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::mat::dense2D<Value, Parameters>::size_type size_type;

	typedef typename mtl::mat::dense2D<Value, Parameters>::orientation   orientation;
    };
#else
    template <typename Value, typename Parameters>
    struct OrientedCollection<mtl::mat::dense2D<Value, Parameters> >
	: public Collection<mtl::mat::dense2D<Value, Parameters> >
    {
	typedef typename mtl::mat::dense2D<Value, Parameters>::orientation   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__

    template <typename Value, unsigned long Mask, typename Parameters>
    concept_map OrientedCollection<mtl::mat::morton_dense<Value, Mask, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::mat::morton_dense<Value, Mask, Parameters>::size_type size_type;

	typedef typename mtl::mat::morton_dense<Value, Mask, Parameters>::orientation   orientation;
    };

#else

    template <typename Value, unsigned long Mask, typename Parameters>
    struct OrientedCollection<mtl::mat::morton_dense<Value, Mask, Parameters> >
	: public Collection<mtl::mat::morton_dense<Value, Mask, Parameters> >
    {
	typedef typename mtl::mat::morton_dense<Value, Mask, Parameters>::orientation   orientation;
    };

#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map OrientedCollection<mtl::mat::compressed2D<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::mat::compressed2D<Value, Parameters>::size_type size_type;

	typedef typename mtl::mat::compressed2D<Value, Parameters>::orientation   orientation;
    };
#else
    template <typename Value, typename Parameters>
    struct OrientedCollection<mtl::mat::compressed2D<Value, Parameters> >
	: public Collection<mtl::mat::compressed2D<Value, Parameters> >
    {
	typedef typename mtl::mat::compressed2D<Value, Parameters>::orientation   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
#else
    template <typename Matrix>
    struct OrientedCollection<mtl::mat::indirect<Matrix> >
	: public Collection<mtl::mat::indirect<Matrix> >
    {
	typedef row_major   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map OrientedCollection<mtl::vec::dense_vector<Value, Parameters> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::vec::dense_vector<Value, Parameters>::size_type size_type;

	typedef typename mtl::vec::dense_vector<Value, Parameters>::orientation   orientation;
    };
#else
    template <typename Value, typename Parameters>
    struct OrientedCollection<mtl::vec::dense_vector<Value, Parameters> >
	: public Collection<mtl::vec::dense_vector<Value, Parameters> >
    {
	typedef typename mtl::vec::dense_vector<Value, Parameters>::orientation   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value, typename Parameters>
    concept_map OrientedCollection<mtl::vec::strided_vector_ref<Value, Parameters> >
    {
	typedef typename boost::remove_const<Value>::type            value_type;
	typedef const Value&     const_reference;
	typedef typename mtl::vec::strided_vector_ref<Value, Parameters>::size_type size_type;

	typedef typename mtl::vec::strided_vector_ref<Value, Parameters>::orientation   orientation;
    };
#else
    template <typename Value, typename Parameters>
    struct OrientedCollection<mtl::vec::strided_vector_ref<Value, Parameters> >
	: public Collection<mtl::vec::strided_vector_ref<Value, Parameters> >
    {
	typedef typename mtl::vec::strided_vector_ref<Value, Parameters>::orientation   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Value>
    concept_map OrientedCollection<std::vector<Value> >
    {
	typedef Value            value_type;
	typedef const Value&     const_reference;
	typedef typename std::vector<Value>::size_type size_type;

	typedef mtl::tag::col_major   orientation;
    };
#else
    template <typename Value>
    struct OrientedCollection<std::vector<Value> >
	: public Collection<std::vector<Value> >
    {
	typedef mtl::tag::col_major   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Scaling, typename Coll>
    concept_map OrientedCollection< mat::scaled_view<Scaling, Coll> >
    {
	typedef typename mat::scaled_view<Scaling, Coll>::value_type        value_type;
	typedef typename mat::scaled_view<Scaling, Coll>::const_reference   const_reference;
	typedef typename mat::scaled_view<Scaling, Coll>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Scaling, typename Coll>
    struct OrientedCollection< mat::scaled_view<Scaling, Coll> >
	: public Collection< mat::scaled_view<Scaling, Coll> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

// added by Hui Li
#ifdef __GXX_CONCEPTS__
    template <typename Coll, typename RScaling>
    concept_map OrientedCollection< mat::rscaled_view<Coll,RScaling> >
    {
	typedef typename mat::rscaled_view<Coll,RScaling>::value_type        value_type;
	typedef typename mat::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	typedef typename mat::rscaled_view<Coll,RScaling>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Coll, typename RScaling>
    struct OrientedCollection< mat::rscaled_view<Coll,RScaling> >
	: public Collection< mat::rscaled_view<Coll,RScaling> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Scaling, typename Coll>
    concept_map OrientedCollection< mtl::vec::scaled_view<Scaling, Coll> >
    {
	typedef typename mtl::vec::scaled_view<Scaling, Coll>::value_type        value_type;
	typedef typename mtl::vec::scaled_view<Scaling, Coll>::const_reference   const_reference;
	typedef typename mtl::vec::scaled_view<Scaling, Coll>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Scaling, typename Coll>
    struct OrientedCollection< mtl::vec::scaled_view<Scaling, Coll> >
	: public Collection< mtl::vec::scaled_view<Scaling, Coll> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

//added by Hui Li
#ifdef __GXX_CONCEPTS__
    template <typename Coll, typename RScaling>
    concept_map OrientedCollection< mtl::vec::rscaled_view<Coll,RScaling> >
    {
	typedef typename mtl::vec::rscaled_view<Coll,RScaling>::value_type        value_type;
	typedef typename mtl::vec::rscaled_view<Coll,RScaling>::const_reference   const_reference;
	typedef typename mtl::vec::rscaled_view<Coll,RScaling>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Coll, typename RScaling>
    struct OrientedCollection< mtl::vec::rscaled_view<Coll,RScaling> >
	: public Collection< mtl::vec::rscaled_view<Coll,RScaling> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map OrientedCollection<mat::conj_view<Coll> >
    {
	typedef typename mat::conj_view<Coll>::value_type         value_type;
	typedef typename mat::conj_view<Coll>::const_reference    const_reference;
	typedef typename mat::conj_view<Coll>::size_type          size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Coll>
    struct OrientedCollection<mat::conj_view<Coll> >
	: public Collection<mat::conj_view<Coll> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

// Refrain from concept definition for newly added types
template <typename Matrix>
struct OrientedCollection<mat::exp_view<Matrix> >
  : Collection<mat::exp_view<Matrix> >
{
    typedef typename OrientedCollection<Matrix>::orientation       orientation;
};

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map OrientedCollection<mtl::vec::conj_view<Coll> >
    {
	typedef typename mtl::vec::conj_view<Coll>::value_type        value_type;
	typedef typename mtl::vec::conj_view<Coll>::const_reference   const_reference;
	typedef typename mtl::vec::conj_view<Coll>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Coll>
    struct OrientedCollection<mtl::vec::conj_view<Coll> >
	: public Collection<mtl::vec::conj_view<Coll> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Functor, typename Coll>
    concept_map OrientedCollection< mtl::vec::map_view<Functor, Coll> >
    {
	typedef typename mtl::vec::map_view<Functor, Coll>::value_type        value_type;
	typedef typename mtl::vec::map_view<Functor, Coll>::const_reference   const_reference;
	typedef typename mtl::vec::map_view<Functor, Coll>::size_type         size_type;

	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#else
    template <typename Functor, typename Coll>
    struct OrientedCollection< mtl::vec::map_view<Functor, Coll> >
	: public Collection< mtl::vec::map_view<Functor, Coll> >
    {
	typedef typename OrientedCollection<Coll>::orientation       orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map OrientedCollection<mtl::mat::transposed_view<Coll> >
    {
	typedef typename mtl::mat::transposed_view<Coll>::value_type        value_type;
	typedef typename mtl::mat::transposed_view<Coll>::const_reference   const_reference;
	typedef typename mtl::mat::transposed_view<Coll>::size_type         size_type;

	typedef typename mtl::traits::transposed_orientation<typename OrientedCollection<Coll>::orientation>::type   orientation;
    };
#else
    template <typename Coll>
    struct OrientedCollection<mtl::mat::transposed_view<Coll> >
	: public Collection<mtl::mat::transposed_view<Coll> >
    {
	typedef typename mtl::traits::transposed_orientation<typename OrientedCollection<Coll>::orientation>::type   orientation;
    };
#endif

#ifdef __GXX_CONCEPTS__
    template <typename Coll>
    concept_map OrientedCollection<mat::hermitian_view<Coll> >
    {
	typedef typename mat::hermitian_view<Coll>::value_type        value_type;
	typedef typename mat::hermitian_view<Coll>::const_reference   const_reference;
	typedef typename mat::hermitian_view<Coll>::size_type         size_type;

	typedef typename mtl::traits::transposed_orientation<typename OrientedCollection<Coll>::orientation>::type   orientation;
    };
#else
    template <typename Coll>
    struct OrientedCollection<mat::hermitian_view<Coll> >
      : public Collection<mat::hermitian_view<Coll> >
    {
	typedef typename mtl::traits::transposed_orientation<typename OrientedCollection<Coll>::orientation>::type   orientation;
    };
#endif

/*@}*/ // end of group Concepts

} // namespace mtl

#endif // MTL_COLLECTION_INCLUDE


/*!
@file
Forward declares `boost::hana::Ring`.

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

#ifndef BOOST_HANA_FWD_CONCEPT_RING_HPP
#define BOOST_HANA_FWD_CONCEPT_RING_HPP

#include <boost/hana/config.hpp>

BOOST_HANA_NAMESPACE_BEGIN
    //! @ingroup group-concepts
    //! @defgroup group-Ring Ring
    //! The `Ring` concept represents `Group`s that also form a `Monoid`
    //! under a second binary operation that distributes over the first.
    //!
    //! A [Ring][1] is an algebraic structure built on top of a `Group`
    //! which requires a monoidal structure with respect to a second binary
    //! operation. This second binary operation must distribute over the
    //! first one. Specifically, a `Ring` is a triple `(S, +, *)` such that
    //! `(S, +)` is a `Group`, `(S, *)` is a `Monoid` and `*` distributes
    //! over `+`, i.e.
    //! @code
    //!     x * (y + z) == (x * y) + (x * z)
    //! @endcode
    //!
    //! The second binary operation is often written `*` with its identity
    //! written `1`, in reference to the `Ring` of integers under
    //! multiplication. The method names used here refer to this exact ring.
    //!
    //!
    //! Minimal complete definintion
    //! ----------------------------
    //! `one` and `mult` satisfying the laws
    //!
    //!
    //! Laws
    //! ----
    //! For all objects `x`, `y`, `z` of a `Ring` `R`, the following laws must
    //! be satisfied:
    //! @code
    //!     mult(x, mult(y, z)) == mult(mult(x, y), z)          // associativity
    //!     mult(x, one<R>()) == x                              // right identity
    //!     mult(one<R>(), x) == x                              // left identity
    //!     mult(x, plus(y, z)) == plus(mult(x, y), mult(x, z)) // distributivity
    //! @endcode
    //!
    //!
    //! Refined concepts
    //! ----------------
    //! `Monoid`, `Group`
    //!
    //!
    //! Concrete models
    //! ---------------
    //! `hana::integral_constant`
    //!
    //!
    //! Free model for non-boolean arithmetic data types
    //! ------------------------------------------------
    //! A data type `T` is arithmetic if `std::is_arithmetic<T>::%value` is
    //! true. For a non-boolean arithmetic data type `T`, a model of `Ring` is
    //! automatically defined by using the provided `Group` model and setting
    //! @code
    //!     mult(x, y) = (x * y)
    //!     one<T>() = static_cast<T>(1)
    //! @endcode
    //!
    //! @note
    //! The rationale for not providing a Ring model for `bool` is the same
    //! as for not providing Monoid and Group models.
    //!
    //!
    //! Structure-preserving functions
    //! ------------------------------
    //! Let `A` and `B` be two `Ring`s. A function `f : A -> B` is said to
    //! be a [Ring morphism][2] if it preserves the ring structure between
    //! `A` and `B`. Rigorously, for all objects `x, y` of data type `A`,
    //! @code
    //!     f(plus(x, y)) == plus(f(x), f(y))
    //!     f(mult(x, y)) == mult(f(x), f(y))
    //!     f(one<A>()) == one<B>()
    //! @endcode
    //! Because of the `Ring` structure, it is easy to prove that the
    //! following will then also be satisfied:
    //! @code
    //!     f(zero<A>()) == zero<B>()
    //!     f(negate(x)) == negate(f(x))
    //! @endcode
    //! which is to say that `f` will then also be a `Group` morphism.
    //! Functions with these properties interact nicely with `Ring`s,
    //! which is why they are given such a special treatment.
    //!
    //!
    //! [1]: http://en.wikipedia.org/wiki/Ring_(mathematics)
    //! [2]: http://en.wikipedia.org/wiki/Ring_homomorphism
    template <typename R>
    struct Ring;
BOOST_HANA_NAMESPACE_END

#endif // !BOOST_HANA_FWD_CONCEPT_RING_HPP


/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 * @file    LevenbergMarquardtOptimizer.cpp
 * @brief
 * @author  Richard Roberts
 * @date	Feb 26, 2012
 */

#include <cmath>

#include <gtsam/linear/linearExceptions.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/SuccessiveLinearizationOptimizer.h>

#include <boost/algorithm/string.hpp>
#include <string>

using namespace std;

namespace gtsam {

/* ************************************************************************* */
LevenbergMarquardtParams::VerbosityLM LevenbergMarquardtParams::verbosityLMTranslator(const std::string &src) const {
  std::string s = src;  boost::algorithm::to_upper(s);
  if (s == "SILENT") return LevenbergMarquardtParams::SILENT;
  if (s == "LAMBDA") return LevenbergMarquardtParams::LAMBDA;
  if (s == "TRYLAMBDA") return LevenbergMarquardtParams::TRYLAMBDA;
  if (s == "TRYCONFIG") return LevenbergMarquardtParams::TRYCONFIG;
  if (s == "TRYDELTA") return LevenbergMarquardtParams::TRYDELTA;
  if (s == "DAMPED") return LevenbergMarquardtParams::DAMPED;

  /* default is silent */
  return LevenbergMarquardtParams::SILENT;
}

/* ************************************************************************* */
std::string LevenbergMarquardtParams::verbosityLMTranslator(VerbosityLM value) const {
  std::string s;
  switch (value) {
  case LevenbergMarquardtParams::SILENT:    s = "SILENT" ;     break;
  case LevenbergMarquardtParams::LAMBDA:    s = "LAMBDA" ;     break;
  case LevenbergMarquardtParams::TRYLAMBDA: s = "TRYLAMBDA" ;  break;
  case LevenbergMarquardtParams::TRYCONFIG: s = "TRYCONFIG" ;  break;
  case LevenbergMarquardtParams::TRYDELTA:  s = "TRYDELTA" ;   break;
  case LevenbergMarquardtParams::DAMPED:    s = "DAMPED" ;     break;
  default:                                  s = "UNDEFINED" ;  break;
  }
  return s;
}

/* ************************************************************************* */
void LevenbergMarquardtParams::print(const std::string& str) const {
  SuccessiveLinearizationParams::print(str);
  std::cout << "              lambdaInitial: " << lambdaInitial << "\n";
  std::cout << "               lambdaFactor: " << lambdaFactor << "\n";
  std::cout << "           lambdaUpperBound: " << lambdaUpperBound << "\n";
  std::cout << "                verbosityLM: " << verbosityLMTranslator(verbosityLM) << "\n";
  std::cout.flush();
}

/* ************************************************************************* */
void LevenbergMarquardtOptimizer::iterate() {

  // Linearize graph
  GaussianFactorGraph::shared_ptr linear = graph_.linearize(state_.values, *params_.ordering);

  // Pull out parameters we'll use
  const NonlinearOptimizerParams::Verbosity nloVerbosity = params_.verbosity;
  const LevenbergMarquardtParams::VerbosityLM lmVerbosity = params_.verbosityLM;

  // Keep increasing lambda until we make make progress
  while(true) {
    if (lmVerbosity >= LevenbergMarquardtParams::TRYLAMBDA)
      cout << "trying lambda = " << state_.lambda << endl;

    // Add prior-factors
    // TODO: replace this dampening with a backsubstitution approach
    GaussianFactorGraph dampedSystem(*linear);
    {
      double sigma = 1.0 / std::sqrt(state_.lambda);
      dampedSystem.reserve(dampedSystem.size() + dimensions_.size());
      // for each of the variables, add a prior
      for(Index j=0; j<dimensions_.size(); ++j) {
        size_t dim = (dimensions_)[j];
        Matrix A = eye(dim);
        Vector b = zero(dim);
        SharedDiagonal model = noiseModel::Isotropic::Sigma(dim,sigma);
        GaussianFactor::shared_ptr prior(new JacobianFactor(j, A, b, model));
        dampedSystem.push_back(prior);
      }
    }
    if (lmVerbosity >= LevenbergMarquardtParams::DAMPED) dampedSystem.print("damped");

    // Try solving
    try {
      // Solve Damped Gaussian Factor Graph
      const VectorValues delta = solveGaussianFactorGraph(dampedSystem, params_);

      if (lmVerbosity >= LevenbergMarquardtParams::TRYLAMBDA) cout << "linear delta norm = " << delta.vector().norm() << endl;
      if (lmVerbosity >= LevenbergMarquardtParams::TRYDELTA) delta.print("delta");

      // update values
      Values newValues = state_.values.retract(delta, *params_.ordering);

      // create new optimization state with more adventurous lambda
      double error = graph_.error(newValues);

      if (lmVerbosity >= LevenbergMarquardtParams::TRYLAMBDA) cout << "next error = " << error << endl;

      if(error <= state_.error) {
        state_.values.swap(newValues);
        state_.error = error;
        state_.lambda /= params_.lambdaFactor;
        break;
      } else {
        // Either we're not cautious, or the same lambda was worse than the current error.
        // The more adventurous lambda was worse too, so make lambda more conservative
        // and keep the same values.
        if(state_.lambda >= params_.lambdaUpperBound) {
          if(nloVerbosity >= NonlinearOptimizerParams::ERROR)
            cout << "Warning:  Levenberg-Marquardt giving up because cannot decrease error with maximum lambda" << endl;
          break;
        } else {
          state_.lambda *= params_.lambdaFactor;
        }
      }
    } catch(const IndeterminantLinearSystemException& e) {
      if(lmVerbosity >= LevenbergMarquardtParams::LAMBDA)
        cout << "Negative matrix, increasing lambda" << endl;
      // Either we're not cautious, or the same lambda was worse than the current error.
      // The more adventurous lambda was worse too, so make lambda more conservative
      // and keep the same values.
      if(state_.lambda >= params_.lambdaUpperBound) {
        if(nloVerbosity >= NonlinearOptimizerParams::ERROR)
          cout << "Warning:  Levenberg-Marquardt giving up because cannot decrease error with maximum lambda" << endl;
        break;
      } else {
        state_.lambda *= params_.lambdaFactor;
      }
    } catch(...) {
      throw;
    }
  } // end while

  // Increment the iteration counter
  ++state_.iterations;
}

} /* namespace gtsam */


/**
 * \file boost/numeric/ublasx/operation/transform.hpp
 *
 * The \c transform operation.
 *
 * Copyright (c) 2010, 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_TRANSFORM_HPP
#define BOOST_NUMERIC_UBLASX_OPERATION_TRANSFORM_HPP

#include <boost/numeric/ublas/expression_types.hpp>
//#include <boost/numeric/ublas/fwd.hpp>
//#include <boost/numeric/ublas/functional.hpp>
#include <boost/numeric/ublas/traits.hpp>
#include <boost/numeric/ublasx/expression/matrix_unary_functor.hpp>
#include <boost/numeric/ublasx/expression/vector_unary_functor.hpp>
//#include <boost/shared_ptr.hpp>//FIXME
//#include <boost/function.hpp>//FIXME

namespace boost { namespace numeric { namespace ublasx {

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

//namespace detail {
//
//template <typename T, typename F>
//struct scalar_unary_generic: public scalar_unary_functor<T>
//{
//	typedef typename scalar_unary_functor<T>::value_type value_type;
//	typedef typename scalar_unary_functor<T>::argument_type argument_type;
//	typedef typename scalar_unary_functor<T>::result_type result_type;
//	//typedef typename F::result_type result_type;
//
//	explicit scalar_unary_generic(F const& f)
//	: f_(f)
//	{
//	}
//
//
//	BOOST_UBLAS_INLINE
//	result_type operator()(argument_type t)
//	{
//std::cerr << "In scalar_unary_generic: " << t << std::endl;
//		return f_(t);
//	}
//
//	boost::function<result_type (argument_type)> f_;
//};
//
//} // Namespace detail

/**
 * \brief Apply a function to each element of a given vector expression.
 *
 * \tparam VectorExprT The type of the input vector expression.
 * \tparam UnaryFunctorT The type of the unary functor.
 *
 * \param ve The input vector expression.
 * \param f The unary functor to be applied to each vector element.
 * \return A vector expression representing the application of \a f to each
 *  element of \a ve.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <typename VectorExprT, typename UnaryFunctorT>
BOOST_UBLAS_INLINE
typename vector_unary_functor_traits<
	VectorExprT,
	typename UnaryFunctorT::result_type (typename UnaryFunctorT::argument_type)
>::result_type transform(vector_expression<VectorExprT> const& ve, UnaryFunctorT const& f)
{
	typedef typename vector_unary_functor_traits<
				VectorExprT,
				typename UnaryFunctorT::result_type (typename UnaryFunctorT::argument_type)
			>::expression_type expression_type;

	return expression_type(ve(), f);
}

/**
 * \brief Apply a function to each element of a given matrix expression.
 *
 * \tparam MatrixExprT The type of the input matrix expression.
 * \tparam UnaryFunctorT The type of the unary functor.
 *
 * \param me The input matrix expression.
 * \param f The unary functor to be applied to each matrix element.
 * \return A matrix expression representing the application of \a f to each
 *  element of \a me.
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */
template <typename MatrixExprT, typename UnaryFunctorT>
BOOST_UBLAS_INLINE
typename matrix_unary_functor_traits<
	MatrixExprT,
//	UnaryFunctorT
	typename UnaryFunctorT::result_type (typename UnaryFunctorT::argument_type)
//	detail::scalar_unary_generic<
//		typename matrix_traits<MatrixExprT>::value_type,
//		UnaryFunctorT
//	>
>::result_type transform(matrix_expression<MatrixExprT> const& me, UnaryFunctorT const& f)
{
//	typedef detail::scalar_unary_generic<
//			typename matrix_traits<MatrixExprT>::value_type,
//			UnaryFunctorT
//		> wrapper_functor_type;

	typedef typename matrix_unary_functor_traits<
			MatrixExprT,
//			wrapper_functor_type
			typename UnaryFunctorT::result_type (typename UnaryFunctorT::argument_type)
		>::expression_type expression_type;

	//return expression_type(me(), wrapper_functor_type(f));
	return expression_type(me(), f);
}

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

#endif // BOOST_NUMERIC_UBLASX_OPERATION_TRANSFORM_HPP


#include <utility/Configurations.hpp>
#include <data/Spin_System.hpp>
#include <engine/Vectormath.hpp>
#include <utility/Constants.hpp>
#include <utility/Logging.hpp>
#include <utility/Exception.hpp>

#include <Eigen/Dense>

#include <fmt/format.h>

#include <random>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using Utility::Constants::Pi;

namespace Utility
{
	namespace Configurations
	{
		void filter_to_mask(const vectorfield & spins, const vectorfield & positions, filterfunction filter, intfield & mask)
		{
			int nos = spins.size();
			mask = intfield(nos, 0);

			for (unsigned int iatom = 0; iatom < mask.size(); ++iatom)
			{
				if (filter(spins[iatom], positions[iatom]))
				{
					mask[iatom] = 1;
				}
			}
		}

		void Move(vectorfield& configuration, const Data::Geometry & geometry, int da, int db, int dc)
		{
			int delta = geometry.n_cell_atoms*da + geometry.n_cell_atoms*geometry.n_cells[0] * db + geometry.n_cell_atoms*geometry.n_cells[0] * geometry.n_cells[1] * dc;
			if (delta < 0)
				delta += geometry.nos;
			std::rotate(configuration.begin(), configuration.begin() + delta, configuration.end());
		}

		void Insert(Data::Spin_System &s, const vectorfield& configuration, int shift, filterfunction filter)
		{
			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;
			int nos = s.nos;
			if (shift < 0) shift += nos;

			if (nos != configuration.size())
			{
				Log(Log_Level::Warning, Log_Sender::All, "Tried to insert spin configuration with NOS != NOS_system");
				return;
			}

			for (int iatom = 0; iatom < nos; ++iatom)
			{
				if (filter(spins[iatom], positions[iatom]))
				{
					spins[iatom] = configuration[(iatom + shift) % nos];
				}
			}
		}

		void Domain(Data::Spin_System & s, Vector3 v, filterfunction filter)
		{
			if (v.norm() < 1e-8)
			{
				Log( Log_Level::Warning, Log_Sender::All, fmt::format("Homogeneous vector was ({}, {}, {}) and got set to (0, 0, 1)", v[0], v[1], v[2]) );
				v[0] = 0.0; v[1] = 0.0; v[2] = 1.0;		// if vector is zero -> set vector to 0,0,1 (posZdir)
			}
			else
			{
				v.normalize();
			}

			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;

			for (int iatom = 0; iatom < s.nos; ++iatom)
			{
				if (filter(spins[iatom], positions[iatom]))
				{
					spins[iatom] = v;
				}
			}
		}

		void Random(Data::Spin_System & s, filterfunction filter, bool external)
		{
			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;

			auto distribution = std::uniform_real_distribution<scalar>(-1, 1);
			if (!external) {
				for (int iatom = 0; iatom < s.nos; ++iatom)
				{
					if (filter(spins[iatom], positions[iatom]))
					{
						Engine::Vectormath::get_random_vector_unitsphere(distribution, s.llg_parameters->prng, spins[iatom]);
					}
				}
			}
			else {
				std::mt19937 prng = std::mt19937(123456789);
				for (int iatom = 0; iatom < s.nos; ++iatom)
				{
					if (filter(spins[iatom], positions[iatom]))
					{
						Engine::Vectormath::get_random_vector_unitsphere(distribution, s.llg_parameters->prng, spins[iatom]);
					}
				}
			}
		}

		void Add_Noise_Temperature(Data::Spin_System & s, scalar temperature, int delta_seed, filterfunction filter)
		{
			if (temperature == 0.0) return;

			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;
			vectorfield xi(spins.size());
			intfield mask;

			filter_to_mask(spins, positions, filter, mask);

			scalar epsilon = std::sqrt(temperature*Constants::k_B);

			std::mt19937 * prng;
			if (delta_seed!=0) prng = new std::mt19937(123456789+delta_seed);
			else prng = &s.llg_parameters->prng;

			auto distribution = std::uniform_real_distribution<scalar>(-1, 1);

			Engine::Vectormath::get_random_vectorfield_unitsphere(*prng, xi);
			Engine::Vectormath::scale(xi, epsilon);
			Engine::Vectormath::add_c_a(1, xi, *s.spins, mask);
			Engine::Vectormath::normalize_vectors(*s.spins);
		}

		void Hopfion(Data::Spin_System & s, Vector3 pos, scalar r, int order, filterfunction filter)
		{
			using std::pow;
			using std::sqrt;
			using std::acos;
			using std::sin;
			using std::cos;
			using std::atan;
			using std::atan2;

			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;

			if (r != 0.0)
			{
				scalar tmp;
				scalar d, T, t, F, f;
				for (int n = 0; n<s.nos; n++)
				{
					// Distance of spin from center
					if (filter(spins[n], positions[n]))
					{
						d = (positions[n] - pos).norm();

						// Theta
						if (d == 0)
						{
							T = 0;
						}
						else
						{
							T = (positions[n][2] - pos[2]) / d; // angle with respect to the main axis of toroid [0,0,1]
						}
						T = acos(T);
						// ...
						t = d / r;	// r is a big radius of the torus
						t = 1.0 + 4.22 / (t*t);
						tmp = Pi*(1.0 - 1.0 / sqrt(t));
						t = sin(tmp)*sin(T);
						t = acos(1.0 - 2.0*t*t);
						// ...
						F = atan2(positions[n][1] - pos[1], positions[n][0] - pos[0]);
						if (T > Pi / 2.0)
						{
							f = F + atan(1.0 / (tan(tmp)*cos(T)));
						}
						else
						{
							f = F + atan(1.0 / (tan(tmp)*cos(T))) + Pi;
						}
						// Spin orientation
						spins[n][0] = sin(t)*cos(order * f);
						spins[n][1] = sin(t)*sin(order * f);
						spins[n][2] = cos(t);
					}
				}
			}
		}

		void Skyrmion(Data::Spin_System & s, Vector3 pos, scalar r, scalar order, scalar phase, bool upDown, bool achiral, bool rl, bool experimental, filterfunction filter)
		{
			//bool experimental uses Method similar to PHYSICAL REVIEW B 67, 020401(R) (2003)

			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;

			// skaled to fit with
			scalar r_new = r;
			if (experimental) { r_new = r*1.2; }
			int iatom, ksi = ((int)rl) * 2 - 1, dir = ((int)upDown) * 2 - 1;
			scalar distance, phi_i, theta_i;
			for (iatom = 0; iatom < s.nos; ++iatom)
			{
				distance = std::sqrt(std::pow(s.geometry->positions[iatom][0] - pos[0], 2) + std::pow(s.geometry->positions[iatom][1] - pos[1], 2));
				distance = distance / r_new;
				if (filter(spins[iatom], positions[iatom]))
				{
					double x = (s.geometry->positions[iatom][0] - pos[0]) / distance / r_new;
					phi_i = std::acos(std::max(-1.0, std::min(1.0, x)));
					if (distance == 0) { phi_i = 0; }
					if (s.geometry->positions[iatom][1] - pos[1] < 0.0) { phi_i = - phi_i ; }
					phi_i += phase / 180 * Pi;
					if (experimental) { theta_i = Pi - 4 * std::asin(std::tanh(distance)); }
					else { theta_i = Pi - Pi *distance; }

					spins[iatom][0] = ksi * std::sin(theta_i) * std::cos(order * phi_i);
					spins[iatom][1] = ksi * std::sin(theta_i) * std::sin(order * (phi_i + achiral * Pi));
					spins[iatom][2] = std::cos(theta_i) * -dir;
				}
			}
			for (auto& v : spins) v.normalize();
		}
		// end Skyrmion

		void SpinSpiral(Data::Spin_System & s, std::string direction_type, Vector3 q, Vector3 axis, scalar theta, filterfunction filter)
		{
			scalar phase;
			Vector3 vx{ 1,0,0 }, vy{ 0,1,0 }, vz{ 0,0,1 };
			Vector3 e1, e2;

			Vector3 a1 = s.geometry->bravais_vectors[0];
			Vector3 a2 = s.geometry->bravais_vectors[1];
			Vector3 a3 = s.geometry->bravais_vectors[2];

			// -------------------- Preparation --------------------
			axis.normalize();

			/*
			if axis_z=0 its in the xy-plane
				axis, vz, (axis x vz)
			else its either above or below the xy-plane.
			if its above the xy-plane, it points in z-direction
				axis, vx, -vy
			if its below the xy-plane, it points in -z-direction
				axis, vx, vy
			*/

			// Choose orthogonalisation basis for Grahm-Schmidt
			//		We will need two vectors with which the axis always forms the
			//		same orientation (händigkeit des vektor-dreibeins)
			// If axis_z=0 its in the xy-plane
			//		the vectors should be: axis, vz, (axis x vz)
			if (axis[2] == 0)
			{
				e2 = axis.cross(vz);
				e1 = vz;
			}
			// Else its either above or below the xy-plane.
			//		if its above the xy-plane, it points in z-direction
			//		the vectors should be: axis, vx, -vy
			else if (axis[2] > 0)
			{
				e1 = vx;
				e2 = -vy;
			}
			//		if its below the xy-plane, it points in -z-direction
			//		the vectors should be: axis, vx, vy
			else if (axis[2] < 0)
			{
				e1 = vx;
				e2 = vy;
			}

			// Some normalisations
			theta = theta / 180.0 * Pi;
			scalar qnorm = q.norm();
			scalar axnorm = axis.norm();
			axis.normalize();

			// Grahm-Schmidt orthogonalization: two vectors orthogonal to an axis
			Vector3 v1, v2;
			//u1 = axis
			//u2 = v1 = vx - vx*axis/|axis|^2 * axis
			//u3 = v2 = vy - vy*axis/|axis|^2 * axis - vy*v1/|v1|^2 * v1
			scalar proj1 = 0, proj2 = 0, proj3 = 0, proj1a=0, proj2a=0, proj3a=0, proj1b=0, proj2b=0, proj3b=0;
			// Projections
			proj1a = e1.dot(axis);
			proj2a = e2.dot(axis);
			proj1b = axis.dot(axis);
			proj2b = axis.dot(axis);
			proj1 += proj1a / proj1b;
			proj2 += proj2a / proj2b;

			// First vector
			v1 = e1 - proj1 * axis;

			// One more projection
			proj3a = e2.dot(v1);
			proj3b = v1.dot(v1);
			proj3 = proj3a / proj3b;

			// Second vector
			v2 = e2 - proj2 * axis - proj3*v1;

			// -------------------- Spin Spiral creation --------------------
			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;
			if (direction_type == "Reciprocal Lattice")
			{
				// bi = 2*pi*(aj x ak) / (ai * (aj x ak))
				Vector3 b1, b2, b3;
				b1 = 2.0 * Pi * a2.cross(a3) / (a1.dot(a2.cross(a3)));
				b2 = 2.0 * Pi * a3.cross(a1) / (a2.dot(a3.cross(a1)));
				b3 = 2.0 * Pi * a1.cross(a2) / (a3.dot(a1.cross(a2)));
				// The q-vector is specified in units of the reciprocal lattice
				Vector3 projBQ = q[0]*b1 + q[1]*b2 + q[2]*b3;
				q = projBQ;
			}
			else if (direction_type == "Real Lattice")
			{
				// The q-vector is specified in units of the real lattice
				Vector3 projBQ = { q.dot(a1), q.dot(a2), q.dot(a3) };
				q = projBQ;
			}
			else if (direction_type == "Real Space")
			{
				// The q-vector is specified in units of (x, y, z)
			}
			else
			{
				Log(Log_Level::Warning, Log_Sender::All, "Got passed invalid type for SS: " + direction_type);
			}
			for (int iatom = 0; iatom < s.nos; ++iatom)
			{
				if (filter(spins[iatom], positions[iatom]))
				{
					// Phase is scalar product of spin position and q
					phase = s.geometry->positions[iatom].dot(q);
					//phase = phase / 180.0 * Pi;// / period;
					// The opening angle determines how far from the axis the spins rotate around it.
					//		The rotation is done by alternating between v1 and v2 periodically
					scalar norms = 0.0;
					spins[iatom] = axis * std::cos(theta)
						+ v1 * std::cos(phase) * std::sin(theta)
						+ v2 * std::sin(phase) * std::sin(theta);
					spins[iatom].normalize();
				}
			}// endfor iatom
		}

		void SpinSpiral(Data::Spin_System & s, std::string direction_type, Vector3 q1, Vector3 q2, Vector3 axis, scalar theta, filterfunction filter)
		{
			Vector3 vx{ 1,0,0 }, vy{ 0,1,0 }, vz{ 0,0,1 };
			Vector3 e1, e2;
			Vector3 qm, qk;

			Vector3 a1 = s.geometry->bravais_vectors[0];
			Vector3 a2 = s.geometry->bravais_vectors[1];
			Vector3 a3 = s.geometry->bravais_vectors[2];

			// -------------------- Preparation --------------------
			axis.normalize();

			/*
			if axis_z=0 its in the xy-plane
				axis, vz, (axis x vz)
			else its either above or below the xy-plane.
			if its above the xy-plane, it points in z-direction
				axis, vx, -vy
			if its below the xy-plane, it points in -z-direction
				axis, vx, vy
			*/

			// Choose orthogonalisation basis for Grahm-Schmidt
			//		We will need two vectors with which the axis always forms the
			//		same orientation (händigkeit des vektor-dreibeins)
			// If axis_z=0 its in the xy-plane
			//		the vectors should be: axis, vz, (axis x vz)
			if (axis[2] == 0)
			{
				e2 = axis.cross(vz);
				e1 = vz;
			}
			// Else its either above or below the xy-plane.
			//		if its above the xy-plane, it points in z-direction
			//		the vectors should be: axis, vx, -vy
			else if (axis[2] > 0)
			{
				e1 = vx;
				e2 = -vy;
			}
			//		if its below the xy-plane, it points in -z-direction
			//		the vectors should be: axis, vx, vy
			else if (axis[2] < 0)
			{
				e1 = vx;
				e2 = vy;
			}

			// Some normalisations
			theta = theta / 180.0 * Pi;
			scalar q1norm = q1.norm();
			scalar q2norm = q2.norm();
			scalar axnorm = axis.norm();
			axis.normalize();

			// Grahm-Schmidt orthogonalization: two vectors orthogonal to an axis
			Vector3 v1, v2;
			//u1 = axis
			//u2 = v1 = vx - vx*axis/|axis|^2 * axis
			//u3 = v2 = vy - vy*axis/|axis|^2 * axis - vy*v1/|v1|^2 * v1
			scalar proj1 = 0, proj2 = 0, proj3 = 0, proj1a=0, proj2a=0, proj3a=0, proj1b=0, proj2b=0, proj3b=0;
			// Projections
			proj1a = e1.dot(axis);
			proj2a = e2.dot(axis);
			proj1b = axis.dot(axis);
			proj2b = axis.dot(axis);
			proj1 += proj1a / proj1b;
			proj2 += proj2a / proj2b;

			// First vector
			v1 = e1 - proj1 * axis;

			// One more projection
			proj3a = e2.dot(v1);
			proj3b = v1.dot(v1);
			proj3 = proj3a / proj3b;

			// Second vector
			v2 = e2 - proj2 * axis - proj3*v1;

			// -------------------- Spin Spiral creation --------------------
			auto& spins = *s.spins;
			auto& positions = s.geometry->positions;
			if (direction_type == "Reciprocal Lattice")
			{
				// bi = 2*pi*(aj x ak) / (ai * (aj x ak))
				Vector3 b1, b2, b3;
				b1 = 2.0 * Pi * a2.cross(a3) / (a1.dot(a2.cross(a3)));
				b2 = 2.0 * Pi * a3.cross(a1) / (a2.dot(a3.cross(a1)));
				b3 = 2.0 * Pi * a1.cross(a2) / (a3.dot(a1.cross(a2)));

				// The q-vectors are specified in units of the reciprocal lattice
				Vector3 projBQ = q1[0]*b1 + q1[1]*b2 + q1[2]*b3;
				q1 = projBQ;
				projBQ = q2[0]*b1 + q2[1]*b2 + q2[2]*b3;
				q2 = projBQ;
				qm = (q1+q2)*0.5;
				qk = (q1-q2)*0.5;

			}
			else if (direction_type == "Real Lattice")
			{
				// The q-vector is specified in units of the real lattice
				Vector3 projBQ = { q1.dot(a1), q1.dot(a2), q1.dot(a3) };
				q1 = projBQ;
				projBQ = { q2.dot(a1), q2.dot(a2), q2.dot(a3) };
				q2 = projBQ;
			}
			else if (direction_type == "Real Space")
			{
				// The q-vector is specified in units of (x, y, z)
			}
			else
			{
				Log(Log_Level::Warning, Log_Sender::All, "Got passed invalid type for SS: " + direction_type);
			}

			for (int iatom = 0; iatom < s.nos; ++iatom)
			{
				if (filter(spins[iatom], positions[iatom]))
				{
					// Phase is scalar product of spin position and q
					auto& r = s.geometry->positions[iatom];
					//phase = phase / 180.0 * Pi;// / period;
					// The opening angle determines how far from the axis the spins rotate around it.
					//		The rotation is done by alternating between v1 and v2 periodically
					scalar norms = 0.0;
					spins[iatom] =	axis * std::sin(r.dot(qm))
						+ v1 * std::cos(r.dot(qm)) * std::sin(r.dot(qk))
						+ v2 * std::cos(r.dot(qm)) * std::cos(r.dot(qk));
					spins[iatom].normalize();
				}
			}// endfor iatom
		}

	}//end namespace Spin_Setters
}//end namespace Utility


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without modification, are
 *    permitted provided that the following conditions are met:
 *      - Redistributions of source code must retain the above copyright notice, this list of
 *        conditions and the following disclaimer.
 *      - Redistributions in binary form must reproduce the above copyright notice, this list of
 *        conditions and the following disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *      - Neither the name of the Delft University of Technology nor the names of its contributors
 *        may be used to endorse or promote products derived from this software without specific
 *        prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 *    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *    OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      15XXXX    D. Dirkx          File created.
 *
 *    References
 *
 *    Notes
 *
 */

#include <iostream>
#include <boost/make_shared.hpp>

#include "Tudat/Astrodynamics/Gravitation/gravityFieldModel.h"

namespace tudat
{
namespace gravitation
{

//! Set predefined central gravity field settings.
boost::shared_ptr< GravityFieldModel > getPredefinedCentralGravityField(
    BodiesWithPredefinedCentralGravityFields bodyWithPredefinedCentralGravityField )
{
    double gravitationalParameter = 0.0;

    // Select body with prefined central gravity field.
    switch( bodyWithPredefinedCentralGravityField )
    {
    case sun:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 1.32712440018e20;

        break;

    case mercury:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.2, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 2.203289218e13;

        break;

    case venus:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.2, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 3.2485504415e14;

        break;

    case earth:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.2, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 3.9859383624e14;

        break;

    case moon:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.2, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 4.903686391e12;

        break;

    case mars:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.2, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 4.2828018915e13;

        break;

    case jupiter:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.3, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 1.2668579374e17;

        break;

    case saturn:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.3, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 3.793100511400001e16;

        break;

    case uranus:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.3, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 5.793943348799999e15;

        break;

    case neptune:

        // Set gravitational parameter [m^3 s^-2].
        // Reference: Mass taken from Table 1.3, pg. 6, (de Pater, 2010), value
        //            of gravitational constant taken from
        //            http://ssd.jpl.nasa.gov/?constants#ref.
        gravitationalParameter = 6.834733937e15;

        break;

    default:

        // Print cerr statement.
        std::cerr << "Desired predefined central gravity field does not exist." << std::endl;
    }
    return boost::make_shared< GravityFieldModel >( gravitationalParameter );
}

} // namespace gravitation
} // namespace tudat


/**
 * @file graph_utils.hpp
 * @author Leonardo Arcari (leonardo1.arcari@gmail.com)
 * @version 1.0.0
 * @date 2018-10-28
 *
 * @copyright Copyright (c) 2018 Leonardo Arcari
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef BOOST_GRAPH_UTILS_H
#define BOOST_GRAPH_UTILS_H

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <sstream>
#include <stack>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>

#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>

#include <arlib/graph_types.hpp>
#include <arlib/path.hpp>
#include <arlib/type_traits.hpp>

#include <arlib/multi_predecessor_map.hpp>
#include <boost/graph/compressed_sparse_row_graph.hpp>

/**
 * An Alternative-Routing library for Boost.Graph
 */
namespace arlib {
using CSRGraph = boost::compressed_sparse_row_graph<
    boost::bidirectionalS, boost::no_property,
    boost::property<boost::edge_weight_t, double>>;

CSRGraph read_csr_graph_from_string(const std::string &graph);
std::optional<CSRGraph> read_csr_graph_from_file(const std::string_view path);

/**
 * Constructs a PropertyGraph from vertices, edges and weights contained in a
 * .gr-format string. An example of .gr-format string is the following:
 * ```
 * d
 * # nb_vertices nb_edges
 * 3 2
 * # v1 v2 weight
 * 0 1 4
 * 1 2 3
 * ```
 *
 * @tparam PropertyGraph The Graph type
 * @param graph A .gr-format string defining the graph.
 * @return the constructed graph.
 */
template <typename PropertyGraph>
PropertyGraph read_graph_from_string(const std::string &graph) {
  auto ss = std::stringstream{graph};
  std::string line{};

  // Drop graph type info
  std::getline(ss, line);

  // Get number of nodes and edges
  std::getline(ss, line);
  auto line_s = std::stringstream{line};
  int nb_nodes, nb_edges;
  line_s >> nb_nodes >> nb_edges;

  // Get edges and weights
  // Get edges and weights
  using Length = typename arlib::length_of_t<CSRGraph>;
  auto edges = std::vector<std::pair<long unsigned, long unsigned>>{};
  auto weights = std::vector<Length>{};

  long unsigned s, t;
  Length w;
  while (std::getline(ss, line)) {
    line_s.str(line);
    line_s.seekg(std::ios_base::beg);
    line_s >> s >> t >> w;

    edges.emplace_back(s, t);
    weights.push_back(w);
  }

  // Return a PropertyGraph from data
  auto G = PropertyGraph(std::begin(edges), std::end(edges),
                         std::begin(weights), nb_nodes);

  return G;
}

template <typename PropertyGraph>
std::optional<PropertyGraph> read_graph_from_file(const std::string_view path) {
  namespace fs = std::filesystem;
  auto fs_path = fs::path(path);

  if (!fs::is_regular_file(fs_path)) {
    std::cerr << fs_path << " is not a regular file.\n";
    return {};
  }

  if (fs::is_empty(fs_path)) {
    std::cerr << fs_path << " is empty.\n";
    return {};
  }

  auto buffer = std::stringstream{};
  auto input = std::ifstream{fs_path.string()};
  buffer << input.rdbuf();

  return {read_graph_from_string<PropertyGraph>(buffer.str())};
}

/**
 * Create a string representation of a graph edges and their weight.
 *
 * @tparam Graph A model of EdgeListGraph
 * @param G The graph
 * @return The edges and weights dump
 */
template <typename Graph> std::string dump_edges_weight(const Graph &G) {
  using namespace boost;
  auto ss = std::stringstream{};
  auto weight = get(edge_weight, G);

  for (auto ei = edges(G).first; ei != edges(G).second; ++ei) {
    auto w = weight[*ei];

    ss << "(" << source(*ei, G) << ", " << target(*ei, G) << "; " << w << ") ";
  }

  return ss.str();
}

/**
 * Construct a new Graph from a vector of edges, taking their weights from
 * another graph.
 *
 * @pre Edges in `edge_list` exist in `G`. If not, the behavior is undefined.
 *
 * @tparam Graph The type of the output graph.
 * @param edge_list Vector of `(vertex_id, vertex_id)` edges.
 * @param G The source graph from where to take `edge_list` edges' weights.
 * @return The constructed graph.
 */
template <typename Graph>
Graph build_graph_from_edges(const std::vector<VPair> &edge_list,
                             const Graph &G) {
  using namespace boost;
  using Length = typename boost::property_traits<typename boost::property_map<
      Graph, boost::edge_weight_t>::type>::value_type;
  auto weight = get(edge_weight, G);
  auto weights = std::vector<Length>{};

  for (auto &e : edge_list) {
    auto u = e.first;
    auto v = e.second;
    auto edge_in_G = edge(u, v, G).first;
    weights.push_back(weight[edge_in_G]);
  }

  return Graph{std::begin(edge_list), std::end(edge_list), std::begin(weights),
               num_vertices(G)};
}

/**
 * Construct an [Alternative Graph] from a sequence of
 * paths, taking their weights from another source graph.
 *
 * [Alternative Graph]: http://drops.dagstuhl.de/opus/volltexte/2013/4248/
 *
 * @pre Edges in `paths` exist in `g`. If not, the behavior is undefined.
 *
 * @tparam Graph The type of the output graph.
 * @param paths A vector of simple paths.
 * @param g The source graph from where to take `edge_list` edges' weights.
 * @return The constructed graph.
 */
template <typename Graph>
Graph build_AG(const std::vector<Path<Graph>> &paths, const Graph &g) {
  using namespace boost;
  using Vertex = typename boost::graph_traits<Graph>::vertex_descriptor;
  using Length = typename boost::property_traits<typename boost::property_map<
      Graph, boost::edge_weight_t>::type>::value_type;

  auto weight = get(edge_weight, g);
  auto es = std::vector<VPair>{};
  auto weights = std::vector<Length>{};
  auto nodes = std::unordered_set<Vertex>{};

  for (auto &path : paths) {
    for (auto it = edges(path).first; it != edges(path).second; ++it) {
      auto u = source(*it, g);
      auto v = target(*it, g);

      auto e = edge(u, v, g).first; // Assume it exists
      auto w = weight[e];

      es.emplace_back(u, v);
      weights.push_back(w);

      if (nodes.find(u) == std::end(nodes)) {
        nodes.insert(u);
      }

      if (nodes.find(v) == std::end(nodes)) {
        nodes.insert(v);
      }
    }
  }

  return Graph{std::begin(es), std::end(es), std::begin(weights),
               num_vertices(g)};
}

/**
 * Implementations details of kSPwLO algorithms
 */
namespace details {
/**
 * Construct a Path from a sequence of vertices of a graph `G`.
 *
 * @pre `vertex_descriptor`s of `path` must be vertices of `G`.
 *
 * @tparam Graph A model of a graph for which `edge(u, v, G)` is a valid
 *         expression.
 * @tparam WeightMap The weight or "length" of each edge in the graph. The
 *         weights must all be non-negative, and the algorithm will throw a
 *         negative_edge exception is one of the edges is negative. The type
 *         WeightMap must be a model of Readable Property Map. The edge
 *         descriptor type of the graph needs to be usable as the key type for
 *         the weight map. The value type for this map must be the same as the
 *         value type of the distance map.
 * @param path A sequence of vertices of `G` representing a simple-path.
 * @param G The graph.
 * @param W The Weight Property Map of `G`.
 * @return The constructed Path.
 */
template <typename Graph, typename WeightMap,
          typename Vertex = vertex_of_t<Graph>>
Path<Graph> build_path_from(std::vector<Vertex> const &path, Graph const &G,
                            WeightMap const &W) {
  using namespace boost;
  using Length = typename property_traits<
      typename property_map<Graph, edge_weight_t>::type>::value_type;
  using Edge = typename graph_traits<Graph>::edge_descriptor;

  auto u = *path.begin();
  auto len = Length{};

  auto path_es = std::unordered_set<Edge, boost::hash<Edge>>{};
  for (auto it = std::next(std::begin(path)); it != std::end(path); ++it) {
    auto v = *it;
    auto [e, is_ok] = edge(u, v, G);
    assert(is_ok && "[arlib::details::build_path_from] Edge not found.");
    auto weight = W[e];
    len += weight;
    path_es.insert(e);
    u = v;
  }

  auto path_vs = std::unordered_set<Vertex, boost::hash<Vertex>>{path.cbegin(),
                                                                 path.cend()};
  auto edge_pred = alternative_path_edges{std::move(path_es)};
  auto vertex_pred = alternative_path_vertices{std::move(path_vs)};
  using FilteredGraph = filtered_graph<Graph, alternative_path_edges<Edge>,
                                       alternative_path_vertices<Vertex>>;
  auto fg = std::make_shared<FilteredGraph>(G, edge_pred, vertex_pred);
  return Path{fg, len};
}

/**
 * Construct a Path from a sequence of vertices of a graph `G`.
 *
 * @pre `vertex_descriptor`s of `path` must be vertices of `G`.
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @param path A sequence of vertices of `G` representing a simple-path.
 * @param G The graph.
 * @return The constructed Path.
 */
template <typename Graph, typename Vertex = vertex_of_t<Graph>>
Path<Graph> build_path_from(std::vector<Vertex> const &path, Graph const &G) {
  using namespace boost;
  using Edge = typename graph_traits<Graph>::edge_descriptor;
  BOOST_CONCEPT_ASSERT((PropertyGraphConcept<Graph, Edge, edge_weight_t>));

  auto W = get(edge_weight, G);
  return build_path_from(path, G, W);
}

} // namespace details

/**
 * Construct a sequence of Path from a graph `G` a pair of source-target
 * vertices a multi_predecessor_map holding alternative paths from `s` to `t`.
 *
 * multi_predecessor_map is often filled by *alternative routing* algorithms
 * like onepass_plus(), esx() or penalty().
 *
 * @tparam Graph A model of a graph for which `edge(u, v, G)` is a valid
 *         expression.
 * @tparam WeightMap The weight or "length" of each edge in the graph. The
 *         weights must all be non-negative, and the algorithm will throw a
 *         negative_edge exception is one of the edges is negative. The type
 *         WeightMap must be a model of Readable Property Map. The edge
 *         descriptor type of the graph needs to be usable as the key type for
 *         the weight map. The value type for this map must be the same as the
 *         value type of the distance map.
 * @param G The graph.
 * @param pmap The multi predecessor map of `G`.
 * @param weight The Weight Property Map of `G`.
 * @param s The source vertex.
 * @param t The target vertex.
 * @return A sequence of constructed Path.
 */
template <typename Graph, typename WeightMap,
          typename Vertex = vertex_of_t<Graph>>
std::vector<Path<Graph>> to_paths(Graph const &G,
                                  multi_predecessor_map<Vertex> &pmap,
                                  WeightMap const &weight, Vertex s, Vertex t) {
  auto res = std::vector<Path<Graph>>{};
  auto Q = std::stack<std::pair<int, std::vector<Vertex>>>{};
  for (auto const &[k_th, v] : get(pmap, t)) {
    Q.push({k_th, {t}});
  }

  while (!Q.empty()) {
    auto [k_th, path] = std::move(Q.top());
    Q.pop();
    if (path.front() != s) {
      auto const &preds = get(pmap, path.front());
      // Stack a pair only for predecessor of same k_th
      for (auto const &[k_prime, pred] : preds) {
        if (k_prime == k_th) {
          auto path_prime = path;
          path_prime.insert(path_prime.begin(), pred);
          Q.push({k_prime, std::move(path_prime)});
        }
      }
    } else {
      res.push_back(details::build_path_from(path, G, weight));
    }
  }

  std::sort(res.begin(), res.end(), [](auto const &a, auto const &b) {
    return a.length() < b.length();
  });
  return res;
}

/**
 * Construct a sequence of Path from a graph `G` a pair of source-target
 * vertices a multi_predecessor_map holding alternative paths from `s` to `t`.
 *
 * multi_predecessor_map is often filled by *alternative routing* algorithms
 * like onepass_plus(), esx() or penalty().
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @param G The graph.
 * @param pmap The multi predecessor map of `G`.
 * @param s The source vertex.
 * @param t The target vertex.
 * @return A sequence of constructed Path.
 */
template <typename Graph, typename Vertex = vertex_of_t<Graph>>
std::vector<Path<Graph>> to_paths(Graph const &G,
                                  multi_predecessor_map<Vertex> &pmap, Vertex s,
                                  Vertex t) {
  using namespace boost;
  using Edge = typename graph_traits<Graph>::edge_descriptor;
  BOOST_CONCEPT_ASSERT((PropertyGraphConcept<Graph, Edge, edge_weight_t>));

  auto weight = get(edge_weight, G);
  return to_paths(G, pmap, weight, s, t);
}
} // namespace arlib

#endif